@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/package.json +1 -1
  2. package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
  3. package/skills/cosmic-build/SKILL.md +24 -9
  4. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
  5. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
  6. package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
  7. package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
  8. package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
  9. package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
  10. package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
  11. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
  12. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
  13. package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
  14. package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
  15. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
  16. package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
  17. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
  18. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
  19. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
  20. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
  21. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
  22. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
  25. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
  26. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
  27. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
  28. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
  29. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
  30. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
  31. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
  32. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
  33. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
  34. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
  35. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
  36. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
  37. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
  38. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
  39. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
  40. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
  41. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
  42. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
  43. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
  44. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
  45. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
  46. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
  47. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
  48. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
  49. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
  50. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
  51. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
  52. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
  53. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
  54. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
  55. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
  56. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
  57. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
  58. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
  59. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
  60. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
  61. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
  62. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
  63. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
  64. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
  65. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
  66. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
  67. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
  68. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
  69. package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
  70. package/skills/cosmic-build/references/compile-guide.md +36 -1
  71. package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
  72. package/skills/cosmic-build/references/delivery.md +16 -2
  73. package/skills/cosmic-build/references/execution-contract.md +15 -3
  74. package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
  75. package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
  76. package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
  77. package/skills/cosmic-build/references/report-filter-contract.md +53 -15
  78. package/skills/cosmic-build/references/sdk-inspection.md +4 -1
  79. package/skills/cosmic-build/references/type-registry.json +26 -17
  80. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
  81. package/skills/cosmic-build/scripts/_http_client.py +3 -0
  82. package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
  83. package/skills/cosmic-build/scripts/configure_list.py +112 -0
  84. package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
  85. package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
  86. package/skills/cosmic-build/scripts/execution_binding.py +25 -0
  87. package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
  88. package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
  89. package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
  90. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
  91. package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
  92. package/skills/cosmic-build/scripts/register_plugins.py +27 -10
  93. package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
  94. package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
  95. package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
  96. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
  97. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
  98. package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
  99. package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
  100. package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
  101. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
  102. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
  103. package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
  104. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
  105. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
  106. package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
  107. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
  108. package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
  109. package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
  110. package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
  111. package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
  112. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
  113. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
  114. package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
  115. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
  116. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
  117. package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
  118. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
  119. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
  120. package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
  121. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
  122. package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
  123. package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
  124. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
  125. package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
  126. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
  127. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
  128. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
  129. package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
  130. package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
  131. package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
  132. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
  133. package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
  134. package/skills/cosmic-build/scripts/test_environment.py +67 -1
  135. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
  136. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
  138. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
  139. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
  143. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
  144. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
  145. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
  146. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
  147. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
  177. package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
  178. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
  179. package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
  180. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
  181. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
  182. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
  183. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
  184. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
  185. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
  193. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
  194. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
  196. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
  197. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
  198. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
  199. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
  200. package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
  201. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
  202. package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
  203. package/skills/cosmic-dev-best-practices/references/README.md +2 -2
  204. package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
  205. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
  206. package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
  207. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
  208. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
  209. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
  210. package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
  211. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
  212. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
  213. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
  214. package/skills/erp-analysis/references/delivery-contract.md +2 -1
  215. package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
  216. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
  217. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
  218. package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
