|
42 | 42 | ANALYSIS_ARTIFACT_ROLE_PRIMARY_ANALYSIS, |
43 | 43 | ANALYSIS_ARTIFACT_ROLE_REQUESTED_OUTPUT, |
44 | 44 | ANALYSIS_ARTIFACT_ROLE_SUPPORTING_OUTPUT, |
| 45 | + ANALYSIS_DELIVERABLE_MAX_ARTIFACT_ID_LENGTH, |
| 46 | + ANALYSIS_DELIVERABLE_MAX_ARTIFACTS, |
45 | 47 | build_analysis_deliverable_contract, |
46 | 48 | is_analysis_internal_lineage_field, |
47 | 49 | project_structured_deliverable_row, |
@@ -6855,6 +6857,17 @@ def _normalize_tabular_run_planner_metadata(planner_metadata): |
6855 | 6857 | 'action_mode': str(deliverable_contract.get('action_mode') or '').strip().lower()[:40], |
6856 | 6858 | 'analysis_required': bool(deliverable_contract.get('analysis_required')), |
6857 | 6859 | 'primary_artifact_role': str(deliverable_contract.get('primary_artifact_role') or '').strip().lower()[:80], |
| 6860 | + 'requested_artifacts': [ |
| 6861 | + { |
| 6862 | + 'artifact_id': str(artifact.get('artifact_id') or '').strip()[:ANALYSIS_DELIVERABLE_MAX_ARTIFACT_ID_LENGTH], |
| 6863 | + 'role': str(artifact.get('role') or '').strip().lower()[:40], |
| 6864 | + 'format': str(artifact.get('format') or '').strip().lower()[:20], |
| 6865 | + 'required': bool(artifact.get('required', True)), |
| 6866 | + 'request_order': _safe_int(artifact.get('request_order'), default=0, minimum=0), |
| 6867 | + } |
| 6868 | + for artifact in list(deliverable_contract.get('requested_artifacts') or [])[:ANALYSIS_DELIVERABLE_MAX_ARTIFACTS] |
| 6869 | + if isinstance(artifact, dict) and str(artifact.get('artifact_id') or '').strip() |
| 6870 | + ], |
6858 | 6871 | 'public_output_schema': [ |
6859 | 6872 | str(field_name or '').strip() |
6860 | 6873 | for field_name in list(deliverable_contract.get('public_output_schema') or [])[:TABULAR_GENERATION_PLAN_MAX_FIELDS] |
@@ -8689,6 +8702,33 @@ def _build_or_update_artifact_set_manifest(run): |
8689 | 8702 | 'rollback_state': str(existing_manifest.get('rollback_state') or '').strip().lower()[:40], |
8690 | 8703 | 'members': members, |
8691 | 8704 | } |
| 8705 | + if ( |
| 8706 | + str((run or {}).get('status') or '').strip().lower() == TABULAR_EXPORT_STATUS_COMPLETED |
| 8707 | + and lifecycle_state != TABULAR_ARTIFACT_SET_LIFECYCLE_COMPLETED |
| 8708 | + ): |
| 8709 | + log_event( |
| 8710 | + '[TABULAR_GENERATED_OUTPUT] Artifact set stuck below completed lifecycle on a completed run', |
| 8711 | + { |
| 8712 | + 'run_id': run.get('id'), |
| 8713 | + 'conversation_id': run.get('conversation_id'), |
| 8714 | + 'task_type': manifest.get('task_type'), |
| 8715 | + 'persisted_lifecycle_state': str(existing_manifest.get('lifecycle_state') or ''), |
| 8716 | + 'recomputed_lifecycle_state': lifecycle_state, |
| 8717 | + 'validation_state': manifest.get('validation_state'), |
| 8718 | + 'validation_report': existing_manifest.get('validation_report'), |
| 8719 | + 'members': [ |
| 8720 | + { |
| 8721 | + 'member_id': member.get('member_id'), |
| 8722 | + 'role': member.get('role'), |
| 8723 | + 'lifecycle_state': member.get('lifecycle_state'), |
| 8724 | + 'validation_state': member.get('validation_state'), |
| 8725 | + 'has_artifact_message_id': bool(member.get('artifact_message_id')), |
| 8726 | + } |
| 8727 | + for member in members |
| 8728 | + ], |
| 8729 | + }, |
| 8730 | + level=logging.WARNING, |
| 8731 | + ) |
8692 | 8732 | return manifest |
8693 | 8733 |
|
8694 | 8734 |
|
@@ -8738,13 +8778,33 @@ def _publish_artifact_set_members(run, published_member_ids): |
8738 | 8778 | ] |
8739 | 8779 | deliverable_contract = _get_tabular_run_deliverable_contract(run) |
8740 | 8780 | artifact_set_valid = True |
| 8781 | + validation_report = None |
8741 | 8782 | if deliverable_contract: |
8742 | 8783 | validation_report = validate_analysis_artifact_set(deliverable_contract, validation_artifacts) |
8743 | 8784 | artifact_set_valid = validation_report.valid |
8744 | 8785 | manifest['validation_state'] = 'validated' if validation_report.valid else 'invalid' |
8745 | 8786 | manifest['validation_report'] = validation_report.to_dict() |
8746 | 8787 | else: |
8747 | 8788 | manifest['validation_state'] = 'validated' |
| 8789 | + log_event( |
| 8790 | + '[TABULAR_GENERATED_OUTPUT] Artifact set publication validation', |
| 8791 | + { |
| 8792 | + 'run_id': run.get('id'), |
| 8793 | + 'conversation_id': run.get('conversation_id'), |
| 8794 | + 'task_type': _normalize_tabular_run_task_type(run.get('task_type')), |
| 8795 | + 'published_member_ids': sorted(published_ids), |
| 8796 | + 'has_deliverable_contract': bool(deliverable_contract), |
| 8797 | + 'artifact_set_valid': artifact_set_valid, |
| 8798 | + 'reason_codes': list(validation_report.reason_codes) if validation_report else [], |
| 8799 | + 'counts': dict(validation_report.counts) if validation_report else {}, |
| 8800 | + 'validation_artifacts': validation_artifacts, |
| 8801 | + 'expected_artifact_ids': [ |
| 8802 | + artifact.get('artifact_id') |
| 8803 | + for artifact in list((deliverable_contract or {}).get('requested_artifacts') or []) |
| 8804 | + ], |
| 8805 | + }, |
| 8806 | + level=logging.INFO, |
| 8807 | + ) |
8748 | 8808 | manifest['lifecycle_state'] = ( |
8749 | 8809 | TABULAR_ARTIFACT_SET_LIFECYCLE_COMPLETED |
8750 | 8810 | if artifact_set_valid |
@@ -10946,7 +11006,9 @@ def queue_tabular_generated_output_run( |
10946 | 11006 | 'planner_started_at': None, |
10947 | 11007 | 'planner_completed_at': None, |
10948 | 11008 | 'processed_rows': 0, |
10949 | | - 'output_schema': contract_internal_checkpoint_schema or None, |
| 11009 | + # Only lock the schema up front when real output columns are already known; otherwise |
| 11010 | + # defer to batch-1 discovery instead of validating against a lineage-only placeholder. |
| 11011 | + 'output_schema': contract_internal_checkpoint_schema if contract_public_output_schema else None, |
10950 | 11012 | 'public_output_schema': contract_public_output_schema, |
10951 | 11013 | 'internal_checkpoint_schema': contract_internal_checkpoint_schema, |
10952 | 11014 | 'lineage_schema': [ |
@@ -11103,17 +11165,16 @@ def check_due_tabular_generated_output_runs_once(limit=None): |
11103 | 11165 | 'reason': f"{candidate.get('reason')}; claim or processing did not start", |
11104 | 11166 | }) |
11105 | 11167 |
|
11106 | | - if scanned_candidates or candidates: |
11107 | | - log_event( |
11108 | | - '[TABULAR_GENERATED_OUTPUT] Background scheduler scan result', |
11109 | | - { |
11110 | | - 'scanned_count': len(scanned_candidates), |
11111 | | - 'candidate_count': len(candidates), |
11112 | | - 'status_counts': status_counts, |
11113 | | - 'processed_run_ids': processed, |
11114 | | - 'processed_count': len(processed), |
11115 | | - 'skipped': skipped[:10], |
11116 | | - }, |
11117 | | - debug_only=True, |
11118 | | - ) |
| 11168 | + log_event( |
| 11169 | + '[TABULAR_GENERATED_OUTPUT] Background scheduler scan result', |
| 11170 | + { |
| 11171 | + 'scanned_count': len(scanned_candidates), |
| 11172 | + 'candidate_count': len(candidates), |
| 11173 | + 'status_counts': status_counts, |
| 11174 | + 'processed_run_ids': processed, |
| 11175 | + 'processed_count': len(processed), |
| 11176 | + 'skipped': skipped[:10], |
| 11177 | + }, |
| 11178 | + debug_only=True, |
| 11179 | + ) |
11119 | 11180 | return processed |
0 commit comments