@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
@@ -21,6 +21,31 @@ def execution_binding():
21
21
  "cosmicApi": str(root / "skills" / "cosmic-meta-api" / "scripts" / "cosmic_api.py")}
22
22
 
23
23
 
24
+ def bound_entrypoint(app_root, relative):
25
+ """Reuse the selected runtime across this installation's locale entries.
26
+
27
+ Do not treat translated executables as equivalent or follow arbitrary paths
28
+ from context. The selected executable still checks its own revision/env.
29
+ """
30
+ context = Path(app_root) / "_runtime" / "context.json"
31
+ if not context.is_file():
32
+ return None
33
+ stored = json.loads(context.read_text(encoding="utf-8-sig"))
34
+ source = (stored.get("executionBinding") or {}).get("rootDir")
35
+ if not source:
36
+ return None
37
+ current = Path(__file__).resolve().parents[1]
38
+ selected = Path(source).resolve()
39
+ package = lambda root: root.parent.parent if root.parent.name == ".locales" else root
40
+ if current == selected or package(current) != package(selected):
41
+ return None
42
+ target = (selected / relative).resolve()
43
+ target.relative_to(selected)
44
+ if not target.is_file():
45
+ raise ValueError("SKILL_BOUND_ENTRY_MISSING: selected installed source lacks " + relative)
46
+ return target
47
+
48
+
24
49
  def validate_execution_binding(app_root):
25
50
  context = Path(app_root) / "_runtime" / "context.json"
26
51
  if not context.is_file():
@@ -147,7 +147,7 @@ _SIGNATURE_RE = re.compile(
147
147
  # "继承自 IEntryOperate(分录行操作……)") cannot reach the sentence's period through the names
148
148
  # charset, so the whole match used to fail and the class looked like it had no supertypes at all.
149
149
  _SUPERTYPE_RE = re.compile(
150
- r"(?:inherits from|[Ee]xtends|implements|继承自|实现)\s*"
150
+ r"(?:[Ii]nherits from|[Ee]xtends|implements|继承自|实现)\s*"
151
151
  r"(?P<names>[\w.,、,\s]+?)"
152
152
  r"(?=[.。()();;]|$)")
153
153
  _TYPE_TOKEN_RE = re.compile(r"[A-Z][A-Za-z0-9_.$]*")
@@ -310,7 +310,7 @@ def describe_api(selector: str, members: list[str] | None = None) -> dict:
310
310
  "evidenceScope": "Registered declarations and documented supertypes; undocumented members are not proof of SDK absence."}
311
311
 
312
312
 
313
- def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None) -> dict:
313
+ def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None, *, core_logic: list[str] | None = None, hit_modules: list[str] | None = None) -> dict:
314
314
  """Resolve coding references from the existing type and API registries."""
315
315
  registry = json.loads(TYPE_REGISTRY_PAGE.read_text(encoding="utf-8-sig"))
316
316
  definition = next((item for item in registry["types"] if item["type"] == plugin_type), None)
@@ -330,6 +330,8 @@ def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None) ->
330
330
  "arguments": ["--class", "<receiver class>", "--sdk-dir", "<bound target SDK directory>",
331
331
  "--member", "<required member>"]},
332
332
  }
333
+ evidence["specializedReferences"] = {name: str(root / registry["specializedStandards"][name])
334
+ for name in hit_modules or [] if name in registry.get("specializedStandards", {})}
333
335
  if plugin_type == "operation":