@@ -0,0 +1,78 @@
1
+ package kd.bos.app.uhyl.bfy11.operate;
2
+
3
+ import java.util.List;
4
+
5
+ import kd.bos.dataentity.entity.DynamicObject;
6
+ import kd.bos.dataentity.entity.DynamicObjectCollection;
7
+ import kd.bos.entity.ExtendedDataEntity;
8
+ import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
9
+ import kd.bos.entity.plugin.AddValidatorsEventArgs;
10
+ import kd.bos.entity.plugin.PreparePropertysEventArgs;
11
+ import kd.bos.entity.validate.AbstractValidator;
12
+
13
+ /**
14
+ * 定场单提交校验操作插件。
15
+ *
16
+ * 提交时逐行校验订场明细:
17
+ * 1. 结束时间必须晚于开始时间
18
+ * 2. 时段必须在营业时间 08:00–22:00 之内
19
+ *
20
+ * 批量提交多张单时每张单独立判断,合格的照常提交,不合格的给出原因。
21
+ */
22
+ public class UhylBookingOrderOpPlugin extends AbstractOperationServicePlugIn {
23
+
24
+ private static final String ENTRY_COLLECTION = "uhyl_book_order_detail";
25
+ private static final String FIELD_START_TIME = "uhyl_start_time";
26
+ private static final String FIELD_END_TIME = "uhyl_end_time";
27
+ private static final String FIELD_BILLNO = "billno";
28
+
29
+ /** 营业开始时间 08:00:00(午夜后秒数) */
30
+ private static final int BUSINESS_OPEN_SECONDS = 8 * 3600;
31
+ /** 营业结束时间 22:00:00(午夜后秒数) */
32
+ private static final int BUSINESS_CLOSE_SECONDS = 22 * 3600;
33
+
34
+ @Override
35
+ public void onPreparePropertys(PreparePropertysEventArgs e) {
36
+ super.onPreparePropertys(e);
37
+ List<String> fieldKeys = e.getFieldKeys();
38
+ fieldKeys.add(FIELD_BILLNO);
39
+ fieldKeys.add(ENTRY_COLLECTION);
40
+ }
41
+
42
+ @Override
43
+ public void onAddValidators(AddValidatorsEventArgs e) {
44
+ super.onAddValidators(e);
45
+ e.addValidator(new AbstractValidator() {
46
+ @Override
47
+ public void validate() {
48
+ for (ExtendedDataEntity entity : this.getDataEntities()) {
49
+ DynamicObject dataEntity = entity.getDataEntity();
50
+ String billNo = dataEntity.getString(FIELD_BILLNO);
51
+ DynamicObjectCollection entryRows =
52
+ dataEntity.getDynamicObjectCollection(ENTRY_COLLECTION);
53
+ if (entryRows == null || entryRows.isEmpty()) {
54
+ continue;
55
+ }
56
+ for (int i = 0; i < entryRows.size(); i++) {
57
+ DynamicObject row = entryRows.get(i);
58
+ int rowNum = i + 1;
59
+ int startTime = row.getInt(FIELD_START_TIME);
60
+ int endTime = row.getInt(FIELD_END_TIME);
61
+
62
+ if (endTime <= startTime) {
63
+ this.addErrorMessage(entity,
64
+ "单据[" + billNo + "]第" + rowNum + "行:结束时间必须晚于开始时间");
65
+ }
66
+
67
+ if (startTime < BUSINESS_OPEN_SECONDS
68
+ || endTime > BUSINESS_CLOSE_SECONDS) {
69
+ this.addErrorMessage(entity,
70
+ "单据[" + billNo + "]第" + rowNum
71
+ + "行:时段必须在营业时间 08:00–22:00 之内");
72
+ }
73
+ }
74
+ }
75
+ }
76
+ });
77
+ }
78
+ }
@@ -73,3 +73,15 @@ print(json.dumps(cmd_formactions._verify_add_field_contract(
73
73
  assert result["ok"] is False, scripts
74
74
  assert "mustInput" in result["scope"], scripts
75
75
  assert result["mustInputReadBack"]["observed"] is False, scripts
76
+
77
+
78
+ def test_omitted_type_is_rejected_before_target_lookup_or_write():
79
+ for scripts in SCRIPT_TREES:
80
+ result = _run(scripts, """
81
+ result = cmd_formactions.do_add_field({}, None, "selected_form", "New business field", "business_field")
82
+ print(json.dumps(result))
83
+ """)
84
+ assert result["status"] is False
85
+ assert result["errorCode"] == "FIELD_SPEC_INVALID"
86
+ assert result["data"]["writeAttempted"] is False
87
+ assert "field-type" in result["message"]
@@ -11,7 +11,10 @@ APP_BUILD_ROOT = Path(__file__).resolve().parents[2]
11
11
 
12
12
 
13
13
  def _load_sync_module():
14
- path = APP_BUILD_ROOT / "scripts/sync_generated_locale_assets.py"
14
+ # 同步器按设计只发布在规范树;显式运行中文镜像测试也使用同一个同步器。
15
+ root = next(root for root in (APP_BUILD_ROOT, *APP_BUILD_ROOT.parents)
16
+ if (root / "scripts" / "sync_generated_locale_assets.py").is_file())
17
+ path = root / "scripts" / "sync_generated_locale_assets.py"
15
18
  spec = importlib.util.spec_from_file_location("sync_generated_locale_assets", path)
16
19
  assert spec is not None and spec.loader is not None
17
20
  module = importlib.util.module_from_spec(spec)
@@ -39,11 +42,22 @@ class GeneratedLocaleAssetsTest(unittest.TestCase):
39
42
  "kingscript-gate-v1", "mcp-server-v1", "metalog-v1",
40
43
  "operation-contract-harvest-v1", "operation-write-v1",
41
44
  "plugin-handoff-v1", "property-policy-contract-v1", "python-runtime-v1",
42
- "report-tests-v1", "report-write-v1", "rule-write-v1", "execution-recovery-v1", "plugin-handoff-query-v1"},
45
+ "report-tests-v1", "report-write-v1", "rule-write-v1", "execution-recovery-v1", "plugin-handoff-query-v1",
46
+ "list-execution-v1", "report-repair-v1"},
43
47
  set(result["families"]),
44
48
  )
45
49
  self.assertEqual(len(result["files"]), result["counts"]["in_sync"])
46
50
 
51
+ def test_report_repair_sync_preserves_localized_runtime_diagnostics(self) -> None:
52
+ module = _load_sync_module()
53
+ generated = {item for group in module.GENERATED_FAMILIES.values() for item in group}
54
+ for name in ("cmd_apps.py", "validators.py"):
55
+ self.assertNotIn(f"skills/cosmic-meta-api/scripts/_core/{name}", generated)
56
+ self.assertIn("skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py",
57
+ module.GENERATED_FAMILIES["report-repair-v1"])
58
+ result = module.inspect(["report-repair-v1"])
59
+ self.assertTrue(result["ok"], result)
60
+
47
61
  def test_execution_recovery_mirrors_are_machine_synced(self) -> None:
48
62
  module = _load_sync_module()
49
63
  result = module.inspect(["execution-recovery-v1"])
