@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/package.json +1 -1
  2. package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
  3. package/skills/cosmic-build/SKILL.md +24 -9
  4. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
  5. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
  6. package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
  7. package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
  8. package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
  9. package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
  10. package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
  11. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
  12. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
  13. package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
  14. package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
  15. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
  16. package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
  17. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
  18. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
  19. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
  20. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
  21. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
  22. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
  25. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
  26. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
  27. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
  28. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
  29. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
  30. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
  31. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
  32. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
  33. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
  34. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
  35. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
  36. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
  37. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
  38. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
  39. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
  40. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
  41. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
  42. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
  43. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
  44. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
  45. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
  46. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
  47. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
  48. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
  49. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
  50. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
  51. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
  52. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
  53. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
  54. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
  55. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
  56. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
  57. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
  58. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
  59. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
  60. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
  61. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
  62. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
  63. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
  64. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
  65. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
  66. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
  67. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
  68. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
  69. package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
  70. package/skills/cosmic-build/references/compile-guide.md +36 -1
  71. package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
  72. package/skills/cosmic-build/references/delivery.md +16 -2
  73. package/skills/cosmic-build/references/execution-contract.md +15 -3
  74. package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
  75. package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
  76. package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
  77. package/skills/cosmic-build/references/report-filter-contract.md +53 -15
  78. package/skills/cosmic-build/references/sdk-inspection.md +4 -1
  79. package/skills/cosmic-build/references/type-registry.json +26 -17
  80. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
  81. package/skills/cosmic-build/scripts/_http_client.py +3 -0
  82. package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
  83. package/skills/cosmic-build/scripts/configure_list.py +112 -0
  84. package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
  85. package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
  86. package/skills/cosmic-build/scripts/execution_binding.py +25 -0
  87. package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
  88. package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
  89. package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
  90. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
  91. package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
  92. package/skills/cosmic-build/scripts/register_plugins.py +27 -10
  93. package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
  94. package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
  95. package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
  96. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
  97. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
  98. package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
  99. package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
  100. package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
  101. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
  102. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
  103. package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
  104. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
  105. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
  106. package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
  107. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
  108. package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
  109. package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
  110. package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
  111. package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
  112. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
  113. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
  114. package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
  115. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
  116. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
  117. package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
  118. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
  119. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
  120. package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
  121. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
  122. package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
  123. package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
  124. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
  125. package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
  126. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
  127. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
  128. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
  129. package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
  130. package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
  131. package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
  132. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
  133. package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
  134. package/skills/cosmic-build/scripts/test_environment.py +67 -1
  135. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
  136. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
  138. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
  139. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
  143. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
  144. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
  145. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
  146. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
  147. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
  177. package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
  178. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
  179. package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
  180. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
  181. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
  182. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
  183. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
  184. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
  185. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
  193. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
  194. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
  196. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
  197. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
  198. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
  199. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
  200. package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
  201. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
  202. package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
  203. package/skills/cosmic-dev-best-practices/references/README.md +2 -2
  204. package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
  205. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
  206. package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
  207. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
  208. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
  209. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
  210. package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
  211. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
  212. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
  213. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
  214. package/skills/erp-analysis/references/delivery-contract.md +2 -1
  215. package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
  216. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
  217. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
  218. package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
@@ -6,7 +6,35 @@ from pathlib import Path
6
6
  SCRIPTS = Path(__file__).resolve().parents[1]
7
7
  sys.path.insert(0, str(SCRIPTS))
8
8
 
9
- from plugin_source_gate import gate_path, read_state, write_state # noqa: E402
9
+ from plugin_source_gate import gate_path, read_state, write_state, validate_registration_entry # noqa: E402
10
+
11
+
12
+ def test_registration_rechecks_legacy_operation_receipts(tmp_path):
13
+ import hashlib
14
+ import pytest
15
+
16
+ source = tmp_path / "DeleteFlock.java"
17
+ for call, status, error in (
18
+ ('"demo_flock", "delete", ids, option', "passed", "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER"),
19
+ ('"demo_flock", "delete", ids, option', "skipped-by-config", "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER"),
20
+ ('"delete", "demo_flock", ids', "skipped-by-config", "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"),
21
+ ('"delete", "demo_flock", ids, option', "skipped-by-config", None),
22
+ # 未收录重载但已有实际编译证据,不凭文档缺失否认它。
23
+ ('"delete", "demo_flock", ids', "passed", None),
24
+ ):
25
+ source.write_text('''import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
26
+ import kd.bos.servicehelper.operation.OperationServiceHelper;
27
+ class DeleteFlock extends AbstractOperationServicePlugIn {
28
+ void run() { OperationServiceHelper.executeOperate(''' + call + '''); }
29
+ }''', encoding="utf-8")
30
+ entry = {"className": "DeleteFlock", "type": "operation", "formNumber": "demo_flock",
31
+ "filePath": str(source.resolve()), "compileStatus": status,
32
+ "sourceHash": hashlib.sha256(source.read_bytes()).hexdigest()}
33
+ if error:
34
+ with pytest.raises(ValueError, match=error):
35
+ validate_registration_entry(entry, "OPERATION")
36
+ continue
37
+ assert validate_registration_entry(entry, "OPERATION")["compileStatus"] == status
10
38
 
11
39
 
12
40
  def test_gate_advances_from_coding_to_source_ready_without_consuming_proof(tmp_path, capsys):