334
336
  evidence["operationDispatch"] = {
335
337
  "transactionEvent": {"class": "kd.bos.entity.plugin.args.OperationArgs", "accessor": "event.getOperationKey()",
@@ -397,11 +399,28 @@ def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None) ->
397
399
  for owner, member in re.findall(r"\b([A-Z][A-Za-z0-9_]*)\.([a-z][A-Za-z0-9_]*)\s*\(", content):
398
400
  if _page_by_simple_name(owner):
399
401
  apis.setdefault(owner, set()).add(member)
402
+ # Only exact class/member names already supplied by the task select contracts.
403
+ for name in dict.fromkeys(re.findall(r"[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*", "\n".join(core_logic or []))):
404
+ owner, separator, member = name.rpartition(".")
405
+ page = _page(name) if name.startswith("kd.bos.") else _page_by_simple_name(name)
406
+ if page:
407
+ apis.setdefault(page["fqcn"], set())
408
+ continue
409
+ page = (_page(owner) if owner.startswith("kd.bos.") else _page_by_simple_name(owner)) if separator else None
410
+ if page:
411
+ apis.setdefault(page["fqcn"], set()).add(member)
400
412
  if selected:
401
413
  evidence["scenes"] = selected
414
+ if apis:
415
+ canonical = {}
416
+ for owner, members in apis.items():
417
+ contract = describe_api(owner)
418
+ if contract.get("status") is not True:
419
+ continue
420
+ name = contract["class"]
421
+ canonical[name] = (canonical.get(name, set()) | members) if name not in canonical or (canonical[name] and members) else set()
402
422
  evidence["apiContracts"] = [describe_api(owner, sorted(members))
403
- for owner, members in sorted(apis.items()) if members
404
- and _page_by_simple_name(owner)["fqcn"] != base]
423
+ for owner, members in sorted(canonical.items()) if owner != base]
405
424
  for contract in evidence["apiContracts"]:
