@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
- package/skills/cosmic-build/SKILL.md +24 -9
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
- package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
- package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
- package/skills/cosmic-build/references/compile-guide.md +36 -1
- package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
- package/skills/cosmic-build/references/delivery.md +16 -2
- package/skills/cosmic-build/references/execution-contract.md +15 -3
- package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
- package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
- package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
- package/skills/cosmic-build/references/report-filter-contract.md +53 -15
- package/skills/cosmic-build/references/sdk-inspection.md +4 -1
- package/skills/cosmic-build/references/type-registry.json +26 -17
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
- package/skills/cosmic-build/scripts/_http_client.py +3 -0
- package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
- package/skills/cosmic-build/scripts/configure_list.py +112 -0
- package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
- package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
- package/skills/cosmic-build/scripts/execution_binding.py +25 -0
- package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
- package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
- package/skills/cosmic-build/scripts/register_plugins.py +27 -10
- package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
- package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
- package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
- package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
- package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
- package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
- package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
- package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
- package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
- package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
- package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
- package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
- package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
- package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
- package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
- package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
- package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
- package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
- package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
- package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
- package/skills/cosmic-build/scripts/test_environment.py +67 -1
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
- package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
- package/skills/cosmic-dev-best-practices/references/README.md +2 -2
- package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
- package/skills/erp-analysis/references/delivery-contract.md +2 -1
- package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
|
@@ -66,7 +66,28 @@ def describe(value: object, pointer: str) -> dict:
|
|
|
66
66
|
return result
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def
|
|
69
|
+
def validate_input_files(document: dict) -> None:
|
|
70
|
+
"""准备失败后,旧成功快照不能掩盖已经变化或丢失的输入。"""
|
|
71
|
+
if not isinstance(document, dict):
|
|
72
|
+
raise QueryError("HANDOFF_INPUT_INVALID", "handoff must be an object")
|
|
73
|
+
records = document.get("inputFiles", [])
|
|
74
|
+
if not isinstance(records, list):
|
|
75
|
+
raise QueryError("HANDOFF_INPUT_INVALID", "inputFiles must be an array")
|
|
76
|
+
for record in records:
|
|
77
|
+
if not isinstance(record, dict) or not isinstance(record.get("path"), str) or not Path(record["path"]).is_absolute():
|
|
78
|
+
raise QueryError("HANDOFF_INPUT_INVALID", "inputFiles must contain absolute paths and hashes")
|
|
79
|
+
if not isinstance(record.get("sha256"), str) or len(record["sha256"]) != 64:
|
|
80
|
+
raise QueryError("HANDOFF_INPUT_INVALID", "inputFiles must contain SHA256 hashes")
|
|
81
|
+
file = Path(record["path"])
|
|
82
|
+
try:
|
|
83
|
+
digest = hashlib.sha256(file.read_bytes()).hexdigest() if file.is_file() else None
|
|
84
|
+
except OSError as exc:
|
|
85
|
+
raise QueryError("HANDOFF_INPUT_STALE", f"Prepared input is unreadable: {file}; prepare again") from exc
|
|
86
|
+
if digest != record["sha256"]:
|
|
87
|
+
raise QueryError("HANDOFF_INPUT_STALE", f"Prepared input changed or disappeared: {file}; prepare the handoff again before coding or archiving")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_handoff(filename: str, *, validate_inputs: bool = True) -> tuple[Path, dict]:
|
|
70
91
|
file = Path(filename)
|
|
71
92
|
if not file.is_absolute() or file.suffix.lower() != ".json":
|
|
72
93
|
raise QueryError("HANDOFF_PATH_INVALID", "handoff must be an absolute JSON path returned by preparation")
|
|
@@ -78,16 +99,145 @@ def load_handoff(filename: str) -> tuple[Path, dict]:
|
|
|
78
99
|
raise QueryError("HANDOFF_INVALID", "file is not a successful plugin handoff")
|
|
79
100
|
if not isinstance(document.get("handoffPath"), str) or Path(document["handoffPath"]).resolve() != file.resolve():
|
|
80
101
|
raise QueryError("HANDOFF_PATH_MISMATCH", "handoffPath does not match the selected file")
|
|
102
|
+
if validate_inputs:
|
|
103
|
+
validate_input_files(document)
|
|
81
104
|
return file, document
|
|
82
105
|
|
|
83
106
|
|
|
107
|
+
def observed_source_gate(handoff: dict) -> dict:
|
|
108
|
+
"""Project the current local gate, never an unfulfilled preparation request."""
|
|
109
|
+
from plugin_source_gate import gate_path
|
|
110
|
+
|
|
111
|
+
requested = handoff.get("sourceGate")
|
|
112
|
+
if not isinstance(requested, dict):
|
|
113
|
+
return {}
|
|
114
|
+
if not handoff.get("projectRoot") or not handoff.get("className"):
|
|
115
|
+
raise QueryError("HANDOFF_SOURCE_GATE_INVALID", "source gate requires projectRoot and className")
|
|
116
|
+
expected = Path(gate_path(handoff["projectRoot"], handoff["className"]))
|
|
117
|
+
if not requested.get("path") or Path(requested["path"]).resolve() != expected.resolve():
|
|
118
|
+
raise QueryError("HANDOFF_SOURCE_GATE_INVALID", "source gate path does not match this class")
|
|
119
|
+
try:
|
|
120
|
+
content = expected.read_text(encoding="utf-8").strip() if expected.is_file() else "missing"
|
|
121
|
+
except (OSError, UnicodeError) as error:
|
|
122
|
+
raise QueryError("HANDOFF_SOURCE_GATE_UNAVAILABLE", "cannot read this class's source gate") from error
|
|
123
|
+
state, _, session = content.partition(":")
|
|
124
|
+
return {"sourceGate": {**requested, "state": state, "sessionId": session or None,
|
|
125
|
+
"verified": state in ("coding", "source_ready"),
|
|
126
|
+
"evidenceScope": "Local marker only; session binding still applies to edits, and this is not a compile receipt."}}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def report_coding_context(document: dict, operation: str) -> dict:
|
|
130
|
+
"""报表契约独立于运行时表单实体图,保留真实类型证据和精确查询指针。"""
|
|
131
|
+
from compile_plugin_source import inspect_compile_environment
|
|
132
|
+
|
|
133
|
+
handoff = document["handoff"]
|
|
134
|
+
contract = handoff.get("reportContract")
|
|
135
|
+
evidence = handoff["codingEvidence"]
|
|
136
|
+
if not isinstance(contract, dict) or contract.get("formNumber") != handoff.get("formNumber"):
|
|
137
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "a reportContract for this exact report is required")
|
|
138
|
+
sections = ("dataSetColumns", "dataSetColumnTypes", "columnPresentation", "filterEnforcement", "filterValueContracts")
|
|
139
|
+
if "defaultSorts" in contract:
|
|
140
|
+
sections = (*sections, "defaultSorts")
|
|
141
|
+
if "defaultSortPolicy" in contract and not isinstance(contract["defaultSortPolicy"], dict):
|
|
142
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "defaultSortPolicy must be an object")
|
|
143
|
+
if any(not isinstance(contract.get(key), list) for key in sections):
|
|
144
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "report contract sections must be arrays")
|
|
145
|
+
if any(not isinstance(row, dict) for key in sections[1:] for row in contract[key]) or any(not isinstance(row, str) for row in contract[sections[0]]):
|
|
146
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "report columns must be strings and contract records must be objects")
|
|
147
|
+
if not isinstance(evidence.get("apiContracts", []), list) or any(not isinstance(row, dict) for row in evidence.get("apiContracts", [])):
|
|
148
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "apiContracts must be an array of objects")
|
|
149
|
+
if not isinstance(handoff.get("specialized", {}), dict) or not isinstance(handoff.get("targetSdk", {}), dict):
|
|
150
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "specialized and targetSdk must be objects")
|
|
151
|
+
if not isinstance(handoff.get("specialized", {}).get("externalContracts", []), list):
|
|
152
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "externalContracts must be an array")
|
|
153
|
+
result = {
|
|
154
|
+
"status": True, "contextMode": operation, "domain": "report",
|
|
155
|
+
"handoffPath": document["handoffPath"],
|
|
156
|
+
"handoffSha256": hashlib.sha256(Path(document["handoffPath"]).read_bytes()).hexdigest(),
|
|
157
|
+
**{key: handoff[key] for key in ("appRoot", "projectRoot", "formNumber", "type", "className",
|
|
158
|
+
"sourcePath", "fqcn", "ownershipMode", "existingMethods", "coreLogic", "sourceGate") if key in handoff},
|
|
159
|
+
**observed_source_gate(handoff),
|
|
160
|
+
"reportContract": {
|
|
161
|
+
"path": "/handoff/reportContract",
|
|
162
|
+
**{key: contract[key] for key in ("schemaVersion", "formNumber", "source", "pluginTypes", "permissionFilterChannel", "defaultSortPolicy") if key in contract},
|
|
163
|
+
"sections": {key: {"path": f"/handoff/reportContract/{key}", "count": len(contract[key])} for key in sections},
|
|
164
|
+
**{key: contract[key] for key in sections},
|
|
165
|
+
},
|
|
166
|
+
"externalContracts": {"path": "/handoff/specialized/externalContracts",
|
|
167
|
+
"entities": [entry.get("entity") for entry in handoff.get("specialized", {}).get("externalContracts", [])
|
|
168
|
+
if isinstance(entry, dict)]},
|
|
169
|
+
"codingEvidence": {
|
|
170
|
+
**{key: evidence[key] for key in ("codingEntry", "signatureTypes", "apiLookup", "sdkLookup", "scenes", "standards", "specializedReferences") if key in evidence},
|
|
171
|
+
**({"guide": evidence["guide"]} if isinstance(evidence.get("guide"), str) and len(evidence["guide"]) <= 512 else {}),
|
|
172
|
+
"apiContracts": [{"class": item.get("class"), "path": f"/handoff/codingEvidence/apiContracts/{index}"}
|
|
173
|
+
for index, item in enumerate(evidence.get("apiContracts", []))],
|
|
174
|
+
},
|
|
175
|
+
"sdkUsage": handoff.get("sdkUsage", []),
|
|
176
|
+
"targetSdk": {**{key: (handoff.get("targetSdk") or {}).get(key) for key in ("status", "evidenceScope")},
|
|
177
|
+
"path": "/handoff/targetSdk"},
|
|
178
|
+
"stateWarnings": handoff.get("stateWarnings", []),
|
|
179
|
+
"queryRootEvidence": {"boundary": "Report columns are DataSet output, not persisted query roots; external entities require their own verified contracts."},
|
|
180
|
+
"evidenceScope": "Design and source facts only; compilation, deployment, filter value classes and runtime behavior are unverified.",
|
|
181
|
+
}
|
|
182
|
+
if not result["targetSdk"]["status"]:
|
|
183
|
+
result["targetSdk"] = {"status": "not-provided"}
|
|
184
|
+
if operation == "coding-start":
|
|
185
|
+
result["buildEnvironment"] = inspect_compile_environment(handoff["projectRoot"]) if handoff.get("projectRoot") else {"status": "not-provided"}
|
|
186
|
+
limit = MAX_CODING_START_BYTES if operation == "coding-start" else MAX_CODING_CONTEXT_BYTES
|
|
187
|
+
if len(encoded(result).encode("utf-8")) > limit:
|
|
188
|
+
for key in sections:
|
|
189
|
+
result["reportContract"].pop(key)
|
|
190
|
+
result["reportContract"]["detailsOmitted"] = True
|
|
191
|
+
for key in ("sdkUsage", "existingMethods", "coreLogic"):
|
|
192
|
+
if key in result:
|
|
193
|
+
result[key + "Path"] = f"/handoff/{key}"
|
|
194
|
+
result.pop(key)
|
|
195
|
+
result["externalContracts"] = {"path": "/handoff/specialized/externalContracts"}
|
|
196
|
+
result["codingEvidence"] = {"path": "/handoff/codingEvidence"}
|
|
197
|
+
result["reportContract"].pop("pluginTypes", None)
|
|
198
|
+
result["stateWarnings"] = {"path": "/handoff/stateWarnings"}
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
|
|
84
202
|
def query(document: dict, args: argparse.Namespace) -> dict:
|
|
203
|
+
if args.operation == "runtime-context":
|
|
204
|
+
from test_environment import plugin_runtime_context
|
|
205
|
+
handoff = document["handoff"]
|
|
206
|
+
if not handoff.get("appRoot") or not handoff.get("formNumber"):
|
|
207
|
+
raise QueryError("HANDOFF_RUNTIME_TARGET_MISSING", "runtime-context requires appRoot and formNumber")
|
|
208
|
+
return {"status": True, "contextMode": "runtime-context", "runtime": plugin_runtime_context(
|
|
209
|
+
handoff["appRoot"], handoff["formNumber"], resolve_entry=getattr(args, "resolve_entry", False))}
|
|
85
210
|
if args.operation in ("coding-context", "coding-start"):
|
|
86
211
|
handoff = document["handoff"]
|
|
87
212
|
graph = handoff.get("formGraph")
|
|
88
213
|
evidence = handoff.get("codingEvidence")
|
|
89
|
-
if not isinstance(
|
|
90
|
-
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "
|
|
214
|
+
if not isinstance(evidence, dict):
|
|
215
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "codingEvidence is required")
|
|
216
|
+
needs = handoff.get("metadataNeeds")
|
|
217
|
+
if needs is not None and not (handoff.get("type") in ("reportlist", "report-plugin", "reportform", "reporttreelist") and isinstance(handoff.get("reportContract"), dict)):
|
|
218
|
+
if not isinstance(needs, list) or any(not isinstance(item, str) or item not in {"form", "list", "entity", "operate"} for item in needs):
|
|
219
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "metadataNeeds is invalid")
|
|
220
|
+
if "entity" in needs and not isinstance(graph, dict):
|
|
221
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "selected entity metadata is missing")
|
|
222
|
+
if ({"form", "list"} & set(needs)) and not isinstance(handoff.get("uiContext"), dict):
|
|
223
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "selected page metadata is missing")
|
|
224
|
+
if "operate" in needs and "operationContracts" not in handoff and "availableOperations" not in handoff:
|
|
225
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "selected operation metadata is missing")
|
|
226
|
+
if handoff.get("type") in ("reportlist", "report-plugin", "reportform", "reporttreelist") and isinstance(handoff.get("reportContract"), dict):
|
|
227
|
+
return report_coding_context(document, args.operation)
|
|
228
|
+
ui = handoff.get("uiContext")
|
|
229
|
+
source = handoff.get("sourceContext")
|
|
230
|
+
operation_context = handoff.get("operationContext")
|
|
231
|
+
operation_only = (isinstance(operation_context, dict) and operation_context.get("source") == "listOperations"
|
|
232
|
+
and operation_context.get("formNumber") == handoff.get("formNumber")
|
|
233
|
+
and handoff.get("type") == "operation" and bool(handoff.get("operationKeys")))
|
|
234
|
+
source_only = (isinstance(source, dict) and source.get("scope") == "source-only"
|
|
235
|
+
and source.get("sourcePath") == handoff.get("sourcePath")
|
|
236
|
+
and handoff.get("ownershipMode") == "modify" and handoff.get("type") != "operation")
|
|
237
|
+
if not isinstance(graph, dict) and not source_only and not operation_only and not (isinstance(ui, dict) and ui.get("formNumber") == handoff.get("formNumber") and ui.get("source") == "getFormMetadata"):
|
|
238
|
+
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "verified page context, source context or formGraph is required")
|
|
239
|
+
if not isinstance(graph, dict):
|
|
240
|
+
graph = {"entities": []}
|
|
91
241
|
if not isinstance(handoff.get("relatedForms", []), list) or not isinstance(evidence.get("apiContracts", []), list):
|
|
92
242
|
raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "relatedForms and apiContracts must be arrays")
|
|
93
243
|
|
|
@@ -110,12 +260,20 @@ def query(document: dict, args: argparse.Namespace) -> dict:
|
|
|
110
260
|
start = {
|
|
111
261
|
"status": True, "contextMode": "coding-start", "handoffPath": document["handoffPath"],
|
|
112
262
|
"handoffSha256": hashlib.sha256(Path(document["handoffPath"]).read_bytes()).hexdigest(),
|
|
113
|
-
|
|
114
|
-
"
|
|
263
|
+
"compileCommand": [sys.executable, str(Path(__file__).with_name("compile_plugin_source.py")), "--handoff", document["handoffPath"]],
|
|
264
|
+
**({"runtimeContextCommand": [sys.executable, str(Path(__file__).resolve()), "runtime-context", "--handoff", document["handoffPath"]]} if not source_only else {}),
|
|
265
|
+
**({"testCommand": [sys.executable, str(Path(__file__).with_name("run_java_tests.py")), "--project-root", handoff["projectRoot"]]} if handoff.get("projectRoot") else {}),
|
|
266
|
+
**pick(handoff, ("appRoot", "projectRoot", "formNumber", "className", "sourcePath", "fqcn", "operationKeys", "coreLogic", "sourceGate", "metadataNeeds")),
|
|
267
|
+
**observed_source_gate(handoff),
|
|
268
|
+
**({"uiContext": ui} if isinstance(ui, dict) else {}),
|
|
269
|
+
**({"sourceContext": source} if source_only else {}),
|
|
270
|
+
**({"operationContext": operation_context} if operation_only else {}),
|
|
271
|
+
**({"entities": [{**pick(entity, ("entityKey", "role", "collectionProperty", "collectionPath")),
|
|
115
272
|
"fieldsPath": f"/handoff/formGraph/entities/{index}/fields",
|
|
116
|
-
"fields": [pick(field, ("fieldKey", "fieldName", "fieldType", "javaType", "valueAccessor", "idAccessor"))
|
|
273
|
+
"fields": [pick(field, ("fieldKey", "fieldName", "fieldType", "javaType", "valueAccessor", "idAccessor", "timeValueUnit", "note"))
|
|
117
274
|
for field in entity.get("fields", [])]}
|
|
118
|
-
for index, entity in enumerate(graph["entities"])]
|
|
275
|
+
for index, entity in enumerate(graph["entities"])]}
|
|
276
|
+
if isinstance(handoff.get("formGraph"), dict) else {"entityEvidenceScope": "Entity fields and query roots were not requested and are unverified."}),
|
|
119
277
|
"relatedForms": [{**pick(form, ("formNumber", "formName", "metadataVersion")),
|
|
120
278
|
"path": f"/handoff/relatedForms/{index}"}
|
|
121
279
|
for index, form in enumerate(handoff.get("relatedForms", []))],
|
|
@@ -127,24 +285,40 @@ def query(document: dict, args: argparse.Namespace) -> dict:
|
|
|
127
285
|
for row in handoff.get("availableOperations", [])
|
|
128
286
|
if isinstance(row, dict) and row.get("operationKey") in handoff.get("operationKeys", [])],
|
|
129
287
|
"stateWarnings": handoff.get("stateWarnings", []),
|
|
130
|
-
"stateFields": [pick(field, ("fieldKey", "fieldName", "entityKey", "comboOptions"))
|
|
288
|
+
"stateFields": [pick(field, ("fieldKey", "fieldName", "entityKey", "comboOptions"))
|
|
289
|
+
for field in handoff.get("stateFields", [])
|
|
290
|
+
if field.get("entityKey") in {entity.get("entityKey") for entity in graph["entities"]}],
|
|
291
|
+
"stateFieldsScope": "Main form only; related-form values remain at /handoff/stateFields",
|
|
131
292
|
"buildEnvironment": inspect_compile_environment(handoff["projectRoot"]) if handoff.get("projectRoot") else {"status": "not-provided"},
|
|
132
293
|
"targetSdk": target_sdk(),
|
|
133
|
-
"codingEvidence": {**pick(evidence, ("codingEntry", "operationDispatch", "apiLookup", "sdkLookup")),
|
|
294
|
+
"codingEvidence": {**pick(evidence, ("codingEntry", "operationDispatch", "apiLookup", "sdkLookup", "scenes", "standards", "specializedReferences")),
|
|
134
295
|
"signatureTypes": [pick(item, ("class", "import")) for item in evidence.get("signatureTypes", [])],
|
|
135
296
|
"signatureTypesPath": "/handoff/codingEvidence/signatureTypes",
|
|
297
|
+
"guideSignatures": evidence.get("guideSignatures", []),
|
|
136
298
|
**({"guide": evidence["guide"]} if isinstance(evidence.get("guide"), str) and len(evidence["guide"]) <= 512 else {}),
|
|
137
|
-
"apiContracts": [{**pick(contract, ("class",)),
|
|
299
|
+
"apiContracts": [{**pick(contract, ("class", "import")),
|
|
138
300
|
"path": f"/handoff/codingEvidence/apiContracts/{index}",
|
|
139
|
-
"members": list(dict.fromkeys(member["name"] for member in contract.get("members", []) if "name" in member))
|
|
301
|
+
"members": list(dict.fromkeys(member["name"] for member in contract.get("members", []) if "name" in member)),
|
|
302
|
+
"signatures": [pick(member, ("name", "signature", "declaredBy", "deprecated")) for member in contract.get("members", [])]}
|
|
140
303
|
for index, contract in enumerate(evidence.get("apiContracts", []))]},
|
|
304
|
+
"specialized": pick(handoff.get("specialized", {}), ("hitModules", "constraints")),
|
|
141
305
|
"remainingContext": {"stateFields": "/handoff/stateFields", "specialized": "/handoff/specialized", "reportContract": "/handoff/reportContract"},
|
|
142
306
|
"evidenceScope": "Startup facts and pointers only; collection membership is not proof of a standalone query root, and source compilation does not establish business coverage",
|
|
143
307
|
}
|
|
144
308
|
if len(encoded(start).encode("utf-8")) > MAX_CODING_START_BYTES:
|
|
145
|
-
for entity in start
|
|
309
|
+
for entity in start.get("entities", []):
|
|
146
310
|
entity["fieldCount"] = len(entity.pop("fields"))
|
|
147
311
|
entity["fieldsOmitted"] = True
|
|
312
|
+
if len(encoded(start).encode("utf-8")) > MAX_CODING_START_BYTES and isinstance(start.get("uiContext"), dict):
|
|
313
|
+
page = start["uiContext"]
|
|
314
|
+
start["uiContext"] = {**pick(page, ("formNumber", "formName", "modelType", "source")),
|
|
315
|
+
"controlsPath": "/handoff/uiContext/controls", "controlCount": len(page.get("controls", [])),
|
|
316
|
+
"pluginsPath": "/handoff/uiContext/plugins", "pluginCount": len(page.get("plugins", []))}
|
|
317
|
+
if len(encoded(start).encode("utf-8")) > MAX_CODING_START_BYTES:
|
|
318
|
+
for contract in start["codingEvidence"]["apiContracts"]:
|
|
319
|
+
contract["signaturesOmitted"] = True
|
|
320
|
+
contract.pop("signatures", None)
|
|
321
|
+
start["codingEvidence"].pop("guideSignatures", None)
|
|
148
322
|
return start
|
|
149
323
|
|
|
150
324
|
def compact_graph(value: dict) -> dict:
|
|
@@ -163,27 +337,31 @@ def query(document: dict, args: argparse.Namespace) -> dict:
|
|
|
163
337
|
],
|
|
164
338
|
}
|
|
165
339
|
|
|
166
|
-
|
|
340
|
+
context = {
|
|
167
341
|
"status": True,
|
|
168
342
|
"contextMode": "coding-context",
|
|
169
343
|
"handoffPath": document["handoffPath"],
|
|
170
344
|
"handoffSha256": hashlib.sha256(Path(document["handoffPath"]).read_bytes()).hexdigest(),
|
|
171
|
-
**pick(handoff, ("appRoot", "projectRoot", "formNumber", "className", "sourcePath", "fqcn", "operationKeys", "coreLogic", "specialized", "sourceGate")),
|
|
172
|
-
|
|
345
|
+
**pick(handoff, ("appRoot", "projectRoot", "formNumber", "className", "sourcePath", "fqcn", "operationKeys", "coreLogic", "specialized", "sourceGate", "metadataNeeds")),
|
|
346
|
+
**observed_source_gate(handoff),
|
|
347
|
+
**({"formGraph": compact_graph(graph)} if isinstance(handoff.get("formGraph"), dict) else {}),
|
|
348
|
+
**({"uiContext": ui} if isinstance(ui, dict) else {}),
|
|
349
|
+
**({"sourceContext": source} if source_only else {}),
|
|
350
|
+
**({"operationContext": operation_context} if operation_only else {}),
|
|
173
351
|
"targetSdk": target_sdk(),
|
|
174
|
-
"queryRootEvidence": {
|
|
352
|
+
**({"queryRootEvidence": {
|
|
175
353
|
"formNumber": graph.get("formNumber"),
|
|
176
354
|
"nonRootEntityKeys": [entity["entityKey"] for entity in graph["entities"]
|
|
177
355
|
if entity.get("role") in ("entries", "subentries") and isinstance(entity.get("entityKey"), str)],
|
|
178
356
|
"boundary": "EntryType and SubEntryType are collection members here, not evidence of standalone QueryServiceHelper roots; runtime query behavior is unverified by this snapshot.",
|
|
179
|
-
},
|
|
357
|
+
}} if isinstance(handoff.get("formGraph"), dict) else {"entityEvidenceScope": "Entity fields and query roots were not requested and are unverified."}),
|
|
180
358
|
"relatedForms": [compact_graph(form) for form in handoff.get("relatedForms", [])],
|
|
181
359
|
"operationContracts": handoff.get("operationContracts", []),
|
|
182
360
|
"availableOperations": handoff.get("availableOperations", []),
|
|
183
361
|
"stateFields": handoff.get("stateFields", []),
|
|
184
362
|
"stateWarnings": handoff.get("stateWarnings", []),
|
|
185
363
|
"codingEvidence": {
|
|
186
|
-
**pick(evidence, ("codingEntry", "signatureTypes", "operationDispatch", "apiLookup", "sdkLookup")),
|
|
364
|
+
**pick(evidence, ("codingEntry", "signatureTypes", "operationDispatch", "apiLookup", "sdkLookup", "scenes", "standards", "specializedReferences")),
|
|
187
365
|
**({"guide": evidence["guide"]} if isinstance(evidence.get("guide"), str) and len(evidence["guide"]) <= 512 else {}),
|
|
188
366
|
"apiContracts": [
|
|
189
367
|
{
|
|
@@ -194,6 +372,12 @@ def query(document: dict, args: argparse.Namespace) -> dict:
|
|
|
194
372
|
],
|
|
195
373
|
},
|
|
196
374
|
}
|
|
375
|
+
if len(encoded(context).encode("utf-8")) > MAX_CODING_CONTEXT_BYTES and isinstance(context.get("uiContext"), dict):
|
|
376
|
+
page = context["uiContext"]
|
|
377
|
+
context["uiContext"] = {**pick(page, ("formNumber", "formName", "modelType", "source")),
|
|
378
|
+
"controlsPath": "/handoff/uiContext/controls", "controlCount": len(page.get("controls", [])),
|
|
379
|
+
"pluginsPath": "/handoff/uiContext/plugins", "pluginCount": len(page.get("plugins", []))}
|
|
380
|
+
return context
|
|
197
381
|
value = resolve_pointer(document, args.path)
|
|
198
382
|
if args.operation == "get":
|
|
199
383
|
return {"status": True, "path": args.path, "value": value}
|
|
@@ -216,22 +400,25 @@ def query(document: dict, args: argparse.Namespace) -> dict:
|
|
|
216
400
|
|
|
217
401
|
def main() -> int:
|
|
218
402
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
219
|
-
parser.add_argument("operation", choices=("index", "find", "get", "coding-context", "coding-start"))
|
|
403
|
+
parser.add_argument("operation", choices=("index", "find", "get", "coding-context", "coding-start", "runtime-context"))
|
|
220
404
|
parser.add_argument("--handoff", required=True, help="Exact handoffPath returned by prepare_plugin_handoff.py")
|
|
221
405
|
parser.add_argument("--path", default="", help="JSON pointer, such as /handoff/formGraph/entities")
|
|
222
406
|
parser.add_argument("--offset", type=int, default=0, help="First child for index")
|
|
223
407
|
parser.add_argument("--limit", type=int, default=DEFAULT_PAGE_SIZE, help="Children per index page")
|
|
224
408
|
parser.add_argument("--field", help="Direct object field for exact find")
|
|
225
409
|
parser.add_argument("--equals", help="Exact value for find")
|
|
410
|
+
parser.add_argument("--resolve-entry", action="store_true", help="Resolve a missing runtime entry through the bound platform")
|
|
226
411
|
args = parser.parse_args()
|
|
227
412
|
try:
|
|
413
|
+
if args.resolve_entry and args.operation != "runtime-context":
|
|
414
|
+
raise QueryError("HANDOFF_RUNTIME_ARGUMENT_INVALID", "--resolve-entry applies only to runtime-context")
|
|
228
415
|
if args.offset < 0 or not 1 <= args.limit <= MAX_PAGE_SIZE:
|
|
229
416
|
raise QueryError("HANDOFF_PAGE_INVALID", "offset must be nonnegative and limit must be 1–50")
|
|
230
417
|
if args.operation == "find" and (not args.field or args.equals is None or not args.path):
|
|
231
418
|
raise QueryError("HANDOFF_FIND_INVALID", "find requires --path, --field and --equals")
|
|
232
419
|
if args.operation != "find" and (args.field is not None or args.equals is not None):
|
|
233
420
|
raise QueryError("HANDOFF_FIND_INVALID", "--field and --equals apply only to find")
|
|
234
|
-
if args.operation in ("coding-context", "coding-start") and (args.path or args.offset or args.limit != DEFAULT_PAGE_SIZE):
|
|
421
|
+
if args.operation in ("coding-context", "coding-start", "runtime-context") and (args.path or args.offset or args.limit != DEFAULT_PAGE_SIZE):
|
|
235
422
|
raise QueryError("HANDOFF_CODING_CONTEXT_ARGUMENT_INVALID", "coding-context accepts only --handoff")
|
|
236
423
|
_, document = load_handoff(args.handoff)
|
|
237
424
|
response = query(document, args)
|
|
@@ -78,11 +78,16 @@ def _load_type_owners(root_dir):
|
|
|
78
78
|
_REGISTRARS = {
|
|
79
79
|
'report-pipeline': (
|
|
80
80
|
'report pipeline (registerReportPlugin)',
|
|
81
|
-
'Type {type} is registered by
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
81
|
+
'Type {type} is not registered by this API. The report pipeline registers report plugins '
|
|
82
|
+
'by slot: a reportlist plugin through registerReportPlugin into the report list\'s '
|
|
83
|
+
'ReportQueryPlugin; a reporttreelist plugin goes into the left tree\'s ReportTreeAp.ReportPlugin '
|
|
84
|
+
'through setReportProperties --target tree, once a user has added the report tree control. '
|
|
85
|
+
'A reportform plugin is the report form\'s own form plugin and '
|
|
86
|
+
'is registered by this script instead, with targetType BILL_FORM. This script neither '
|
|
87
|
+
'registered nor verified a {type} plugin: '
|
|
88
|
+
'plugin/queryEditable does not return report query plugins. The evidence is the report '
|
|
89
|
+
'read-back — getReportSchema with design-state evidence, where <ReportQueryPlugin> carries '
|
|
90
|
+
'the registered class, and setReportProperties --target tree for the left tree.',
|
|
86
91
|
),
|
|
87
92
|
'platform-class-discovery': (
|
|
88
93
|
'platform class discovery',
|
|
@@ -404,9 +409,9 @@ def main():
|
|
|
404
409
|
code, _, message = str(exc).partition(':')
|
|
405
410
|
print(json.dumps(_failure(code, message.strip(), writeAttempted=False), ensure_ascii=False))
|
|
406
411
|
return 2
|
|
407
|
-
server = _load_config(root_dir)
|
|
412
|
+
server = _load_config(root_dir) if any(type_map.get(c.get('type')) for c in candidates) else None
|
|
408
413
|
|
|
409
|
-
if not server:
|
|
414
|
+
if not server and any(type_map.get(c.get('type')) for c in candidates):
|
|
410
415
|
print(json.dumps(_failure(
|
|
411
416
|
'PLUGIN_SERVER_CONFIG_UNAVAILABLE',
|
|
412
417
|
'~/.kd/config.json not configured or load failed (kd_env_loader)',
|
|
@@ -446,7 +451,9 @@ def main():
|
|
|
446
451
|
results.append({
|
|
447
452
|
'className': c['className'],
|
|
448
453
|
'type': plugin_type,
|
|
449
|
-
'success':
|
|
454
|
+
'success': False,
|
|
455
|
+
'status': 'routed',
|
|
456
|
+
'deferred': True,
|
|
450
457
|
# 与本脚本真正执行过的注册区分开,避免调用方把 success=true 读成
|
|
451
458
|
# 「已在此处注册并校验」。
|
|
452
459
|
'registeredHere': False,
|
|
@@ -513,6 +520,9 @@ def main():
|
|
|
513
520
|
'success': False,
|
|
514
521
|
'message': str(e),
|
|
515
522
|
}
|
|
523
|
+
result['registeredHere'] = result['success']
|
|
524
|
+
result['verifiedHere'] = False
|
|
525
|
+
result['formNumber'] = form_number
|
|
516
526
|
if not result['success']:
|
|
517
527
|
has_failure = True
|
|
518
528
|
else:
|
|
@@ -543,13 +553,20 @@ def main():
|
|
|
543
553
|
if verification['status'] != 'pass':
|
|
544
554
|
has_failure = True
|
|
545
555
|
|
|
556
|
+
if verification and verification.get('checks'):
|
|
557
|
+
for result in results:
|
|
558
|
+
result['verifiedHere'] = any(check.get('verified') and _registration_identity(result) ==
|
|
559
|
+
{key: check[key] for key in ('formNumber', 'className', 'targetType', 'operationKey')}
|
|
560
|
+
for check in verification['checks'])
|
|
561
|
+
deferred = sum(1 for result in results if result.get('deferred'))
|
|
546
562
|
output = {
|
|
547
|
-
'status': 'failed' if has_failure else 'success',
|
|
563
|
+
'status': 'failed' if has_failure else 'routed' if deferred == len(results) else 'partial' if deferred else 'success',
|
|
548
564
|
'results': results,
|
|
549
565
|
'summary': {
|
|
550
566
|
'total': len(results),
|
|
551
567
|
'success': sum(1 for r in results if r['success']),
|
|
552
|
-
'failed': sum(1 for r in results if not r['success']),
|
|
568
|
+
'failed': sum(1 for r in results if not r['success'] and not r.get('deferred')),
|
|
569
|
+
**({'deferred': deferred} if deferred else {}),
|
|
553
570
|
},
|
|
554
571
|
}
|
|
555
572
|
if source_checks:
|
|
@@ -17,6 +17,7 @@ import json
|
|
|
17
17
|
import os
|
|
18
18
|
import subprocess
|
|
19
19
|
import sys
|
|
20
|
+
from datetime import datetime, timezone
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Any
|
|
22
23
|
from urllib.parse import parse_qs, urlparse
|
|
@@ -26,10 +27,12 @@ if str(SCRIPT_DIR) not in sys.path:
|
|
|
26
27
|
sys.path.insert(0, str(SCRIPT_DIR))
|
|
27
28
|
|
|
28
29
|
from preview_url import PreviewUrlError, get_form_name, get_preview_url
|
|
30
|
+
from _http_client import _is_transient
|
|
29
31
|
from test_environment import export_test_environment
|
|
30
32
|
|
|
31
33
|
_DYM_BACKGROUND_NOTICE = None
|
|
32
34
|
_TEST_ENVIRONMENT_HANDOFF = None
|
|
35
|
+
_PREVIEW_EVIDENCE = None
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class ReportPluginSourceMissing(Exception):
|
|
@@ -224,6 +227,10 @@ def undelivered_report_plugins(
|
|
|
224
227
|
except (OSError, ValueError):
|
|
225
228
|
# 契约读不出来,不能当作插件缺失的证据。
|
|
226
229
|
continue
|
|
230
|
+
# 类要按哪种类型交付,由注册它的槽位决定。pluginTypes 出现之前写的契约只注册取数插件。
|
|
231
|
+
plugin_types = contract.get("pluginTypes")
|
|
232
|
+
if not isinstance(plugin_types, dict):
|
|
233
|
+
plugin_types = {}
|
|
227
234
|
for class_name in contract.get("pluginClasses") or []:
|
|
228
235
|
registered = str(class_name or "").strip()
|
|
229
236
|
if not registered:
|
|
@@ -234,6 +241,7 @@ def undelivered_report_plugins(
|
|
|
234
241
|
missing.append({
|
|
235
242
|
"formNumber": number,
|
|
236
243
|
"className": registered,
|
|
244
|
+
"type": str(plugin_types.get(registered) or "reportlist"),
|
|
237
245
|
"expectedSource": "src/main/java/" + registered.replace(".", "/") + ".java",
|
|
238
246
|
"reason": (
|
|
239
247
|
"no Java source recorded in archive.json" if not recorded
|
|
@@ -251,12 +259,13 @@ def _refuse_undelivered(missing: list[dict[str, str]]) -> None:
|
|
|
251
259
|
for item in missing:
|
|
252
260
|
lines.append(
|
|
253
261
|
f" {item['formNumber']} registered {item['className']} "
|
|
254
|
-
f"({item['reason']});
|
|
262
|
+
f"({item['reason']}); deliver it as type {item.get('type') or 'reportlist'}, "
|
|
263
|
+
f"expected source {item['expectedSource']}"
|
|
255
264
|
)
|
|
256
265
|
lines.append(
|
|
257
|
-
" Registration is metadata only. Deliver
|
|
258
|
-
"references/java-plugin-delivery.md as type
|
|
259
|
-
"narrowly, and let it reach archive.json — then render the cards."
|
|
266
|
+
" Registration is metadata only. Deliver each plugin through "
|
|
267
|
+
"references/java-plugin-delivery.md as the type named for it — generate the source, "
|
|
268
|
+
"compile it narrowly, and let it reach archive.json — then render the cards."
|
|
260
269
|
)
|
|
261
270
|
raise ReportPluginSourceMissing("\n".join(lines))
|
|
262
271
|
|
|
@@ -279,14 +288,19 @@ def _join_url(base: str, path: str) -> str:
|
|
|
279
288
|
return base + path
|
|
280
289
|
|
|
281
290
|
|
|
282
|
-
def _archived_preview_urls(artifacts: list[Any]) -> dict[str, str]:
|
|
291
|
+
def _archived_preview_urls(artifacts: list[Any], base_url: str = "") -> dict[str, str]:
|
|
283
292
|
"""formNumber -> 早前成功运行归档的预览链接(getPreviewUrl 失败时兜底)。"""
|
|
284
293
|
urls: dict[str, str] = {}
|
|
285
294
|
for artifact in artifacts:
|
|
286
295
|
if not isinstance(artifact, dict):
|
|
287
296
|
continue
|
|
288
297
|
raw_url = str(artifact.get("url") or "").strip()
|
|
289
|
-
|
|
298
|
+
parsed = urlparse(raw_url)
|
|
299
|
+
base = urlparse(base_url)
|
|
300
|
+
if not raw_url or parsed.scheme not in ("http", "https") or not parsed.netloc or parsed.username or parsed.password:
|
|
301
|
+
continue
|
|
302
|
+
if base_url and (parsed.scheme != base.scheme or parsed.netloc != base.netloc or
|
|
303
|
+
not (parsed.path == base.path or parsed.path.startswith(base.path.rstrip("/") + "/"))):
|
|
290
304
|
continue
|
|
291
305
|
form_number = _artifact_form_number(artifact)
|
|
292
306
|
if form_number:
|
|
@@ -346,8 +360,9 @@ def render(
|
|
|
346
360
|
) -> list[str]:
|
|
347
361
|
global _DYM_BACKGROUND_NOTICE
|
|
348
362
|
_DYM_BACKGROUND_NOTICE = None
|
|
349
|
-
global _TEST_ENVIRONMENT_HANDOFF
|
|
363
|
+
global _TEST_ENVIRONMENT_HANDOFF, _PREVIEW_EVIDENCE
|
|
350
364
|
_TEST_ENVIRONMENT_HANDOFF = None
|
|
365
|
+
_PREVIEW_EVIDENCE = {}
|
|
351
366
|
archive_path = Path(app_root) / "archive.json"
|
|
352
367
|
if not archive_path.exists():
|
|
353
368
|
print(f"Warning: {archive_path} does not exist, skipping kdform cards", file=sys.stderr)
|
|
@@ -420,7 +435,7 @@ def render(
|
|
|
420
435
|
_TEST_ENVIRONMENT_HANDOFF = export_test_environment(
|
|
421
436
|
app_root, server_config, [number for number, _title in form_entries]
|
|
422
437
|
)
|
|
423
|
-
archived_urls = _archived_preview_urls(artifacts)
|
|
438
|
+
archived_urls = _archived_preview_urls(artifacts, base_url)
|
|
424
439
|
# 子集渲染不能清掉其他表单已经归档的卡片。
|
|
425
440
|
requested_set = {fn for fn, _title in form_entries}
|
|
426
441
|
artifacts = [
|
|
@@ -432,34 +447,46 @@ def render(
|
|
|
432
447
|
|
|
433
448
|
for fn, title in form_entries:
|
|
434
449
|
if title == fn:
|
|
435
|
-
|
|
450
|
+
try:
|
|
451
|
+
title = get_form_name(server_config, base_url, fn) or title
|
|
452
|
+
except Exception:
|
|
453
|
+
# 名称读取失败只降级标题,不阻断目标报表卡片。
|
|
454
|
+
print(f"Warning: form name lookup failed for {fn}; using its exact form number", file=sys.stderr)
|
|
436
455
|
api_ids = api_ids_map.get(fn, [])
|
|
437
456
|
if isinstance(api_ids, list):
|
|
438
457
|
api_ids_str = ",".join(str(i) for i in api_ids)
|
|
439
458
|
else:
|
|
440
459
|
api_ids_str = str(api_ids)
|
|
441
460
|
|
|
461
|
+
preview = {"status": "fresh", "runtimeVerified": False,
|
|
462
|
+
"checkedAt": datetime.now(timezone.utc).isoformat()}
|
|
442
463
|
try:
|
|
443
464
|
url = get_preview_url(server_config, base_url, fn)
|
|
444
465
|
except Exception as e:
|
|
445
466
|
if "FORM_NOT_FOUND" in str(e).upper():
|
|
446
467
|
deleted_numbers.append(fn)
|
|
447
468
|
print(
|
|
448
|
-
f"
|
|
469
|
+
f"Notice: {fn} no longer exists; removed its archived card and emitted no replacement",
|
|
449
470
|
file=sys.stderr,
|
|
450
471
|
)
|
|
451
472
|
continue
|
|
452
473
|
# getPreviewUrl 瞬时失败不能丢掉整个回复的卡片。
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
474
|
+
http_status = getattr(e, "code", None)
|
|
475
|
+
url = archived_urls.get(fn, "") if _is_transient(e) else ""
|
|
476
|
+
preview.update(status="archived" if url else "unavailable", errorType=type(e).__name__)
|
|
477
|
+
if isinstance(http_status, int):
|
|
478
|
+
preview["httpStatus"] = http_status
|
|
479
|
+
preview["retryable"] = _is_transient(e)
|
|
480
|
+
fallback = "reusing archived preview link (not a fresh preview verification)" if url else "rendering card without preview link"
|
|
481
|
+
print(f"Warning: getPreviewUrl({fn}) failed; {fallback}", file=sys.stderr)
|
|
456
482
|
# 平台忽略 viewType=list 时,由已验证列表元数据恢复标准 bos_list 入口。
|
|
457
483
|
try:
|
|
458
484
|
list_url = get_preview_url(
|
|
459
485
|
server_config, base_url, fn, view_type="list", form_preview_url=url,
|
|
460
|
-
)
|
|
461
|
-
except Exception:
|
|
486
|
+
) if preview["status"] == "fresh" else ""
|
|
487
|
+
except Exception as exc:
|
|
462
488
|
list_url = ""
|
|
489
|
+
preview["listErrorType"] = type(exc).__name__
|
|
463
490
|
if list_url and list_url == url:
|
|
464
491
|
print(
|
|
465
492
|
f"Warning: getPreviewUrl({fn}, viewType=list) returned the editor URL; omitted listUrl",
|
|
@@ -474,6 +501,8 @@ def render(
|
|
|
474
501
|
card = {"title": title, "url": url, "metadata": metadata, "plugins": plugins, "designer": designer}
|
|
475
502
|
if list_url:
|
|
476
503
|
card["listUrl"] = list_url
|
|
504
|
+
preview["listStatus"] = "fresh" if list_url else "unavailable" if preview["status"] == "fresh" else "not-run"
|
|
505
|
+
_PREVIEW_EVIDENCE[fn] = preview
|
|
477
506
|
browser_entries[fn] = {"formName": title, "previewUrl": url, "listUrl": list_url}
|
|
478
507
|
if api:
|
|
479
508
|
card["api"] = api
|
|
@@ -489,11 +518,14 @@ def render(
|
|
|
489
518
|
)
|
|
490
519
|
|
|
491
520
|
archive["artifacts"] = artifacts
|
|
521
|
+
previous_evidence = archive.get("previewEvidence")
|
|
522
|
+
archive["previewEvidence"] = {**{key: value for key, value in (previous_evidence.items() if isinstance(previous_evidence, dict) else [])
|
|
523
|
+
if key not in {number for number, _title in form_entries}}, **_PREVIEW_EVIDENCE}
|
|
492
524
|
_write_json(archive_path, archive)
|
|
493
525
|
|
|
494
526
|
if requested_numbers and set(deleted_numbers) == set(requested_numbers):
|
|
495
527
|
raise DeletedArtifactNoCard(
|
|
496
|
-
"DELIVERY_NOT_APPLICABLE: requested
|
|
528
|
+
"DELIVERY_NOT_APPLICABLE: every requested form was deleted; no artifact card was emitted"
|
|
497
529
|
)
|
|
498
530
|
|
|
499
531
|
if blocks and not wait_dym:
|
|
@@ -510,8 +542,9 @@ def render(
|
|
|
510
542
|
|
|
511
543
|
|
|
512
544
|
def main() -> int:
|
|
513
|
-
global _TEST_ENVIRONMENT_HANDOFF
|
|
545
|
+
global _TEST_ENVIRONMENT_HANDOFF, _PREVIEW_EVIDENCE
|
|
514
546
|
_TEST_ENVIRONMENT_HANDOFF = None
|
|
547
|
+
_PREVIEW_EVIDENCE = None
|
|
515
548
|
parser = argparse.ArgumentParser(description="Render output cards")
|
|
516
549
|
parser.add_argument("--app-root", required=True, help="App root directory")
|
|
517
550
|
parser.add_argument("--wait-dym", action="store_true",
|
|
@@ -572,6 +605,8 @@ def main() -> int:
|
|
|
572
605
|
print(block, flush=True)
|
|
573
606
|
if _TEST_ENVIRONMENT_HANDOFF:
|
|
574
607
|
print("TEST_ENVIRONMENT_HANDOFF=" + json.dumps(_TEST_ENVIRONMENT_HANDOFF, ensure_ascii=False), flush=True)
|
|
608
|
+
if _PREVIEW_EVIDENCE:
|
|
609
|
+
print("PREVIEW_EVIDENCE=" + json.dumps(_PREVIEW_EVIDENCE, ensure_ascii=False), flush=True)
|
|
575
610
|
if _DYM_BACKGROUND_NOTICE:
|
|
576
611
|
# 后台同步是必须交给宿主执行的动作,不是诊断日志。放在卡片块后的
|
|
577
612
|
# stdout,避免调用方隐藏 stderr 时把同步请求一起丢弃。
|
|
@@ -580,9 +615,11 @@ def main() -> int:
|
|
|
580
615
|
print(
|
|
581
616
|
"DELIVERY: Preserve the exact blocks for the final delivery response; do not rerender them. "
|
|
582
617
|
"These are domain artifacts, not evidence that the whole request or acceptance is complete. "
|
|
583
|
-
"
|
|
584
|
-
"
|
|
585
|
-
"
|
|
618
|
+
"Formal Expert Team completion reuses the native cards saved in archive.json and publishes "
|
|
619
|
+
"them at the assigned range's successful endpoint; do not repeat host-published cards. "
|
|
620
|
+
"For other delegated implementation, return the blocks and actual results to the owner. "
|
|
621
|
+
"Standalone finalization calls output_check once if available, then includes the blocks "
|
|
622
|
+
"without Markdown links or code fences.",
|
|
586
623
|
file=sys.stderr,
|
|
587
624
|
flush=True,
|
|
588
625
|
)
|