@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.
- package/package.json +1 -1
- package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
- package/skills/cosmic-build/SKILL.md +24 -9
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
- package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
- package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
- package/skills/cosmic-build/references/compile-guide.md +36 -1
- package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
- package/skills/cosmic-build/references/delivery.md +16 -2
- package/skills/cosmic-build/references/execution-contract.md +15 -3
- package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
- package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
- package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
- package/skills/cosmic-build/references/report-filter-contract.md +53 -15
- package/skills/cosmic-build/references/sdk-inspection.md +4 -1
- package/skills/cosmic-build/references/type-registry.json +26 -17
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
- package/skills/cosmic-build/scripts/_http_client.py +3 -0
- package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
- package/skills/cosmic-build/scripts/configure_list.py +112 -0
- package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
- package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
- package/skills/cosmic-build/scripts/execution_binding.py +25 -0
- package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
- package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
- package/skills/cosmic-build/scripts/register_plugins.py +27 -10
- package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
- package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
- package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
- package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
- package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
- package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
- package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
- package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
- package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
- package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
- package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
- package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
- package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
- package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
- package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
- package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
- package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
- package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
- package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
- package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
- package/skills/cosmic-build/scripts/test_environment.py +67 -1
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
- package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
- package/skills/cosmic-dev-best-practices/references/README.md +2 -2
- package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
- package/skills/erp-analysis/references/delivery-contract.md +2 -1
- package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
|
@@ -86,7 +86,7 @@ def _is_standard(isv: str, standard: bool | None = None) -> bool:
|
|
|
86
86
|
"""Determine whether to use standard naming rules (no ISV prefix).
|
|
87
87
|
|
|
88
88
|
本地补丁 #16:命名规则强制标准模式(preset 作者 2026-08-27)——原判定整段停用。
|
|
89
|
-
|
|
89
|
+
上游逻辑保留在下方注释,便于对照与回退。
|
|
90
90
|
"""
|
|
91
91
|
# 上游原逻辑(已停用):
|
|
92
92
|
# if standard is not None:
|
|
@@ -39,7 +39,7 @@ if str(_ROOT_SCRIPTS) not in sys.path:
|
|
|
39
39
|
sys.path.insert(0, str(_ROOT_SCRIPTS))
|
|
40
40
|
|
|
41
41
|
from kd_env_loader import load_effective_skill_config, get_locale
|
|
42
|
-
from kd_auth import get_token
|
|
42
|
+
from kd_auth import get_token, make_auth_headers
|
|
43
43
|
from _http_client import DEFAULT_USER_AGENT, with_default_user_agent, get_ssl_context
|
|
44
44
|
|
|
45
45
|
|
|
@@ -64,14 +64,11 @@ def do_auth(cfg):
|
|
|
64
64
|
return token
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def _api_get(
|
|
68
|
-
url = base_url.rstrip("/") + path
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"Content-Type": "application/json",
|
|
73
|
-
"User-Agent": DEFAULT_USER_AGENT,
|
|
74
|
-
})
|
|
67
|
+
def _api_get(cfg, path, access_token):
|
|
68
|
+
url = cfg["base_url"].rstrip("/") + path
|
|
69
|
+
headers = make_auth_headers(cfg, access_token, style="legacy_api",
|
|
70
|
+
content_type="application/json")
|
|
71
|
+
req = urllib.request.Request(url, headers=headers)
|
|
75
72
|
with urllib.request.urlopen(req, timeout=60, context=get_ssl_context()) as resp:
|
|
76
73
|
return json.loads(resp.read().decode("utf-8"))
|
|
77
74
|
|
|
@@ -84,13 +81,17 @@ def _resolve_cfg(raw, env="server"):
|
|
|
84
81
|
"""Compatible with nested config (server/pdm sub-nodes) and flat config"""
|
|
85
82
|
section = raw.get(env, raw)
|
|
86
83
|
return {
|
|
84
|
+
# Pass the whole section through: kd_auth routes on normalAccessToken and also
|
|
85
|
+
# reads loginUser / userType / acgwIdentity
|
|
86
|
+
**section,
|
|
87
87
|
"base_url": section.get("base_url") or section.get("baseUrl", ""),
|
|
88
88
|
"app_id": section.get("app_id") or section.get("appId", ""),
|
|
89
89
|
"app_secret": section.get("app_secret") or section.get("appSecret", ""),
|
|
90
90
|
"tenant_id": section.get("tenant_id") or section.get("tenantId", ""),
|
|
91
91
|
"account_id": section.get("account_id") or section.get("accountId", ""),
|
|
92
92
|
"login_user": section.get("login_user") or section.get("user", ""),
|
|
93
|
-
"user_type": section.get("user_type") or section.get("
|
|
93
|
+
"user_type": section.get("user_type") or section.get("userType") or "Mobile",
|
|
94
|
+
"normalAccessToken": section.get("normalAccessToken") is True,
|
|
94
95
|
"language": section.get("language", "zh_CN"),
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -114,7 +115,7 @@ class MetaExportClient:
|
|
|
114
115
|
query = "&".join(f"{k}={v}" for k, v in params.items())
|
|
115
116
|
path = f"/kapi/v2/devportal/ai-meta/getMetadataExportFiles?{query}"
|
|
116
117
|
|
|
117
|
-
resp = _api_get(self._cfg
|
|
118
|
+
resp = _api_get(self._cfg, path, self._get_token())
|
|
118
119
|
error_code = resp.get("errorCode")
|
|
119
120
|
if error_code and error_code != "0":
|
|
120
121
|
raise RuntimeError(
|
|
@@ -176,6 +176,12 @@ class SpecDeriver:
|
|
|
176
176
|
"op": "fieldProperty",
|
|
177
177
|
},
|
|
178
178
|
))
|
|
179
|
+
if field_key and fld.get("comboOptions"):
|
|
180
|
+
specs.append(AssertionSpec(type="combo_options_equal", target=field_key,
|
|
181
|
+
expected=fld["comboOptions"], metadata={"fieldType": field_type}))
|
|
182
|
+
if field_key and fld.get("unitFieldKey"):
|
|
183
|
+
specs.append(AssertionSpec(type="unit_binding_equals", target=field_key,
|
|
184
|
+
expected=fld["unitFieldKey"], metadata={"fieldType": field_type}))
|
|
179
185
|
for setting in artifact.get("propertySettings") or []:
|
|
180
186
|
if not isinstance(setting, dict):
|
|
181
187
|
continue
|
|
@@ -460,6 +466,8 @@ class Verifier:
|
|
|
460
466
|
"field_exists": self._check_field_exists,
|
|
461
467
|
"field_not_exists": self._check_field_not_exists,
|
|
462
468
|
"property_equals": self._check_property_equals,
|
|
469
|
+
"combo_options_equal": self._check_field_configuration,
|
|
470
|
+
"unit_binding_equals": self._check_field_configuration,
|
|
463
471
|
"element_exists": self._check_element_exists,
|
|
464
472
|
"element_not_exists": self._check_element_not_exists,
|
|
465
473
|
"element_parent": self._check_element_parent,
|
|
@@ -765,6 +773,40 @@ class Verifier:
|
|
|
765
773
|
message=f"Field '{target}' still exists in the form structure (expected to be deleted)",
|
|
766
774
|
)
|
|
767
775
|
|
|
776
|
+
def _check_field_configuration(self, spec, form_id, form_number):
|
|
777
|
+
metadata = self._get_full_metadata(form_number)
|
|
778
|
+
if "_error" in metadata:
|
|
779
|
+
return VerifyResult(spec, None, "error", f"loadFullMetadata failed: {metadata['_error']}")
|
|
780
|
+
items = (metadata.get("entitymeta") or {}).get("Items") or []
|
|
781
|
+
matches = [item for item in items if isinstance(item, dict) and item.get("Key") == spec.target]
|
|
782
|
+
if len(matches) != 1:
|
|
783
|
+
return VerifyResult(spec, None, "error", f"Expected one exact field {spec.target}, found {len(matches)}")
|
|
784
|
+
field = matches[0]
|
|
785
|
+
if spec.type == "unit_binding_equals":
|
|
786
|
+
units = [item for item in items if isinstance(item, dict) and item.get("_Type_") == "UnitField"
|
|
787
|
+
and item.get("Id") == field.get("UnitFieldId") and field.get("UnitFieldId")]
|
|
788
|
+
actual = units[0].get("Key") if len(units) == 1 else None
|
|
789
|
+
matches = actual == spec.expected and units[0].get("ParentId") == field.get("ParentId") if units else False
|
|
790
|
+
else:
|
|
791
|
+
if spec.metadata.get("fieldType") == "RadioField":
|
|
792
|
+
groups = [item for item in items if isinstance(item, dict) and item.get("Id") == field.get("Group") and field.get("Group")]
|
|
793
|
+
if len(groups) != 1:
|
|
794
|
+
return VerifyResult(spec, None, "error", f"Radio group for {spec.target} is unresolved")
|
|
795
|
+
field = groups[0]
|
|
796
|
+
actual = []
|
|
797
|
+
for index, option in enumerate(field.get("Items") or []):
|
|
798
|
+
if not isinstance(option, dict):
|
|
799
|
+
return VerifyResult(spec, None, "error", f"Invalid option readback for {spec.target}")
|
|
800
|
+
caption = option.get("Caption")
|
|
801
|
+
if isinstance(caption, dict):
|
|
802
|
+
expected_label = spec.expected[index].get("label") if index < len(spec.expected) else None
|
|
803
|
+
caption = expected_label if expected_label in caption.values() else next(iter(caption.values()), "")
|
|
804
|
+
actual.append({"label": str(caption if caption is not None else ""),
|
|
805
|
+
"value": str(option["Value"]) if option.get("Value") is not None else ""})
|
|
806
|
+
matches = actual == spec.expected
|
|
807
|
+
return VerifyResult(spec, actual, "pass" if matches else "fail",
|
|
808
|
+
f"{spec.target}: {spec.type} {'verified' if matches else 'differs from requested configuration'}")
|
|
809
|
+
|
|
768
810
|
def _check_property_equals(self, spec, form_id, form_number):
|
|
769
811
|
if not form_number:
|
|
770
812
|
return VerifyResult(
|
package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py
CHANGED
|
@@ -18,9 +18,9 @@ import cosmic_api # noqa: E402
|
|
|
18
18
|
|
|
19
19
|
@pytest.mark.parametrize("batch", [False, True])
|
|
20
20
|
def test_serve_add_field_uses_verified_destination(batch):
|
|
21
|
-
args = {"formName": "form", "fieldName": "Field", "bizWord": "field", "panel": "panel"}
|
|
21
|
+
args = {"formName": "form", "fieldName": "Field", "bizWord": "field", "fieldType": "TextField", "panel": "panel"}
|
|
22
22
|
if batch:
|
|
23
|
-
args = {"formName": "form", "fields": [{"name": "Field", "bizWord": "field", "panel": "panel"}]}
|
|
23
|
+
args = {"formName": "form", "fields": [{"name": "Field", "bizWord": "field", "fieldType": "TextField", "panel": "panel"}]}
|
|
24
24
|
prepared = {"status": True}
|
|
25
25
|
placements = [{"panel": "resolved_panel"}]
|
|
26
26
|
created = _field_result("field")
|
|
@@ -39,7 +39,7 @@ def test_serve_missing_destination_stops_creation():
|
|
|
39
39
|
"cosmic_api.do_add_fields",
|
|
40
40
|
) as create:
|
|
41
41
|
result = cosmic_api._dispatch_serve({}, "token", "addField", {
|
|
42
|
-
"formName": "form", "fields": [{"name": "Field", "bizWord": "field", "panel": "missing"}],
|
|
42
|
+
"formName": "form", "fields": [{"name": "Field", "bizWord": "field", "fieldType": "TextField", "panel": "missing"}],
|
|
43
43
|
})
|
|
44
44
|
assert result is blocked
|
|
45
45
|
create.assert_not_called()
|
|
@@ -66,13 +66,13 @@ def _field_result(*keys):
|
|
|
66
66
|
|
|
67
67
|
def test_batch_contract_accepts_per_field_placement_and_rejects_two_anchors():
|
|
68
68
|
cosmic_api.validate_add_fields_input([
|
|
69
|
-
{"name": "验收字段A", "bizWord": "acceptanceFieldA", "required": True},
|
|
70
|
-
{"name": "验收字段B", "bizWord": "acceptanceFieldB", "panel": "高级信息"},
|
|
69
|
+
{"name": "验收字段A", "bizWord": "acceptanceFieldA", "fieldType": "TextField", "required": True},
|
|
70
|
+
{"name": "验收字段B", "bizWord": "acceptanceFieldB", "fieldType": "TextField", "panel": "高级信息"},
|
|
71
71
|
])
|
|
72
72
|
|
|
73
73
|
try:
|
|
74
74
|
cosmic_api.validate_add_fields_input([{
|
|
75
|
-
"name": "验收字段B", "bizWord": "acceptanceFieldB",
|
|
75
|
+
"name": "验收字段B", "bizWord": "acceptanceFieldB", "fieldType": "TextField",
|
|
76
76
|
"before": "字段一", "after": "字段二",
|
|
77
77
|
}])
|
|
78
78
|
except ValueError as exc:
|
|
@@ -84,8 +84,8 @@ def test_batch_contract_accepts_per_field_placement_and_rejects_two_anchors():
|
|
|
84
84
|
def test_mixed_batch_does_not_apply_shared_panel_to_unplaced_fields():
|
|
85
85
|
args = SimpleNamespace(
|
|
86
86
|
fields=[
|
|
87
|
-
{"name": "验收字段A", "bizWord": "acceptanceFieldA", "required": True},
|
|
88
|
-
{"name": "验收字段B", "bizWord": "acceptanceFieldB", "panel": "高级信息"},
|
|
87
|
+
{"name": "验收字段A", "bizWord": "acceptanceFieldA", "fieldType": "TextField", "required": True},
|
|
88
|
+
{"name": "验收字段B", "bizWord": "acceptanceFieldB", "fieldType": "TextField", "panel": "高级信息"},
|
|
89
89
|
],
|
|
90
90
|
panel=None, before=None, after=None,
|
|
91
91
|
)
|
package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
8
|
+
import meta_verifier as verifier
|
|
9
|
+
import cosmic_api
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def artifact():
|
|
13
|
+
return {"entities": [{"entityKey": "example", "fields": [
|
|
14
|
+
{"fieldKey": "mode", "dataType": "ComboField", "comboOptions": [
|
|
15
|
+
{"label": "Pending", "value": "0"}, {"label": "Accepted", "value": "accepted"}]},
|
|
16
|
+
{"fieldKey": "quantity", "dataType": "QtyField", "unitFieldKey": "measure"},
|
|
17
|
+
{"fieldKey": "measure", "dataType": "UnitField"},
|
|
18
|
+
]}]}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def metadata():
|
|
22
|
+
return {"entitymeta": {"Items": [
|
|
23
|
+
{"Key": "mode", "_Type_": "ComboField", "Items": [
|
|
24
|
+
{"Caption": {"zh_CN": "待处理", "en_US": "Pending"}, "Value": 0},
|
|
25
|
+
{"Caption": "Accepted", "Value": "accepted"}]},
|
|
26
|
+
{"Key": "quantity", "_Type_": "QtyField", "UnitFieldId": "UNIT", "ParentId": "ENTRY"},
|
|
27
|
+
{"Key": "measure", "_Type_": "UnitField", "Id": "UNIT", "ParentId": "ENTRY"},
|
|
28
|
+
]}}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def configuration_specs():
|
|
32
|
+
return [spec for spec in verifier.SpecDeriver.from_build_artifact(artifact())
|
|
33
|
+
if spec.type in {"combo_options_equal", "unit_binding_equals"}]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_configuration_receipts_use_one_shared_native_read(monkeypatch):
|
|
37
|
+
calls = []
|
|
38
|
+
def read(*args):
|
|
39
|
+
calls.append(args)
|
|
40
|
+
return {"status": True, "data": metadata()}
|
|
41
|
+
monkeypatch.setattr(verifier, "do_load_full_metadata", read)
|
|
42
|
+
report = verifier.Verifier({}, "test").verify("FORM", "example", configuration_specs())
|
|
43
|
+
assert report.status == "pass"
|
|
44
|
+
assert len(calls) == 1
|
|
45
|
+
receipt = cosmic_api._compact_verified_targets(report)
|
|
46
|
+
assert receipt[0]["actual"] == artifact()["entities"][0]["fields"][0]["comboOptions"]
|
|
47
|
+
assert receipt[1]["expected"] == receipt[1]["actual"] == "measure"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize("change", ["stored-value", "label", "order", "extra", "wrong-unit", "other-entry"])
|
|
51
|
+
def test_mismatched_configuration_never_becomes_verified(monkeypatch, change):
|
|
52
|
+
data = metadata()
|
|
53
|
+
items = data["entitymeta"]["Items"]
|
|
54
|
+
if change == "stored-value":
|
|
55
|
+
items[0]["Items"][0]["Value"] = "generated_1"
|
|
56
|
+
if change == "label":
|
|
57
|
+
items[0]["Items"][0]["Caption"] = "Different"
|
|
58
|
+
if change == "order":
|
|
59
|
+
items[0]["Items"].reverse()
|
|
60
|
+
if change == "extra":
|
|
61
|
+
items[0]["Items"].append({"Caption": "Extra", "Value": "extra"})
|
|
62
|
+
if change == "wrong-unit":
|
|
63
|
+
items[1]["UnitFieldId"] = "MISSING"
|
|
64
|
+
if change == "other-entry":
|
|
65
|
+
items[2]["ParentId"] = "OTHER"
|
|
66
|
+
monkeypatch.setattr(verifier, "do_load_full_metadata", lambda *args: {"status": True, "data": data})
|
|
67
|
+
report = verifier.Verifier({}, "test").verify("FORM", "example", configuration_specs())
|
|
68
|
+
assert report.status == "fail"
|
|
69
|
+
assert report.summary["failed"] == 1
|
|
70
|
+
assert len(cosmic_api._compact_verified_targets(report)) == 1
|
|
71
|
+
assert any(result.actual != result.spec.expected or change == "other-entry"
|
|
72
|
+
for result in report.results if result.status == "fail")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_unavailable_readback_is_an_error_and_never_a_pass(monkeypatch):
|
|
76
|
+
monkeypatch.setattr(verifier, "do_load_full_metadata", lambda *args: {"status": False, "message": "Unavailable"})
|
|
77
|
+
report = verifier.Verifier({}, "test").verify("FORM", "example", configuration_specs())
|
|
78
|
+
assert report.status == "partial"
|
|
79
|
+
assert report.summary["errors"] == 2
|
|
80
|
+
assert cosmic_api._compact_verified_targets(report) == []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_requested_shared_unit_is_legal_and_preserved(monkeypatch):
|
|
84
|
+
data = artifact()
|
|
85
|
+
second = copy.deepcopy(data["entities"][0]["fields"][1])
|
|
86
|
+
second["fieldKey"] = "other_quantity"
|
|
87
|
+
data["entities"][0]["fields"].append(second)
|
|
88
|
+
observed = metadata()
|
|
89
|
+
observed["entitymeta"]["Items"].append({"Key": "other_quantity", "_Type_": "QtyField", "UnitFieldId": "UNIT", "ParentId": "ENTRY"})
|
|
90
|
+
monkeypatch.setattr(verifier, "do_load_full_metadata", lambda *args: {"status": True, "data": observed})
|
|
91
|
+
specs = [spec for spec in verifier.SpecDeriver.from_build_artifact(data) if spec.type == "unit_binding_equals"]
|
|
92
|
+
report = verifier.Verifier({}, "test").verify("FORM", "example", specs)
|
|
93
|
+
assert report.status == "pass"
|
|
94
|
+
assert len(report.results) == 2
|
package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py
CHANGED
|
@@ -9,6 +9,9 @@ from unittest.mock import patch
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
11
|
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
12
|
+
PACKAGE = SCRIPTS.parents[2]
|
|
13
|
+
if PACKAGE.parent.name == ".locales":
|
|
14
|
+
PACKAGE = PACKAGE.parent.parent
|
|
12
15
|
sys.path.insert(0, str(SCRIPTS))
|
|
13
16
|
import cosmic_api
|
|
14
17
|
from _core.build_receipts import BuildReceipts
|
|
@@ -17,6 +20,32 @@ from _core.execution_errors import attach_execution_error
|
|
|
17
20
|
from execution_binding import execution_binding, validate_execution_binding
|
|
18
21
|
|
|
19
22
|
|
|
23
|
+
@pytest.mark.parametrize("selected_locale,current_locale", [("", ".locales/zh"), (".locales/zh", "")])
|
|
24
|
+
def test_locale_entry_reuses_exact_selected_source_without_rebinding(tmp_path, selected_locale, current_locale):
|
|
25
|
+
import execution_binding as binding
|
|
26
|
+
package = tmp_path / "installed"
|
|
27
|
+
current = package / current_locale
|
|
28
|
+
selected = package / selected_locale
|
|
29
|
+
entry = selected / "skills/cosmic-meta-api/scripts/cosmic_api.py"
|
|
30
|
+
entry.parent.mkdir(parents=True)
|
|
31
|
+
entry.write_text("# selected runtime")
|
|
32
|
+
app = tmp_path / "app"
|
|
33
|
+
(app / "_runtime").mkdir(parents=True)
|
|
34
|
+
context = app / "_runtime/context.json"
|
|
35
|
+
original = json.dumps({"executionBinding": {"rootDir": str(selected), "revision": "unchanged"}})
|
|
36
|
+
context.write_text(original)
|
|
37
|
+
with patch.object(binding, "__file__", str(current / "scripts/execution_binding.py")):
|
|
38
|
+
assert binding.bound_entrypoint(app, "skills/cosmic-meta-api/scripts/cosmic_api.py") == entry
|
|
39
|
+
with pytest.raises(ValueError, match="SKILL_BOUND_ENTRY_MISSING"):
|
|
40
|
+
binding.bound_entrypoint(app, "skills/missing.py")
|
|
41
|
+
context.write_text(json.dumps({"executionBinding": {"rootDir": str(tmp_path / "another-install")}}))
|
|
42
|
+
assert binding.bound_entrypoint(app, "skills/cosmic-meta-api/scripts/cosmic_api.py") is None
|
|
43
|
+
context.write_text(original)
|
|
44
|
+
with patch.object(binding, "__file__", str(selected / "scripts/execution_binding.py")):
|
|
45
|
+
assert binding.bound_entrypoint(app, "skills/cosmic-meta-api/scripts/cosmic_api.py") is None
|
|
46
|
+
assert context.read_text() == original
|
|
47
|
+
|
|
48
|
+
|
|
20
49
|
@contextmanager
|
|
21
50
|
def _isolated_cli():
|
|
22
51
|
from _core import config, formatters
|
|
@@ -43,6 +72,23 @@ def _artifacts(tmp_path):
|
|
|
43
72
|
return data
|
|
44
73
|
|
|
45
74
|
|
|
75
|
+
def test_cli_forwards_once_and_preserves_selected_runtime_failure(tmp_path, capfd):
|
|
76
|
+
selected = tmp_path / "selected cli.py"
|
|
77
|
+
selected.write_text('import json,sys\nprint(json.dumps({"status":False,"errorCode":"SKILL_REVISION_MISMATCH","argv":sys.argv[1:]}))\nsys.exit(3)\n')
|
|
78
|
+
arguments = ["--stdout", "getProperties", "--app-root", str(tmp_path),
|
|
79
|
+
"--formNumber", "example", "--elementKey", "amount", "--propertyNames", "Scale"]
|
|
80
|
+
with _isolated_cli(), patch.object(sys, "argv", ["cosmic_api.py", *arguments]), \
|
|
81
|
+
patch.object(cosmic_api, "_resolve_app_root_arg", return_value=str(tmp_path)), \
|
|
82
|
+
patch.object(cosmic_api, "bound_entrypoint", return_value=selected), \
|
|
83
|
+
patch.object(cosmic_api, "validate_execution_binding", side_effect=AssertionError("must validate in selected runtime")), \
|
|
84
|
+
patch.object(cosmic_api, "load_config", return_value={}), \
|
|
85
|
+
patch.object(cosmic_api, "do_auth", side_effect=AssertionError("must not authenticate twice")):
|
|
86
|
+
assert cosmic_api.main() == 3
|
|
87
|
+
result = json.loads(capfd.readouterr().out)
|
|
88
|
+
assert result["errorCode"] == "SKILL_REVISION_MISMATCH"
|
|
89
|
+
assert result["argv"] == arguments
|
|
90
|
+
|
|
91
|
+
|
|
46
92
|
def test_verified_dependency_receipt_survives_restart_and_only_pending_target_runs(tmp_path):
|
|
47
93
|
artifacts = _artifacts(tmp_path)
|
|
48
94
|
base, bill = artifacts
|
|
@@ -132,7 +178,7 @@ def test_reference_or_input_errors_do_not_claim_platform_limit(code, category):
|
|
|
132
178
|
@pytest.mark.parametrize("locale", ["", ".locales/zh"])
|
|
133
179
|
def test_cli_stdout_and_result_file_are_the_same_json_in_path_with_spaces(tmp_path, locale):
|
|
134
180
|
output = tmp_path / "result with spaces.json"
|
|
135
|
-
result = subprocess.run([sys.executable, str(
|
|
181
|
+
result = subprocess.run([sys.executable, str(PACKAGE / locale / "skills" / "cosmic-meta-api" / "scripts" / "cosmic_api.py"), "--stdout", "--output", str(output),
|
|
136
182
|
"describeCommand", "runBatch"], capture_output=True, text=True)
|
|
137
183
|
assert result.returncode == 0, result.stderr
|
|
138
184
|
assert json.loads(result.stdout) == json.loads(output.read_text())
|
|
@@ -37,9 +37,12 @@ so rewording the messages keeps them passing.
|
|
|
37
37
|
|
|
38
38
|
from __future__ import annotations
|
|
39
39
|
|
|
40
|
+
import copy
|
|
40
41
|
import sys
|
|
41
42
|
from pathlib import Path
|
|
42
43
|
|
|
44
|
+
import pytest
|
|
45
|
+
|
|
43
46
|
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
44
47
|
if str(SCRIPTS) not in sys.path:
|
|
45
48
|
sys.path.insert(0, str(SCRIPTS))
|
|
@@ -121,6 +124,90 @@ def test_change_decision_is_one_function() -> None:
|
|
|
121
124
|
assert workspace_gate(NOOP_RESULT) is _effected_metadata_change(NOOP_RESULT) is False
|
|
122
125
|
|
|
123
126
|
|
|
127
|
+
COMBO_OP = {"op": "modify", "target": {"treeType": "entity", "elementType": "field", "value": "status"},
|
|
128
|
+
"path": "comboItems.add", "value": [{"value": "A", "caption": "Draft"}]}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _receipt_result(ops, statuses, changed=False):
|
|
132
|
+
return {"status": True, "errorCode": "0", "data": {
|
|
133
|
+
"success": True, "formId": "60HJ2YS9I1R5", "opsCount": len(ops), "stage": "committed",
|
|
134
|
+
"writeAttempted": True, "rolledBack": False, "metadataChanged": changed,
|
|
135
|
+
"opResults": [{"opIndex": index, "op": op["op"], "treeType": op["target"]["treeType"],
|
|
136
|
+
"elementType": op["target"]["elementType"], "target": op["target"]["value"],
|
|
137
|
+
"path": op.get("path"), "status": status}
|
|
138
|
+
for index, (op, status) in enumerate(zip(ops, statuses))]}}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _run_receipt(monkeypatch, result, ops=None):
|
|
142
|
+
monkeypatch.setattr(cmd_metaops, "_api_call", lambda *a, **k: result)
|
|
143
|
+
return cmd_metaops.do_modify_meta({}, "token", "60HJ2YS9I1R5", ops or [COMBO_OP],
|
|
144
|
+
form_number="kdtest_bill")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_complete_matching_noop_is_success_without_claiming_change(monkeypatch):
|
|
148
|
+
result = _receipt_result([COMBO_OP], ["no_op"])
|
|
149
|
+
before = copy.deepcopy(result)
|
|
150
|
+
outcome = _run_receipt(monkeypatch, result)
|
|
151
|
+
assert outcome["status"] is True
|
|
152
|
+
assert outcome["data"]["noOp"] is True
|
|
153
|
+
assert outcome["data"]["metadataChanged"] is False
|
|
154
|
+
assert outcome["data"]["noOpEvidence"]["scope"] == "handler"
|
|
155
|
+
assert _effected_metadata_change(outcome) is False # 不因合法 no_op 导出/同步 DYM。
|
|
156
|
+
assert result == before
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.parametrize("changes", [
|
|
160
|
+
{"status": "no_channel"}, {"status": "rejected"}, {"status": "unknown"},
|
|
161
|
+
{"opIndex": -1}, {"opIndex": True}, {"opIndex": "0"}, {"target": "other"},
|
|
162
|
+
{"path": "comboItems.remove"}, {"op": "remove"}, {"treeType": "form"},
|
|
163
|
+
{"errorCode": "META_PATH_NO_CHANNEL"}])
|
|
164
|
+
@pytest.mark.parametrize("changed", [False, True])
|
|
165
|
+
def test_incomplete_or_wrong_op_receipt_fails_even_if_metadata_changed(monkeypatch, changes, changed):
|
|
166
|
+
result = _receipt_result([COMBO_OP], ["no_op"], changed)
|
|
167
|
+
result["data"]["opResults"][0].update(changes)
|
|
168
|
+
outcome = _run_receipt(monkeypatch, result)
|
|
169
|
+
assert outcome["status"] is False
|
|
170
|
+
assert outcome["errorCode"] == "METADATA_OPS_UNCONFIRMED"
|
|
171
|
+
assert outcome["data"]["platformResult"] is result
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@pytest.mark.parametrize("changes", [
|
|
175
|
+
{"opResults": []}, {"opResults": None}, {"stage": "precheck"}, {"stage": "post_commit"},
|
|
176
|
+
{"rolledBack": True}, {"formId": "other"}, {"opsCount": 2}])
|
|
177
|
+
def test_missing_coverage_or_unconfirmed_stage_is_not_noop_success(monkeypatch, changes):
|
|
178
|
+
result = _receipt_result([COMBO_OP], ["no_op"])
|
|
179
|
+
result["data"].update(changes)
|
|
180
|
+
assert _run_receipt(monkeypatch, result)["errorCode"] == "METADATA_OPS_UNCONFIRMED"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_duplicate_and_partial_receipts_cannot_confirm_whole_batch(monkeypatch):
|
|
184
|
+
ops = [COMBO_OP, {**COMBO_OP, "target": {**COMBO_OP["target"], "value": "status2"}}]
|
|
185
|
+
result = _receipt_result(ops, ["no_op", "no_op"])
|
|
186
|
+
result["data"]["opResults"][1]["opIndex"] = 0
|
|
187
|
+
assert _run_receipt(monkeypatch, result, ops)["errorCode"] == "METADATA_OPS_UNCONFIRMED"
|
|
188
|
+
result = _receipt_result(ops, ["no_op", "submitted"])
|
|
189
|
+
assert _run_receipt(monkeypatch, result, ops)["errorCode"] == "METADATA_UNCHANGED"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_applied_false_snapshot_stays_unconfirmed_and_real_update_passes(monkeypatch):
|
|
193
|
+
result = _receipt_result([COMBO_OP], ["applied"])
|
|
194
|
+
assert _run_receipt(monkeypatch, result)["errorCode"] == "METADATA_UNCHANGED"
|
|
195
|
+
result["data"]["metadataChanged"] = True
|
|
196
|
+
assert _run_receipt(monkeypatch, result) is result
|
|
197
|
+
result = _receipt_result([COMBO_OP], ["no_op"], changed=True)
|
|
198
|
+
assert _run_receipt(monkeypatch, result)["errorCode"] == "METADATA_OPS_UNCONFIRMED"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_batch_verified_noop_is_not_unknown_after_write(monkeypatch):
|
|
202
|
+
result = _receipt_result([COMBO_OP], ["no_op"])
|
|
203
|
+
monkeypatch.setattr(cmd_metaops, "_api_call", lambda *a, **k: result)
|
|
204
|
+
batch = cmd_metaops.do_batch_modify({}, "token", [
|
|
205
|
+
{"formId": "60HJ2YS9I1R5", "formNumber": "kdtest_bill", "ops": [COMBO_OP]}])
|
|
206
|
+
assert batch["status"] is True
|
|
207
|
+
assert batch["data"]["results"][0]["outcome"] == "succeeded"
|
|
208
|
+
assert batch["data"]["results"][0]["response"]["data"]["noOp"] is True
|
|
209
|
+
|
|
210
|
+
|
|
124
211
|
# ── layer 2: the MetaOps preflight ──────────────────────────────────────────────────
|
|
125
212
|
|
|
126
213
|
def _move(extra):
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""The platform design-state export is a second read-back source for reports.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
syncDym exports the platform's own design-state metadata, and that export does carry both.
|
|
10
|
-
These tests pin the parse and the promotion rules, including the two conservative ones: an
|
|
11
|
-
empty section in the export never proves absence, and a canonical projection is never
|
|
12
|
-
overwritten by a file that may predate the last write.
|
|
4
|
+
formLayout 只提供控件树;syncDym 可以补充 ReportQueryPlugin 和过滤属性证据。
|
|
5
|
+
InitField 是默认显示列配置,其中 purpose:sort 是旧 API 污染,不证明排序持久化或运行生效。
|
|
6
|
+
这些测试保留污染夹具作为负向回归:不解析或晋升排序;空插件不证明不存在;
|
|
7
|
+
旧导出不能覆盖 canonical 数据,而 canonical 排序投影同样不能证明运行排序。
|
|
13
8
|
"""
|
|
14
9
|
|
|
15
10
|
import json
|
|
@@ -101,15 +96,12 @@ def _derived_schema(filters=()):
|
|
|
101
96
|
class ParseDesignStateExportTest(unittest.TestCase):
|
|
102
97
|
"""The export is parsed into exactly the sections it can prove."""
|
|
103
98
|
|
|
104
|
-
def
|
|
99
|
+
def test_polluted_init_field_is_not_sort_evidence(self):
|
|
105
100
|
with tempfile.TemporaryDirectory() as tmp:
|
|
106
101
|
path = _write_dym(tmp, "kdtest_rpt",
|
|
107
102
|
_dym(sorts=(("order_date", "desc"), ("order_no", "asc"))))
|
|
108
103
|
evidence = report_ops.parse_report_dym(path)
|
|
109
|
-
self.
|
|
110
|
-
{"fieldKey": "order_date", "direction": "desc"},
|
|
111
|
-
{"fieldKey": "order_no", "direction": "asc"},
|
|
112
|
-
])
|
|
104
|
+
self.assertNotIn("reportSorts", evidence)
|
|
113
105
|
|
|
114
106
|
def test_non_sort_init_field_entries_are_ignored(self):
|
|
115
107
|
with tempfile.TemporaryDirectory() as tmp:
|
|
@@ -118,7 +110,7 @@ class ParseDesignStateExportTest(unittest.TestCase):
|
|
|
118
110
|
'"purpose": "freeze", "direction": "desc"')
|
|
119
111
|
path = _write_dym(tmp, "kdtest_rpt", body)
|
|
120
112
|
evidence = report_ops.parse_report_dym(path)
|
|
121
|
-
self.
|
|
113
|
+
self.assertNotIn("reportSorts", evidence)
|
|
122
114
|
|
|
123
115
|
def test_only_the_report_query_plugin_slot_counts(self):
|
|
124
116
|
with tempfile.TemporaryDirectory() as tmp:
|
|
@@ -163,21 +155,20 @@ class PromoteDesignStateEvidenceTest(unittest.TestCase):
|
|
|
163
155
|
report_ops.attach_dym_evidence(target, evidence)
|
|
164
156
|
return target
|
|
165
157
|
|
|
166
|
-
def
|
|
158
|
+
def test_only_plugins_become_verifiable(self):
|
|
167
159
|
schema = self._attach(sorts=(("order_date", "desc"),),
|
|
168
160
|
plugins=(("kd.kdtest.report.P", True, "fetch"),))
|
|
169
161
|
self.assertEqual(schema["readBackSource"], "formLayout+dym")
|
|
170
|
-
self.assertEqual(schema["unverifiableSections"], [])
|
|
171
|
-
self.assertEqual(schema["dymEvidence"]["provedSections"],
|
|
172
|
-
["reportSorts", "reportPlugins"])
|
|
162
|
+
self.assertEqual(schema["unverifiableSections"], ["reportSorts"])
|
|
163
|
+
self.assertEqual(schema["dymEvidence"]["provedSections"], ["reportPlugins"])
|
|
173
164
|
self.assertEqual(schema["undecidablePluginProperties"],
|
|
174
165
|
["type", "bizAppId", "fpk", "description"])
|
|
175
166
|
|
|
176
167
|
def test_an_empty_section_never_proves_absence(self):
|
|
177
168
|
schema = self._attach(sorts=(("order_date", "desc"),))
|
|
178
169
|
# No plugin in the export: that is missing evidence, not proof of no plugin.
|
|
179
|
-
self.assertEqual(schema["unverifiableSections"], ["reportPlugins"])
|
|
180
|
-
self.
|
|
170
|
+
self.assertEqual(schema["unverifiableSections"], ["reportSorts", "reportPlugins"])
|
|
171
|
+
self.assertNotIn("dymEvidence", schema)
|
|
181
172
|
|
|
182
173
|
def test_canonical_projection_is_never_overwritten(self):
|
|
183
174
|
canonical = {
|
|
@@ -289,16 +280,25 @@ class VerifyAgainstDesignStateEvidenceTest(unittest.TestCase):
|
|
|
289
280
|
report_ops.attach_dym_evidence(schema, evidence)
|
|
290
281
|
return schema
|
|
291
282
|
|
|
292
|
-
def
|
|
283
|
+
def test_matching_pollution_never_verifies_sorting(self):
|
|
293
284
|
result = report_ops.report_readback_verification(
|
|
294
285
|
"sorts",
|
|
295
286
|
[{"fieldKey": "order_date", "direction": "desc"},
|
|
296
287
|
{"fieldKey": "order_no", "direction": "asc"}],
|
|
297
288
|
self._promoted(),
|
|
298
289
|
)
|
|
299
|
-
self.
|
|
290
|
+
self.assertFalse(result["verified"])
|
|
291
|
+
self.assertEqual(result["errorCode"], "REPORT_SORTS_API_UNSAFE")
|
|
300
292
|
self.assertEqual(result["source"], "formLayout+dym")
|
|
301
293
|
|
|
294
|
+
def test_matching_canonical_sorts_are_still_unverified(self):
|
|
295
|
+
sorts = [{"fieldKey": "order_date", "direction": "desc"}]
|
|
296
|
+
result = report_ops.report_readback_verification("sorts", sorts, {
|
|
297
|
+
"readBackAvailable": True, "readBackSource": "canonical", "reportSorts": sorts,
|
|
298
|
+
})
|
|
299
|
+
self.assertFalse(result["verified"])
|
|
300
|
+
self.assertEqual(result["errorCode"], "REPORT_SORTS_API_UNSAFE")
|
|
301
|
+
|
|
302
302
|
def test_wrong_sort_direction_is_a_mismatch(self):
|
|
303
303
|
result = report_ops.report_readback_verification(
|
|
304
304
|
"sorts", [{"fieldKey": "order_date", "direction": "asc"}], self._promoted())
|