406
425
  contract["members"] = [{key: value for key, value in item.items()
407
426
  if key != "notes" or item["deprecated"]}
@@ -586,6 +605,94 @@ def _arguments(code: str, open_paren: int) -> list[str] | None:
586
605
  return None
587
606
 
588
607
 
608
+ def operation_call_findings(source_text: str, entity_numbers: tuple[str, ...] = ()) -> tuple[list[dict], list[dict]]:
609
+ """检查操作调用的参数角色;仅阻断已有实体证据的颠倒,不把缺少文档当作 API 不存在。"""
610
+ code = stripped(source_text)
611
+ source = commentless(source_text)
612
+ imports = _imports(code)
613
+ helpers = {
614
+ "kd.bos.servicehelper.operation.OperationServiceHelper": "executeOperate",
615
+ "kd.bos.servicehelper.operation.DeleteServiceHelper": "deleteOperate",
616
+ }
617
+ calls = []
618
+ for match in re.finditer(
619
+ r"(?<![\w$.])(?P<owner>kd\.bos\.[\w$.]+|[A-Z][\w$]*)\s*\.\s*(?P<member>\w+)\s*\(", code):
620
+ owner = match["owner"] if match["owner"].startswith(PLATFORM_PACKAGE) else imports.get(match["owner"])
621
+ if owner not in helpers and owner != "kd.bos.servicehelper.BusinessDataServiceHelper":
622
+ continue
623
+ arguments = _arguments(source, match.end() - 1)
624
+ if arguments is not None:
625
+ calls.append((owner, match["member"], arguments, match.start()))
626
+ # 静态导入须解析到同一 SDK 类;本地同名方法会遮蔽它,不能按平台调用检查。
627
+ local_methods = {body["name"] for body in method_bodies(source_text)}
628
+ for match in re.finditer(r"\bimport\s+static\s+(kd\.bos\.[\w$.]+)\.(\w+)\s*;", code):
629
+ if helpers.get(match[1]) != match[2] or match[2] in local_methods:
630
+ continue
631
+ for call in re.finditer(r"(?<![\w$.])" + re.escape(match[2]) + r"\s*\(", code):
632
+ arguments = _arguments(source, call.end() - 1)
633
+ if arguments is not None:
634
+ calls.append((match[1], match[2], arguments, call.start()))
635
+
636
+ constants = {}
637
+ for match in re.finditer(r'\bfinal\s+String\s+([\w$]+)\s*=\s*("(?:[^"\\]|\\.)*")\s*;', source):
638
+ # 同名局部变量、字段或参数存在时,不跨作用域猜测常量值。
639
+ if code[match.start():match.start() + 5] == "final" and len(re.findall(
640
+ r"\bString\s+" + re.escape(match[1]) + r"\b", code)) == 1:
641
+ constants[match[1]] = match[2]
642
+
643
+ def value(expression):
644
+ token = expression.strip()
645
+ while token.startswith("(") and _call_end(token, 0) == len(token):
646
+ token = token[1:-1].strip()
647
+ token = constants.get(token, token)
648
+ if not re.fullmatch(r'"(?:[^"\\]|\\.)*"', token):
649
+ return None
650
+ try:
651
+ return json.loads(token)
652
+ except ValueError:
653
+ return None
654
+
655
+ entities = set(entity_numbers)
656
+ # loadSingle 的第二个参数是实体名或类型;能解析为字符串时提供实体角色证据。
657
+ for owner, member, arguments, _ in calls:
658
+ if owner == "kd.bos.servicehelper.BusinessDataServiceHelper" and member == "loadSingle" and len(arguments) >= 2:
659
+ entity = value(arguments[1])
660
+ if entity is not None:
661
+ entities.add(entity)
662
+ issues = []
663
+ warnings = []
664
+ # 仅识别文档示例已有的标准操作;自定义操作不从变量名、前缀或相似名字推断。
665
+ standard_keys = {"save", "submit", "audit", "unaudit", "delete"}
666
+ for owner, member, arguments, offset in calls:
667
+ if helpers.get(owner) != member:
668
+ continue
669
+ page = _page(owner)
670
+ signatures = page["signatures"].get(member, set()) if page else set()
671
+ if not signatures:
672
+ continue
673
+ finding = {"line": code.count("\n", 0, offset) + 1, "call": owner + "." + member,
674
+ "reference": str(page["path"]),
675
+ "registeredSignatures": sorted(member + "(" + ", ".join(params) + ")" for params in signatures)}
676
+ if len(arguments) not in {len(params) for params in signatures}:
677
+ warnings.append({**finding, "severity": "warning", "errorCode": "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED",
678
+ "argumentCount": len(arguments),
679
+ "message": "操作调用的参数数量不匹配已收录签名。executeOperate 的已收录重载需要第四个 OperateOption;不能套用其他 helper 的重载。未收录不等于不存在,使用绑定 SDK 查证并编译;compile=false 不证明该调用合法。"})
680
+ # deleteOperate(entityName, pks, option) 是已收录的合法三参数重载,无操作键参数。
681
+ if owner.endswith("DeleteServiceHelper") and len(arguments) == 3 or len(arguments) < 2:
682
+ continue
683
+ operation, entity = value(arguments[0]), value(arguments[1])
684
+ if entity not in standard_keys or operation is None or operation == entity or operation in standard_keys:
685
+ continue
686
+ message = "操作调用应为 (operationKey, entityNumber, ids/dataEntities, option),不能先传实体编码再传操作编码;两个参数都是 String,javac 无法验证角色顺序。"
687
+ if operation in entities:
688
+ issues.append({**finding, "errorCode": "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER",
689
+ "operationArgument": arguments[0], "entityArgument": arguments[1], "message": message})
690
+ continue
691
+ warnings.append({**finding, "severity": "warning", "errorCode": "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER_UNVERIFIED",
692
+ "message": message + " 当前缺少首参的实体证据,按目标表单操作定义核实,不自动交换参数。"})
693
+ return issues, warnings
694
+
695
+
589
696
  def filter_operator_issues(source_text: str) -> list[dict]:
590
697
  """QFilter comparison operators written as a string literal instead of a QCP constant."""
591
698
  constants = _qcp_constants()
@@ -850,6 +957,220 @@ def scene_misuse_issues(source_text: str) -> list[dict]:
850
957
  return issues
851
958
 
852
959
 
960
+ # Report form callbacks the platform delivers only to a plugin that added itself as the control's
961
+ # listener. Verified against the 8.0 SDK bytecode: AbstractFormPlugin.registerListener is empty and
962
+ # AbstractReportFormPlugin does not override it; AbstractGrid's hyperlink and cell listener lists
963
+ # and Toolbar's item listener list are filled by their add* methods alone, and neither ReportList
964
+ # nor ReportView calls one for the plugin. An override without its add* call compiles, registers
965
+ # and opens, then never fires: the drill-down link or the toolbar button does nothing, and nothing
966
+ # is logged.
967
+ _REPORT_FORM_BASE = "kd.bos.report.plugin.AbstractReportFormPlugin"
968
+ _REPORT_DATA_BASE = "kd.bos.entity.report.AbstractReportListDataPlugin"
969
+ _REPORT_LISTENER_RULES = (
970
+ {
971
+ "callbacks": ("hyperLinkClick",),
972
+ "registration": re.compile(r"\baddHyperClickListener\s*\("),
973
+ "add": "addHyperClickListener",
974
+ "example": 'ReportList reportList = this.getControl("reportlistap"); '
975
+ "reportList.addHyperClickListener(this);",
976
+ },
977
+ {
978
+ "callbacks": ("itemClick", "beforeItemClick"),
979
+ "registration": re.compile(r"\baddItemClickListeners?\s*\("),
980
+ "add": "addItemClickListeners",
981
+ "example": 'this.addItemClickListeners("toolbarap");',
982
+ },
983
+ {
984
+ "callbacks": ("cellClick", "cellDoubleClick"),
985
+ "registration": re.compile(r"\baddCellClickListener\s*\("),
986
+ "add": "addCellClickListener",
987
+ "example": 'ReportList reportList = this.getControl("reportlistap"); '
988
+ "reportList.addCellClickListener(this);",
989
+ },
990
+ )
991
+
992
+
993
+ def _outermost_base_fqcn(code: str) -> str:
994
+ declared = _outermost_base_class(code)
995
+ return _resolved_base_import(_imports(code), declared[1]) if declared else ""
996
+
997
+
998
+ def report_listener_issues(source_text: str) -> list[dict]:
999
+ """A report form callback overridden by a plugin that never adds itself as its listener.
1000
+
1001
+ Judged only for a class that directly extends AbstractReportFormPlugin: a project base class
1002
+ may register on the subclass's behalf, and a listener passed as a lambda or an anonymous class
1003
+ is not an override at all. An add* call anywhere in the class counts — one placed in
1004
+ initialize() is already rejected by the lifecycle rule.
1005
+ """
1006
+ code = stripped(source_text)
1007
+ if PLATFORM_PACKAGE not in code or _outermost_base_fqcn(code) != _REPORT_FORM_BASE:
1008
+ return []
1009
+ bodies = {body["name"]: body for body in method_bodies(source_text)}
1010
+ issues: list[dict] = []
1011
+ for rule in _REPORT_LISTENER_RULES:
1012
+ if rule["registration"].search(code):
1013
+ continue
1014
+ for name in rule["callbacks"]:
1015
+ if name not in bodies:
1016
+ continue
1017
+ issues.append({
1018
+ "line": code[: bodies[name]["start"]].count("\n") + 1,
1019
+ "callback": name,
1020
+ "requiredCall": rule["add"],
1021
+ "message": (
1022
+ f"{name}() is never called: the platform delivers it only to a plugin that "
1023
+ f"added itself as the control's listener, and this class never calls "
1024
+ f"{rule['add']}. Add the call in registerListener(EventObject), for example "
1025
+ f"{rule['example']}"
1026
+ ),
1027
+ })
1028
+ return issues
1029
+
1030
+
1031
+ _QUERY_DECLARATION_RE = re.compile(
1032
+ r"\bquery\s*\(\s*(?:final\s+)?(?:[\w$]+\s*\.\s*)*ReportQueryParam\s+[\w$]+\s*,\s*"
1033
+ r"(?:final\s+)?(?:java\s*\.\s*lang\s*\.\s*)?Object\s+(?P<selected>[\w$]+)\s*\)")
1034
+ _BATCH_READ_RE = re.compile(r"\b(?:getCurrentBatchRows|byBatchInfo)\s*\(")
1035
+ _RETURNS_VALUE_RE = re.compile(r"\breturn\s+(?!null\s*;)")
1036
+
1037
+
1038
+ def report_batch_issues(source_text: str) -> list[dict]:
1039
+ """A batched report data plugin whose query() returns the whole result for every batch.
1040
+
1041
+ A non-null queryBatchBy() result switches batching on: the platform splits its basis rows by
1042
+ countPerBatch and calls query() once per batch, handing over the current batch as selectedObj
1043
+ and as queryParam.byBatchInfo().getCurrentBatchRows(). A query() that reads neither returns
1044
+ the full result each time, and the report shows every row once per batch. A byBatchInfo()
1045
+ call anywhere outside queryBatchBy() counts as reading the batch, so a helper method that
1046
+ narrows the query is accepted without being followed.
1047
+ """
1048
+ code = stripped(source_text)
1049
+ if PLATFORM_PACKAGE not in code or _outermost_base_fqcn(code) != _REPORT_DATA_BASE:
1050
+ return []
1051
+ bodies = method_bodies(source_text)
1052
+ batch = next((body for body in bodies if body["name"] == "queryBatchBy"), None)
1053
+ if batch is None or not _RETURNS_VALUE_RE.search(code[batch["start"]:batch["end"]]):
1054
+ return []
1055
+ if _BATCH_READ_RE.search(code[:batch["start"]] + code[batch["end"]:]):
1056
+ return []
1057
+ declaration = _QUERY_DECLARATION_RE.search(code)
1058
+ query = min((body for body in bodies
1059
+ if declaration and body["name"] == "query" and body["start"] > declaration.end()),
1060
+ key=lambda body: body["start"], default=None)
1061
+ if query is None:
1062
+ return []
1063
+ selected = declaration.group("selected")
1064
+ if re.search(rf"\b{re.escape(selected)}\b", code[query["start"]:query["end"]]):
1065
+ return []
1066
+ return [{
1067
+ "line": code[: query["start"]].count("\n") + 1,
1068
+ "method": "query",
1069
+ "message": (
1070
+ "queryBatchBy() returns batch basis rows, so the platform calls query() once per "
1071
+ f"batch, but query() reads neither its {selected} argument nor "
1072
+ "queryParam.byBatchInfo().getCurrentBatchRows(): every batch returns the whole result "
1073
+ "and the report shows each row once per batch. Narrow query() to the current batch, "
1074
+ "for example with an in filter on the basis values getCurrentBatchRows() returns, or "
1075
+ "return null from queryBatchBy() when the report is not batched"
1076
+ ),
1077
+ }]
1078
+
1079
+
1080
+ # Names report examples written against a later release use that the 8.0 SDK has nowhere: none of
1081
+ # the 326,774 classes in the 1,016 jars of an 8.0 lib/bos + lib/trd install mentions them, as a
1082
+ # declaration or as a call, so no 8.0 receiver can carry them. They are warnings rather than
1083
+ # findings because a later target SDK may add them, and the target's own javac settles it when
1084
+ # compilation runs; with compilation skipped on an 8.0 target the class registers and then fails
1085
+ # to load. A source that declares the name itself is left alone.
1086
+ _SDK_ABSENT_NAMES = {
1087
+ "setAsyncDataCount": (
1088
+ "8.0 has no asynchronous row count; ReportList.setAsyncTotal(boolean) defers the total "
1089
+ "line only"),
1090
+ "setCol2ndResultMode": (
1091
+ "8.0 TreeReportListEvent offers setTreeReportList(boolean) and setTreeExpandColId(String) "
1092
+ "only, and no flat header-filter result mode"),
1093
+ "TreeDataMode": "8.0 has no TreeDataMode type",
1094
+ "getFilterQFilters": "use FilterInfo.getQFilters()",
1095
+ }
1096
+ _ABSENT_NAME_RE = re.compile(r"\b(?P<name>" + "|".join(_SDK_ABSENT_NAMES) + r")\b")
1097
+ # FilterInfo declares no getCustomFilter in 8.0; the custom filter list is ReportQueryParam's.
1098
+ _FILTER_CUSTOM_FILTER_RE = re.compile(r"\bgetFilter\s*\(\s*\)\s*\.\s*getCustomFilter\s*\(")
1099
+
1100
+
1101
+ def sdk_absent_name_warnings(source_text: str) -> list[dict]:
1102
+ """Report API names the 8.0 SDK does not have, each with what 8.0 offers instead."""
1103
+ code = stripped(source_text)
1104
+ if PLATFORM_PACKAGE not in code:
1105
+ return []
1106
+ own = set(re.findall(r"\b(?:class|interface|enum)\s+([\w$]+)", code))
1107
+ own |= {body["name"] for body in method_bodies(source_text)}
1108
+ warnings: list[dict] = []
1109
+ for match in _ABSENT_NAME_RE.finditer(code):
1110
+ name = match.group("name")
1111
+ if name in own:
1112
+ continue
1113
+ warnings.append({
1114
+ "line": code[: match.start()].count("\n") + 1,
1115
+ "name": name,
1116
+ "severity": "warning",
1117
+ "errorCode": "PLUGIN_SOURCE_ABSENT_FROM_8_0_SDK",
1118
+ "message": (
1119
+ f"{name} is absent from the 8.0 SDK: {_SDK_ABSENT_NAMES[name]}. On an 8.0 target "
1120
+ "the source fails to compile, or with compilation skipped the class fails to "
1121
+ "load; keep it only after references/sdk-inspection.md finds it on the target SDK"
1122
+ ),
1123
+ })
1124
+ if "kd.bos.entity.report." in code:
1125
+ for match in _FILTER_CUSTOM_FILTER_RE.finditer(code):
1126
+ warnings.append({
1127
+ "line": code[: match.start()].count("\n") + 1,
1128
+ "name": "FilterInfo.getCustomFilter",
1129
+ "severity": "warning",
1130
+ "errorCode": "PLUGIN_SOURCE_ABSENT_FROM_8_0_SDK",
1131
+ "message": (
1132
+ "FilterInfo declares no getCustomFilter in the 8.0 SDK; the custom filter list "
1133
+ "is queryParam.getCustomFilter(), on ReportQueryParam"
1134
+ ),
1135
+ })
1136
+ warnings.sort(key=lambda item: item["line"])
1137
+ return warnings
1138
+
1139
+
1140
+ _SOURCE_CAST_RE = re.compile(
1141
+ r"\(\s*(?P<type>(?!Object\b)[A-Z][\w$]*)\s*\)\s*\(?\s*[\w$]+\s*\.\s*getSource\s*\(\s*\)")
1142
+
1143
+
1144
+ def report_source_cast_warnings(source_text: str) -> list[dict]:
1145
+ """A packageData() that casts the event source without checking its type first.
1146
+
1147
+ packageData fires once for every cell, and the report examples guard the cast with
1148
+ `instanceof ReportColumn`; an unguarded cast throws ClassCastException on the first cell whose
1149
+ source is another type, which fails the whole page rather than one cell.
1150
+ """
1151
+ code = stripped(source_text)
1152
+ if PLATFORM_PACKAGE not in code or _outermost_base_fqcn(code) != _REPORT_FORM_BASE:
1153
+ return []
1154
+ warnings: list[dict] = []
1155
+ for body in method_bodies(source_text):
1156
+ segment = code[body["start"]:body["end"]]
1157
+ if body["name"] != "packageData" or re.search(r"\binstanceof\b", segment):
1158
+ continue
1159
+ cast = _SOURCE_CAST_RE.search(segment)
1160
+ if cast:
1161
+ warnings.append({
1162
+ "line": code[: body["start"] + cast.start()].count("\n") + 1,
1163
+ "severity": "warning",
1164
+ "errorCode": "PLUGIN_REPORT_SOURCE_CAST_UNGUARDED",
1165
+ "message": (
1166
+ f"packageData() casts getSource() to {cast.group('type')} without an "
1167
+ "instanceof check; it fires for every cell, so test the type first, as the "
1168
+ "report examples do with `if (source instanceof ReportColumn)`"
1169
+ ),
1170
+ })
1171
+ return warnings
1172
+
1173
+
853
1174
  def _class_bodies(code: str) -> list[dict]:
854
1175
  """Locate named and anonymous class scopes in comment/literal-free Java."""
855
1176
  pattern = re.compile(
@@ -1407,9 +1728,21 @@ def main() -> int:
1407
1728
  description="Check platform API contracts in one Java plugin source")
1408
1729
  mode = parser.add_mutually_exclusive_group(required=True)
1409
1730
  mode.add_argument("--source-path")
1731
+ mode.add_argument("--describe-plugin-type", help="Exact runtime plugin type; returns its guide and base contract")
1410
1732
  mode.add_argument("--describe-class", action="append", help="Exact SDK class name or fully qualified name; repeat to batch lookups")
1411
1733
  parser.add_argument("--member", action="append", help="Limit API evidence to named members")
1412
1734
  args = parser.parse_args()
1735
+ if args.describe_plugin_type:
1736
+ registry = json.loads(TYPE_REGISTRY_PAGE.read_text(encoding="utf-8-sig"))
1737
+ entry = next((item for item in registry.get("types", []) if item.get("type") == args.describe_plugin_type), None)
1738
+ if not entry:
1739
+ print(json.dumps({"status": False, "errorCode": "PLUGIN_TYPE_UNSUPPORTED", "type": args.describe_plugin_type}))
1740
+ return 2
1741
+ root = TYPE_REGISTRY_PAGE.parent.parent
1742
+ print(json.dumps({"status": True, "type": entry["type"], "codingEntry": str(root / "skills/cosmic-coding-impl/SKILL.md"), "baseClass": entry.get("baseImport"),
1743
+ "guide": str(root / entry["guide"]) if entry.get("guide") else None,
1744
+ "sceneIndex": str(root / "knowledge/cosmic/platform/scenes/_index.md")}, ensure_ascii=False))
1745
+ return 0
1413
1746
  if args.describe_class:
1414
1747
  results = [describe_api(selector, args.member) for selector in args.describe_class]
1415
1748
  print(json.dumps({"status": all(item["status"] for item in results), "classes": results}, ensure_ascii=False))
@@ -1423,16 +1756,21 @@ def main() -> int:
1423
1756
  }, ensure_ascii=False))