@@ -21,7 +49,7 @@ def test_gate_advances_from_coding_to_source_ready_without_consuming_proof(tmp_p
21
49
  read_state(project_root, "CouponPlugin")
22
50
  result = json.loads(capsys.readouterr().out.splitlines()[-1])
23
51
 
24
- assert result == {"state": "source_ready", "verdict": "compile_backed_pass"}
52
+ assert result == {"state": "source_ready", "verdict": "source_ready_only"}
25
53
  assert Path(gate_path(project_root, "CouponPlugin")).exists()
26
54
 
27
55
 
@@ -68,3 +68,24 @@ def test_unmapped_module_and_explicit_source_only_are_distinct(target):
68
68
  result = verify_target_sdk(str(project), {})
69
69
  assert result['status'] == 'not-run'
70
70
  assert 'unverified' in result['evidenceScope']
71
+
72
+
73
+ def test_source_only_still_validates_sdk_usage_shape(tmp_path, monkeypatch):
74
+ config = tmp_path / 'appconfig.json'
75
+ config.write_text('{"compile":false}')
76
+ monkeypatch.setenv('KD_APPCONFIG', str(config))
77
+ with pytest.raises(TargetSdkError) as error:
78
+ verify_target_sdk(str(tmp_path), {}, [{'class': 'kd.bos.entity.report.ReportColumn', 'members': 'getFieldKey'}])
79
+ assert error.value.error_code == 'PLUGIN_SDK_USAGE_INVALID'
80
+ result = verify_target_sdk(str(tmp_path), {}, [{'class': 'kd.bos.entity.report.ReportColumn'}])
81
+ assert result['status'] == 'not-run' and result['compile'] is False
82
+ assert 'classes' not in result
83
+
84
+
85
+ def test_unselected_guide_parameter_does_not_block_target_sdk(target):
86
+ result = verify_target_sdk(str(target[0]), {
87
+ "baseContract": {"class": "kd.bos.probe.Probe"},
88
+ "signatureTypes": [{"class": "kd.bos.probe.UnusedLifecycleEvent"}],
89
+ })
90
+ assert result["status"] == "verified"
91
+ assert [item["class"] for item in result["classes"]] == ["kd.bos.probe.Probe"]
@@ -124,3 +124,168 @@ def test_contract_load_failure_is_loud() -> None:
124
124
  raise AssertionError("契约缺失时应当抛错,而不是返回空表")
125
125
  finally:
126
126
  pr._PROPERTY_NAMES, pr._CONTRACT = saved_names, saved_contract
127
+
128
+
129
+ def test_known_and_unknown_properties_do_not_partially_reroute():
130
+ op = _entity_modify(value={"mustInput": True, "notAProperty": 1})
131
+ structural, calls, unresolved = plan_property_reroute([op])
132
+ assert structural == [op] and calls == [] and unresolved
133
+
134
+
135
+ def test_empty_property_map_is_unresolved():
136
+ assert plan_property_reroute([_entity_modify(value={})])[2]
137
+
138
+
139
+ def test_modify_preflight_never_writes_a_partial_request(monkeypatch):
140
+ import pytest
141
+ from _core import cmd_metaops
142
+ writes = []
143
+ monkeypatch.setattr(cmd_metaops, "_api_call", lambda *a, **k: writes.append(a))
144
+ monkeypatch.setattr(cmd_metaops, "do_set_properties_verified", lambda *a, **k: writes.append(a))
145
+ for ops, code in [
146
+ ([_entity_modify(value={"mustInput": True, "unknown": 1})], "PROPERTY_REROUTE_UNRESOLVED"),
147
+ ([_entity_modify(path="mustInput", value=True),
148
+ {"op": "add", "target": {"treeType": "form", "elementType": "control", "value": "p"},
149
+ "value": {"name": "Panel", "containerType": "FlexPanelAp"}}], "validation failed"),
150
+ ([_entity_modify(path="mustInput", value=True),
151
+ {"op": "remove", "target": {"treeType": "entity", "elementType": "field", "value": "f1"}}],
152
+ "NON_ATOMIC_PROPERTY_OP"),
153
+ ]:
154
+ with pytest.raises(ValueError, match=code):
155
+ cmd_metaops.do_modify_meta({}, "token", "FID", ops, "demo")
156
+ with pytest.raises(ValueError, match="PROPERTY_REROUTE_FORM_REQUIRED"):
157
+ cmd_metaops.do_modify_meta({}, "token", "FID", [_entity_modify(path="mustInput", value=True)])
158
+ assert writes == []
159
+
160
+
161
+ def test_modify_property_failure_stops_and_is_not_reported_as_success(monkeypatch):
162
+ from _core import cmd_metaops
163
+ writes = []
164
+ def write(*args):
165
+ writes.append(args)
166
+ return {"status": False, "errorCode": "FAIL", "verification": {"verified": False}}
167
+ monkeypatch.setattr(cmd_metaops, "do_set_properties_verified", write)
168
+ result = cmd_metaops.do_modify_meta({}, "token", "FID", [
169
+ _entity_modify(path="mustInput", value=True),
170
+ _entity_modify(key="f2", path="name", value="名称"),
171
+ ], "demo")
172
+ assert result["status"] is False and result["errorCode"] == "PROPERTY_REROUTE_INCOMPLETE"
173
+ assert len(writes) == 1 and result["data"]["pending"][0]["elementKey"] == "f2"
174
+ assert result["retryAdvice"] == "reconcile_before_retry"
175
+
176
+
177
+ def test_property_exception_retains_prior_receipts_and_stops(monkeypatch):
178
+ from _core import cmd_metaops
179
+ writes = []
180
+ def write(*args):
181
+ writes.append(args)
182
+ if len(writes) == 2:
183
+ raise RuntimeError("readback unavailable after dispatch")
184
+ return {"status": True, "errorCode": "0", "verification": {"verified": True}}
185
+ monkeypatch.setattr(cmd_metaops, "do_set_properties_verified", write)
186
+ result = cmd_metaops.do_modify_meta({}, "token", "FID", [
187
+ _entity_modify(key=key, path="mustInput", value=True) for key in ("f1", "f2", "f3")], "demo")
188
+ assert result["status"] is False and len(writes) == 2
189
+ assert result["data"]["rerouted"][0]["result"]["verification"]["verified"]
190
+ assert result["data"]["rerouted"][1]["result"]["errorCode"] == "PROPERTY_REROUTE_EXCEPTION"
191
+ assert result["data"]["pending"][0]["elementKey"] == "f3"
192
+
193
+
194
+ def test_property_success_requires_independent_verification(monkeypatch):
195
+ from _core import cmd_metaops
196
+ for verified in (False, True):
197
+ monkeypatch.setattr(cmd_metaops, "do_set_properties_verified", lambda *a, **k: {
198
+ "status": True, "errorCode": "0", "verification": {"verified": verified}})
199
+ result = cmd_metaops.do_modify_meta({}, "token", "FID", [
200
+ _entity_modify(path="mustInput", value=True)], "demo")
201
+ assert result["status"] is verified
202
+
203
+
204
+ def test_combo_null_values_and_boolean_add_contract():
205
+ from _core.validators import _validate_ops
206
+ for value in (None, [None], [{"value": None, "caption": "选项"}], [{"caption": "选项"}]):
207
+ assert _validate_ops([_entity_modify(path="comboItems.add", value=value)])
208
+ assert _validate_ops([_entity_modify(path="comboItems.remove", value=[None])])
209
+ assert _validate_ops([_entity_modify(path="comboItems.add", value=[
210
+ {"value": 0, "caption": "零"}, {"value": "01", "caption": "一"}])]) == []
211
+ op = {"op": "add", "target": {"treeType": "entity", "elementType": "field", "value": "amount"},
212
+ "value": {"fieldType": "DecimalField", "bizWord": "amount", "name": "金额", "mustInput": True}}
213
+ assert _validate_ops([op]) == []
214
+ assert _validate_ops([{**op, "value": {**op["value"], "mustInput": "true"}}])
215
+
216
+
217
+ def test_combo_create_validates_every_supplied_alias_and_effective_label():
218
+ from _core.validators import _validate_ops
219
+ op = {"op": "add", "target": {"treeType": "entity", "elementType": "field", "value": "mode"},
220
+ "value": {"fieldType": "ComboField", "bizWord": "mode", "name": "模式"}}
221
+ rows = [{"value": 0, "caption": "零"}, {"value": "01", "label": "一"}]
222
+ for options in ({"items": rows}, {"comboOptions": rows}, {"items": rows, "comboOptions": rows}):
223
+ assert _validate_ops([{**op, "value": {**op["value"], **options}}]) == []
224
+ for options in (
225
+ {}, {"items": None}, {"items": []}, {"items": {"value": "a"}},
226
+ {"items": [None]}, {"items": [{"value": None, "caption": "空"}]},
227
+ {"items": rows, "comboOptions": [{"value": "a", "caption": "甲"}]},
228
+ {"items": None, "comboOptions": rows}, {"items": rows, "comboOptions": None},
229
+ {"items": [{"value": "a", "label": " ", "caption": "不会被使用"}]},
230
+ {"items": [{"value": "a", "label": 1, "caption": "不会被使用"}]},
231
+ {"items": [{"value": 0, "caption": "零"}, {"value": "0", "caption": "重复"}]},
232
+ ):
233
+ assert _validate_ops([{**op, "value": {**op["value"], **options}}]), options
234
+
235
+
236
+ def test_combo_modify_aliases_use_only_backend_supported_shapes():
237
+ from _core.validators import _validate_ops
238
+ for prefix in ("comboItems", "comboOptions"):
239
+ for path in (prefix, prefix + ".add"):
240
+ for rows in ([{"value": "01", "caption": "一"}], [{"value": 0, "label": None, "caption": "零"}]):
241
+ assert _validate_ops([_entity_modify(path=path, value=rows)]) == []
242
+ for rows in (None, "a", [None], [0], [{"value": None, "caption": "空"}],
243
+ [{"value": "a", "label": "", "caption": "不会被使用"}],
244
+ [{"value": "a", "caption": "甲"}, {"value": "a", "caption": "重复"}]):
245
+ assert _validate_ops([_entity_modify(path=path, value=rows)]), (path, rows)
246
+ for rows in ("01", ["01", "0"], [{"value": 0}, {"value": "01"}]):
247
+ assert _validate_ops([_entity_modify(path=prefix + ".remove", value=rows)]) == []
248
+ for rows in (None, [None], [0], [False], [{"value": None}], [{"value": ""}], ["a", "a"]):
249
+ assert _validate_ops([_entity_modify(path=prefix + ".remove", value=rows)]), rows
250
+ for path in (prefix + ".unknown", prefix + ".add.extra", prefix + "."):
251
+ assert _validate_ops([_entity_modify(path=path, value=[])]), path
252
+
253
+
254
+ def test_invalid_modify_paths_and_combo_inputs_never_dispatch(monkeypatch):
255
+ import pytest
256
+ from _core import cmd_metaops
257
+ from _core.validators import _validate_ops
258
+ writes = []
259
+ monkeypatch.setattr(cmd_metaops, "_api_call", lambda *a, **k: writes.append(a))
260
+ monkeypatch.setattr(cmd_metaops, "do_set_properties_verified", lambda *a, **k: writes.append(a))
261
+ for path in (False, 0, 1, [], {}, "", " "):
262
+ op = _entity_modify(path=path, value={"mustInput": True})
263
+ structural, calls, unresolved = plan_property_reroute([op])
264
+ assert structural == [op] and calls == [] and unresolved
265
+ form_op = {**op, "target": {**op["target"], "treeType": "form"}}
266
+ assert _validate_ops([form_op])
267
+ with pytest.raises(ValueError, match="validation failed"):
268
+ cmd_metaops.do_modify_meta({}, "token", "FID", [form_op], "demo")
269
+ with pytest.raises(ValueError, match="PROPERTY_REROUTE_UNRESOLVED"):
270
+ cmd_metaops.do_modify_meta({}, "token", "FID", [op], "demo")
271
+ for prefix in ("comboItems", "comboOptions"):
272
+ for suffix, value in (("", None), (".add", [None]), (".remove", [0]), (".unknown", [])):
273
+ with pytest.raises(ValueError, match="validation failed"):
274
+ cmd_metaops.do_modify_meta({}, "token", "FID", [
275
+ _entity_modify(path=prefix + suffix, value=value)], "demo")
276
+ op = {"op": "add", "target": {"treeType": "entity", "elementType": "field", "value": "mode"},
277
+ "value": {"fieldType": "ComboField", "name": "模式", "bizWord": "mode",
278
+ "items": None, "comboOptions": [{"value": "a", "caption": "甲"}]}}
279
+ with pytest.raises(ValueError, match="validation failed"):
280
+ cmd_metaops.do_modify_meta({}, "token", "FID", [op], "demo")
281
+ assert writes == []
282
+
283
+
284
+ def test_verify_guidance_requires_reconciliation_not_write_replay():
285
+ guide = (APP_BUILD / "skills/cosmic-meta-api/modules/verify/SKILL.md").read_text(encoding="utf-8")
286
+ assert "reconcile_before_retry" in guide
287
+ assert "只重试验证读取,禁止重放写入" in guide
288
+ assert "不得重放整个 artifact / ops 数组" in guide
289
+ assert "WARN 不能证明本次写入未发生" in guide
290
+ assert "自动切换到建议的替代 API 重新执行" not in guide
291
+ assert "automatically switch to the suggested alternative API, re-execute" not in guide
@@ -997,3 +997,38 @@ def test_element_resolver_collapses_field_and_fieldap_with_same_key() -> None:
997
997
  {"key": "uhyl_text_field", "name": "文本", "type": "FieldAp"},
998
998
  ]
