@shundoo-ai/dsh-cosmic 1.0.19 → 1.0.21

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 (245) hide show
  1. package/cordis.patch.yml +5 -5
  2. package/package.json +1 -1
  3. package/skills/cosmic-build/CHANGELOG.md +4 -0
  4. package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
  5. package/skills/cosmic-build/SKILL.md +46 -13
  6. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
  7. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
  8. package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
  9. package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
  10. package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
  11. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
  12. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
  13. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
  14. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
  15. package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
  16. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
  17. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
  18. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
  19. package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
  20. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
  21. package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
  22. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
  25. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
  26. package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
  27. package/skills/cosmic-build/references/application-context.md +35 -1
  28. package/skills/cosmic-build/references/common-reference.md +1 -1
  29. package/skills/cosmic-build/references/compile-guide.md +55 -68
  30. package/skills/cosmic-build/references/delivery.md +59 -7
  31. package/skills/cosmic-build/references/execution-contract.md +38 -0
  32. package/skills/cosmic-build/references/form-discovery.md +7 -1
  33. package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
  34. package/skills/cosmic-build/references/operation-dispatch.md +17 -0
  35. package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
  36. package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
  37. package/skills/cosmic-build/references/report-filter-contract.md +78 -0
  38. package/skills/cosmic-build/references/sdk-inspection.md +23 -0
  39. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
  40. package/skills/cosmic-build/scripts/_http_client.py +55 -55
  41. package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
  42. package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
  43. package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
  44. package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
  45. package/skills/cosmic-build/scripts/execution_binding.py +40 -0
  46. package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
  47. package/skills/cosmic-build/scripts/kd_api.py +23 -22
  48. package/skills/cosmic-build/scripts/kd_auth.py +1 -1
  49. package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
  50. package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
  51. package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
  52. package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
  53. package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
  54. package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
  55. package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
  56. package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
  57. package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
  58. package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
  59. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
  60. package/skills/cosmic-build/scripts/preview_url.py +35 -6
  61. package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
  62. package/skills/cosmic-build/scripts/register_plugins.py +28 -2
  63. package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
  64. package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
  65. package/skills/cosmic-build/scripts/run.py +2 -2
  66. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
  67. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
  68. package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
  69. package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
  70. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
  71. package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
  72. package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
  73. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
  74. package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
  75. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
  76. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
  77. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
  78. package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
  79. package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
  80. package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
  81. package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
  82. package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
  83. package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
  84. package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
  85. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
  86. package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
  87. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
  88. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
  89. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
  90. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
  91. package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
  92. package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
  93. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
  94. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
  95. package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
  96. package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
  97. package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
  98. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
  99. package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
  100. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
  101. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
  102. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
  103. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
  104. package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
  105. package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
  106. package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
  107. package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
  108. package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
  109. package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
  110. package/skills/cosmic-build/scripts/test_environment.py +49 -0
  111. package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
  112. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
  113. package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
  114. package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
  115. package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
  116. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
  117. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
  118. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
  119. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
  120. package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
  121. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
  122. package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
  123. package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
  124. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
  125. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
  126. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
  127. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
  128. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
  129. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
  130. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
  131. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
  132. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
  133. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
  134. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
  135. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
  136. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
  138. package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
  139. package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
  143. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
  144. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
  145. package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
  146. package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
  147. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
  177. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
  178. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
  179. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
  180. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
  181. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
  182. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
  183. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
  184. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
  185. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
  193. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
  194. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
  196. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
  197. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
  198. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
  199. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
  200. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
  201. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
  202. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
  203. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
  204. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
  205. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.md +6 -2
  206. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  207. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +20 -5
  208. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
  209. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
  210. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
  211. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
  212. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
  213. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
  214. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
  215. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
  216. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
  217. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
  218. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
  219. package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
  220. package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
  221. package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
  222. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
  223. package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
  224. package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
  225. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
  226. package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
  227. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
  228. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
  229. package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
  230. package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
  231. package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
  232. package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
  233. package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
  234. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
  235. package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
  236. package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
  237. package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
  238. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
  239. package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
  240. package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
  241. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
  242. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
  243. package/skills/erp-analysis/references/delivery-contract.md +4 -2
  244. package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
  245. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/.dsh/cosmic/.kd/config.json +0 -21
@@ -51,6 +51,8 @@ import json
51
51
  import argparse
52
52
  import builtins
53
53
  import hashlib
54
+ import shlex
55
+ import subprocess
54
56
  from datetime import datetime, timezone
55
57
 
56
58
  # Windows default stdout is GBK, cannot output ⚠️ and other Unicode chars, force UTF-8
@@ -95,6 +97,7 @@ from _core.validators import ( # noqa: F401
95
97
  )
96
98
 
97
99
  from field_decorator import ( # noqa: F401
100
+ make_entity_key_for,
98
101
  decorate_artifact,
99
102
  decorate_ops,
100
103
  validate_dynamic_form_number,
@@ -307,6 +310,14 @@ def _append_meta_event(phase, **payload):
307
310
 
308
311
 
309
312
  def _summarize_build_meta_result(result, artifact=None):
313
+ if isinstance(result, dict) and result.get("status") is False and result.get("writeAttempted") is False:
314
+ summary = {"ok": False, "successCount": 0, "failedCount": 1, "formNumbers": [],
315
+ "errors": [_extract_error_message(result) or result.get("errorCode")],
316
+ "errorCode": result.get("errorCode"), "writeAttempted": False}
317
+ if result.get("errorCode") == "TARGET_ALREADY_EXISTS":
318
+ _attach_target_exists_contract(summary, (result.get("data") or {}).get("conflicts", []))
319
+ summary["writeAttempted"] = False
320
+ return summary
310
321
  data = result.get("data") if isinstance(result, dict) else {}
311
322
  data = data if isinstance(data, dict) else {}
312
323
  entity_results = data.get("entityResults") or []
@@ -673,6 +684,79 @@ def _dynamic_form_identity_preflight(artifact):
673
684
  }
674
685
 
675
686
 
687
+ def _resolve_dynamic_reference_fields(artifact, *, app_root=None, isv="", query_forms=None, entity_exists=None):
688
+ """Use the entity-authoring resolver for dynamic-form reference fields."""
689
+ entities = artifact.get("entities") or []
690
+ if not entities or entities[0].get("type") != "MainEntity":
691
+ return None
692
+ modeling = os.path.abspath(os.path.join(SCRIPT_DIR, "../../cosmic-meta-modeling/scripts"))
693
+ if modeling not in sys.path:
694
+ sys.path.insert(0, modeling)
695
+ from basedata_resolver import BasedataResolver
696
+ resolver = BasedataResolver(app_root=app_root, isv=isv, query_forms=query_forms, entity_exists=entity_exists)
697
+ resolved = {}
698
+ pending = []
699
+ for entity in entities:
700
+ for field in entity.get("fields") or []:
701
+ if field.get("dataType") not in {"BasedataField", "MulBasedataField"}:
702
+ continue
703
+ reference = field.get("reference") or {}
704
+ selector = str(reference.get("formNumber") or field.get("prdRef") or field.get("basedataNumber") or "").strip()
705
+ if reference and reference.get("mode") != "platform":
706
+ pending.append({"field": field.get("bizWord"), "reason": "dynamic_reference_requires_platform_target"})
707
+ continue
708
+ if not selector:
709
+ pending.append({"field": field.get("bizWord"), "reason": "reference_target_missing"})
710
+ continue
711
+ if selector not in resolved:
712
+ resolved[selector] = resolver.resolve(prd_ref=selector)
713
+ target = resolved[selector]
714
+ if target.is_ok:
715
+ field["basedataNumber"] = target.number
716
+ continue
717
+ pending.append({"field": field.get("bizWord"), "selector": selector, "reason": target.reason,
718
+ "status": target.status, "candidates": [item.to_dict() for item in target.candidates]})
719
+ if not pending:
720
+ return None
721
+ return {
722
+ "status": False, "errorCode": "DYNAMIC_REFERENCE_TARGET_UNRESOLVED",
723
+ "message": "Resolve declared reference targets before creation; preserve the requested reference field types.",
724
+ "writeAttempted": False, "metadataChanged": False, "retryAdvice": "resolve_reference_target",
725
+ "data": {"unresolvedReferences": pending},
726
+ }
727
+
728
+
729
+ def _dynamic_creation_receipt(app_root, artifact_path, result=None):
730
+ """Retain a partial creation receipt for the same request across input changes."""
731
+ if not app_root:
732
+ return None
733
+ identity = os.path.normcase(os.path.abspath(artifact_path))
734
+ key = hashlib.sha256(identity.encode("utf-8")).hexdigest()[:24]
735
+ receipt = os.path.join(app_root, "_runtime", "cosmic-metadata", "output", "retained-run-" + key + ".json")
736
+ if result is not None:
737
+ if result.get("metadataRetained") is not True or result.get("writeAttempted") is not True:
738
+ return None
739
+ retained = dict(result)
740
+ retained.update(status=False, errorCode="DYNAMIC_FORM_RECOVERY_REQUIRED", doNotRetryRun=True,
741
+ terminal=True, retryable=False, retryAdvice="inspect_retained_form", artifactPath=identity,
742
+ allowedNextActions=["inspect_retained_form"])
743
+ rows = (result.get("data") or {}).get("entityResults") or []
744
+ retained["reconciliation"] = [{
745
+ "command": "getFormSchema", "formId": row["formId"],
746
+ "arguments": ["--stdout", "getFormSchema", "--app-root", os.path.abspath(app_root), "--formId", row["formId"]],
747
+ } for row in rows if isinstance(row, dict) and row.get("formId")]
748
+ retained["modelInstruction"] = "This creation request already wrote metadata. Inspect the returned formId and repair the existing form; changing its artifact does not authorize another creation."
749
+ os.makedirs(os.path.dirname(receipt), exist_ok=True)
750
+ with open(receipt, "w", encoding="utf-8") as stream:
751
+ json.dump(retained, stream, ensure_ascii=False, indent=2)
752
+ return retained
753
+ if not os.path.isfile(receipt):
754
+ return None
755
+ with open(receipt, "r", encoding="utf-8") as stream:
756
+ retained = json.load(stream)
757
+ return {**retained, "writeAttempted": False, "metadataChanged": False}
758
+
759
+
676
760
  def _dynamic_form_artifact_preflight(artifact):
677
761
  """The public run command enforces the same dynamic contract as the authoring validator."""
678
762
  roots = artifact.get("entities") or []
@@ -765,6 +849,7 @@ def _apply_build_failure_contract(payload, summary):
765
849
  if summary.get("errorCode") not in (
766
850
  "TARGET_ALREADY_EXISTS", "FUNCTIONAL_GROUP_MOUNT_INCOMPLETE",
767
851
  "DYNAMIC_FORM_IDENTITY_DRIFT", "POST_BUILD_CONTINUATION_REQUIRED",
852
+ "POST_BUILD_PHASE_FAILED",
768
853
  ):
769
854
  return payload
770
855
  payload["status"] = False
@@ -776,12 +861,58 @@ def _apply_build_failure_contract(payload, summary):
776
861
  "doNotRetryBuildMeta", "targetConflicts", "unitMountFailures",
777
862
  "requestedFormNumber", "actualFormNumbers",
778
863
  "allowedNextActions", "continuation", "continuations", "modelInstruction",
864
+ "retainedTargets", "reconciliation", "repairPlans", "failedPhase",
865
+ "notStartedArtifacts",
779
866
  ):
780
867
  if key in summary:
781
868
  payload[key] = summary[key]
782
869
  return payload
783
870
 
784
871
 
872
+ class _PostBuildWriteError(ValueError):
873
+ def __init__(self, message, result, pending_write):
874
+ super().__init__(message)
875
+ self.result = result
876
+ self.pending_write = pending_write
877
+
878
+
879
+ def _attach_post_build_phase_failure(summary, payload, *, phase, app_root,
880
+ artifact_path, pending_write=None):
881
+ """A successful root save survives a later write or verification failure."""
882
+ targets = [
883
+ {"formId": row["formId"],
884
+ "formNumber": row.get("formNumber") or row.get("entityKey"),
885
+ "formName": row.get("formName") or row.get("displayName"),
886
+ "failedPhase": phase}
887
+ for row in ((payload or {}).get("data") or {}).get("entityResults", [])
888
+ if isinstance(row, dict) and row.get("formId")
889
+ and (row.get("formNumber") or row.get("entityKey"))
890
+ ]
891
+ if not targets:
892
+ return summary
893
+ summary.update({
894
+ "ok": False, "errorCode": "POST_BUILD_PHASE_FAILED",
895
+ "message": f"The form exists; phase {phase} requires reconciliation before repair",
896
+ "failedPhase": phase, "terminal": True, "retryable": False,
897
+ "writeAttempted": True, "metadataChanged": True, "metadataRetained": True,
898
+ "doNotRetryBuildMeta": True, "retryAdvice": "inspect_retained_form",
899
+ "retainedTargets": targets,
900
+ "reconciliation": [{"command": "getFormSchema", "appRoot": app_root,
901
+ "input": {"formId": target["formId"],
902
+ "formNumber": target["formNumber"]}}
903
+ for target in targets],
904
+ "repairPlans": [{
905
+ "failedPhase": phase, "appRoot": app_root,
906
+ "artifactPath": os.path.abspath(artifact_path),
907
+ "pendingWrites": [pending_write] if pending_write else [],
908
+ "precondition": "Read the retained form first. Reuse matching elements; execute only writes proven absent. Stop on mismatched identities or unknown write outcomes.",
909
+ }],
910
+ "allowedNextActions": ["getFormSchema", "getProperties", "listElements"],
911
+ "modelInstruction": "Preserve the saved form and the original artifact. Inspect the exact retained target before a narrow existing-form repair; never rerun emit, runBatch or buildMeta for this artifact. Reconcile every failed or unknown post-write before retrying. The batch results use ok and phase, not success.",
912
+ })
913
+ return summary
914
+
915
+
785
916
  def _apply_dynamic_ui_failure_contract(payload, summary, error):
786
917
  """Expose a retained post-build UI failure on the public command envelope."""
787
918
  if not isinstance(payload, dict) or not isinstance(summary, dict):
@@ -995,6 +1126,7 @@ def _summarize_run_batch(summary):
995
1126
  target_conflicts = []
996
1127
  unit_mount_failures = []
997
1128
  continuations = []
1129
+ post_build_failures = []
998
1130
  for item in results:
999
1131
  if not isinstance(item, dict):
1000
1132
  continue
@@ -1013,6 +1145,8 @@ def _summarize_run_batch(summary):
1013
1145
  continuation = one.get("continuation")
1014
1146
  if isinstance(continuation, dict):
1015
1147
  continuations.append(continuation)
1148
+ if one.get("errorCode") == "POST_BUILD_PHASE_FAILED":
1149
+ post_build_failures.append(one)
1016
1150
  if not item.get("ok"):
1017
1151
  errors.extend(str(e) for e in (item.get("errors") or [])[:3 - len(errors)])
1018
1152
  if continuation is None:
@@ -1053,8 +1187,23 @@ def _summarize_run_batch(summary):
1053
1187
  }
1054
1188
  result = _attach_functional_group_mount_contract(result, unit_mount_failures)
1055
1189
  result = _attach_target_exists_contract(result, target_conflicts)
