@shundoo-ai/dsh-cosmic 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/cordis.patch.yml +5 -5
  2. package/package.json +1 -1
  3. package/skills/cosmic-build/CHANGELOG.md +4 -0
  4. package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
  5. package/skills/cosmic-build/SKILL.md +46 -13
  6. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
  7. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
  8. package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
  9. package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
  10. package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
  11. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
  12. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
  13. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
  14. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
  15. package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
  16. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
  17. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
  18. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
  19. package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
  20. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
  21. package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
  22. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
  25. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
  26. package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
  27. package/skills/cosmic-build/references/application-context.md +35 -1
  28. package/skills/cosmic-build/references/common-reference.md +1 -1
  29. package/skills/cosmic-build/references/compile-guide.md +55 -68
  30. package/skills/cosmic-build/references/delivery.md +59 -7
  31. package/skills/cosmic-build/references/execution-contract.md +38 -0
  32. package/skills/cosmic-build/references/form-discovery.md +7 -1
  33. package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
  34. package/skills/cosmic-build/references/operation-dispatch.md +17 -0
  35. package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
  36. package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
  37. package/skills/cosmic-build/references/report-filter-contract.md +78 -0
  38. package/skills/cosmic-build/references/sdk-inspection.md +23 -0
  39. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
  40. package/skills/cosmic-build/scripts/_http_client.py +55 -55
  41. package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
  42. package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
  43. package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
  44. package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
  45. package/skills/cosmic-build/scripts/execution_binding.py +40 -0
  46. package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
  47. package/skills/cosmic-build/scripts/kd_api.py +23 -22
  48. package/skills/cosmic-build/scripts/kd_auth.py +1 -1
  49. package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
  50. package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
  51. package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
  52. package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
  53. package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
  54. package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
  55. package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
  56. package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
  57. package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
  58. package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
  59. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
  60. package/skills/cosmic-build/scripts/preview_url.py +35 -6
  61. package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
  62. package/skills/cosmic-build/scripts/register_plugins.py +28 -2
  63. package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
  64. package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
  65. package/skills/cosmic-build/scripts/run.py +2 -2
  66. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
  67. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
  68. package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
  69. package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
  70. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
  71. package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
  72. package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
  73. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
  74. package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
  75. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
  76. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
  77. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
  78. package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
  79. package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
  80. package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
  81. package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
  82. package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
  83. package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
  84. package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
  85. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
  86. package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
  87. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
  88. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
  89. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
  90. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
  91. package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
  92. package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
  93. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
  94. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
  95. package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
  96. package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
  97. package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
  98. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
  99. package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
  100. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
  101. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
  102. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
  103. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
  104. package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
  105. package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
  106. package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
  107. package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
  108. package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
  109. package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
  110. package/skills/cosmic-build/scripts/test_environment.py +49 -0
  111. package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
  112. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
  113. package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
  114. package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
  115. package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
  116. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
  117. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
  118. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
  119. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
  120. package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
  121. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
  122. package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
  123. package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
  124. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
  125. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
  126. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
  127. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
  128. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
  129. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
  130. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
  131. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
  132. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
  133. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
  134. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
  135. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
  136. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
  138. package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
  139. package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
  143. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
  144. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
  145. package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
  146. package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
  147. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
  177. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
  178. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
  179. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
  180. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
  181. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
  182. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
  183. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
  184. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
  185. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
  193. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
  194. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
  196. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
  197. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
  198. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
  199. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
  200. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
  201. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
  202. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
  203. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
  204. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
  205. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.md +6 -2
  206. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  207. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +20 -5
  208. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
  209. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
  210. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
  211. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
  212. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
  213. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
  214. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
  215. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
  216. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
  217. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
  218. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
  219. package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
  220. package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
  221. package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
  222. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
  223. package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
  224. package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
  225. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
  226. package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
  227. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
  228. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
  229. package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
  230. package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
  231. package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
  232. package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
  233. package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
  234. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
  235. package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
  236. package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
  237. package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
  238. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
  239. package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
  240. package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
  241. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
  242. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
  243. package/skills/erp-analysis/references/delivery-contract.md +4 -2
  244. package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
  245. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/.dsh/cosmic/.kd/config.json +0 -21
@@ -10,9 +10,150 @@ sys.path.insert(0, str(ROOT / "skills/cosmic-meta-modeling/scripts"))
10
10
  sys.path.insert(0, str(ROOT / "skills/cosmic-meta-api/scripts"))
11
11
  import data_model_emit as emitter
12
12
  import cosmic_api as cli
13
+ from _core import cmd_forms
13
14
  from _core.number_validator import validate_page_number
14
15
 
15
16
 