999
999
  assert MODULE.resolve_element_rows(rows, "文本", "demo") == "uhyl_text_field"
1000
+
1001
+
1002
+
1003
+ def test_currency_binding_requires_live_candidates_before_write() -> None:
1004
+ schema = {"status": True, "data": [{"propertyName": "CurrencyFieldId", "editorType": "ecombo", "complexity": "simple", "editable": True}]}
1005
+ with (patch.object(MODULE, "do_get_property_schema", return_value=schema),
1006
+ patch.object(MODULE, "do_set_properties") as setter):
1007
+ result = MODULE.do_set_properties_verified({}, "token", "demo", "amount", {"CurrencyFieldId": "币别"})
1008
+ assert result["status"] is False
1009
+ assert result["errorCode"] == "PROPERTY_VALUE_INVALID"
1010
+ assert "candidates are unavailable" in result["data"]["errors"][0]["message"]
1011
+ setter.assert_not_called()
1012
+
1013
+
1014
+ def test_currency_binding_rejects_ambiguous_live_option_names() -> None:
1015
+ declaration = {"propertyName": "CurrencyFieldId", "editorType": "ecombo", "complexity": "simple", "options": [
1016
+ {"id": "a", "name": "币别"}, {"id": "b", "name": "币别"},
1017
+ ]}
1018
+ compiled, _, rejection = MODULE._compile_simple_property_map([declaration], {"CurrencyFieldId": "币别"})
1019
+ assert compiled is None
1020
+ assert rejection["errorCode"] == "PROPERTY_VALUE_INVALID"
1021
+ assert "ambiguous" in rejection["data"]["errors"][0]["message"]
1022
+ assert MODULE._normalize_simple_property_value(declaration, "a") == "a"
1023
+
1024
+
1025
+ def test_field_selector_resolves_declared_child_scope_without_options() -> None:
1026
+ schema = {"status": True, "data": [{"propertyName": "CurrencyFieldId", "editorType": "ecombo", "complexity": "simple", "editable": True, "constraints": {"scope": "child", "types": ["CurrencyField"]}}]}
1027
+ rows = {"status": True, "data": [{"category": "field", "key": "currency", "id": "currency-id", "type": "CurrencyField", "parentKey": "amount"}]}
1028
+ with (patch.object(MODULE, "do_get_property_schema", return_value=schema),
1029
+ patch.object(MODULE, "do_list_elements", return_value=rows),
1030
+ patch.object(MODULE, "do_set_properties", return_value={"status": True}) as setter,
1031
+ patch.object(MODULE, "do_get_properties", return_value={"status": True, "data": {"CurrencyFieldId": "currency-id"}})):
1032
+ result = MODULE.do_set_properties_verified({}, "token", "demo", "amount", {"CurrencyFieldId": "currency"})
1033
+ assert result["verification"]["verified"]
1034
+ assert setter.call_args.args[-1] == {"CurrencyFieldId": "currency-id"}
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
 