1424
1757
  return 2
1425
1758
  source_text = source.read_text(encoding="utf-8-sig", errors="replace")
1759
+ operation_issues, operation_warnings = operation_call_findings(source_text)
1426
1760
  mismatches = return_type_issues(source_text)
1427
1761
  operators = filter_operator_issues(source_text)
1428
1762
  missing = forbidden_member_issues(source_text)
1429
1763
  misplaced = scene_misuse_issues(source_text)
1764
+ unregistered = report_listener_issues(source_text)
1765
+ unbatched = report_batch_issues(source_text)
1430
1766
  bases = base_class_issues(source_text)
1431
1767
  overrides = override_signature_issues(source_text)
1432
1768
  remembered_imports = unverified_import_issues(source_text)
1433
1769
  remembered_members = unverified_member_issues(source_text)
1434
1770
  error_code = "0"
1435
- if mismatches:
1771
+ if operation_issues:
1772
+ error_code = operation_issues[0]["errorCode"]
1773
+ elif mismatches:
1436
1774
  error_code = "PLUGIN_SOURCE_RETURN_TYPE_MISMATCH"
1437
1775
  elif operators:
1438
1776
  error_code = "PLUGIN_SOURCE_QFILTER_STRING_OPERATOR"
@@ -1440,6 +1778,10 @@ def main() -> int:
1440
1778
  error_code = "PLUGIN_SOURCE_NONEXISTENT_MEMBER"