@@ -60,6 +74,11 @@ class GeneratedLocaleAssetsTest(unittest.TestCase):
60
74
  module = _load_sync_module()
61
75
  family = module.GENERATED_FAMILIES["report-tests-v1"]
62
76
  self.assertIn("scripts/test/test_plugin_handoff.py", family)
77
+ self.assertIn("scripts/test/test_dcs_code_gate.py", family)
78
+ self.assertIn("scripts/dcs_code_gate.py", module.GENERATED_FAMILIES["plugin-handoff-v1"])
79
+ self.assertIn("scripts/dcs-rule-catalog.json", module.GENERATED_FAMILIES["plugin-handoff-v1"])
80
+ result = module.inspect(["plugin-handoff-v1"])
81
+ self.assertTrue(result["ok"], result)
63
82
  for _family, source, target in module.iter_assets(["report-tests-v1"]):
64
83
  with self.subTest(asset=source.name):
65
84
  self.assertTrue(target.is_file(), f"{source.name} has no zh mirror")
@@ -114,6 +133,8 @@ class ReportSingleSourceTest(unittest.TestCase):
114
133
  expected = {
115
134
  "setReportColumns": "columns",
116
135
  "addReportColumns": "columns",
136
+ # Single-leaf deletion has its own minimal payload: one exact fieldKey, never a column set.
137
+ "removeReportColumn": "columnRemoval",
117
138
  "setReportFilters": "filters",
118
139
  "setReportSorts": "sorts",
119
140
  "registerReportPlugin": "plugin",
@@ -69,6 +69,7 @@ def test_timeprop_hints_survive_single_and_batch_handoff_without_false_blocking(
69
69
  source = project_root / "src" / "main" / "java" / "demo" / "Booking.java"
70
70
  source.parent.mkdir(parents=True)
71
71
  source.write_text(
72
+ 'package demo;\n'
72
73
  'class Booking {\n'
73
74
  ' private static final String START = "start_time";\n'
74
75
  ' void check(DynamicObject row) { row.getDate(START); }\n'
@@ -196,10 +197,10 @@ BUILD FAILED
196
197
 
197
198
  assert errors == [
198
199
  {"file": "/tmp/DailyTask.java", "line": 7,
199
- "message": "程序包kd.bos.datahelper不存在", "detail": []},
200
+ "message": "程序包kd.bos.datahelper不存在", "detail": ["import kd.bos.datahelper.BusinessDataServiceHelper;", "^"]},
200
201
  {"file": "/tmp/DailyTask.java", "line": 14,
201
202
  "message": "cannot find symbol",
202
- "detail": ["symbol: variable SaveServiceHelper", "location: class DailyTask"]},
203
+ "detail": ["SaveServiceHelper.save(values);", "^", "symbol: variable SaveServiceHelper", "location: class DailyTask"]},
203
204
  ]
204
205
 
205
206
 
@@ -399,6 +400,87 @@ def test_compile_refuses_a_qfilter_operator_written_as_a_string_literal(tmp_path
399
400
  assert not (project_root / ".temp").exists()
400
401
 
401
402
 
403
+ def test_compile_refuses_swapped_operation_arguments_even_without_javac(tmp_path, monkeypatch):
404
+ project = tmp_path / "module"
405
+ source = project / "src" / "main" / "java" / "demo" / "DeleteFlock.java"
406
+ source.parent.mkdir(parents=True)
407
+ monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda write_derived: {
408
+ "status": "disabled", "compile": False,
409
+ })
410
+ for option in ("", ", OperateOption.create()"):
411
+ source.write_text('''package demo;
412
+ import kd.bos.servicehelper.BusinessDataServiceHelper;
413
+ import kd.bos.servicehelper.operation.OperationServiceHelper;
414
+ import kd.bos.dataentity.entity.DynamicObject;
415
+ ''' + ('import kd.bos.dataentity.OperateOption;\n' if option else '') + '''class DeleteFlock {
416
+ private static final String FLOCK_ENTITY = "demo_flock";
417
+ void run(Object id) {
418
+ DynamicObject flk = BusinessDataServiceHelper.loadSingle(id, FLOCK_ENTITY);
419
+ OperationServiceHelper.executeOperate(FLOCK_ENTITY, "delete", new DynamicObject[]{flk}''' + option + ''');
420
+ }
421
+ }
422
+ ''', encoding="utf-8")
423
+ result = compile_plugin_source.compile_source(
424
+ project, source, "DeleteFlock", 30, app_root=tmp_path / "app",
425
+ plugin_type="operation", form_number="demo_receipt", operation_keys=["delete"],
426
+ description="删除关联记录",
427
+ )
428
+ assert result["status"] is False, result
429
+ assert "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER" in result["errorCodes"]
430
+ assert not (tmp_path / "app" / "archive.json").exists()
431
+ assert not (project / ".temp").exists()
432
+
433
+
434
+ def test_unverified_operation_overload_cannot_be_archived_when_compilation_is_disabled(tmp_path, monkeypatch):
435
+ project = tmp_path / "module"
436
+ source = project / "src" / "main" / "java" / "demo" / "OperationCall.java"
437
+ source.parent.mkdir(parents=True)
438
+ monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda write_derived: {
439
+ "status": "disabled", "compile": False,
440
+ })
441
+ for option, expected in (("", False), (", option", True)):
442
+ source.write_text('''package demo;
443
+ import kd.bos.servicehelper.operation.OperationServiceHelper;
444
+ import kd.bos.dataentity.OperateOption;
445
+ class OperationCall {
446
+ Object run(Object id, OperateOption option) {
447
+ return OperationServiceHelper.executeOperate("delete", "demo_flock", new Object[]{id}''' + option + ''');
448
+ }
449
+ }''', encoding="utf-8")
450
+ result = compile_plugin_source.compile_source(
451
+ project, source, "OperationCall", 30, app_root=tmp_path / "app",
452
+ plugin_type="operation", form_number="demo_receipt", operation_keys=["delete"],
453
+ description="关联记录删除",
454
+ )
455
+ assert result["status"] is expected, result
456
+ if not expected:
457
+ assert result["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"
458
+ assert not (tmp_path / "app" / "archive.json").exists()
459
+ assert not (project / ".temp").exists()
460
+ continue
461
+ assert result["compileSkipped"] is True
462
+ assert result["loadingVerification"]["runtimeVerified"] is False
463
+
464
+
465
+ def test_operation_contract_cli_blocks_proven_swap_and_reports_unknown_overload(tmp_path):
466
+ source = tmp_path / "OperationCall.java"
467
+ source.write_text('''import kd.bos.servicehelper.BusinessDataServiceHelper;
468
+ import kd.bos.servicehelper.operation.OperationServiceHelper;
469
+ class OperationCall {
470
+ void run(Object id) {
471
+ Object flk = BusinessDataServiceHelper.loadSingle(id, "demo_flock");
472
+ OperationServiceHelper.executeOperate("demo_flock", "delete", new Object[]{id});
473
+ }
474
+ }''', encoding="utf-8")
475
+ run = subprocess.run([sys.executable, str(SCRIPTS / "platform_api_contract.py"),
476
+ "--source-path", str(source)], capture_output=True, text=True)
477
+ result = json.loads(run.stdout)
478
+ assert run.returncode == 2
479
+ assert result["errorCode"] == "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER"
480
+ assert result["issues"][0]["line"] == 6
481
+ assert result["sourceWarnings"][0]["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"
482
+
483
+
402
484
  def test_compile_refuses_a_member_name_that_does_not_exist(tmp_path, monkeypatch):
403
485
  project_root = tmp_path / "module"
404
486
  source = project_root / "src" / "main" / "java" / "demo" / "EntryCountFormPlugin.java"
@@ -574,10 +656,11 @@ def test_compile_accepts_the_registered_return_type(tmp_path, monkeypatch):
574
656
  "import kd.bos.dataentity.metadata.dynamicobject.DynamicObjectType;\n"
575
657
  "import kd.bos.servicehelper.BusinessDataServiceHelper;\n"
576
658
  "public class Task {\n"
577
- " public void run(Object[] pks, DynamicObjectType type) {\n"
659
+ " public Object[] run(Object[] pks, DynamicObjectType type) {\n"
578
660
  " DynamicObject[] rows = BusinessDataServiceHelper.load(pks, type);\n"
579
661
  " Map<Object, DynamicObject> cached = BusinessDataServiceHelper.loadFromCache(pks, type);\n"
580
662
  " DynamicObject one = BusinessDataServiceHelper.loadSingle(pks[0], type);\n"
663
+ " return new Object[] {rows, cached, one};\n"
581
664
  " }\n"
582
665
  "}\n",
583
666
  encoding="utf-8",
@@ -596,6 +679,78 @@ def test_compile_accepts_the_registered_return_type(tmp_path, monkeypatch):
596
679
  assert result["compileSkipped"] is True
597
680
 
598
681
 
682
+ def test_compile_refuses_what_the_dcs_deployment_gate_would_reject(tmp_path, monkeypatch):
683
+ """DCS rejects the packaged module after delivery; the same rules stop it here, before archive."""
684
+ project_root = tmp_path / "module"
685
+ source = project_root / "src" / "main" / "java" / "demo" / "Sync.java"
686
+ app_root = tmp_path / "app" / "uhyl_demo"
687
+ source.parent.mkdir(parents=True)
688
+ app_root.mkdir(parents=True)
689
+ body = (
690
+ "package demo;\n"
691
+ "import java.util.HashMap;\n"
692
+ "import java.util.Map;\n"
693
+ "public class Sync {\n"
694
+ " private static final Map<String, Object> CACHE = new HashMap<>();\n"
695
+ " public Object read(String key) {\n"
696
+ " %s\n"
697
+ " return CACHE.get(key);\n"
698
+ " }\n"
699
+ "}\n"
700
+ )
701
+ source.write_text(body % "System.out.println(key);", encoding="utf-8")
702
+ monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda write_derived: {
703
+ "status": "disabled", "compile": False,
704
+ })
705
+
706
+ def compile_it():
707
+ return compile_plugin_source.compile_source(
708
+ project_root, source, "Sync", 30,
709
+ app_root=app_root, plugin_type="task", form_number="uhyl_demo",
710
+ operation_keys=[], description="sync cache",
711
+ )
712
+
713
+ result = compile_it()
714
+
715
+ assert result["status"] is False
716
+ assert result["errorCode"] == "PLUGIN_SOURCE_DCS_RULE_VIOLATION"
717
+ codes = {code for issue in result["issues"] for code in issue["ruleCodes"]}
718
+ assert {"CVE-KD-0056-02", "java:S106"} <= codes
719
+ assert all(issue["fix"] and issue["dcsRisk"] for issue in result["issues"])
720
+ assert not (app_root / "archive.json").exists()
721
+ # Commented-out code is advisory: it is reported but does not stop delivery.
722
+ source.write_text((body % "// return CACHE.get(key.trim());").replace(
723
+ " private static final Map<String, Object> CACHE = new HashMap<>();\n", "").replace(
724
+ "import java.util.HashMap;\nimport java.util.Map;\n", "").replace(
725
+ "return CACHE.get(key);", "return key;"), encoding="utf-8")
726
+ result = compile_it()
727
+ assert result["status"] is True
728
+ assert [item["errorCode"] for item in result["sourceWarnings"]] == ["PLUGIN_SOURCE_DCS_RULE_ADVISORY"]
729
+ assert result["sourceWarnings"][0]["ruleCodes"] == ["java:S125"]
730
+
731
+
732
+ def test_new_spreadsheet_rules_stop_delivery_before_archive_with_compile_disabled(tmp_path, monkeypatch):
733
+ project = tmp_path / "module"
734
+ source = project / "src" / "main" / "java" / "demo" / "Task.java"
735
+ source.parent.mkdir(parents=True)
736
+ source.write_text(
737
+ 'package demo;\nimport kd.bos.servicehelper.BusinessDataServiceHelper;\n'
738
+ 'public class Task {\n'
739
+ ' public void run(Object[] ids) {\n'
740
+ ' for (Object id : ids) { BusinessDataServiceHelper.loadSingle(id); }\n'
741
+ ' }\n}\n', encoding="utf-8")
742
+ monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: {"status": "disabled", "compile": False})
743
+ result = compile_plugin_source.compile_source(
744
+ project, source, "Task", 30, app_root=tmp_path / "app", plugin_type="task",
745
+ form_number="demo", operation_keys=[], description="check new DCS rules")
746
+ assert result["status"] is False
747
+ assert "PLUGIN_SOURCE_DCS_RULE_VIOLATION" in result["errorCodes"]
748
+ codes = {code for issue in result["issues"] for code in issue.get("ruleCodes", [])}
749
+ assert "CVE-KD-0051-47" in codes
750
+ assert not (tmp_path / "app" / "archive.json").exists()
751
+ assert not (project / ".temp").exists()
752
+
753
+
599
754
  def test_the_compile_skipped_result_names_what_the_source_gates_do_not_cover(tmp_path, monkeypatch):