1056
- if continuations and result.get("errorCode") not in {
1190
+ if post_build_failures and result.get("errorCode") not in {
1057
1191
  "TARGET_ALREADY_EXISTS", "FUNCTIONAL_GROUP_MOUNT_INCOMPLETE",
1192
+ }:
1193
+ result.update({key: value for key, value in post_build_failures[0].items()
1194
+ if key in {"errorCode", "message", "terminal", "retryable",
1195
+ "retryAdvice", "writeAttempted", "metadataChanged",
1196
+ "metadataRetained", "doNotRetryBuildMeta",
1197
+ "allowedNextActions", "modelInstruction"}})
1198
+ for key in ("retainedTargets", "reconciliation", "repairPlans"):
1199
+ result[key] = [value for failure in post_build_failures for value in failure.get(key, [])]
1200
+ result["notStartedArtifacts"] = [item["artifactPath"] for item in results
1201
+ if item.get("writeAttempted") is False
1202
+ and item.get("artifactPath")]
1203
+ if continuations:
1204
+ result["continuations"] = continuations
1205
+ if continuations and result.get("errorCode") not in {
1206
+ "TARGET_ALREADY_EXISTS", "FUNCTIONAL_GROUP_MOUNT_INCOMPLETE", "POST_BUILD_PHASE_FAILED",
1058
1207
  }:
1059
1208
  result.update({
1060
1209
  "ok": False,
@@ -1079,6 +1228,11 @@ def _summarize_run_batch(summary):
1079
1228
  "once against its retained form, then continue the remaining metadata scope."
1080
1229
  ),
1081
1230
  })
1231
+ attempts = [item.get("writeAttempted", (item.get("summary") or {}).get("writeAttempted",
1232
+ (item.get("result") or {}).get("writeAttempted"))) for item in results if isinstance(item, dict)]
1233
+ if attempts:
1234
+ result["writeAttempted"] = (True if any(value is True for value in attempts)
1235
+ else False if all(value is False for value in attempts) else None)
1082
1236
  return result
1083
1237
 
1084
1238
 
@@ -1242,6 +1396,108 @@ def _resolve_app_root_arg(args):
1242
1396
  return os.path.abspath(raw)
1243
1397
 
1244
1398
 
1399
+ def _normalize_key_property(args, properties):
1400
+ result = dict(properties)
1401
+ selector = "Key" if "Key" in result else "key" if "key" in result else None
1402
+ if not selector:
1403
+ return result
1404
+ workspace = _load_workspace_at(_resolve_app_root_arg(args)) or {}
1405
+ standard = workspace.get("standard")
1406
+ if isinstance(standard, str):
1407
+ standard = standard.strip().lower() == "true"
1408
+ result[selector] = make_entity_key_for(
1409
+ str(result[selector] or "").strip(), str(workspace.get("isv") or ""),
1410
+ standard=standard)
1411
+ return result
1412
+
1413
+
1414
+ def _rename_element_key(cfg, token, args):
1415
+ """Rename one metadata key and migrate observable rule string references."""
1416
+ old_key = resolve_element_selector(cfg, token, args.formNumber, args.element)
1417
+ normalized = _normalize_key_property(args, {"Key": args.new_key})
1418
+ new_key = str(normalized["Key"] or "").strip()
1419
+ if not new_key or new_key == old_key:
1420
+ return {
1421
+ "status": True, "errorCode": "0", "writeAttempted": False,
1422
+ "data": {"alreadySatisfied": True, "changed": False, "formNumber": args.formNumber,
1423
+ "oldKey": old_key, "newKey": new_key},
1424
+ "verification": {"verified": True, "preservationVerified": True},
1425
+ }
1426
+ before_elements = do_list_elements(cfg, token, args.formNumber)
1427
+ before_rows = before_elements.get("data") if _is_api_success(before_elements) else []
1428
+ before = next((row for row in before_rows if isinstance(row, dict)
1429
+ and str(row.get("key") or "") == old_key), None)
1430
+ if before is None:
1431
+ return {"status": False, "errorCode": "RENAME_TARGET_NOT_FOUND",
1432
+ "message": "The exact current element is absent; nothing was written",
1433
+ "writeAttempted": False}
1434
+ rules_result = do_list_rules(cfg, token, args.formNumber)
1435
+ if not _is_api_success(rules_result):
1436
+ return {"status": False, "errorCode": "RENAME_REFERENCE_READ_FAILED",
1437
+ "message": "Cannot inspect rule references before renaming; nothing was written",
1438
+ "writeAttempted": False, "data": {"cause": rules_result}}
1439
+ pattern = re.compile(rf"(?<![A-Za-z0-9_]){re.escape(old_key)}(?![A-Za-z0-9_])")
1440
+ patches = []
1441
+ for rule in (rules_result.get("data") or {}).get("rules") or []:
1442
+ if not isinstance(rule, dict):
1443
+ continue
1444
+ patch = {
1445
+ "ruleType": rule.get("ruleType"), "ruleId": rule.get("ruleId"),
1446
+ }
1447
+ for source in ("preCondition", "triggerField"):
1448
+ value = rule.get(source)
1449
+ if isinstance(value, str) and pattern.search(value):
1450
+ patch[source] = pattern.sub(new_key, value)
1451
+ if len(patch) > 2:
1452
+ patches.append(patch)
1453
+ written = do_set_properties_verified(
1454
+ cfg, token, args.formNumber, old_key, {"Key": new_key})
1455
+ if not _is_api_success(written) or not (written.get("verification") or {}).get("verified"):
1456
+ return written
1457
+ migrated = []
1458
+ for patch in patches:
1459
+ updated = do_update_rule_verified(cfg, token, args.formNumber, patch)
1460
+ if not _is_api_success(updated) or not (updated.get("verification") or {}).get("verified"):
1461
+ rollback = do_set_properties_verified(
1462
+ cfg, token, args.formNumber, new_key, {"Key": old_key})
1463
+ return {
1464
+ "status": False, "errorCode": "RENAME_REFERENCE_MIGRATION_FAILED",
1465
+ "message": "Key changed but a rule reference migration failed; rollback was attempted",
1466
+ "writeAttempted": True, "metadataRetained": not (
1467
+ _is_api_success(rollback) and (rollback.get("verification") or {}).get("verified")),
1468
+ "data": {"failedRule": patch, "ruleResult": updated, "rollback": rollback},
1469
+ }
1470
+ migrated.append(str(patch.get("ruleId") or ""))
1471
+ after_elements = do_list_elements(cfg, token, args.formNumber)
1472
+ after_rows = after_elements.get("data") if _is_api_success(after_elements) else []
1473
+ after = next((row for row in after_rows if isinstance(row, dict)
1474
+ and str(row.get("key") or "") == new_key), None)
1475
+ preserved = bool(after) and all(
1476
+ before.get(key) == after.get(key) for key in ("name", "type", "parentKey", "index")
1477
+ )
1478
+ final_rules = do_list_rules(cfg, token, args.formNumber)
1479
+ stale = [
1480
+ str(rule.get("ruleId") or "") for rule in ((final_rules.get("data") or {}).get("rules") or [])
1481
+ if isinstance(rule, dict) and any(
1482
+ isinstance(rule.get(key), str) and pattern.search(rule[key])
1483
+ for key in ("preCondition", "triggerField")
1484
+ )
1485
+ ] if _is_api_success(final_rules) else ["<read-failed>"]
1486
+ verified = after is not None and preserved and not stale
1487
+ return {
1488
+ "status": verified, "errorCode": "0" if verified else "RENAME_READBACK_MISMATCH",
1489
+ "message": None if verified else "Renamed key did not preserve the requested observable state",
1490
+ "writeAttempted": True, "metadataRetained": True,
1491
+ "data": {"changed": True, "verified": verified, "formNumber": args.formNumber,
1492
+ "oldKey": old_key, "newKey": new_key, "migratedRuleIds": migrated,
1493
+ "staleRuleIds": stale},
1494
+ "verification": {"verified": verified, "preservationVerified": preserved,
1495
+ "oldKeyAbsent": not any(isinstance(row, dict)
1496
+ and str(row.get("key") or "") == old_key for row in after_rows),
1497
+ "newKeyPresent": after is not None, "staleRuleIds": stale},
1498
+ }
1499
+
1500
+
1245
1501
  def _assert_workspace_matches_archive(app_root, workspace):
1246
1502
  """Fail closed before a platform write if appRoot and workspace identities drift."""
1247
1503
  if not app_root:
@@ -1311,6 +1567,15 @@ def _assert_platform_target_matches_archive(
1311
1567
  )
1312
1568
 
1313
1569
 
1570
+ def _unit_app_target(app_root, biz_app_id=None, biz_app_number=None):
1571
+ if not app_root:
1572
+ return biz_app_id, biz_app_number
1573
+ workspace = _load_workspace_at(app_root)
1574
+ _assert_platform_target_matches_archive(app_root, workspace, biz_app_id=biz_app_id, biz_app_number=biz_app_number)
1575
+ app_id = biz_app_id or workspace.get("bizAppId")
1576
+ return app_id, biz_app_number or (None if app_id else workspace.get("appNumber"))
1577
+
1578
+
1314
1579
  def _retarget_runtime_dir(app_root):
1315
1580
  """Re-point global RUNTIME_DIR/WORKSPACE_JSON to `{app_root}/_runtime/cosmic-metadata`.
1316
1581
 
@@ -1323,6 +1588,9 @@ def _retarget_runtime_dir(app_root):
1323
1588
  if not app_root:
1324
1589
  return
1325
1590
  resolved = os.path.abspath(os.path.expanduser(app_root))
1591
+ # kd_token_cache resolves its workspace independently from _core.config.
1592
+ # Keep authentication state under the same explicit appRoot as metadata state.
1593
+ os.environ["PROJECT_ROOT"] = resolved
1326
1594
  from _core import config as _cfg
1327
1595
  _cfg._init_paths(app_root=resolved)
1328
1596
  RUNTIME_DIR = _cfg.RUNTIME_DIR
@@ -1708,6 +1976,9 @@ def _resolve_owned_report_target(cfg, token, args, command):
1708
1976
 
1709
1977
  def _attach_report_readback(cfg, token, form_id, section, expected, result_data):
1710
1978
  schema = do_get_report_schema(cfg, token, form_id=form_id)
1979
+ if section == "filters" and any(item.get("source") for item in expected):
1980
+ from _core.report_ops import report_filter_source_expectations
1981
+ schema["filterSourceExpectations"] = report_filter_source_expectations(cfg, token, expected)
1711
1982
  if not schema.get("readBackAvailable"):
1712
1983
  # No canonical read-back on this deployment; the write already returned platform
1713
1984
  # success, so report succeeded-but-unverified, not a mismatch.
@@ -2658,11 +2929,14 @@ from _core.formatters import ( # noqa: F401
2658
2929
  _print_forms, _print_schema, _print_templates,
2659
2930
  _print_form_layout_tree, _print_panels,
2660
2931
  _print_find_results, _print_biz_hierarchy,
2661
- pjson, _transform_build_meta_result,
2932
+ pjson, _transform_build_meta_result, save_run_batch_result,
2662
2933
  set_output_file, set_stdout_mode, set_current_command, _is_en,
2663
2934
  )
2664
2935
 
2665
2936
 
2937
+ from execution_binding import execution_binding, validate_execution_binding
2938
+ from _core.execution_errors import attach_execution_error
2939
+
2666
2940
  _FORMATTER_PJSON = pjson
2667
2941
  _CLI_MACHINE_MODE = False
2668
2942
  _CLI_RESULT_EMITTED = False
@@ -2675,6 +2949,27 @@ class _CliUsageError(ValueError):
2675
2949
  self.recovery = recovery
2676
2950
 
2677
2951
 
2952
+ def _load_mapping_request(args):
2953
+ request = _load_json_arg(args.input_file)
2954
+ if not isinstance(request, dict):
2955
+ raise _CliUsageError("--input must contain a JSON object with mappings")
2956
+ overrides = {
2957
+ "entity": args.entity,
2958
+ "conditionField": args.condition_field,
2959
+ "operator": args.operator,
2960
+ "value": args.value,
2961
+ "events": args.events,
2962
+ "addEvents": args.add_events,
2963
+ }
2964
+ for key, value in overrides.items():
2965
+ if value is None or value == "":
2966
+ continue
2967
+ if key in request and request[key] != value:
2968
+ raise _CliUsageError(f"--input conflicts with --{key}: {request[key]!r} != {value!r}")
2969
+ request[key] = value
2970
+ return request
2971
+
2972
+
2678
2973
  class _CliResultFailure(Exception):
2679
2974
  def __init__(self, exit_code=1):
2680
2975
  super().__init__("structured command failure")
@@ -2707,6 +3002,14 @@ class _CliArgumentParser(argparse.ArgumentParser):
2707
3002
  def error(self, message):
2708
3003
  if "--stdout" in sys.argv[1:]:
2709
3004
  command = self.prog.rsplit(" ", 1)[-1]
3005
+ selected_parser = self
3006
+ for action in self._actions:
3007
+ if isinstance(action, argparse._SubParsersAction):
3008
+ selected = next((arg for arg in sys.argv[1:] if arg in action.choices), None)
3009
+ if selected:
3010
+ command = selected
3011
+ selected_parser = action.choices[selected]
3012
+ message = re.sub(r"\s*\(choose from .+\)\s*$", "", str(message))
2710
3013
  if _shell_split_inline_json(message):
2711
3014
  # Prefixed so `_exception_error_code` publishes the transport code: the caller
2712
3015
  # must change how the payload is passed, not what the payload says.
@@ -2718,8 +3021,8 @@ class _CliArgumentParser(argparse.ArgumentParser):
2718
3021
  " that path as the same argument. argparse reported: " + str(message or "")
2719
3022
  )
2720
3023
  raise _CliUsageError(message, {
2721
- "usage": self.format_usage().strip() if command in _CATALOG_COMMAND_POLICIES else f"{self.prog} <command> [arguments]",
2722
- "describeCommand": f"describeCommand {command}" if command in _CATALOG_COMMAND_POLICIES else "catalog",
3024
+ "usage": selected_parser.format_usage().strip() if command in _CATALOG_COMMAND_POLICIES else f"{self.prog} <command> [arguments]",
3025
+ "describeCommand": f"describeCommand {command}" if command in _CATALOG_COMMAND_POLICIES else "describeCommand <selected-command>",
2723
3026
  })
2724
3027
  super().error(message)
2725
3028
 
@@ -2731,7 +3034,7 @@ def _is_machine_failure(result):
2731
3034
  def pjson(obj, force_stdout=False):
2732
3035
  """Emit one structured result and make semantic failure affect the exit code."""
2733
3036
  global _CLI_RESULT_EMITTED
2734
- obj = _attach_verified_form_delivery(obj, _CLI_DELIVERY_ARGS)
3037
+ obj = attach_execution_error(_attach_verified_form_delivery(obj, _CLI_DELIVERY_ARGS))
2735
3038
  _FORMATTER_PJSON(obj, force_stdout=force_stdout)
2736
3039
  _CLI_RESULT_EMITTED = True
2737
3040
  if _CLI_MACHINE_MODE and _is_machine_failure(obj):
@@ -2739,14 +3042,16 @@ def pjson(obj, force_stdout=False):
2739
3042
 
2740
3043
 
2741
3044
  def _persist_domain_model(result, output_file, model_type):
2742
- if _is_machine_failure(result): return result
3045
+ if _is_machine_failure(result):
3046
+ return result
2743
3047
  raw = (result or {}).get("data")
2744
3048
  if not isinstance(raw, str) or not raw.strip():
2745
- raise ValueError("DOMAIN_MODEL_DATA_INVALID: 平台未返回元模型 JSON 文本")
3049
+ raise ValueError("DOMAIN_MODEL_DATA_INVALID: platform returned no JSON text")
2746
3050
  path = os.path.abspath(output_file)
2747
3051
  os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
2748
3052
  with open(path, "w", encoding="utf-8", newline="\n") as stream:
2749
- stream.write(raw); stream.write("\n")
3053
+ stream.write(raw)
3054
+ stream.write("\n")
2750
3055
  return {"status": True, "errorCode": "0", "message": None, "data": {
2751
3056
  "modelType": model_type, "path": path, "bytes": os.path.getsize(path),
2752
3057
  "sha256": hashlib.sha256(raw.encode("utf-8")).hexdigest(),
@@ -2760,6 +3065,10 @@ def _exception_error_code(exc):
2760
3065
  return match.group(1), match.group(2) or text
2761
3066
  if isinstance(exc, _CliUsageError):
2762
3067
  return "CLI_ARGUMENT_ERROR", text
3068
+ if isinstance(exc, ImportError):
3069
+ return "SKILL_INSTALLATION_ERROR", text
3070
+ if isinstance(exc, (TimeoutError, ConnectionError)):
3071
+ return "ENVIRONMENT_RUNTIME_ERROR", text
2763
3072
  return "CLI_EXECUTION_ERROR", text or exc.__class__.__name__
2764
3073
 
2765
3074
 
@@ -2771,6 +3080,13 @@ def _emit_machine_error(error_code, message, recovery=None):
2771
3080
  "errorCode": str(error_code or "CLI_EXECUTION_ERROR"),
2772
3081
  "message": str(message or "Command failed"),
2773
3082
  "data": None,
3083
+ "errorCategory": attach_execution_error({"status": False, "errorCode": error_code})["errorCategory"],
3084
+ "capabilityLimitConfirmed": error_code == "FULL_METADATA_API_UNAVAILABLE",
3085
+ "executionBinding": execution_binding(),
3086
+ **({"writeAttempted": False} if error_code in {
3087
+ "SKILL_SOURCE_MISMATCH", "SKILL_REVISION_MISMATCH", "ENVIRONMENT_BINDING_MISMATCH",
3088
+ "CONDITIONAL_CARRY_SOURCE_PROPERTY_UNAVAILABLE",
3089
+ } else {}),
2774
3090
  **({"recovery": recovery} if recovery else {}),
2775
3091
  },
2776
3092
  force_stdout=True,
@@ -2886,12 +3202,16 @@ from _core.commands import ( # noqa: F401
2886
3202
  do_ensure_conditional_entry_style,
2887
3203
  do_ensure_conditional_control_state,
2888
3204
  do_ensure_conditional_clear_fields,
3205
+ do_ensure_conditional_field_change,
2889
3206
  do_ensure_conditional_copy_field_value,
3207
+ do_ensure_copy_field_mappings,
2890
3208
  do_ensure_conditional_carry_reference_property,
3209
+ do_ensure_carry_reference_mappings,
2891
3210
  do_ensure_conditional_calculation,
2892
3211
  do_ensure_quantity_conversion,
2893
3212
  do_ensure_conditional_field_state,
2894
3213
  do_ensure_field_calculation,
3214
+ do_ensure_time_difference_calculation,
2895
3215
  do_ensure_rule_function_formula,
2896
3216
  do_ensure_rule_set,
2897
3217
  do_update_rule, do_update_rule_verified,
@@ -2906,7 +3226,7 @@ from _core.commands import ( # noqa: F401
2906
3226
  do_describe_property_input,
2907
3227
  do_set_properties, do_set_properties_verified,
2908
3228
  do_apply_property_policy, validate_property_policy_input,
2909
- control_catalog, validate_control_batch_input, do_add_controls,
3229
+ control_catalog, control_input_schema, validate_control_batch_input, do_add_controls,
2910
3230
  do_set_summary_to_field_resolved,
2911
3231
  resolve_element_rows, resolve_element_selector, resolve_element_for_properties,
2912
3232
  compile_must_input_equality,
@@ -2917,7 +3237,7 @@ from _core.commands import ( # noqa: F401
2917
3237
  do_set_combo_items, do_set_ref_props, do_set_display_prop,
2918
3238
  read_ref_props, ref_props_names,
2919
3239
  do_set_permission_control, do_set_business_control,
2920
- do_set_complex_property, do_set_custom_style, do_set_collapse_summary, do_list_parameter_forms,
3240
+ do_set_complex_property, do_configure_dropdown_menu, do_set_custom_style, do_set_collapse_summary, do_list_parameter_forms,
2921
3241
  do_ensure_tab_page_operations,
2922
3242
  do_search_basedata, do_get_basedata_entity_id,
2923
3243
  # List/filter/toolbar shortcut commands (ported from bos-designer copy)
@@ -3021,7 +3341,7 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3021
3341
  """