1441
1779
  elif misplaced:
1442
1780
  error_code = "PLUGIN_SOURCE_LIFECYCLE_MISUSE"
1781
+ elif unregistered:
1782
+ error_code = "PLUGIN_SOURCE_LISTENER_UNREGISTERED"
1783
+ elif unbatched:
1784
+ error_code = "PLUGIN_SOURCE_BATCH_NOT_NARROWED"
1443
1785
  elif bases:
1444
1786
  error_code = "PLUGIN_SOURCE_BASE_CLASS_MISMATCH"
1445
1787
  elif overrides:
@@ -1451,8 +1793,11 @@ def main() -> int:
1451
1793
  "errorCode": error_code,
1452
1794
  "sourcePath": str(source.resolve()),
1453
1795
  "issues": (
1454
- mismatches + operators + missing + misplaced + bases + overrides
1455
- + remembered_imports + remembered_members
1796
+ operation_issues + mismatches + operators + missing + misplaced + unregistered + unbatched + bases
1797
+ + overrides + remembered_imports + remembered_members
1798
+ ),
1799
+ "sourceWarnings": (
1800
+ operation_warnings + sdk_absent_name_warnings(source_text) + report_source_cast_warnings(source_text)
1456
1801
  ),
1457
1802
  }, ensure_ascii=False))
