@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
|
@@ -11,7 +11,7 @@ import json
|
|
|
11
11
|
import copy
|
|
12
12
|
import os
|
|
13
13
|
from .cmd_forms import _resolve_form_id, do_query_forms
|
|
14
|
-
from .cmd_properties import
|
|
14
|
+
from .cmd_properties import do_set_properties_verified
|
|
15
15
|
from .cmd_result import _effected_metadata_change, _is_backend_npe
|
|
16
16
|
from .config import _api_call
|
|
17
17
|
from .formatters import _extract_error_message, _is_api_success
|
|
@@ -87,6 +87,37 @@ def validate_batch_manifest(manifest):
|
|
|
87
87
|
+ "; ".join(validation_errors)
|
|
88
88
|
)
|
|
89
89
|
return manifest
|
|
90
|
+
|
|
91
|
+
def _metaops_receipt_problem(data, form_id, ops):
|
|
92
|
+
"""逐 op 回执必须覆盖本次请求;submitted 不是逐项持久化证明。"""
|
|
93
|
+
receipts = data.get("opResults")
|
|
94
|
+
if not isinstance(receipts, list) or len(receipts) != len(ops):
|
|
95
|
+
return "逐项回执缺失或数量不完整"
|
|
96
|
+
if data.get("formId") != form_id or data.get("opsCount") != len(ops):
|
|
97
|
+
return "回执表单或操作数量不匹配"
|
|
98
|
+
by_index = {}
|
|
99
|
+
for receipt in receipts:
|
|
100
|
+
if not isinstance(receipt, dict) or type(receipt.get("opIndex")) is not int:
|
|
101
|
+
return "逐项回执或索引类型非法"
|
|
102
|
+
index = receipt["opIndex"]
|
|
103
|
+
if index in by_index or not 0 <= index < len(ops):
|
|
104
|
+
return "逐项索引重复或越界"
|
|
105
|
+
by_index[index] = receipt
|
|
106
|
+
for index, op in enumerate(ops):
|
|
107
|
+
receipt = by_index[index]
|
|
108
|
+
target = op.get("target") or {}
|
|
109
|
+
for name, value in (("op", op.get("op")), ("treeType", target.get("treeType")),
|
|
110
|
+
("elementType", target.get("elementType"))):
|
|
111
|
+
if str(receipt.get(name) or "").lower() != str(value or "").lower():
|
|
112
|
+
return f"ops[{index}] 的操作或目标类型不匹配"
|
|
113
|
+
if receipt.get("target") != target.get("value") or receipt.get("path") != op.get("path"):
|
|
114
|
+
return f"ops[{index}] 的目标或属性路径不匹配"
|
|
115
|
+
if receipt.get("status") not in {"applied", "no_op", "submitted"}:
|
|
116
|
+
return f"ops[{index}] 未落实:{receipt.get('status')}"
|
|
117
|
+
if receipt.get("errorCode") not in (None, "", "0"):
|
|
118
|
+
return f"ops[{index}] 包含错误:{receipt['errorCode']}"
|
|
119
|
+
return None
|
|
120
|
+
|
|
90
121
|
def do_modify_meta(cfg, token, form_id, ops, form_number=None):
|
|
91
122
|
"""Execute one non-empty structural MetaOps transaction on an exact form."""
|
|
92
123
|
# Auto-resolve when formId is missing but formNumber is provided (improves CLI/capability caller robustness)
|
|
@@ -102,63 +133,49 @@ def do_modify_meta(cfg, token, form_id, ops, form_number=None):
|
|
|
102
133
|
"NON_ATOMIC_PROPERTY_OP: modifyMeta accepts structural MetaOps only; "
|
|
103
134
|
"use the dedicated property command with its own recovery evidence"
|
|
104
135
|
)
|
|
105
|
-
#
|
|
106
|
-
# 此前只是报错让调用方自己改写,而改写所需的信息全在 op 里:
|
|
107
|
-
# target.value → elementKey,path → 属性名,value → 属性值
|
|
108
|
-
# 所以这里直接改道到 Property API,少一个「该用哪个命令」的决策点。
|
|
109
|
-
# 只改道 entity 树的属性 modify:form 树的 modify 确实可用,而且整个 ops 数组在
|
|
110
|
-
# MetaOpsApplier 里是单事务单次 save,拆成 N 次 setProperties 会牺牲原子性
|
|
111
|
-
# (实测一次 modifyMeta 改 3 个控件 833ms,等价的 3 次 setProperties 是 1734ms 且无法整体回滚)。
|
|
136
|
+
# 所有本地校验先于写入;属性请求必须完整解析,不能静默丢弃未知属性。
|
|
112
137
|
structural_ops, property_calls, unresolved = plan_property_reroute(ops)
|
|
113
|
-
|
|
114
138
|
if unresolved:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
139
|
+
raise ValueError("PROPERTY_REROUTE_UNRESOLVED: " + "; ".join(unresolved))
|
|
140
|
+
validation_errors = _validate_ops(structural_ops) if structural_ops else []
|
|
141
|
+
if validation_errors:
|
|
142
|
+
raise ValueError("MetaOps validation failed, API request not sent: "
|
|
143
|
+
+ "; ".join(validation_errors))
|
|
144
|
+
if property_calls and not form_number:
|
|
145
|
+
raise ValueError("PROPERTY_REROUTE_FORM_REQUIRED: 属性改道需要 --formNumber;未发送写入")
|
|
146
|
+
if property_calls and structural_ops:
|
|
147
|
+
# 结构重建与属性写入跨事务,调换执行顺序不能普遍保持原 ops 的语义。
|
|
148
|
+
raise ValueError(
|
|
149
|
+
"NON_ATOMIC_PROPERTY_OP: 结构变更与属性改道不能混为一次事务;"
|
|
150
|
+
"先独立完成并回读结构变更,再对实际字段执行 setProperties;未发送写入")
|
|
118
151
|
|
|
119
152
|
rerouted: list = []
|
|
120
153
|
for call in property_calls:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
property_calls = []
|
|
129
|
-
rerouted = []
|
|
130
|
-
structural_ops = ops
|
|
131
|
-
break
|
|
132
|
-
outcome = do_set_properties(
|
|
133
|
-
cfg, token, target_form, call["elementKey"], call["properties"])
|
|
154
|
+
try:
|
|
155
|
+
outcome = do_set_properties_verified(
|
|
156
|
+
cfg, token, form_number, call["elementKey"], call["properties"])
|
|
157
|
+
except Exception as exc:
|
|
158
|
+
# 读写可能已发生;保留此前回执,不把异常解释成整批未写入。
|
|
159
|
+
outcome = {"status": False, "errorCode": "PROPERTY_REROUTE_EXCEPTION",
|
|
160
|
+
"message": str(exc), "verification": {"verified": False}}
|
|
134
161
|
rerouted.append({"elementKey": call["elementKey"],
|
|
135
162
|
"properties": call["properties"],
|
|
136
163
|
"fromOps": call["ops"],
|
|
137
164
|
"result": outcome})
|
|
165
|
+
if not _is_api_success(outcome) or not (outcome.get("verification") or {}).get("verified"):
|
|
166
|
+
return {"status": False, "errorCode": "PROPERTY_REROUTE_INCOMPLETE",
|
|
167
|
+
"message": "属性改道未由独立回读证明;停止后续写入,核对当前状态后再修复",
|
|
168
|
+
"writeAttempted": True, "retryAdvice": "reconcile_before_retry",
|
|
169
|
+
"data": {"rerouted": rerouted, "pending": property_calls[len(rerouted):]}}
|
|
138
170
|
|
|
139
|
-
if
|
|
140
|
-
print("[modifyMeta] ↪ 属性变更已改道 Property API(modifyMeta 对字段/实体属性不可靠):",
|
|
141
|
-
flush=True)
|
|
171
|
+
if property_calls:
|
|
142
172
|
for line in _describe_reroute(property_calls):
|
|
143
|
-
print(f"
|
|
144
|
-
|
|
145
|
-
# 改道后没有结构性 op 就不必再打扰 modifyMeta
|
|
146
|
-
if not structural_ops:
|
|
147
|
-
if not rerouted:
|
|
148
|
-
raise ValueError(
|
|
149
|
-
"METAOPS_NOTHING_TO_DO: 所有 op 既非结构变更、也无法改道为属性变更;"
|
|
150
|
-
"请检查上面列出的未解析项")
|
|
173
|
+
print(f"[modifyMeta] {line}", flush=True)
|
|
151
174
|
return {"status": True, "errorCode": "0", "data": {
|
|
152
|
-
"rerouted": rerouted, "unresolved":
|
|
153
|
-
"note": "
|
|
175
|
+
"rerouted": rerouted, "unresolved": [],
|
|
176
|
+
"note": "字段/实体属性已由 setProperties 写入并独立回读;未调用 modifyMeta,非跨元素原子事务"}}
|
|
154
177
|
|
|
155
178
|
ops = structural_ops
|
|
156
|
-
validation_errors = _validate_ops(ops)
|
|
157
|
-
if validation_errors:
|
|
158
|
-
print("[validate] ⚠️ MetaOps validation found issues: ", flush=True)
|
|
159
|
-
for e in validation_errors:
|
|
160
|
-
print(f" - {e}", flush=True)
|
|
161
|
-
raise ValueError(f"MetaOps validation failed ({len(validation_errors)} issues), blocked, API request not sent.")
|
|
162
179
|
payload = {"formId": form_id, "ops": ops}
|
|
163
180
|
result = _api_call(cfg, token, "modifyMeta", payload, method="POST")
|
|
164
181
|
if rerouted and isinstance(result, dict):
|
|
@@ -166,6 +183,28 @@ def do_modify_meta(cfg, token, form_id, ops, form_number=None):
|
|
|
166
183
|
if isinstance(result["data"], dict):
|
|
167
184
|
result["data"]["rerouted"] = rerouted
|
|
168
185
|
if _is_api_success(result):
|
|
186
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
187
|
+
if "opResults" in data:
|
|
188
|
+
problem = _metaops_receipt_problem(data, form_id, ops)
|
|
189
|
+
if not problem and (data.get("success") is not True or data.get("stage") != "committed"
|
|
190
|
+
or data.get("rolledBack") is not False):
|
|
191
|
+
problem = "提交或回滚阶段未确认"
|
|
192
|
+
if problem:
|
|
193
|
+
return {"status": False, "errorCode": "METADATA_OPS_UNCONFIRMED",
|
|
194
|
+
"message": problem + ";核对现场后再重试,不重放整批",
|
|
195
|
+
"data": {**data, "platformResult": result},
|
|
196
|
+
"retryAdvice": "reconcile_before_retry"}
|
|
197
|
+
if all(row["status"] == "no_op" for row in data["opResults"]):
|
|
198
|
+
if data.get("metadataChanged") is not False:
|
|
199
|
+
return {"status": False, "errorCode": "METADATA_OPS_UNCONFIRMED",
|
|
200
|
+
"message": "no_op 与整体变更标志矛盾;先核对现场",
|
|
201
|
+
"data": {**data, "platformResult": result}}
|
|
202
|
+
confirmed = copy.deepcopy(result)
|
|
203
|
+
confirmed["data"]["noOp"] = True
|
|
204
|
+
confirmed["data"]["noOpEvidence"] = {"source": "modifyMeta.opResults",
|
|
205
|
+
"scope": "handler", "opIndexes": list(range(len(ops)))}
|
|
206
|
+
print(f"[modifyMeta] 请求已满足({len(ops)} 项 no_op);未报告新变更", flush=True)
|
|
207
|
+
return confirmed
|
|
169
208
|
if not _effected_metadata_change(result):
|
|
170
209
|
advisory = _extract_error_message(result) or (
|
|
171
210
|
(result.get("data") or {}).get("message") if isinstance(result.get("data"), dict) else ""
|
|
@@ -187,8 +226,11 @@ def do_modify_meta(cfg, token, form_id, ops, form_number=None):
|
|
|
187
226
|
"formId": form_id,
|
|
188
227
|
"formNumber": form_number,
|
|
189
228
|
"opsCount": len(ops),
|
|
190
|
-
"writeAttempted": True,
|
|
229
|
+
"writeAttempted": data.get("writeAttempted", True),
|
|
191
230
|
"metadataChanged": False,
|
|
231
|
+
"opResults": data.get("opResults"),
|
|
232
|
+
"stage": data.get("stage"),
|
|
233
|
+
"rolledBack": data.get("rolledBack"),
|
|
192
234
|
"platformResult": result,
|
|
193
235
|
},
|
|
194
236
|
}
|
|
@@ -1430,6 +1430,27 @@ def _normalize_extension_original_readonly(result):
|
|
|
1430
1430
|
return result
|
|
1431
1431
|
|
|
1432
1432
|
|
|
1433
|
+
def _unsafe_report_sort_rejection(complex_type, data):
|
|
1434
|
+
"""只拦截排序污染,合法默认显示列配置保持原有写入路径。"""
|
|
1435
|
+
values = ([data] if complex_type == "initField" else
|
|
1436
|
+
[value for key, value in data.items() if key.lower() == "initfield"]
|
|
1437
|
+
if isinstance(data, dict) else [])
|
|
1438
|
+
for value in values:
|
|
1439
|
+
if isinstance(value, str):
|
|
1440
|
+
try:
|
|
1441
|
+
value = json.loads(value)
|
|
1442
|
+
except ValueError:
|
|
1443
|
+
continue
|
|
1444
|
+
rows = value if isinstance(value, list) else [value]
|
|
1445
|
+
if any(isinstance(row, dict) and row.get("purpose") == "sort" for row in rows):
|
|
1446
|
+
complex_type = "ReportSorts"
|
|
1447
|
+
if complex_type != "ReportSorts":
|
|
1448
|
+
return None
|
|
1449
|
+
from _core.report_ops import REPORT_SORTS_API_UNSAFE
|
|
1450
|
+
return {"status": False, "errorCode": "REPORT_SORTS_API_UNSAFE",
|
|
1451
|
+
"message": REPORT_SORTS_API_UNSAFE, "writeAttempted": False}
|
|
1452
|
+
|
|
1453
|
+
|
|
1433
1454
|
def do_set_properties(cfg, token, form_number, element_key, properties):
|
|
1434
1455
|
"""设置简单属性.
|
|
1435
1456
|
|
|
@@ -1456,6 +1477,9 @@ def do_set_properties(cfg, token, form_number, element_key, properties):
|
|
|
1456
1477
|
),
|
|
1457
1478
|
"data": {"receivedType": type(properties).__name__},
|
|
1458
1479
|
}
|
|
1480
|
+
rejection = _unsafe_report_sort_rejection("setProperties", properties)
|
|
1481
|
+
if rejection is not None:
|
|
1482
|
+
return rejection
|
|
1459
1483
|
rejection = _assert_asst_type_keys(properties)
|
|
1460
1484
|
if rejection is not None:
|
|
1461
1485
|
return rejection
|
|
@@ -1512,21 +1536,19 @@ def _resolve_property_declaration(rows, selector):
|
|
|
1512
1536
|
|
|
1513
1537
|
|
|
1514
1538
|
def _option_value(options, value):
|
|
1515
|
-
for option in options or []
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
if
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
if str(value).casefold() in aliases:
|
|
1529
|
-
return option_id, True
|
|
1539
|
+
rows = [option for option in options or [] if isinstance(option, dict)]
|
|
1540
|
+
exact = [option for option in rows if value == option.get("id") and option.get("id") is not None]
|
|
1541
|
+
if exact:
|
|
1542
|
+
return exact[0]["id"], True
|
|
1543
|
+
matches = [option for option in rows if str(value).casefold() in {
|
|
1544
|
+
str(candidate).casefold() for candidate in (option.get("id"), option.get("name"), option.get("key"))
|
|
1545
|
+
if candidate is not None
|
|
1546
|
+
}]
|
|
1547
|
+
ids = {str(option.get("id")) for option in matches if option.get("id") is not None}
|
|
1548
|
+
if len(ids) > 1:
|
|
1549
|
+
raise ValueError("ambiguous enum value: %r" % value)
|
|
1550
|
+
if len(ids) == 1:
|
|
1551
|
+
return next(option["id"] for option in matches if option.get("id") is not None), True
|
|
1530
1552
|
return value, False
|
|
1531
1553
|
|
|
1532
1554
|
|
|
@@ -1535,6 +1557,8 @@ def _normalize_simple_property_value(declaration, value):
|
|
|
1535
1557
|
editor = str(declaration.get("editorType") or "").casefold()
|
|
1536
1558
|
options = declaration.get("options") or []
|
|
1537
1559
|
constraints = declaration.get("constraints") or {}
|
|
1560
|
+
if editor == "ecombo" and not options and value not in (None, "") and not _field_reference_constraints(declaration):
|
|
1561
|
+
raise ValueError("field selector candidates are unavailable in the live schema")
|
|
1538
1562
|
if str(declaration.get("propertyName") or "") in {"Width", "Height"}:
|
|
1539
1563
|
value = _normalize_dimension_value(declaration["propertyName"], value)
|
|
1540
1564
|
if (
|
|
@@ -5156,6 +5180,9 @@ def do_set_complex_property(cfg, token, form_number, complex_type, data, element
|
|
|
5156
5180
|
"""
|
|
5157
5181
|
|
|
5158
5182
|
rejection = _assert_known_complex_type(complex_type)
|
|
5183
|
+
if rejection is not None:
|
|
5184
|
+
return rejection
|
|
5185
|
+
rejection = _unsafe_report_sort_rejection(complex_type, data)
|
|
5159
5186
|
if rejection is not None:
|
|
5160
5187
|
return rejection
|
|
5161
5188
|
if complex_type == "dropdownItems":
|
|
@@ -11,6 +11,7 @@ import hashlib
|
|
|
11
11
|
import json
|
|
12
12
|
import re
|
|
13
13
|
from contextvars import ContextVar
|
|
14
|
+
from functools import wraps
|
|
14
15
|
|
|
15
16
|
from .config import _api_call
|
|
16
17
|
from .cmd_listui import do_load_full_metadata
|
|
@@ -57,6 +58,22 @@ def _rule_element_inventory(cfg, token, form_number, category):
|
|
|
57
58
|
batch["rows"][category] = copy.deepcopy(result)
|
|
58
59
|
return result
|
|
59
60
|
|
|
61
|
+
def _single_rule_inventory(fn):
|
|
62
|
+
"""Reuse one form inventory during a single semantic write, never across calls."""
|
|
63
|
+
@wraps(fn)
|
|
64
|
+
def run(cfg, token, form_number, *args, **kwargs):
|
|
65
|
+
existing = _RULE_BATCH_INVENTORY.get()
|
|
66
|
+
if existing is not None and existing["formNumber"] == form_number:
|
|
67
|
+
return fn(cfg, token, form_number, *args, **kwargs)
|
|
68
|
+
inventory = {"formNumber": form_number, "rows": {}, "reads": 0, "hits": 0}
|
|
69
|
+
scope = _RULE_BATCH_INVENTORY.set(inventory)
|
|
70
|
+
try:
|
|
71
|
+
return fn(cfg, token, form_number, *args, **kwargs)
|
|
72
|
+
finally:
|
|
73
|
+
_RULE_BATCH_INVENTORY.reset(scope)
|
|
74
|
+
return run
|
|
75
|
+
|
|
76
|
+
|
|
60
77
|
# ─── 规则管理 API ──────────────────────────────────────────────────────────────
|
|
61
78
|
|
|
62
79
|
_FORM_RULE_DEFAULT_RET = FORM_RULE_DEFAULT_RET
|
|
@@ -949,9 +966,105 @@ def _strip_formula_literals(expression):
|
|
|
949
966
|
return "".join(chars)
|
|
950
967
|
|
|
951
968
|
|
|
969
|
+
def _compile_time_range_actions(cfg, token, form_number, rule_config):
|
|
970
|
+
"""Expand explicit clock ranges using the platform's stored endpoint keys.
|
|
971
|
+
|
|
972
|
+
Only a TimeRangeField assigned a quoted HH:MM[-HH:MM] literal is handled.
|
|
973
|
+
Other FormulaAction expressions are untouched. Never infer an endpoint key.
|
|
974
|
+
"""
|
|
975
|
+
if rule_config.get("ruleType") != "bizRule":
|
|
976
|
+
return rule_config, []
|
|
977
|
+
pattern = re.compile(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(['\"])(\d{1,2}):(\d{2})(?::(\d{2}))?\s*[-~]\s*(\d{1,2}):(\d{2})(?::(\d{2}))?\2\s*$")
|
|
978
|
+
candidates = []
|
|
979
|
+
for branch in ("trueActions", "falseActions"):
|
|
980
|
+
for action in rule_config.get(branch) or []:
|
|
981
|
+
if not isinstance(action, dict):
|
|
982
|
+
continue
|
|
983
|
+
action_type = next((value for key, value in action.items() if str(key).casefold() == "actiontype"), "")
|
|
984
|
+
expression = next((value for key, value in action.items() if str(key).casefold() == "expression"), "")
|
|
985
|
+
match = pattern.fullmatch(str(expression or "")) if action_type == "FormulaAction" else None
|
|
986
|
+
if match:
|
|
987
|
+
candidates.append((branch, action, match))
|
|
988
|
+
if not candidates:
|
|
989
|
+
return rule_config, []
|
|
990
|
+
listed = _rule_element_inventory(cfg, token, form_number, "field")
|
|
991
|
+
rows = listed.get("data") if isinstance(listed, dict) else None
|
|
992
|
+
if not _is_api_success(listed) or not isinstance(rows, list):
|
|
993
|
+
raise RuntimeError("TIME_RANGE_FIELD_INVENTORY_UNAVAILABLE")
|
|
994
|
+
by_key = {str(row.get("key") or "").strip(): row for row in rows if isinstance(row, dict)}
|
|
995
|
+
for _, _, match in candidates:
|
|
996
|
+
matching_rows = [row for row in rows if isinstance(row, dict) and str(row.get("key") or "").strip() == match.group(1)]
|
|
997
|
+
if len(matching_rows) > 1:
|
|
998
|
+
raise ValueError("TIME_RANGE_FIELD_AMBIGUOUS")
|
|
999
|
+
range_candidates = [(branch, action, match) for branch, action, match in candidates
|
|
1000
|
+
if str((by_key.get(match.group(1)) or {}).get("type") or "") == "TimeRangeField"]
|
|
1001
|
+
if not range_candidates:
|
|
1002
|
+
return rule_config, []
|
|
1003
|
+
loaded = do_load_full_metadata(cfg, token, form_number)
|
|
1004
|
+
if not _is_api_success(loaded):
|
|
1005
|
+
raise RuntimeError("TIME_RANGE_METADATA_UNAVAILABLE")
|
|
1006
|
+
from .cmd_listui import _metadata_nodes
|
|
1007
|
+
nodes = list(_metadata_nodes(loaded.get("data")))
|
|
1008
|
+
mount = str(rule_config.get("mountPoint") or "").strip()
|
|
1009
|
+
if rule_config.get("ruleId"):
|
|
1010
|
+
rules = [(node, parent) for node, parent in nodes if isinstance(node, dict)
|
|
1011
|
+
and node.get("_Type_") == "BizRule" and str(node.get("Id") or "") == str(rule_config["ruleId"])]
|
|
1012
|
+
if len(rules) != 1 or not rules[0][1]:
|
|
1013
|
+
raise ValueError("TIME_RANGE_RULE_OWNER_UNRESOLVED")
|
|
1014
|
+
mount = rules[0][1]
|
|
1015
|
+
compiled = copy.deepcopy(rule_config)
|
|
1016
|
+
virtual = []
|
|
1017
|
+
for branch in {branch for branch, _, _ in range_candidates}:
|
|
1018
|
+
sequences = [next((value for key, value in action.items() if str(key).casefold() == "seq"), None)
|
|
1019
|
+
for action in rule_config[branch]]
|
|
1020
|
+
if any(value is not None for value in sequences):
|
|
1021
|
+
if any(not isinstance(value, int) for value in sequences) or sequences != sorted(set(sequences)):
|
|
1022
|
+
raise ValueError("TIME_RANGE_ACTION_ORDER_AMBIGUOUS")
|
|
1023
|
+
for branch, original, match in reversed(range_candidates):
|
|
1024
|
+
key = match.group(1)
|
|
1025
|
+
owner = str(by_key[key].get("parentKey") or "").strip()
|
|
1026
|
+
if not owner or not mount or owner != mount:
|
|
1027
|
+
raise ValueError("TIME_RANGE_ENTITY_MISMATCH: range field must belong to the rule entity")
|
|
1028
|
+
fields = [node for node, _ in nodes if isinstance(node, dict)
|
|
1029
|
+
and str(node.get("_Type_") or node.get("Type") or "") == "TimeRangeField"
|
|
1030
|
+
and str(node.get("Key") or "") == key
|
|
1031
|
+
and node.get("StartDateFieldKey") and node.get("EndDateFieldKey")]
|
|
1032
|
+
if len(fields) != 1:
|
|
1033
|
+
raise ValueError("TIME_RANGE_METADATA_AMBIGUOUS: cannot resolve the time range field")
|
|
1034
|
+
endpoints = [str(fields[0].get(name) or "").strip() for name in ("StartDateFieldKey", "EndDateFieldKey")]
|
|
1035
|
+
if len(set(endpoints)) != 2 or any(not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", key) for key in endpoints):
|
|
1036
|
+
raise ValueError("TIME_RANGE_ENDPOINTS_UNRESOLVED")
|
|
1037
|
+
groups = [(match.group(3), match.group(4), match.group(5)), (match.group(6), match.group(7), match.group(8))]
|
|
1038
|
+
times = [(int(hour), int(minute), int(second or 0)) for hour, minute, second in groups]
|
|
1039
|
+
if any(hour > 23 or minute > 59 or second > 59 for hour, minute, second in times):
|
|
1040
|
+
raise ValueError("TIME_RANGE_VALUE_INVALID: clock time must be between 00:00:00 and 23:59:59")
|
|
1041
|
+
values = [hour * 3600 + minute * 60 + second for hour, minute, second in times]
|
|
1042
|
+
if values[0] > values[1]:
|
|
1043
|
+
raise ValueError("TIME_RANGE_OVERNIGHT_UNSUPPORTED: confirm platform semantics before writing")
|
|
1044
|
+
actions = compiled[branch]
|
|
1045
|
+
index = next(i for i, action in enumerate(rule_config[branch]) if action is original)
|
|
1046
|
+
first = actions[index]
|
|
1047
|
+
second = copy.deepcopy(first)
|
|
1048
|
+
expression_key = next(k for k in first if str(k).casefold() == "expression")
|
|
1049
|
+
first[expression_key] = f"{endpoints[0]} = {values[0]}"
|
|
1050
|
+
second[expression_key] = f"{endpoints[1]} = {values[1]}"
|
|
1051
|
+
for field in list(second):
|
|
1052
|
+
if str(field).casefold() == "id":
|
|
1053
|
+
second.pop(field)
|
|
1054
|
+
actions.insert(index + 1, second)
|
|
1055
|
+
virtual.extend({"key": endpoint, "type": "TimeField", "parentKey": owner} for endpoint in endpoints)
|
|
1056
|
+
for branch in {branch for branch, _, _ in range_candidates}:
|
|
1057
|
+
for index, action in enumerate(compiled[branch], 1):
|
|
1058
|
+
for field in list(action):
|
|
1059
|
+
if str(field).casefold() == "seq":
|
|
1060
|
+
action.pop(field)
|
|
1061
|
+
action["seq"] = index
|
|
1062
|
+
return compiled, virtual
|
|
1063
|
+
|
|
1064
|
+
|
|
952
1065
|
def _validate_formula_action_scope(
|
|
953
1066
|
cfg, token, form_number, rule_config, allow_scalar_set_functions=False,
|
|
954
|
-
allowed_dotted_references=(),
|
|
1067
|
+
allowed_dotted_references=(), virtual_fields=(),
|
|
955
1068
|
):
|
|
956
1069
|
"""Reject FormulaAction expressions that escape the mounted entity.
|
|
957
1070
|
|
|
@@ -980,6 +1093,13 @@ def _validate_formula_action_scope(
|
|
|
980
1093
|
str(row.get("key") or "").strip(): row
|
|
981
1094
|
for row in rows if isinstance(row, dict) and str(row.get("key") or "").strip()
|
|
982
1095
|
}
|
|
1096
|
+
for row in virtual_fields:
|
|
1097
|
+
existing = by_key.get(row["key"])
|
|
1098
|
+
if existing and (str(existing.get("type") or "") != "TimeField"
|
|
1099
|
+
or str(existing.get("parentKey") or "").strip() != row["parentKey"]):
|
|
1100
|
+
raise ValueError("TIME_RANGE_ENDPOINT_COLLISION")
|
|
1101
|
+
if not existing:
|
|
1102
|
+
by_key[row["key"]] = row
|
|
983
1103
|
for action in actions:
|
|
984
1104
|
expression = str(action.get("Expression") or "").strip()
|
|
985
1105
|
bare = _strip_formula_literals(expression)
|
|
@@ -1152,7 +1272,7 @@ def _validate_set_equation_action_scope(cfg, token, form_number, rule_config):
|
|
|
1152
1272
|
if not actions:
|
|
1153
1273
|
return rule_config
|
|
1154
1274
|
mounted = str(rule_config.get("mountPoint") or "").strip()
|
|
1155
|
-
listed =
|
|
1275
|
+
listed = _rule_element_inventory(cfg, token, form_number=form_number, category="field")
|
|
1156
1276
|
rows = listed.get("data") if isinstance(listed, dict) else None
|
|
1157
1277
|
if not _is_api_success(listed) or not isinstance(rows, list):
|
|
1158
1278
|
raise RuntimeError(
|
|
@@ -1896,9 +2016,10 @@ def do_add_rule_verified(
|
|
|
1896
2016
|
_validate_rule_write_input(rule_config, command="addRule")
|
|
1897
2017
|
client_mount = bool(str(rule_config.get("clientMount") or "").strip())
|
|
1898
2018
|
resolved = _resolve_rule_entity(cfg, token, form_number, rule_config)
|
|
2019
|
+
resolved, virtual_fields = _compile_time_range_actions(cfg, token, form_number, resolved)
|
|
1899
2020
|
resolved = _validate_formula_action_scope(
|
|
1900
2021
|
cfg, token, form_number, resolved, allow_scalar_set_functions,
|
|
1901
|
-
allowed_dotted_references,
|
|
2022
|
+
allowed_dotted_references, virtual_fields,
|
|
1902
2023
|
)
|
|
1903
2024
|
resolved = _validate_clear_value_action_scope(
|
|
1904
2025
|
cfg, token, form_number, resolved
|
|
@@ -5183,6 +5304,7 @@ def do_ensure_carry_reference_mappings(cfg, token, form_number, request):
|
|
|
5183
5304
|
return result
|
|
5184
5305
|
|
|
5185
5306
|
|
|
5307
|
+
@_single_rule_inventory
|
|
5186
5308
|
def do_ensure_conditional_copy_field_value(
|
|
5187
5309
|
cfg, token, form_number, entity, condition_field, operator, value,
|
|
5188
5310
|
source, target, only_empty_target=False, add_events=None, events=None,
|
|
@@ -5225,8 +5347,9 @@ def do_ensure_conditional_copy_field_value(
|
|
|
5225
5347
|
"mountPoint": context["entityKey"],
|
|
5226
5348
|
"description": description,
|
|
5227
5349
|
"preDescription": (
|
|
5228
|
-
f"{context['conditionName']} {context['operator']}
|
|
5229
|
-
f"{context['requestedValue']
|
|
5350
|
+
f"{context['conditionName']} {context['operator']}"
|
|
5351
|
+
f"{(' ' + context['requestedValue']) if context['requestedValue'] else ''}"
|
|
5352
|
+
f" 时把{source_name}复制到{target_name}"
|
|
5230
5353
|
),
|
|
5231
5354
|
"preCondition": context["expression"],
|
|
5232
5355
|
"triggerField": f"{context['conditionName']}({context['conditionKey']})",
|
|
@@ -6121,6 +6244,13 @@ def do_ensure_conditional_entry_style(
|
|
|
6121
6244
|
|
|
6122
6245
|
def do_update_rule_verified(cfg, token, form_number, rule_config):
|
|
6123
6246
|
_validate_rule_write_input(rule_config, command="updateRule")
|
|
6247
|
+
rule_config, virtual_fields = _compile_time_range_actions(cfg, token, form_number, rule_config)
|
|
6248
|
+
if virtual_fields:
|
|
6249
|
+
_validate_formula_action_scope(
|
|
6250
|
+
cfg, token, form_number,
|
|
6251
|
+
{**rule_config, "mountPoint": virtual_fields[0]["parentKey"]},
|
|
6252
|
+
virtual_fields=virtual_fields,
|
|
6253
|
+
)
|
|
6124
6254
|
preserved_rule = None
|
|
6125
6255
|
if set(rule_config) <= {"ruleType", "ruleId", "enabled", "preCondition", "preDescription", "description", "triggerField"} and (
|
|
6126
6256
|
set(rule_config) - {"ruleType", "ruleId"}
|
|
@@ -371,7 +371,7 @@ _API_PATH = {
|
|
|
371
371
|
"metalogCompare": "/kapi/v2/devportal/ai-meta/metalog/compare",
|
|
372
372
|
"metalogRestore": "/kapi/v2/devportal/ai-meta/metalog/restore",
|
|
373
373
|
|
|
374
|
-
# ─── 本地补丁(LOCAL-PATCHES #13 / #14):上游 0.0.
|
|
374
|
+
# ─── 本地补丁(LOCAL-PATCHES #13 / #14):上游 0.0.343 仍无这些端点 ───
|
|
375
375
|
"ksqlListDbRoutes": "/kapi/v2/devportal/ai-meta/ksql/listDbRoutes",
|
|
376
376
|
"ksqlListDbTypes": "/kapi/v2/devportal/ai-meta/ksql/listDbTypes",
|
|
377
377
|
"ksqlListTables": "/kapi/v2/devportal/ai-meta/ksql/listTables",
|
|
@@ -12,7 +12,7 @@ def classify_execution_error(result):
|
|
|
12
12
|
if attempted is not False and (result.get("outcome") == "unknown_after_write"
|
|
13
13
|
or result.get("phase") == "writeUnknown"):
|
|
14
14
|
return "unknown_write"
|
|
15
|
-
if code
|
|
15
|
+
if code in ("FULL_METADATA_API_UNAVAILABLE", "REPORT_COLUMN_DELETE_API_UNAVAILABLE"):
|
|
16
16
|
return "capability_limit"
|
|
17
17
|
if code.endswith("LOOKUP_UNCONFIRMED"):
|
|
18
18
|
return "environment"
|
|
@@ -652,6 +652,22 @@ def save_run_batch_result(result, app_root, artifact_paths):
|
|
|
652
652
|
return receipt
|
|
653
653
|
|
|
654
654
|
|
|
655
|
+
def result_summary(obj, result_path):
|
|
656
|
+
"""Keep continuation evidence inline; the original receipt stays on disk."""
|
|
657
|
+
summary = {key: obj[key] for key in (
|
|
658
|
+
"status", "errorCode", "errorCategory", "message", "total", "completed", "success", "failed",
|
|
659
|
+
"forms", "delivery", "executionFinished", "writeAttempted", "metadataRetained",
|
|
660
|
+
"doNotRetryBuildMeta", "resume", "reconcile", "reconciliation", "repairPlans", "modelInstruction",
|
|
661
|
+
) if key in obj}
|
|
662
|
+
summary["resultPath"] = os.path.abspath(result_path)
|
|
663
|
+
if isinstance(obj.get("results"), list):
|
|
664
|
+
summary["results"] = [{**{key: row[key] for key in ("artifactPath", "ok", "phase", "error", "errors") if key in row},
|
|
665
|
+
**({"verification": {key: row["verification"][key] for key in ("performed", "status", "verified", "summary", "verifiedTargets", "issues")
|
|
666
|
+
if key in row["verification"]}} if isinstance(row.get("verification"), dict) else {})}
|
|
667
|
+
for row in obj["results"] if isinstance(row, dict)]
|
|
668
|
+
return summary
|
|
669
|
+
|
|
670
|
+
|
|
655
671
|
def pjson(obj, force_stdout=False):
|
|
656
672
|
"""Output JSON result.
|
|
657
673
|
|
|
@@ -699,17 +715,7 @@ def pjson(obj, force_stdout=False):
|
|
|
699
715
|
if isinstance(obj, dict) and (obj.get("delivery") or obj.get("results")):
|
|
700
716
|
# Keep the complete receipt on disk, while exposing the facts
|
|
701
717
|
# needed to continue. Never infer verification from exit status.
|
|
702
|
-
summary =
|
|
703
|
-
"status", "errorCode", "errorCategory", "message", "total", "completed", "success", "failed",
|
|
704
|
-
"forms", "delivery", "executionFinished", "writeAttempted", "metadataRetained",
|
|
705
|
-
"doNotRetryBuildMeta", "resume", "reconcile", "reconciliation",
|
|
706
|
-
) if key in obj}
|
|
707
|
-
summary["resultPath"] = os.path.abspath(target_file)
|
|
708
|
-
if isinstance(obj.get("results"), list):
|
|
709
|
-
summary["results"] = [{**{key: row[key] for key in ("artifactPath", "ok", "phase", "error") if key in row},
|
|
710
|
-
**({"verification": {key: row["verification"][key] for key in ("performed", "status", "verified")
|
|
711
|
-
if key in row["verification"]}} if isinstance(row.get("verification"), dict) else {})}
|
|
712
|
-
for row in obj["results"] if isinstance(row, dict)]
|
|
718
|
+
summary = result_summary(obj, target_file)
|
|
713
719
|
print("RESULT_SUMMARY=" + json.dumps(summary, ensure_ascii=False, separators=(",", ":")), file=sys.stdout, flush=True)
|
|
714
720
|
else:
|
|
715
721
|
print(text, file=sys.stdout, flush=True)
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
Why this exists
|
|
4
4
|
---------------
|
|
5
5
|
`validators.py` rule S5a refuses `modify` on `treeType=entity` with `elementType`
|
|
6
|
-
`field`/`entity
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
`field`/`entity`:处理器对未知 path 或不适用类型只 warning 后返回,可能报告成功
|
|
7
|
+
但没有修改。modifyMeta 白名单使用 camelCase(`mustInput`),Property API 使用
|
|
8
|
+
PascalCase(`MustInput`);白名单支持的 mustInput 不能概括为一定无效。
|
|
9
|
+
原 combo NPE 缺少完整堆栈,尚不能归因为索引重建。
|
|
10
10
|
|
|
11
11
|
Refusing is correct but it leaves the caller to translate the call by hand — and the
|
|
12
12
|
information needed for that translation is already in the op:
|
|
@@ -102,6 +102,9 @@ def plan_property_reroute(ops: list) -> tuple[list, list, list]:
|
|
|
102
102
|
structural.append(op)
|
|
103
103
|
continue
|
|
104
104
|
target = op.get("target") or {}
|
|
105
|
+
if not isinstance(target, dict):
|
|
106
|
+
structural.append(op)
|
|
107
|
+
continue
|
|
105
108
|
if (op.get("op") != "modify"
|
|
106
109
|
or target.get("treeType") != "entity"
|
|
107
110
|
or target.get("elementType") not in ("field", "entity")):
|
|
@@ -109,6 +112,10 @@ def plan_property_reroute(ops: list) -> tuple[list, list, list]:
|
|
|
109
112
|
continue
|
|
110
113
|
|
|
111
114
|
path = op.get("path")
|
|
115
|
+
if path is not None and (not isinstance(path, str) or not path.strip()):
|
|
116
|
+
unresolved.append(f"ops[{index}]: modify.path 必须是非空字符串;Map 形式应省略 path")
|
|
117
|
+
structural.append(op)
|
|
118
|
+
continue
|
|
112
119
|
if _is_combo_path(path):
|
|
113
120
|
structural.append(op) # S5a 豁免,保持原样
|
|
114
121
|
continue
|
|
@@ -144,7 +151,10 @@ def plan_property_reroute(ops: list) -> tuple[list, list, list]:
|
|
|
144
151
|
f"ops[{index}]: 属性 {bad} 不在属性契约(property-input-index.json)里,"
|
|
145
152
|
f"不做名称猜测。确认名字后改用 "
|
|
146
153
|
f"setProperties --elementKey {element_key} --input '{{...}}'")
|
|
147
|
-
|
|
154
|
+
# 一个请求有未知属性时整体保留并拒绝,不能只写已知部分后报告成功。
|
|
155
|
+
if bad or not resolved:
|
|
156
|
+
if not raw:
|
|
157
|
+
unresolved.append(f"ops[{index}]: 属性 Map 为空,无法改道")
|
|
148
158
|
structural.append(op)
|
|
149
159
|
continue
|
|
150
160
|
|