@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
@@ -0,0 +1,70 @@
1
+ import json
2
+ import shutil
3
+ import subprocess
4
+ import sys
5
+ import zipfile
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
11
+ from plugin_target_sdk import TargetSdkError, verify_target_sdk
12
+
13
+
14
+ @pytest.fixture
15
+ def target(tmp_path, monkeypatch):
16
+ if not shutil.which('javac') or not shutil.which('javap'):
17
+ pytest.skip('JDK tools unavailable')
18
+ project = tmp_path / 'gradle' / 'code' / 'probe'
19
+ project.mkdir(parents=True)
20
+ gradle = project.parents[1]
21
+ (gradle / 'settings.gradle').write_text("include(':probe')\nproject(':probe').projectDir=new File('code/probe')\n")
22
+ (gradle / 'gradlew').touch()
23
+ sdk = tmp_path / 'sdk'
24
+ bos = sdk / 'mservice-cosmic' / 'lib' / 'bos'
25
+ bos.mkdir(parents=True)
26
+ (gradle / 'gradle.properties').write_text(f'systemProp.cosmic_home={sdk}\n')
27
+ (gradle / 'config.gradle').write_text('cosmic_libs_path="${cosmic_home}/mservice-cosmic/lib"\nbos="${cosmic_libs_path}/bos"')
28
+ source = tmp_path / 'Probe.java'
29
+ source.write_text('package kd.bos.probe; public class Probe { public void trigger() {} }')
30
+ subprocess.run(['javac', '-d', str(tmp_path), str(source)], check=True, capture_output=True)
31
+ with zipfile.ZipFile(bos / 'probe.jar', 'w') as jar:
32
+ jar.write(tmp_path / 'kd/bos/probe/Probe.class', 'kd/bos/probe/Probe.class')
33
+ config = tmp_path / 'appconfig.json'
34
+ config.write_text(json.dumps({'compile': True, 'GRADLE_ROOT': str(gradle),
35
+ 'JAVA_HOME': str(Path(shutil.which('java')).parent.parent)}))
36
+ monkeypatch.setenv('KD_APPCONFIG', str(config))
37
+ return project, config
38
+
39
+
40
+ def test_bound_target_uses_real_sdk_declarations_without_config_changes(target):
41
+ project, config = target
42
+ before = config.read_bytes()
43
+ result = verify_target_sdk(str(project), {'baseContract': {'class': 'kd.bos.probe.Probe'}},
44
+ [{'class': 'kd.bos.probe.Probe', 'members': ['trigger']}])
45
+ assert result['status'] == 'verified' and result['module'] == ':probe'
46
+ assert result['classes'][0]['jarSha256']
47
+ assert result['contracts'][0]['members'][0]['declaration'] == 'public void trigger();'
48
+ assert config.read_bytes() == before
49
+
50
+
51
+ @pytest.mark.parametrize('usage,code', [
52
+ ([{'class': 'kd.bos.probe.Absent'}], 'SDK_CLASS_NOT_FOUND'),
53
+ ([{'class': 'kd.bos.probe.Probe', 'members': ['absent']}], 'PLUGIN_SDK_MEMBER_UNVERIFIED'),
54
+ ([{'class': 'kd.bos.probe.Probe', 'members': 'trigger'}], 'PLUGIN_SDK_USAGE_INVALID'),
55
+ ])
56
+ def test_unproven_sdk_selectors_fail_before_generation(target, usage, code):
57
+ with pytest.raises(TargetSdkError) as error:
58
+ verify_target_sdk(str(target[0]), {}, usage)
59
+ assert error.value.error_code == code
60
+
61
+
62
+ def test_unmapped_module_and_explicit_source_only_are_distinct(target):
63
+ project, config = target
64
+ with pytest.raises(TargetSdkError) as error:
65
+ verify_target_sdk(str(project.parent / 'stale'), {})
66
+ assert error.value.error_code == 'PLUGIN_MODULE_UNRESOLVED'
67
+ config.write_text('{"compile":false}')
68
+ result = verify_target_sdk(str(project), {})
69
+ assert result['status'] == 'not-run'
70
+ assert 'unverified' in result['evidenceScope']
@@ -43,7 +43,8 @@ def test_list_view_type_is_forwarded_as_extra_query_param() -> None:
43
43
  config = {"baseUrl": "http://example.test/ierp"}
44
44
  with mock.patch.object(preview_url.kd_api, "get", return_value=response) as request:
45
45
  preview_url.get_preview_url(
46
- config, "http://example.test/ierp", "extension_form_number", view_type="list"
46
+ config, "http://example.test/ierp", "extension_form_number", view_type="list",
47
+ form_preview_url="http://example.test/ierp/?formId=extension_form_number",
47
48
  )