1458
1803
  return 0 if error_code == "0" else 2
@@ -81,7 +81,8 @@ def validate_registration_entry(entry, target_type, class_name=None):
81
81
  if not source.is_absolute() or not source.is_file():
82
82
  raise ValueError("PLUGIN_LOCAL_SOURCE_UNVERIFIED: resolve the archive source path before registration")
83
83
  from java_source import stripped
84
- text = stripped(source.read_text(encoding="utf-8-sig"))
84
+ source_text = source.read_text(encoding="utf-8-sig")
85
+ text = stripped(source_text)
85
86
  declaration = re.search(r"\bclass\s+" + re.escape(class_name.rsplit(".", 1)[-1])
86
87
  + r"\b([^{}]*)\{", text)
87
88
  if declaration is None:
@@ -98,6 +99,13 @@ def validate_registration_entry(entry, target_type, class_name=None):
98
99
  actual = hashlib.sha256(source.read_bytes()).hexdigest()
99
100
  if entry.get("sourceHash") != actual:
100
101
  raise ValueError("PLUGIN_SOURCE_CHANGED: source differs from its compile result; compile the current source again")
102
+ # 旧版本的成功回执不包含参数角色门禁;注册前重验,避免沿用历史误放行的源码。
103
+ from platform_api_contract import operation_call_findings
104
+ issues, warnings = operation_call_findings(source_text, (str(entry.get("formNumber") or ""),))
105
+ blockers = issues + ([item for item in warnings if item["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"]
106
+ if entry["compileStatus"] == "skipped-by-config" else [])
107
+ if blockers:
108
+ raise ValueError(blockers[0]["errorCode"] + ": " + blockers[0]["message"])
101
109
  return {"compileStatus": entry["compileStatus"], "sourceHash": actual}
102
110
 
103
111
 
@@ -6,30 +6,34 @@ from inspect_platform_sdk import inspect_sdk
6
6
 
7
7
 
8
8
  class TargetSdkError(ValueError):
9
- def __init__(self, code, message):
9
+ def __init__(self, code, message, *, details=None):
10
10
  super().__init__(message)
11
11
  self.error_code = code
12
+ self.details = details or {}
12
13
 
13
14
 
14
15
  def verify_target_sdk(project_root, coding, usage=None):
16
+ if usage is not None and (not isinstance(usage, list) or any(not isinstance(item, dict)
17
+ or not isinstance(item.get('class'), str) or not item['class']
18
+ or set(item) - {'class', 'members'} or not isinstance(item.get('members', []), list)
19
+ or any(not isinstance(member, str) or not member for member in item.get('members', [])) for item in usage)):
20
+ raise TargetSdkError('PLUGIN_SDK_USAGE_INVALID', 'sdkUsage requires exact class and member selectors')
15
21
  environment = inspect_compile_environment(project_root)
16
22
  if environment.get('status') == 'disabled':
17
23
  return {'status': 'not-run', 'compile': False, 'projectRoot': str(Path(project_root).resolve()),
18
24
  'evidenceScope': 'Source-only configuration: target SDK, compilation and runtime are unverified'}
19
25
  if environment.get('status') != 'ready':
20
26
  raise TargetSdkError(environment.get('code') or 'PLUGIN_SDK_ENVIRONMENT_UNVERIFIED',
21
- environment.get('message') or 'Target compile environment is unverified')
27
+ environment.get('message') or 'Target compile environment is unverified',
28
+ details={'buildEnvironment': environment})
22
29
  jars = sorted({str(file.resolve()) for directory in environment.get('sdkDirectories', [])
23
30
  for file in Path(directory['path']).glob('*.jar')})
