@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
@@ -45,6 +45,7 @@ def _readback(
45
45
  "menu": {
46
46
  "menuId": "MENU-ID",
47
47
  "name": "测试菜单",
48
+ "description": "测试菜单",
48
49
  "menuType": entity_type,
49
50
  "formNumber": form_number,
50
51
  "openType": "MainNewTabPage",
@@ -73,6 +74,21 @@ def test_add_menu_defaults_bill_to_list_and_verifies_readback() -> None:
73
74
  assert result["data"]["verified"] is True
74
75
  add_payload = api.call_args_list[1].args[3]
75
76
  assert add_payload["parameterType"] == "ListShowParameter"
77
+ assert add_payload["description"] == "测试菜单"
78
+
79
+
80
+ def test_add_menu_preserves_explicit_description_and_refuses_null_readback():
81
+ for actual, verified in (("说明", True), (None, False)):
82
+ readback = _readback(None, form_number=None)
83
+ readback["data"]["menu"]["description"] = actual
84
+ with mock.patch.object(APPS, "_api_call", side_effect=[_write(), readback]) as api:
85
+ result = APPS.do_add_menu({}, "token", "APP-ID", "测试菜单",
86
+ menu_type="category", description="说明")
87
+ assert api.call_args_list[0].args[3]["description"] == "说明"
88
+ assert result["status"] is verified
89
+ if not verified:
90
+ assert result["errorCode"] == "MENU_WRITE_VERIFY_FAILED"
91
+ assert any("description" in error for error in result["data"]["errors"])
76
92
 
77
93
 
78
94
  def test_add_menu_defaults_non_list_form_to_form_entry() -> None:
@@ -139,10 +139,24 @@ def test_failed_child_query_preserves_underlying_error(tmp_path, capsys):
139
139
  "print(json.dumps({'status': False, 'errorCode': 'APP_ROOT_IDENTITY_INVALID'}))\n"
140
140
  "sys.exit(2)\n"
141
141
  )
142
- assert emitter._run_cosmic_api(str(script), "queryPublicTemplates", {}, app_root=tmp_path) is None
142
+ assert emitter._run_cosmic_api(str(script), "queryPublicTemplates", {}, app_root=tmp_path)["errorCode"] == "APP_ROOT_IDENTITY_INVALID"
143
143
  assert "APP_ROOT_IDENTITY_INVALID" in capsys.readouterr().err
144
144
 
145
145
 
146
+ def test_template_lookup_stops_at_environment_failure_without_mislabeling_input(monkeypatch, tmp_path):
147
+ calls = []
148
+ def read(*args, **kwargs):
149
+ calls.append(args)
150
+ return {"status": False, "errorCategory": "environment", "errorCode": "SKILL_REVISION_MISMATCH",
151
+ "message": "Use the current installed source"}
152
+ monkeypatch.setattr(emitter, "_run_cosmic_api", read)
153
+ logs = emitter._Logs(tmp_path)
154
+ section = {"artifact": {"entities": [{"type": "BillEntity", "bizName": "request", "templateKey": "bos_billtpl"}]}}
155
+ assert emitter._validate_template_keys(section, logs, "cosmic_api.py", app_root=tmp_path) is False
156
+ assert len(calls) == 1
157
+ assert logs.error_details[0]["code"] == "SKILL_REVISION_MISMATCH"
158
+
159
+
146
160
  def test_template_query_normalizes_legacy_model_type(monkeypatch):
147
161
  calls = []
148
162
  monkeypatch.setattr(cmd_forms, "_api_call", lambda *args, **kwargs: calls.append(args) or {
@@ -154,6 +168,21 @@ def test_template_query_normalizes_legacy_model_type(monkeypatch):
154
168
  assert calls[1][3] == {"modelType": "BaseFormModel", "bizAppNumber": "demo"}
155
169
 
156
170
 
171
+ def test_compiler_forwards_before_generating_files(tmp_path, monkeypatch, capfd):
172
+ import execution_binding
173
+ selected = tmp_path / "selected compiler.py"
174
+ selected.write_text('import json,sys\nprint(json.dumps({"argv":sys.argv[1:]}))\n')
175
+ source = tmp_path / "request.json"
176
+ source.write_text('{"artifact":{"entities":[]}}')
177
+ original = source.read_bytes()
178
+ arguments = ["--app-root", str(tmp_path), "--authoring-file", str(source)]
179
+ monkeypatch.setattr(execution_binding, "bound_entrypoint", lambda *args: selected)
180
+ assert emitter.main(arguments) == 0
181
+ assert json.loads(capfd.readouterr().out)["argv"] == arguments
182
+ assert source.read_bytes() == original
183
+ assert not (tmp_path / "_runtime").exists()
184
+
185
+
157
186
  @pytest.mark.parametrize("kind,expected", [("BillEntity", "bos_billtpl"), ("BaseEntity", "bos_basetpl"), ("MainEntity", None), ("EntryEntity", None)])
158
187
  def test_default_template_only_for_entity_roots(kind, expected):
159
188
  data = {"artifact": {"entities": [{"type": kind}]}}
@@ -246,3 +275,28 @@ def test_builtin_carry_uses_declared_target_without_overwriting_explicit_referen
246
275
  assert builtin["reference"] == {"mode": "platform", "formNumber": "bd_customer"}
247
276
  assert explicit["reference"]["formNumber"] == "user_selected"
248
277
  assert "reference" not in plain
278
+
279
+
280
+ def test_explicit_blank_bill_preserves_fields_and_emits_blank_template(monkeypatch, tmp_path):
281
+ entity = {"type": "BillEntity", "bizName": "blankbill", "displayName": "Blank bill", "templateKey": "blankPage", "fields": [
282
+ {"bizWord": "description", "displayName": "Description", "dataType": "TextField"},
283
+ ]}
284
+ section = {"artifact": {"entities": [entity]}, "assumptions": []}
285
+ (tmp_path / "archive.json").write_text(json.dumps({"isvNumber": "demo", "appCode": "sample", "standard": False}))
286
+ monkeypatch.setattr(emitter, "_run_cosmic_api", lambda *args, **kwargs: {
287
+ "status": True, "data": {"success": True, "templates": [{"number": "blankPage", "name": "空白页面", "modelType": "BillFormModel"}]},
288
+ })
289
+ logs = emitter._Logs(tmp_path)
290
+ assert emitter._validate_template_keys(section, logs, "unused", app_root=tmp_path)
291
+ with patch.object(emitter, "_call_get_entity_fields") as get_preset:
292
+ emitter._merge_template_fields(section, logs, "unused", app_root=tmp_path)
293
+ get_preset.assert_not_called()
294
+ assert entity["fields"][0]["bizWord"] == "description"
295
+ files, manifest, _ = emitter.assemble_artifact_emit(section, tmp_path, "blank-bill", logs)
296
+ assert not logs.errors
297
+ artifact = json.loads(files[0].read_text())
298
+ assert artifact["templateNumber"] == "blankPage"
299
+ assert manifest["artifacts"][0]["templateNumber"] == "blankPage"
300
+ assert len(artifact["entities"][0]["fields"]) == 1
301
+ assert cli._resolve_template_number(None, str(files[0]), None) == "blankPage"
302
+ assert cli._artifact_supports_blank_template(artifact, "blankPage")
@@ -69,7 +69,7 @@ def test_remove_field_publishes_mutually_exclusive_single_and_batch_selectors():
69
69
  def test_add_field_batch_validate_only_runs_without_platform_access(tmp_path):
70
70
  source = tmp_path / "fields.json"
71
71
  source.write_text(json.dumps([
72
- {"name": "说明", "bizWord": "note"},
72
+ {"name": "说明", "bizWord": "note", "fieldType": "TextField"},
73
73
  {"name": "来源", "bizWord": "source", "fieldType": "ComboField",
74
74
  "comboOptions": [{"label": "线上", "value": "online"}, {"label": "电话", "value": "phone"}]},
75
75
  ]), encoding="utf-8")
@@ -0,0 +1,84 @@
1
+ """Replay task02's real source through the native compiler before registration."""
2
+ import json
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ SCRIPTS = Path(__file__).resolve().parents[1]
11
+ sys.path.insert(0, str(SCRIPTS))
12
+ from compile_plugin_source import operation_field_issues
13
+
14
+ SOURCE = Path(__file__).with_name("fixtures") / "operation-entry-field-prepare.java"
15
+ CONTEXT = {"formGraph": {"entities": [{"role": "entries", "collectionProperty": "uhyl_book_order_detail",
16
+ "fields": [{"fieldKey": "uhyl_start_time"}, {"fieldKey": "uhyl_end_time"}]}]}}
17
+
18
+
19
+ def test_real_batch_failure_identifies_both_missing_fields():
20
+ issues = operation_field_issues(SOURCE.read_text(), CONTEXT)
21
+ assert {item["fieldKey"] for item in issues} == {"uhyl_start_time", "uhyl_end_time"}
22
+ assert all(item["collectionProperty"] == "uhyl_book_order_detail" for item in issues)
23
+ assert all(item["line"] > 50 for item in issues)
24
+
25
+
26
+ @pytest.mark.parametrize("change", [
27
+ lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", "fieldKeys.add(FIELD_START_TIME); fieldKeys.add(FIELD_END_TIME);"),
28
+ lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", 'fieldKeys.add("uhyl_start_time"); fieldKeys.add("uhyl_end_time");'),
29
+ lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", 'fieldKeys.add("uhyl_book_order_detail.uhyl_start_time"); fieldKeys.add("uhyl_book_order_detail.uhyl_end_time");'),
30
+ lambda text: text.replace("extends AbstractOperationServicePlugIn", "extends ProjectBase"),
31
+ lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", "prepareFields(e);"),
32
+ lambda text: text.replace("getDynamicObjectCollection(ENTRY_COLLECTION)", 'getDynamicObjectCollection("unrelated")'),
33
+ lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", "fieldKeys.addAll(requiredKeys());"),
34
+ lambda text: text.replace("entity.getDataEntity()", "BusinessDataServiceHelper.loadSingle(id)"),
35
+ ])
36
+ def test_correct_or_unresolved_data_flow_is_not_falsely_blocked(change):
37
+ assert operation_field_issues(change(SOURCE.read_text()), CONTEXT) == []
38
+
39
+
40
+ def test_comments_do_not_prepare_fields_and_absent_metadata_is_not_invented():
41
+ source = SOURCE.read_text().replace("fieldKeys.add(ENTRY_COLLECTION);", "fieldKeys.add(ENTRY_COLLECTION); // fieldKeys.add(FIELD_START_TIME);")
42
+ assert len(operation_field_issues(source, CONTEXT)) == 2
43
+ assert operation_field_issues(source, None) == []
44
+
45
+
46
+ def test_single_and_batch_native_compilation_reject_before_archiving(tmp_path):
47
+ module = tmp_path / "module"
48
+ source = module.joinpath("src", "main", "java", "kd", "bos", "app", "uhyl", "bfy11", "operate",
49
+ "UhylBookingOrderOpPlugin.java")
50
+ source.parent.mkdir(parents=True)
51
+ source.write_text(SOURCE.read_text())
52
+ app = module / "app/uhyl_bfy11"
53
+ app.mkdir(parents=True)
54
+ handoff = tmp_path / "handoff.json"
55
+ handoff.write_text(json.dumps({"status": True, "handoff": {**CONTEXT, "projectRoot": str(module),
56
+ "appRoot": str(app), "sourcePath": str(source), "className": source.stem, "type": "operation",
57
+ "formNumber": "uhyl_booking_order", "description": "提交时段校验", "operationKeys": ["submit"]}}))
58
+ config = tmp_path / "appconfig.json"
59
+ config.write_text('{"compile": false}')
60
+ env = {**os.environ, "KD_APPCONFIG": str(config)}
61
+ single = [sys.executable, str(SCRIPTS / "compile_plugin_source.py"), "--handoff", str(handoff)]
62
+ batch_file = tmp_path / "sources.json"
63
+ batch_file.write_text(json.dumps([{"handoffPath": str(handoff)}]))
64
+ batch = [sys.executable, str(SCRIPTS / "compile_plugin_source.py"), "--project-root", str(module),
65
+ "--app-root", str(app), "--sources-file", str(batch_file)]
66
+ for args in (single, batch):
67
+ run = subprocess.run(args, env=env, capture_output=True, text=True)
68
+ result = json.loads(run.stdout)
69
+ outcome = result["results"][0] if "results" in result else result
70
+ assert run.returncode != 0
71
+ assert outcome["errorCode"] == "PLUGIN_SOURCE_OPERATION_FIELD_NOT_PREPARED"
72
+ assert outcome["phase"] == "source_validation"
73
+ assert not (app / "archive.json").exists()
74
+ source.write_text(SOURCE.read_text().replace("fieldKeys.add(ENTRY_COLLECTION);",
75
+ "fieldKeys.add(ENTRY_COLLECTION); fieldKeys.add(FIELD_START_TIME); fieldKeys.add(FIELD_END_TIME);"))
76
+ run = subprocess.run(single, env=env, capture_output=True, text=True)
77
+ result = json.loads(run.stdout)
78
+ assert run.returncode == 0, result
79
+ assert result["compileSkipped"] is True # source-only proof, not a business pass
80
+
81
+
82
+ def test_validator_preparation_is_not_mistaken_for_an_omission():
83
+ source = SOURCE.read_text().replace("public void validate() {", "public java.util.Set<String> preparePropertys() { return requiredFields(); } public void validate() {")
84
+ assert operation_field_issues(source, CONTEXT) == []
@@ -7,6 +7,7 @@ cases are as load-bearing as the positive ones.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import json
10
11
  import re
11
12
  import sys
12
13
  from pathlib import Path
@@ -16,13 +17,97 @@ sys.path.insert(0, str(SCRIPTS))
16
17
 
17
18
  from platform_api_contract import ( # noqa: E402
18
19
  _page, _qcp_constants, _scenes_verified_imports, base_class_issues,
19
- filter_operator_issues, override_signature_issues, return_type_issues,
20
+ filter_operator_issues, override_signature_issues, return_type_issues, operation_call_findings,
20
21
  unverified_import_issues, unverified_member_issues,
21
22
  describe_api, plugin_coding_evidence, scene_misuse_issues,
23
+ forbidden_member_issues, report_batch_issues, report_listener_issues,
24
+ report_source_cast_warnings, sdk_absent_name_warnings,
22
25
  )
23
26
  sys.path.append(str(SCRIPTS.parents[2] / "scripts" if SCRIPTS.parent.parent.name == ".locales" else SCRIPTS))
24
27
 
25
28
 
29
+ def test_operation_argument_roles_use_entity_evidence_not_identifier_spelling():
30
+ source = '''
31
+ import kd.bos.servicehelper.BusinessDataServiceHelper;
32
+ import kd.bos.servicehelper.operation.OperationServiceHelper;
33
+ class P {
34
+ static final String FLOCK_ENTITY = "demo_flock";
35
+ static final String DELETE = "delete";
36
+ void run(Object id) {
37
+ Object flk = BusinessDataServiceHelper.loadSingle(id, FLOCK_ENTITY);
38
+ OperationServiceHelper.executeOperate((FLOCK_ENTITY), DELETE, new Object[]{id}, option());
39
+ }
40
+ }
41
+ '''
42
+ issues, warnings = operation_call_findings(source)
43
+ assert [item["errorCode"] for item in issues] == ["PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER"]
44
+ assert issues[0]["line"] == 9
45
+ assert len(issues[0]["registeredSignatures"]) == 2
46
+ assert warnings == []
47
+ assert operation_call_findings(source.replace('(FLOCK_ENTITY), DELETE', 'DELETE, FLOCK_ENTITY')) == ([], [])
48
+ # 没有实体证据时只提示,不将合法自定义操作或变量名推断为错误。
49
+ unknown = source.replace('Object flk = BusinessDataServiceHelper.loadSingle(id, FLOCK_ENTITY);', '')
50
+ issues, warnings = operation_call_findings(unknown)
51
+ assert issues == []
52
+ assert warnings[0]["errorCode"] == "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER_UNVERIFIED"
53
+ assert operation_call_findings(unknown, ("demo_flock",))[0]
54
+ assert operation_call_findings(source.replace('"delete"', '"custom_delete"')) == ([], [])
55
+
56
+
57
+ def test_operation_overloads_do_not_borrow_signatures_from_a_different_helper():
58
+ source = 'import kd.bos.servicehelper.operation.OperationServiceHelper;\n'
59
+ issues, warnings = operation_call_findings(source + 'OperationServiceHelper.executeOperate("delete", "demo_flock", new Object[]{id});')
60
+ assert issues == []
61
+ assert warnings[0]["argumentCount"] == 3
62
+ assert warnings[0]["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"
63
+ assert operation_call_findings(source + 'OperationServiceHelper.executeOperate("delete", "demo_flock", new Object[]{id}, option);') == ([], [])
64
+ # DeleteServiceHelper 确有 (entityName, pks, option) 三参数签名,不能一律要求四参数。
65
+ assert operation_call_findings('import kd.bos.servicehelper.operation.DeleteServiceHelper;\n'
66
+ 'DeleteServiceHelper.deleteOperate("demo_flock", new Object[]{id}, option);') == ([], [])
67
+
68
+
69
+ def test_operation_scan_resolves_qualified_and_static_import_calls():
70
+ for call, imports in (
71
+ ('kd.bos.servicehelper.operation.OperationServiceHelper.executeOperate', ''),
72
+ ('executeOperate', 'import static kd.bos.servicehelper.operation.OperationServiceHelper.executeOperate;\n'),
73
+ ('DeleteServiceHelper.deleteOperate', 'import kd.bos.servicehelper.operation.DeleteServiceHelper;\n'),
74
+ ):
75
+ source = imports + call + '("demo_flock", "delete", new Object[]{id}, option);'
76
+ issues, warnings = operation_call_findings(source, ("demo_flock",))
77
+ assert len(issues) == 1
78
+ assert warnings == []
79
+ local = '''import static kd.bos.servicehelper.operation.OperationServiceHelper.executeOperate;
80
+ class P {
81
+ void executeOperate(String entity, String op, Object[] ids, Object option) { use(ids); }
82
+ void run() { executeOperate("demo_flock", "delete", ids, option); }
83
+ }'''
84
+ assert operation_call_findings(local, ("demo_flock",)) == ([], [])
85
+
86
+
87
+ def test_operation_scan_ignores_literals_comments_other_receivers_and_shadowed_constants():
88
+ source = '''import kd.bos.servicehelper.operation.OperationServiceHelper;
89
+ class P {
90
+ final String ENTITY = "demo_flock";
91
+ void run(String ENTITY) { OperationServiceHelper.executeOperate(ENTITY, "delete", ids, option); }
92
+ String note = "OperationServiceHelper.executeOperate(ENTITY, delete, ids)";
93
+ // OperationServiceHelper.executeOperate("demo_flock", "delete", ids);
94
+ }'''
95
+ assert operation_call_findings(source, ("demo_flock",)) == ([], [])
96
+ assert operation_call_findings('import demo.OperationServiceHelper;\n'
97
+ 'OperationServiceHelper.executeOperate("demo_flock", "delete", ids);', ("demo_flock",)) == ([], [])
98
+ assert operation_call_findings('import kd.bos.servicehelper.operation.OperationServiceHelper;\n'
99
+ 'OperationServiceHelper.executeOperate(operation, entity, ids, option);') == ([], [])
100
+
101
+
102
+ def test_operation_scenes_pass_the_new_call_contract():
103
+ platform = SCRIPTS.parent / "knowledge" / "cosmic" / "platform"
104
+ for name in ("operation/execute-operate.md", "operation/operation-custom-params.md",
105
+ "operation/operation-microservice.md", "data-access/business-data/delete-service.md"):
106
+ text = (platform / "scenes" / name).read_text(encoding="utf-8-sig")
107
+ for block in re.findall(r"```java\n(.*?)```", text, re.DOTALL):
108
+ assert operation_call_findings(block) == ([], []), name
109
+
110
+
26
111
  def test_api_lookup_distinguishes_same_named_members_by_receiver():
27
112
  plugin = describe_api("AbstractOperationServicePlugIn", ["getDataEntities"])
28
113
  validator = describe_api("AbstractValidator", ["getDataEntities"])
@@ -984,3 +1069,239 @@ def test_scene_evidence_cannot_read_outside_registered_scene_tree():
984
1069
  import pytest
985
1070
  with pytest.raises(ValueError, match="unknown coding scene"):
986
1071
  plugin_coding_evidence("operation", ["../../../../references/type-registry.json"])
1072
+
1073
+
1074
+ def _report_form(body: str, imports: str = "") -> str:
1075
+ return (
1076
+ "package demo;\n"
1077
+ "import java.util.EventObject;\n"
1078
+ "import kd.bos.form.control.events.ItemClickEvent;\n"
1079
+ "import kd.bos.form.events.HyperLinkClickEvent;\n"
1080
+ "import kd.bos.form.events.HyperLinkClickListener;\n"
1081
+ "import kd.bos.report.ReportList;\n"
1082
+ "import kd.bos.report.plugin.AbstractReportFormPlugin;\n"
1083
+ f"{imports}"
1084
+ "public class DrillPlugin extends AbstractReportFormPlugin implements HyperLinkClickListener {\n"
1085
+ f"{body}"
1086
+ "}\n"
1087
+ )
1088
+
1089
+
1090
+ _DRILL = (
1091
+ " @Override\n"
1092
+ " public void hyperLinkClick(HyperLinkClickEvent evt) {\n"
1093
+ " String field = evt.getFieldName();\n"
1094
+ " }\n"
1095
+ )
1096
+ _REGISTER_DRILL = (
1097
+ " @Override\n"
1098
+ " public void registerListener(EventObject e) {\n"
1099
+ " super.registerListener(e);\n"
1100
+ " ReportList reportList = this.getControl(\"reportlistap\");\n"
1101
+ " reportList.addHyperClickListener(this);\n"
1102
+ " }\n"
1103
+ )
1104
+
1105
+
1106
+ def test_a_report_drill_down_without_its_listener_is_rejected():
1107
+ """Verified on the 8.0 bytecode: nothing adds a report form plugin to the grid's hyperlink
1108
+ listeners but addHyperClickListener, so this compiles, opens and the link does nothing."""
1109
+ issues = report_listener_issues(_report_form(_DRILL))
1110
+
1111
+ assert len(issues) == 1
1112
+ assert issues[0]["callback"] == "hyperLinkClick"
1113
+ assert issues[0]["requiredCall"] == "addHyperClickListener"
1114
+ assert issues[0]["line"] == 10
1115
+ assert "registerListener" in issues[0]["message"]
1116
+
1117
+
1118
+ def test_a_report_drill_down_that_registers_itself_is_accepted():
1119
+ assert report_listener_issues(_report_form(_REGISTER_DRILL + _DRILL)) == []
1120
+
1121
+
1122
+ def test_toolbar_and_cell_callbacks_each_need_their_own_listener():
1123
+ issues = report_listener_issues(_report_form(
1124
+ _REGISTER_DRILL + _DRILL
1125
+ + " @Override\n"
1126
+ " public void itemClick(ItemClickEvent evt) { super.itemClick(evt); }\n"
1127
+ " public void cellClick(CellClickEvent evt) { }\n",
1128
+ imports="import kd.bos.form.control.events.CellClickEvent;\n"))
1129
+
1130
+ assert [(issue["callback"], issue["requiredCall"]) for issue in issues] == [
1131
+ ("itemClick", "addItemClickListeners"), ("cellClick", "addCellClickListener")]
1132
+ assert report_listener_issues(_report_form(
1133
+ " @Override\n"
1134
+ " public void registerListener(EventObject e) {\n"
1135
+ " this.addItemClickListeners(\"toolbarap\");\n"
1136
+ " }\n"
1137
+ " @Override\n"
1138
+ " public void itemClick(ItemClickEvent evt) { super.itemClick(evt); }\n")) == []
1139
+
1140
+
1141
+ def test_the_listener_rule_judges_only_a_direct_report_form_plugin():
1142
+ """A project base class may register for its subclass, and a bill plugin's toolbar is outside
1143
+ the report evidence, so neither is judged."""
1144
+ assert report_listener_issues(_report_form(_DRILL).replace(
1145
+ "extends AbstractReportFormPlugin", "extends ProjectReportFormPlugin")) == []
1146
+ assert report_listener_issues(
1147
+ "package demo;\n"
1148
+ "import kd.bos.bill.AbstractBillPlugIn;\n"
1149
+ "import kd.bos.form.control.events.ItemClickEvent;\n"
1150
+ "public class Bill extends AbstractBillPlugIn {\n"
1151
+ " public void itemClick(ItemClickEvent evt) { }\n"
1152
+ "}\n") == []
1153
+
1154
+
1155
+ def _report_data(batch_by: str, query: str, helpers: str = "") -> str:
1156
+ return (
1157
+ "package demo;\n"
1158
+ "import kd.bos.algo.DataSet;\n"
1159
+ "import kd.bos.entity.report.AbstractReportListDataPlugin;\n"
1160
+ "import kd.bos.entity.report.ReportQueryParam;\n"
1161
+ "public class BatchPlugin extends AbstractReportListDataPlugin {\n"
1162
+ " @Override\n"
1163
+ " public DataSet queryBatchBy(ReportQueryParam queryParam) {\n"
1164
+ f"{batch_by}"
1165
+ " }\n"
1166
+ " @Override\n"
1167
+ " public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable {\n"
1168
+ f"{query}"
1169
+ " }\n"
1170
+ f"{helpers}"
1171
+ "}\n"
1172
+ )
1173
+
1174
+
1175
+ _BATCH_BY = (
1176
+ " queryParam.byBatchInfo().setCountPerBatch(100);\n"
1177
+ " return basis(queryParam);\n"
1178
+ )
1179
+
1180
+
1181
+ def test_a_batched_query_that_ignores_its_batch_is_rejected():
1182
+ issues = report_batch_issues(_report_data(_BATCH_BY, " return all(queryParam);\n"))
1183
+
1184
+ assert len(issues) == 1
1185
+ assert issues[0]["method"] == "query"
1186
+ assert issues[0]["line"] == 12
1187
+ assert "selectedObj" in issues[0]["message"]
1188
+ assert "getCurrentBatchRows" in issues[0]["message"]
1189
+
1190
+
1191
+ def test_a_batched_query_that_reads_its_batch_is_accepted():
1192
+ assert report_batch_issues(_report_data(
1193
+ _BATCH_BY, " return byRows(queryParam.byBatchInfo().getCurrentBatchRows());\n")) == []
1194
+ assert report_batch_issues(_report_data(_BATCH_BY, " return byBasis(selectedObj);\n")) == []
1195
+ # A helper that reads the batch is accepted without being followed.
1196
+ assert report_batch_issues(_report_data(
1197
+ _BATCH_BY, " return narrowed(queryParam);\n",
1198
+ " private DataSet narrowed(ReportQueryParam p) { return byRows(p.byBatchInfo()); }\n")) == []
1199
+
1200
+
1201
+ def test_a_query_batch_by_that_never_batches_is_not_judged():
1202
+ assert report_batch_issues(_report_data(
1203
+ " return null;\n", " return all(queryParam);\n")) == []
1204
+
1205
+
1206
+ def test_sdk_absent_report_names_are_warned_with_what_8_0_offers():
1207
+ """No class file in the 8.0 SDK mentions these names, so no 8.0 receiver carries them."""
1208
+ warnings = sdk_absent_name_warnings(_report_form(
1209
+ " @Override\n"
1210
+ " public void beforeQuery(kd.bos.entity.report.ReportQueryParam queryParam) {\n"
1211
+ " ReportList reportList = this.getControl(\"reportlistap\");\n"
1212
+ " reportList.setAsyncDataCount(true);\n"
1213
+ " // reportList.setAsyncDataCount(true); is not code\n"
1214
+ " Object custom = queryParam.getFilter().getCustomFilter();\n"
1215
+ " Object kept = queryParam.getCustomFilter();\n"
1216
+ " }\n"))
1217
+
1218
+ assert [item["name"] for item in warnings] == ["setAsyncDataCount", "FilterInfo.getCustomFilter"]
1219
+ assert all(item["severity"] == "warning" for item in warnings)
1220
+ assert "setAsyncTotal" in warnings[0]["message"]
1221
+ assert "queryParam.getCustomFilter()" in warnings[1]["message"]
1222
+
1223
+
1224
+ def test_a_name_the_source_declares_itself_is_not_warned():
1225
+ assert sdk_absent_name_warnings(_report_form(
1226
+ " private void setAsyncDataCount(boolean on) { }\n"
1227
+ " void run() { setAsyncDataCount(true); }\n")) == []
1228
+
1229
+
1230
+ def test_an_unguarded_source_cast_in_package_data_is_warned():
1231
+ package_data = (
1232
+ " @Override\n"
1233
+ " public void packageData(PackageDataEvent event) {\n"
1234
+ " ReportColumn column = (ReportColumn) event.getSource();\n"
1235
+ " }\n")
1236
+ imports = ("import kd.bos.entity.datamodel.events.PackageDataEvent;\n"
1237
+ "import kd.bos.entity.report.ReportColumn;\n")
1238
+ warnings = report_source_cast_warnings(_report_form(package_data, imports))
1239
+
1240
+ assert [item["errorCode"] for item in warnings] == ["PLUGIN_REPORT_SOURCE_CAST_UNGUARDED"]
1241
+ guarded = package_data.replace(
1242
+ " ReportColumn column",
1243
+ " if (!(event.getSource() instanceof ReportColumn)) { return; }\n ReportColumn column")
1244
+ assert report_source_cast_warnings(_report_form(guarded, imports)) == []
1245
+
1246
+
1247
+ def test_the_report_pages_pass_every_blocking_source_gate():
1248
+ """The report scenes and guides are what the coding step copies. A blocking gate that rejects
1249
+ one of their complete classes means either the page teaches a defect or the gate is too wide,
1250
+ and both have to be settled here rather than at a user's compile boundary."""
1251
+ from java_source import duplicate_methods
1252
+
1253
+ platform = SCRIPTS.parent / "knowledge" / "cosmic" / "platform"
1254
+ pages = sorted((platform / "scenes" / "report").glob("*.md")) + [
1255
+ platform / "scenes" / "system-service" / "report-plugin-interface.md",
1256
+ platform / "report-plugin.md", platform / "reportlistdata-plugin.md",
1257
+ platform / "reporttreedata-plugin.md",
1258
+ ]
1259
+ gates = (duplicate_methods, return_type_issues, base_class_issues, override_signature_issues,
1260
+ filter_operator_issues, forbidden_member_issues, unverified_import_issues,
1261
+ unverified_member_issues, scene_misuse_issues, report_listener_issues,
1262
+ report_batch_issues)
1263
+ findings: list[str] = []
1264
+ classes = 0
1265
+ for page in pages:
1266
+ for block in re.findall(r"```java\n(.*?)```",
1267
+ page.read_text(encoding="utf-8-sig", errors="replace"), re.DOTALL):
1268
+ if not re.search(r"^\s*public\s+(?:(?:final|abstract)\s+)*class\s+\w+[^;{]*\{",
1269
+ block, re.MULTILINE):
1270
+ continue
1271
+ classes += 1
1272
+ findings += [f"{page.name}: {gate.__name__}: {issue.get('message')}"
1273
+ for gate in gates for issue in gate(block)]
1274
+
1275
+ assert classes >= 30, "too few complete classes found, so the sweep proves little"
1276
+ assert not findings, "\n".join(findings)
1277
+
1278
+
1279
+ def test_exact_api_and_specialized_knowledge_are_selected_without_a_scene():
1280
+ evidence = plugin_coding_evidence("form", core_logic=["QueryServiceHelper.queryOne 查询"],
1281
+ hit_modules=["idempotency", "security-input-validation"])
1282
+ contract = next(item for item in evidence["apiContracts"] if item["class"] == "kd.bos.servicehelper.QueryServiceHelper")
1283
+ assert {member["name"] for member in contract["members"]} == {"queryOne"}
1284
+ assert all(Path(path).is_file() for path in evidence["specializedReferences"].values())
1285
+ assert set(evidence["specializedReferences"]) == {"idempotency", "security-input-validation"}
1286
+ assert "apiContracts" not in plugin_coding_evidence("form", core_logic=["按业务规则处理页面"])
1287
+
1288
+
1289
+ def test_plugin_type_lookup_returns_exact_registry_guide_without_inventory():
1290
+ import subprocess
1291
+ result = subprocess.run([sys.executable, str(SCRIPTS / "platform_api_contract.py"), "--describe-plugin-type", "operation"], capture_output=True, text=True)
1292
+ assert result.returncode == 0
1293
+ payload = json.loads(result.stdout)
1294
+ assert payload["type"] == "operation"
1295
+ assert Path(payload["guide"]).is_file()
1296
+ assert payload["baseClass"]
1297
+ assert "types" not in payload
1298
+ invalid = subprocess.run([sys.executable, str(SCRIPTS / "platform_api_contract.py"), "--describe-plugin-type", "unknown"], capture_output=True, text=True)
1299
+ assert invalid.returncode == 2 and json.loads(invalid.stdout)["errorCode"] == "PLUGIN_TYPE_UNSUPPORTED"
1300
+
1301
+
1302
+ def test_documented_inherited_transaction_event_members_are_available():
1303
+ for name in ["BeginOperationTransactionArgs", "EndOperationTransactionArgs"]:
1304
+ value = describe_api("kd.bos.entity.plugin.args." + name, ["getOperationKey"])
1305
+ assert value["status"] is True
1306
+ assert value["undocumentedMembers"] == []
1307
+ assert any(item["declaredBy"] == "kd.bos.entity.plugin.args.OperationArgs" for item in value["members"])