7
7
 
8
8
  SCRIPT = Path(__file__).resolve().parents[1] / "query_plugin_handoff.py"
9
+ sys.path.insert(0, str(SCRIPT.parent))
9
10
 
10
11
 
11
12
  def handoff_file(tmp_path):
@@ -59,6 +60,44 @@ def test_target_readiness_is_bounded_and_missing_evidence_is_not_claimed(tmp_pat
59
60
  assert 'classes' not in context['targetSdk'] and 'large-evidence-' not in output
60
61
 
61
62
 
63
+ def test_coding_projection_reads_gate_without_repreparing_or_mutating_snapshot(tmp_path):
64
+ file, document = handoff_file(tmp_path)
65
+ gate = tmp_path / ".temp" / ".BookingPlugin-gate"
66
+ document["handoff"].update({"projectRoot": str(tmp_path), "className": "BookingPlugin",
67
+ "sourceGate": {"requestedState": "coding", "path": str(gate)}})
68
+ file.write_text(json.dumps(document), encoding="utf-8")
69
+ original = file.read_bytes()
70
+ for content, expected, verified in [(None, "missing", False), ("coding:session-123", "coding", True),
71
+ ("source_ready", "source_ready", True), ("invalid", "invalid", False)]:
72
+ if content is not None:
73
+ gate.parent.mkdir(exist_ok=True)
74
+ gate.write_text(content, encoding="utf-8")
75
+ for operation in ("coding-start", "coding-context"):
76
+ code, context, _ = query(file, operation)
77
+ assert code == 0
78
+ assert context["sourceGate"]["requestedState"] == "coding"
79
+ assert context["sourceGate"]["path"] == str(gate)
80
+ assert context["sourceGate"]["state"] == expected
81
+ assert context["sourceGate"]["verified"] == verified
82
+ assert context["sourceGate"]["sessionId"] == ("session-123" if content == "coding:session-123" else None)
83
+ assert "not a compile receipt" in context["sourceGate"]["evidenceScope"]
84
+ assert file.read_bytes() == original
85
+ assert (gate.read_text() if gate.exists() else None) == content
86
+ gate.write_bytes(b"\xff")
87
+ code, context, _ = query(file, "coding-start")
88
+ assert code == 2 and context["errorCode"] == "HANDOFF_SOURCE_GATE_UNAVAILABLE"
89
+
90
+
91
+ def test_coding_projection_rejects_gate_for_another_class(tmp_path):
92
+ file, document = handoff_file(tmp_path)
93
+ document["handoff"].update({"projectRoot": str(tmp_path), "className": "BookingPlugin",
94
+ "sourceGate": {"requestedState": "coding", "path": str(tmp_path / ".temp" / ".OtherPlugin-gate")}})
95
+ file.write_text(json.dumps(document), encoding="utf-8")
96
+ for operation in ("coding-start", "coding-context"):
97
+ code, context, _ = query(file, operation)
98
+ assert code == 2 and context["errorCode"] == "HANDOFF_SOURCE_GATE_INVALID"
99
+
100
+
62
101
  def test_index_find_and_get_return_only_requested_native_context(tmp_path):
63
102
  file, document = handoff_file(tmp_path)
64
103
  original = hashlib.sha256(file.read_bytes()).hexdigest()
@@ -158,3 +197,185 @@ def test_large_startup_inventory_is_explicitly_referenced_instead_of_dumped(tmp_
158
197
  assert result["entities"][0]["fieldCount"] == 1000
159
198
  assert result["entities"][0]["fieldsPath"] == "/handoff/formGraph/entities/0/fields"
160
199
  assert "fields" not in result["entities"][0]
200
+
201
+
202
+ def test_report_context_uses_real_contract_without_fabricating_form_graph(tmp_path):
203
+ from prepare_plugin_handoff import _compile_report_contract
204
+ file, document = handoff_file(tmp_path)
205
+ document['handoff'].pop('formGraph')
206
+ document['handoff'].update(type='reportlist', formNumber='sample_rpt',
207
+ reportContract=_compile_report_contract({'reportColumns': [
208
+ {'kind': 'column', 'fieldKey': 'person_id', 'dateIndex': 'person_id', 'fieldType': 'bigint',
209
+ 'caption': '人员 / Person', 'presentation': {'kind': 'basedata', 'entityId': 'bos_user', 'displayProp': 'name'}}],
210
+ 'reportFilters': [{'fieldPath': 'flt_period', 'displayName': '期间 / Period', 'compare': '='}],
211
+ 'reportSorts': [{'fieldKey': 'person_id', 'direction': 'desc'}],
212
+ 'reportPlugins': [{'className': 'kd.test.report.P', 'type': 'Java'}]}, 'sample_rpt'))
213
+ file.write_text(json.dumps(document, ensure_ascii=False))
214
+ for operation in ('coding-start', 'coding-context'):
215
+ code, context, stdout = query(file, operation)
216
+ assert code == 0 and context['domain'] == 'report'
217
+ assert 'formGraph' not in context and 'large-evidence-' not in stdout
218
+ assert context['reportContract']['dataSetColumnTypes'][0]['algoDataType'] == 'DataType.LongType'
219
+ assert context['reportContract']['columnPresentation'][0]['entityId'] == 'bos_user'
220
+ assert context['reportContract']['filterValueContracts'][0]['filterKey'] == 'flt_period'
221
+ assert context['reportContract']['defaultSorts'] == [
222
+ {'fieldKey': 'person_id', 'dateIndex': 'person_id', 'direction': 'desc'}]
223
+ assert context['reportContract']['defaultSortPolicy']['priority'] == 'interactiveThenDefault'
224
+ assert context['reportContract']['sections']['defaultSorts']['path'] == '/handoff/reportContract/defaultSorts'
225
+ assert context['codingEvidence']['apiContracts'][0]['path'].endswith('/0')
226
+ assert 'runtime' in context['evidenceScope']
227
+ document['handoff']['reportContract']['formNumber'] = 'other_rpt'
228
+ file.write_text(json.dumps(document))
229
+ assert query(file, 'coding-start')[1]['errorCode'] == 'HANDOFF_CODING_CONTEXT_INVALID'
230
+ document['handoff']['reportContract']['formNumber'] = 'sample_rpt'
231
+ document['handoff']['targetSdk'] = ['invalid']
232
+ file.write_text(json.dumps(document))
233
+ assert query(file, 'coding-start')[1]['errorCode'] == 'HANDOFF_CODING_CONTEXT_INVALID'
234
+
235
+
236
+ def test_large_report_contract_uses_exact_section_pointers(tmp_path):
237
+ file, document = handoff_file(tmp_path)
238
+ document['handoff'].update(type='reportform', formNumber='sample_rpt', reportContract={
239
+ 'formNumber': 'sample_rpt', 'dataSetColumns': [f'col_{i}' for i in range(1200)],
240
+ 'dataSetColumnTypes': [{'fieldKey': f'col_{i}', 'algoDataType': 'LongType'} for i in range(1200)],
241
+ 'columnPresentation': [], 'filterEnforcement': [], 'filterValueContracts': []})
242
+ file.write_text(json.dumps(document))
243
+ code, context, output = query(file, 'coding-start')
244
+ assert code == 0 and len(output.encode()) <= 16 * 1024
245
+ assert context['reportContract']['detailsOmitted'] is True
246
+ assert context['reportContract']['sections']['dataSetColumnTypes'] == {
247
+ 'path': '/handoff/reportContract/dataSetColumnTypes', 'count': 1200}
248
+ assert 'formGraph' not in context
249
+ document['handoff']['reportContract']['dataSetColumnTypes'] = [False]
250
+ file.write_text(json.dumps(document))
251
+ assert query(file, 'coding-start')[1]['errorCode'] == 'HANDOFF_CODING_CONTEXT_INVALID'
252
+
253
+
254
+ def test_changed_or_missing_prepared_input_rejects_old_success_snapshot(tmp_path):
255
+ file, document = handoff_file(tmp_path)
256
+ intent = tmp_path / 'intent.json'
257
+ intent.write_text('{"coreLogic":["old"]}')
258
+ document['inputFiles'] = [{'path': str(intent), 'sha256': hashlib.sha256(intent.read_bytes()).hexdigest()}]
259
+ file.write_text(json.dumps(document))
260
+ assert query(file, 'coding-start')[0] == 0
261
+ intent.write_text('{"coreLogic":["new"]}')
262
+ assert query(file, 'coding-start')[1]['errorCode'] == 'HANDOFF_INPUT_STALE'
263
+ document['inputFiles'][0]['path'] = str(tmp_path / 'missing.json')
264
+ file.write_text(json.dumps(document))
265
+ assert query(file, 'index')[1]['errorCode'] == 'HANDOFF_INPUT_STALE'
266
+ document['inputFiles'][0]['path'] = 'relative.json'
267
+ file.write_text(json.dumps(document))
268
+ assert query(file, 'index')[1]['errorCode'] == 'HANDOFF_INPUT_INVALID'
269
+
270
+
271
+ def test_startup_keeps_main_form_enums_without_loading_all_related_form_values(tmp_path):
272
+ file, document = handoff_file(tmp_path)
273
+ source = {"fieldKey": "source", "fieldName": "来源", "entityKey": "booking_head",
274
+ "comboOptions": [{"label": "ERP", "value": "ERP"}, {"label": "BPM 转嫁数据", "value": "opt_2"}]}
275
+ related = [{"fieldKey": "category_" + str(index), "entityKey": "related_form",
276
+ "comboOptions": [{"label": "关联枚举" * 100, "value": str(index)}]} for index in range(50)]
277
+ document["handoff"]["stateFields"] = [source, *related]
278
+ file.write_text(json.dumps(document, ensure_ascii=False), encoding="utf-8")
279
+ before = file.read_bytes()
280
+ code, start, output = query(file, "coding-start")
281
+ assert code == 0 and len(output.encode("utf-8")) < 16384
282
+ assert start["stateFields"] == [source]
283
+ assert start["remainingContext"]["stateFields"] == "/handoff/stateFields"
284
+ assert "Main form only" in start["stateFieldsScope"]
285
+ code, value, _ = query(file, "get", "--path", "/handoff/stateFields/50")
286
+ assert code == 0 and value["value"] == related[-1]
287
+ assert file.read_bytes() == before
288
+
289
+
290
+ def test_runtime_context_cli_reads_the_same_bound_target_and_never_renders_cards(tmp_path):
291
+ file, document = handoff_file(tmp_path)
292
+ app = tmp_path / "app/demo"
293
+ (app / "_runtime").mkdir(parents=True)
294
+ (app / "_runtime/context.json").write_text(json.dumps({"environment": {"baseUrl": "http://127.0.0.1:8080/ierp"}}))
295
+ url = "http://127.0.0.1:8080/ierp/?formId=bos_list&type=list&billFormId=booking"
296
+ archive = app / "archive.json"
297
+ archive.write_text(json.dumps({"artifacts": [{"formNumber": "booking", "listUrl": url}]}))
298
+ document["handoff"].update({"appRoot": str(app), "formNumber": "booking"})
299
+ file.write_text(json.dumps(document))
300
+ before = archive.read_bytes()
301
+ code, result, _ = query(file, "runtime-context")
302
+ assert code == 0 and result["runtime"]["listUrl"] == url
303
+ assert result["runtime"]["runtimeVerified"] is False
304
+ assert archive.read_bytes() == before
305
+ code, result, _ = query(file, "coding-start", "--resolve-entry")
306
+ assert code == 2 and result["errorCode"] == "HANDOFF_RUNTIME_ARGUMENT_INVALID"
307
+
308
+
309
+ def test_startup_supplies_selected_signatures_without_changing_member_inventory(tmp_path):
310
+ file, document = handoff_file(tmp_path)
311
+ document["handoff"]["codingEvidence"]["guideSignatures"] = ["public void onPreparePropertys(PreparePropertysEventArgs e)"]
312
+ file.write_text(json.dumps(document))
313
+ code, result, output = query(file, "coding-start")
314
+ assert code == 0
315
+ contract = result["codingEvidence"]["apiContracts"][0]
316
+ assert contract["members"] == ["query"]
317
+ assert contract["signatures"][0]["signature"] == document["handoff"]["codingEvidence"]["apiContracts"][0]["members"][0]["signature"]
318
+ assert contract["import"] == "import kd.bos.servicehelper.QueryServiceHelper;"
319
+ assert result["codingEvidence"]["guideSignatures"] == document["handoff"]["codingEvidence"]["guideSignatures"]
320
+ assert result["entities"][1]["fields"][0]["timeValueUnit"] == "seconds-after-midnight"
321
+ assert result["runtimeContextCommand"][-1] == str(file)
322
+ assert len(output.encode()) <= 16 * 1024
323
+
324
+
325
+ def test_time_empty_and_zero_contract_reaches_startup_without_another_lookup(tmp_path):
326
+ from prepare_plugin_handoff import _field_contract
327
+ file, document = handoff_file(tmp_path)
328
+ field = _field_contract({"fieldKey": "start_time", "fieldType": "TimeProp"})
329
+ assert "-1" in field["note"] and "Zero is valid midnight" in field["note"]
330
+ document["handoff"]["formGraph"]["entities"][1]["fields"] = [field]
331
+ file.write_text(json.dumps(document))
332
+ code, start, output = query(file, "coding-start")
333
+ assert code == 0
334
+ assert start["entities"][1]["fields"][0]["note"] == field["note"]
335
+ assert start["compileCommand"][-2:] == ["--handoff", str(file)]
336
+ assert len(output.encode()) <= 16 * 1024
337
+
338
+
339
+ def test_operation_execution_locales_share_the_real_implementation():
340
+ root = SCRIPT.parents[1]
341
+ if root.name == "zh" and root.parent.name == ".locales":
342
+ root = root.parent.parent
343
+ for name in ["query_plugin_handoff.py", "compile_plugin_source.py", "prepare_plugin_handoff.py", "test_environment.py"]:
344
+ assert (root / "scripts" / name).read_bytes() == (root / ".locales/zh/scripts" / name).read_bytes()
345
+
346
+
347
+ def test_bound_startup_returns_the_actual_target_test_command(tmp_path):
348
+ file, document = handoff_file(tmp_path)
349
+ document['handoff']['projectRoot'] = str(tmp_path)
350
+ file.write_text(json.dumps(document))
351
+ code, start, _ = query(file, 'coding-start')
352
+ assert code == 0
353
+ assert start['testCommand'] == [sys.executable, str(SCRIPT.with_name('run_java_tests.py')), '--project-root', str(tmp_path)]
354
+ assert Path(start['testCommand'][1]).is_file()
355
+
356
+
357
+ def test_large_page_startup_exposes_exact_control_pointer(tmp_path):
358
+ file, document = handoff_file(tmp_path)
359
+ handoff = document["handoff"]
360
+ handoff.pop("formGraph")
361
+ handoff["formNumber"] = "sample_page"
362
+ handoff["uiContext"] = {"formNumber": "sample_page", "source": "getFormMetadata", "plugins": [],
363
+ "controls": [{"key": f"control_{index}", "type": "TextField"} for index in range(2000)]}
364
+ file.write_text(json.dumps(document), encoding="utf-8")
365
+ for operation in ("coding-start", "coding-context"):
366
+ code, result, output = query(file, operation)
367
+ assert code == 0 and len(output.encode()) < (16 if operation == "coding-start" else 48) * 1024
368
+ assert result["uiContext"]["controlCount"] == 2000
369
+ assert result["uiContext"]["controlsPath"] == "/handoff/uiContext/controls"
370
+ code, result, _ = query(file, "get", "--path", "/handoff/uiContext/controls/1999")
371
+ assert code == 0 and result["value"]["key"] == "control_1999"
372
+
373
+
374
+ def test_selected_entity_metadata_cannot_be_replaced_by_page_context(tmp_path):
375
+ file, document = handoff_file(tmp_path)
376
+ document["handoff"].pop("formGraph")
377
+ document["handoff"].update({"formNumber": "sample_page", "metadataNeeds": ["form", "entity"],
378
+ "uiContext": {"formNumber": "sample_page", "source": "getFormMetadata", "controls": [], "plugins": []}})
379
+ file.write_text(json.dumps(document))
380
+ code, result, _ = query(file, "coding-start")
381
+ assert code == 2 and result["errorCode"] == "HANDOFF_CODING_CONTEXT_INVALID"
@@ -262,8 +262,9 @@ class MainTest(unittest.TestCase):
262
262
  mock_config.return_value = {"baseUrl": "https://api.example.com"}
263
263
 
264
264
  output = self._run()
265
- self.assertEqual(output["status"], "success")
266
- self.assertEqual(output["summary"]["success"], 1)
265
+ self.assertEqual(output["status"], "routed")
266
+ self.assertEqual(output["summary"], {"total": 1, "success": 0, "failed": 0, "deferred": 1})
267
+ mock_config.assert_not_called()
267
268
  self.assertIn("needs no registration call", output["results"][0]["message"])
268
269
  self.assertEqual(output["results"][0]["registeredBy"], "platform class discovery")
269
270
  self.assertFalse(output["results"][0]["registeredHere"])
@@ -610,10 +611,81 @@ class ReportPluginNotOwnedHereTest(unittest.TestCase):
610
611
  self.assertEqual(output["verification"]["status"], "not_run")
611
612
  self.assertEqual(output["verification"]["checks"], [])
612
613
  self.assertIn("nothing to verify", output["verification"]["reason"])
613
- # not_run is not a failure: the class really is registered, elsewhere.
614
- self.assertEqual(output["status"], "success")
614
+ # 转交不证明其他注册器已经执行,不能计入实际注册成功。
615
+ self.assertEqual(output["status"], "routed")
616
+ self.assertEqual(output["summary"], {"total": 1, "success": 0, "failed": 0, "deferred": 1})
617
+ self.assertFalse(output["results"][0]["success"])
618
+ mock_config.assert_not_called()
615
619
  mock_get.assert_not_called()
616
620
 
617
621
 
622
+ class ReportFormPluginRegisteredHereTest(unittest.TestCase):
623
+ """A reportform plugin is the report form's own form plugin, registered and verified here.
624
+
625
+ The registry used to name the report pipeline as its registrar, and the pipeline writes only the
626
+ data-fetch slot and the left tree: the class was reported as owned elsewhere and never
627
+ registered, so the report's hyperlink, toolbar and header-filter handlers did nothing. The
628
+ plugin service registers a BILL_FORM target on the form's root plugin list, which for a report
629
+ is the report form, and queryEditable reads that list back as location BILL_FORM. Uses the real
630
+ registry on purpose: the registry entry is what this test pins.
631
+ """
632
+
633
+ def _run(self, extra_args=None):
634
+ argv = ["register_plugins.py", "--app-root", "/tmp"] + list(extra_args or [])
635
+ with mock.patch("builtins.print") as mock_print, mock.patch("sys.argv", argv):
636
+ register_plugins.main()
637
+ return json.loads(mock_print.call_args[0][0])
638
+
639
+ def test_the_registry_routes_report_form_plugins_to_the_form_plugin_list(self):
640
+ root = register_plugins._find_root_dir()
641
+ self.assertEqual(register_plugins._load_type_registry(root)["reportform"], "BILL_FORM")
642
+ self.assertEqual(register_plugins._load_type_owners(root)["reportform"], "plugin-register-api")
643
+ # The data-fetch and left-tree types stay with the report pipeline's own slots.
644
+ self.assertNotIn("reportlist", register_plugins._load_type_registry(root))
645
+ self.assertNotIn("reporttreelist", register_plugins._load_type_registry(root))
646
+
647
+ @mock.patch("register_plugins.kd_api.get")
648
+ @mock.patch("register_plugins.kd_api.post")
649
+ @mock.patch("register_plugins._load_config")
650
+ @mock.patch("register_plugins._load_classes_from_archive")
651
+ def test_report_form_plugin_registers_on_the_report_and_reads_back(
652
+ self, mock_load_classes, mock_config, mock_post, mock_get,
653
+ ):
654
+ mock_load_classes.return_value = [{
655
+ "className": "kd.uhyl.report.KpiDrillReportPlugin", "type": "reportform",
656
+ "formNumber": "uhyl_kpi_rpt", "description": "drill"}]
657
+ mock_config.return_value = {"baseUrl": "https://api.example.com"}
658
+ mock_post.return_value = {"status": True, "data": {"success": True, "message": ""}}
659
+ mock_get.return_value = {"status": True, "data": [
660
+ {"className": "kd.uhyl.report.KpiReportPlugin", "location": "REPORT_QUERY_PLUGIN"},
661
+ {"className": "kd.uhyl.report.KpiDrillReportPlugin", "location": "BILL_FORM"},
662
+ ]}
663
+
664
+ output = self._run(["--verify"])
665
+
666
+ self.assertEqual(output["status"], "success")
667
+ body = mock_post.call_args[0][3]
668
+ self.assertEqual(body["targetType"], "BILL_FORM")
669
+ self.assertEqual(body["formNumber"], "uhyl_kpi_rpt")
670
+ self.assertEqual(output["verification"]["status"], "pass")
671
+ self.assertTrue(output['results'][0]['registeredHere'])
672
+ self.assertTrue(output['results'][0]['verifiedHere'])
673
+ self.assertEqual(output["activation"]["registeredClasses"], ["kd.uhyl.report.KpiDrillReportPlugin"])
674
+
675
+ def test_mixed_report_delivery_distinguishes_registered_page_from_routed_query(self):
676
+ candidates = [{"className": "kd.sample.report.Query", "type": "reportlist", "formNumber": "sample_rpt"},
677
+ {"className": "kd.sample.report.Page", "type": "reportform", "formNumber": "sample_rpt"}]
678
+ with mock.patch.object(register_plugins, '_load_classes_from_archive', return_value=candidates), \
679
+ mock.patch.object(register_plugins, '_load_config', return_value={'baseUrl': 'https://example.test'}), \
680
+ mock.patch.object(register_plugins.kd_api, 'post', return_value={'status': True, 'data': {'success': True}}), \
681
+ mock.patch.object(register_plugins.kd_api, 'get', return_value={'status': True, 'data': [
682
+ {'className': 'kd.sample.report.Page', 'location': 'BILL_FORM'}]}):
683
+ output = self._run(['--verify'])
684
+ self.assertEqual(output['status'], 'partial')
685
+ self.assertEqual(output['summary'], {'total': 2, 'success': 1, 'failed': 0, 'deferred': 1})
686
+ self.assertFalse(output['results'][0]['verifiedHere'])
687
+ self.assertTrue(output['results'][1]['verifiedHere'])
688
+
689
+
618
690
  if __name__ == "__main__":
619
691
  unittest.main()