48
49
 
49
50
  request.assert_called_once_with(
@@ -54,6 +55,48 @@ def test_list_view_type_is_forwarded_as_extra_query_param() -> None:
54
55
  )
55
56
 
56
57
 
58
+ def test_duplicate_list_response_uses_verified_standard_list_route() -> None:
59
+ config = {"baseUrl": "http://example.test/ierp"}
60
+
61
+ def api(_base, path, _config, params):
62
+ if path == preview_url.PREVIEW_URL_PATH:
63
+ assert params == {"formNumber": "cup_request", "viewType": "list"}
64
+ return {"status": True, "errorCode": "0", "data": "http://example.test/ierp/?formId=cup_request"}
65
+ assert path == preview_url.FORM_SCHEMA_PATH
66
+ assert params == {"formId": "cup_request"}
67
+ return {"status": True, "errorCode": "0", "data": {
68
+ "listLayout": {"type": "FormAp"},
69
+ "listConfig": {"entityId": "cup_request"}, "listColumns": [{"key": "billno"}],
70
+ }}
71
+
72
+ with mock.patch.object(preview_url.kd_api, "get", side_effect=api) as request:
73
+ actual = preview_url.get_preview_url(
74
+ config, config["baseUrl"], "cup_request", view_type="list",
75
+ form_preview_url="http://example.test/ierp/?formId=cup_request",
76
+ )
77
+
78
+ assert actual == "http://example.test/ierp/?formId=bos_list&type=list&billFormId=cup_request"
79
+ assert request.call_count == 2
80
+
81
+
82
+ def test_duplicate_list_response_without_list_metadata_is_not_exposed() -> None:
83
+ config = {"baseUrl": "http://example.test/ierp"}
84
+
85
+ def api(_base, path, _config, _params):
86
+ if path == preview_url.PREVIEW_URL_PATH:
87
+ return {"status": True, "errorCode": "0", "data": "http://example.test/ierp/?formId=plain_page"}
88
+ return {"status": True, "errorCode": "0", "data": {
89
+ "formNumber": "plain_page", "listLayout": None, "listConfig": None,
90
+ }}
91
+
92
+ with mock.patch.object(preview_url.kd_api, "get", side_effect=api):
93
+ with pytest.raises(preview_url.PreviewUrlError, match="LIST_PREVIEW_METADATA_UNAVAILABLE"):
94
+ preview_url.get_preview_url(
95
+ config, config["baseUrl"], "plain_page", view_type="list",
96
+ form_preview_url="http://example.test/ierp/?formId=plain_page",
97
+ )
98
+
99
+
57
100
  def test_relative_platform_preview_url_is_resolved_against_environment() -> None:
58
101
  assert preview_url._response_preview_url(
59
102
  {"status": True, "errorCode": "0", "data": "/ierp/?formId=source"},
@@ -23,6 +23,12 @@ from _core import cmd_properties # noqa: E402
23
23
  from _core import cmd_workspace # noqa: E402
24
24
 
25
25
 
26
+ def test_lock_in_complex_policy_reports_the_supported_set_shape():
27
+ with pytest.raises(ValueError, match="PROPERTY_POLICY_LOCK_SHAPE_INVALID") as raised:
28
+ cmd_properties._normalize_recorded_complex_properties({"Lock": {"new": "lock"}})
29
+ assert '"set":{"Lock":"new,edit,submit,audit"}' in str(raised.value)
30
+
31
+
26
32
  ROWS = [
27
33
  {
28
34
  "key": "demo",
@@ -73,6 +79,22 @@ ROWS = [
73
79
  ]
74
80
 
75
81
 
82
+ def test_same_caption_entry_fields_use_explicit_entity_scope_for_a_single_target():
83
+ rows = [
84
+ {"key": "booking", "name": "定场单", "type": "BillEntity", "category": "entity", "id": "H"},
85
+ {"key": "details", "name": "订场明细", "type": "EntryEntity", "category": "entity", "id": "E"},
86
+ {"key": "services", "name": "附加服务", "type": "SubEntryEntity", "category": "entity", "id": "S"},
87
+ {"key": "booking_price", "name": "单价", "type": "PriceField", "category": "field", "parentKey": "details", "parentId": "E"},
88
+ {"key": "service_price", "name": "单价", "type": "PriceField", "category": "field", "parentKey": "services", "parentId": "S"},
89
+ ]
90
+ bulk = cmd_properties._property_policy_field_rows(rows, {"targets": {"fields": ["单价"], "entities": ["entries"]}}, "booking")
91
+ assert {row["key"] for row in bulk} == {"booking_price", "service_price"}
92
+ exact = cmd_properties._property_policy_field_rows(rows, {"targets": {"fields": [{"entity": "订场明细", "field": "单价"}]}}, "booking")
93
+ assert [row["key"] for row in exact] == ["booking_price"]
94
+ with pytest.raises(ValueError):
95
+ cmd_properties._property_policy_field_rows(rows, {"targets": {"fields": [{"field": "单价"}]}}, "booking")
96
+
97
+
76
98
  def test_lock_policy_uses_verified_form_control_path() -> None:
77
99
  from _core import cmd_listui
78
100
 
@@ -11,6 +11,7 @@ building it and had to be fixed.
11
11
  from __future__ import annotations
12
12
 
13
13
  import json
14
+ import subprocess
14
15
  import sys
15
16
  from pathlib import Path
16
17
 
@@ -226,8 +227,6 @@ def test_both_locales_resolve_the_contract_index() -> None:
226
227
  while still returning a well-formed answer - a silent wrong result, which is worse
227
228
  than an error. Both locales must load the same index.
228
229
  """
229
- import importlib.util
230
-
231
230
  loaded = {}
232
231
  # 契约解析器已随属性域搬到 _core/cmd_properties.py;按定义位置取,不写死文件。
233
232
  import sys as _sys
@@ -237,15 +236,18 @@ def test_both_locales_resolve_the_contract_index() -> None:
237
236
  assert len(holders) == 2, sorted(str(x) for x in holders)
238
237
  for path in sorted(holders, key=lambda x: ".locales" in str(x)):
239
238
  label = "zh" if ".locales" in str(path) else "canonical"
240
- source = path.read_text(encoding="utf-8")
241
- # exercise the resolver in isolation: no package import, no network, no auth
242
- namespace = {"__file__": str(path), "pathlib": __import__("pathlib"),
243
- "json": json}
244
- start = source.index("def _contract_dirs():")
245
- end = source.index("def _property_input_index():")
246
- exec(compile(source[start:end], str(path), "exec"), namespace) # noqa: S102
247
- doc = namespace["_load_json_contract"]("property-input-index.json")
248
- loaded[label] = doc
239
+ scripts = path.parents[1]
240
+ result = subprocess.run(
241
+ [
242
+ sys.executable, "-c",
243
+ "import json,sys; sys.path.insert(0,sys.argv[1]); "
244
+ "from _core.cmd_properties import _load_json_contract; "
245
+ "print(json.dumps(_load_json_contract('property-input-index.json')))",
246
+ str(scripts),
247
+ ],
248
+ check=True, capture_output=True, text=True,
249
+ )
250
+ loaded[label] = json.loads(result.stdout)
249
251
 
250
252
  for label, doc in loaded.items():
251
253
  assert doc.get("properties"), f"{label} failed to load the index"
@@ -0,0 +1,160 @@
1
+ import hashlib
2
+ import json
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+
8
+ SCRIPT = Path(__file__).resolve().parents[1] / "query_plugin_handoff.py"
9
+
10
+
11
+ def handoff_file(tmp_path):
12
+ file = tmp_path / "_runtime" / "plugin-handoffs" / "BookingPlugin.json"
13
+ file.parent.mkdir(parents=True)
14
+ document = {
15
+ "status": True,
16
+ "handoffPath": str(file),
17
+ "handoff": {
18
+ "formGraph": {"formNumber": "booking", "entities": [
19
+ {"entityKey": "booking_head", "fields": [
20
+ {"fieldKey": "quantity", "fieldName": "数量", "comboOptions": ["1", "2"]},
21
+ {"fieldKey": "member", "fieldName": "会员", "required": False},
22
+ ]},
23
+ {"entityKey": "booking_entry", "role": "entries", "parentEntityKey": "booking_head",
24
+ "collectionPath": ["booking_entry"], "fields": [
25
+ {"fieldKey": "start_time", "fieldType": "TimeProp", "javaType": "Integer",
26
+ "valueAccessor": 'getInt("start_time")', "timeValueUnit": "seconds-after-midnight"}]},
27
+ ]},
28
+ "operationContracts": [
29
+ {"operationKey": "save", "parameter": {"entity": "booking_head"}},
30
+ {"operationKey": "confirm", "parameter": {"entity": "booking_head"}},
31
+ ],
32
+ "codingEvidence": {"signatureTypes": [{"class": "ExtendedDataEntity", "import": "kd.bos.entity.ExtendedDataEntity"}],
33
+ "apiContracts": [{"class": "kd.bos.servicehelper.QueryServiceHelper", "import": "import kd.bos.servicehelper.QueryServiceHelper;", "members": [{"name": "query", "signature": "public static DynamicObjectCollection query(String entityName)"}]}],
34
+ "guide": "large-evidence-" * 1800},
35
+ },
36
+ }
37
+ file.write_text(json.dumps(document, ensure_ascii=False), encoding="utf-8")
38
+ return file, document
39
+
40
+
41
+ def query(file, operation, *args):
42
+ result = subprocess.run([sys.executable, str(SCRIPT), operation, "--handoff", str(file), *args],
43
+ capture_output=True, text=True, check=False)
44
+ return result.returncode, json.loads(result.stdout), result.stdout
45
+
46
+
47
+ def test_target_readiness_is_bounded_and_missing_evidence_is_not_claimed(tmp_path):
48
+ file, document = handoff_file(tmp_path)
49
+ code, context, _ = query(file, "coding-start")
50
+ assert code == 0 and context['targetSdk']['status'] == 'not-provided'
51
+ assert 'path' not in context['targetSdk']
52
+ document['handoff']['targetSdk'] = {'status': 'verified', 'module': ':probe',
53
+ 'evidenceScope': 'SDK declarations only', 'classes': [{'class': 'large-evidence-' * 1800}]}
54
+ file.write_text(json.dumps(document))
55
+ for operation in ['coding-start', 'coding-context']:
56
+ code, context, output = query(file, operation)
57
+ assert code == 0 and context['targetSdk']['status'] == 'verified'
58
+ assert context['targetSdk']['path'] == '/handoff/targetSdk'
59
+ assert 'classes' not in context['targetSdk'] and 'large-evidence-' not in output
60
+
61
+
62
+ def test_index_find_and_get_return_only_requested_native_context(tmp_path):
63
+ file, document = handoff_file(tmp_path)
64
+ original = hashlib.sha256(file.read_bytes()).hexdigest()
65
+ code, root, stdout = query(file, "index")
66
+ assert code == 0 and len(stdout.encode("utf-8")) <= 8192
67
+ assert [item["path"] for item in root["items"]] == ["/status", "/handoffPath", "/handoff"]
68
+ assert "large-evidence" not in stdout
69
+
70
+ code, graph, stdout = query(file, "index", "--path", "/handoff/formGraph/entities")
71
+ assert code == 0 and graph["items"][0]["identity"] == {"entityKey": "booking_head"}
72
+ code, field, _ = query(file, "find", "--path", "/handoff/formGraph/entities/0/fields",
73
+ "--field", "fieldKey", "--equals", "member")
74
+ assert code == 0 and [item["path"] for item in field["matches"]] == ["/handoff/formGraph/entities/0/fields/1"]
75
+ code, item, _ = query(file, "get", "--path", field["matches"][0]["path"])
76
+ assert code == 0 and item["value"] == document["handoff"]["formGraph"]["entities"][0]["fields"][1]
77
+
78
+ code, operation, _ = query(file, "find", "--path", "/handoff/operationContracts",
79
+ "--field", "operationKey", "--equals", "confirm")
80
+ assert code == 0 and operation["matches"][0]["path"] == "/handoff/operationContracts/1"
81
+ code, signature, _ = query(file, "get", "--path", "/handoff/codingEvidence/signatureTypes/0")
82
+ assert code == 0 and signature["value"] == document["handoff"]["codingEvidence"]["signatureTypes"][0]
83
+ assert hashlib.sha256(file.read_bytes()).hexdigest() == original
84
+
85
+
86
+ def test_large_or_invalid_queries_fail_without_dumping_handoff(tmp_path):
87
+ file, _ = handoff_file(tmp_path)
88
+ for operation, arguments, expected in [
89
+ ("get", ["--path", "/handoff"], "HANDOFF_QUERY_TOO_LARGE"),
90
+ ("get", ["--path", "/handoff/formGraph/entities/9"], "HANDOFF_POINTER_NOT_FOUND"),
91
+ ("get", ["--path", "handoff/formGraph"], "HANDOFF_POINTER_INVALID"),
92
+ ("find", ["--path", "/handoff/formGraph", "--field", "entityKey", "--equals", "booking_head"], "HANDOFF_NOT_RECORD_LIST"),
93
+ ("index", ["--path", "/handoff/operationContracts", "--offset", "-1"], "HANDOFF_PAGE_INVALID"),
94
+ ]:
95
+ code, output, stdout = query(file, operation, *arguments)
96
+ assert code == 2 and output["errorCode"] == expected
97
+ assert len(stdout.encode("utf-8")) < 8192 and "large-evidence" not in stdout
98
+
99
+ code, page, _ = query(file, "index", "--path", "/handoff/operationContracts", "--limit", "1")
100
+ assert code == 0 and page["nextOffset"] == 1
101
+ code, page, _ = query(file, "index", "--path", "/handoff/operationContracts", "--offset", "1", "--limit", "1")
102
+ assert code == 0 and page["nextOffset"] is None and page["items"][0]["identity"]["operationKey"] == "confirm"
103
+
104
+
105
+ def test_query_rejects_unrelated_json_even_when_shape_looks_like_handoff(tmp_path):
106
+ file, document = handoff_file(tmp_path)
107
+ document["handoffPath"] = str(tmp_path / "other.json")
108
+ file.write_text(json.dumps(document), encoding="utf-8")
109
+ code, output, _ = query(file, "index")
110
+ assert code == 2 and output["errorCode"] == "HANDOFF_PATH_MISMATCH"
111
+
112
+
113
+ def test_coding_context_returns_required_types_once_without_large_guide(tmp_path):
114
+ file, _ = handoff_file(tmp_path)
115
+ original = hashlib.sha256(file.read_bytes()).hexdigest()
116
+ code, context, stdout = query(file, "coding-context")
117
+ assert code == 0 and context["contextMode"] == "coding-context"
118
+ assert context["handoffSha256"] == original
119
+ assert context["formGraph"]["entities"][0]["fields"][0]["fieldKey"] == "quantity"
120
+ assert context["queryRootEvidence"]["formNumber"] == "booking"
121
+ assert context["queryRootEvidence"]["nonRootEntityKeys"] == ["booking_entry"]
122
+ assert "standalone QueryServiceHelper roots" in context["queryRootEvidence"]["boundary"]
123
+ assert context["formGraph"]["entities"][1]["fields"][0]["valueAccessor"] == 'getInt("start_time")'
124
+ assert context["formGraph"]["entities"][1]["fields"][0]["timeValueUnit"] == "seconds-after-midnight"
125
+ assert context["operationContracts"][0]["operationKey"] == "save"
126
+ assert context["codingEvidence"]["apiContracts"][0]["members"][0]["signature"].startswith("public static DynamicObjectCollection query")
127
+ assert context["stateWarnings"] == []
128
+ assert "large-evidence" not in stdout and len(stdout.encode("utf-8")) < 32 * 1024
129
+ assert hashlib.sha256(file.read_bytes()).hexdigest() == original
130
+
131
+
132
+ def test_startup_context_keeps_field_facts_and_exact_paths_without_expanding_api_or_related_forms(tmp_path):
133
+ file, document = handoff_file(tmp_path)
134
+ document["handoff"]["relatedForms"] = [{"formNumber": "member", "entities": [{"fields": [{"private": "not needed at startup"}]}]}]
135
+ document["handoff"]["availableOperations"] = [{"operationKey": "complete"}]
136
+ document["handoff"]["codingEvidence"]["operationDispatch"] = {"validator": {"accessor": "this.getOperateKey()"}}
137
+ file.write_text(json.dumps(document), encoding="utf-8")
138
+ code, result, output = query(file, "coding-start")
139
+ assert code == 0 and result["contextMode"] == "coding-start"
140
+ assert result["entities"][1]["fields"][0]["valueAccessor"] == 'getInt("start_time")'
141
+ assert result["relatedForms"][0]["path"] == "/handoff/relatedForms/0"
142
+ assert result["availableOperationKeys"] == ["complete"]
143
+ assert result["availableOperationsPath"] == "/handoff/availableOperations"
144
+ assert result["codingEvidence"]["operationDispatch"]["validator"]["accessor"] == "this.getOperateKey()"
145
+ assert result["codingEvidence"]["apiContracts"][0]["members"] == ["query"]
146
+ assert "large-evidence" not in output and "not needed at startup" not in output
147
+
148
+
149
+ def test_large_startup_inventory_is_explicitly_referenced_instead_of_dumped(tmp_path):
150
+ file, document = handoff_file(tmp_path)
151
+ document["handoff"]["formGraph"]["entities"][0]["fields"] = [
152
+ {"fieldKey": f"field_{index}", "fieldName": "A large inventory field", "fieldType": "TextProp"}
153
+ for index in range(1000)]
154
+ file.write_text(json.dumps(document), encoding="utf-8")
155
+ code, result, output = query(file, "coding-start")
156
+ assert code == 0 and len(output.encode()) <= 16 * 1024
157
+ assert result["entities"][0]["fieldsOmitted"] is True
158
+ assert result["entities"][0]["fieldCount"] == 1000
159
+ assert result["entities"][0]["fieldsPath"] == "/handoff/formGraph/entities/0/fields"
160
+ assert "fields" not in result["entities"][0]
@@ -137,9 +137,28 @@ class BuildRegisterPayloadTest(unittest.TestCase):
137
137
  "description": "desc",
138
138
  "enabled": True,
139
139
  "targetType": "form",
140
+ "pluginType": "JAVA",
140
141
  })
141
142
  self.assertNotIn("operationKey", payload)
142
143
 
144
+ def test_application_binding_comes_from_archive(self):
145
+ with tempfile.TemporaryDirectory() as tmp:
146
+ Path(tmp, "archive.json").write_text(json.dumps({
147
+ "appId": "selected-app", "javas": [{
148
+ "className": "example.Plugin", "type": "billform",
149
+ "filePath": "Plugin.java", "formNumber": "booking",
150
+ "description": "booking", "operationKey": "",
151
+ "bizAppId": "different-app",
152
+ }],
153
+ }))
154
+ candidate = register_plugins._load_classes_from_archive(tmp)[0]
155
+ payload = register_plugins._build_register_payload(
156
+ candidate["formNumber"], candidate["className"], candidate["description"],
157
+ "BILL_FORM", biz_app_id=candidate["bizAppId"],
158
+ )
159
+ self.assertEqual(payload["bizAppId"], "selected-app")
160
+ self.assertEqual(payload["pluginType"], "JAVA")
161
+
143
162
  def test_operation_key_included_when_truthy(self):
144
163
  payload = register_plugins._build_register_payload(
145
164
  "sch_job", "com.example.OpPlugin", "desc", "form", operation_key="save",
@@ -334,7 +353,7 @@ class MainTest(unittest.TestCase):
334
353
  mock_config, mock_post):
335
354
  mock_load_classes.return_value = [
336
355
  {"className": "com.example.FormPlugin", "type": "form_plugin",
337
- "formNumber": "sch_job", "description": "test plugin"},
356
+ "formNumber": "sch_job", "description": "test plugin", "bizAppId": "selected-app"},
338
357
  ]
339
358
  mock_registry.return_value = {"form_plugin": "form"}
340
359
  mock_config.return_value = {"baseUrl": "https://api.example.com"}
@@ -343,6 +362,10 @@ class MainTest(unittest.TestCase):
343
362
  output = self._run()
344
363
  self.assertEqual(output["status"], "success")
345
364
  self.assertEqual(output["summary"], {"total": 1, "success": 1, "failed": 0})
365
+ self.assertEqual(output["activation"]["state"], "registration_unverified")
366
+ self.assertIsNone(output["activation"]["requiresServiceRestart"])
367
+ self.assertFalse(output["activation"]["runtimeVerified"])
368
+ self.assertNotIn("nextAction", output["activation"])
346
369
 
347
370
  # Verify kd_api.post call: legacy_api style + correct payload
348
371
  args, kwargs = mock_post.call_args
@@ -354,6 +377,8 @@ class MainTest(unittest.TestCase):
354
377
  self.assertEqual(body["className"], "com.example.FormPlugin")
355
378
  self.assertEqual(body["targetType"], "form")
356
379
  self.assertEqual(body["formNumber"], "sch_job")
380
+ self.assertEqual(body["bizAppId"], "selected-app")
381
+ self.assertEqual(body["pluginType"], "JAVA")
357
382
 
358
383
  @mock.patch("register_plugins.kd_api.post")
359
384
  @mock.patch("register_plugins._load_config")
@@ -417,6 +442,11 @@ class MainTest(unittest.TestCase):
417
442
 
418
443
  self.assertEqual(output["status"], "success")
419
444
  self.assertEqual(output["verification"]["status"], "pass")
445
+ self.assertEqual(output["activation"]["registeredClasses"], ["com.example.OpPlugin"])
446
+ self.assertEqual(output["activation"]["state"], "runtime_unverified")
447
+ self.assertNotIn("nextAction", output["activation"])
448
+ self.assertIsNone(output["activation"]["requiresServiceRestart"])
449
+ self.assertFalse(output["activation"]["runtimeVerified"])
420
450
  mock_get.assert_called_once_with(
421
451
  "https://api.example.com", register_plugins.QUERY_PATH,
422
452
  {"baseUrl": "https://api.example.com"}, {"formNumber": "sch_job"},
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import hashlib
4
4
  import importlib.util
5
5
  import json
6
+ import os
6
7
  from pathlib import Path
7
8
  import subprocess
8
9
  import sys
@@ -67,16 +68,42 @@ def test_cli_delivers_one_block_and_the_output_check_handoff(capsys, tmp_path):
67
68
  block = ':::render:kdform\n{"title":"Example","url":"https://example.test"}\n:::'
68
69
  with mock.patch.object(sys, "argv", [
69
70
  str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "example",
70
- ]), mock.patch.object(CARDS, "render", return_value=[block]) as render:
71
+ ]), mock.patch.object(CARDS, "render", return_value=[block]) as render, mock.patch.object(
72
+ CARDS, "_DYM_BACKGROUND_NOTICE", ""
73
+ ):
71
74
  assert CARDS.main() == 0
72
75
  output = capsys.readouterr()
73
76
  assert output.out == block + "\n"
74
77
  assert "output_check" in output.err
75
- assert "search_tools" in output.err
76
78
  assert "Markdown" in output.err
77
79
  render.assert_called_once()
78
80
 
79
81
 
82
+ def test_cli_binds_auth_cache_to_explicit_app_root(tmp_path):
83
+ block = ':::render:kdform\n{"title":"Example","url":"https://example.test"}\n:::'
84
+ with mock.patch.dict(os.environ, {}, clear=False), mock.patch.object(sys, "argv", [
85
+ str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "example",
86
+ ]), mock.patch.object(CARDS, "render", return_value=[block]), mock.patch.object(
87
+ CARDS, "_DYM_BACKGROUND_NOTICE", ""
88
+ ):
89
+ assert CARDS.main() == 0
90
+ assert os.environ["PROJECT_ROOT"] == str(tmp_path.resolve())
91
+
92
+
93
+ def test_background_handoff_survives_suppressed_stderr(capsys, tmp_path):
94
+ block = ':::render:kdform\n{"title":"Example","url":"https://example.test"}\n:::'
95
+ notice = 'DYM_BACKGROUND_REQUEST={"formNumbers":["a","b"]}'
96
+ with mock.patch.object(sys, "argv", [
97
+ str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "a", "--form-number", "b",
98
+ ]), mock.patch.object(CARDS, "render", return_value=[block]), mock.patch.object(
99
+ CARDS, "_DYM_BACKGROUND_NOTICE", notice
100
+ ):
101
+ assert CARDS.main() == 0
102
+ output = capsys.readouterr()
103
+ assert output.out == block + "\n" + notice + "\n"
104
+ assert "Markdown" in output.err
105
+
106
+
80
107
  def test_cli_does_not_request_output_check_when_rendering_fails(capsys, tmp_path):
81
108
  with mock.patch.object(sys, "argv", [
82
109
  str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "example",
@@ -149,6 +176,7 @@ def test_existing_form_modification_renders_only_exact_target(tmp_path: Path) ->
149
176
  "http://example.test/ierp",
150
177
  "target_form",
151
178
  view_type="list",
179
+ form_preview_url="http://example.test/ierp/?formId=platform_original_id",
152
180
  ),
153
181
  ]
154
182
  saved = json.loads((tmp_path / "archive.json").read_text(encoding="utf-8"))
@@ -436,7 +464,7 @@ def test_card_carries_list_preview_url_and_degrades_silently(tmp_path: Path) ->
436
464
  json.dumps(archive, ensure_ascii=False), encoding="utf-8"
437
465
  )
438
466
 
439
- def preview_with_list(config, base_url, form_number, view_type=""):
467
+ def preview_with_list(config, base_url, form_number, view_type="", form_preview_url=""):
440
468
  if view_type == "list":
441
469
  return "http://example.test/ierp/?formId=list_view_id"
442
470
  return "http://example.test/ierp/?formId=form_view_id"
@@ -454,7 +482,7 @@ def test_card_carries_list_preview_url_and_degrades_silently(tmp_path: Path) ->
454
482
  saved = json.loads((tmp_path / "archive.json").read_text(encoding="utf-8"))
455
483
  assert saved["artifacts"][0]["listUrl"].endswith("formId=list_view_id")
456
484
 
457
- def preview_without_list(config, base_url, form_number, view_type=""):
485
+ def preview_without_list(config, base_url, form_number, view_type="", form_preview_url=""):
458
486
  if view_type == "list":
459
487
  raise RuntimeError("list view unsupported")
460
488
  return "http://example.test/ierp/?formId=form_view_id"
@@ -471,6 +499,24 @@ def test_card_carries_list_preview_url_and_degrades_silently(tmp_path: Path) ->
471
499
  assert "formId=form_view_id" in degraded[0]
472
500
  assert "listUrl" not in degraded[0]
473
501
 
502
+ def preview_with_duplicate_list(config, base_url, form_number, view_type="", form_preview_url=""):
503
+ return "http://example.test/ierp/?formId=form_view_id"
504
+
505
+ with mock.patch(
506
+ "kd_env_loader.load_effective_skill_config",
507
+ return_value={"server": {"baseUrl": "http://example.test/ierp"}},
508
+ ), mock.patch.object(CARDS, "get_preview_url", side_effect=preview_with_duplicate_list):
509
+ duplicate = CARDS.render(
510
+ str(tmp_path), include_plan=False, target_form_number="target_form"
511
+ )
512
+
513
+ assert len(duplicate) == 1
514
+ assert "listUrl" not in duplicate[0]
515
+ saved = json.loads((tmp_path / "archive.json").read_text(encoding="utf-8"))
516
+ assert "listUrl" not in saved["artifacts"][0]
517
+ handoff = json.loads((tmp_path / "_runtime/test-environment.json").read_text(encoding="utf-8"))
518
+ assert "listUrl" not in handoff["forms"][0]
519
+
474
520
 
475
521
  def test_localized_card_cli_keeps_target_filter_contract() -> None:
476
522
  localized = SCRIPT.parents[1] / ".locales" / "zh" / "scripts" / SCRIPT.name
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import importlib
4
4
  import json
5
+ import subprocess
5
6
  import sys
6
7
  import tempfile
7
8
  import unittest
@@ -19,6 +20,32 @@ cosmic_api = importlib.import_module("cosmic_api")
19
20
 
20
21
 
21
22
  class ReportBusinessContractTest(unittest.TestCase):
23
+ def test_report_schema_cli_contains_all_payload_definitions_locally(self):
24
+ completed = subprocess.run([
25
+ sys.executable, str(APP_BUILD_ROOT / "skills/cosmic-meta-modeling/scripts/report_validator.py"), "--schema",
26
+ ], capture_output=True, text=True, check=True)
27
+ schema = json.loads(completed.stdout)
28
+ self.assertEqual(schema["properties"]["reportColumns"]["$ref"], "#/$defs/reportWrite_columns")
29
+ self.assertIn("kind", schema["$defs"]["reportWrite_column"]["required"])
30
+ self.assertIn("caption", schema["$defs"]["reportWrite_column"]["required"])
31
+ self.assertEqual(schema["$defs"]["reportWrite_sort"]["properties"]["direction"]["enum"], ["asc", "desc"])
32
+
33
+ def check_references(value):
34
+ if isinstance(value, list):
35
+ for item in value:
36
+ check_references(item)
37
+ if not isinstance(value, dict):
38
+ return
39
+ if "$ref" in value:
40
+ self.assertTrue(value["$ref"].startswith("#/"))
41
+ target = schema
42
+ for segment in value["$ref"][2:].split("/"):
43
+ target = target[segment.replace("~1", "/").replace("~0", "~")]
44
+ for item in value.values():
45
+ check_references(item)
46
+
47
+ check_references(schema)
48
+
22
49
  def test_report_pipeline_returns_authoritative_readback_form_number(self) -> None:
23
50
  artifact = {
24
51
  "templateNumber": "bos_rpttpl",
@@ -353,13 +353,14 @@ class TreeSemanticsTest(unittest.TestCase):
353
353
  )
354
354
  self.assertGreater(checked, 0, "the id/pid scan matched nothing; the pattern is stale")
355
355
 
356
- def test_routing_and_coding_mapping_agree_on_the_report_types(self):
356
+ def test_routing_uses_registered_report_types(self):
357
357
  routing = _read(APP_BUILD_ROOT / "references/plugin-runtime-routing.md")
358
- mapping = _read(APP_BUILD_ROOT / "skills/cosmic-coding-impl/SKILL.md")
358
+ types = {entry["type"] for entry in json.loads(
359
+ _read(APP_BUILD_ROOT / "references/type-registry.json"))["types"]}
359
360
  for plugin_type in re.findall(r"`(report[a-z]+)`", routing):
360
361
  with self.subTest(type=plugin_type):
361
- self.assertIn(f"| `{plugin_type}` |", mapping,
362
- f"routing offers type {plugin_type} that the coding mapping lacks")
362
+ self.assertIn(plugin_type, types,
363
+ f"routing offers type {plugin_type} absent from the registry")
363
364
 
364
365
  def test_left_tree_type_is_described_by_its_real_signature(self):
365
366
  routing = _read(APP_BUILD_ROOT / "references/plugin-runtime-routing.md")