600
755
  """`compileSkipped=true` archives and registers with nothing having compiled the source, so the
601
756
  result has to say which classes of error the four gates cannot reach."""
@@ -795,7 +950,9 @@ def test_batch_builds_one_module_once_and_keeps_full_diagnostics_in_one_log(tmp_
795
950
  assert "--rerun-tasks" not in calls[0]
796
951
  assert len(result["results"]) == 6
797
952
  assert all(item["status"] is False for item in result["results"])
798
- assert sum(len(item.get("diagnostics", [])) for item in result["results"]) == 5
953
+ assert all(any(error["file"].endswith(item["className"] + ".java") for error in item["diagnostics"]) for item in result["results"])
954
+ assert all(item["diagnosticCount"] == 60 for item in result["results"])
955
+ assert all(len(item["diagnostics"]) == 5 for item in result["results"])
799
956
  paths = {item["logPath"] for item in result["results"]}
800
957
  assert len(paths) == 1
801
958
  assert Path(next(iter(paths))).read_text() == diagnostics
@@ -870,3 +1027,129 @@ def test_compile_consumes_handoff_without_retyped_identity(tmp_path, monkeypatch
870
1027
  archive = json.loads((tmp_path / "app" / "archive.json").read_text())
871
1028
  assert archive["javas"][0]["className"] == "Example"
872
1029
  assert archive["javas"][0]["filePath"] == str(source.resolve())
1030
+
1031
+
1032
+ _REPORT_FORM_SOURCE = '''package sample;
1033
+ import java.util.List;
1034
+ import kd.bos.form.events.HyperLinkClickListener;
1035
+ import kd.bos.report.events.SortAndFilterEvent;
1036
+ import kd.bos.report.plugin.AbstractReportFormPlugin;
1037
+ public class KpiFormPlugin extends AbstractReportFormPlugin implements HyperLinkClickListener {
1038
+ @Override
1039
+ public void setSortAndFilter(List<SortAndFilterEvent> allColumns) {
1040
+ super.setSortAndFilter(allColumns);
1041
+ for (SortAndFilterEvent column : allColumns) {
1042
+ column.setFilter(true);
1043
+ }
1044
+ }
1045
+ %s}
1046
+ '''
1047
+ _REPORT_DATA_SOURCE = '''package sample;
1048
+ import java.util.ArrayList;
1049
+ import java.util.List;
1050
+ import kd.bos.algo.DataSet;
1051
+ import kd.bos.entity.report.AbstractReportListDataPlugin;
1052
+ import kd.bos.entity.report.ReportQueryParam;
1053
+ import kd.bos.orm.query.QFilter;
1054
+ import kd.bos.servicehelper.QueryServiceHelper;
1055
+ public class KpiDataPlugin extends AbstractReportListDataPlugin {
1056
+ @Override
1057
+ public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable {
1058
+ List<QFilter> filters = new ArrayList<>(queryParam.getFilter().getQFilters());
1059
+ %s return QueryServiceHelper.queryDataSet("kpi", "uhyl_kpi", "id", filters.toArray(new QFilter[filters.size()]), null);
1060
+ }
1061
+ }
1062
+ '''
1063
+
1064
+
1065
+ def _report_source(tmp_path, name, text):
1066
+ source = tmp_path / "src" / "main" / "java" / "sample" / f"{name}.java"
1067
+ source.parent.mkdir(parents=True, exist_ok=True)
1068
+ source.write_text(text, encoding="utf-8")
1069
+ return source
1070
+
1071
+
1072
+ def _compile_report(tmp_path, source, name, plugin_type):
1073
+ return compile_plugin_source.compile_source(
1074
+ tmp_path, source, name, 10, app_root=tmp_path / "app", plugin_type=plugin_type,
1075
+ form_number="uhyl_kpi_rpt", operation_keys=[], description="report plugin")
1076
+
1077
+
1078
+ def test_compile_refuses_a_report_drill_down_that_never_registers_its_listener(tmp_path, monkeypatch):
1079
+ """The defect compiles and registers; only the gate can say the link will never fire."""
1080
+ monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: {"status": "disabled"})
1081
+ source = _report_source(tmp_path, "KpiFormPlugin", _REPORT_FORM_SOURCE % (
1082
+ " @Override\n"
1083
+ " public void hyperLinkClick(kd.bos.form.events.HyperLinkClickEvent evt) { }\n"))
1084
+
1085
+ result = _compile_report(tmp_path, source, "KpiFormPlugin", "reportform")
1086
+
1087
+ assert result["status"] is False
1088
+ assert result["errorCode"] == "PLUGIN_SOURCE_LISTENER_UNREGISTERED"
1089
+ assert result["issues"][0]["requiredCall"] == "addHyperClickListener"
1090
+ assert not (tmp_path / "app" / "archive.json").exists()
1091
+
1092
+
1093
+ def test_head_filtering_without_its_data_half_is_warned_by_whichever_plugin_compiles_second(
1094
+ tmp_path, monkeypatch):
1095
+ monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: {"status": "disabled"})
1096
+ data = _report_source(tmp_path, "KpiDataPlugin", _REPORT_DATA_SOURCE % "")
1097
+ form = _report_source(tmp_path, "KpiFormPlugin", _REPORT_FORM_SOURCE % "")
1098
+
1099
+ first = _compile_report(tmp_path, data, "KpiDataPlugin", "reportlist")
1100
+ second = _compile_report(tmp_path, form, "KpiFormPlugin", "reportform")
1101
+
1102
+ assert first["status"] is True and first["sourceWarnings"] == []
1103
+ assert second["status"] is True
1104
+ assert [item["errorCode"] for item in second["sourceWarnings"]] == [
1105
+ "PLUGIN_REPORT_HEAD_FILTER_UNAPPLIED"]
1106
+ assert "KpiDataPlugin" in second["sourceWarnings"][0]["message"]
1107
+ # The data plugin compiled again after the form plugin sees the same gap from its side.
1108
+ again = _compile_report(tmp_path, data, "KpiDataPlugin", "reportlist")
1109
+ assert [item["errorCode"] for item in again["sourceWarnings"]] == [
1110
+ "PLUGIN_REPORT_HEAD_FILTER_UNAPPLIED"]
1111
+ assert "KpiFormPlugin" in again["sourceWarnings"][0]["message"]
1112
+ # Applying the header conditions closes it on both sides.
1113
+ data.write_text(_REPORT_DATA_SOURCE % (
1114
+ " filters.addAll(queryParam.getFilter().getHeadFilters());\n"), encoding="utf-8")
1115
+ assert _compile_report(tmp_path, data, "KpiDataPlugin", "reportlist")["sourceWarnings"] == []
1116
+ assert _compile_report(tmp_path, form, "KpiFormPlugin", "reportform")["sourceWarnings"] == []
1117
+
1118
+
1119
+ def test_stale_handoff_fails_before_archiving_or_advancing_source_gate(tmp_path, monkeypatch):
1120
+ import hashlib
1121
+ import pytest
1122
+ input_file = tmp_path / 'intent.json'
1123
+ input_file.write_text('{"coreLogic":["old"]}')
1124
+ handoff = tmp_path / 'handoff.json'
1125
+ handoff.write_text(json.dumps({'status': True, 'handoff': {}, 'inputFiles': [
1126
+ {'path': str(input_file), 'sha256': hashlib.sha256(input_file.read_bytes()).hexdigest()}]}))
1127
+ input_file.write_text('{"coreLogic":["new"]}')
1128
+ with pytest.raises(compile_plugin_source.CompileError) as error:
1129
+ compile_plugin_source.compile_handoff(str(handoff))
1130
+ assert error.value.code == 'PLUGIN_HANDOFF_STALE'
1131
+ assert not (tmp_path / 'archive.json').exists()
1132
+ assert not (tmp_path / 'output' / 'generated').exists()
1133
+
1134
+
1135
+ def test_batch_checks_all_sources_before_any_build_or_archive(tmp_path, monkeypatch, capsys):
1136
+ project = tmp_path / "module"
1137
+ java = project / "src" / "main" / "java" / "demo"
1138
+ java.mkdir(parents=True)
1139
+ app = tmp_path / "app"
1140
+ good = java / "Good.java"
1141
+ bad = java / "Bad.java"
1142
+ good.write_text("package demo; public class Good {}\n")
1143
+ bad.write_text("package demo; public class Bad { // L3-BLOCK: unresolved requirement\n}\n")
1144
+ batch = tmp_path / "sources.json"
1145
+ batch.write_text(json.dumps([{"sourcePath": str(source), "className": source.stem, "type": "task",
1146
+ "formNumber": "example", "description": "scheduled task"}
1147
+ for source in (good, bad)]))
1148
+ monkeypatch.setattr(sys, "argv", ["compile_plugin_source.py", "--project-root", str(project),
1149
+ "--app-root", str(app), "--sources-file", str(batch)])
1150
+ monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: (_ for _ in ()).throw(AssertionError("build was started")))
1151
+ assert compile_plugin_source.main() == 2
1152
+ result = json.loads(capsys.readouterr().out)
1153
+ assert [item["errorCode"] for item in result["results"]] == ["PLUGIN_BATCH_PREFLIGHT_BLOCKED", "PLUGIN_SOURCE_PLACEHOLDER_BLOCKED"]
1154
+ assert not (app / "archive.json").exists()
1155
+ assert not (project / ".temp").exists()
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+ import json
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ SCRIPT = Path(__file__).resolve().parents[1] / "configure_list.py"
12
+ spec = importlib.util.spec_from_file_location("configure_list_delivery", SCRIPT)
13
+ assert spec and spec.loader
14
+ DRIVER = importlib.util.module_from_spec(spec)
15
+ spec.loader.exec_module(DRIVER)
16
+
17
+
18
+ @pytest.fixture
19
+ def children(tmp_path, monkeypatch):
20
+ root = tmp_path / "应用目录"
21
+ root.mkdir()
22
+ calls = tmp_path / "calls.jsonl"
23
+ api = tmp_path / "native_api.py"
24
+ payload = tmp_path / "payload.json"
25
+ rendered = tmp_path / "rendered.txt"
26
+ api.write_text("import sys,json\nfrom pathlib import Path\n"
27
+ f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(['configure',sys.argv[1:]])+'\\n')\n"
28
+ f"value=json.loads(Path({str(payload)!r}).read_text(encoding='utf-8'))\n"
29
+ "Path(sys.argv[sys.argv.index('--output')+1]).write_text(json.dumps(value,ensure_ascii=False),encoding='utf-8')\n"
30
+ "raise SystemExit(0 if value['status'] else 3)\n", encoding="utf-8")
31
+ renderer = tmp_path / "render_output_cards.py"
32
+ renderer.write_text("import sys,json\nfrom pathlib import Path\n"
33
+ f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(['render',sys.argv[1:]])+'\\n')\n"
34
+ f"print(Path({str(rendered)!r}).read_text(encoding='utf-8'))\n", encoding="utf-8")
35
+ monkeypatch.setattr(DRIVER, "API", api)
36
+ monkeypatch.setattr(DRIVER, "SCRIPTS", tmp_path)
37
+ return root, payload, rendered, calls
38
+
39
+
40
+ def receipt(root, outcome):
41
+ return {"status": outcome in {"verified", "verified_noop"}, "errorCode": "0" if outcome in {"verified", "verified_noop"} else "TEST_REJECTION",
42
+ "writeAttempted": outcome == "verified", "mutation": {"outcome": outcome, "writeAttempted": outcome == "verified"},
43
+ "verification": {"verified": outcome in {"verified", "verified_noop"}, "columns": [{"fieldKey": "count", "properties": {
44
+ "Summary": {"requested": 0, "actual": 0, "matched": True},
45
+ "ZeroShow": {"requested": False, "actual": False, "matched": True}}}]},
46
+ "data": {"formNumber": "exact_form", "appRoot": str(root), "structuralOps": 0,
47
+ "propertyWrites": int(outcome == "verified"),
48
+ "completion": {"requiresArtifactFinalization": outcome == "verified"}}}
49
+
50
+
51
+ def test_verified_update_combines_native_delivery_preserves_assertions_and_tracked_sync(children):
52
+ root, payload, rendered, calls = children
53
+ expected = receipt(root, "verified")
54
+ payload.write_text(json.dumps(expected))
55
+ block = ':::render:kdform\n{"title":"测试表","url":"https://example.test"}\n:::'
56
+ sync = 'DYM_BACKGROUND_REQUEST={"command":["python3","worker.py"],"run_in_background":true,"preview":false}'
57
+ rendered.write_text(block + "\n" + sync, encoding="utf-8")
58
+ value = '{"columns":[{"fieldKey":"a\' $()","properties":{"ZeroShow":false}}]}'
59
+ code, output = DRIVER.execute(str(root), None, "用户显示名", value)
60
+ assert code == 0 and output.startswith(block + "\n") and sync in output
61
+ result = json.loads(output.split("RESULT_SUMMARY=", 1)[1])
62
+ assert result["verification"] == expected["verification"]
63
+ assert result["verifiedPropertyCount"] == 2
64
+ assert result["completion"]["requiresArtifactFinalization"] is False
65
+ assert result["data"]["completion"] == result["completion"]
66
+ assert result["completion"]["nextAction"] == "launch_returned_background_request_then_respond"
67
+ observed = [json.loads(line) for line in calls.read_text().splitlines()]
68
+ assert [row[0] for row in observed] == ["configure", "render"]
69
+ assert observed[0][1][observed[0][1].index("--input") + 1] == value
70
+ assert observed[1][1][observed[1][1].index("--form-number") + 1] == "exact_form"
71
+ assert observed[1][1][observed[1][1].index("--app-root") + 1] == str(root)
72
+ # Exercise the actual unchanged host auto-arm predicate, not a copied regex.
73
+ source = SCRIPT.parents[3] / "src" / "tool" / "bash.ts"
74
+ output_path = root / "driver-output.txt"
75
+ output_path.write_text(output, encoding="utf-8")
76
+ run = subprocess.run(["bun", "-e", "const {isSuccessfulRenderOutput}=await import(process.argv[1]); process.stdout.write(JSON.stringify(isSuccessfulRenderOutput(await Bun.file(process.argv[2]).text(),0)))", str(source), str(output_path)], capture_output=True, text=True, timeout=30)
77
+ assert run.returncode == 0, run.stderr
78
+ assert json.loads(run.stdout) is True
79
+
80
+
81
+ @pytest.mark.parametrize("outcome", ["verified_noop", "failed_preflight"])
82
+ def test_noop_and_refusal_end_without_old_cards_or_delivery(children, outcome):
83
+ root, payload, _rendered, calls = children
84
+ expected = receipt(root, outcome)
85
+ payload.write_text(json.dumps(expected))
86
+ (root / "archive.json").write_text(json.dumps({"artifacts": [{"title": "OLD_CARD"}]}))
87
+ code, output = DRIVER.execute(str(root), None, "display", '{}')
88
+ result = json.loads(output)
89
+ assert code == (0 if outcome == "verified_noop" else 3)
90
+ assert ":::render:" not in output and "OLD_CARD" not in output and "DYM_BACKGROUND_REQUEST" not in output
91
+ assert result["verification"] == expected["verification"]
92
+ assert result["completion"]["requiresArtifactFinalization"] is False
93
+ assert result["data"]["completion"] == result["completion"]
94
+ assert [json.loads(line)[0] for line in calls.read_text().splitlines()] == ["configure"]
95
+
96
+
97
+ def test_render_failure_exposes_delivery_only_retry_without_replaying_write(children):
98
+ root, payload, rendered, calls = children
99
+ payload.write_text(json.dumps(receipt(root, "verified")))
100
+ rendered.write_text("No card was produced")
101
+ code, output = DRIVER.execute(str(root), None, "display", '{}')
102
+ result = json.loads(output)
103
+ assert code == 3 and result["verification"]["verified"] is True
104
+ assert result["status"] is False and result["errorCode"] == "LIST_DELIVERY_PENDING"
105
+ assert result["configurationVerified"] is True
106
+ assert result["completion"]["nextAction"] == "retry_delivery_only"
107
+ assert "render_output_cards.py" in result["completion"]["command"][1]
108
+ assert [json.loads(line)[0] for line in calls.read_text().splitlines()].count("configure") == 1
109
+
110
+
111
+ def test_unknown_write_state_never_renders_or_replays(children):
112
+ root, payload, _rendered, calls = children
113
+ payload.write_text(json.dumps(receipt(root, "unknown_after_write")))
114
+ code, output = DRIVER.execute(str(root), None, "display", '{}')
115
+ assert code == 3 and json.loads(output)["completion"]["nextAction"] == "reconcile_write_state"
116
+ assert [json.loads(line)[0] for line in calls.read_text().splitlines()] == ["configure"]
117
+
118
+
119
+ def test_native_preflight_receipt_without_duplicate_top_level_flag_still_reports_zero_write(children):
120
+ root, payload, _rendered, _calls = children
121
+ value = receipt(root, "failed_preflight")
122
+ value.pop("writeAttempted")
123
+ value["data"]["writeAttempted"] = False
124
+ payload.write_text(json.dumps(value))
125
+ code, output = DRIVER.execute(str(root), None, "display", '{}')
126
+ assert code == 3 and json.loads(output)["writeAttempted"] is False
127
+
128
+
129
+ def test_list_executables_remain_identical_in_localized_package():
130
+ root = SCRIPT.parents[1]
131
+ for relative in ["scripts/configure_list.py", "skills/cosmic-meta-api/scripts/_core/cmd_listui.py"]:
132
+ assert (root / relative).read_bytes() == (root / ".locales/zh" / relative).read_bytes()