17
+ def test_emitter_resolves_template_name_before_preset_read(monkeypatch, tmp_path):
18
+ entity = {"type": "BillEntity", "bizName": "order", "templateKey": "单据带组织模版"}
19
+ section = {"artifact": {"entities": [entity]}}
20
+ calls = []
21
+ def run(_path, command, args, *, app_root):
22
+ calls.append((command, args))
23
+ rows = ([{"number": "vendor_org_template", "name": "单据带组织模板",
24
+ "modelType": "BillFormModel"}]
25
+ if command == "queryPublicTemplates" and args["modelType"] == "BillFormModel" else [])
26
+ return {"status": True, "data": {"success": True, "templates": rows}}
27
+ monkeypatch.setattr(emitter, "_run_cosmic_api", run)
28
+ class Logs:
29
+ def info(self, *args, **kwargs): pass
30
+ def warn(self, *args, **kwargs): pass
31
+ def err(self, *args, **kwargs): raise AssertionError(args)
32
+ assert emitter._validate_template_keys(section, Logs(), "cosmic_api.py", app_root=tmp_path)
33
+ assert entity["templateKey"] == "vendor_org_template"
34
+ assert calls == [("queryPublicTemplates", {"modelType": "BillFormModel"})]
35
+
36
+
37
+ def test_emitter_rejects_unknown_template_before_batch(monkeypatch, tmp_path):
38
+ section = {"artifact": {"entities": [{
39
+ "type": "BillEntity", "bizName": "order", "templateKey": "bill_template",
40
+ }]}}
41
+ class Logs:
42
+ errors = []
43
+ def info(self, *args, **kwargs): pass
44
+ def warn(self, *args, **kwargs): pass
45
+ def err(self, code, message, **kwargs): self.errors.append((code, message))
46
+ def run(_path, command, args, *, app_root):
47
+ if command == "getEntityFields":
48
+ return {"status": False, "data": None}
49
+ return {"status": True, "data": {"success": True, "templates": [
50
+ {"number": "bos_billtpl", "name": "单据模板", "modelType": "BillFormModel"},
51
+ {"number": "blankPage", "name": "空白页面", "modelType": "BillFormModel"},
52
+ ]}}
53
+ monkeypatch.setattr(emitter, "_run_cosmic_api", run)
54
+ logs = Logs()
55
+ assert not emitter._validate_template_keys(section, logs, "cosmic_api.py", app_root=tmp_path)
56
+ assert logs.errors[0][0] == "INVALID-TEMPLATEKEY"
57
+ assert "bos_billtpl" in logs.errors[0][1]
58
+
59
+
60
+ def test_emitter_checks_exact_template_when_catalog_is_unavailable(monkeypatch, tmp_path):
61
+ section = {"artifact": {"entities": [{
62
+ "type": "BillEntity", "bizName": "order", "templateKey": "bos_billtpl",
63
+ }]}}
64
+ class Logs:
65
+ errors = []
66
+ warnings = []
67
+ def info(self, *args, **kwargs): pass
68
+ def warn(self, code, message, **kwargs): self.warnings.append(code)
69
+ def err(self, code, message, **kwargs): self.errors.append(code)
70
+ def run(_path, command, args, *, app_root):
71
+ if command == "getEntityFields":
72
+ return {"status": True, "data": {"headerFields": {
73
+ "headerKey": args["formNumber"], "fields": [],
74
+ }}}
75
+ return None
76
+ monkeypatch.setattr(emitter, "_run_cosmic_api", run)
77
+ logs = Logs()
78
+ assert emitter._validate_template_keys(section, logs, "cosmic_api.py", app_root=tmp_path)
79
+ assert "TEMPLATE-NOT-LISTED" in logs.warnings
80
+ assert logs.errors == []
81
+
82
+
83
+ def test_catalog_failure_cannot_approve_unverified_template(monkeypatch, tmp_path):
84
+ section = {"artifact": {"entities": [{
85
+ "type": "BillEntity", "bizName": "order", "templateKey": "bill_template",
86
+ }]}}
87
+ class Logs:
88
+ errors = []
89
+ def info(self, *args, **kwargs): pass
90
+ def warn(self, *args, **kwargs): pass
91
+ def err(self, code, message, **kwargs): self.errors.append(code)
92
+ monkeypatch.setattr(emitter, "_run_cosmic_api", lambda *args, **kwargs: None)
93
+ logs = Logs()
94
+ assert not emitter._validate_template_keys(section, logs, "cosmic_api.py", app_root=tmp_path)
95
+ assert logs.errors == ["TEMPLATE-LOOKUP-UNAVAILABLE"]
96
+
97
+
98
+ def test_template_subprocess_keeps_app_binding_when_cwd_is_project_root(tmp_path, monkeypatch):
99
+ project = tmp_path / "project"
100
+ app_root = project / "app" / "demo"
101
+ runtime = app_root / "_runtime"
102
+ runtime.mkdir(parents=True)
103
+ (app_root / "archive.json").write_text(json.dumps({
104
+ "type": "cosmic", "appCode": "demo", "appId": "APP-ID",
105
+ }))
106
+ (runtime / "context.json").write_text(json.dumps({
107
+ "appCode": "demo", "appRoot": str(app_root), "projectRoot": str(project),
108
+ }))
109
+ # Real subprocess and path initialization, with transport isolated from the platform.
110
+ script = tmp_path / "template_cli.py"
111
+ script.write_text(
112
+ "import json, sys\n"
113
+ f"sys.path.insert(0, {str(ROOT / 'skills/cosmic-meta-api/scripts')!r})\n"
114
+ "from _core import config\n"
115
+ "config._init_paths(app_root=sys.argv[sys.argv.index('--app-root') + 1])\n"
116
+ "rows = [] if sys.argv[2] == 'queryBizTemplates' else "
117
+ "[{'number': 'bos_billtpl', 'name': 'Bill', 'modelType': 'BillFormModel'}]\n"
118
+ "print(json.dumps({'status': True, 'data': {'success': True, 'templates': rows}}))\n"
119
+ )
120
+ monkeypatch.chdir(project)
121
+ section = {"artifact": {"entities": [{
122
+ "type": "BillEntity", "bizName": "order", "templateKey": "bos_billtpl",
123
+ }]}}
124
+ logs = emitter._Logs(app_root)
125
+ assert "_section_io" not in section
126
+ assert emitter._validate_template_keys(section, logs, str(script), app_root=app_root)
127
+ assert not logs.errors
128
+ assert (runtime / "cosmic-metadata" / "workspace.json").is_file()
129
+ assert not (project / "_runtime").exists()
130
+ assert not (project / "metadata").exists()
131
+ assert not (project / "modify").exists()
132
+ assert not (project / "app" / "_runtime").exists()
133
+
134
+
135
+ def test_failed_child_query_preserves_underlying_error(tmp_path, capsys):
136
+ script = tmp_path / "failed_cli.py"
137
+ script.write_text(
138
+ "import json, sys\n"
139
+ "print(json.dumps({'status': False, 'errorCode': 'APP_ROOT_IDENTITY_INVALID'}))\n"
140
+ "sys.exit(2)\n"
141
+ )
142
+ assert emitter._run_cosmic_api(str(script), "queryPublicTemplates", {}, app_root=tmp_path) is None
143
+ assert "APP_ROOT_IDENTITY_INVALID" in capsys.readouterr().err
144
+
145
+
146
+ def test_template_query_normalizes_legacy_model_type(monkeypatch):
147
+ calls = []
148
+ monkeypatch.setattr(cmd_forms, "_api_call", lambda *args, **kwargs: calls.append(args) or {
149
+ "status": True, "data": {"templates": []},
150
+ })
151
+ cmd_forms.do_query_public_templates({}, "token", "FormModel_Bill")
152
+ cmd_forms.do_query_biz_templates({}, "token", "FormModel_Base", biz_app_number="demo")
153
+ assert calls[0][3] == {"modelType": "BillFormModel"}
154
+ assert calls[1][3] == {"modelType": "BaseFormModel", "bizAppNumber": "demo"}
155
+
156
+
16
157
  @pytest.mark.parametrize("kind,expected", [("BillEntity", "bos_billtpl"), ("BaseEntity", "bos_basetpl"), ("MainEntity", None), ("EntryEntity", None)])