3022
3342
  if not isinstance(cmd_args, dict):
3023
3343
  raise ValueError("serve args must be an object")
3024
- if cmd in {"modifyMeta", "batchModify", "deleteForm", "enableDisableForm", "inheritForm", "layoutForm"}:
3344
+ if cmd in {"modifyMeta", "batchModify", "deleteForm", "enableDisableForm", "inheritForm", "layoutForm", "registerPlugin"}:
3025
3345
  app_root = str(cmd_args.get("appRoot") or "").strip()
3026
3346
  if not app_root:
3027
3347
  raise ValueError(
@@ -3186,9 +3506,13 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3186
3506
  )
3187
3507
 
3188
3508
  elif cmd == "getDomainModel":
3189
- return do_get_domain_model(cfg, token, cmd_args["modelType"],
3190
- only_entity=cmd_args.get("onlyEntity"), ext=cmd_args.get("ext"),
3191
- bizext=cmd_args.get("bizext"), app_id=cmd_args.get("appId"))
3509
+ return do_get_domain_model(
3510
+ cfg, token, cmd_args["modelType"],
3511
+ only_entity=cmd_args.get("onlyEntity"),
3512
+ ext=cmd_args.get("ext"),
3513
+ bizext=cmd_args.get("bizext"),
3514
+ app_id=cmd_args.get("appId"),
3515
+ )
3192
3516
 
3193
3517
  elif cmd == "queryPublicTemplates":
3194
3518
  return do_query_public_templates(
@@ -3312,7 +3636,8 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3312
3636
  return do_register_plugin(
3313
3637
  cfg, token,
3314
3638
  form_number=cmd_args.get("formNumber"),
3315
- plugin_config=cmd_args.get("pluginConfig", {})
3639
+ plugin_config=normalize_plugin_registration_input(cmd_args.get("pluginConfig", {})),
3640
+ app_root=cmd_args.get("appRoot"),
3316
3641
  )
3317
3642
 
3318
3643
  elif cmd == "updatePlugin":
@@ -3370,6 +3695,16 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3370
3695
  right=cmd_args.get("right"),
3371
3696
  )
3372
3697
 
3698
+ elif cmd == "ensureTimeDifferenceCalculation":
3699
+ return do_ensure_time_difference_calculation(
3700
+ cfg, token,
3701
+ form_number=cmd_args.get("formNumber"),
3702
+ entity=cmd_args.get("entity"),
3703
+ target=cmd_args.get("target"),
3704
+ start=cmd_args.get("start"),
3705
+ end=cmd_args.get("end"),
3706
+ )
3707
+
3373
3708
  elif cmd == "ensureRuleFunctionFormula":
3374
3709
  return do_ensure_rule_function_formula(
3375
3710
  cfg, token,
@@ -3389,6 +3724,8 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3389
3724
  value=cmd_args.get("value"),
3390
3725
  target=cmd_args.get("target"),
3391
3726
  behavior=cmd_args.get("behavior"),
3727
+ add_events=cmd_args.get("addEvents"),
3728
+ events=cmd_args.get("events"),
3392
3729
  )
3393
3730
 
3394
3731
  elif cmd == "ensureConditionalEntryStyle":
@@ -3423,9 +3760,24 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3423
3760
  form_number=cmd_args.get("formNumber"), entity=cmd_args.get("entity"),
3424
3761
  condition_field=cmd_args.get("conditionField"), operator=cmd_args.get("operator"),
3425
3762
  value=cmd_args.get("value"), targets=cmd_args.get("targets"),
3763
+ add_events=cmd_args.get("addEvents"),
3764
+ )
3765
+
3766
+ elif cmd == "ensureConditionalFieldChange":
3767
+ return do_ensure_conditional_field_change(
3768
+ cfg, token, form_number=cmd_args.get("formNumber"),
3769
+ entity=cmd_args.get("entity"),
3770
+ condition_field=cmd_args.get("conditionField"),
3771
+ operator=cmd_args.get("operator"), value=cmd_args.get("value"),
3772
+ target=cmd_args.get("target"), add_events=cmd_args.get("addEvents"),
3773
+ events=cmd_args.get("events"),
3426
3774
  )
3427
3775
 
3428
3776
  elif cmd == "ensureConditionalCopyFieldValue":
3777
+ if isinstance(cmd_args.get("input"), dict):
3778
+ return do_ensure_copy_field_mappings(
3779
+ cfg, token, cmd_args.get("formNumber"), cmd_args["input"],
3780
+ )
3429
3781
  return do_ensure_conditional_copy_field_value(
3430
3782
  cfg, token,
3431
3783
  form_number=cmd_args.get("formNumber"), entity=cmd_args.get("entity"),
@@ -3435,6 +3787,10 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3435
3787
  )
3436
3788
 
3437
3789
  elif cmd == "ensureConditionalCarryReferenceProperty":
3790
+ if isinstance(cmd_args.get("input"), dict):
3791
+ return do_ensure_carry_reference_mappings(
3792
+ cfg, token, cmd_args.get("formNumber"), cmd_args["input"],
3793
+ )
3438
3794
  return do_ensure_conditional_carry_reference_property(
3439
3795
  cfg, token,
3440
3796
  form_number=cmd_args.get("formNumber"), entity=cmd_args.get("entity"),
@@ -3903,9 +4259,10 @@ def _dispatch_serve(cfg, token, cmd, cmd_args):
3903
4259
  elif cmd == "getUnit":
3904
4260
  return do_get_unit(cfg, token, cmd_args.get("bizAppId"), cmd_args.get("unitId"))
3905
4261
  elif cmd == "addUnit":
4262
+ unit_app_id, unit_app_number = _unit_app_target(cmd_args.get("appRoot"), cmd_args.get("bizAppId"), cmd_args.get("bizAppNumber"))
3906
4263
  return do_add_unit(cfg, token,
3907
- biz_app_id=cmd_args.get("bizAppId"),
3908
- biz_app_number=cmd_args.get("bizAppNumber"),
4264
+ biz_app_id=unit_app_id,
4265
+ biz_app_number=unit_app_number,
3909
4266
  name=cmd_args.get("name"),
3910
4267
  number=cmd_args.get("number"),
3911
4268
  description=cmd_args.get("description"),
@@ -4320,7 +4677,8 @@ def _form_delivery(app_root, numbers):
4320
4677
  "instruction": (
4321
4678
  "写入及回读完成,产物交付尚未完成。若当前请求没有其他明确修改,下一步执行 command,"
4322
4679
  "按 references/delivery.md 完成本轮交付;否则先完成剩余修改,再按应用汇总本轮目标收口。"
4323
- "宿主提供 output_check 时在取得卡片后调用,并将返回的卡片协议块原样放入最终答复。"
4680
+ "这是 app-build 的领域交付,不代表业务验收完成。独立 app-build 在最终展示卡片时调用 output_check;"
4681
+ "专家团委托实施时保留卡片与目标引用,继续负责人安排的 E2E,再由负责人统一最终交付。"
4324
4682
  "此前轮次已展示该表单不代表本轮修改已交付。"
4325
4683
  "不要重放已完成的写入,不在中间步骤同步 DYM。"
4326
4684
  ),
@@ -4592,7 +4950,7 @@ def _serve_loop(cfg):
4592
4950
  token = do_auth(cfg)
4593
4951
 
4594
4952
  # Ready signal
4595
- print("###SERVE_READY###", flush=True)
4953
+ print("###SERVE_READY###", file=sys.stdout, flush=True)
4596
4954
 
4597
4955
  for line in sys.stdin:
4598
4956
  line = line.strip()
@@ -4601,7 +4959,7 @@ def _serve_loop(cfg):
4601
4959
  try:
4602
4960
  request = json.loads(line)
4603
4961
  except (json.JSONDecodeError, ValueError) as e:
4604
- print(json.dumps({"success": False, "error": "Invalid JSON: %s" % str(e)}, ensure_ascii=False), flush=True)
4962
+ print(json.dumps({"success": False, "error": "Invalid JSON: %s" % str(e)}, ensure_ascii=False), file=sys.stdout, flush=True)
4605
4963
  continue
4606
4964
 
4607
4965
  cmd = request.get("cmd", "")
@@ -4610,14 +4968,14 @@ def _serve_loop(cfg):
4610
4968
  if cmd == "exit":
4611
4969
  break
4612
4970
  if cmd == "ping":
4613
- print(json.dumps({"success": True, "data": "pong"}, ensure_ascii=False), flush=True)
4971
+ print(json.dumps({"success": True, "data": "pong"}, ensure_ascii=False), file=sys.stdout, flush=True)
4614
4972
  continue
4615
4973
 
4616
4974
  try:
4617
4975
  result = _dispatch_serve(cfg, token, cmd, cmd_args)
4618
- print(json.dumps({"success": _serve_result_success(result), "data": result}, ensure_ascii=False), flush=True)
4976
+ print(json.dumps({"success": _serve_result_success(result), "data": result}, ensure_ascii=False), file=sys.stdout, flush=True)
4619
4977
  except Exception as e:
4620
- print(json.dumps({"success": False, "error": str(e)}, ensure_ascii=False), flush=True)
4978
+ print(json.dumps({"success": False, "error": str(e)}, ensure_ascii=False), file=sys.stdout, flush=True)
4621
4979
 
4622
4980
 
4623
4981
  # ─── Report Pipeline ─────────────────────────────────────────────────────────
@@ -4933,6 +5291,9 @@ def _execute_report_pipeline(cfg, token, artifact, biz_app_id, biz_unit_id, app_
4933
5291
  if columns:
4934
5292
  checks.append(report_readback_verification("columns", columns, final_schema))
4935
5293
  if filters:
5294
+ if any(item.get("source") for item in filters):
5295
+ from _core.report_ops import report_filter_source_expectations
5296
+ final_schema["filterSourceExpectations"] = report_filter_source_expectations(cfg, token, filters)
4936
5297
  checks.append(report_readback_verification("filters", filters, final_schema))
4937
5298
  if sorts and "reportSorts" not in unverifiable:
4938
5299
  checks.append(report_readback_verification("sorts", sorts, final_schema))
@@ -5022,7 +5383,8 @@ def _execute_report_pipeline(cfg, token, artifact, biz_app_id, biz_unit_id, app_
5022
5383
  _plugin_contract = (
5023
5384
  None if (query_data_source and not plugins)
5024
5385
  else compile_report_plugin_contract(
5025
- {"reportColumns": columns, "reportFilters": filters, "reportPlugins": plugins},
5386
+ {"reportColumns": columns, "reportFilters": filters, "reportPlugins": plugins,
5387
+ "reportProperties": artifact.get("reportProperties") or {}},
5026
5388
  form_number,
5027
5389
  )
5028
5390
  )
@@ -5081,12 +5443,14 @@ def _execute_report_pipeline(cfg, token, artifact, biz_app_id, biz_unit_id, app_
5081
5443
 
5082
5444
  _CATALOG_INTERNAL_COMMANDS = {"catalog", "describeCommand"}
5083
5445
  _CATALOG_PAYLOAD_SCHEMA_REFS = {
5446
+ "addControls": "skills/cosmic-meta-api/contracts/cli/control-authoring.json#/inputSchema",
5084
5447
  "modifyMeta": "skills/cosmic-meta-api/contracts/schema/metaops-structural.schema.json",
5085
5448
  "batchModify": "skills/cosmic-meta-api/contracts/schema/batch-modify-manifest.schema.json",
5086
5449
  "applyContainerLayout": "skills/cosmic-meta-api/contracts/schema/container-layout.schema.json",
5087
5450
  "applyFormComposition": "skills/cosmic-meta-api/contracts/schema/form-composition.schema.json",
5088
5451
  "applyPanelOrder": "skills/cosmic-meta-api/contracts/schema/panel-order.schema.json",
5089
5452
  "configureEntryGrid": "skills/cosmic-meta-api/contracts/schema/entry-grid.schema.json",
5453
+ "configureDropdownMenu": "skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json",
5090
5454
  "applyFormUi": "skills/cosmic-meta-api/contracts/schema/existing-form-ui.schema.json",
5091
5455
  "applyFormGrid": "skills/cosmic-meta-api/contracts/schema/form-grid.schema.json",
5092
5456
  "ensureOperationValidation": "skills/cosmic-meta-api/contracts/schema/operation-validation.schema.json",
@@ -5135,6 +5499,7 @@ _CATALOG_COMMAND_CONTRACT_REFS = {
5135
5499
  "applyFormComposition": "skills/cosmic-meta-api/contracts/cli/layout-ui.json#commands/applyFormComposition",
5136
5500
  "applyPanelOrder": "skills/cosmic-meta-api/contracts/cli/layout-ui.json#commands/applyPanelOrder",
5137
5501
  "configureEntryGrid": "skills/cosmic-meta-api/contracts/cli/layout-ui.json#commands/configureEntryGrid",
5502
+ "configureDropdownMenu": "skills/cosmic-meta-api/contracts/cli/layout-ui.json#commands/configureDropdownMenu",
5138
5503
  "applyFormUi": "skills/cosmic-meta-api/contracts/cli/layout-ui.json#commands/applyFormUi",
5139
5504
  "ensureTabPage": "skills/cosmic-meta-api/contracts/cli/layout-ui.json#commands/ensureTabPage",
5140
5505
  "moveFormElement": "skills/cosmic-meta-api/contracts/cli/layout-ui.json#commands/moveFormElement",
@@ -5156,6 +5521,7 @@ _CATALOG_COMMAND_CONTRACT_REFS = {
5156
5521
  "deleteRule": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/deleteRule",
5157
5522
  "listRuleFunctions": "skills/cosmic-meta-api/contracts/cli/rule-functions.json",
5158
5523
  "ensureFieldCalculation": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureFieldCalculation",
5524
+ "ensureTimeDifferenceCalculation": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureTimeDifferenceCalculation",
5159
5525
  "ensureRuleFunctionFormula": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureRuleFunctionFormula",
5160
5526
  "ensureRuleSet": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureRuleSet",
5161
5527
  "ensureConditionalFieldState": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalFieldState",
@@ -5163,6 +5529,7 @@ _CATALOG_COMMAND_CONTRACT_REFS = {
5163
5529
  "ensureConditionalEntryStyle": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalEntryStyle",
5164
5530
  "ensureConditionalControlState": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalControlState",
5165
5531
  "ensureConditionalClearFields": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalClearFields",
5532
+ "ensureConditionalFieldChange": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalFieldChange",
5166
5533
  "ensureConditionalCopyFieldValue": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalCopyFieldValue",
5167
5534
  "ensureConditionalCarryReferenceProperty": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalCarryReferenceProperty",
5168
5535
  "ensureConditionalCalculation": "skills/cosmic-meta-api/contracts/cli/rule-domain.json#commands/ensureConditionalCalculation",
@@ -5178,6 +5545,7 @@ _CATALOG_COMMAND_CONTRACT_REFS = {
5178
5545
  "setReportRowBackColor": "skills/cosmic-meta-api/contracts/cli/report-write-contracts.json#commands/setReportRowBackColor",
5179
5546
  "setReportFilterFieldConfig": "skills/cosmic-meta-api/contracts/cli/report-write-contracts.json#commands/setReportFilterFieldConfig",
5180
5547
  "setReportDefaultHiddenFields": "skills/cosmic-meta-api/contracts/cli/report-write-contracts.json#commands/setReportDefaultHiddenFields",
5548
+ "renameElementKey": "skills/cosmic-meta-api/contracts/cli/property-batch.json#commands/renameElementKey",
5181
5549
  "registerReportPluginExt": "skills/cosmic-meta-api/contracts/cli/report-write-contracts.json#commands/registerReportPluginExt",
5182
5550
  "setBasedataDefault": "skills/cosmic-meta-api/contracts/cli/property-value-encoding.json#defaultValueModel",
5183
5551
  "setCollapseSummary": "skills/cosmic-meta-api/contracts/cli/collapse-summary.json",
@@ -5223,10 +5591,10 @@ _CATALOG_COMMANDS_BY_EFFECT = {
5223
5591
  "resetEnv", "serve", "setConfig", "setInitialized", "setPdmConfig", "syncDym",
5224
5592
  }),
5225
5593
  "write": frozenset({
5226
- "addAdvancedContainer", "addButton", "addControls", "addEntryEntity", "addField", "addFieldPanel", "addFilterColumn", "addListColumn", "applyContainerLayout", "applyFormComposition", "applyPanelOrder", "applyFormUi", "applyFormGrid", "applyFormSplit", "applyFormTabs", "configureEntryGrid", "configureList", "configureSelectionFields", "ensureTabPage", "moveFormElement",
5594
+ "addAdvancedContainer", "addButton", "addControls", "addEntryEntity", "addField", "addFieldPanel", "addFilterColumn", "addListColumn", "applyContainerLayout", "applyFormComposition", "applyPanelOrder", "applyFormUi", "applyFormGrid", "applyFormSplit", "applyFormTabs", "configureDropdownMenu", "configureEntryGrid", "configureList", "configureSelectionFields", "ensureTabPage", "moveFormElement",
5227
5595
  "addReportColumns", "addSimpleField",
5228
5596
  "addMenu", "addOperation", "addRule", "addUnit", "batchModify",
5229
- "ensureConditionalCalculation", "ensureConditionalCarryReferenceProperty", "ensureConditionalClearFields", "ensureConditionalCopyFieldValue", "ensureConditionalControlState", "ensureConditionalClientAppearance", "ensureConditionalEntryStyle", "ensureConditionalFieldState", "ensureFieldCalculation", "ensureQuantityConversion", "ensureRuleFunctionFormula", "ensureRuleSet", "ensureTabPageOperations",
5597
+ "ensureConditionalCalculation", "ensureConditionalCarryReferenceProperty", "ensureConditionalClearFields", "ensureConditionalFieldChange", "ensureConditionalCopyFieldValue", "ensureConditionalControlState", "ensureConditionalClientAppearance", "ensureConditionalEntryStyle", "ensureConditionalFieldState", "ensureFieldCalculation", "ensureTimeDifferenceCalculation", "ensureQuantityConversion", "ensureRuleFunctionFormula", "ensureRuleSet", "ensureTabPageOperations",
5230
5598
  "bootstrapWorkspace", "buildMeta", "copyForm", "createPage", "createScript",
5231
5599
  "enableDisableForm", "extendApp", "extendForm", "extendFormFlow",
5232
5600
  "importForm", "inheritForm", "layoutForm", "modifyMeta", "moveForm",
@@ -5238,7 +5606,7 @@ _CATALOG_COMMANDS_BY_EFFECT = {
5238
5606
  "setDisplayProp", "setEditSearchProp", "setFeatures", "setFilter", "setFormBackground",
5239
5607
  "setFreezeList", "setInitField", "setMustInputCondition", "setNetworkControl",
5240
5608
  "setSplitTables", "setInstantSync",
5241
- "setOrgFuncs", "setOrgViewScheme", "setPermissionControl", "setProperties",
5609
+ "setOrgFuncs", "setOrgViewScheme", "setPermissionControl", "setProperties", "renameElementKey",
5242
5610
  "setRefLayout", "setRefProps", "setReportColumns",
5243
5611
  "setReportDefaultHiddenFields", "setReportFilterFieldConfig", "setReportFilters",
5244
5612
  "setReportProperties", "setReportQueryDataSource", "setReportRowBackColor",
@@ -5664,7 +6032,7 @@ _CATALOG_ARGUMENT_CONSTRAINTS = {
5664
6032
  "addField": [
5665
6033
  {
5666
6034
  "kind": "batch_fields",
5667
- "message": "--fields accepts name,bizWord,fieldType,combo,comboJson,required,fullLine and per-item panel/before/after. combo accepts a comma-separated string or JSON string array; comboJson accepts option objects. --validate-only checks all specs without authentication; ordered per-field writes share target preparation and stop at the first failure",
6035
+ "message": "--fields accepts name,bizWord,fieldType,combo,comboJson,required,fullLine,panel,before,after,basedata,mulTable,unitField,flexType,refBaseField,refDisplayProp,asstTypeId,asstType. BasedataField/MulBasedataField also accept reference:{mode:platform,formNumber:<exact number>} instead of basedata. comboJson uses stable value/label objects. --validate-only checks all specs without authentication; ordered writes share target preparation and stop at the first failure",
5668
6036
  },
5669
6037
  {
5670
6038
  "kind": "header_placement",
@@ -5750,7 +6118,7 @@ _CATALOG_ARGUMENT_CONSTRAINTS = {
5750
6118
  for _property_command in (
5751
6119
  "setProperties", "setMustInputCondition", "setDefValueDesign",
5752
6120
  "setBasedataDefault", "setRefProps", "setRefLayout", "setOrgFuncs",
5753
- "setOrgViewScheme", "setAsstTypeId", "setComplexProperty", "setCustomStyle", "setCollapseSummary", "ensureTabPageOperations",
6121
+ "setOrgViewScheme", "setAsstTypeId", "setComplexProperty", "configureDropdownMenu", "setCustomStyle", "setCollapseSummary", "ensureTabPageOperations",
5754
6122
  "setFeatures", "setFilter", "setDisplayProp", "setEditSearchProp", "setFormBackground",
5755
6123
  "setFreezeList", "setInitField", "setCompareTypeConfig",
5756
6124
  "setNetworkControl", "setPermissionControl", "setBusinessControl",
@@ -5778,6 +6146,8 @@ for _lifecycle_command in (
5778
6146
  "message": "the command resolves and verifies one exact existing form in the same invocation",
5779
6147
  })
5780
6148
  _CATALOG_CONDITIONAL_EFFECTS = {
6149
+ "runBatch": [{"when": {"reconcile_only": True}, "effect": "local", "risk": "low",
6150
+ "summary": "Reads platform state and updates only local recovery receipts; never creates metadata."}],
5781
6151
  "getApp": [
5782
6152
  {
5783
6153
  "when": {"sync_identity": True},
@@ -6015,9 +6385,14 @@ def _command_descriptor(subparsers, command, *, detailed=False):
6015
6385
  result_contract = "JSONL stdio" if command == "serve" else "JSON stdout"
6016
6386
  descriptor["execution"] = {
6017
6387
  "entry": os.path.abspath(__file__),
6388
+ "python": sys.executable,
6389
+ "argvPrefix": [sys.executable, os.path.abspath(__file__), "--stdout", command],
6390
+ "binding": execution_binding(),
6018
6391
  "template": (
6019
- f'{sys.executable} {os.path.abspath(__file__)} --stdout '
6020
- f'{command} ' + ('--app-root "<appRoot>" ' if requires_app_root else '') + '<arguments>'
6392
+ (subprocess.list2cmdline([sys.executable, os.path.abspath(__file__), "--stdout", command])
6393
+ if os.name == "nt" else shlex.join([sys.executable, os.path.abspath(__file__), "--stdout", command])) + " "
6394
+ + ('--app-root "<appRoot>" ' if requires_app_root else '')
6395
+ + '<arguments>'
6021
6396
  ),
6022
6397
  "result": result_contract,
6023
6398
  }
@@ -6067,7 +6442,10 @@ def _command_descriptor(subparsers, command, *, detailed=False):
6067
6442
  "humanGate": "use the token only after a later user message confirms the exact returned targets",
6068
6443
  }
6069
6444
  if payload_schema_ref:
6070
- descriptor["payloadSchema"] = _catalog_payload_schema(payload_schema_ref)
6445
+ descriptor["payloadSchema"] = (
6446
+ control_input_schema() if command == "addControls"
6447
+ else _catalog_payload_schema(payload_schema_ref)
6448
+ )
6071
6449
  return descriptor
6072
6450
 
6073
6451
 
@@ -6245,6 +6623,10 @@ def _main_impl():
6245
6623
  help="Exact existing application code; verifies and synchronizes a trusted appRoot identity before any build write")
6246
6624
  prb.add_argument("--emit-manifest", default=None, dest="emit_manifest",
6247
6625
  help="Exact _emit_manifest.json produced for this workNo; prevents cross-run artifact reuse")
6626
+ prb.add_argument("--reconcile-only", action="store_true",
6627
+ help="Read-only recovery of --resume-result: verify retained exact identities or confirm absence before a later resume")
6628
+ prb.add_argument("--resume-result", default=None,
6629
+ help="Original durable runBatch result; reuse verified objects and execute only confirmed unwritten inputs")
6248
6630
  prb.add_argument("--verify", action="store_true",
6249
6631
  help="After each successful buildMeta, exactly read back entities, fields and parent links")
6250
6632
  prb.add_argument("--bizAppId", required=False, default=None,
@@ -6276,13 +6658,13 @@ def _main_impl():
6276
6658
  pi.add_argument("--force", action="store_true",
6277
6659
  help="Force refresh, even if config.md is already initialized")
6278
6660
  pi.add_argument("--isv", default=None,
6279
- help="[Bootstrap mode] Temporarily override isv in config.json (for first-time initialization)")
6661
+ help="[Bootstrap mode] Temporarily override isv from ~/.kd/config.json (for first-time initialization)")
6280
6662
  pi.add_argument("--app-number", default=None, dest="app_number_override",
6281
- help="[Bootstrap mode] Temporarily override app_number in config.json (for first-time initialization)")
6663
+ help="[Bootstrap mode] Temporarily override app_number from ~/.kd/config.json (for first-time initialization)")
6282
6664
 
6283
- # setConfig (write selected values to config.json during AI-guided initialization)
6665
+ # setConfig (write selected values to ~/.kd/config.json / workspace.json during AI-guided initialization)
6284
6666
  psc = sub.add_parser("setConfig",
6285
- help="Write fields to scripts/config.json (no auth needed); credential fields and business fields can be written in one go")
6667
+ help="Write fields to ~/.kd/config.json default environment and workspace.json (no auth needed); credential fields and business fields can be written in one go")
6286
6668
  psc.add_argument("--app-number", default=None, dest="app_number",
6287
6669
  help="App number (written to workspace.json), e.g., uhyl_aireqdecomp")
6288
6670
  psc.add_argument("--biz-cloud-name", default=None, dest="biz_cloud_name",
@@ -6364,7 +6746,7 @@ def _main_impl():
6364
6746
 
6365
6747
  # queryFormsByApp (query within specified app/cloud scope)
6366
6748
  pqfa = sub.add_parser("queryFormsByApp", help="Query explicit app/cloud scope or the selected app-root; ignores ambient defaults. ZERO rows means no matches in that scope, not global absence. Use queryForms for global discovery.")
6367
- pqfa.add_argument("--appNumber", default=None,
6749
+ pqfa.add_argument("--appNumber", "--appCode", dest="appNumber", default=None,
6368
6750
  help="App number exact filter (e.g., uhyl_aireqdecomp)")
6369
6751
  pqfa.add_argument("--cloudNumber", default=None,
6370
6752
  help="Business cloud number exact filter (e.g., uhyl_bfyTest1)")
@@ -6372,9 +6754,9 @@ def _main_impl():
6372
6754
  "--keyword", "--form-name", "--form", "--formNumber",
6373
6755
  dest="keyword", default=None,
6374
6756
  help=(
6375
- "可选过滤条件:按表单名称或编码缩小结果;不传即可列出所选范围内全部表单。"
6376
- "无过滤条件时不要传空值或自行构造占位文本;--form-name、--form 和 "
6377
- "--formNumber 是兼容的参数写法"
6757
+ "Optional filter by form name or number; omit it to list all forms in the selected scope. "
6758
+ "Do not pass an empty or invented placeholder when no filter is intended; --form-name, "
6759
+ "--form and --formNumber are compatible selector spellings"
6378
6760
  ),
6379
6761
  )
6380
6762
  pqfa.add_argument("--json", action="store_true", dest="raw_json",
@@ -6399,15 +6781,25 @@ def _main_impl():
6399
6781
  pgs.add_argument("--json", action="store_true", dest="raw_json",
6400
6782
  help="Output raw JSON")
6401
6783
 
6402
- pgdm = sub.add_parser("getDomainModel", help="按准确模型类型读取平台元模型 JSON")
6403
- pgdm.add_argument("--modelType", required=True, help="准确的平台模型类型,例如 BillFormModel")
6404
- pgdm.add_argument("--onlyEntity", default=None, help="可选 onlyEntity 查询值,原样传递")
6405
- pgdm.add_argument("--ext", default=None, help="可选扩展设计器查询值,原样传递")
6406
- pgdm.add_argument("--bizext", default=None, help="可选业务对象扩展查询值,原样传递")
6407
- pgdm.add_argument("--appId", default=None, help="可选应用编码,按接口 appId 查询键原样传递")
6784
+ pgdm = sub.add_parser(
6785
+ "getDomainModel",
6786
+ help="Read one platform domain-model JSON by exact model type",
6787
+ )
6788
+ pgdm.add_argument("--modelType", required=True,
6789
+ help="Exact platform model type, for example BillFormModel")
6790
+ pgdm.add_argument("--onlyEntity", default=None,
6791
+ help="Optional platform onlyEntity query value, passed through unchanged")
6792
+ pgdm.add_argument("--ext", default=None,
6793
+ help="Optional platform extension-designer query value, passed through unchanged")
6794
+ pgdm.add_argument("--bizext", default=None,
6795
+ help="Optional platform business-object-extension query value, passed through unchanged")
6796
+ pgdm.add_argument("--appId", default=None,
6797
+ help="Optional platform application code carried in the appId query key")
6408
6798
  pgdm_output = pgdm.add_mutually_exclusive_group(required=True)
6409
- pgdm_output.add_argument("--output-file", dest="domain_output_file", help="将 data JSON 文本写入 UTF-8 文件,stdout 只返回紧凑回执")
6410
- pgdm_output.add_argument("--stdout-data", action="store_true", help="明确输出完整响应;BillFormModel 可能超过 1 MB")
6799
+ pgdm_output.add_argument("--output-file", dest="domain_output_file",
6800
+ help="Write data JSON text to this UTF-8 file and print only a compact receipt")
6801
+ pgdm_output.add_argument("--stdout-data", action="store_true",
6802
+ help="Explicitly print the complete response; BillFormModel can exceed 1 MB")
6411
6803
 
6412
6804
  # discover (zero cold-start one-click discovery: locate form/app + get main entity + existing bizWord styles)
6413
6805
  pdv = sub.add_parser(
@@ -6438,7 +6830,7 @@ def _main_impl():
6438
6830
  paf_selectors = paf.add_mutually_exclusive_group(required=True)
6439
6831
  paf_selectors.add_argument("--field-name", dest="field_name",
6440
6832
  help="New field display name")
6441
- paf_selectors.add_argument("--fields", help="JSON array/file: name, bizWord, fieldType, optional combo/comboJson/required/fullLine/panel/before/after; one ordered batch")
6833
+ paf_selectors.add_argument("--fields", help="JSON array/file: name,bizWord,fieldType; optional comboJson,required,fullLine,panel,before,after,basedata,reference,mulTable,unitField,flexType,refBaseField,refDisplayProp,asstTypeId,asstType; one ordered batch")
6442
6834
  paf.add_argument("--validate-only", action="store_true", help="Validate --fields completely without platform writes")
6443
6835
  paf.add_argument("--biz-word", dest="biz_word",
6444
6836
  help="Concise semantic English business word without ISV prefix")
@@ -6707,15 +7099,15 @@ def _main_impl():
6707
7099
 
6708
7100
  # queryPublicTemplates
6709
7101
  ppt = sub.add_parser("queryPublicTemplates", help="Query public template list")
6710
- ppt.add_argument("--modelType", default="FormModel_Bill",
6711
- help="Model type: FormModel_Bill (Bill)/ FormModel_Base(Base Data)")
7102
+ ppt.add_argument("--modelType", default="BillFormModel",
7103
+ help="Model type: BillFormModel (Bill) / BaseFormModel (Base Data)")
6712
7104
  ppt.add_argument("--json", action="store_true", dest="raw_json",
6713
7105
  help="Output raw JSON")
6714
7106
 
6715
7107
  # queryBizTemplates
6716
7108
  pbt = sub.add_parser("queryBizTemplates", help="Query business template list (specified app scope)")
6717
- pbt.add_argument("--modelType", default="FormModel_Bill",
6718
- help="Model type: FormModel_Bill / FormModel_Base")
7109
+ pbt.add_argument("--modelType", default="BillFormModel",
7110
+ help="Model type: BillFormModel / BaseFormModel")
6719
7111
  pbt.add_argument("--bizAppId", default=None, help="App ID")
6720
7112
  pbt.add_argument("--bizAppNumber", default=None,
6721
7113
  help="App number (mutually exclusive with bizAppId)")
@@ -6935,6 +7327,17 @@ def _main_impl():
6935
7327
  help="Binary arithmetic operator")
6936
7328
  p_efc.add_argument("--right", required=True, help="Right numeric operand field")
6937
7329
 
7330
+ p_etdc = sub.add_parser(
7331
+ "ensureTimeDifferenceCalculation",
7332
+ help="Idempotently calculate decimal hours between two same-entity TimeFields")
7333
+ p_etdc_target = p_etdc.add_mutually_exclusive_group(required=True)
7334
+ p_etdc_target.add_argument("--formNumber", help="Exact form number")
7335
+ p_etdc_target.add_argument("--form-name", dest="form_name", help="Exact form name or number")
7336
+ p_etdc.add_argument("--entity", help="Optional exact entity; inferred from fields when omitted")
7337
+ p_etdc.add_argument("--target", required=True, help="Numeric target field key or caption")
7338
+ p_etdc.add_argument("--start", required=True, help="Start TimeField key or caption")
7339
+ p_etdc.add_argument("--end", required=True, help="End TimeField key or caption")
7340
+
6938
7341
  p_erff = sub.add_parser(
6939
7342
  "ensureRuleFunctionFormula",
6940
7343
  help="Compile one released FX function call into a same-entity formula rule",
@@ -6948,6 +7351,11 @@ def _main_impl():
6948
7351
  "--input", required=True, dest="input_file",
6949
7352
  help="Inline JSON or file with {call:{functionId,args:[{field}|{literal}|{call}|{condition}]}}",
6950
7353
  )
7354
+ p_erff_timing = p_erff.add_mutually_exclusive_group()
7355
+ p_erff_timing.add_argument("--event", action="append", dest="events",
7356
+ help="Select exact trigger timings for a new rule")
7357
+ p_erff_timing.add_argument("--add-event", action="append", dest="add_events",
7358
+ help="Append timing without clearing existing events")
6951
7359
 
6952
7360
  p_ecfs = sub.add_parser(
6953
7361
  "ensureConditionalFieldState",
@@ -6968,6 +7376,11 @@ def _main_impl():
6968
7376
  help="Target field key or caption; repeat to control multiple fields in one rule")
6969
7377
  p_ecfs.add_argument("--behavior", required=True, choices=["show", "hide", "lock", "unlock"],
6970
7378
  help="Field state while the condition is true")
7379
+ p_ecfs_timing = p_ecfs.add_mutually_exclusive_group()
7380
+ p_ecfs_timing.add_argument("--add-event", action="append", dest="add_events",
7381
+ help="Append a named timing to both branches while preserving existing timings")
7382
+ p_ecfs_timing.add_argument("--event", action="append", dest="events",
7383
+ help="Select the exact trigger timings for a new rule; repeat per timing")
6971
7384
 
6972
7385
  p_eces = sub.add_parser(
6973
7386
  "ensureConditionalEntryStyle",
@@ -7026,7 +7439,32 @@ def _main_impl():
7026
7439
  choices=["equals", "not-equals", "greater-than", "greater-or-equal", "less-than", "less-or-equal"])
7027
7440
  p_eccf.add_argument("--value", required=True)
7028
7441
  p_eccf.add_argument("--target", required=True, dest="targets", nargs="+",
7029
- help="One or more target field keys/captions in the same entity")
7442
+ action="extend",
7443
+ help="One or more target field keys/captions; repeat --target or list them after one flag")
7444
+ p_eccf_timing = p_eccf.add_mutually_exclusive_group()
7445
+ p_eccf_timing.add_argument("--add-event", action="append", dest="add_events",
7446
+ help="Append a named timing, e.g. AfterCopy, preserving existing event bits")
7447
+ p_eccf_timing.add_argument("--event", action="append", dest="events",
7448
+ help="Select exact timings for a new rule; repeat per timing")
7449
+
7450
+ p_ecfc = sub.add_parser(
7451
+ "ensureConditionalFieldChange",
7452
+ help="Ensure one conditional field value-update service with optional trigger timings")
7453
+ p_ecfc_target = p_ecfc.add_mutually_exclusive_group(required=True)
7454
+ p_ecfc_target.add_argument("--formNumber", help="Exact form number")
7455
+ p_ecfc_target.add_argument("--form-name", dest="form_name", help="Exact form name or number")
7456
+ p_ecfc.add_argument("--entity", help="Optional exact entity; inferred from the fields")
7457
+ p_ecfc.add_argument("--condition-field", required=True, dest="condition_field")
7458
+ p_ecfc.add_argument("--operator", required=True,
7459
+ choices=["equals", "not-equals", "greater-than", "greater-or-equal",
7460
+ "less-than", "less-or-equal", "empty", "not-empty"])
7461
+ p_ecfc.add_argument("--value", help="Omit for empty/not-empty")
7462
+ p_ecfc.add_argument("--target", required=True, help="Field whose value-update service is called")
7463
+ p_ecfc_timing = p_ecfc.add_mutually_exclusive_group()
7464
+ p_ecfc_timing.add_argument("--add-event", action="append", dest="add_events",
7465
+ help="Append a named timing, e.g. AfterCopy")
7466
+ p_ecfc_timing.add_argument("--event", action="append", dest="events",
7467
+ help="Select the exact trigger timings for a new rule; repeat per timing")
7030
7468
 
7031
7469
  p_eccv = sub.add_parser(
7032
7470
  "ensureConditionalCopyFieldValue",
@@ -7035,13 +7473,15 @@ def _main_impl():
7035
7473
  p_eccv_target.add_argument("--formNumber", help="Exact form number")
7036
7474
  p_eccv_target.add_argument("--form-name", dest="form_name", help="Exact form name or number")
7037
7475
  p_eccv.add_argument("--entity", help="Optional exact entity; inferred from fields when omitted")
7038
- p_eccv.add_argument("--condition-field", required=True, dest="condition_field")
7476
+ p_eccv.add_argument("--condition-field", dest="condition_field")
7039
7477
  p_eccv.add_argument(
7040
- "--operator", required=True,
7478
+ "--operator",
7041
7479
  choices=["equals", "not-equals", "greater-than", "greater-or-equal", "less-than", "less-or-equal"])
7042
- p_eccv.add_argument("--value", required=True)
7043
- p_eccv.add_argument("--source", required=True, help="Source field key or caption")
7044
- p_eccv.add_argument("--target", required=True, help="Target field key or caption")
7480
+ p_eccv.add_argument("--value")
7481
+ p_eccv.add_argument("--source", help="Source field key or caption")
7482
+ p_eccv.add_argument("--target", help="Target field key or caption")
7483
+ p_eccv.add_argument("--input", dest="input_file",
7484
+ help="JSON {mappings:[{source,target}], optional conditionField/operator/value/ruleId/branch}; branch defaults to true (condition met), false executes when not met")
7045
7485
  p_eccv.add_argument("--only-empty-target", action="store_true", dest="only_empty_target")
7046
7486
 
7047
7487
  p_ecca = sub.add_parser(
@@ -7076,10 +7516,12 @@ def _main_impl():
7076
7516
  "--operator",
7077
7517
  choices=["equals", "not-equals", "greater-than", "greater-or-equal", "less-than", "less-or-equal", "empty", "not-empty"])
7078
7518
  p_eccr.add_argument("--value")
7079
- p_eccr.add_argument("--source-reference", required=True, dest="source_reference")
7080
- p_eccr.add_argument("--source-property", required=True, dest="source_property",
7519
+ p_eccr.add_argument("--source-reference", dest="source_reference")
7520
+ p_eccr.add_argument("--source-property", dest="source_property",
7081
7521
  help="Exact property key already declared in the source field RefProps")
7082
- p_eccr.add_argument("--target", required=True, help="Same-entity target field")
7522
+ p_eccr.add_argument("--target", help="Same-entity target field")
7523
+ p_eccr.add_argument("--input", dest="input_file",
7524
+ help="JSON {mappings:[{sourceReference,sourceProperty,target}], optional conditionField/operator/value/ruleId/branch}; branch defaults to true (condition met), false executes when not met")
7083
7525
  p_eccr.add_argument("--only-empty-target", action="store_true", dest="only_empty_target")
7084
7526
 
7085
7527
  p_eqc = sub.add_parser(
@@ -7093,6 +7535,12 @@ def _main_impl():
7093
7535
  help="Source QtyField key or caption")
7094
7536
  p_eqc.add_argument("--target-qty", required=True, action="append", dest="target_qty",
7095
7537
  help="Target QtyField key or caption; repeat for multiple targets")
7538
+ for service_parser in (p_eccs, p_eccv, p_ecca, p_eccr, p_eqc):
7539
+ timing = service_parser.add_mutually_exclusive_group()
7540
+ timing.add_argument("--event", action="append", dest="events",
7541
+ help="Select exact timings for a new rule; repeat per timing")
7542
+ timing.add_argument("--add-event", action="append", dest="add_events",
7543
+ help="Append a timing while preserving existing timings")
7096
7544
 
7097
7545
  p_ur = sub.add_parser("updateRule", help="Modify rule")
7098
7546
  p_ur_target = p_ur.add_mutually_exclusive_group(required=True)
@@ -7231,6 +7679,19 @@ def _main_impl():
7231
7679
  p_sp_input.add_argument("--input", dest="input_file",
7232
7680
  help='Legacy one-element property map, e.g. {"MustInput":true}')
7233
7681
 
7682
+ p_rename = sub.add_parser(
7683
+ "renameElementKey",
7684
+ help="Rename one existing field/control/entry key and migrate verified rule references",
7685
+ )
7686
+ p_rename_form = p_rename.add_mutually_exclusive_group(required=True)
7687
+ p_rename_form.add_argument("--formNumber", help="Exact existing form number")
7688
+ p_rename_form.add_argument("--form-name", dest="form_name", help="Exact form name or number")
7689
+ p_rename.add_argument("--app-code", "--appCode", default=None, dest="app_code")
7690
+ p_rename.add_argument("--app-name", "--appName", default=None, dest="app_name")
7691
+ p_rename.add_argument("--element", required=True, help="Current exact key or unique display name")
7692
+ p_rename.add_argument("--new-key", required=True, dest="new_key",
7693
+ help="Requested new logical key; developer prefix is applied once")
7694
+
7234
7695
  p_sf = sub.add_parser("setFeatures", help="Set feature control (complex property)")
7235
7696
  p_sf_form = p_sf.add_mutually_exclusive_group(required=True)
7236
7697
  p_sf_form.add_argument("--formNumber", help="Exact existing form number")
@@ -7312,6 +7773,21 @@ def _main_impl():
7312
7773
  p_scp.add_argument("--input", required=True, dest="input_file",
7313
7774
  help="Property data JSON file path or inline JSON string")
7314
7775
 
7776
+ p_cdm = sub.add_parser(
7777
+ "configureDropdownMenu",
7778
+ help="Edit one button dropdown menu, preserving existing identities in one save",
7779
+ )
7780
+ p_cdm_form = p_cdm.add_mutually_exclusive_group(required=True)
7781
+ p_cdm_form.add_argument("--formNumber", help="Exact existing form number")
7782
+ p_cdm_form.add_argument("--form-name", dest="form_name", help="Exact form name or number")
7783
+ p_cdm_target = p_cdm.add_mutually_exclusive_group()
7784
+ p_cdm_target.add_argument("--elementKey", help="Persisted button/toolbar-item key")
7785
+ p_cdm_target.add_argument("--element", help="Exact key or unique display name")
7786
+ p_cdm.add_argument("--input", required=True, dest="input_file",
7787
+ help="Dropdown-menu intent JSON or UTF-8 JSON file")
7788
+ p_cdm.add_argument("--locale",
7789
+ help="Exact language key returned for an ambiguous multilingual menu title")
7790
+
7315
7791
  p_scs = sub.add_parser(
7316
7792
  "setCustomStyle",
7317
7793
  help="Set one element's custom CSS; the CLI owns designer-compatible encoding",
@@ -7463,7 +7939,7 @@ def _main_impl():
7463
7939
  help=("Must-input condition JSON with RuleCondition and RuleDescription; "
7464
7940
  "the CLI supplies _Type_=PreCondition; RuleTransExpr and Id are optional"))
7465
7941
  p_smic_condition.add_argument(
7466
- "--when-element",
7942
+ "--when-element", "--condition-field",
7467
7943
  help="Exact trigger-field key or business display name for a simple comparison")
7468
7944
  p_smic_condition.add_argument(
7469
7945
  "--clear", action="store_true",
@@ -7474,6 +7950,8 @@ def _main_impl():
7474
7950
  "typed from the trigger field, while selection labels resolve to persisted values"))
7475
7951
  p_smic.add_argument(
7476
7952
  "--operator", choices=["=", "<>", ">", ">=", "<", "<=", "empty", "not-empty"],
7953
+ type=lambda value: {"equals": "=", "not-equals": "<>", "greater-than": ">",
7954
+ "less-than": "<", "greater-or-equal": ">=", "less-or-equal": "<="}.get(value, value),
7477
7955
  help="Comparison operator used with --when-element; empty/not-empty take no value")
7478
7956
  p_smic.add_argument(
7479
7957
  "--value", dest="when_value",
@@ -7792,12 +8270,12 @@ def _main_impl():
7792
8270
  p_au.add_argument("--bizAppId", default=None, help="App ID")
7793
8271
  p_au.add_argument("--bizAppNumber", default=None, help="应forNumber")
7794
8272
  p_au.add_argument("--name", required=True, help="Feature group名称")
7795
- p_au.add_argument("--number", default=None,
7796
- help="Feature groupNumber(optional;default由后端生成 lingee_<lowercase unitId>)")
8273
+ p_au.add_argument("--number", required=True,
8274
+ help="Feature group code (required by the platform; preserve the requested code)")
7797
8275
  p_au.add_argument("--description", default=None,
7798
- help="Feature group描述(optional;defaultempty string)")
8276
+ help="Feature group description (defaults to the group name)")
7799
8277
  p_au.add_argument("--seq", type=int, default=None,
7800
- help="序号 [0, 1000)(optional;default0)")
8278
+ help="序号 [1, 999];省略时按现有分组的已验证序号追加")
7801
8279
  p_au.add_argument("--parentUnitId", default=None, help="上级Feature groupID(空=一级分组)")
7802
8280
  p_au.add_argument("--isLeaf", default=None, choices=["true", "false"], help="是否叶子节点(Defaulttrue)")
7803
8281
 
@@ -7973,7 +8451,7 @@ def _main_impl():
7973
8451
  p_lf.add_argument("--pageType", required=True, choices=["Empty", "Inherit"],
7974
8452
  help="页面类型:Empty(空白)/ Inherit(继承)")
7975
8453
 
7976
- # KSQL 导出/查询(预置数据/基础数据,管理员/子管理员)
8454
+ # KSQL 导出/查询(预置数据/基础数据,管理员/子管理员)—— 本地补丁 #14
7977
8455
  p_ksql_r = sub.add_parser("ksqlListDbRoutes", help="KSQL 分库列表(sys/secd/scm...)")
7978
8456
  p_ksql_dt = sub.add_parser("ksqlListDbTypes", help="KSQL 支持的数据库类型列表")
7979
8457
  p_ksql_tr = sub.add_parser("ksqlTranslate", help="翻译 KSQL 语句")
@@ -8246,7 +8724,7 @@ def _main_impl():
8246
8724
  p_acs_target.add_argument("--form-name", dest="form_name", help="Exact form name or number")
8247
8725
  p_acs.add_argument(
8248
8726
  "--input", dest="input_file", required=True,
8249
- help="Inline JSON or JSON file with parent and compact controls array",
8727
+ help="JSON object with controls [{type, name}]; optional parent defaults to the live form root",
8250
8728
  )
8251
8729
  p_acs.add_argument(
8252
8730
  "--app-root", default=None, dest="app_root",
@@ -8714,7 +9192,11 @@ def _main_impl():
8714
9192
  _load_ops(args.input_file)
8715
9193
  )
8716
9194
  except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc:
8717
- raise _CliUsageError(str(exc)) from exc
9195
+ raise _CliUsageError(str(exc), {
9196
+ **(getattr(exc, "recovery", None) or {}),
9197
+ "describeCommand": "describeCommand addControls",
9198
+ "payloadSchemaRef": _CATALOG_PAYLOAD_SCHEMA_REFS["addControls"],
9199
+ }) from exc
8718
9200
 
8719
9201
  if args.cmd == "setMustInputCondition":
8720
9202
  if args.when_element and args.when_equals is None and args.operator is None:
@@ -8739,6 +9221,7 @@ def _main_impl():
8739
9221
  _route_unstructured_prints_to_stderr()
8740
9222
  app_root = _resolve_app_root_arg(args)
8741
9223
  if app_root:
9224
+ validate_execution_binding(app_root)
8742
9225
  _retarget_runtime_dir(app_root)
8743
9226
 
8744
9227
  # 设置全局输出文件(--output)
@@ -8782,7 +9265,7 @@ def _main_impl():
8782
9265
  raise _CliUsageError("--fields carries each field specification; do not combine single-field specifications")
8783
9266
  if args.field_type != "TextField" or args.required or any(getattr(args, name, None) for name in (
8784
9267
  "basedata", "ref_bill", "mul_table", "unit_field", "flex_type", "ref_base_field", "ref_display_prop", "asstTypeId", "asst_type", "extAppId")):
8785
- raise _CliUsageError("--fields supports per-item basic and selection specs; use the single-field entry for reference-specific or selected-extension options")
9268
+ raise _CliUsageError("--fields carries reference/type options on each item; do not combine single-field or selected-extension flags")
8786
9269
  validate_add_fields_input(args.fields)
8787
9270
  if args.validate_only:
8788
9271
  pjson({"status": True, "data": {"validated": len(args.fields), "writeAttempted": False,
@@ -9001,11 +9484,23 @@ def _main_impl():
9001
9484
  pjson(_dynamic_identity_failure)
9002
9485
  sys.exit(8)
9003
9486
 
9487
+ if (raw_artifact.get("entities") or [{}])[0].get("type") == "MainEntity":
9488
+ _retained_creation = _dynamic_creation_receipt(app_root, artifact_file)
9489
+ if _retained_creation:
9490
+ pjson(_retained_creation)
9491
+ return 3
9492
+
9004
9493
  _dynamic_input_failure = _dynamic_form_artifact_preflight(raw_artifact)
9005
9494
  if _dynamic_input_failure:
9006
9495
  pjson(_dynamic_input_failure)
9007
9496
  return 1
9008
9497
 
9498
+ _dynamic_reference_failure = _resolve_dynamic_reference_fields(
9499
+ raw_artifact, app_root=app_root, isv=(_load_workspace_at(app_root) or {}).get("isv", ""))
9500
+ if _dynamic_reference_failure:
9501
+ pjson(_dynamic_reference_failure)
9502
+ return 1
9503
+
9009
9504
  # 2) workspace 回退:优先 --app-root 下 workspace;其次全局 RUNTIME_DIR。
9010
9505
  # 显式名称尚未解析时只允许身份自举,禁止自动创建默认分组。
9011
9506
  ws = _load_workspace_for_write(
@@ -9016,6 +9511,12 @@ def _main_impl():
9016
9511
  )
9017
9512
  _isv = ws.get("isv", "")
9018
9513
  _app = ws.get("appNumber", "")
9514
+ if _explicit_dynamic_identity:
9515
+ _standard = ws.get("standard")
9516
+ if isinstance(_standard, str):
9517
+ _standard = _standard.strip().lower() == "true"
9518
+ _explicit_dynamic_identity = make_entity_key_for(
9519
+ _explicit_dynamic_identity, _isv, standard=_standard)
9019
9520
 
9020
9521
  # 3) 模板resolve:CLI > artifact.templateNumber > manifest 对应项
9021
9522
  _tpl = _resolve_template_number(args.template, artifact_file, manifest)
@@ -9229,6 +9730,12 @@ def _main_impl():
9229
9730
  except Exception as exc:
9230
9731
  _apply_dynamic_ui_failure_contract(result, _summary_run, exc)
9231
9732
  _apply_build_failure_contract(result, _summary_run)
9733
+ if ents0 and ents0[0].get("type") == "MainEntity":
9734
+ _retained_creation = _dynamic_creation_receipt(app_root, artifact_file, result)
9735
+ if _retained_creation:
9736
+ result.update(doNotRetryRun=True, retryable=False, retryAdvice="inspect_retained_form",
9737
+ reconciliation=_retained_creation["reconciliation"],
9738
+ modelInstruction=_retained_creation["modelInstruction"])
9232
9739
  _append_meta_event(
9233
9740
  "buildMeta.finished",
9234
9741
  command="run",
@@ -9334,6 +9841,7 @@ def _main_impl():
9334
9841
  cfg,
9335
9842
  app_root,
9336
9843
  identity_only=bool(args.bizUnitId or requested_biz_unit_name),
9844
+ app_code=args.app_code,
9337
9845
  )
9338
9846
  _isv = ws.get("isv", "")
9339
9847
  _app = ws.get("appNumber", "")
@@ -9370,14 +9878,19 @@ def _main_impl():
9370
9878
  BuildDependencyError,
9371
9879
  bind_ref_bill_ids,
9372
9880
  collect_ref_bill_targets,
9373
- dependency_blocked_results,
9881
+ collect_build_dependencies,
9882
+ partition_build_wave,
9374
9883
  describe_build_plan,
9375
9884
  plan_build_waves,
9376
9885
  split_deferred_ref_bill_writes,
9377
9886
  split_deferred_property_fields,
9378
9887
  validate_deferred_property_field_sources,
9888
+ validate_ref_bill_owner,
9379
9889
  )
9380
9890
 
9891
+ dependencies = collect_build_dependencies(artifact_files, manifest=manifest, isv=_isv)
9892
+ if flat_parallel and any(dependencies.values()):
9893
+ raise _CliUsageError("BATCH_INPUT_DEPENDENCIES_REQUIRE_WAVES: referenced objects must finish before dependent writes")
9381
9894
  if flat_parallel or len(artifact_files) <= 1:
9382
9895
  build_waves = [artifact_files]
9383
9896
  else:
@@ -9386,6 +9899,7 @@ def _main_impl():
9386
9899
  artifact_files,
9387
9900
  manifest=manifest,
9388
9901
  isv=_isv,
9902
+ dependencies=dependencies,
9389
9903
  )
9390
9904
  except BuildDependencyError as exc:
9391
9905
  pjson(exc.to_dict())
@@ -9418,7 +9932,7 @@ def _main_impl():
9418
9932
  ]
9419
9933
  continue
9420
9934
  prepared = decorated
9421
- artifact_errors = validate_deferred_property_field_sources(prepared)
9935
+ artifact_errors = validate_deferred_property_field_sources(prepared) + validate_ref_bill_owner(prepared)
9422
9936
  validation_copy = copy.deepcopy(prepared)
9423
9937
  validation_copy.pop("listConfiguration", None)
9424
9938
  validation_copy.pop("formComposition", None)
@@ -9463,6 +9977,20 @@ def _main_impl():
9463
9977
  })
9464
9978
  sys.exit(3)
9465
9979
 
9980
+ from _core.build_receipts import BuildReceipts
9981
+ from kd_env_loader import load_environment_identity
9982
+ progress = BuildReceipts(
9983
+ app_root, biz_app_id, load_environment_identity(), prepared_artifacts,
9984
+ resume_result=args.resume_result, reconcile_only=args.reconcile_only,
9985
+ options=["--bizAppId", str(biz_app_id)]
9986
+ + (["--bizUnitId", str(biz_unit_id)] if biz_unit_id else [])
9987
+ + (["--template", args.template] if args.template is not None else []),
9988
+ ) if app_root else None
9989
+ if args.reconcile_only and not args.resume_result:
9990
+ raise _CliUsageError("BATCH_INPUT_RESUME_REQUIRED: --reconcile-only requires --resume-result")
9991
+ if args.resume_result and progress is None:
9992
+ raise _CliUsageError("BATCH_INPUT_APP_ROOT_REQUIRED: resume requires --app-root")
9993
+
9466
9994
  from threading import Lock
9467
9995
  ref_bill_id_cache = {}
9468
9996
  ref_bill_id_lock = Lock()
@@ -9475,36 +10003,39 @@ def _main_impl():
9475
10003
  cached = ref_bill_id_cache.get(key)
9476
10004
  if cached:
9477
10005
  return cached
9478
- queried = do_query_forms(cfg, token, keyword=target_number)
9479
- rows = (queried or {}).get("data") or []
9480
- exact = [
9481
- row for row in rows
9482
- if isinstance(row, dict)
9483
- and str(row.get("formNumber") or "").strip().lower() == key
9484
- ]
9485
- unique_ids = {
9486
- str(row.get("formId") or "").strip()
9487
- for row in exact
9488
- if str(row.get("formId") or "").strip()
9489
- }
9490
- if len(unique_ids) != 1:
9491
- raise ValueError(
9492
- f"REF_BILL_TARGET_UNRESOLVED: formNumber={target_number!r} "
9493
- f"matched {len(unique_ids)} exact form IDs"
9494
- )
9495
- non_bill = [
9496
- str(row.get("modelType") or "").strip()
9497
- for row in exact
9498
- if str(row.get("modelType") or "").strip() != "BillFormModel"
9499
- ]
9500
- if non_bill:
9501
- raise ValueError(
9502
- f"REF_BILL_TARGET_TYPE_MISMATCH: formNumber={target_number!r} "
9503
- f"is not a BillFormModel"
9504
- )
9505
- form_id = next(iter(unique_ids))
10006
+ queried = do_query_forms(cfg, token, keyword=target_number)
10007
+ if not _is_api_success(queried):
10008
+ raise RuntimeError("REF_BILL_LOOKUP_UNCONFIRMED: " + (_extract_error_message(queried) or "queryForms failed"))
10009
+ rows = (queried or {}).get("data") or []
10010
+ exact = [
10011
+ row for row in rows
10012
+ if isinstance(row, dict)
10013
+ and str(row.get("formNumber") or "").strip().lower() == key
10014
+ ]
10015
+ unique_ids = {
10016
+ str(row.get("formId") or "").strip()
10017
+ for row in exact
10018
+ if str(row.get("formId") or "").strip()
10019
+ }
10020
+ if len(unique_ids) != 1:
10021
+ raise ValueError(
10022
+ f"REF_BILL_TARGET_UNRESOLVED: formNumber={target_number!r} "
10023
+ f"matched {len(unique_ids)} exact form IDs"
10024
+ )
10025
+ non_bill = [
10026
+ str(row.get("modelType") or "").strip()
10027
+ for row in exact
10028
+ if str(row.get("modelType") or "").strip() != "BillFormModel"
10029
+ ]
10030
+ if non_bill:
10031
+ raise ValueError(
10032
+ f"REF_BILL_TARGET_TYPE_MISMATCH: formNumber={target_number!r} "
10033
+ f"is not a BillFormModel"
10034
+ )
10035
+ form_id = next(iter(unique_ids))
10036
+ with ref_bill_id_lock:
9506
10037
  ref_bill_id_cache[key] = form_id
9507
- return form_id
10038
+ return form_id
9508
10039
 
9509
10040
  def _bind_ref_bill_targets(artifact):
9510
10041
  resolved = {}
@@ -9588,8 +10119,16 @@ def _main_impl():
9588
10119
  ops=decorated_ops,
9589
10120
  )
9590
10121
  if not _is_api_success(result):
9591
- raise ValueError(
9592
- "REF_BILL_ADD_FAILED: " + (_extract_error_message(result) or "modifyMeta failed")
10122
+ raise _PostBuildWriteError(
10123
+ "REF_BILL_ADD_FAILED: " + (_extract_error_message(result) or "modifyMeta failed"),
10124
+ result,
10125
+ {"command": "modifyMeta", "appRoot": app_root,
10126
+ "input": {"formId": str(receipt.get("formId") or "").strip(),
10127
+ "formNumber": root_number, "ops": raw_ops},
10128
+ "expectedFields": [{"fieldKey": item["field"]["fieldKey"],
10129
+ "fieldType": "RefBillField",
10130
+ "billEntityId": item["field"].get("billEntityId")}
10131
+ for item in deferred]},
9593
10132
  )
9594
10133
  verified = []
9595
10134
  for item in deferred:
@@ -9948,7 +10487,33 @@ def _main_impl():
9948
10487
  )
9949
10488
 
9950
10489
  def _one(afile):
10490
+ reused = progress.reused(afile) if progress else None
10491
+ if reused:
10492
+ return reused
9951
10493
  t0 = _time.time()
10494
+ if args.reconcile_only:
10495
+ previous = progress.current(afile)
10496
+ if previous.get("writeAttempted") is False:
10497
+ return previous
10498
+ receipt = previous.get("result") or {}
10499
+ targets = (receipt.get("data") or {}).get("entityResults", [])
10500
+ if targets and all(row.get("formId") for row in targets) and len(targets) == len(previous["targetNumbers"]):
10501
+ verification = _verify_build_meta_readback(cfg, token, prepared_artifacts[afile], receipt)
10502
+ return {**previous, "ok": False, "phase": "retainedRepairRequired",
10503
+ "reconciliationVerification": verification,
10504
+ "errorCode": "BATCH_WRITE_RECONCILIATION_REQUIRED",
10505
+ "errors": ["Retained identity read back; finish the original repair plan through existing-form writers. Creation remains blocked."]}
10506
+ observed = [do_query_forms(cfg, token, keyword=number) for number in previous["targetNumbers"]]
10507
+ absent = all(_is_api_success(result) and isinstance(result.get("data"), list)
10508
+ and not any(str(row.get("formNumber") or "").casefold() == number.casefold()
10509
+ for row in result["data"] if isinstance(row, dict))
10510
+ for number, result in zip(previous["targetNumbers"], observed))
10511
+ return {**previous, "ok": False, "phase": "reconciledAbsent" if absent else "writeUnknown",
10512
+ "writeAttempted": False if absent else previous.get("writeAttempted"),
10513
+ "absenceEvidence": observed if absent else None,
10514
+ "errorCode": "BATCH_INPUT_READY_TO_RESUME" if absent else "BATCH_WRITE_RECONCILIATION_REQUIRED",
10515
+ "errors": ["Absence confirmed; use the returned resume command" if absent
10516
+ else "Read did not prove absence; do not adopt a same-number object as this batch's success"]}
9952
10517
  raw = copy.deepcopy(prepared_artifacts[afile])
9953
10518
  tpl_one = tpl_per_artifact.get(afile, "bos_billtpl")
9954
10519
  dec = raw
@@ -9957,12 +10522,16 @@ def _main_impl():
9957
10522
  try:
9958
10523
  ref_bill_errors = _bind_ref_bill_targets(dec)
9959
10524
  except Exception as exc:
9960
- ref_bill_errors = [str(exc)]
10525
+ return {"artifact": os.path.basename(afile), "ok": False, "phase": "resolveRefBill",
10526
+ "writeAttempted": False, "errorCode": _exception_error_code(exc)[0],
10527
+ "errors": [str(exc)], "elapsed_sec": round(_time.time() - t0, 2)}
9961
10528
  if ref_bill_errors:
9962
10529
  return {
9963
10530
  "artifact": os.path.basename(afile),
9964
10531
  "ok": False,
9965
10532
  "phase": "resolveRefBill",
10533
+ "writeAttempted": False,
10534
+ "errorCode": "REF_BILL_TARGET_UNRESOLVED",
9966
10535
  "errors": ref_bill_errors,
9967
10536
  "elapsed_sec": round(_time.time() - t0, 2),
9968
10537
  }
@@ -9976,19 +10545,39 @@ def _main_impl():
9976
10545
  "artifact": os.path.basename(afile),
9977
10546
  "ok": False,
9978
10547
  "phase": "preValidate",
10548
+ "writeAttempted": False,
10549
+ "errorCode": "ARTIFACT_INPUT_INVALID",
9979
10550
  "errors": errs,
9980
10551
  "elapsed_sec": round(_time.time() - t0, 2),
9981
10552
  }
10553
+ if progress:
10554
+ try:
10555
+ progress.start(afile)
10556
+ except ValueError as exc:
10557
+ return {"artifact": os.path.basename(afile), "ok": False,
10558
+ "phase": "preflight", "writeAttempted": False,
10559
+ "errorCode": "BATCH_WRITE_RECONCILIATION_REQUIRED", "errors": [str(exc)],
10560
+ "elapsed_sec": round(_time.time() - t0, 2)}
9982
10561
  try:
9983
10562
  raw_result = do_build_meta(
9984
10563
  cfg, token, dec,
9985
10564
  biz_app_id=biz_app_id,
9986
10565
  biz_unit_id=biz_unit_id,
9987
10566
  template_number=tpl_one,
10567
+ on_write_result=(lambda response: progress.finish(afile, {
10568
+ "ok": False, "phase": "rootReceipt", "writeAttempted": True,
10569
+ "result": _transform_build_meta_result(response),
10570
+ })) if progress else None,
9988
10571
  )
9989
10572
  transformed = _transform_build_meta_result(raw_result)
9990
10573
  # Derive write success from entity counts, never transport status alone.
9991
10574
  summary_one = _summarize_build_meta_result(transformed, artifact=dec)
10575
+ if progress:
10576
+ progress.finish(afile, {"ok": False, "phase": "buildMeta",
10577
+ "writeAttempted": raw_result.get("writeAttempted", True),
10578
+ "result": transformed, "summary": summary_one,
10579
+ "metadataRetained": any(row.get("formId") for row in
10580
+ (transformed.get("data") or {}).get("entityResults", []))})
9992
10581
  verification = None
9993
10582
  final_ok = bool(summary_one.get("ok"))
9994
10583
  phase = "buildMeta"
@@ -10000,6 +10589,7 @@ def _main_impl():
10000
10589
  list_configuration_result = None
10001
10590
  dynamic_ui_result = None
10002
10591
  form_composition_result = None
10592
+ pending_write = None
10003
10593
  property_partial_only = (
10004
10594
  not final_ok
10005
10595
  and int(summary_one.get("successCount") or 0) > 0
@@ -10039,6 +10629,9 @@ def _main_impl():
10039
10629
  final_ok = False
10040
10630
  phase = "addRefBill"
10041
10631
  errors.append(str(exc))
10632
+ if isinstance(exc, _PostBuildWriteError):
10633
+ ref_bill_result = {"modifyMeta": exc.result, "status": False}
10634
+ pending_write = exc.pending_write
10042
10635
  if final_ok and deferred_ref_bill_phase["propertySettings"]:
10043
10636
  try:
10044
10637
  ref_bill_property_result = _apply_ref_bill_property_settings(
@@ -10148,11 +10741,20 @@ def _main_impl():
10148
10741
  f"failed={verify_summary.get('failed', 0)} "
10149
10742
  f"errors={verify_summary.get('errors', 0)}"
10150
10743
  )
10744
+ if not final_ok and phase != "buildMeta" and not summary_one.get("errorCode"):
10745
+ _attach_post_build_phase_failure(
10746
+ summary_one, transformed, phase=phase, app_root=app_root,
10747
+ artifact_path=afile, pending_write=pending_write,
10748
+ )
10151
10749
  _apply_build_failure_contract(transformed, summary_one)
10152
10750
  return {
10153
10751
  "artifact": os.path.basename(afile),
10752
+ "artifactPath": os.path.abspath(afile),
10154
10753
  "ok": final_ok,
10155
10754
  "phase": phase,
10755
+ "writeAttempted": raw_result.get("writeAttempted", True),
10756
+ "errorCode": (transformed.get("errorCode") or "0") if phase == "buildMeta"
10757
+ else _exception_error_code(ValueError(errors[0]))[0] if errors else "0",
10156
10758
  "result": transformed,
10157
10759
  "summary": summary_one,
10158
10760
  "refBillResult": ref_bill_result,
@@ -10173,19 +10775,40 @@ def _main_impl():
10173
10775
  return {
10174
10776
  "artifact": os.path.basename(afile),
10175
10777
  "ok": False,
10176
- "phase": "buildMeta",
10778
+ "phase": "writeUnknown",
10779
+ "writeAttempted": None,
10780
+ "errorCode": "BATCH_WRITE_RESULT_UNKNOWN",
10177
10781
  "errors": [str(e)],
10178
10782
  "elapsed_sec": round(_time.time() - t0, 2),
10179
10783
  }
10180
10784
 
10785
+ def _cancelled(path):
10786
+ row = {"artifact": os.path.basename(path), "artifactPath": path, "ok": False,
10787
+ "phase": "cancelled", "writeAttempted": False,
10788
+ "errors": ["Not started because --fail-fast was requested"], "elapsed_sec": 0.0}
10789
+ return progress.finish(path, row) if progress else row
10790
+
10181
10791
  results = []
10182
10792
  fail_fast = bool(getattr(args, "fail_fast", False))
10183
10793
  t_start = _time.time()
10184
10794
  aborted = False
10185
10795
  dependency_blocked = False
10186
- for wave_idx, wave_files in enumerate(build_waves, 1):
10796
+ succeeded_paths = set()
10797
+ if progress:
10798
+ for source in artifact_files:
10799
+ reused = progress.reused(source)
10800
+ if reused:
10801
+ _cache_build_receipts(reused)
10802
+ succeeded_paths.add(os.path.normpath(os.path.abspath(source)))
10803
+ for wave_idx, wave_files in enumerate([artifact_files] if args.reconcile_only else build_waves, 1):
10187
10804
  if aborted:
10188
- break
10805
+ results.extend(_cancelled(path) for path in wave_files)
10806
+ continue
10807
+ wave_files, blocked = (wave_files, []) if args.reconcile_only else partition_build_wave(wave_files, dependencies, succeeded_paths)
10808
+ results.extend(progress.finish(row["artifactPath"], row) if progress else row for row in blocked)
10809
+ dependency_blocked = dependency_blocked or bool(blocked)
10810
+ if not wave_files:
10811
+ continue
10189
10812
  if len(build_waves) > 1:
10190
10813
  print(
10191
10814
  f"[runBatch] —— wave {wave_idx}/{len(build_waves)} "
@@ -10194,14 +10817,19 @@ def _main_impl():
10194
10817
  flush=True,
10195
10818
  )
10196
10819
  with ThreadPoolExecutor(max_workers=concurrency) as pool:
10197
- wave_result_start = len(results)
10198
10820
  futures = {pool.submit(_one, f): f for f in wave_files}
10199
10821
  try:
10200
10822
  for fut in as_completed(futures):
10823
+ if fut.cancelled():
10824
+ results.append(_cancelled(futures[fut]))
10825
+ continue
10201
10826
  r = fut.result()
10827
+ if progress:
10828
+ r = progress.finish(futures[fut], r)
10202
10829
  results.append(r)
10203
10830
  if r.get("ok"):
10204
10831
  _cache_build_receipts(r)
10832
+ succeeded_paths.add(os.path.normpath(os.path.abspath(futures[fut])))
10205
10833
  print(
10206
10834
  f"[runBatch] {'✅' if r['ok'] else '❌'} "
10207
10835
  f"{r['artifact']} ({r['elapsed_sec']}s, phase={r['phase']})",
@@ -10213,27 +10841,9 @@ def _main_impl():
10213
10841
  if not other.done():
10214
10842
  other.cancel()
10215
10843
  aborted = True
10216
- break
10217
10844
  finally:
10218
- pool.shutdown(wait=not (fail_fast and aborted))
10219
-
10220
- wave_failed = any(
10221
- not result["ok"] for result in results[wave_result_start:]
10222
- )
10223
- if wave_failed and wave_idx < len(build_waves):
10224
- blocked = dependency_blocked_results(
10225
- build_waves,
10226
- failed_wave_index=wave_idx,
10227
- )
10228
- results.extend(blocked)
10229
- dependency_blocked = bool(blocked)
10230
- aborted = True
10231
- print(
10232
- f"[runBatch] ⛔ wave {wave_idx} 失败;"
10233
- f"{len(blocked)} 份后续 artifact 因依赖屏障未写入",
10234
- file=sys.stderr,
10235
- flush=True,
10236
- )
10845
+ # Running writes cannot be cancelled; collect their real receipts.
10846
+ pool.shutdown(wait=True)
10237
10847
 
10238
10848
  total = round(_time.time() - t_start, 2)
10239
10849
  succ = sum(1 for r in results if r["ok"])
@@ -10258,14 +10868,14 @@ def _main_impl():
10258
10868
  retained = [
10259
10869
  row for item in results if not item.get("ok")
10260
10870
  for row in ((item.get("result") or {}).get("data") or {}).get("entityResults", [])
10261
- if row.get("formId") and not row.get("success")
10871
+ if row.get("formId")
10262
10872
  ]
10263
10873
  if retained:
10264
- summary["retainedTargets"] = retained
10874
+ summary.setdefault("retainedTargets", retained)
10265
10875
  summary["metadataRetained"] = True
10266
10876
  summary["doNotRetryBuildMeta"] = True
10267
10877
  summary.setdefault("retryAdvice", "reconcile_before_retry")
10268
- summary["reconciliation"] = [{"command": "getFormSchema", "formId": row["formId"]} for row in retained]
10878
+ summary.setdefault("reconciliation", [{"command": "getFormSchema", "formId": row["formId"]} for row in retained])
10269
10879
  _append_meta_event(
10270
10880
  "buildMeta.finished",
10271
10881
  command="runBatch",
@@ -10276,11 +10886,12 @@ def _main_impl():
10276
10886
  **_summary_batch,
10277
10887
  )
10278
10888
  # auto归档:批量场景下把所有成功 formNumber 一次性回写
10279
- if app_root and _summary_batch.get("ok") and _summary_batch.get("formNumbers"):
10889
+ if not args.reconcile_only and app_root and _summary_batch.get("ok") and _summary_batch.get("formNumbers"):
10280
10890
  _archive_finalize_metadata(app_root, _summary_batch["formNumbers"])
10281
10891
  # auto-sync dym to repo (runBatch)
10282
10892
  if (
10283
10893
  _summary_batch.get("ok")
10894
+ and not args.reconcile_only
10284
10895
  and not args.no_sync_dym
10285
10896
  and app_root
10286
10897
  and _summary_batch.get("formNumbers")
@@ -10295,7 +10906,18 @@ def _main_impl():
10295
10906
  file=sys.stderr,
10296
10907
  flush=True,
10297
10908
  )
10298
- pjson(_attach_batch_delivery(_attach_available_form_numbers(cfg, token, summary), app_root))
10909
+ summary = _attach_batch_delivery(_attach_available_form_numbers(cfg, token, summary), app_root)
10910
+ if progress:
10911
+ summary["executionFinished"] = True
10912
+ summary["processId"] = os.getpid()
10913
+ summary.update({key: progress.receipt[key] for key in
10914
+ ("executionBinding", "batchIdentity", "appRoot", "artifactPaths", "resultPath", "resume", "reconcile", "notStartedArtifacts")})
10915
+ summary.setdefault("reconciliation", []).extend(progress.receipt["reconciliation"])
10916
+ from _core.build_receipts import _atomic_json
10917
+ _atomic_json(progress.result_path, summary)
10918
+ elif app_root:
10919
+ summary = save_run_batch_result(summary, app_root, artifact_files)
10920
+ pjson(summary)
10299
10921
  if args.verify:
10300
10922
  verified = sum(
10301
10923
  1 for item in results
@@ -10419,8 +11041,13 @@ def _main_impl():
10419
11041
 
10420
11042
  elif args.cmd == "getDomainModel":
10421
11043
  token = do_auth(cfg)
10422
- result = do_get_domain_model(cfg, token, args.modelType, only_entity=args.onlyEntity,
10423
- ext=args.ext, bizext=args.bizext, app_id=args.appId)
11044
+ result = do_get_domain_model(
11045
+ cfg, token, args.modelType,
11046
+ only_entity=args.onlyEntity,
11047
+ ext=args.ext,
11048
+ bizext=args.bizext,
11049
+ app_id=args.appId,
11050
+ )
10424
11051
  pjson(_persist_domain_model(result, args.domain_output_file, args.modelType)
10425
11052
  if args.domain_output_file else result)
10426
11053
 
@@ -10889,7 +11516,7 @@ def _main_impl():
10889
11516
  plugin_config, form_number=args.formNumber,
10890
11517
  biz_app_id=target.get("appId"))
10891
11518
  result = do_register_plugin(cfg, token, form_number=args.formNumber,
10892
- plugin_config=plugin_config)
11519
+ plugin_config=plugin_config, app_root=args.app_root)
10893
11520
  if _is_api_success(result):
10894
11521
  result = _attach_plugin_readback(
10895
11522
  cfg, token, args.formNumber, plugin_config, result,
@@ -11002,6 +11629,18 @@ def _main_impl():
11002
11629
  args.left, args.operator, args.right)
11003
11630
  pjson(_attach_bound_form_identity(result, args))
11004
11631
 
11632
+ elif args.cmd == "ensureTimeDifferenceCalculation":
11633
+ token = do_auth(cfg)
11634
+ blocked = _bind_rule_domain_target(cfg, token, args, args.cmd)
11635
+ if blocked is not None:
11636
+ pjson(blocked)
11637
+ else:
11638
+ _assert_form_number_target_owned(cfg, token, args, args.cmd)
11639
+ result = do_ensure_time_difference_calculation(
11640
+ cfg, token, args.formNumber, args.entity, args.target,
11641
+ args.start, args.end)
11642
+ pjson(_attach_bound_form_identity(result, args))
11643
+
11005
11644
  elif args.cmd == "ensureRuleFunctionFormula":
11006
11645
  token = do_auth(cfg)
11007
11646
  blocked = _bind_rule_domain_target(cfg, token, args, args.cmd)
@@ -11015,11 +11654,14 @@ def _main_impl():
11015
11654
  "RULE_FUNCTION_INPUT_INVALID: expected exactly {call:{functionId,args}}"
11016
11655
  )
11017
11656
  result = do_ensure_rule_function_formula(
11018
- cfg, token, args.formNumber, args.entity, args.target, payload["call"]
11657
+ cfg, token, args.formNumber, args.entity, args.target, payload["call"],
11658
+ args.add_events, args.events,
11019
11659
  )
11020
11660
  pjson(_attach_bound_form_identity(result, args))
11021
11661
 
11022
11662
  elif args.cmd == "ensureConditionalFieldState":
11663
+ if args.events and args.add_events:
11664
+ raise _CliUsageError("use --event for exact new timings or --add-event to append")
11023
11665
  if args.operator in {"empty", "not-empty"} and args.value is not None:
11024
11666
  raise _CliUsageError(f"--operator {args.operator} takes no --value")
11025
11667
  if args.operator not in {"empty", "not-empty"} and args.value is None:
@@ -11032,7 +11674,8 @@ def _main_impl():
11032
11674
  _assert_form_number_target_owned(cfg, token, args, args.cmd)
11033
11675
  result = do_ensure_conditional_field_state(
11034
11676
  cfg, token, args.formNumber, args.entity, args.condition_field,
11035
- args.operator, args.value, args.target, args.behavior)
11677
+ args.operator, args.value, args.target, args.behavior,
11678
+ args.add_events, args.events)
11036
11679
  pjson(_attach_bound_form_identity(result, args))
11037
11680
 
11038
11681
  elif args.cmd == "ensureConditionalEntryStyle":
@@ -11068,7 +11711,8 @@ def _main_impl():
11068
11711
  _assert_form_number_target_owned(cfg, token, args, args.cmd)
11069
11712
  result = do_ensure_conditional_control_state(
11070
11713
  cfg, token, args.formNumber, args.entity, args.condition_field,
11071
- args.operator, args.value, args.target_control, args.behavior)
11714
+ args.operator, args.value, args.target_control, args.behavior,
11715
+ args.add_events, args.events)
11072
11716
  pjson(_attach_bound_form_identity(result, args))
11073
11717
 
11074
11718
  elif args.cmd == "ensureConditionalClearFields":
@@ -11080,20 +11724,46 @@ def _main_impl():
11080
11724
  _assert_form_number_target_owned(cfg, token, args, args.cmd)
11081
11725
  result = do_ensure_conditional_clear_fields(
11082
11726
  cfg, token, args.formNumber, args.entity, args.condition_field,
11083
- args.operator, args.value, args.targets)
11727
+ args.operator, args.value, args.targets, args.add_events, args.events)
11728
+ pjson(_attach_bound_form_identity(result, args))
11729
+
11730
+ elif args.cmd == "ensureConditionalFieldChange":
11731
+ if args.events and args.add_events:
11732
+ raise _CliUsageError("use --event for exact new timings or --add-event to append")
11733
+ if args.operator in {"empty", "not-empty"} and args.value is not None:
11734
+ raise _CliUsageError(f"--operator {args.operator} takes no --value")
11735
+ if args.operator not in {"empty", "not-empty"} and args.value is None:
11736
+ raise _CliUsageError("--value is required with this --operator")
11737
+ token = do_auth(cfg)
11738
+ blocked = _bind_rule_domain_target(cfg, token, args, args.cmd)
11739
+ if blocked is not None:
11740
+ pjson(blocked)
11741
+ else:
11742
+ _assert_form_number_target_owned(cfg, token, args, args.cmd)
11743
+ result = do_ensure_conditional_field_change(
11744
+ cfg, token, args.formNumber, args.entity, args.condition_field,
11745
+ args.operator, args.value, args.target, args.add_events, args.events)
11084
11746
  pjson(_attach_bound_form_identity(result, args))
11085
11747
 
11086
11748
  elif args.cmd == "ensureConditionalCopyFieldValue":
11749
+ if bool(args.input_file) == bool(args.source or args.target):
11750
+ raise _CliUsageError("use --input for several mappings, or --source/--target for one mapping")
11751
+ if args.input_file and args.only_empty_target:
11752
+ raise _CliUsageError("with --input put per-row onlyEmptyTarget inside mappings")
11753
+ if not args.input_file and not all((args.condition_field, args.operator, args.value, args.source, args.target)):
11754
+ raise _CliUsageError("single mapping requires condition-field, operator, value, source and target")
11087
11755
  token = do_auth(cfg)
11088
11756
  blocked = _bind_rule_domain_target(cfg, token, args, args.cmd)
11089
11757
  if blocked is not None:
11090
11758
  pjson(blocked)
11091
11759
  else:
11092
11760
  _assert_form_number_target_owned(cfg, token, args, args.cmd)
11093
- result = do_ensure_conditional_copy_field_value(
11761
+ result = (do_ensure_copy_field_mappings(
11762
+ cfg, token, args.formNumber, _load_mapping_request(args),
11763
+ ) if args.input_file else do_ensure_conditional_copy_field_value(
11094
11764
  cfg, token, args.formNumber, args.entity, args.condition_field,
11095
11765
  args.operator, args.value, args.source, args.target,
11096
- args.only_empty_target)
11766
+ args.only_empty_target, args.add_events, args.events))
11097
11767
  pjson(_attach_bound_form_identity(result, args))
11098
11768
 
11099
11769
  elif args.cmd == "ensureConditionalCalculation":
@@ -11106,20 +11776,28 @@ def _main_impl():
11106
11776
  result = do_ensure_conditional_calculation(
11107
11777
  cfg, token, args.formNumber, args.entity, args.condition_field,
11108
11778
  args.operator, args.value, args.target, args.left,
11109
- args.calculation, args.right)
11779
+ args.calculation, args.right, args.add_events, args.events)
11110
11780
  pjson(_attach_bound_form_identity(result, args))
11111
11781
 
11112
11782
  elif args.cmd == "ensureConditionalCarryReferenceProperty":
11783
+ if bool(args.input_file) == bool(args.source_reference or args.source_property or args.target):
11784
+ raise _CliUsageError("use --input for several mappings, or --source-reference/--source-property/--target for one mapping")
11785
+ if args.input_file and args.only_empty_target:
11786
+ raise _CliUsageError("with --input put per-row onlyEmptyTarget inside mappings")
11787
+ if not args.input_file and not all((args.source_reference, args.source_property, args.target)):
11788
+ raise _CliUsageError("single mapping requires --source-reference, --source-property and --target")
11113
11789
  token = do_auth(cfg)
11114
11790
  blocked = _bind_rule_domain_target(cfg, token, args, args.cmd)
11115
11791
  if blocked is not None:
11116
11792
  pjson(blocked)
11117
11793
  else:
11118
11794
  _assert_form_number_target_owned(cfg, token, args, args.cmd)
11119
- result = do_ensure_conditional_carry_reference_property(
11795
+ result = (do_ensure_carry_reference_mappings(
11796
+ cfg, token, args.formNumber, _load_mapping_request(args),
11797
+ ) if args.input_file else do_ensure_conditional_carry_reference_property(
11120
11798
  cfg, token, args.formNumber, args.entity, args.condition_field,
11121
11799
  args.operator, args.value, args.source_reference, args.source_property,
11122
- args.target, args.only_empty_target)
11800
+ args.target, args.only_empty_target, args.add_events, args.events))
11123
11801
  pjson(_attach_bound_form_identity(result, args))
11124
11802
 
11125
11803
  elif args.cmd == "ensureQuantityConversion":
@@ -11131,7 +11809,7 @@ def _main_impl():
11131
11809
  _assert_form_number_target_owned(cfg, token, args, args.cmd)
11132
11810
  result = do_ensure_quantity_conversion(
11133
11811
  cfg, token, args.formNumber, args.entity,
11134
- args.source_qty, args.target_qty)
11812
+ args.source_qty, args.target_qty, args.add_events, args.events)
11135
11813
  pjson(_attach_bound_form_identity(result, args))
11136
11814
 
11137
11815
  elif args.cmd == "updateRule":
@@ -11251,6 +11929,7 @@ def _main_impl():
11251
11929
  )
11252
11930
  else:
11253
11931
  properties = _load_ops(args.input_file)
11932
+ properties = _normalize_key_property(args, properties)
11254
11933
  element_key = args.elementKey or resolve_element_for_properties(
11255
11934
  cfg, token, args.formNumber, args.element, properties)
11256
11935
  result = do_set_properties_verified(
@@ -11258,6 +11937,15 @@ def _main_impl():
11258
11937
  element_key=element_key, properties=properties)
11259
11938
  pjson(_attach_bound_form_identity(result, args))
11260
11939
 
11940
+ elif args.cmd == "renameElementKey":
11941
+ token = do_auth(cfg)
11942
+ blocked = _bind_existing_form_target(cfg, token, args, args.cmd)
11943
+ if blocked is not None:
11944
+ pjson(blocked)
11945
+ else:
11946
+ _assert_form_number_target_owned(cfg, token, args, args.cmd)
11947
+ pjson(_attach_bound_form_identity(_rename_element_key(cfg, token, args), args))
11948
+
11261
11949
  elif args.cmd == "setFeatures":
11262
11950
  token = do_auth(cfg)
11263
11951
  blocked = _bind_existing_form_target(cfg, token, args, args.cmd)
@@ -11395,6 +12083,20 @@ def _main_impl():
11395
12083
  cfg, token, args.formNumber, element_key, args.css, args.clear)
11396
12084
  pjson(_attach_bound_form_identity(result, args))
11397
12085
 
12086
+ elif args.cmd == "configureDropdownMenu":
12087
+ token = do_auth(cfg)
12088
+ blocked = _bind_existing_form_target(cfg, token, args, args.cmd)
12089
+ if blocked is not None:
12090
+ pjson(blocked)
12091
+ else:
12092
+ _assert_form_number_target_owned(cfg, token, args, args.cmd)
12093
+ element_key = args.elementKey or (resolve_element_selector(
12094
+ cfg, token, args.formNumber, args.element, category="control") if args.element else None)
12095
+ result = do_configure_dropdown_menu(
12096
+ cfg, token, args.formNumber, element_key, _load_ops(args.input_file),
12097
+ locale=args.locale)
12098
+ pjson(_attach_bound_form_identity(result, args))
12099
+
11398
12100
  elif args.cmd == "setCollapseSummary":
11399
12101
  token = do_auth(cfg)
11400
12102
  blocked = _bind_existing_form_target(cfg, token, args, args.cmd)
@@ -12399,10 +13101,11 @@ def _main_impl():
12399
13101
  pjson(result)
12400
13102
 
12401
13103
  elif args.cmd == "addUnit":
13104
+ unit_app_id, unit_app_number = _unit_app_target(_resolve_app_root_arg(args), args.bizAppId, args.bizAppNumber)
12402
13105
  token = do_auth(cfg)
12403
13106
  result = do_add_unit(cfg, token,
12404
- biz_app_id=args.bizAppId,
12405
- biz_app_number=args.bizAppNumber,
13107
+ biz_app_id=unit_app_id,
13108
+ biz_app_number=unit_app_number,
12406
13109
  name=args.name,
12407
13110
  number=args.number,
12408
13111
  description=args.description,
@@ -12823,7 +13526,7 @@ def _main_impl():
12823
13526
  app_root=_resolve_app_root_arg(args))
12824
13527
  pjson(result)
12825
13528
 
12826
- elif args.cmd == "ksqlListDbRoutes":
13529
+ elif args.cmd == "ksqlListDbRoutes": # 本地补丁 #14
12827
13530
  token = do_auth(cfg)
12828
13531
  pjson(_api_call(cfg, token, "ksqlListDbRoutes", {}, method="POST"))
12829
13532
 
@@ -13339,6 +14042,9 @@ def main():
13339
14042
  sys.argv[1:] = _normalize_argv(sys.argv[1:])
13340
14043
  _CLI_MACHINE_MODE = "--stdout" in sys.argv[1:]
13341
14044
  _CLI_RESULT_EMITTED = False
14045
+ if _CLI_MACHINE_MODE:
14046
+ _route_unstructured_prints_to_stderr()
14047
+ set_stdout_mode(True)
13342
14048
  for index, argument in enumerate(sys.argv[1:]):
13343
14049
  if argument == "--output" and index + 2 <= len(sys.argv[1:]):
13344
14050
  set_output_file(sys.argv[1:][index + 1])