@shundoo-ai/dsh-cosmic 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cordis.patch.yml +5 -5
- package/package.json +1 -1
- package/skills/cosmic-build/CHANGELOG.md +4 -0
- package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
- package/skills/cosmic-build/SKILL.md +46 -13
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
- package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
- package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
- package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
- package/skills/cosmic-build/references/application-context.md +35 -1
- package/skills/cosmic-build/references/common-reference.md +1 -1
- package/skills/cosmic-build/references/compile-guide.md +55 -68
- package/skills/cosmic-build/references/delivery.md +59 -7
- package/skills/cosmic-build/references/execution-contract.md +38 -0
- package/skills/cosmic-build/references/form-discovery.md +7 -1
- package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
- package/skills/cosmic-build/references/operation-dispatch.md +17 -0
- package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
- package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
- package/skills/cosmic-build/references/report-filter-contract.md +78 -0
- package/skills/cosmic-build/references/sdk-inspection.md +23 -0
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
- package/skills/cosmic-build/scripts/_http_client.py +55 -55
- package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
- package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
- package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
- package/skills/cosmic-build/scripts/execution_binding.py +40 -0
- package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
- package/skills/cosmic-build/scripts/kd_api.py +23 -22
- package/skills/cosmic-build/scripts/kd_auth.py +1 -1
- package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
- package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
- package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
- package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
- package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
- package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
- package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
- package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
- package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
- package/skills/cosmic-build/scripts/preview_url.py +35 -6
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
- package/skills/cosmic-build/scripts/register_plugins.py +28 -2
- package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
- package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
- package/skills/cosmic-build/scripts/run.py +2 -2
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
- package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
- package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
- package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
- package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
- package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
- package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
- package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
- package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
- package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
- package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
- package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
- package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
- package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
- package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
- package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
- package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
- package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
- package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
- package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
- package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
- package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
- package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
- package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
- package/skills/cosmic-build/scripts/test_environment.py +49 -0
- package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
- package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
- package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
- package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.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 +20 -5
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
- package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
- package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
- package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
- package/skills/erp-analysis/references/delivery-contract.md +4 -2
- package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/.dsh/cosmic/.kd/config.json +0 -21
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
9
|
+
CLASSPATH = os.environ.get("COSMIC_SDK_QUERY_CLASSPATH", "")
|
|
10
|
+
HOME = os.environ.get("COSMIC_SDK_JAVA_HOME", "")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.skipif(not CLASSPATH or not HOME, reason="target SDK and Java 8 home unavailable")
|
|
14
|
+
def test_native_typed_fields_and_aggregation_use_actual_sdk(tmp_path):
|
|
15
|
+
javac = Path(HOME) / "bin" / ("javac.exe" if os.name == "nt" else "javac")
|
|
16
|
+
java = Path(HOME) / "bin" / ("java.exe" if os.name == "nt" else "java")
|
|
17
|
+
subprocess.run([str(javac), "-source", "8", "-target", "8", "-cp", CLASSPATH, "-d", str(tmp_path),
|
|
18
|
+
str(ROOT / "assets/platform-report-filters/TypedReportFilterFields.java"),
|
|
19
|
+
str(ROOT / "examples/java-rules/CosmicReportAggregation.java"),
|
|
20
|
+
str(ROOT / "scripts/test/fixtures/ReportSdkCheck.java")], check=True, capture_output=True, text=True, timeout=60)
|
|
21
|
+
result = subprocess.run([str(java), "-DclusterName=report-sdk-check", "-Dlogfactory.className=ReportSdkCheck$TestLogFactory",
|
|
22
|
+
"-cp", os.pathsep.join([str(tmp_path), CLASSPATH]), "ReportSdkCheck"], cwd=tmp_path,
|
|
23
|
+
check=True, capture_output=True, text=True, timeout=30)
|
|
24
|
+
assert "report SDK checks passed" in result.stdout
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.skipif(not CLASSPATH or not HOME, reason="target SDK and Java 8 home unavailable")
|
|
28
|
+
def test_benchmark_cli_requires_equal_results_before_reporting_times(tmp_path):
|
|
29
|
+
target = tmp_path / "benchmark.json"
|
|
30
|
+
result = subprocess.run([sys.executable, str(ROOT / "scripts/report_algo_benchmark.py"),
|
|
31
|
+
"--sdk-classpath", CLASSPATH, "--java-home", HOME, "--rows", "1000", "--groups", "10",
|
|
32
|
+
"--iterations", "3", "--output", str(target)], check=True, capture_output=True, text=True, timeout=90)
|
|
33
|
+
data = json.loads(result.stdout)
|
|
34
|
+
assert data == json.loads(target.read_text())
|
|
35
|
+
assert data["status"] == "passed" and data["resultsEqual"] is True and data["databaseQueries"] == 0
|
|
36
|
+
assert data["baselineP50Ms"] > 0 and data["optimizedP95Ms"] > 0
|
|
@@ -38,7 +38,7 @@ def test_rule_batch_accepts_atomic_flag_spelling_without_changing_semantics():
|
|
|
38
38
|
assert normalized["args"]["onlyEmptyTarget"] is True
|
|
39
39
|
with patch.object(cmd_rules, "do_ensure_conditional_copy_field_value", return_value={"status": True}) as writer:
|
|
40
40
|
cmd_rules._execute_rule_set_item({}, "token", "form", normalized)
|
|
41
|
-
assert writer.call_args.args[-
|
|
41
|
+
assert writer.call_args.args[-3] is True
|
|
42
42
|
item["args"]["onlyEmptyTarget"] = False
|
|
43
43
|
with pytest.raises(ValueError, match="ARGS_CONFLICT"):
|
|
44
44
|
cmd_rules._validate_rule_set_request({"rules": [item]})
|
|
@@ -31,6 +31,7 @@ def test_rule_domain_contract_publishes_declarative_commands() -> None:
|
|
|
31
31
|
assert "ensureConditionalEntryStyle" in document["commands"]
|
|
32
32
|
assert "ensureConditionalFieldState" in document["commands"]
|
|
33
33
|
assert "ensureFieldCalculation" in document["commands"]
|
|
34
|
+
assert "ensureTimeDifferenceCalculation" in document["commands"]
|
|
34
35
|
assert "ensureRuleFunctionFormula" in document["commands"]
|
|
35
36
|
assert "DateTime" in document["commands"]["ensureRuleFunctionFormula"]["releasedGroups"]
|
|
36
37
|
assert "ensureRuleSet" in document["commands"]
|
|
@@ -69,6 +70,7 @@ def test_cli_and_modifier_route_common_calculation_without_raw_rule_json() -> No
|
|
|
69
70
|
cli = CLI.read_text(encoding="utf-8")
|
|
70
71
|
modifier = MODIFIER.read_text(encoding="utf-8")
|
|
71
72
|
assert '"ensureFieldCalculation"' in cli
|
|
73
|
+
assert '"ensureTimeDifferenceCalculation"' in cli
|
|
72
74
|
assert '"ensureRuleFunctionFormula"' in cli
|
|
73
75
|
assert '"ensureRuleSet"' in cli
|
|
74
76
|
assert '"ensureConditionalFieldState"' in cli
|
|
@@ -81,6 +83,7 @@ def test_cli_and_modifier_route_common_calculation_without_raw_rule_json() -> No
|
|
|
81
83
|
assert '"ensureQuantityConversion"' in cli
|
|
82
84
|
assert '"setSummaryToField"' in cli
|
|
83
85
|
assert "ensureFieldCalculation --entity" in modifier
|
|
86
|
+
assert "ensureTimeDifferenceCalculation --entity" in modifier
|
|
84
87
|
assert "function-formulas.md" in RULE_SKILL.read_text()
|
|
85
88
|
assert "ensureRuleFunctionFormula --app-root" in RULE_SKILL.with_name("function-formulas.md").read_text()
|
|
86
89
|
assert "ensureConditionalFieldState --entity" in modifier
|
|
@@ -98,6 +101,7 @@ def test_rule_commands_publish_the_small_rule_domain_contract() -> None:
|
|
|
98
101
|
cli = CLI.read_text(encoding="utf-8")
|
|
99
102
|
assert '"addRule": "skills/cosmic-meta-api/contracts/cli/rule-domain.json' in cli
|
|
100
103
|
assert '"ensureFieldCalculation": "skills/cosmic-meta-api/contracts/cli/rule-domain.json' in cli
|
|
104
|
+
assert '"ensureTimeDifferenceCalculation": "skills/cosmic-meta-api/contracts/cli/rule-domain.json' in cli
|
|
101
105
|
assert '"ensureRuleFunctionFormula": "skills/cosmic-meta-api/contracts/cli/rule-domain.json' in cli
|
|
102
106
|
assert '"ensureConditionalFieldState": "skills/cosmic-meta-api/contracts/cli/rule-domain.json' in cli
|
|
103
107
|
assert '"ensureConditionalEntryStyle": "skills/cosmic-meta-api/contracts/cli/rule-domain.json' in cli
|
|
@@ -126,6 +130,7 @@ def test_root_routes_common_rules_directly_to_the_rule_entry() -> None:
|
|
|
126
130
|
assert "--project-root" in text
|
|
127
131
|
assert "data.appRoot" in text
|
|
128
132
|
assert "ensureFieldCalculation --entity" in text
|
|
133
|
+
assert "ensureTimeDifferenceCalculation --entity" in text
|
|
129
134
|
assert "ensureConditionalFieldState --entity" in text
|
|
130
135
|
assert "ensureConditionalClearFields --entity" in text
|
|
131
136
|
assert "@first" in text
|
|
@@ -159,3 +164,18 @@ def test_rule_entity_context_accepts_header_and_entry_business_aliases() -> None
|
|
|
159
164
|
|
|
160
165
|
assert header["key"] == "demo"
|
|
161
166
|
assert entry["key"] == "demo_entry"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_calculation_scopes_repeated_price_caption_to_the_unique_participating_entity(monkeypatch):
|
|
170
|
+
entities = [{"key": "demo", "type": "BillEntity"}, {"key": "detail", "type": "EntryEntity"},
|
|
171
|
+
{"key": "addon", "type": "SubEntryEntity"}]
|
|
172
|
+
fields = [{"key": "amount", "name": "金额小计", "parentKey": "detail"},
|
|
173
|
+
{"key": "duration", "name": "时长", "parentKey": "detail"},
|
|
174
|
+
{"key": "price", "name": "单价", "parentKey": "detail"},
|
|
175
|
+
{"key": "service_price", "name": "单价", "parentKey": "addon"}]
|
|
176
|
+
monkeypatch.setattr(cmd_rules, "_rule_element_inventory", lambda cfg, token, form_number, category:
|
|
177
|
+
{"status": True, "data": entities if category == "entity" else fields})
|
|
178
|
+
selected, _ = cmd_rules._resolve_rule_entity_context({}, "token", "demo", "entries", ["金额小计", "时长", "单价"])
|
|
179
|
+
assert selected["key"] == "detail"
|
|
180
|
+
with pytest.raises(ValueError, match="AMBIGUOUS"):
|
|
181
|
+
cmd_rules._resolve_rule_entity_context({}, "token", "demo", "entries", ["单价"])
|
|
@@ -519,6 +519,7 @@ def test_rule_function_formula_compiles_and_uses_verified_writer(
|
|
|
519
519
|
)
|
|
520
520
|
|
|
521
521
|
requested = add_verified.call_args.args[3]
|
|
522
|
+
assert requested["description"] == "来源金额非空时,结果金额 = RemoveZero"
|
|
522
523
|
assert requested["trueActions"][0]["expression"] == "target = RemoveTailZero(source)"
|
|
523
524
|
assert requested["preCondition"] == "source <> null"
|
|
524
525
|
assert requested["falseActions"][0]["expression"] == "target"
|
|
@@ -623,11 +624,20 @@ def test_rule_function_formula_adopts_exact_existing_rule(
|
|
|
623
624
|
}]}},
|
|
624
625
|
}
|
|
625
626
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
)
|
|
627
|
+
with mock.patch.object(cmd_rules, "do_update_rule_verified", return_value={
|
|
628
|
+
"status": True, "data": {"ruleId": "rule-id"},
|
|
629
|
+
"verification": {"verified": True, "preservationVerified": True},
|
|
630
|
+
}) as update_verified:
|
|
631
|
+
result = cmd_rules.do_ensure_rule_function_formula(
|
|
632
|
+
{}, "token", "demo", "header", "target",
|
|
633
|
+
{"functionId": "RemoveZero", "args": [{"field": "source"}]},
|
|
634
|
+
)
|
|
630
635
|
|
|
631
|
-
assert result["data"]["changed"] is
|
|
632
|
-
assert result["writeAttempted"] is
|
|
636
|
+
assert result["data"]["changed"] is True
|
|
637
|
+
assert result["writeAttempted"] is True
|
|
638
|
+
requested_patch = update_verified.call_args.args[3]
|
|
639
|
+
assert requested_patch == {
|
|
640
|
+
"ruleType": "bizRule", "ruleId": "rule-id",
|
|
641
|
+
"description": "来源金额非空时,结果金额 = RemoveZero",
|
|
642
|
+
}
|
|
633
643
|
add_verified.assert_not_called()
|
|
@@ -70,6 +70,7 @@ def test_rule_hot_path_does_not_rediscover_published_commands_or_delivery_identi
|
|
|
70
70
|
for path in entries:
|
|
71
71
|
text = path.read_text(encoding="utf-8")
|
|
72
72
|
assert "ensureFieldCalculation --entity" in text
|
|
73
|
+
assert "ensureTimeDifferenceCalculation --entity" in text
|
|
73
74
|
assert "data.appRoot" in text
|
|
74
75
|
assert "data.formNumber" in text
|
|
75
76
|
assert "references/delivery.md" in text
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import json
|
|
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
|
+
from test_environment import export_test_environment
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_export_preserves_target_and_excludes_credentials(tmp_path):
|
|
12
|
+
context = tmp_path / "_runtime" / "context.json"
|
|
13
|
+
context.parent.mkdir()
|
|
14
|
+
context.write_text(json.dumps({
|
|
15
|
+
"appId": "APP", "appCode": "demo", "appName": "Demo",
|
|
16
|
+
"environment": {"baseUrl": "http://127.0.0.1:8080/ierp", "accountId": "123"},
|
|
17
|
+
"cookie": "must-not-export",
|
|
18
|
+
}))
|
|
19
|
+
result = export_test_environment(tmp_path, {
|
|
20
|
+
"baseUrl": "http://127.0.0.1:8080/ierp/", "accountId": "123", "loginUser": "test-user",
|
|
21
|
+
"password": "must-not-export", "appSecret": "must-not-export", "token": "must-not-export",
|
|
22
|
+
}, ["form_a", "form_a", "form_b"], {"form_a": "FORM-ID"}, {
|
|
23
|
+
"form_a": {"formName": "Form A", "previewUrl": "http://127.0.0.1:8080/ierp/?formId=form_a", "cookie": "must-not-export"},
|
|
24
|
+
})
|
|
25
|
+
saved = json.loads(Path(result["path"]).read_text())
|
|
26
|
+
assert saved["baseUrl"] == "http://127.0.0.1:8080/ierp"
|
|
27
|
+
assert saved["accountId"] == "123"
|
|
28
|
+
assert saved["application"]["appId"] == "APP"
|
|
29
|
+
assert saved["forms"] == [{"formNumber": "form_a", "formId": "FORM-ID", "formName": "Form A", "previewUrl": "http://127.0.0.1:8080/ierp/?formId=form_a"}, {"formNumber": "form_b"}]
|
|
30
|
+
assert "must-not-export" not in json.dumps(result)
|
|
31
|
+
assert "authMode" not in saved
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.parametrize("server", [
|
|
35
|
+
{"baseUrl": "https://devops.invalid/", "accountId": "123"},
|
|
36
|
+
{"baseUrl": "http://127.0.0.1:8080/ierp", "accountId": "different"},
|
|
37
|
+
{"baseUrl": "http://user:secret@127.0.0.1:8080/ierp"},
|
|
38
|
+
])
|
|
39
|
+
def test_export_rejects_changed_environment_or_credential_url(tmp_path, server):
|
|
40
|
+
context = tmp_path / "_runtime" / "context.json"
|
|
41
|
+
context.parent.mkdir()
|
|
42
|
+
context.write_text(json.dumps({"environment": {"baseUrl": "http://127.0.0.1:8080/ierp", "accountId": "123"}}))
|
|
43
|
+
with pytest.raises(ValueError):
|
|
44
|
+
export_test_environment(tmp_path, server, ["form_a"])
|
|
45
|
+
assert not (tmp_path / "_runtime" / "test-environment.json").exists()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
11
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
12
|
+
from verify_plugin_loading import verify_loading
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def compiled(tmp_path):
|
|
17
|
+
if not shutil.which("javac"):
|
|
18
|
+
pytest.skip("javac unavailable")
|
|
19
|
+
source = tmp_path / "Report.java"
|
|
20
|
+
source.write_text('package example; public class Report { public String version() { return "current"; } }')
|
|
21
|
+
subprocess.run(["javac", "-source", "8", "-target", "8", "-d", str(tmp_path), str(source)],
|
|
22
|
+
capture_output=True, check=True)
|
|
23
|
+
artifact = tmp_path / "example" / "Report.class"
|
|
24
|
+
deployed = tmp_path / "outputdir" / "example" / "Report.class"
|
|
25
|
+
deployed.parent.mkdir(parents=True)
|
|
26
|
+
shutil.copyfile(artifact, deployed)
|
|
27
|
+
digest = hashlib.sha256(artifact.read_bytes()).hexdigest()
|
|
28
|
+
receipt = {"status": True, "compileSkipped": False, "sourcePath": str(source), "className": "Report",
|
|
29
|
+
"sourceReview": {"sourceHash": hashlib.sha256(source.read_bytes()).hexdigest()},
|
|
30
|
+
"compiledArtifact": {"path": str(artifact), "fqcn": "example.Report", "sha256": digest}}
|
|
31
|
+
trace = {"status": "log-lines-found", "plugin": "example.Report", "traceId": "query-current", "observations": [
|
|
32
|
+
{"pluginMatch": "fully-qualified", "loadedClassSha256": digest}]}
|
|
33
|
+
return receipt, artifact, deployed, trace
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_artifact_equality_never_implies_jvm_loading(compiled):
|
|
37
|
+
receipt, artifact, deployed, trace = compiled
|
|
38
|
+
result = verify_loading(receipt, artifact, deployed)
|
|
39
|
+
assert result["artifactMatch"] is True and result["runtimeVerified"] is False
|
|
40
|
+
assert result["status"] == "blocked"
|
|
41
|
+
assert verify_loading(receipt, artifact, deployed, trace)["status"] == "passed"
|
|
42
|
+
trace["observations"][0]["loadedClassSha256"] = "0" * 64
|
|
43
|
+
assert verify_loading(receipt, artifact, deployed, trace)["reason"] == "loaded-class-differs"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_stale_deployment_and_source_changes_fail(compiled, tmp_path):
|
|
47
|
+
receipt, artifact, deployed, trace = compiled
|
|
48
|
+
source = tmp_path / "old" / "Report.java"
|
|
49
|
+
source.parent.mkdir()
|
|
50
|
+
source.write_text('package example; public class Report { public String version() { return "old"; } }')
|
|
51
|
+
subprocess.run(["javac", "-source", "8", "-target", "8", "-d", str(deployed.parents[1]), str(source)],
|
|
52
|
+
capture_output=True, check=True)
|
|
53
|
+
assert verify_loading(receipt, artifact, deployed, trace)["reason"] == "deployed-class-differs"
|
|
54
|
+
Path(receipt["sourcePath"]).write_text('package example; public class Report {}')
|
|
55
|
+
assert verify_loading(receipt, artifact, deployed, trace)["reason"] == "source-changed-after-compilation"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_skipped_compile_unbound_artifact_and_short_logger_do_not_pass(compiled):
|
|
59
|
+
receipt, artifact, deployed, trace = compiled
|
|
60
|
+
trace["observations"][0]["pluginMatch"] = "simple-name-unverified"
|
|
61
|
+
assert verify_loading(receipt, artifact, deployed, trace)["runtimeVerified"] is False
|
|
62
|
+
receipt["compiledArtifact"] = None
|
|
63
|
+
assert verify_loading(receipt, artifact, deployed, trace)["reason"] == "compile-artifact-not-bound"
|
|
64
|
+
receipt["compileSkipped"] = True
|
|
65
|
+
assert verify_loading(receipt, artifact, deployed, trace)["reason"] == "compilation-not-proven"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_loading_cli_emits_structured_result_and_nonzero_for_missing_runtime(compiled, tmp_path):
|
|
69
|
+
receipt, artifact, deployed, trace = compiled
|
|
70
|
+
file = tmp_path / "compile.json"
|
|
71
|
+
file.write_text(json.dumps(receipt))
|
|
72
|
+
args = [sys.executable, str(SCRIPTS / "verify_plugin_loading.py"), "--compile-result", str(file),
|
|
73
|
+
"--compiled-class", str(artifact), "--deployed-class", str(deployed)]
|
|
74
|
+
result = subprocess.run(args, capture_output=True, text=True)
|
|
75
|
+
assert result.returncode == 2 and json.loads(result.stdout)["runtimeVerified"] is False
|
|
76
|
+
observation = tmp_path / "trace.json"
|
|
77
|
+
observation.write_text(json.dumps(trace))
|
|
78
|
+
result = subprocess.run([*args, "--runtime-trace", str(observation)], capture_output=True, text=True)
|
|
79
|
+
assert result.returncode == 0 and json.loads(result.stdout)["runtimeVerified"] is True
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Export only non-secret target identity for the implementation-to-QA handoff."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def environment_identity(server):
|
|
9
|
+
url = urlsplit(str(server.get("baseUrl") or server.get("base_url") or ""))
|
|
10
|
+
if url.scheme not in ("http", "https") or not url.hostname or url.username or url.password or url.query or url.fragment:
|
|
11
|
+
raise ValueError("Test environment requires a complete HTTP(S) baseUrl without credentials")
|
|
12
|
+
return {
|
|
13
|
+
"baseUrl": urlunsplit((url.scheme.lower(), url.netloc.lower(), url.path.rstrip("/"), "", "")),
|
|
14
|
+
**{key: str(server[key]) for key in ("accountId",) if server.get(key)},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def export_test_environment(app_root, server, form_numbers, form_ids=None, form_links=None):
|
|
19
|
+
root = Path(app_root).resolve()
|
|
20
|
+
context_path = root / "_runtime" / "context.json"
|
|
21
|
+
context = json.loads(context_path.read_text(encoding="utf-8-sig")) if context_path.is_file() else {}
|
|
22
|
+
current = environment_identity(server)
|
|
23
|
+
bound = context.get("environment")
|
|
24
|
+
if bound and any(bound.get(key) and bound[key] != current.get(key) for key in ("baseUrl", "accountId")):
|
|
25
|
+
raise ValueError("Delivery environment differs from the application's bound environment; reconcile the target before delivery")
|
|
26
|
+
handoff = {
|
|
27
|
+
"schemaVersion": "cosmic-test-environment/v1",
|
|
28
|
+
**current,
|
|
29
|
+
"source": "app-build verified target",
|
|
30
|
+
"application": {
|
|
31
|
+
"appRoot": str(root),
|
|
32
|
+
**{key: str(context[key]) for key in ("appCode", "appName", "appId") if context.get(key)},
|
|
33
|
+
},
|
|
34
|
+
"forms": [
|
|
35
|
+
{
|
|
36
|
+
"formNumber": number,
|
|
37
|
+
**({"formId": form_ids[number]} if form_ids and form_ids.get(number) else {}),
|
|
38
|
+
**(
|
|
39
|
+
{key: form_links[number][key] for key in ("formName", "previewUrl", "listUrl") if form_links[number].get(key)}
|
|
40
|
+
if form_links and number in form_links else {}
|
|
41
|
+
),
|
|
42
|
+
}
|
|
43
|
+
for number in dict.fromkeys(form_numbers)
|
|
44
|
+
],
|
|
45
|
+
}
|
|
46
|
+
target = root / "_runtime" / "test-environment.json"
|
|
47
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
target.write_text(json.dumps(handoff, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
49
|
+
return {"path": str(target), "environment": handoff}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Read-only comparison of compiled/deployed bytes and explicit class-loading evidence."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from java_source import stripped
|
|
11
|
+
from plugin_corpus.jvm_classfile import parse_class
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def verify_loading(receipt, compiled_class, deployed_class, runtime_trace=None):
|
|
15
|
+
if not isinstance(receipt, dict) or runtime_trace is not None and (
|
|
16
|
+
not isinstance(runtime_trace, dict) or not isinstance(runtime_trace.get("observations"), list)
|
|
17
|
+
or any(not isinstance(item, dict) for item in runtime_trace["observations"])):
|
|
18
|
+
raise ValueError("Expected compile receipt and plugin-trace objects")
|
|
19
|
+
if receipt.get("status") is not True or receipt.get("compileSkipped") is not False:
|
|
20
|
+
return {"status": "blocked", "runtimeVerified": False, "reason": "compilation-not-proven"}
|
|
21
|
+
source = Path(receipt["sourcePath"])
|
|
22
|
+
if hashlib.sha256(source.read_bytes()).hexdigest() != receipt["sourceReview"]["sourceHash"]:
|
|
23
|
+
return {"status": "failed", "runtimeVerified": False, "reason": "source-changed-after-compilation"}
|
|
24
|
+
package = re.search(r"\bpackage\s+([\w.]+)\s*;", stripped(source.read_text(encoding="utf-8-sig")))
|
|
25
|
+
fqcn = (package[1] + "." if package else "") + receipt["className"].rsplit(".", 1)[-1]
|
|
26
|
+
paths = [Path(compiled_class), Path(deployed_class)]
|
|
27
|
+
if any(not path.is_file() for path in paths):
|
|
28
|
+
return {"status": "blocked", "runtimeVerified": False, "reason": "class-file-unavailable", "fqcn": fqcn}
|
|
29
|
+
artifacts = [{"path": str(path.resolve()), "sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
30
|
+
"fqcn": parse_class(path.read_bytes())["name"].replace("/", ".")} for path in paths]
|
|
31
|
+
if not receipt.get("compiledArtifact"):
|
|
32
|
+
return {"status": "blocked", "runtimeVerified": False, "reason": "compile-artifact-not-bound", "artifacts": artifacts}
|
|
33
|
+
if (receipt["compiledArtifact"].get("fqcn") != fqcn
|
|
34
|
+
or receipt["compiledArtifact"].get("sha256") != artifacts[0]["sha256"]
|
|
35
|
+
or Path(receipt["compiledArtifact"]["path"]).resolve() != paths[0].resolve()):
|
|
36
|
+
return {"status": "failed", "runtimeVerified": False, "reason": "compile-artifact-changed", "artifacts": artifacts}
|
|
37
|
+
if any(item["fqcn"] != fqcn for item in artifacts):
|
|
38
|
+
return {"status": "failed", "runtimeVerified": False, "reason": "class-identity-mismatch", "artifacts": artifacts}
|
|
39
|
+
if artifacts[0]["sha256"] != artifacts[1]["sha256"]:
|
|
40
|
+
return {"status": "failed", "runtimeVerified": False, "reason": "deployed-class-differs", "artifacts": artifacts}
|
|
41
|
+
observed = ([item.get("loadedClassSha256") for item in runtime_trace.get("observations", [])
|
|
42
|
+
if item.get("pluginMatch") == "fully-qualified" and item.get("loadedClassSha256")]
|
|
43
|
+
if runtime_trace and runtime_trace.get("status") == "log-lines-found"
|
|
44
|
+
and runtime_trace.get("plugin") == fqcn and runtime_trace.get("traceId") else [])
|
|
45
|
+
matched = bool(observed) and all(value == artifacts[0]["sha256"] for value in observed)
|
|
46
|
+
return {"status": "passed" if matched else "failed" if observed else "blocked",
|
|
47
|
+
"runtimeVerified": matched, "artifactMatch": True, "fqcn": fqcn, "artifacts": artifacts,
|
|
48
|
+
"reason": "loaded-class-matches" if matched else "loaded-class-differs" if observed else "runtime-loading-evidence-missing",
|
|
49
|
+
"traceId": runtime_trace.get("traceId") if runtime_trace else None,
|
|
50
|
+
"evidenceLimit": "Artifact equality alone does not prove JVM activation. Runtime proof consumes explicit request-scoped loaded-class hashes, never hashes of files merely read after replacement."}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main():
|
|
54
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
55
|
+
parser.add_argument("--compile-result", required=True)
|
|
56
|
+
parser.add_argument("--compiled-class", required=True)
|
|
57
|
+
parser.add_argument("--deployed-class", required=True)
|
|
58
|
+
parser.add_argument("--runtime-trace", help="JSON output of cosmic-e2e-test/scripts/plugin-trace.mjs")
|
|
59
|
+
args = parser.parse_args()
|
|
60
|
+
try:
|
|
61
|
+
result = verify_loading(json.loads(Path(args.compile_result).read_text(encoding="utf-8-sig")),
|
|
62
|
+
args.compiled_class, args.deployed_class,
|
|
63
|
+
json.loads(Path(args.runtime_trace).read_text(encoding="utf-8-sig")) if args.runtime_trace else None)
|
|
64
|
+
except (OSError, ValueError, KeyError, TypeError, AttributeError) as error:
|
|
65
|
+
result = {"status": "blocked", "runtimeVerified": False, "reason": "invalid-loading-evidence", "message": str(error)}
|
|
66
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
67
|
+
return 0 if result["status"] == "passed" else 2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
raise SystemExit(main())
|