24
31
  if not jars:
25
32
  raise TargetSdkError('PLUGIN_TARGET_SDK_UNRESOLVED', 'The bound module does not establish target SDK libraries')
26
- if usage is not None and (not isinstance(usage, list) or any(not isinstance(item, dict)
27
- or not isinstance(item.get('class'), str) or not item['class']
28
- or set(item) - {'class', 'members'} or not isinstance(item.get('members', []), list)
29
- or any(not isinstance(member, str) or not member for member in item.get('members', [])) for item in usage)):
30
- raise TargetSdkError('PLUGIN_SDK_USAGE_INVALID', 'sdkUsage requires exact class and member selectors')
33
+ # Guide signatures include unrelated lifecycle types; verify the actual base and
34
+ # selected API receivers. Explicit sdkUsage verifies needed event declarations.
31
35
  classes = sorted({coding.get('baseContract', {}).get('class'),
32
- *[item.get('class') for item in coding.get('signatureTypes', [])]} - {None})
36
+ *[item.get('class') for item in coding.get('apiContracts', [])]} - {None})
33
37
  verified = [inspect_sdk(jars, name, mode='locate') for name in classes if name.startswith('kd.bos.')]
34
38
  contracts = [inspect_sdk(jars, item['class'], item.get('members')) for item in usage or []]
35
39
  for result in [*verified, *contracts]: