@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.
Files changed (218) hide show
  1. package/package.json +1 -1
  2. package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
  3. package/skills/cosmic-build/SKILL.md +24 -9
  4. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
  5. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
  6. package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
  7. package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
  8. package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
  9. package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
  10. package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
  11. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
  12. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
  13. package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
  14. package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
  15. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
  16. package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
  17. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
  18. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
  19. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
  20. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
  21. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
  22. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
  25. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
  26. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
  27. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
  28. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
  29. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
  30. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
  31. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
  32. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
  33. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
  34. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
  35. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
  36. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
  37. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
  38. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
  39. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
  40. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
  41. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
  42. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
  43. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
  44. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
  45. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
  46. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
  47. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
  48. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
  49. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
  50. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
  51. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
  52. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
  53. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
  54. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
  55. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
  56. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
  57. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
  58. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
  59. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
  60. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
  61. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
  62. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
  63. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
  64. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
  65. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
  66. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
  67. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
  68. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
  69. package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
  70. package/skills/cosmic-build/references/compile-guide.md +36 -1
  71. package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
  72. package/skills/cosmic-build/references/delivery.md +16 -2
  73. package/skills/cosmic-build/references/execution-contract.md +15 -3
  74. package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
  75. package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
  76. package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
  77. package/skills/cosmic-build/references/report-filter-contract.md +53 -15
  78. package/skills/cosmic-build/references/sdk-inspection.md +4 -1
  79. package/skills/cosmic-build/references/type-registry.json +26 -17
  80. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
  81. package/skills/cosmic-build/scripts/_http_client.py +3 -0
  82. package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
  83. package/skills/cosmic-build/scripts/configure_list.py +112 -0
  84. package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
  85. package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
  86. package/skills/cosmic-build/scripts/execution_binding.py +25 -0
  87. package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
  88. package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
  89. package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
  90. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
  91. package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
  92. package/skills/cosmic-build/scripts/register_plugins.py +27 -10
  93. package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
  94. package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
  95. package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
  96. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
  97. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
  98. package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
  99. package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
  100. package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
  101. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
  102. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
  103. package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
  104. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
  105. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
  106. package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
  107. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
  108. package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
  109. package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
  110. package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
  111. package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
  112. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
  113. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
  114. package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
  115. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
  116. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
  117. package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
  118. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
  119. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
  120. package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
  121. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
  122. package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
  123. package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
  124. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
  125. package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
  126. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
  127. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
  128. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
  129. package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
  130. package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
  131. package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
  132. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
  133. package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
  134. package/skills/cosmic-build/scripts/test_environment.py +67 -1
  135. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
  136. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
  138. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
  139. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
  143. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
  144. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
  145. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
  146. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
  147. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
  177. package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
  178. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
  179. package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
  180. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
  181. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
  182. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
  183. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
  184. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
  185. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
  193. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
  194. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
  196. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
  197. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
  198. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
  199. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
  200. package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
  201. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
  202. package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
  203. package/skills/cosmic-dev-best-practices/references/README.md +2 -2
  204. package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
  205. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
  206. package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
  207. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
  208. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
  209. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
  210. package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
  211. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
  212. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
  213. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
  214. package/skills/erp-analysis/references/delivery-contract.md +2 -1
  215. package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
  216. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
  217. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
  218. 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 do_set_properties
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
- # 字段/实体的属性型 modify 由 S5a 判为不可靠(camelCase/PascalCase 静默空转、索引 NPE)。
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
- print("[modifyMeta] ⚠️ 以下属性无法安全改道,需调用方确认后自行处理:", flush=True)
116
- for u in unresolved:
117
- print(f" - {u}", flush=True)
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
- # 改道需要 formNumber(Property API 按编码寻址)。只在调用方已经提供时改道 ——
122
- # 与其自己造一条 formId→formNumber 的反查(多一次网络调用、多一处可能出错),
123
- # 不如把这种情况如实说清楚,让原来的 S5a 报错继续生效。
124
- target_form = form_number
125
- if not target_form:
126
- print("[modifyMeta] ⚠️ 属性改道需要 --formNumber(Property API 按编码寻址);"
127
- "本次只提供了 formId,改道跳过,仍按原规则校验", flush=True)
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 rerouted:
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" {line}", flush=True)
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": unresolved,
153
- "note": "字段/实体属性变更已由 setProperties 执行,未调用 modifyMeta"}}
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
- if not isinstance(option, dict):
1517
- continue
1518
- option_id = option.get("id")
1519
- option_name = option.get("name")
1520
- option_key = option.get("key")
1521
- if value == option_id:
1522
- return option_id, True
1523
- aliases = {
1524
- str(candidate).casefold()
1525
- for candidate in (option_id, option_name, option_key)
1526
- if candidate is not None
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 = do_list_elements(cfg, token, form_number=form_number, category="field")
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']} 时把{source_name}复制到{target_name}"
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.326 仍无这些端点 ───
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 == "FULL_METADATA_API_UNAVAILABLE":
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 = {key: obj[key] for key in (
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`, because that path is unreliable: the modifyMeta whitelist is camelCase
7
- (`mustInput`) while the Property API is PascalCase (`MustInput`), so a non-whitelisted
8
- property **reports success and changes nothing**; some combinations also hit an
9
- index-rebuild NPE.
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
- if not resolved:
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