17
158
  def test_default_template_only_for_entity_roots(kind, expected):
18
159
  data = {"artifact": {"entities": [{"type": kind}]}}
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  import builtins
5
5
  import ast
6
6
  import json
7
+ import os
7
8
  import subprocess
8
9
  import sys
9
10
  from contextlib import redirect_stderr, redirect_stdout
@@ -69,7 +70,8 @@ def test_add_field_batch_validate_only_runs_without_platform_access(tmp_path):
69
70
  source = tmp_path / "fields.json"
70
71
  source.write_text(json.dumps([
71
72
  {"name": "说明", "bizWord": "note"},
72
- {"name": "来源", "bizWord": "source", "fieldType": "ComboField", "combo": "线上,电话"},
73
+ {"name": "来源", "bizWord": "source", "fieldType": "ComboField",
74
+ "comboOptions": [{"label": "线上", "value": "online"}, {"label": "电话", "value": "phone"}]},
73
75
  ]), encoding="utf-8")
74
76
  result = subprocess.run([sys.executable, str(COSMIC_SCRIPTS / "cosmic_api.py"),
75
77
  "--stdout", "addField", "--form-name", "missing_form", "--project-root", str(tmp_path),
@@ -561,6 +563,7 @@ def test_rule_domain_binding_rejects_two_form_selectors() -> None:
561
563
  def test_rule_domain_catalog_exposes_form_name_and_optional_entity() -> None:
562
564
  commands = (
563
565
  "ensureFieldCalculation",
566
+ "ensureTimeDifferenceCalculation",
564
567
  "ensureConditionalFieldState",
565
568
  "ensureConditionalEntryStyle",
566
569
  "ensureConditionalControlState",
@@ -1495,9 +1498,32 @@ def test_catalog_invoke_rejects_effects_and_runtime_override(tmp_path: Path) ->
1495
1498
  raise AssertionError("unavailable action must not be invoked")
1496
1499
 
1497
1500
 
1501
+ @pytest.mark.parametrize("root_name", ["project", "app"])
1502
+ def test_project_or_app_container_cannot_be_used_as_app_root(tmp_path, root_name):
1503
+ root = tmp_path / root_name
1504
+ root.mkdir()
1505
+ previous = cosmic_config.RUNTIME_DIR
1506
+ with pytest.raises(ValueError, match="APP_ROOT_IDENTITY_INVALID"):
1507
+ cosmic_config._init_paths(app_root=root)
1508
+ assert cosmic_config.RUNTIME_DIR == previous
1509
+ assert list(root.iterdir()) == []
1510
+
1511
+
1512
+ def test_unbound_session_does_not_create_application_artifact_directories(tmp_path, monkeypatch):
1513
+ monkeypatch.chdir(tmp_path)
1514
+ with mock.patch.dict(vars(cosmic_config)):
1515
+ cosmic_config._init_paths(temp_dir=str(tmp_path / "session"))
1516
+ assert Path(cosmic_config.WORKSPACE_JSON).exists()
1517
+ assert not Path(cosmic_config.ARTIFACTS_DIR).exists()
1518
+ assert not Path(cosmic_config.BATCH_MANIFESTS_DIR).exists()
1519
+
1520
+
1498
1521
  def test_explicit_app_root_controls_every_runtime_path(tmp_path: Path) -> None:
1499
1522
  app_root = tmp_path / "app" / "kdtest_demo"
1500
1523
  app_root.mkdir(parents=True)
1524
+ (app_root / "archive.json").write_text(json.dumps({
1525
+ "type": "cosmic", "appCode": "kdtest_demo", "appId": "APP-ID",
1526
+ }))
1501
1527
  names = (
1502
1528
  "RUNTIME_DIR",
1503
1529
  "TOKEN_CACHE",
@@ -1510,11 +1536,14 @@ def test_explicit_app_root_controls_every_runtime_path(tmp_path: Path) -> None:
1510
1536
  "WORKSPACE_JSON",
1511
1537
  )
1512
1538
  previous = {name: getattr(cosmic_config, name) for name in names}
1539
+ previous_binding = getattr(cosmic_config, "_BOUND_APP_ROOT", None)
1540
+ previous_project_root = os.environ.get("PROJECT_ROOT")
1513
1541
  try:
1514
- cosmic_config._init_paths(app_root=app_root)
1542
+ cosmic_api._retarget_runtime_dir(app_root)
1515
1543
  assert Path(cosmic_config.RUNTIME_DIR) == (
1516
1544
  app_root / "_runtime" / "cosmic-metadata"
1517
1545
  )
1546
+ assert os.environ["PROJECT_ROOT"] == str(app_root.resolve())
1518
1547
  assert Path(cosmic_config.ARTIFACTS_DIR) == app_root / "metadata"
1519
1548
  assert Path(cosmic_config.BATCH_MANIFESTS_DIR) == app_root / "modify"
1520
1549
  assert Path(cosmic_config._get_temp_dir()) == Path(
@@ -1522,6 +1551,11 @@ def test_explicit_app_root_controls_every_runtime_path(tmp_path: Path) -> None:
1522
1551
  )
1523
1552
  assert not (app_root / "app").exists()
1524
1553
  finally:
1554
+ if previous_project_root is None:
1555
+ os.environ.pop("PROJECT_ROOT", None)
1556
+ else:
1557
+ os.environ["PROJECT_ROOT"] = previous_project_root
1558
+ cosmic_config._BOUND_APP_ROOT = previous_binding
1525
1559
  for name, value in previous.items():
1526
1560
  setattr(cosmic_config, name, value)
1527
1561
 
@@ -18,7 +18,79 @@ from platform_api_contract import ( # noqa: E402
18
18
  _page, _qcp_constants, _scenes_verified_imports, base_class_issues,
19
19
  filter_operator_issues, override_signature_issues, return_type_issues,
20
20
  unverified_import_issues, unverified_member_issues,
21
+ describe_api, plugin_coding_evidence, scene_misuse_issues,
21
22
  )
23
+ sys.path.append(str(SCRIPTS.parents[2] / "scripts" if SCRIPTS.parent.parent.name == ".locales" else SCRIPTS))
24
+
25
+
26
+ def test_api_lookup_distinguishes_same_named_members_by_receiver():
27
+ plugin = describe_api("AbstractOperationServicePlugIn", ["getDataEntities"])
28
+ validator = describe_api("AbstractValidator", ["getDataEntities"])
29
+ event = describe_api("BeforeOperationArgs", ["getDataEntities", "setCancel"])
30
+ assert plugin["members"][0]["deprecated"] is True
31
+ assert "DynamicObject[]" in plugin["members"][0]["signature"]
32
+ assert all(not item["deprecated"] for item in validator["members"])
33
+ assert any("ExtendedDataEntity[]" in item["signature"] for item in validator["members"])
34
+ assert {item["name"] for item in event["members"]} == {"getDataEntities", "setCancel"}
35
+ assert describe_api("BeforeOperationArgs", ["missingExport"])["undocumentedMembers"] == ["missingExport"]
36
+
37
+
38
+ def test_mistyped_platform_package_suggests_only_a_registered_candidate():
39
+ result = describe_api("kd.bos.service.BusinessDataServiceHelper", ["loadSingle"])
40
+ assert result["status"] is False
41
+ assert result["errorCode"] == "API_CLASS_NOT_DOCUMENTED"
42
+ assert result["candidateClass"] == "kd.bos.servicehelper.BusinessDataServiceHelper"
43
+ assert Path(result["candidateReference"]).is_file()
44
+ assert Path(result["sdkLookup"]["script"]).is_file()
45
+
46
+
47
+ def test_coding_evidence_uses_registered_type_and_exact_argument_references():
48
+ evidence = plugin_coding_evidence("operation")
49
+ assert evidence["baseContract"]["class"] == "kd.bos.entity.plugin.AbstractOperationServicePlugIn"
50
+ assert any("beforeExecuteOperationTransaction" in signature for signature in evidence["guideSignatures"])
51
+ assert any(item["class"] == "kd.bos.entity.plugin.args.BeforeOperationArgs" for item in evidence["parameterTypes"])
52
+ assert all(Path(item["reference"]).is_file() for item in evidence["parameterTypes"])
53
+ assert evidence["operationDispatch"]["transactionEvent"]["accessor"] == "event.getOperationKey()"
54
+ assert evidence["operationDispatch"]["validator"]["accessor"] == "this.getOperateKey()"
55
+
56
+
57
+ def test_validator_in_same_file_does_not_hide_operation_receiver_misuse():
58
+ source = '''
59
+ import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
60
+ import kd.bos.entity.validate.AbstractValidator;
61
+ public class SamplePlugin extends AbstractOperationServicePlugIn {
62
+ public void onAddValidators(AddValidatorsEventArgs e) {
63
+ e.addValidator(new AbstractValidator() {
64
+ public void validate() {
65
+ this.getDataEntities();
66
+ SamplePlugin.this.getDataEntities();
67
+ }
68
+ });
69
+ }
70
+ private void inspect() { this.getDataEntities(); }
71
+ public void beginOperationTransaction(BeginOperationTransactionArgs e) { e.getDataEntities(); }
72
+ }
73
+ '''
74
+ issues = scene_misuse_issues(source)
75
+ assert len(issues) == 2
76
+ assert {source.splitlines()[issue["line"] - 1].strip() for issue in issues} == {
77
+ "SamplePlugin.this.getDataEntities();", "private void inspect() { this.getDataEntities(); }",
78
+ }
79
+
80
+
81
+ def test_named_validator_and_an_unrelated_class_keep_their_own_receiver():
82
+ source = '''
83
+ import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
84
+ import kd.bos.entity.validate.AbstractValidator;
85
+ public class SamplePlugin extends kd.bos.entity.plugin.AbstractOperationServicePlugIn {
86
+ private void inspect() { getDataEntities(); }
87
+ }
88
+ class Validator extends AbstractValidator { public void validate() { this.getDataEntities(); } }
89
+ class Other { public void inspect() { this.getDataEntities(); } }
90
+ '''
91
+ issues = scene_misuse_issues(source)
92
+ assert len(issues) == 1
93
+ assert issues[0]["line"] == 5
22
94
 
23
95
 
24
96
  def _source(body: str, imports: str = "") -> str:
@@ -207,6 +279,32 @@ def test_a_string_operator_names_the_qcp_constant_to_use():
207
279
  assert "QCP.not_in" in issues[0]["message"]
208
280
 
209
281
 
282
+ def test_recorded_qcp_equal_typo_returns_the_verified_constant_before_javac():
283
+ source = _filter_source(' QFilter f = new QFilter("id", QCP.equal, id);\n').replace(
284
+ "import kd.bos.orm.query.QFilter;", "import kd.bos.orm.query.QFilter;\nimport kd.bos.orm.query.QCP;")
285
+ issues = filter_operator_issues(source)
286
+ assert len(issues) == 1
287
+ assert issues[0]["constant"] == "QCP.equals"
288
+ assert '"="' not in issues[0]["message"]
289
+ assert filter_operator_issues(source.replace("QCP.equal,", "QCP.equals,")) == []
290
+
291
+
292
+ def test_a_project_qcp_class_is_not_judged_against_platform_constants():
293
+ source = _filter_source(' QFilter f = new QFilter("id", QCP.equal, id);\n').replace(
294
+ "import kd.bos.orm.query.QCP;\n", "")
295
+ assert filter_operator_issues(source) == []
296
+
297
+
298
+ def test_pre_post_scene_provides_query_load_save_and_filter_evidence():
299
+ evidence = plugin_coding_evidence("operation", ["operation/custom-validator", "operation/pre-post-operation"])
300
+ assert Path(evidence["codingEntry"]).is_file()
301
+ classes = {item["class"]: item for item in evidence["apiContracts"]}
302
+ assert "kd.bos.servicehelper.QueryServiceHelper" in classes
303
+ assert any(item["name"] == "loadSingle" for item in classes["kd.bos.servicehelper.BusinessDataServiceHelper"]["members"])
304
+ assert any(item["name"] == "update" for item in classes["kd.bos.servicehelper.operation.SaveServiceHelper"]["members"])
305
+ assert next(item for item in evidence["filterOperators"] if item["literal"] == "=")["constant"] == "QCP.equals"
306
+
307
+
210
308
  def test_an_operator_with_a_public_helper_is_told_to_use_the_helper():
211
309
  """`"is null"` used to be reported as QCP.is_null, an @SdkInternal constant the same page tells
212
310
  the coding step to avoid in favour of QFilter.isNull."""
@@ -694,6 +792,38 @@ def test_a_jdk_root_member_on_a_platform_receiver_is_exempt():
694
792
  )) == []
695
793
 
696
794
 
795
+ def test_generic_methods_on_the_pages_are_registered():
796
+ """`public <T extends Control> T getControl(String key)` declares its type parameters before
797
+ the return type. The signature pattern used to skip such lines, so IFormView.getControl and
798
+ IDataModel.getContextVariable looked unregistered: describe-class sent the coding step to the
799
+ SDK for a documented member, and an ordinary call was reported as a remembered spelling."""
800
+ view = _page("kd.bos.form.IFormView")
801
+ model = _page("kd.bos.entity.datamodel.IDataModel")
802
+
803
+ assert ("String",) in view["signatures"]["getControl"]
804
+ assert "getControl" in view["genericReturns"]
805
+ assert ("String",) in model["signatures"]["getContextVariable"]
806
+ assert [item["name"] for item in describe_api("kd.bos.form.IFormView", ["getControl"])["members"]] == ["getControl"]
807
+
808
+
809
+ def test_ordinary_calls_to_generic_members_are_not_findings():
810
+ source = (
811
+ "package demo;\n"
812
+ "import kd.bos.form.control.Button;\n"
813
+ "import kd.bos.form.plugin.AbstractFormPlugin;\n"
814
+ "public class DemoPlugin extends AbstractFormPlugin {\n"
815
+ " @Override\n"
816
+ " public void registerListener(java.util.EventObject e) {\n"
817
+ " Button ok = this.getView().getControl(\"btnok\");\n"
818
+ " Object value = this.getModel().getContextVariable(\"key\");\n"
819
+ " }\n"
820
+ "}\n"
821
+ )
822
+
823
+ assert unverified_member_issues(source) == []
824
+ assert return_type_issues(source) == []
825
+
826
+
697
827
  def test_a_real_service_lookup_is_not_a_truncated_name():
698
828
  """`getService(Class)` is IView's generic service lookup — the scenes call it — while the
699
829
  page registers only `getServiceAppId(String)`. A pure truncation is deliberately not a
@@ -805,7 +935,7 @@ def test_the_generated_index_reports_no_drift():
805
935
  import subprocess
806
936
 
807
937
  result = subprocess.run(
808
- [sys.executable, str(SCRIPTS / "gen_api_index.py"), "--check"],
938
+ [sys.executable, str((SCRIPTS.parents[2] / "scripts" if SCRIPTS.parent.parent.name == ".locales" else SCRIPTS) / "gen_api_index.py"), "--check"],
809
939
  capture_output=True, text=True)
810
940
 
811
941
  assert result.returncode == 0, result.stdout + result.stderr
@@ -824,3 +954,33 @@ def test_the_scenes_verified_block_holds_only_pageless_imports():
824
954
  assert pageless, "the exemption block is empty, so the contract is vacuous"
825
955
  assert all(_page(fqcn) is None for fqcn in pageless)
826
956
  assert _scenes_verified_imports() == _registered_fqcns() | pageless
957
+
958
+
959
+ def test_selected_scene_expands_data_service_signatures_and_real_paths():
960
+ evidence = plugin_coding_evidence("operation", ["operation/cross-entity-aggregate-writeback"])
961
+ contracts = {entry["class"]: entry for entry in evidence["apiContracts"]}
962
+ assert "kd.bos.servicehelper.operation.SaveServiceHelper" in contracts
963
+ query = contracts["kd.bos.servicehelper.QueryServiceHelper"]
964
+ assert any("DynamicObjectCollection" in item["signature"] for item in query["members"] if item["name"] == "query")
965
+ load = contracts["kd.bos.servicehelper.BusinessDataServiceHelper"]
966
+ assert any("Object pk, String entityName" in item["signature"] for item in load["members"] if item["name"] == "loadSingle")
967
+ assert all(Path(entry["reference"]).is_file() for entry in contracts.values())
968
+ assert all(Path(scene).is_file() for scene in evidence["scenes"])
969
+
970
+
971
+ def test_scene_return_types_provide_verified_imports_for_validator_entities():
972
+ evidence = plugin_coding_evidence("operation", ["operation/custom-validator"])
973
+ types = {entry["class"]: entry for entry in evidence["signatureTypes"]}
974
+ extended = types["kd.bos.entity.ExtendedDataEntity"]
975
+ assert extended["import"] == "import kd.bos.entity.ExtendedDataEntity;"
976
+ assert Path(extended["reference"]).is_file()
977
+ validator = next(entry for entry in evidence["apiContracts"] if entry["class"] == "kd.bos.entity.validate.AbstractValidator")
978
+ assert any("ExtendedDataEntity[]" in member["signature"] for member in validator["members"])
979
+ assert len(types) == len(evidence["signatureTypes"])
980
+ assert all(Path(entry["reference"]).is_file() for entry in types.values())
981
+
982
+
983
+ def test_scene_evidence_cannot_read_outside_registered_scene_tree():
984
+ import pytest
985
+ with pytest.raises(ValueError, match="unknown coding scene"):
986
+ plugin_coding_evidence("operation", ["../../../../references/type-registry.json"])
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import importlib
4
4
  import json
5
5
  import sys
6
+ import tempfile
7
+ import hashlib
6
8
  import unittest
7
9
  from pathlib import Path
8
10
 
@@ -12,6 +14,8 @@ API_SCRIPTS = APP_BUILD_ROOT / "skills" / "cosmic-meta-api" / "scripts"
12
14
  if str(API_SCRIPTS) not in sys.path:
13
15
  sys.path.insert(0, str(API_SCRIPTS))
14
16
 
17
+ sys.path.insert(0, str(APP_BUILD_ROOT / "scripts"))
18
+
15
19
  cmd_plugins = importlib.import_module("_core.cmd_plugins")
16
20
 
17
21
 
@@ -100,5 +104,92 @@ class PluginBusinessContractTest(unittest.TestCase):
100
104
  self.assertEqual("fpk-1", verification["fpk"])
101
105
 
102
106
 
107
+ class LocalJavaRegistrationTest(unittest.TestCase):
108
+ def check(self, source, archived_type, requested_target, *, declared_path=None):
109
+ with tempfile.TemporaryDirectory() as tmp:
110
+ file = Path(tmp, "Example.java")
111
+ file.write_text(source, encoding="utf-8")
112
+ Path(tmp, "archive.json").write_text(json.dumps({"javas": [{
113
+ "className": "example.Example", "type": archived_type,
114
+ "filePath": declared_path or str(file),
115
+ "sourceHash": hashlib.sha256(file.read_bytes()).hexdigest(), "compileStatus": "passed",
116
+ }]}), encoding="utf-8")
117
+ # Invalid inputs must fail before _api_call can access these absent credentials.
118
+ return cmd_plugins.validate_local_java_registration(tmp, {
119
+ "className": "example.Example", "targetType": requested_target,
120
+ })
121
+
122
+ def test_form_plugin_cannot_be_retargeted_to_save_to_bypass_registration_failure(self):
123
+ with self.assertRaisesRegex(ValueError, "PLUGIN_REGISTRATION_TYPE_MISMATCH"):
124
+ self.check("public class Example extends AbstractFormPlugin {}", "form", "OPERATION")
125
+
126
+ def test_removing_the_plugin_base_to_pass_javac_does_not_allow_registration(self):
127
+ with self.assertRaisesRegex(ValueError, "PLUGIN_SOURCE_BASE_CLASS_MISSING"):
128
+ self.check("public class Example { public void beginOperationTransaction() {} }",
129
+ "operation", "OPERATION")
130
+
131
+ def test_source_and_registration_target_must_agree_even_if_archive_type_was_changed(self):
132
+ with self.assertRaisesRegex(ValueError, "PLUGIN_SOURCE_TARGET_MISMATCH"):
133
+ self.check("public class Example extends AbstractFormPlugin {}", "operation", "OPERATION")
134
+
135
+ def test_direct_registration_does_not_bypass_a_missing_archive_source(self):
136
+ with self.assertRaisesRegex(ValueError, "PLUGIN_LOCAL_SOURCE_UNVERIFIED"):
137
+ self.check("public class Example extends AbstractFormPlugin {}", "form", "BILL_FORM",
138
+ declared_path="example/Example.java")
139
+
140
+ def test_valid_form_and_operation_sources_pass_the_local_consistency_check(self):
141
+ self.assertEqual(self.check("public class Example extends AbstractFormPlugin {}", "form", "BILL_FORM")["compileStatus"], "passed")
142
+ self.assertEqual(self.check("public class Example extends AbstractOperationServicePlugIn {}", "operation", "OPERATION")["compileStatus"], "passed")
143
+
144
+ def test_existing_deployed_class_without_local_declaration_remains_supported(self):
145
+ with tempfile.TemporaryDirectory() as tmp:
146
+ Path(tmp, "archive.json").write_text('{"javas": []}', encoding="utf-8")
147
+ self.assertIsNone(cmd_plugins.validate_local_java_registration(tmp, {
148
+ "className": "existing.Plugin", "targetType": "BILL_FORM",
149
+ }))
150
+
151
+
103
152
  if __name__ == "__main__":
104
153
  unittest.main()
154
+
155
+
156
+ def test_both_registration_entries_reject_manual_and_stale_archive(tmp_path, monkeypatch, capsys):
157
+ import hashlib
158
+ import register_plugins
159
+ source = tmp_path / "Example.java"
160
+ source.write_text("public class Example extends AbstractFormPlugin {}")
161
+ entry = {"className": "example.Example", "type": "form", "filePath": str(source),
162
+ "formNumber": "example", "description": "example", "operationKey": ""}
163
+ config = {"className": "example.Example", "targetType": "BILL_FORM"}
164
+ monkeypatch.setattr(sys, "argv", ["register_plugins.py", "--app-root", str(tmp_path)])
165
+ def no_auth(*a, **k):
166
+ raise AssertionError("invalid source must be rejected before credentials or platform calls")
167
+ monkeypatch.setattr(register_plugins, "_load_config", no_auth)
168
+ for expected in ["PLUGIN_SOURCE_NOT_READY", "PLUGIN_SOURCE_CHANGED"]:
169
+ (tmp_path / "archive.json").write_text(json.dumps({"javas": [entry]}))
170
+ with unittest.TestCase().assertRaisesRegex(ValueError, expected):
171
+ cmd_plugins.do_register_plugin({}, None, "example", config, app_root=str(tmp_path))
172
+ assert register_plugins.main() == 2
173
+ assert json.loads(capsys.readouterr().out)["errorCode"] == expected
174
+ entry.update(compileStatus="passed", sourceHash=hashlib.sha256(source.read_bytes()).hexdigest())
175
+ source.write_text(source.read_text() + "\n// changed after compilation")
176
+
177
+
178
+ def test_explicit_compile_skip_is_reported_as_skip_not_compile_pass(tmp_path):
179
+ import hashlib
180
+ source = tmp_path / "Example.java"
181
+ source.write_text("public class Example extends AbstractFormPlugin {}")
182
+ (tmp_path / "archive.json").write_text(json.dumps({"javas": [{
183
+ "className": "example.Example", "type": "form", "filePath": str(source),
184
+ "sourceHash": hashlib.sha256(source.read_bytes()).hexdigest(), "compileStatus": "skipped-by-config",
185
+ }]}))
186
+ result = cmd_plugins.validate_local_java_registration(str(tmp_path), {"className": "example.Example", "targetType": "BILL_FORM"})
187
+ assert result["compileStatus"] == "skipped-by-config"
188
+
189
+
190
+ def test_serve_registration_requires_application_context_before_platform_access():
191
+ import cosmic_api
192
+ with unittest.TestCase().assertRaisesRegex(ValueError, "SERVE_APP_ROOT_REQUIRED"):
193
+ cosmic_api._dispatch_serve({}, None, "registerPlugin", {
194
+ "formNumber": "example", "pluginConfig": {"className": "example.Plugin", "targetType": "BILL_FORM"},
195
+ })