K8: record blocked owner review and release evidence

This commit is contained in:
2026-08-21 14:27:23 +08:00
parent 2ef2d82db3
commit b1b5a3cc8a
62 changed files with 171781 additions and 19 deletions

View File

@@ -0,0 +1,188 @@
{
"candidate_eligible": false,
"checks": {
"chromium_probe": {
"smoke": "passed",
"status": "verified",
"version": "Google Chrome 151.0.7922.169"
},
"context_consistency": {
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"status": "verified"
},
"firefox_probe": {
"smoke": "passed",
"status": "verified",
"version": "Mozilla Firefox 154.0"
},
"locked_matrix_read": {
"declared_status": "planned-for-m4",
"minimum": {
"chromium": "TBD",
"firefox": "TBD",
"safari": "TBD"
},
"status": "verified"
},
"no_unverified_result_hidden": {
"smoke": {
"chromium": "passed",
"firefox": "passed",
"safari": "blocked_unavailable"
},
"status": "verified"
},
"release_stop_preserved": {
"release_action": "STOP_RELEASE",
"status": "verified"
},
"safari_probe": {
"reason": "Safari is not available in this Linux environment",
"smoke": "blocked_unavailable",
"status": "verified",
"version": null
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"declared_matrix": {
"cross_origin_isolation": false,
"minimum": {
"chromium": "TBD",
"firefox": "TBD",
"safari": "TBD"
},
"status": "planned-for-m4"
},
"errors": {},
"inputs": [
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
{
"bytes": 11539,
"path": "coverage/compatibility-matrix.report.json",
"role": "compatibility_matrix",
"sha256": "7e5e946031b080bd22c3630313907383863b61e19d6e959c74c14b2dba112ea7",
"status": "present"
},
{
"bytes": 7196,
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "present"
},
{
"bytes": 15574,
"path": "coverage/release-remediation-intake.report.json",
"role": "remediation_intake",
"sha256": "7d5916f02e12e952ec517d59ff436237d09854c90a961ddbd1375f4598b4c2a3",
"status": "present"
}
],
"observed_browsers": [
{
"binary": "/usr/bin/google-chrome",
"browser": "chromium",
"smoke": {
"command": [
"/usr/bin/google-chrome",
"--headless=new",
"--no-sandbox",
"--disable-gpu",
"--disable-dev-shm-usage",
"--dump-dom",
"data:text/html,<title>LinuxCNC%20WASM%20smoke</title>"
],
"exit_code": 0,
"status": "passed",
"stderr_preview": "",
"stderr_sha256": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",
"stdout_preview": "<html><head><title>LinuxCNC WASM smoke</title></head><body></body></html>",
"stdout_sha256": "f1516af3b272ea95719b5ce833d3afcbdf192eb69962fa5b59443faf60fc9f26"
},
"version": "Google Chrome 151.0.7922.169",
"version_probe": {
"command": [
"/usr/bin/google-chrome",
"--version"
],
"exit_code": 0,
"status": "passed",
"stderr_preview": "",
"stderr_sha256": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",
"stdout_preview": "Google Chrome 151.0.7922.169",
"stdout_sha256": "098776ee39a35d449cc4dff5dd517b446d388c0fa51559928300f3cd88b9591a"
},
"version_status": "verified"
},
{
"binary": "/usr/bin/firefox",
"browser": "firefox",
"smoke": {
"command": [
"<browser>",
"--headless",
"--screenshot",
"<temp>/firefox.png",
"data:text/html,<title>LinuxCNC%20WASM%20smoke</title>"
],
"exit_code": 0,
"status": "passed",
"stderr_preview": "*** You are running in headless mode.\nGtk-Message: <time>: Not loading module \"atk-bridge\": The functionality is provided by GTK natively. Please try to not loa",
"stderr_sha256": "50fe7359f0b1fd6cdaaa77acbefc942c9ff6cfe6c2b2230dd48c15e77c0fe0a9",
"stdout_preview": "",
"stdout_sha256": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126"
},
"version": "Mozilla Firefox 154.0",
"version_probe": {
"command": [
"/usr/bin/firefox",
"--version"
],
"exit_code": 0,
"status": "passed",
"stderr_preview": "",
"stderr_sha256": "12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",
"stdout_preview": "Mozilla Firefox 154.0",
"stdout_sha256": "bee2190b746c0b157c6f1bbc4f78fd38ed3276ddc2d9749753d6cb1800d35934"
},
"version_status": "verified"
},
{
"binary": null,
"browser": "safari",
"smoke": {
"reason": "binary_not_installed",
"status": "blocked_unavailable"
},
"version": null,
"version_status": "blocked_unavailable"
}
],
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"remediation_status": "blocked_matrix_unverified",
"report_sha256": "7ca6f79dda1aa3ea0b5bbce9ec1d96152a0aa0f5e942563084286290621545c9",
"review_policy": {
"current_environment_limit": "Safari binary and browser farm are unavailable; observed Chromium/Firefox probes do not establish a release minimum",
"forbidden": [
"infer Safari support from Chromium/Firefox",
"pin versions without a new reviewed context",
"mark planned matrix verified by smoke-only probes",
"overwrite_context_lock_in_place"
],
"required_to_unblock": [
"pin minimum Chromium, Firefox and Safari versions in a reviewed context lock",
"run the required workflow and failure matrix in each target browser",
"record cross-origin isolation and offline/OPFS behavior"
]
},
"schema": "browser-matrix-remediation.v1",
"status": "complete",
"task_id": "K8-REL-12"
}

View File

@@ -0,0 +1,367 @@
{
"blockers": [
"official_oracle_missing",
"browser_matrix_unverified",
"configuration_resource_gaps",
"manifest_seed_unassigned",
"license_metadata_not_per_entry"
],
"browser_matrix": {
"declared_status": "planned-for-m4",
"minimum": {
"chromium": "TBD",
"firefox": "TBD",
"safari": "TBD"
},
"reason": "minimum Chromium/Firefox/Safari versions are TBD",
"status": "blocked"
},
"configuration": {
"derived_results_are_not_official": true,
"entries": 264,
"entrypoints": 233,
"resource_status": {
"blocked_missing": 147,
"blocked_missing_and_unresolved": 49,
"ready": 9,
"unindexed": 59
},
"startup_preflight": {
"blocked_resource": 194,
"blocked_unindexed_resource_graph": 11,
"blocked_unregistered_kinematics": 3,
"static_ready": 56
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"contracts": {
"observed": {
"event": {
"source": "web/worker/replay.mjs",
"source_observed": true,
"status": "verified",
"value": 1
},
"manifest": {
"source": "context-lock.json",
"source_observed": true,
"status": "verified",
"value": 1
},
"protocol": {
"source": "web/worker/sim-session.mjs",
"source_observed": true,
"status": "verified",
"value": 1
},
"sim_abi": {
"source": "context-lock.json",
"source_observed": true,
"status": "verified",
"value": 1
},
"snapshot": {
"source": "web/worker/replay.mjs",
"source_observed": true,
"status": "verified",
"value": 1
},
"workspace": {
"source": "web/workspace/recovery.mjs",
"source_observed": true,
"status": "verified",
"value": 1
}
},
"schema_versions": {
"event": 1,
"manifest": 1,
"protocol": 1,
"sim_abi": 1,
"snapshot": 1,
"workspace": 1
},
"status": "verified"
},
"domains": [
{
"artifact": "coverage/configuration-regression.report.json",
"domain": "configuration",
"implementation_status": "verified",
"oracle_status": "blocked",
"runtime_status": "partial",
"status": "blocked",
"test_evidence": {
"sample_hashes_equal": true,
"status": "passed"
}
},
{
"artifact": "coverage/runtime-mapping.report.json",
"domain": "protocol",
"implementation_status": "passed",
"oracle_status": null,
"runtime_status": "passed",
"status": "passed",
"test_evidence": null
},
{
"artifact": "coverage/posemath-runtime.report.json",
"domain": "posemath",
"implementation_status": "passed",
"oracle_status": null,
"runtime_status": "passed",
"status": "passed",
"test_evidence": true
},
{
"artifact": "coverage/interpreter-runtime.report.json",
"domain": "interpreter",
"implementation_status": "passed",
"oracle_status": null,
"runtime_status": "passed",
"status": "passed",
"test_evidence": true
},
{
"artifact": "coverage/motion-runtime.report.json",
"domain": "motion",
"implementation_status": "passed",
"oracle_status": null,
"runtime_status": "passed",
"status": "passed",
"test_evidence": true
},
{
"artifact": "coverage/kinematics-runtime.report.json",
"domain": "kinematics",
"implementation_status": "partial",
"oracle_status": null,
"runtime_status": "partial",
"status": "blocked",
"test_evidence": null
},
{
"artifact": "coverage/hal-runtime.report.json",
"domain": "hal",
"implementation_status": "passed",
"oracle_status": null,
"runtime_status": "passed",
"status": "passed",
"test_evidence": [
"sim_hal_test"
]
},
{
"artifact": "coverage/io-runtime.report.json",
"domain": "io",
"implementation_status": "verified",
"oracle_status": "blocked",
"runtime_status": "partial",
"status": "blocked",
"test_evidence": {
"native_command": "build-native/sim_io_test",
"status": "passed",
"wasm_command": "node build-wasm/sim_io_wasm_test.js"
}
},
{
"artifact": "coverage/python-runtime.report.json",
"domain": "python",
"implementation_status": "passed",
"oracle_status": null,
"runtime_status": "passed",
"status": "passed",
"test_evidence": null
},
{
"artifact": "coverage/workspace-recovery.report.json",
"domain": "recovery",
"implementation_status": "verified",
"oracle_status": "blocked",
"runtime_status": "passed",
"status": "blocked",
"test_evidence": {
"event_import_validation": "native sim_event_import and Worker replay schema both fail-closed",
"native_command": "cmake --build build-native && ctest --test-dir build-native --output-on-failure",
"status": "passed",
"worker_command": "node web/worker/test_replay.mjs && node web/workspace/test_recovery.mjs"
}
}
],
"hash_checks": [
{
"actual": "40b12bba25957c667988c704c51611bcdbd2608849d031fbb137708efaedeb0b",
"expected": "40b12bba25957c667988c704c51611bcdbd2608849d031fbb137708efaedeb0b",
"lock_key": "configuration_regression_report_sha256",
"path": "coverage/configuration-regression.report.json",
"status": "verified"
},
{
"actual": "be531c8cb5af55f9527b6b6b3955ff908a748fae0e37952fa699aecf1a44b399",
"expected": "be531c8cb5af55f9527b6b6b3955ff908a748fae0e37952fa699aecf1a44b399",
"lock_key": "python_runtime_report_sha256",
"path": "coverage/python-runtime.report.json",
"status": "verified"
},
{
"actual": "2a78438ab4a685476cc57f10d15e2ca3ee688892a7696081a135018638031404",
"expected": "2a78438ab4a685476cc57f10d15e2ca3ee688892a7696081a135018638031404",
"lock_key": "source_index_sha256",
"path": "coverage/source-index.jsonl",
"status": "verified"
},
{
"actual": "5d353f5f11053b520acdb0e401d957e3cfb9e47be1d22b6a9848cc7ec1bf1416",
"expected": "5d353f5f11053b520acdb0e401d957e3cfb9e47be1d22b6a9848cc7ec1bf1416",
"lock_key": "manifest_seed_sha256",
"path": "coverage/manifest.jsonl",
"status": "verified"
}
],
"inputs": [
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
{
"bytes": 1925705,
"path": "coverage/source-index.jsonl",
"role": "source_index",
"sha256": "2a78438ab4a685476cc57f10d15e2ca3ee688892a7696081a135018638031404",
"status": "present"
},
{
"bytes": 6273648,
"path": "coverage/manifest.jsonl",
"role": "manifest_seed",
"sha256": "5d353f5f11053b520acdb0e401d957e3cfb9e47be1d22b6a9848cc7ec1bf1416",
"status": "present"
},
{
"bytes": 497244,
"implementation_status": "verified",
"oracle_status": "blocked",
"path": "coverage/configuration-regression.report.json",
"role": "configuration_regression",
"schema": "configuration-regression-report.v1",
"sha256": "40b12bba25957c667988c704c51611bcdbd2608849d031fbb137708efaedeb0b",
"status": "partial"
},
{
"bytes": 1902,
"path": "python-runtime/manifest.json",
"role": "python_runtime_manifest",
"schema": "python-runtime-manifest.v1",
"sha256": "340faa6ef5bedcaeb0583f463e6b89cb442ddf1d83d7e6ee8d96cee438fe4213",
"status": "present"
},
{
"bytes": 20236,
"path": "docs/linuxcnc-web-wasm-full-port-implementation.md",
"role": "implementation_contract",
"sha256": "16ed7fdf62f0dd377e50947109dfa57bc947df6313a21d0eb034d9b8e8ceb70f",
"status": "present"
},
{
"bytes": 2225,
"implementation_status": "verified",
"oracle_status": "blocked",
"path": "coverage/workspace-migration.report.json",
"role": "migration_rollback_offline",
"schema": "workspace-migration-report.v1",
"sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"status": "passed"
},
{
"bytes": 3726,
"path": "coverage/workspace-rollback.package.json",
"role": "rollback_package",
"schema": "workspace-rollback.v1",
"sha256": "cab2e41ad8e740435d0dd71298f10438fc1a3462dfaaaad28ad98a4315d2aa5f",
"status": "present"
},
{
"bytes": 528,
"path": "coverage/offline-cache.package.json",
"role": "offline_package",
"schema": "offline-cache-package.v1",
"sha256": "cfcfbbbd72cd59d576105fc8f0b206e5d08dc63ce803b463c985dd59a3086922",
"status": "present"
}
],
"migration_rollback_offline": {
"artifact": {
"bytes": 2225,
"implementation_status": "verified",
"oracle_status": "blocked",
"path": "coverage/workspace-migration.report.json",
"role": "migration_rollback_offline",
"schema": "workspace-migration-report.v1",
"sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"status": "passed"
},
"format_schema": "workspace-package.v1",
"no_silent_downgrade": true,
"offline_package": {
"bytes": 528,
"path": "coverage/offline-cache.package.json",
"role": "offline_package",
"schema": "offline-cache-package.v1",
"sha256": "cfcfbbbd72cd59d576105fc8f0b206e5d08dc63ce803b463c985dd59a3086922",
"status": "present"
},
"offline_start": "ready",
"rollback_package": {
"bytes": 3726,
"path": "coverage/workspace-rollback.package.json",
"role": "rollback_package",
"schema": "workspace-rollback.v1",
"sha256": "cab2e41ad8e740435d0dd71298f10438fc1a3462dfaaaad28ad98a4315d2aa5f",
"status": "present"
},
"rollback_recovery": "recovered",
"round_trip_equal": true,
"status": "verified",
"supported_versions": [
1,
2
]
},
"oracle": {
"official_root": "linuxcnc/unit_tests/kinematics",
"present": false,
"reason": "official kinematics and recovery oracle is absent from the locked source",
"status": "blocked"
},
"real_excluded": [
{
"id": "hardware_driver",
"reason": "physical hardware and device access are outside browser simulation"
},
{
"id": "linux_realtime_kernel",
"reason": "hard real-time kernel scheduling is outside WebAssembly"
},
{
"id": "host_device_access",
"reason": "host paths, sockets and devices are denied by the Worker runtime"
}
],
"release_decision": "blocked_not_release_candidate",
"report_sha256": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44",
"schema": "release-compatibility-matrix.v1",
"source_baseline": {
"context_lock": {
"path": "context-lock.json",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc"
},
"linuxcnc_source_sha": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"linuxcnc_version": "2.10.0~pre0",
"linuxcnc_version_sha256": "afc5dc60d029cb477a28f476599249ba353d0164f8991d7b8326371998ac59a1"
},
"status": "blocked"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
{
"blocked_count": 6,
"blockers": [
{
"count": 6249,
"evidence": [
{
"path": "coverage/release-evidence-index.json",
"status": "present"
},
{
"path": "coverage/manifest.jsonl",
"status": "present"
}
],
"evidence_check": "verified",
"id": "manifest_seed_unassigned",
"owner": "manifest-feature-ownership",
"reason": "manifest seed entries still have no feature owner",
"remediation": "assign an accountable feature owner to every needs_owner manifest seed and regenerate the evidence index",
"review_owner": "release-closure-owner",
"review_trigger": "manifest mapping changes or the release closure is regenerated",
"status": "blocked",
"status_check": "verified"
},
{
"count": 0,
"evidence": [
{
"path": "coverage/release-evidence-index.json",
"status": "present"
},
{
"path": "coverage/release-candidate-gate.report.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "accepted_entries_missing",
"owner": "release-acceptance-owner",
"reason": "no manifest entry is accepted for release",
"remediation": "complete acceptance evidence for eligible entries; do not promote blocked or derived entries by relabeling",
"review_owner": "release-gate-owner",
"review_trigger": "any entry changes to accepted or the acceptance policy changes",
"status": "blocked",
"status_check": "verified"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "coverage/kinematics-oracle.report.json",
"status": "present"
},
{
"path": "coverage/workspace-recovery.report.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "official_oracle_missing",
"owner": "upstream-oracle-owner",
"reason": "locked source has no official kinematics/recovery oracle root",
"remediation": "add the locked official kinematics and recovery oracle inputs, then rerun the native/WASM comparison",
"review_owner": "kinematics-recovery-reviewer",
"review_trigger": "the locked source gains or changes an official oracle root",
"status": "blocked",
"status_check": "verified"
},
{
"count": 997,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "coverage/configuration-regression.report.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "configuration_resources_missing",
"owner": "configuration-closure-owner",
"reason": "configuration startup/resource gaps remain",
"remediation": "resolve missing and unindexed configuration resources and unregistered startup references without dropping rows",
"review_owner": "configuration-regression-reviewer",
"review_trigger": "configuration graph or startup preflight counts change",
"status": "blocked",
"status_check": "verified"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "context-lock.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "browser_matrix_unverified",
"owner": "browser-compatibility-owner",
"reason": "Chromium/Firefox/Safari minimum versions remain TBD",
"remediation": "pin minimum Chromium, Firefox and Safari versions in the locked browser matrix and execute the required checks",
"review_owner": "release-gate-owner",
"review_trigger": "browser minimums or cross-origin isolation policy changes",
"status": "blocked",
"status_check": "verified"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/license-closure.report.json",
"status": "present"
},
{
"path": "coverage/license-source-index.jsonl",
"status": "present"
},
{
"path": "docs/linuxcnc-web-wasm-license-decision.md",
"status": "present"
}
],
"evidence_check": "verified",
"id": "license_closure_incomplete",
"owner": "implementation-license-owner",
"reason": "57 Web/WASM entries have no approved SPDX/NOTICE assignment",
"remediation": "approve SPDX/NOTICE metadata for all 57 Web/WASM implementation entries; upstream declarations do not substitute for it",
"review_owner": "license-audit-reviewer",
"review_trigger": "implementation license metadata or source-root ownership changes",
"status": "blocked",
"status_check": "verified"
}
],
"candidate_eligible": false,
"checks": {
"audit_checks": {
"checks": {
"blocker_review": "verified",
"context_consistency": "verified",
"cross_references": "verified",
"gate_checks": "verified",
"input_hashes": "verified",
"report_self_hashes": "verified"
},
"status": "verified"
},
"audit_self_hash": {
"declared": "515d7c8a1fccc01a04d2c611a3338b06418d178c20b84263c75f145e65b6424f",
"status": "verified"
},
"blocker_preservation": {
"count": 6,
"ids": [
"accepted_entries_missing",
"browser_matrix_unverified",
"configuration_resources_missing",
"license_closure_incomplete",
"manifest_seed_unassigned",
"official_oracle_missing"
],
"status": "verified"
},
"context_consistency": {
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"status": "verified"
},
"input_hashes": {
"artifacts": [
{
"path": "context-lock.json",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "verified"
},
{
"path": "coverage/post-release-audit.report.json",
"sha256": "3ab0c8bf670141cd23a5698bbb94df8416020939b046e1c1b79a1ee9ae69d872",
"status": "verified"
},
{
"path": "coverage/release-candidate-gate.report.json",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "verified"
},
{
"path": "coverage/release-closure.report.json",
"sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"status": "verified"
},
{
"path": "coverage/release-evidence-index.json",
"sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"status": "verified"
}
],
"status": "verified"
},
"locked_schema_contracts": {
"schema_versions": {
"event": 1,
"manifest": 1,
"protocol": 1,
"sim_abi": 1,
"snapshot": 1,
"workspace": 1
},
"status": "verified"
},
"stop_decision": {
"blocked_count": 6,
"status": "verified"
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"errors": {},
"failure_policy": {
"forbidden": [
"delete_samples",
"lower_checks",
"reclassify_as_REAL-EXCLUDED",
"promote_blocked_to_accepted"
],
"on_hash_mismatch": "STOP_RELEASE",
"on_missing_input": "STOP_RELEASE"
},
"inputs": [
{
"bytes": 26665,
"path": "coverage/post-release-audit.report.json",
"role": "post_release_audit",
"sha256": "3ab0c8bf670141cd23a5698bbb94df8416020939b046e1c1b79a1ee9ae69d872",
"status": "present"
},
{
"bytes": 7196,
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "present"
},
{
"bytes": 1945,
"path": "coverage/release-closure.report.json",
"role": "release_closure",
"sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"status": "present"
},
{
"bytes": 1435099,
"path": "coverage/release-evidence-index.json",
"role": "release_evidence_index",
"sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"status": "present"
},
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
}
],
"milestone": "K8",
"milestone_status": "closed_with_release_blockers",
"rebuild": {
"commands": [
"python3 scripts/generate_release_closure.py",
"python3 scripts/generate_post_release_audit.py",
"python3 scripts/generate_k8_milestone_closeout.py",
"node web/worker/test_replay.mjs",
"node web/workspace/test_recovery.mjs",
"node web/workspace/test_migration.mjs",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v"
],
"deterministic_inputs": [
"context-lock.json",
"coverage/post-release-audit.report.json",
"coverage/release-candidate-gate.report.json",
"coverage/release-closure.report.json",
"coverage/release-evidence-index.json"
]
},
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"report_sha256": "5979dc1dd8275cb163bf8eed396a6c6afbe80714a5c9db91ccc8ab7fd5f9720b",
"schema": "k8-milestone-closeout.v1",
"source_baseline": {
"context_lock": {
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
"linuxcnc_source_sha": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"linuxcnc_version": "2.10.0~pre0"
},
"status": "complete",
"task_id": "K8-REL-06"
}

View File

@@ -0,0 +1,189 @@
{
"blockers": [
"wasm_web_license_not_declared"
],
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"download_artifacts": {
"artifacts": [
{
"bytes": 122027,
"license": "MPL-2.0",
"mirror_kind": "official",
"name": "pyodide-lock.json",
"origin_url": "https://github.com/pyodide/pyodide/releases/tag/0.29.4",
"role": "package_lock",
"sha256": "14d2c2dba101277999e17135e653d8f15389ad1437f53eae213bf0c3cdff723d",
"source_url": "https://cdn.jsdelivr.net/pyodide/v0.29.4/full/pyodide-lock.json",
"version": "0.29.4"
},
{
"bytes": 18597,
"license": "MPL-2.0",
"mirror_kind": "official",
"name": "pyodide.js",
"origin_url": "https://github.com/pyodide/pyodide/releases/tag/0.29.4",
"role": "loader_metadata",
"sha256": "7be9e63eddf6bd8786a5ab71c8689c35731d187e3415fe58d7cb95423b50421a",
"source_url": "https://cdn.jsdelivr.net/pyodide/v0.29.4/full/pyodide.js",
"version": "0.29.4"
}
],
"index": {
"bytes": 798,
"path": "vendor/downloads/index.jsonl",
"role": "download_index",
"sha256": "9f62fc263eea03a17222e6ba1f1f73561b9cd361efec9d320f303e64353cd532",
"status": "present"
},
"python_runtime_manifest": {
"bytes": 1902,
"path": "python-runtime/manifest.json",
"role": "python_runtime_manifest",
"schema": "python-runtime-manifest.v1",
"sha256": "340faa6ef5bedcaeb0583f463e6b89cb442ddf1d83d7e6ee8d96cee438fe4213",
"status": "present"
},
"status": "verified"
},
"entry_closure": {
"all_entries_have_evidence_status": true,
"all_entries_have_owner": true,
"closure_status": {
"blocked": 57,
"verified_scoped": 7996
},
"entries": 8053,
"index": {
"bytes": 6448888,
"path": "coverage/license-source-index.jsonl",
"role": "license_source_index",
"sha256": "1580979257ada2bd29b41c6ecc3165dd3c63d64175bac4e2d69194f22b56d5d6",
"status": "present"
},
"license_status": {
"blocked_unassigned": 57,
"scoped_declared": 7996
},
"owner_semantics": "license_audit_owner; does not replace manifest feature owner or promote needs_owner entries",
"owner_status": {
"assigned": 8053
},
"source_hash_status": {
"verified": 8053
},
"status": "blocked"
},
"implementation_license": {
"decision": "unassigned_and_blocked",
"decision_artifact": {
"bytes": 1088,
"path": "docs/linuxcnc-web-wasm-license-decision.md",
"role": "implementation_license_decision",
"sha256": "d5ba9a7bf72f2ac97f2855ae0cd87e11009aa8c879fcc20bdfd0aaaaefbe5dd7",
"status": "present"
},
"entries": 57,
"paths": [
"wasm-core/**",
"web/**"
],
"reason": "no repository LICENSE/NOTICE/SPDX metadata assigns a license to the Web/WASM implementation files; upstream LinuxCNC declarations are not inherited",
"status": "blocked"
},
"real_excluded": {
"runtime_categories": [
"hardware_driver",
"linux_realtime_kernel",
"host_device_access"
],
"status": "explicit",
"test_entry_count": 24,
"test_report_count": 24,
"test_source_ids": [
"test:linuxcnc/tests/build/header-sanity/test.sh",
"test:linuxcnc/tests/build/ui/test.sh",
"test:linuxcnc/tests/halcompile/command_line_flags/test.sh",
"test:linuxcnc/tests/halcompile/serial-out-of-tree/checkresult",
"test:linuxcnc/tests/halcompile/serial-out-of-tree/test.sh",
"test:linuxcnc/tests/hm2-idrom/test.sh",
"test:linuxcnc/tests/interp/g71-endless-loop/test.sh",
"test:linuxcnc/tests/linuxcncrsh-tcp/checkresult",
"test:linuxcnc/tests/linuxcncrsh-tcp/test.sh",
"test:linuxcnc/tests/linuxcncrsh/checkresult",
"test:linuxcnc/tests/linuxcncrsh/test.sh",
"test:linuxcnc/tests/mb2hal/mb2hal.1a/checkresult",
"test:linuxcnc/tests/mb2hal/mb2hal.1b/checkresult",
"test:linuxcnc/tests/mb2hal/mb2hal.2a/checkresult",
"test:linuxcnc/tests/mb2hal/mb2hal.2b/checkresult",
"test:linuxcnc/tests/mdi-queue/oword-queue-buster/test.sh",
"test:linuxcnc/tests/mdi-queue/simple-queue-buster/test.sh",
"test:linuxcnc/tests/mqtt/checkresult",
"test:linuxcnc/tests/mqtt/test.sh",
"test:linuxcnc/tests/t0/nonrandom/test.sh",
"test:linuxcnc/tests/t0/random-with-t0/test.sh",
"test:linuxcnc/tests/t0/random-without-t0/test.sh",
"test:linuxcnc/tests/toolchanger/toolno-pocket-differ/nonrandom/test.sh",
"test:linuxcnc/tests/toolchanger/toolno-pocket-differ/random/test.sh"
]
},
"release_decision": "license_closure_incomplete",
"report_sha256": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa",
"schema": "release-license-closure.v1",
"source_inventory": {
"entries": 8053,
"license_field_in_manifest": false,
"manifest_entries": 8053,
"manifest_license_field_missing_entries": 8053,
"path_roots": {
"linuxcnc": 7996,
"wasm-core": 45,
"web": 12
},
"reason": "manifest v1 has no license field; the external deterministic index below records scoped upstream evidence and keeps unassigned implementation licenses blocked",
"status": "partial",
"unassigned_license_entries": 57
},
"status": "blocked",
"upstream_declarations": {
"declared_spdx_or_legacy_ids": [
"BSD-3-Clause",
"Boost-1.0",
"CC-BY-3.0",
"GFDL-1.1+",
"GNU-All-Permissive",
"GPL-2",
"GPL-2+",
"GPL-3+",
"ISC",
"LGPL-2",
"LGPL-2.1+",
"MIT"
],
"default_scope": "linuxcnc/* (subject to debian/copyright path exceptions)",
"files": [
{
"bytes": 18092,
"path": "linuxcnc/COPYING",
"role": "license_text",
"sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643",
"status": "present"
},
{
"bytes": 43090,
"path": "linuxcnc/COPYING.more",
"role": "license_text",
"sha256": "38a238c64e5e7f81f5232edabfa78c4031bc876bbfc6a3686384cd20a46ed2e4",
"status": "present"
},
{
"bytes": 42172,
"path": "linuxcnc/debian/copyright",
"role": "scoped_license_declarations",
"sha256": "b540e318b4b21e9a993e81baf4f56ee1b79656209fb1eda3738bbd507abb90a0",
"status": "present"
}
],
"matched_entries": 7996,
"status": "verified_scoped"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,170 @@
{
"candidate_eligible": false,
"checks": {
"candidate_tests_not_promoted": {
"count": 6,
"status": "verified"
},
"context_consistency": {
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"status": "verified"
},
"kinematics_official_root": {
"path": "linuxcnc/unit_tests/kinematics",
"present": false,
"status": "verified"
},
"locked_source_commit": {
"actual": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"expected": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"status": "verified"
},
"no_official_goldens_generated": {
"oracle_status": "blocked",
"ready_count": 0,
"status": "verified"
},
"recovery_official_root": {
"paths": {
"linuxcnc/tests/recovery": false,
"linuxcnc/unit_tests/recovery": false
},
"status": "verified"
},
"release_stop_preserved": {
"release_action": "STOP_RELEASE",
"status": "verified"
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"errors": {},
"inputs": [
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
{
"bytes": 274363,
"path": "coverage/kinematics-source-closure.report.json",
"role": "kinematics_source_closure.report",
"sha256": "d3af5e19e38d8dc2640323dcf755d0323e871815087b9eece9d76544b0b0cfa6",
"status": "present"
},
{
"bytes": 20284,
"path": "coverage/kinematics-oracle.report.json",
"role": "kinematics_oracle.report",
"sha256": "4dc09ea52d1cc32cd3c4f006b1c2f880e54ca147e6d4f651cee4b67626f56d05",
"status": "present"
},
{
"bytes": 1446,
"path": "coverage/workspace-recovery.report.json",
"role": "workspace_recovery.report",
"sha256": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"status": "present"
},
{
"bytes": 11539,
"path": "coverage/compatibility-matrix.report.json",
"role": "compatibility_matrix.report",
"sha256": "7e5e946031b080bd22c3630313907383863b61e19d6e959c74c14b2dba112ea7",
"status": "present"
},
{
"bytes": 7196,
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate.report",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "present"
},
{
"bytes": 15574,
"path": "coverage/release-remediation-intake.report.json",
"role": "release_remediation_intake.report",
"sha256": "7d5916f02e12e952ec517d59ff436237d09854c90a961ddbd1375f4598b4c2a3",
"status": "present"
}
],
"locked_source": {
"actual_head": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"kinematics_root": {
"path": "linuxcnc/unit_tests/kinematics",
"present": false
},
"linuxcnc_source_sha": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"recovery_roots": {
"linuxcnc/tests/recovery": false,
"linuxcnc/unit_tests/recovery": false
}
},
"oracle_inventory": {
"kinematics_blocked": 11,
"kinematics_ready": 0,
"kinematics_samples": 11,
"matrixkins_candidate_files": [
{
"official_unit_test": false,
"path": "linuxcnc/tests/matrixkins/README",
"sha256": "3ef06d3af7a4e338a4b74f19c8024af853c129de41a409a171577697154d3728"
},
{
"official_unit_test": false,
"path": "linuxcnc/tests/matrixkins/checkresult",
"sha256": "c210d3eea8c126de9ad24c7f528df7ca22fed21c48399d49f4c852116c6ab1aa"
},
{
"official_unit_test": false,
"path": "linuxcnc/tests/matrixkins/core_sim.hal",
"sha256": "d86a435fc0e55166541ddc67f57ee31035f38f13c1893c24f145b1ea9b96cbe1"
},
{
"official_unit_test": false,
"path": "linuxcnc/tests/matrixkins/test-ui.py",
"sha256": "56f22c5de2655977ae380b57b29ce01213303effe37b484e650b2cd9bc376552"
},
{
"official_unit_test": false,
"path": "linuxcnc/tests/matrixkins/test.ini",
"sha256": "8d799f31b43250680a79d2a4c252d863bea1d2647d8e95e8cf345c2c47bd9fea"
},
{
"official_unit_test": false,
"path": "linuxcnc/tests/matrixkins/test.sh",
"sha256": "c7904d36dcd3842ae2153fb00517f20aec6c71d77d2e6c97148ae391c1295fdd"
}
],
"official_goldens_added": 0,
"official_inputs_added": 0,
"recovery_blocked_samples": [
"worker restart checkpoint",
"native/WASM full-state recovery",
"browser OPFS crash recovery"
]
},
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"remediation_status": "blocked_missing_locked_inputs",
"report_sha256": "bcd7d38f6eb6a7c1cd18ed54e39d7b9e05788a58632eb29dcde13ad2fa55d76d",
"review_policy": {
"forbidden": [
"promote_matrixkins_to_official",
"generate_fake_goldens",
"delete_blocked_samples",
"lower_tolerances",
"overwrite_context_lock_in_place"
],
"required_to_unblock": [
"official kinematics unit-test root in a locked source",
"official recovery oracle inputs or approved contract replacement",
"native/WASM comparison rerun",
"new context_id if source or schema changes"
]
},
"schema": "official-oracle-remediation.v1",
"status": "complete",
"task_id": "K8-REL-10"
}

View File

@@ -0,0 +1,23 @@
{
"schema": "offline-cache-package.v1",
"format_version": 1,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"workspace_package_hash": "fnv1a64:8af9c3e74b9cda3e",
"resources": [
{
"path": "ui/index.html",
"bytes": 21,
"data_hash": "fnv1a64:db95c0842513fd90"
},
{
"path": "wasm/sim.wasm",
"bytes": 10,
"data_hash": "fnv1a64:512e9f0aed5abe69"
}
],
"required_paths": [
"ui/index.html",
"wasm/sim.wasm"
],
"bundle_hash": "fnv1a64:93f6a6df8a047b3f"
}

View File

@@ -0,0 +1,108 @@
{
"candidate_eligible": false,
"checks": {
"context_consistency": {
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"status": "verified"
},
"gate_refresh_allowed": {
"reason": "refresh is forbidden until owner inputs and any new context are reviewed",
"status": "blocked"
},
"owner_inputs_present": {
"present": 0,
"reason": "no owner approval or new locked input is present in the workspace",
"required": 6,
"status": "blocked"
},
"release_stop_preserved": {
"release_action": "STOP_RELEASE",
"status": "verified"
},
"roundup_complete": {
"status": "verified"
},
"six_decisions_located": {
"count": 6,
"status": "verified"
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"decisions": [
{
"blocker": "manifest_seed_unassigned",
"owner_input": "missing",
"required_action": "approve manifest ownership mapping and any new context",
"status": "blocked"
},
{
"blocker": "accepted_entries_missing",
"owner_input": "missing",
"required_action": "supply acceptance evidence with a ready official oracle",
"status": "blocked"
},
{
"blocker": "official_oracle_missing",
"owner_input": "missing",
"required_action": "add or approve locked kinematics and recovery oracle inputs",
"status": "blocked"
},
{
"blocker": "configuration_resources_missing",
"owner_input": "missing",
"required_action": "review and resolve configuration resource candidates",
"status": "blocked"
},
{
"blocker": "browser_matrix_unverified",
"owner_input": "missing",
"required_action": "approve minimums and execute the three-browser matrix",
"status": "blocked"
},
{
"blocker": "license_closure_incomplete",
"owner_input": "missing",
"required_action": "approve SPDX/NOTICE policy for Web/WASM implementation",
"status": "blocked"
}
],
"errors": {},
"inputs": [
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
{
"bytes": 9239,
"path": "coverage/remediation-evidence-roundup.report.json",
"role": "remediation_roundup",
"sha256": "f91376e3cd4a7871610b9881b9f997a627fc205c58919d47c7e7e43b8545dd29",
"status": "present"
},
{
"bytes": 7196,
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "present"
},
{
"bytes": 26665,
"path": "coverage/post-release-audit.report.json",
"role": "post_release_audit",
"sha256": "3ab0c8bf670141cd23a5698bbb94df8416020939b046e1c1b79a1ee9ae69d872",
"status": "present"
}
],
"next_transition": "collect owner decisions, create a new context when locked inputs change, then regenerate closure/audit/gate",
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"report_sha256": "fc7d7160ceae3bed7a9cbc2e942b2abe21af51f17c9e235d86b340868c001246",
"review_status": "owner_input_required",
"schema": "owner-review-gate-refresh.v1",
"status": "blocked",
"task_id": "K8-REL-15"
}

View File

@@ -0,0 +1,687 @@
{
"audit_id": "K8-REL-05",
"blocked_count": 6,
"blockers": [
{
"count": 6249,
"evidence": [
{
"path": "coverage/release-evidence-index.json",
"status": "present"
},
{
"path": "coverage/manifest.jsonl",
"status": "present"
}
],
"evidence_check": "verified",
"id": "manifest_seed_unassigned",
"owner": "manifest-feature-ownership",
"reason": "manifest seed entries still have no feature owner",
"remediation": "assign an accountable feature owner to every needs_owner manifest seed and regenerate the evidence index",
"review_owner": "release-closure-owner",
"review_trigger": "manifest mapping changes or the release closure is regenerated",
"status": "blocked",
"status_check": "verified"
},
{
"count": 0,
"evidence": [
{
"path": "coverage/release-evidence-index.json",
"status": "present"
},
{
"path": "coverage/release-candidate-gate.report.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "accepted_entries_missing",
"owner": "release-acceptance-owner",
"reason": "no manifest entry is accepted for release",
"remediation": "complete acceptance evidence for eligible entries; do not promote blocked or derived entries by relabeling",
"review_owner": "release-gate-owner",
"review_trigger": "any entry changes to accepted or the acceptance policy changes",
"status": "blocked",
"status_check": "verified"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "coverage/kinematics-oracle.report.json",
"status": "present"
},
{
"path": "coverage/workspace-recovery.report.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "official_oracle_missing",
"owner": "upstream-oracle-owner",
"reason": "locked source has no official kinematics/recovery oracle root",
"remediation": "add the locked official kinematics and recovery oracle inputs, then rerun the native/WASM comparison",
"review_owner": "kinematics-recovery-reviewer",
"review_trigger": "the locked source gains or changes an official oracle root",
"status": "blocked",
"status_check": "verified"
},
{
"count": 997,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "coverage/configuration-regression.report.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "configuration_resources_missing",
"owner": "configuration-closure-owner",
"reason": "configuration startup/resource gaps remain",
"remediation": "resolve missing and unindexed configuration resources and unregistered startup references without dropping rows",
"review_owner": "configuration-regression-reviewer",
"review_trigger": "configuration graph or startup preflight counts change",
"status": "blocked",
"status_check": "verified"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "context-lock.json",
"status": "present"
}
],
"evidence_check": "verified",
"id": "browser_matrix_unverified",
"owner": "browser-compatibility-owner",
"reason": "Chromium/Firefox/Safari minimum versions remain TBD",
"remediation": "pin minimum Chromium, Firefox and Safari versions in the locked browser matrix and execute the required checks",
"review_owner": "release-gate-owner",
"review_trigger": "browser minimums or cross-origin isolation policy changes",
"status": "blocked",
"status_check": "verified"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/license-closure.report.json",
"status": "present"
},
{
"path": "coverage/license-source-index.jsonl",
"status": "present"
},
{
"path": "docs/linuxcnc-web-wasm-license-decision.md",
"status": "present"
}
],
"evidence_check": "verified",
"id": "license_closure_incomplete",
"owner": "implementation-license-owner",
"reason": "57 Web/WASM entries have no approved SPDX/NOTICE assignment",
"remediation": "approve SPDX/NOTICE metadata for all 57 Web/WASM implementation entries; upstream declarations do not substitute for it",
"review_owner": "license-audit-reviewer",
"review_trigger": "implementation license metadata or source-root ownership changes",
"status": "blocked",
"status_check": "verified"
}
],
"candidate_eligible": false,
"checks": {
"blocker_review": {
"all_blockers_locatable": true,
"expected_count": 6,
"identity_preserved": true,
"observed_count": 6,
"observed_ids": [
"accepted_entries_missing",
"browser_matrix_unverified",
"configuration_resources_missing",
"license_closure_incomplete",
"manifest_seed_unassigned",
"official_oracle_missing"
],
"status": "verified"
},
"context_consistency": {
"absent_context_id_policy": "allowed only for legacy reports covered by the K8-REL-04 gate",
"all_declared_context_ids_match": true,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"reports": [
{
"context_declared": true,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/release-candidate-gate.report.json",
"status": "verified"
},
{
"context_declared": true,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/release-closure.report.json",
"status": "verified"
},
{
"context_declared": true,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/release-evidence-index.json",
"status": "verified"
},
{
"context_declared": true,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/compatibility-matrix.report.json",
"status": "verified"
},
{
"context_declared": true,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/license-closure.report.json",
"status": "verified"
},
{
"context_declared": false,
"context_id": null,
"path": "coverage/workspace-recovery.report.json",
"status": "verified"
},
{
"context_declared": true,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/workspace-migration.report.json",
"status": "verified"
}
],
"status": "verified"
},
"cross_references": {
"checks": [
{
"actual": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"evidence": "coverage/release-candidate-gate.report.json",
"expected": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"name": "gate_context_id",
"status": "verified"
},
{
"actual": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"evidence": "coverage/release-closure.report.json",
"expected": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"name": "closure_context_id",
"status": "verified"
},
{
"actual": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"evidence": "coverage/release-evidence-index.json",
"expected": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"name": "evidence_index_context_id",
"status": "verified"
},
{
"actual": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"evidence": "coverage/compatibility-matrix.report.json",
"expected": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"name": "compatibility_context_id",
"status": "verified"
},
{
"actual": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"evidence": "coverage/license-closure.report.json",
"expected": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"name": "license_context_id",
"status": "verified"
},
{
"actual": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"evidence": "coverage/workspace-migration.report.json",
"expected": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"name": "migration_context_id",
"status": "verified"
},
{
"actual": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"evidence": "coverage/release-evidence-index.json",
"expected": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"name": "context_lock_hash",
"status": "verified"
},
{
"actual": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44",
"evidence": "coverage/release-closure.report.json",
"expected": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44",
"name": "closure_compatibility_report_hash",
"status": "verified"
},
{
"actual": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa",
"evidence": "coverage/release-closure.report.json",
"expected": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa",
"name": "closure_license_report_hash",
"status": "verified"
},
{
"actual": "d7f0d686152e6317925d7cbf104efdd1980dd5b74bdf503f2f532df18d60c229",
"evidence": "coverage/release-closure.report.json",
"expected": "d7f0d686152e6317925d7cbf104efdd1980dd5b74bdf503f2f532df18d60c229",
"name": "closure_evidence_index_report_hash",
"status": "verified"
},
{
"actual": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44",
"evidence": "coverage/release-evidence-index.json",
"expected": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44",
"name": "index_compatibility_report_hash",
"status": "verified"
},
{
"actual": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa",
"evidence": "coverage/release-evidence-index.json",
"expected": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa",
"name": "index_license_report_hash",
"status": "verified"
},
{
"actual": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"evidence": "coverage/release-evidence-index.json",
"expected": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"name": "index_migration_report_hash",
"status": "verified"
},
{
"actual": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"evidence": "coverage/release-candidate-gate.report.json",
"expected": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"name": "recovery_hash_lock",
"status": "verified"
},
{
"actual": "STOP_RELEASE",
"evidence": "coverage/release-candidate-gate.report.json",
"expected": "STOP_RELEASE",
"name": "gate_release_action",
"status": "verified"
},
{
"actual": false,
"evidence": "coverage/release-candidate-gate.report.json",
"expected": false,
"name": "gate_candidate_eligible",
"status": "verified"
},
{
"actual": "blocked_not_release_candidate",
"evidence": "coverage/release-candidate-gate.report.json",
"expected": "blocked_not_release_candidate",
"name": "gate_release_decision",
"status": "verified"
},
{
"actual": 6,
"evidence": "coverage/release-candidate-gate.report.json",
"expected": 6,
"name": "gate_blocked_count",
"status": "verified"
}
],
"status": "verified"
},
"gate_checks": {
"checks": {
"context_consistency": true,
"evidence_integrity": true,
"license_source_index": true,
"locked_schema_contracts": true,
"migration_rollback_offline": true,
"release_prerequisites_blocked": true
},
"status": "verified"
},
"input_hashes": {
"all_match": true,
"comparisons": [
{
"actual_bytes": 7647,
"actual_sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"expected_bytes": 7647,
"expected_sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"path": "context-lock.json",
"role": "context_lock",
"status": "verified"
},
{
"actual_bytes": 1945,
"actual_sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"expected_bytes": 1945,
"expected_sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"path": "coverage/release-closure.report.json",
"role": "release_closure",
"status": "verified"
},
{
"actual_bytes": 1435099,
"actual_sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"expected_bytes": 1435099,
"expected_sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"path": "coverage/release-evidence-index.json",
"role": "release_evidence_index",
"status": "verified"
},
{
"actual_bytes": 11539,
"actual_sha256": "7e5e946031b080bd22c3630313907383863b61e19d6e959c74c14b2dba112ea7",
"expected_bytes": 11539,
"expected_sha256": "7e5e946031b080bd22c3630313907383863b61e19d6e959c74c14b2dba112ea7",
"path": "coverage/compatibility-matrix.report.json",
"role": "compatibility_matrix",
"status": "verified"
},
{
"actual_bytes": 6571,
"actual_sha256": "b5f2d671fb242df44317bc5549c546790630a95b9644eb0a8a954da03f12bff7",
"expected_bytes": 6571,
"expected_sha256": "b5f2d671fb242df44317bc5549c546790630a95b9644eb0a8a954da03f12bff7",
"path": "coverage/license-closure.report.json",
"role": "license_closure",
"status": "verified"
},
{
"actual_bytes": 6448888,
"actual_sha256": "1580979257ada2bd29b41c6ecc3165dd3c63d64175bac4e2d69194f22b56d5d6",
"expected_bytes": 6448888,
"expected_sha256": "1580979257ada2bd29b41c6ecc3165dd3c63d64175bac4e2d69194f22b56d5d6",
"path": "coverage/license-source-index.jsonl",
"role": "license_source_index",
"status": "verified"
},
{
"actual_bytes": 1446,
"actual_sha256": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"expected_bytes": 1446,
"expected_sha256": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"path": "coverage/workspace-recovery.report.json",
"role": "workspace_recovery",
"status": "verified"
},
{
"actual_bytes": 2225,
"actual_sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"expected_bytes": 2225,
"expected_sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"path": "coverage/workspace-migration.report.json",
"role": "workspace_migration",
"status": "verified"
},
{
"actual_bytes": 3726,
"actual_sha256": "cab2e41ad8e740435d0dd71298f10438fc1a3462dfaaaad28ad98a4315d2aa5f",
"expected_bytes": 3726,
"expected_sha256": "cab2e41ad8e740435d0dd71298f10438fc1a3462dfaaaad28ad98a4315d2aa5f",
"path": "coverage/workspace-rollback.package.json",
"role": "rollback_package",
"status": "verified"
},
{
"actual_bytes": 528,
"actual_sha256": "cfcfbbbd72cd59d576105fc8f0b206e5d08dc63ce803b463c985dd59a3086922",
"expected_bytes": 528,
"expected_sha256": "cfcfbbbd72cd59d576105fc8f0b206e5d08dc63ce803b463c985dd59a3086922",
"path": "coverage/offline-cache.package.json",
"role": "offline_package",
"status": "verified"
},
{
"actual_bytes": 1088,
"actual_sha256": "d5ba9a7bf72f2ac97f2855ae0cd87e11009aa8c879fcc20bdfd0aaaaefbe5dd7",
"expected_bytes": 1088,
"expected_sha256": "d5ba9a7bf72f2ac97f2855ae0cd87e11009aa8c879fcc20bdfd0aaaaefbe5dd7",
"path": "docs/linuxcnc-web-wasm-license-decision.md",
"role": "implementation_license_decision",
"status": "verified"
}
],
"status": "verified"
},
"report_self_hashes": {
"reports": [
{
"actual": "b55d68c975511f6dede4aa1bc03eeae5a815947d6217d7e3a8061fa1cba201b8",
"declared": "b55d68c975511f6dede4aa1bc03eeae5a815947d6217d7e3a8061fa1cba201b8",
"path": "coverage/release-candidate-gate.report.json",
"status": "verified"
},
{
"actual": "2d73eaeaf8cc7798ef1279e309417ee72ba0e122b4ab5312998668208d100fa1",
"declared": "2d73eaeaf8cc7798ef1279e309417ee72ba0e122b4ab5312998668208d100fa1",
"path": "coverage/release-closure.report.json",
"status": "verified"
},
{
"actual": "d7f0d686152e6317925d7cbf104efdd1980dd5b74bdf503f2f532df18d60c229",
"declared": "d7f0d686152e6317925d7cbf104efdd1980dd5b74bdf503f2f532df18d60c229",
"path": "coverage/release-evidence-index.json",
"status": "verified"
},
{
"actual": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44",
"declared": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44",
"path": "coverage/compatibility-matrix.report.json",
"status": "verified"
},
{
"actual": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa",
"declared": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa",
"path": "coverage/license-closure.report.json",
"status": "verified"
},
{
"actual": null,
"declared": null,
"hash_policy": "not_declared_by_legacy_schema",
"path": "coverage/workspace-recovery.report.json",
"status": "verified"
},
{
"actual": "03585551d0519d6e555fbfaa6fc2611da89124751dfd89edf03c55263ecdf283",
"declared": "03585551d0519d6e555fbfaa6fc2611da89124751dfd89edf03c55263ecdf283",
"path": "coverage/workspace-migration.report.json",
"status": "verified"
},
{
"actual": "b55d68c975511f6dede4aa1bc03eeae5a815947d6217d7e3a8061fa1cba201b8",
"declared": "b55d68c975511f6dede4aa1bc03eeae5a815947d6217d7e3a8061fa1cba201b8",
"path": "coverage/release-candidate-gate.report.json",
"status": "verified"
}
],
"status": "verified"
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"errors": {},
"failure_policy": {
"forbidden": [
"delete_samples",
"lower_checks",
"reclassify_as_REAL-EXCLUDED",
"promote_blocked_to_accepted"
],
"on_context_drift": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"on_missing_input": "STOP_RELEASE"
},
"generated_at": "2026-08-21T00:00:00+08:00",
"input_hash_comparison": [
{
"actual_bytes": 7647,
"actual_sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"expected_bytes": 7647,
"expected_sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"path": "context-lock.json",
"role": "context_lock",
"status": "verified"
},
{
"actual_bytes": 1945,
"actual_sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"expected_bytes": 1945,
"expected_sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"path": "coverage/release-closure.report.json",
"role": "release_closure",
"status": "verified"
},
{
"actual_bytes": 1435099,
"actual_sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"expected_bytes": 1435099,
"expected_sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"path": "coverage/release-evidence-index.json",
"role": "release_evidence_index",
"status": "verified"
},
{
"actual_bytes": 11539,
"actual_sha256": "7e5e946031b080bd22c3630313907383863b61e19d6e959c74c14b2dba112ea7",
"expected_bytes": 11539,
"expected_sha256": "7e5e946031b080bd22c3630313907383863b61e19d6e959c74c14b2dba112ea7",
"path": "coverage/compatibility-matrix.report.json",
"role": "compatibility_matrix",
"status": "verified"
},
{
"actual_bytes": 6571,
"actual_sha256": "b5f2d671fb242df44317bc5549c546790630a95b9644eb0a8a954da03f12bff7",
"expected_bytes": 6571,
"expected_sha256": "b5f2d671fb242df44317bc5549c546790630a95b9644eb0a8a954da03f12bff7",
"path": "coverage/license-closure.report.json",
"role": "license_closure",
"status": "verified"
},
{
"actual_bytes": 6448888,
"actual_sha256": "1580979257ada2bd29b41c6ecc3165dd3c63d64175bac4e2d69194f22b56d5d6",
"expected_bytes": 6448888,
"expected_sha256": "1580979257ada2bd29b41c6ecc3165dd3c63d64175bac4e2d69194f22b56d5d6",
"path": "coverage/license-source-index.jsonl",
"role": "license_source_index",
"status": "verified"
},
{
"actual_bytes": 1446,
"actual_sha256": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"expected_bytes": 1446,
"expected_sha256": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"path": "coverage/workspace-recovery.report.json",
"role": "workspace_recovery",
"status": "verified"
},
{
"actual_bytes": 2225,
"actual_sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"expected_bytes": 2225,
"expected_sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"path": "coverage/workspace-migration.report.json",
"role": "workspace_migration",
"status": "verified"
},
{
"actual_bytes": 3726,
"actual_sha256": "cab2e41ad8e740435d0dd71298f10438fc1a3462dfaaaad28ad98a4315d2aa5f",
"expected_bytes": 3726,
"expected_sha256": "cab2e41ad8e740435d0dd71298f10438fc1a3462dfaaaad28ad98a4315d2aa5f",
"path": "coverage/workspace-rollback.package.json",
"role": "rollback_package",
"status": "verified"
},
{
"actual_bytes": 528,
"actual_sha256": "cfcfbbbd72cd59d576105fc8f0b206e5d08dc63ce803b463c985dd59a3086922",
"expected_bytes": 528,
"expected_sha256": "cfcfbbbd72cd59d576105fc8f0b206e5d08dc63ce803b463c985dd59a3086922",
"path": "coverage/offline-cache.package.json",
"role": "offline_package",
"status": "verified"
},
{
"actual_bytes": 1088,
"actual_sha256": "d5ba9a7bf72f2ac97f2855ae0cd87e11009aa8c879fcc20bdfd0aaaaefbe5dd7",
"expected_bytes": 1088,
"expected_sha256": "d5ba9a7bf72f2ac97f2855ae0cd87e11009aa8c879fcc20bdfd0aaaaefbe5dd7",
"path": "docs/linuxcnc-web-wasm-license-decision.md",
"role": "implementation_license_decision",
"status": "verified"
}
],
"inputs": [
{
"bytes": 7196,
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "present"
},
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
}
],
"rebuild": {
"commands": [
"python3 scripts/generate_post_release_audit.py",
"python3 scripts/generate_release_closure.py",
"node web/worker/test_replay.mjs",
"node web/workspace/test_recovery.mjs",
"node web/workspace/test_migration.mjs",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v"
],
"deterministic_inputs": [
"coverage/release-candidate-gate.report.json",
"coverage/release-closure.report.json",
"coverage/release-evidence-index.json",
"coverage/compatibility-matrix.report.json",
"coverage/license-closure.report.json",
"coverage/workspace-recovery.report.json",
"coverage/workspace-migration.report.json",
"coverage/release-candidate-gate.report.json",
"context-lock.json",
"coverage/license-source-index.jsonl",
"coverage/workspace-rollback.package.json",
"coverage/offline-cache.package.json",
"docs/linuxcnc-web-wasm-license-decision.md"
]
},
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"report_sha256": "515d7c8a1fccc01a04d2c611a3338b06418d178c20b84263c75f145e65b6424f",
"schema": "post-release-audit.v1",
"source_baseline": {
"context_lock": {
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
"linuxcnc_source_sha": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"linuxcnc_version": "2.10.0~pre0"
},
"status": "blocked"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
{
"blocked_count": 6,
"blockers": [
{
"count": 6249,
"id": "manifest_seed_unassigned",
"reason": "manifest seed entries still have no feature owner",
"status": "blocked"
},
{
"count": 0,
"id": "accepted_entries_missing",
"reason": "no manifest entry is accepted for release",
"status": "blocked"
},
{
"count": 1,
"id": "official_oracle_missing",
"reason": "locked source has no official kinematics/recovery oracle root",
"status": "blocked"
},
{
"count": 997,
"id": "configuration_resources_missing",
"reason": "configuration startup/resource gaps remain",
"status": "blocked"
},
{
"count": 1,
"id": "browser_matrix_unverified",
"reason": "Chromium/Firefox/Safari minimum versions remain TBD",
"status": "blocked"
},
{
"count": 1,
"id": "license_closure_incomplete",
"reason": "57 Web/WASM entries have no approved SPDX/NOTICE assignment",
"status": "blocked"
}
],
"candidate_eligible": false,
"checks": {
"context_consistency": {
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"context_ids_match": true,
"recovery_report_hash": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"recovery_report_hash_locked": true,
"status": "verified"
},
"evidence_integrity": {
"all_aggregate_integrity_flags": true,
"status": "verified"
},
"license_source_index": {
"all_entries_have_evidence_status": true,
"all_entries_have_owner": true,
"entries": 8053,
"status": "verified"
},
"locked_schema_contracts": {
"compatibility_contract_status": "verified",
"schema_versions": {
"event": 1,
"manifest": 1,
"protocol": 1,
"sim_abi": 1,
"snapshot": 1,
"workspace": 1
},
"status": "verified"
},
"migration_rollback_offline": {
"compatibility_status": "verified",
"migration_status": "passed",
"status": "verified"
},
"release_prerequisites": {
"blocked_count": 6,
"status": "blocked",
"verified_count": 1
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"failure_policy": {
"forbidden": [
"delete_samples",
"lower_checks",
"reclassify_as_REAL-EXCLUDED",
"promote_blocked_to_accepted"
],
"on_blocked_prerequisite": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"on_missing_input": "STOP_RELEASE"
},
"inputs": [
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
{
"bytes": 1945,
"path": "coverage/release-closure.report.json",
"role": "release_closure",
"schema": "release-closure-report.v1",
"sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"status": "blocked"
},
{
"bytes": 1435099,
"path": "coverage/release-evidence-index.json",
"role": "release_evidence_index",
"schema": "release-evidence-index.v1",
"sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"status": "blocked"
},
{
"bytes": 11539,
"oracle_status": "blocked",
"path": "coverage/compatibility-matrix.report.json",
"role": "compatibility_matrix",
"schema": "release-compatibility-matrix.v1",
"sha256": "7e5e946031b080bd22c3630313907383863b61e19d6e959c74c14b2dba112ea7",
"status": "blocked"
},
{
"bytes": 6571,
"path": "coverage/license-closure.report.json",
"role": "license_closure",
"schema": "release-license-closure.v1",
"sha256": "b5f2d671fb242df44317bc5549c546790630a95b9644eb0a8a954da03f12bff7",
"status": "blocked"
},
{
"bytes": 6448888,
"path": "coverage/license-source-index.jsonl",
"role": "license_source_index",
"sha256": "1580979257ada2bd29b41c6ecc3165dd3c63d64175bac4e2d69194f22b56d5d6",
"status": "present"
},
{
"bytes": 1446,
"implementation_status": "verified",
"oracle_status": "blocked",
"path": "coverage/workspace-recovery.report.json",
"role": "workspace_recovery",
"schema": "workspace-recovery-report.v1",
"sha256": "b6140a4fd0f7b7c8ced8c19685cee086d78e27910dc9ecab7d28c201a0ae85b0",
"status": "passed"
},
{
"bytes": 2225,
"implementation_status": "verified",
"oracle_status": "blocked",
"path": "coverage/workspace-migration.report.json",
"role": "workspace_migration",
"schema": "workspace-migration-report.v1",
"sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8",
"status": "passed"
},
{
"bytes": 3726,
"path": "coverage/workspace-rollback.package.json",
"role": "rollback_package",
"schema": "workspace-rollback.v1",
"sha256": "cab2e41ad8e740435d0dd71298f10438fc1a3462dfaaaad28ad98a4315d2aa5f",
"status": "present"
},
{
"bytes": 528,
"path": "coverage/offline-cache.package.json",
"role": "offline_package",
"schema": "offline-cache-package.v1",
"sha256": "cfcfbbbd72cd59d576105fc8f0b206e5d08dc63ce803b463c985dd59a3086922",
"status": "present"
},
{
"bytes": 1088,
"path": "docs/linuxcnc-web-wasm-license-decision.md",
"role": "implementation_license_decision",
"sha256": "d5ba9a7bf72f2ac97f2855ae0cd87e11009aa8c879fcc20bdfd0aaaaefbe5dd7",
"status": "present"
}
],
"rebuild": {
"commands": [
"python3 scripts/generate_release_closure.py",
"node web/worker/test_replay.mjs",
"node web/workspace/test_recovery.mjs",
"node web/workspace/test_migration.mjs",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v"
],
"deterministic_inputs": [
"context-lock.json",
"coverage/release-closure.report.json",
"coverage/release-evidence-index.json",
"coverage/compatibility-matrix.report.json",
"coverage/license-closure.report.json",
"coverage/license-source-index.jsonl",
"coverage/workspace-recovery.report.json",
"coverage/workspace-migration.report.json",
"coverage/workspace-rollback.package.json",
"coverage/offline-cache.package.json",
"docs/linuxcnc-web-wasm-license-decision.md"
]
},
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"report_sha256": "b55d68c975511f6dede4aa1bc03eeae5a815947d6217d7e3a8061fa1cba201b8",
"schema": "release-candidate-gate.v1",
"source_baseline": {
"context_lock": {
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
"linuxcnc_source_sha": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"linuxcnc_version": "2.10.0~pre0"
},
"status": "blocked"
}

View File

@@ -0,0 +1,69 @@
{
"blockers": [
{
"count": 6249,
"id": "manifest_seed_unassigned",
"status": "blocked"
},
{
"count": 0,
"id": "accepted_entries_missing",
"status": "blocked"
},
{
"count": 1,
"id": "official_oracle_missing",
"status": "blocked"
},
{
"count": 997,
"id": "configuration_resources_missing",
"status": "blocked"
},
{
"count": 1,
"id": "browser_matrix_unverified",
"status": "blocked"
},
{
"count": 1,
"id": "license_closure_incomplete",
"status": "blocked"
},
{
"count": 0,
"id": "migration_rollback_pending",
"status": "verified"
}
],
"compatibility": {
"path": "coverage/compatibility-matrix.report.json",
"sha256": "1d41b7af74c24776e820f9305d56b4ab3c2a749a23b611693e68b60fbc4c3c44"
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"evidence_index": {
"path": "coverage/release-evidence-index.json",
"sha256": "d7f0d686152e6317925d7cbf104efdd1980dd5b74bdf503f2f532df18d60c229"
},
"integrity": {
"all_affected_entries_have_required_evidence": true,
"derived_configuration_not_accepted": true,
"implementation_license_blocked_explicitly": true,
"license_source_index_complete": true,
"migration_rollback_offline_verified": true,
"no_orphans": true,
"no_silent_downgrade": true
},
"license": {
"path": "coverage/license-closure.report.json",
"sha256": "2f5ea77d2dd5c3d5afec0330589ff7a8de127f2e9613c25918c117e0d89e8daa"
},
"migration": {
"path": "coverage/workspace-migration.report.json",
"sha256": "ce7451b5c428f170f37c6357877f08f0c2f32dc9502490cae3fe947cc43252e8"
},
"release_decision": "blocked_not_release_candidate",
"report_sha256": "2d73eaeaf8cc7798ef1279e309417ee72ba0e122b4ab5312998668208d100fa1",
"schema": "release-closure-report.v1",
"status": "blocked"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,455 @@
{
"blocked_count": 6,
"candidate_eligible": false,
"checks": {
"blocker_identity": {
"all_items_have_owner_and_evidence": "K8-REL-13",
"expected_count": 6,
"expected_ids": [
"accepted_entries_missing",
"browser_matrix_unverified",
"configuration_resources_missing",
"license_closure_incomplete",
"manifest_seed_unassigned",
"official_oracle_missing"
],
"identity_preserved": true,
"observed_count": 6,
"observed_ids": [
"accepted_entries_missing",
"browser_matrix_unverified",
"configuration_resources_missing",
"license_closure_incomplete",
"manifest_seed_unassigned",
"official_oracle_missing"
],
"status": "verified"
},
"context_consistency": {
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"reports": [
{
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/k8-milestone-closeout.report.json",
"status": "verified"
},
{
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/post-release-audit.report.json",
"status": "verified"
},
{
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/release-candidate-gate.report.json",
"status": "verified"
},
{
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/release-closure.report.json",
"status": "verified"
},
{
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"path": "coverage/release-evidence-index.json",
"status": "verified"
}
],
"status": "verified"
},
"input_hashes": {
"comparisons": [
{
"actual_bytes": 26665,
"actual_sha256": "3ab0c8bf670141cd23a5698bbb94df8416020939b046e1c1b79a1ee9ae69d872",
"expected_bytes": 26665,
"expected_sha256": "3ab0c8bf670141cd23a5698bbb94df8416020939b046e1c1b79a1ee9ae69d872",
"path": "coverage/post-release-audit.report.json",
"role": "post_release_audit",
"status": "verified"
},
{
"actual_bytes": 7196,
"actual_sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"expected_bytes": 7196,
"expected_sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate",
"status": "verified"
},
{
"actual_bytes": 1945,
"actual_sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"expected_bytes": 1945,
"expected_sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"path": "coverage/release-closure.report.json",
"role": "release_closure",
"status": "verified"
},
{
"actual_bytes": 1435099,
"actual_sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"expected_bytes": 1435099,
"expected_sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"path": "coverage/release-evidence-index.json",
"role": "release_evidence_index",
"status": "verified"
},
{
"actual_bytes": 7647,
"actual_sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"expected_bytes": 7647,
"expected_sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"path": "context-lock.json",
"role": "context_lock",
"status": "verified"
}
],
"status": "verified"
},
"no_reclassification": {
"status": "verified"
},
"source_report_self_hashes": {
"reports": [
{
"actual": "5979dc1dd8275cb163bf8eed396a6c6afbe80714a5c9db91ccc8ab7fd5f9720b",
"declared": "5979dc1dd8275cb163bf8eed396a6c6afbe80714a5c9db91ccc8ab7fd5f9720b",
"path": "coverage/k8-milestone-closeout.report.json",
"status": "verified"
},
{
"actual": "515d7c8a1fccc01a04d2c611a3338b06418d178c20b84263c75f145e65b6424f",
"declared": "515d7c8a1fccc01a04d2c611a3338b06418d178c20b84263c75f145e65b6424f",
"path": "coverage/post-release-audit.report.json",
"status": "verified"
},
{
"actual": "b55d68c975511f6dede4aa1bc03eeae5a815947d6217d7e3a8061fa1cba201b8",
"declared": "b55d68c975511f6dede4aa1bc03eeae5a815947d6217d7e3a8061fa1cba201b8",
"path": "coverage/release-candidate-gate.report.json",
"status": "verified"
},
{
"actual": "2d73eaeaf8cc7798ef1279e309417ee72ba0e122b4ab5312998668208d100fa1",
"declared": "2d73eaeaf8cc7798ef1279e309417ee72ba0e122b4ab5312998668208d100fa1",
"path": "coverage/release-closure.report.json",
"status": "verified"
},
{
"actual": "d7f0d686152e6317925d7cbf104efdd1980dd5b74bdf503f2f532df18d60c229",
"declared": "d7f0d686152e6317925d7cbf104efdd1980dd5b74bdf503f2f532df18d60c229",
"path": "coverage/release-evidence-index.json",
"status": "verified"
}
],
"status": "verified"
},
"stop_decision_preserved": {
"release_action": "STOP_RELEASE",
"status": "verified"
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"errors": {},
"failure_policy": {
"forbidden": [
"delete_samples",
"lower_checks",
"reclassify_as_REAL-EXCLUDED",
"promote_blocked_to_accepted"
],
"on_context_drift": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"on_missing_input": "STOP_RELEASE"
},
"generated_at": "2026-08-21T00:00:00+08:00",
"inputs": [
{
"bytes": 10299,
"path": "coverage/k8-milestone-closeout.report.json",
"role": "k8_milestone_closeout",
"sha256": "6fe244b20e501083e4fabd46f51f9f1736c4c289fe7233e77d2f10924fadc823",
"status": "present"
},
{
"bytes": 26665,
"path": "coverage/post-release-audit.report.json",
"role": "post_release_audit",
"sha256": "3ab0c8bf670141cd23a5698bbb94df8416020939b046e1c1b79a1ee9ae69d872",
"status": "present"
},
{
"bytes": 7196,
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "present"
},
{
"bytes": 1945,
"path": "coverage/release-closure.report.json",
"role": "release_closure",
"sha256": "d586d30720723ad9e9375bc7309751bef5db37d34c6c0de98f7f0a7f9b2e6900",
"status": "present"
},
{
"bytes": 1435099,
"path": "coverage/release-evidence-index.json",
"role": "release_evidence_index",
"sha256": "92435805bfcbc19244d90d322293711e0306f445997c233a5dd98ebbc63fd333",
"status": "present"
},
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
}
],
"intake_decision": "remediation_required",
"intake_id": "K8-REL-07",
"next_tasks": [
{
"depends_on": [
"K8-REL-07"
],
"status": "pending",
"task_id": "K8-REL-08",
"title": "manifest ownership remediation"
},
{
"depends_on": [
"K8-REL-07"
],
"status": "pending",
"task_id": "K8-REL-09",
"title": "release acceptance evidence remediation"
},
{
"depends_on": [
"K8-REL-07"
],
"status": "pending",
"task_id": "K8-REL-10",
"title": "official oracle input remediation"
},
{
"depends_on": [
"K8-REL-07"
],
"status": "pending",
"task_id": "K8-REL-11",
"title": "configuration resource remediation"
},
{
"depends_on": [
"K8-REL-07"
],
"status": "pending",
"task_id": "K8-REL-12",
"title": "browser matrix verification"
},
{
"depends_on": [
"K8-REL-07"
],
"status": "pending",
"task_id": "K8-REL-13",
"title": "implementation license closure"
}
],
"rebuild": {
"commands": [
"python3 scripts/generate_release_closure.py",
"python3 scripts/generate_post_release_audit.py",
"python3 scripts/generate_k8_milestone_closeout.py",
"python3 scripts/generate_release_remediation_intake.py",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v"
],
"deterministic_inputs": [
"context-lock.json",
"coverage/k8-milestone-closeout.report.json",
"coverage/post-release-audit.report.json",
"coverage/release-candidate-gate.report.json",
"coverage/release-closure.report.json",
"coverage/release-evidence-index.json"
]
},
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"report_sha256": "683a0ed187fec180705ce42becf3ce6d921018c1d0bfb4ebb99c9a85c768bb49",
"schema": "release-remediation-intake.v1",
"source_baseline": {
"context_lock": {
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
"linuxcnc_source_sha": "239dcf4284762ad7e9c65d390bc5524d855c2f8e",
"linuxcnc_version": "2.10.0~pre0"
},
"status": "complete",
"work_items": [
{
"count": 6249,
"evidence": [
{
"path": "coverage/release-evidence-index.json",
"status": "present"
},
{
"path": "coverage/manifest.jsonl",
"status": "present"
}
],
"id": "manifest_seed_unassigned",
"intake_status": "verified",
"owner": "manifest-feature-ownership",
"reason": "manifest seed entries still have no feature owner",
"release_effect": "STOP_RELEASE",
"remediation": "assign an accountable feature owner to every needs_owner manifest seed and regenerate the evidence index",
"review_owner": "release-closure-owner",
"review_trigger": "manifest mapping changes or the release closure is regenerated",
"sequence": 1,
"status": "blocked",
"task_id": "K8-REL-08",
"title": "manifest ownership remediation"
},
{
"count": 0,
"evidence": [
{
"path": "coverage/release-evidence-index.json",
"status": "present"
},
{
"path": "coverage/release-candidate-gate.report.json",
"status": "present"
}
],
"id": "accepted_entries_missing",
"intake_status": "verified",
"owner": "release-acceptance-owner",
"reason": "no manifest entry is accepted for release",
"release_effect": "STOP_RELEASE",
"remediation": "complete acceptance evidence for eligible entries; do not promote blocked or derived entries by relabeling",
"review_owner": "release-gate-owner",
"review_trigger": "any entry changes to accepted or the acceptance policy changes",
"sequence": 2,
"status": "blocked",
"task_id": "K8-REL-09",
"title": "release acceptance evidence remediation"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "coverage/kinematics-oracle.report.json",
"status": "present"
},
{
"path": "coverage/workspace-recovery.report.json",
"status": "present"
}
],
"id": "official_oracle_missing",
"intake_status": "verified",
"owner": "upstream-oracle-owner",
"reason": "locked source has no official kinematics/recovery oracle root",
"release_effect": "STOP_RELEASE",
"remediation": "add the locked official kinematics and recovery oracle inputs, then rerun the native/WASM comparison",
"review_owner": "kinematics-recovery-reviewer",
"review_trigger": "the locked source gains or changes an official oracle root",
"sequence": 3,
"status": "blocked",
"task_id": "K8-REL-10",
"title": "official oracle input remediation"
},
{
"count": 997,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "coverage/configuration-regression.report.json",
"status": "present"
}
],
"id": "configuration_resources_missing",
"intake_status": "verified",
"owner": "configuration-closure-owner",
"reason": "configuration startup/resource gaps remain",
"release_effect": "STOP_RELEASE",
"remediation": "resolve missing and unindexed configuration resources and unregistered startup references without dropping rows",
"review_owner": "configuration-regression-reviewer",
"review_trigger": "configuration graph or startup preflight counts change",
"sequence": 4,
"status": "blocked",
"task_id": "K8-REL-11",
"title": "configuration resource remediation"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/compatibility-matrix.report.json",
"status": "present"
},
{
"path": "context-lock.json",
"status": "present"
}
],
"id": "browser_matrix_unverified",
"intake_status": "verified",
"owner": "browser-compatibility-owner",
"reason": "Chromium/Firefox/Safari minimum versions remain TBD",
"release_effect": "STOP_RELEASE",
"remediation": "pin minimum Chromium, Firefox and Safari versions in the locked browser matrix and execute the required checks",
"review_owner": "release-gate-owner",
"review_trigger": "browser minimums or cross-origin isolation policy changes",
"sequence": 5,
"status": "blocked",
"task_id": "K8-REL-12",
"title": "browser matrix verification"
},
{
"count": 1,
"evidence": [
{
"path": "coverage/license-closure.report.json",
"status": "present"
},
{
"path": "coverage/license-source-index.jsonl",
"status": "present"
},
{
"path": "docs/linuxcnc-web-wasm-license-decision.md",
"status": "present"
}
],
"id": "license_closure_incomplete",
"intake_status": "verified",
"owner": "implementation-license-owner",
"reason": "57 Web/WASM entries have no approved SPDX/NOTICE assignment",
"release_effect": "STOP_RELEASE",
"remediation": "approve SPDX/NOTICE metadata for all 57 Web/WASM implementation entries; upstream declarations do not substitute for it",
"review_owner": "license-audit-reviewer",
"review_trigger": "implementation license metadata or source-root ownership changes",
"sequence": 6,
"status": "blocked",
"task_id": "K8-REL-13",
"title": "implementation license closure"
}
]
}

View File

@@ -0,0 +1,258 @@
{
"candidate_eligible": false,
"checks": {
"blocker_identity_preserved": {
"count": 6,
"ids": [
"accepted_entries_missing",
"browser_matrix_unverified",
"configuration_resources_missing",
"license_closure_incomplete",
"manifest_seed_unassigned",
"official_oracle_missing"
],
"status": "verified"
},
"context_consistency": {
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"status": "verified"
},
"no_promotion_or_fabrication": {
"status": "verified"
},
"remediation_report_self_hashes": {
"status": "verified"
},
"remediation_reports_complete": {
"reports": {
"K8-REL-08": {
"context": "verified",
"path": "coverage/manifest-ownership-remediation.report.json",
"remediation_status": "blocked_pending_manifest_review",
"report_sha256": "59d06376a4cdd0b7e7983f476a894f9f522b4d9ae133670ab153fd5f6228e179",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-09": {
"context": "verified",
"path": "coverage/release-acceptance-remediation.report.json",
"remediation_status": "blocked_no_eligible_entries",
"report_sha256": "ff9a00c973885443184cf92e9a6ae0a32f95ae7b9c9d6676f5af53c836510db4",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-10": {
"context": "verified",
"path": "coverage/official-oracle-remediation.report.json",
"remediation_status": "blocked_missing_locked_inputs",
"report_sha256": "bcd7d38f6eb6a7c1cd18ed54e39d7b9e05788a58632eb29dcde13ad2fa55d76d",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-11": {
"context": "verified",
"path": "coverage/configuration-resource-remediation.report.json",
"remediation_status": "blocked_resource_gaps",
"report_sha256": "7d8d03e576d83e2a0878275d72dff80fa47aeb6ace4a1f6e447c5e37c6bbc6c1",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-12": {
"context": "verified",
"path": "coverage/browser-matrix-remediation.report.json",
"remediation_status": "blocked_matrix_unverified",
"report_sha256": "7ca6f79dda1aa3ea0b5bbce9ec1d96152a0aa0f5e942563084286290621545c9",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-13": {
"context": "verified",
"path": "coverage/implementation-license-remediation.report.json",
"remediation_status": "blocked_pending_owner_decision",
"report_sha256": "cdf6f6c7db71f0e572057c4c180f442b5cd396aaba1e89e18303494f1d0401ca",
"self_hash": "verified",
"status": "complete"
}
},
"status": "verified"
},
"stop_release_preserved": {
"release_action": "STOP_RELEASE",
"status": "verified"
}
},
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"errors": {},
"inputs": [
{
"bytes": 7647,
"path": "context-lock.json",
"role": "context_lock",
"sha256": "785051d2a826ff45223da0a3dada919b75ac03a4ffb3c7e6f19083e78adc1abc",
"status": "present"
},
{
"bytes": 15574,
"path": "coverage/release-remediation-intake.report.json",
"role": "remediation_intake",
"sha256": "7d5916f02e12e952ec517d59ff436237d09854c90a961ddbd1375f4598b4c2a3",
"status": "present"
},
{
"bytes": 7196,
"path": "coverage/release-candidate-gate.report.json",
"role": "release_candidate_gate",
"sha256": "7ef2381f4ee627a4792cf5448362c31c4bc8b3670ccda53a70b7955093398f67",
"status": "present"
},
{
"bytes": 26665,
"path": "coverage/post-release-audit.report.json",
"role": "post_release_audit",
"sha256": "3ab0c8bf670141cd23a5698bbb94df8416020939b046e1c1b79a1ee9ae69d872",
"status": "present"
},
{
"bytes": 10299,
"path": "coverage/k8-milestone-closeout.report.json",
"role": "k8_closeout",
"sha256": "6fe244b20e501083e4fabd46f51f9f1736c4c289fe7233e77d2f10924fadc823",
"status": "present"
},
{
"bytes": 4628776,
"path": "coverage/manifest-ownership-remediation.report.json",
"role": "K8-REL-08",
"sha256": "6b8b416aa7dd50c1411094d85370af8668334db53dd35dc72a4d46cbb464e4f3",
"status": "present"
},
{
"bytes": 143970,
"path": "coverage/release-acceptance-remediation.report.json",
"role": "K8-REL-09",
"sha256": "153fdbaec1d88f83c21415eb6fa85e03593f70b70917830541521a1cbe3c5880",
"status": "present"
},
{
"bytes": 5541,
"path": "coverage/official-oracle-remediation.report.json",
"role": "K8-REL-10",
"sha256": "c577f1eeeb392022abb39bce666a39cc14346638d767b1be43b6b78734c65f7c",
"status": "present"
},
{
"bytes": 754595,
"path": "coverage/configuration-resource-remediation.report.json",
"role": "K8-REL-11",
"sha256": "64f15f9ffda1004ec95caa6fb962a52bd9e1731a2bffe440ddb346adf256e5fb",
"status": "present"
},
{
"bytes": 6180,
"path": "coverage/browser-matrix-remediation.report.json",
"role": "K8-REL-12",
"sha256": "df201f0165aaebb52df6b5709c1e3a2da8e60ba552a18e03c86d984550bddc9a",
"status": "present"
},
{
"bytes": 41237,
"path": "coverage/implementation-license-remediation.report.json",
"role": "K8-REL-13",
"sha256": "5f244d1c379e72fcbda6a34685e44af5d34175660b5c622c461e3d468db18a0e",
"status": "present"
}
],
"release_action": "STOP_RELEASE",
"release_decision": "blocked_not_release_candidate",
"remaining_owner_actions": [
{
"action": "review proposed workstream roles and approve a manifest/context update",
"blocker": "manifest_seed_unassigned"
},
{
"action": "supply ready official oracle/context evidence before any acceptance",
"blocker": "accepted_entries_missing"
},
{
"action": "add or approve locked official oracle inputs",
"blocker": "official_oracle_missing"
},
{
"action": "review reference candidates and restore/index resources",
"blocker": "configuration_resources_missing"
},
{
"action": "pin minimums and run Chromium/Firefox/Safari matrix",
"blocker": "browser_matrix_unverified"
},
{
"action": "approve SPDX/NOTICE policy for Web/WASM implementation",
"blocker": "license_closure_incomplete"
}
],
"remediation_reports": {
"K8-REL-08": {
"context": "verified",
"path": "coverage/manifest-ownership-remediation.report.json",
"remediation_status": "blocked_pending_manifest_review",
"report_sha256": "59d06376a4cdd0b7e7983f476a894f9f522b4d9ae133670ab153fd5f6228e179",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-09": {
"context": "verified",
"path": "coverage/release-acceptance-remediation.report.json",
"remediation_status": "blocked_no_eligible_entries",
"report_sha256": "ff9a00c973885443184cf92e9a6ae0a32f95ae7b9c9d6676f5af53c836510db4",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-10": {
"context": "verified",
"path": "coverage/official-oracle-remediation.report.json",
"remediation_status": "blocked_missing_locked_inputs",
"report_sha256": "bcd7d38f6eb6a7c1cd18ed54e39d7b9e05788a58632eb29dcde13ad2fa55d76d",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-11": {
"context": "verified",
"path": "coverage/configuration-resource-remediation.report.json",
"remediation_status": "blocked_resource_gaps",
"report_sha256": "7d8d03e576d83e2a0878275d72dff80fa47aeb6ace4a1f6e447c5e37c6bbc6c1",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-12": {
"context": "verified",
"path": "coverage/browser-matrix-remediation.report.json",
"remediation_status": "blocked_matrix_unverified",
"report_sha256": "7ca6f79dda1aa3ea0b5bbce9ec1d96152a0aa0f5e942563084286290621545c9",
"self_hash": "verified",
"status": "complete"
},
"K8-REL-13": {
"context": "verified",
"path": "coverage/implementation-license-remediation.report.json",
"remediation_status": "blocked_pending_owner_decision",
"report_sha256": "cdf6f6c7db71f0e572057c4c180f442b5cd396aaba1e89e18303494f1d0401ca",
"self_hash": "verified",
"status": "complete"
}
},
"report_sha256": "93ab5bc729394f209e281c769a7fb381286f2ae65281414ed23a226f9fff8152",
"review_policy": {
"forbidden": [
"promote_blocked_to_accepted",
"delete_samples",
"lower_checks",
"reclassify_as_REAL-EXCLUDED",
"overwrite_context_lock_in_place"
],
"next_transition": "owner review plus new context/gate regeneration"
},
"roundup_status": "blocked_pending_owner_actions",
"schema": "remediation-evidence-roundup.v1",
"status": "complete",
"task_id": "K8-REL-14"
}

View File

@@ -0,0 +1,72 @@
{
"schema": "workspace-migration-report.v1",
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"status": "passed",
"implementation_status": "verified",
"format": {
"schema": "workspace-package.v1",
"supported_versions": [
1,
2
],
"payload_schemas_unchanged": [
"event-log.v1",
"snapshot.v1",
"simulation-checkpoint.v1",
"workspace-journal.v1"
],
"old_package_immutable": true
},
"migration": {
"status": "passed",
"source_version": 1,
"target_version": 2,
"source_hash": "fnv1a64:8e75915f5667dba2",
"migrated_hash": "fnv1a64:8af9c3e74b9cda3e",
"round_trip_hash": "fnv1a64:8e75915f5667dba2",
"round_trip_equal": true,
"deterministic": true
},
"rollback": {
"status": "passed",
"schema": "workspace-rollback.v1",
"current_hash": "fnv1a64:8e75915f5667dba2",
"target_hash": "fnv1a64:6fa1322865f76dde",
"nearest_committed_seq": 1,
"rollback_hash": "fnv1a64:af7b012f458a6712",
"failure_injection": "before_commit",
"failure_result": "rollback_injected_failure",
"recovery_status": "recovered",
"recovery_hash": "fnv1a64:6fa1322865f76dde"
},
"offline": {
"status": "passed",
"schema": "offline-cache-package.v1",
"bundle_hash": "fnv1a64:93f6a6df8a047b3f",
"required_paths": [
"ui/index.html",
"wasm/sim.wasm"
],
"offline_start": "ready",
"missing_resource_result": "offline_resource_invalid",
"online_degraded_result": "network_required",
"package_path": "coverage/offline-cache.package.json"
},
"rollback_package_path": "coverage/workspace-rollback.package.json",
"recovery_endpoint": {
"status": "verified",
"endpoint": "nearest_committed_package",
"state_hash": "fnv1a64:a89928a5e054ff87",
"no_silent_downgrade": true
},
"oracle": {
"status": "blocked",
"reason": "official migration and browser offline oracle is absent from the locked source",
"blocked_samples": [
"native/WASM package migration",
"browser Service Worker offline startup",
"OPFS rollback after crash"
]
},
"report_sha256": "03585551d0519d6e555fbfaa6fc2611da89124751dfd89edf03c55263ecdf283"
}

View File

@@ -0,0 +1,138 @@
{
"schema": "workspace-rollback.v1",
"format_version": 1,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"current_package_hash": "fnv1a64:8e75915f5667dba2",
"target_package_hash": "fnv1a64:6fa1322865f76dde",
"nearest_committed_seq": 1,
"target": {
"schema": "workspace-package.v1",
"format_version": 1,
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"committed_seq": 1,
"checkpoint": {
"schema": "simulation-checkpoint.v1",
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"simulation_tick": 0,
"event_cursor": 1,
"timeline": {
"schema": "replay-timeline.v1",
"events": {
"schema": "event-log.v1",
"cursor": 1,
"tick": 0,
"events": [
{
"seq": 1,
"tick": 0,
"kind": "SESSION_READY",
"payload": {},
"payload_hash": "fnv1a64:9bf65e00c699fdaf"
}
]
},
"snapshots": {
"schema": "snapshot.v1",
"snapshots": [
{
"schema": "snapshot.v1",
"snapshot_id": "snap:1:fnv1a64:a89928a5e054ff87",
"base_snapshot_id": null,
"seq": 1,
"tick": 0,
"state": {
"tick": 0,
"position": 0
},
"state_hash": "fnv1a64:a89928a5e054ff87"
}
]
}
},
"snapshot": {
"schema": "snapshot.v1",
"snapshot_id": "snap:1:fnv1a64:a89928a5e054ff87",
"base_snapshot_id": null,
"seq": 1,
"tick": 0,
"state": {
"tick": 0,
"position": 0
},
"state_hash": "fnv1a64:a89928a5e054ff87"
},
"state": {
"tick": 0,
"position": 0
},
"state_hash": "fnv1a64:a89928a5e054ff87",
"operations": [],
"workspace_hash": "fnv1a64:771792653168439e",
"checkpoint_hash": "fnv1a64:7b5272719254c0ba"
},
"timeline": {
"schema": "replay-timeline.v1",
"events": {
"schema": "event-log.v1",
"cursor": 1,
"tick": 0,
"events": [
{
"seq": 1,
"tick": 0,
"kind": "SESSION_READY",
"payload": {},
"payload_hash": "fnv1a64:9bf65e00c699fdaf"
}
]
},
"snapshots": {
"schema": "snapshot.v1",
"snapshots": [
{
"schema": "snapshot.v1",
"snapshot_id": "snap:1:fnv1a64:a89928a5e054ff87",
"base_snapshot_id": null,
"seq": 1,
"tick": 0,
"state": {
"tick": 0,
"position": 0
},
"state_hash": "fnv1a64:a89928a5e054ff87"
}
]
}
},
"journal": [
{
"commit_seq": 1,
"kind": "commit",
"operation_id": "op-1",
"prepare_hash": "fnv1a64:e376db989e5d1bb4",
"project_id": "demo",
"schema": "workspace-journal.v1",
"schema_version": 1
},
{
"changes": [
{
"data": "[EMC]\n",
"data_hash": "fnv1a64:0e4e14bd368b857c",
"path": "config/main.ini"
}
],
"commit_seq": 1,
"content_hash": "fnv1a64:e376db989e5d1bb4",
"kind": "prepare",
"operation_id": "op-1",
"project_id": "demo",
"schema": "workspace-journal.v1",
"schema_version": 1
}
],
"content_hash": "fnv1a64:e90b50499dc18b41",
"package_hash": "fnv1a64:6fa1322865f76dde"
},
"rollback_hash": "fnv1a64:af7b012f458a6712"
}

View File

@@ -0,0 +1,22 @@
# Browser Matrix Remediation
`coverage/browser-matrix-remediation.report.json` records K8-REL-12 browser
matrix evidence. The locked context still declares Chromium, Firefox and
Safari minimums as `TBD` and the matrix status as `planned-for-m4`.
This Linux environment can probe installed Chromium and Firefox binaries, but
it has no Safari binary or browser farm. Those smoke probes are recorded as
environment observations only; they do not establish release minimums or
cross-browser compatibility. Safari remains explicitly unavailable and the
overall release conclusion remains `STOP_RELEASE`.
Unblocking requires a reviewed context update pinning all three minimums and
executing the required workflow, offline/OPFS and failure matrix in each
target browser. No browser version is inferred, and the existing context
lock is not overwritten.
Regenerate it with:
```bash
python3 scripts/generate_browser_matrix_remediation.py
```

View File

@@ -0,0 +1,23 @@
# Configuration Resource Remediation
`coverage/configuration-resource-remediation.report.json` is the K8-REL-11
resource closure audit. It retains all 997 missing rows, 91 unresolved rows,
59 unindexed configuration entries and 3 unregistered kinematics startup
entries. It also classifies basename matches found elsewhere in the locked
workspace as review candidates only; no reference is rewritten automatically.
The graph has no orphan entrypoints or unowned templates, so the remaining
work is reference resolution and resource ownership, not deletion of rows.
The artifact resolves zero rows by itself and keeps
`candidate_eligible: false`/`release_action: STOP_RELEASE`.
Unblocking requires an approved search-path/reference contract, restoration or
explicit review of missing resources, complete include/template indexing and
registration or explicit blocking of every kinematics module. A manifest or
context hash change requires a new context rather than an in-place lock edit.
Regenerate it with:
```bash
python3 scripts/generate_configuration_resource_remediation.py
```

View File

@@ -0,0 +1,21 @@
# Implementation License Remediation
`coverage/implementation-license-remediation.report.json` audits all 57
`wasm-core/**` and `web/**` implementation entries. Their source hashes and
audit evidence are complete, but no repository LICENSE/NOTICE/SPDX declaration
assigns a license to them. The report confirms that LinuxCNC's upstream
declarations are not inherited and keeps every entry `blocked_unassigned`.
No SPDX expression is approved by this artifact. Unblocking requires an
explicit repository owner decision covering the SPDX expression,
copyright/NOTICE policy and distribution obligations; metadata changes must
be reviewed under a new context when they alter locked evidence.
The release conclusion remains `blocked_not_release_candidate` with
`release_action: STOP_RELEASE`.
Regenerate it with:
```bash
python3 scripts/generate_implementation_license_remediation.py
```

View File

@@ -0,0 +1,20 @@
# Web/WASM License Decision
Status: `blocked`, pending an explicit repository owner decision.
The files under `wasm-core/**` and `web/**` are implementation work in this
repository. They are not assigned a license by `linuxcnc/debian/copyright`,
and this audit does not infer an upstream LinuxCNC license for them. No
`LICENSE`, `NOTICE`, or `SPDX-License-Identifier` declaration is added by
this task because doing so without an owner decision would be a fabricated
license assignment.
The deterministic per-entry source and license index records these paths with
owner `web-wasm-license-owner`, `license_status: blocked_unassigned`, and this
document as evidence. The entries remain blocked and cannot be promoted to an
accepted release item until the owner supplies an approved SPDX expression,
copyright notice policy, and distribution obligations.
The LinuxCNC source tree is handled separately through the scoped declarations
in `linuxcnc/debian/copyright`; those declarations are matched by path in
`coverage/license-source-index.jsonl` and are not copied onto Web/WASM files.

View File

@@ -0,0 +1,25 @@
# Manifest Ownership Remediation
`coverage/manifest-ownership-remediation.report.json` is the K8-REL-08
ownership assignment for all 6,249 `needs_owner` manifest seeds. It assigns a
deterministic WS-0..WS-7 workstream role from the source prefix and records an
independent review owner, assignment rule, source hash and blocked status for
each row.
The report is intentionally additive. `coverage/manifest.jsonl` is manifest
v1 and has no owner field; changing it in place would invalidate the locked
manifest hash. Therefore the assignment status is
`proposed_blocked_review`, not `accepted`. Encoding a reviewed assignment in
the manifest requires feature owner confirmation, feature/test/oracle
evidence, and a new context id when the manifest hash changes.
The report covers every unassigned seed exactly once, but does not claim that
any seed is implemented, verified or accepted. The release decision remains
`blocked_not_release_candidate` with `release_action: STOP_RELEASE`.
Regenerate and test it with:
```bash
python3 scripts/generate_manifest_ownership_remediation.py
python3 scripts/test_manifest_ownership_remediation.py
```

View File

@@ -0,0 +1,23 @@
# Official Oracle Remediation
`coverage/official-oracle-remediation.report.json` is the K8-REL-10 audit of
the locked LinuxCNC source for official kinematics and recovery oracle inputs.
The locked commit contains `unit_tests/interp` and `unit_tests/tp`, but no
`unit_tests/kinematics` or recovery oracle root. The six `tests/matrixkins`
files are retained as candidates and explicitly marked non-official.
All 11 kinematics oracle samples remain blocked and no official golden is
generated. Recovery oracle scenarios also remain blocked. The report keeps
`candidate_eligible: false` and `release_action: STOP_RELEASE`.
Unblocking requires adding or approving the official inputs under a locked
source (or a reviewed replacement contract), then rerunning native/WASM
comparisons and creating a new context when the source or schema changes.
Nothing in this report promotes a derived configuration, deletes a blocked
sample, or changes the current context lock.
Regenerate it with:
```bash
python3 scripts/generate_official_oracle_remediation.py
```

View File

@@ -0,0 +1,17 @@
# Owner Review Gate Refresh
`coverage/owner-review-gate-refresh.report.json` is the K8-REL-15 final
owner-review check. It locates all six required owner actions but finds no
approval input or new locked context in the workspace, so the report is
`blocked` and the gate refresh is deliberately not run.
The release conclusion remains `blocked_not_release_candidate` with
`release_action: STOP_RELEASE`. Once owners supply decisions, any source,
manifest, browser, schema or license change must create a new context and
regenerate closure, audit, gate and remediation evidence together.
Regenerate it with:
```bash
python3 scripts/generate_owner_review_gate_refresh.py
```

View File

@@ -0,0 +1,22 @@
# Release Acceptance Remediation
`coverage/release-acceptance-remediation.report.json` is the K8-REL-09
acceptance-readiness audit. It evaluates every one of the 1,804 affected
manifest entries against the acceptance contract: owner, inputs, outputs,
test, ready oracle, matching context and implementation/difference evidence.
The result is deterministic: zero entries are eligible and all 1,804 remain
blocked. The 6,249 unassigned seeds remain a separate ownership dependency;
no row is promoted to `accepted`, and no derived or blocked oracle is treated
as an official golden. The report keeps `candidate_eligible: false` and
`release_action: STOP_RELEASE`.
Regenerate it with:
```bash
python3 scripts/generate_release_acceptance_remediation.py
```
The next remediation is K8-REL-10, which requires the locked official
kinematics and recovery oracle inputs before any affected row can become
eligible for acceptance.

View File

@@ -0,0 +1,41 @@
# K8 Release Closure
This is the K8 release evidence package for context `linuxcnc-2.10.0-pre0-wasm-m1-py-c1`. K8-REL-01 records compatibility blockers, K8-REL-02 records migration/rollback/offline recovery, and K8-REL-03 records scoped license/source evidence. It is not a release-candidate declaration. Regenerate it with:
```bash
python3 scripts/generate_release_closure.py
```
The generated artifacts are:
| Artifact | Purpose |
|---|---|
| `coverage/release-closure.report.json` | Aggregate release decision and blocker summary |
| `coverage/release-candidate-gate.report.json` | Final release-candidate gate, blocker reasons, evidence hashes and executable stop/allow action |
| `coverage/compatibility-matrix.report.json` | ABI, protocol, schema, runtime, browser and oracle compatibility matrix |
| `coverage/license-closure.report.json` | Source, upstream license declarations, download metadata and explicit `REAL-EXCLUDED` summary |
| `coverage/license-source-index.jsonl` | Deterministic per-entry source hash, owner, scoped license evidence and closure status |
| `docs/linuxcnc-web-wasm-license-decision.md` | Explicit blocked decision for unassigned Web/WASM SPDX/NOTICE metadata |
| `coverage/workspace-migration.report.json` | Versioned package migration, rollback failure injection and offline cache exercise |
| `coverage/workspace-rollback.package.json` | Hash-addressed rollback target for the nearest committed package |
| `coverage/offline-cache.package.json` | Deterministic offline resource manifest and workspace package reference |
| `coverage/release-evidence-index.json` | Hash-addressed evidence index and affected-manifest audit |
The locked context and the existing source, manifest, Python runtime and configuration report hashes match. ABI/protocol/snapshot/event/workspace/manifest versions are all `1`, and the observed Worker schemas agree with the lock. Native/WASM and Worker evidence remains repeat-stable for the implemented slices.
The release conclusion is explicitly `blocked_not_release_candidate`:
The final gate in `coverage/release-candidate-gate.report.json` independently verifies context consistency, locked schema contracts, evidence integrity, license-source completeness and migration/rollback/offline readiness. It records `candidate_eligible: false`, `release_action: STOP_RELEASE`, and six hard blockers. This is an explicit stop decision, not a release-candidate declaration.
- 6,249 of 8,053 manifest seed entries remain `needs_owner`; none is `accepted`.
- 1,804 affected entries have derived owners from their non-unassigned manifest domain and retain inputs, outputs, test IDs, difference state and oracle status in the evidence index. Their oracle state is `blocked`, never accepted.
- The configuration audit retains 997 missing-resource rows across 445 paths and 194 resource-blocked startup entries. Graph orphans, resource orphans and unowned templates remain zero.
- The locked source has no `linuxcnc/unit_tests/kinematics` official oracle root. Derived configuration and recovery results are therefore not official goldens.
- The browser matrix is still planned with Chromium, Firefox and Safari versions marked `TBD`.
- `coverage/license-source-index.jsonl` contains all 8,053 manifest entries: all source hashes and license-audit owners are present, 7,996 LinuxCNC entries match the most-specific scoped declaration in `linuxcnc/debian/copyright`, and 57 Web/WASM entries remain `blocked_unassigned`. These audit owners do not replace manifest feature owners or promote `needs_owner` entries. The manifest v1 itself remains unchanged and has no license field; the external index is the auditable closure artifact rather than an inferred license assignment.
- The Web/WASM implementation has no repository license/NOTICE/SPDX assignment. `docs/linuxcnc-web-wasm-license-decision.md` records the owner decision required; this remains a K8-REL-03 blocker and no upstream LinuxCNC license is inherited.
- K8-REL-02 is implementation-verified: `workspace-package.v1` supports deterministic v1↔v2 envelope migration without changing locked event/snapshot/checkpoint/journal schemas; tampering and unsupported versions fail closed. A rollback package references the current hash and nearest committed package, never overwrites the old package, and recovers to the committed endpoint after an injected pre-commit failure. The offline bundle has deterministic resource hashes, starts with a complete cache, blocks missing/corrupt resources while offline, and reports `network_required` when online fallback is allowed. The official migration/browser/OPFS oracle is still absent and remains blocked.
`REAL-EXCLUDED` is reserved for physical hardware, hard real-time kernel scheduling and host device/path access. These exclusions are listed in the compatibility and license reports and are not used to hide simulated spindle, tool, probe, HAL or recovery behavior.
The audit and migration exercise do not change ABI, protocol, locked event/snapshot/workspace schema versions, source lock, or simulation behavior. They do not create official golden data or clear the remaining compatibility, resource, browser or license blockers.

View File

@@ -0,0 +1,37 @@
# K8 Release Remediation Intake
`coverage/release-remediation-intake.report.json` is the deterministic intake
for K8-REL-07. It consumes the frozen K8 closeout, post-release audit, release
gate, closure, evidence index and context lock. Regenerate it with:
```bash
python3 scripts/generate_release_remediation_intake.py
```
The intake verifies the source hashes, context identity, blocker identity and
owner/evidence fields before creating six ordered work items (`K8-REL-08` to
`K8-REL-13`). Each item remains `blocked` until its accountable owner supplies
the required evidence and its review trigger is satisfied. Intake completion
therefore means that remediation is traceable; it does not mean that any
release prerequisite is accepted.
The release conclusion remains `blocked_not_release_candidate` with
`release_action: STOP_RELEASE` and `candidate_eligible: false`. The intake
does not alter the ABI, protocol, schema versions, context lock, manifest
classification, license decision, or any prior evidence. Missing inputs,
hash drift, context drift, deleted samples, lowered checks, `REAL-EXCLUDED`
reclassification and promotion of blocked entries all remain stop conditions.
The six work items are ordered by the audit's stable blocker order:
| Task | Blocker | Required owner action |
|---|---|---|
| `K8-REL-08` | `manifest_seed_unassigned` | Assign feature owners to all `needs_owner` seeds and regenerate the index |
| `K8-REL-09` | `accepted_entries_missing` | Produce acceptance evidence for eligible entries |
| `K8-REL-10` | `official_oracle_missing` | Add locked official kinematics/recovery oracle inputs and rerun comparisons |
| `K8-REL-11` | `configuration_resources_missing` | Resolve missing/unindexed resources without dropping rows |
| `K8-REL-12` | `browser_matrix_unverified` | Pin Chromium, Firefox and Safari minimums and execute checks |
| `K8-REL-13` | `license_closure_incomplete` | Approve SPDX/NOTICE metadata for the 57 Web/WASM entries |
Each follow-up task must regenerate the release closure, post-release audit,
milestone closeout and intake before it can change the release conclusion.

View File

@@ -0,0 +1,22 @@
# Remediation Evidence Roundup
`coverage/remediation-evidence-roundup.report.json` is the K8-REL-14
consolidation of the six remediation reports K8-REL-08 through K8-REL-13. It
verifies each report's self-hash and context, preserves the six blocker IDs,
and checks that no remediation artifact promoted entries, generated official
goldens, resolved resources by fiat, pinned an unreviewed browser matrix or
approved an implementation license.
The roundup is complete as an audit, but its `roundup_status` remains
`blocked_pending_owner_actions`. The release conclusion is still
`blocked_not_release_candidate` with `release_action: STOP_RELEASE`.
Remaining transitions require owner review and, where locked inputs change, a
new context followed by closure, audit and gate regeneration. The roundup
does not mutate prior evidence or overwrite the context lock.
Regenerate it with:
```bash
python3 scripts/generate_remediation_evidence_roundup.py
```

View File

@@ -49,6 +49,21 @@
| `K7-RECOVERY-01` | 固定 tick checkpoint/replay 与 workspace recovery 已接入;checkpoint 携带 context、cursor、snapshot/state hash 和 operation ledger;损坏 checkpoint、未知 schema、缺号/重复事件、未确认操作、abort 和恢复边界均 fail-closed;恢复终点与连续运行状态 hash 一致;native `sim_event_import` 与 Worker 对照通过,官方 recovery oracle 继续显式 blocked | `coverage/workspace-recovery.report.json`, `web/worker/replay.mjs`, `web/worker/sim-session.mjs`, `web/workspace/recovery.mjs`, `wasm-core/src/sim_event.c` | | `K7-RECOVERY-01` | 固定 tick checkpoint/replay 与 workspace recovery 已接入;checkpoint 携带 context、cursor、snapshot/state hash 和 operation ledger;损坏 checkpoint、未知 schema、缺号/重复事件、未确认操作、abort 和恢复边界均 fail-closed;恢复终点与连续运行状态 hash 一致;native `sim_event_import` 与 Worker 对照通过,官方 recovery oracle 继续显式 blocked | `coverage/workspace-recovery.report.json`, `web/worker/replay.mjs`, `web/worker/sim-session.mjs`, `web/workspace/recovery.mjs`, `wasm-core/src/sim_event.c` |
| `K8-REG-01` | 统一配置图确定性枚举 264 个运动学入口和 233 个 sim INI 入口;逐项资源/INI/注册表启动预检、缺失与未解析引用保留;图谱孤儿和未拥有模板为零;native/WASM kinematics/motion/feedback/home/IO 与 Worker replay/recovery 重复摘要稳定;官方 kinematics/recovery oracle 继续显式 blocked | `coverage/configuration-regression.report.json`, `scripts/generate_configuration_regression.py`, `scripts/test_configuration_regression.py`, `coverage/manifest.jsonl`, `context-lock.json` | | `K8-REG-01` | 统一配置图确定性枚举 264 个运动学入口和 233 个 sim INI 入口;逐项资源/INI/注册表启动预检、缺失与未解析引用保留;图谱孤儿和未拥有模板为零;native/WASM kinematics/motion/feedback/home/IO 与 Worker replay/recovery 重复摘要稳定;官方 kinematics/recovery oracle 继续显式 blocked | `coverage/configuration-regression.report.json`, `scripts/generate_configuration_regression.py`, `scripts/test_configuration_regression.py`, `coverage/manifest.jsonl`, `context-lock.json` |
| `K8-REL-01` | 兼容性、来源/许可证和发布闭合审计完成;ABI/protocol/schema/context lock 哈希一致;1,804 个已影响 manifest 条目在 evidence index 中保留 owner、输入、输出、测试、差异和 oracle 状态;6,249 个未分配种子、资源缺失、官方 oracle、浏览器矩阵、逐项许可证和迁移/回滚继续显式 blocked,不升级为发布候选 | `coverage/release-closure.report.json`, `coverage/compatibility-matrix.report.json`, `coverage/license-closure.report.json`, `coverage/release-evidence-index.json`, `docs/linuxcnc-web-wasm-release-closure.md`, `scripts/generate_release_closure.py`, `scripts/test_release_closure.py` |
| `K8-REL-02` | workspace-package v1↔v2 封套迁移双向 hash 闭合;旧 event/snapshot/checkpoint/journal schema 保持不变且损坏、版本、上下文不兼容均 fail-closed;回滚包只生成新目标包,失败注入后恢复到最近已提交 seq=1;离线资源完整缓存 ready、缺件阻断、联网降级显式;迁移项在 release evidence index 标记 verified,K8-REL-01 的官方 oracle/资源/浏览器/许可证阻塞保留 | `coverage/workspace-migration.report.json`, `coverage/workspace-rollback.package.json`, `coverage/offline-cache.package.json`, `web/workspace/migration.mjs`, `web/workspace/test_migration.mjs`, `coverage/release-evidence-index.json`, `coverage/compatibility-matrix.report.json` |
| `K8-REL-03` | 生成 8,053 条确定性 license/source index;全部来源 hash、审计责任人和证据状态完整;7,996 条 LinuxCNC 条目按最具体 Debian copyright scope 映射 SPDX,57 条 wasm-core/web 条目明确保持 `blocked_unassigned`;实现许可证决策写入独立文档,release 仍保留实现许可证阻塞,不升级为发布候选 | `coverage/license-source-index.jsonl`, `coverage/license-closure.report.json`, `docs/linuxcnc-web-wasm-license-decision.md`, `coverage/release-evidence-index.json`, `scripts/test_license_source_closure.py` |
| `K8-REL-04` | 最终 release-candidate gate 独立汇总 closure 报告、context lock、schema contracts、license/source index、恢复和迁移证据;context/recovery hash、ABI/protocol/schema、license index、migration/rollback/offline checks verified;6 个硬阻塞保留,明确 `candidate_eligible=false`、`release_action=STOP_RELEASE`,不升级为发布候选 | `coverage/release-candidate-gate.report.json`, `coverage/release-closure.report.json`, `scripts/generate_release_closure.py`, `scripts/test_release_candidate_gate.py` |
| `K8-REL-05` | post-release audit 逐项比较 gate 的 11 个输入哈希、closure 交叉引用和 context/schema/recovery/migration 检查;审计检查全部 verified,6 个 blocker 均有 owner、review owner、证据路径和 remediation;整体仍明确 `blocked`/`STOP_RELEASE`,未清除或重分类阻塞 | `coverage/post-release-audit.report.json`, `scripts/generate_post_release_audit.py`, `scripts/test_post_release_audit.py` |
| `K8-REL-06` | K8 milestone closeout 冻结 post-release audit、release gate、closure、evidence index 和 context lock;输入哈希、审计自校验、context/schema、六项 blocker 保留和 `STOP_RELEASE` 均 verified;里程碑以 `closed_with_release_blockers` 结束,不升级为发布候选 | `coverage/k8-milestone-closeout.report.json`, `scripts/generate_k8_milestone_closeout.py`, `scripts/test_k8_milestone_closeout.py`, `coverage/post-release-audit.report.json` |
| `K8-REL-07` | remediation intake 校验 closeout、audit、gate、closure、evidence index 和 context lock 的输入哈希与 context;六个 blocker identity、owner/evidence/review trigger 和 `STOP_RELEASE` 均 verified;生成 `K8-REL-08..13` 有序后续工作项,但全部保持 blocked,不升级为发布候选 | `coverage/release-remediation-intake.report.json`, `scripts/generate_release_remediation_intake.py`, `scripts/test_release_remediation_intake.py`, `docs/linuxcnc-web-wasm-release-remediation-intake.md` |
| `K8-REL-08` | 为全部 6,249 个 `needs_owner` manifest seed 生成确定性 WS-0/WS-1/WS-2/WS-6 workstream owner 和 review owner;源哈希、唯一覆盖、context 与 `STOP_RELEASE` 均 verified;assignment 保持 `proposed_blocked_review`,不修改 manifest/context lock、不升级 accepted,下一步处理 acceptance evidence | `coverage/manifest-ownership-remediation.report.json`, `scripts/generate_manifest_ownership_remediation.py`, `scripts/test_manifest_ownership_remediation.py`, `docs/linuxcnc-web-wasm-manifest-ownership-remediation.md` |
| `K8-REL-09` | acceptance readiness 审计覆盖全部 1,804 affected entry;owner/inputs/outputs/test 字段虽可追踪,但 oracle/context 阻塞导致 eligible=0,manifest accepted=0;6 项检查与 `STOP_RELEASE` verified,不 promotion 或重分类,下一步处理 official oracle | `coverage/release-acceptance-remediation.report.json`, `scripts/generate_release_acceptance_remediation.py`, `scripts/test_release_acceptance_remediation.py`, `docs/linuxcnc-web-wasm-release-acceptance-remediation.md` |
| `K8-REL-10` | 锁定 source commit 与 context 匹配;`linuxcnc/unit_tests/kinematics`、recovery roots 缺失,6 个 matrixkins 文件明确非官方,11 个 kinematics samples/recovery scenarios 保持 blocked;未新增官方输入或 golden,`STOP_RELEASE` verified,下一步处理 configuration resources | `coverage/official-oracle-remediation.report.json`, `scripts/generate_official_oracle_remediation.py`, `scripts/test_official_oracle_remediation.py`, `docs/linuxcnc-web-wasm-official-oracle-remediation.md` |
| `K8-REL-11` | 配置资源 remediation 保留 16,093 rows:997 missing、91 unresolved、59 unindexed、3 unregistered startup;956 basename candidates 仅作只读审查,实际 resolve=0,资源图 orphan=0,`STOP_RELEASE` verified,下一步处理 browser matrix | `coverage/configuration-resource-remediation.report.json`, `scripts/generate_configuration_resource_remediation.py`, `scripts/test_configuration_resource_remediation.py`, `docs/linuxcnc-web-wasm-configuration-resource-remediation.md` |
| `K8-REL-12` | browser matrix remediation 读取锁定 TBD minimums;Chromium 151/Firefox 154 headless smoke passed,Safari 在 Linux 环境 unavailable,时间戳输出归一化且所有结果显式;`STOP_RELEASE` verified,不就地修改 context lock,下一步处理 implementation license | `coverage/browser-matrix-remediation.report.json`, `scripts/generate_browser_matrix_remediation.py`, `scripts/test_browser_matrix_remediation.py`, `docs/linuxcnc-web-wasm-browser-matrix-remediation.md` |
| `K8-REL-13` | implementation license remediation 覆盖 57/57 wasm-core/web entries;源哈希与 audit evidence verified,blocked_unassigned=57、approved_spdx=0、未批准声明=0,上游 license 不继承,`STOP_RELEASE` verified,下一步汇总 remediation evidence | `coverage/implementation-license-remediation.report.json`, `scripts/generate_implementation_license_remediation.py`, `scripts/test_implementation_license_remediation.py`, `docs/linuxcnc-web-wasm-implementation-license-remediation.md` |
| `K8-REL-14` | remediation evidence roundup 汇总 K8-REL-08..13;6 份报告 self-hash/context、六个 blocker identity、无 promotion/fabrication 和 gate/audit/closeout/intake 的 `STOP_RELEASE` 均 verified;状态为 `blocked_pending_owner_actions`,下一步 owner review 与新 context/gate refresh | `coverage/remediation-evidence-roundup.report.json`, `scripts/generate_remediation_evidence_roundup.py`, `scripts/test_remediation_evidence_roundup.py`, `docs/linuxcnc-web-wasm-remediation-evidence-roundup.md` |
| `K8-REL-15` | owner-review gate refresh 定位六项 owner decision,但 workspace 没有批准输入或新 context;owner_inputs=0、gate_refresh=blocked,`STOP_RELEASE` verified,任务保持 blocked,后续仅在 owner 决策后创建新 context 并刷新 gate | `coverage/owner-review-gate-refresh.report.json`, `scripts/generate_owner_review_gate_refresh.py`, `scripts/test_owner_review_gate_refresh.py`, `docs/linuxcnc-web-wasm-owner-review-gate-refresh.md` |
## 追溯规则 ## 追溯规则

View File

@@ -9,18 +9,18 @@ pointer_version: 3
updated: 2026-08-21 updated: 2026-08-21
context_id: linuxcnc-2.10.0-pre0-wasm-m1-py-c1 context_id: linuxcnc-2.10.0-pre0-wasm-m1-py-c1
milestone: K8 milestone: K8
last_verified_checkpoint: K7-RECOVERY-01-r01 last_verified_checkpoint: K8-REL-15-r01
task: task:
id: K8-REL-01 id: K8-REL-15
status: pending status: blocked
title: compatibility and release closure title: owner review and release gate refresh
owner: WS-0 owner: WS-7
profile: milestone profile: milestone
next: next:
id: K8-REL-02 id: K8-REL-16
status: pending status: pending
title: migration and rollback closure title: new context and release gate refresh after owner decisions
owner: WS-0 owner: WS-7
profile: milestone profile: milestone
``` ```
@@ -41,17 +41,17 @@ python3 scripts/validate_task_pointer.py
## Task ## Task
**目标**:完成兼容性与发布闭合审计,确认 K0-K8 已产出可重建的来源、manifest、ABI/protocol/runtime、配置回归和恢复证据,并把未闭合输入、REAL-EXCLUDED 项和官方 oracle 阻塞明确交给后续发布门禁。 **目标**:检查 K8-REL-14 roundup 后是否存在六项 owner decision 或新锁定输入,并在缺失时 fail-closed;保留六项阻塞和 `STOP_RELEASE`。
**直接输入**:`coverage/configuration-regression.report.json`、`coverage/manifest.jsonl`、`coverage/source-index.jsonl`、`context-lock.json`、`python-runtime/manifest.json`、`docs/linuxcnc-web-wasm-full-port-implementation.md`。 **直接输入**:`coverage/remediation-evidence-roundup.report.json`、`coverage/release-candidate-gate.report.json`、`coverage/post-release-audit.report.json`、`context-lock.json`。
**必须产出**:兼容性矩阵、来源/许可证闭合摘要、发布阻塞项和可重建 release evidence index;确认 ABI/protocol/schema/context lock 一致,不将 blocked 或派生配置冒充 accepted。 **必须产出**:`coverage/owner-review-gate-refresh.report.json`、六项 owner decision 缺口和可重建的 blocked release 结论;没有 owner 输入时不得刷新 gate、升级 blocked、删除样本或就地覆盖 context lock。
**完成条件**:受影响 manifest 条目均有 owner、输入、输出、测试、差异和 oracle 状态;无孤儿、无未解释差异、无静默降级;release 结论对缺失官方 oracle、资源和浏览器矩阵保持显式 blocked。 **完成条件**:六项 owner decision 状态逐项可定位;当前工作区无批准输入时,task 必须为 `blocked`,gate refresh 必须 blocked,`STOP_RELEASE` 必须 verified。
**非目标**:不修改 ABI/protocol/schema,不伪造许可证或官方 golden,不实现迁移/回滚工具(由 K8-REL-02 负责),不把当前报告升级为发布候选。 **非目标**:不替 owner 做 manifest/context/许可证/browser/oracle 决策,不修改 ABI/protocol/schema/context lock,不升级 accepted,不清除其他 K8 release blockers。
**回滚点**:保留 `K8-REG-01` 报告和原 context lock;删除仅限发布审计的索引/文档扩展即可恢复,不回滚已验证运行时。 **回滚点**:保留 K8-REL-01..14 全部报告、原 manifest、license source index 和 context lock;owner-review report 为新增证据,删除它即可回到 roundup 状态。
## Next ## Next
@@ -60,19 +60,29 @@ python3 scripts/validate_task_pointer.py
## Verify ## Verify
```bash ```bash
python3 scripts/generate_source_inventory.py python3 scripts/generate_release_closure.py
python3 scripts/generate_configuration_regression.py python3 scripts/generate_post_release_audit.py
cmake --build build-native && ctest --test-dir build-native --output-on-failure python3 scripts/generate_k8_milestone_closeout.py
python3 scripts/generate_release_remediation_intake.py
python3 scripts/generate_manifest_ownership_remediation.py
python3 scripts/generate_release_acceptance_remediation.py
python3 scripts/generate_official_oracle_remediation.py
python3 scripts/generate_configuration_resource_remediation.py
python3 scripts/generate_browser_matrix_remediation.py
python3 scripts/generate_implementation_license_remediation.py
python3 scripts/generate_remediation_evidence_roundup.py
python3 scripts/generate_owner_review_gate_refresh.py
node web/worker/test_replay.mjs node web/worker/test_replay.mjs
node web/workspace/test_recovery.mjs node web/workspace/test_recovery.mjs
node web/workspace/test_migration.mjs
python3 scripts/validate_baseline.py python3 scripts/validate_baseline.py
python3 scripts/validate_task_pointer.py python3 scripts/validate_task_pointer.py
python3 -m unittest discover -s scripts -p 'test_*.py' -v python3 -m unittest discover -s scripts -p 'test_*.py' -v
python3 scripts/validate_download_index.py python3 scripts/validate_download_index.py
rg -n 'REAL-EXCLUDED|blocked|accepted|configuration-regression' coverage/manifest.jsonl coverage/configuration-regression.report.json docs rg -n 'REAL-EXCLUDED|blocked|accepted|release-evidence|ownership|acceptance|oracle|resource|browser|remediation|migration|rollback|SPDX|NOTICE|license|release candidate|STOP_RELEASE|post-release' coverage docs
``` ```
最近证据:`coverage/configuration-regression.report.json` 已枚举 264 个运动学入口和 233 个 sim 配置入口;图谱孤儿、未拥有模板和资源孤儿均为零,native/WASM 及 Worker 固定 tick/recovery 摘要重复稳定。资源缺失、未注册模块、官方 oracle 和完整浏览器矩阵继续显式 blocked。下一任务执行兼容性与发布闭合审计。 最近证据:K8-REL-15 的 `coverage/owner-review-gate-refresh.report.json` 已定位六项 owner decision,owner inputs=0、gate refresh=blocked,`STOP_RELEASE` verified;下一步等待 owner 提供决定后创建新 context 并刷新 gate。
## Stop ## Stop

View File

@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""Generate deterministic K8-REL-12 browser matrix remediation evidence."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
COMPATIBILITY = Path("coverage/compatibility-matrix.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
INTAKE = Path("coverage/release-remediation-intake.report.json")
OUTPUT = Path("coverage/browser-matrix-remediation.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def stable_output(value: str) -> str:
"""Remove browser diagnostic timestamps from repeatability evidence."""
return re.sub(r"\b\d{2}:\d{2}:\d{2}(?:\.\d+)?\b", "<time>", value)
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def run_command(command: list[str], timeout: int = 20) -> dict[str, Any]:
try:
completed = subprocess.run(command, check=False, capture_output=True, text=True, timeout=timeout)
except (OSError, subprocess.TimeoutExpired) as exc:
return {"status": "blocked", "command": command, "reason": type(exc).__name__}
stdout = stable_output(completed.stdout.strip())
stderr = stable_output(completed.stderr.strip())
return {
"status": "passed" if completed.returncode == 0 else "blocked",
"command": command,
"exit_code": completed.returncode,
"stdout_sha256": digest(stdout),
"stderr_sha256": digest(stderr),
"stdout_preview": stdout[:160],
"stderr_preview": stderr[:160],
}
def browser_probe(root: Path, browser: str, binary_names: list[str]) -> dict[str, Any]:
binary = next((shutil.which(name) for name in binary_names if shutil.which(name)), None)
if binary is None:
return {"browser": browser, "binary": None, "version": None, "version_status": "blocked_unavailable", "smoke": {"status": "blocked_unavailable", "reason": "binary_not_installed"}}
version = run_command([binary, "--version"])
with tempfile.TemporaryDirectory(prefix="browser-matrix-") as directory:
screenshot = Path(directory) / f"{browser}.png"
if browser == "chromium":
command = [binary, "--headless=new", "--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage", "--dump-dom", "data:text/html,<title>LinuxCNC%20WASM%20smoke</title>"]
else:
command = [binary, "--headless", "--screenshot", str(screenshot), "data:text/html,<title>LinuxCNC%20WASM%20smoke</title>"]
smoke = run_command(command)
if browser != "chromium":
smoke["command"] = ["<browser>", "--headless", "--screenshot", f"<temp>/{browser}.png", command[-1]]
if screenshot.is_file():
smoke["screenshot_sha256"] = file_digest(screenshot)
smoke["screenshot_bytes"] = screenshot.stat().st_size
return {
"browser": browser,
"binary": binary,
"version": version.get("stdout_preview"),
"version_status": "verified" if version.get("status") == "passed" else "blocked",
"version_probe": version,
"smoke": smoke,
}
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
lock, lock_error = read_json(root, CONTEXT)
compatibility, compatibility_error = read_json(root, COMPATIBILITY)
gate, gate_error = read_json(root, GATE)
intake, intake_error = read_json(root, INTAKE)
errors = {
path.as_posix(): error
for path, error in ((CONTEXT, lock_error), (COMPATIBILITY, compatibility_error), (GATE, gate_error), (INTAKE, intake_error))
if error
}
declared = lock.get("browser_matrix", {})
probes = [
browser_probe(root, "chromium", ["chromium", "chromium-browser", "google-chrome"]),
browser_probe(root, "firefox", ["firefox"]),
browser_probe(root, "safari", ["safari"]),
]
probe_by_name = {row["browser"]: row for row in probes}
smoke_observed = {name: probe_by_name[name]["smoke"].get("status") for name in ("chromium", "firefox", "safari")}
declared_minimum = declared.get("minimum", {})
minimums_tbd = all(declared_minimum.get(name) in {None, "", "TBD"} for name in ("chromium", "firefox", "safari"))
stop_ok = (
gate.get("release_action") == "STOP_RELEASE"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and intake.get("release_action") == "STOP_RELEASE"
and intake.get("candidate_eligible") is False
)
checks = {
"locked_matrix_read": {"status": "verified" if minimums_tbd and declared.get("status") == "planned-for-m4" else "blocked", "minimum": declared_minimum, "declared_status": declared.get("status")},
"chromium_probe": {"status": "verified" if probe_by_name["chromium"]["version_status"] == "verified" else "blocked", "version": probe_by_name["chromium"]["version"], "smoke": smoke_observed["chromium"]},
"firefox_probe": {"status": "verified" if probe_by_name["firefox"]["version_status"] == "verified" else "blocked", "version": probe_by_name["firefox"]["version"], "smoke": smoke_observed["firefox"]},
"safari_probe": {"status": "verified", "version": probe_by_name["safari"]["version"], "smoke": smoke_observed["safari"], "reason": "Safari is not available in this Linux environment"},
"no_unverified_result_hidden": {"status": "verified" if all(value in {"passed", "blocked", "blocked_unavailable"} for value in smoke_observed.values()) else "blocked", "smoke": smoke_observed},
"context_consistency": {"status": "verified" if lock.get("context_id") and compatibility.get("context_id") == lock.get("context_id") and gate.get("context_id") == lock.get("context_id") else "blocked", "context_id": lock.get("context_id")},
"release_stop_preserved": {"status": "verified" if stop_ok else "blocked", "release_action": "STOP_RELEASE" if stop_ok else None},
}
checks_ok = not errors and all(value["status"] == "verified" for value in checks.values())
report: dict[str, Any] = {
"schema": "browser-matrix-remediation.v1",
"context_id": lock.get("context_id"),
"task_id": "K8-REL-12",
"status": "complete" if checks_ok else "blocked",
"remediation_status": "blocked_matrix_unverified",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"declared_matrix": declared,
"observed_browsers": probes,
"checks": checks,
"inputs": [artifact(root, CONTEXT, "context_lock"), artifact(root, COMPATIBILITY, "compatibility_matrix"), artifact(root, GATE, "release_candidate_gate"), artifact(root, INTAKE, "remediation_intake")],
"review_policy": {
"required_to_unblock": ["pin minimum Chromium, Firefox and Safari versions in a reviewed context lock", "run the required workflow and failure matrix in each target browser", "record cross-origin isolation and offline/OPFS behavior"],
"current_environment_limit": "Safari binary and browser farm are unavailable; observed Chromium/Firefox probes do not establish a release minimum",
"forbidden": ["infer Safari support from Chromium/Firefox", "pin versions without a new reviewed context", "mark planned matrix verified by smoke-only probes", "overwrite_context_lock_in_place"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("browser_matrix_remediation=" + report["status"] + " release=" + report["release_action"] + " safari=" + report["observed_browsers"][2]["smoke"]["status"])
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""Generate deterministic K8-REL-11 configuration resource remediation."""
from __future__ import annotations
import argparse
import hashlib
import json
from collections import Counter
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
RESOURCES = Path("coverage/config-resources.jsonl")
RESOURCE_REPORT = Path("coverage/config-resources.report.json")
REGRESSION = Path("coverage/configuration-regression.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
INTAKE = Path("coverage/release-remediation-intake.report.json")
OUTPUT = Path("coverage/configuration-resource-remediation.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def read_jsonl(root: Path, path: Path) -> tuple[list[dict[str, Any]], str | None]:
try:
rows = []
with (root / path).open(encoding="utf-8") as stream:
for line_number, line in enumerate(stream, 1):
if not line.strip():
continue
value = json.loads(line)
if not isinstance(value, dict):
raise ValueError(f"{path}:{line_number} must contain an object")
rows.append(value)
return rows, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return [], str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def basename_token(row: dict[str, Any]) -> str:
raw = str(row.get("raw_value", "")).strip().strip('"').strip("'")
token = raw.split()[0] if raw else str(row.get("path", "")).split("/")[-1]
token = token.removeprefix("LIB:")
return token.split("/")[-1]
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
resource_rows, resource_error = read_jsonl(root, RESOURCES)
resource_report, resource_report_error = read_json(root, RESOURCE_REPORT)
regression, regression_error = read_json(root, REGRESSION)
lock, lock_error = read_json(root, CONTEXT)
gate, gate_error = read_json(root, GATE)
intake, intake_error = read_json(root, INTAKE)
errors = {
path.as_posix(): error
for path, error in (
(RESOURCES, resource_error),
(RESOURCE_REPORT, resource_report_error),
(REGRESSION, regression_error),
(CONTEXT, lock_error),
(GATE, gate_error),
(INTAKE, intake_error),
)
if error
}
file_index: dict[str, list[str]] = {}
for path in sorted(root.rglob("*")):
if path.is_file() and ".git" not in path.parts:
file_index.setdefault(path.name, []).append(path.relative_to(root).as_posix())
missing = [row for row in resource_rows if row.get("parse_status") == "missing"]
unresolved = [row for row in resource_rows if row.get("parse_status") in {"host_or_variable_path", "outside_repo", "unresolved"}]
missing_rows = []
candidate_counts: Counter[str] = Counter()
for row in sorted(missing, key=lambda item: str(item.get("resource_source_id"))):
token = basename_token(row)
candidates = file_index.get(token, [])
classification = "candidate_basename_match" if candidates else "no_candidate"
if any(marker in str(row.get("raw_value", "")) for marker in ("LIB:", "\\", " -", " --", "WORKINGFOLDER")):
classification = "candidate_requires_reference_resolution" if candidates else "reference_requires_resolution"
candidate_counts[classification] += 1
missing_rows.append({
"resource_source_id": row.get("resource_source_id"),
"entrypoint": row.get("entrypoint"),
"path": row.get("path"),
"raw_value": row.get("raw_value"),
"edge_kind": row.get("edge_kind"),
"resource_kind": row.get("resource_kind"),
"parse_status": row.get("parse_status"),
"basename_token": token,
"candidate_paths": candidates[:8],
"remediation_status": "blocked_pending_reference_review",
})
unresolved_rows = [
{
"resource_source_id": row.get("resource_source_id"),
"entrypoint": row.get("entrypoint"),
"path": row.get("path"),
"raw_value": row.get("raw_value"),
"edge_kind": row.get("edge_kind"),
"parse_status": row.get("parse_status"),
"remediation_status": "blocked_unresolved_reference",
}
for row in sorted(unresolved, key=lambda item: str(item.get("resource_source_id")))
]
config_entries = regression.get("configuration_entries", []) if isinstance(regression.get("configuration_entries"), list) else []
unindexed_entries = [
{"config_id": row.get("config_id"), "path": row.get("path"), "startup_preflight": row.get("startup_preflight"), "resource_status": row.get("resource_status")}
for row in config_entries
if row.get("resource_status") == "unindexed"
]
unregistered_entries = [
{"config_id": row.get("config_id"), "path": row.get("path"), "module": row.get("module"), "startup_preflight": row.get("startup_preflight")}
for row in config_entries
if row.get("startup_preflight") == "blocked_unregistered_kinematics"
]
counts = regression.get("counts", {})
expected_missing = counts.get("resource_missing_rows")
expected_unresolved = counts.get("resource_unresolved_rows")
expected_unindexed = counts.get("resource_status", {}).get("unindexed")
all_retained = len(missing_rows) == expected_missing and len(unresolved_rows) == expected_unresolved and len(unindexed_entries) == expected_unindexed
stop_ok = (
gate.get("release_action") == "STOP_RELEASE"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and intake.get("release_action") == "STOP_RELEASE"
and intake.get("candidate_eligible") is False
)
checks = {
"resource_rows_retained": {"status": "verified" if all_retained else "blocked", "missing": len(missing_rows), "unresolved": len(unresolved_rows), "unindexed": len(unindexed_entries)},
"candidate_resolution_is_non_mutating": {"status": "verified" if sum(candidate_counts.values()) == len(missing_rows) and all(row["remediation_status"] != "resolved" for row in missing_rows) else "blocked", "classification": dict(sorted(candidate_counts.items()))},
"startup_blockers_retained": {"status": "verified" if len(unregistered_entries) == counts.get("startup_preflight", {}).get("blocked_unregistered_kinematics") else "blocked", "unregistered": len(unregistered_entries)},
"resource_graph_closure": {"status": "verified" if regression.get("closure", {}).get("resource_orphan_entrypoints") == [] and regression.get("closure", {}).get("graph_orphan_entry_ids") == [] else "blocked"},
"context_consistency": {"status": "verified" if lock.get("context_id") and regression.get("context_id") == lock.get("context_id") else "blocked", "context_id": lock.get("context_id")},
"release_stop_preserved": {"status": "verified" if stop_ok else "blocked", "release_action": "STOP_RELEASE" if stop_ok else None},
}
checks_ok = not errors and all(value["status"] == "verified" for value in checks.values())
report: dict[str, Any] = {
"schema": "configuration-resource-remediation.v1",
"context_id": lock.get("context_id"),
"task_id": "K8-REL-11",
"status": "complete" if checks_ok else "blocked",
"remediation_status": "blocked_resource_gaps",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"inputs": [
artifact(root, CONTEXT, "context_lock"),
artifact(root, RESOURCES, "config_resources"),
artifact(root, RESOURCE_REPORT, "config_resources_report"),
artifact(root, REGRESSION, "configuration_regression"),
artifact(root, GATE, "release_candidate_gate"),
artifact(root, INTAKE, "remediation_intake"),
],
"counts": {
"resource_rows": len(resource_rows),
"missing_rows": len(missing_rows),
"unresolved_rows": len(unresolved_rows),
"unindexed_configuration_entries": len(unindexed_entries),
"unregistered_startup_entries": len(unregistered_entries),
"candidate_basename_matches": sum(value for key, value in candidate_counts.items() if "candidate" in key),
"resolved_by_this_artifact": 0,
},
"candidate_classification": dict(sorted(candidate_counts.items())),
"missing_rows": missing_rows,
"unresolved_rows": unresolved_rows,
"unindexed_entries": sorted(unindexed_entries, key=lambda row: str(row.get("config_id"))),
"unregistered_entries": sorted(unregistered_entries, key=lambda row: str(row.get("config_id"))),
"checks": checks,
"review_policy": {
"required_to_unblock": ["resolve LIB/command/variable references against an approved search path", "restore or explicitly remove missing resource references with source review", "index every include/template entrypoint", "register or explicitly block each kinematics module"],
"forbidden": ["drop_missing_rows", "replace_missing_with_same_basename_without_contract", "reclassify_as_REAL-EXCLUDED", "promote_blocked_startup_to_ready", "overwrite_context_lock_in_place"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("configuration_resource_remediation=" + report["status"] + " release=" + report["release_action"] + " missing=" + str(report["counts"]["missing_rows"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""Generate deterministic K8-REL-13 implementation license remediation."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
LICENSE_REPORT = Path("coverage/license-closure.report.json")
LICENSE_INDEX = Path("coverage/license-source-index.jsonl")
DECISION = Path("docs/linuxcnc-web-wasm-license-decision.md")
GATE = Path("coverage/release-candidate-gate.report.json")
INTAKE = Path("coverage/release-remediation-intake.report.json")
OUTPUT = Path("coverage/implementation-license-remediation.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def read_jsonl(root: Path, path: Path) -> tuple[list[dict[str, Any]], str | None]:
try:
rows = []
with (root / path).open(encoding="utf-8") as stream:
for line_number, line in enumerate(stream, 1):
if not line.strip():
continue
value = json.loads(line)
if not isinstance(value, dict):
raise ValueError(f"{path}:{line_number} must contain an object")
rows.append(value)
return rows, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return [], str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def source_declaration(root: Path, relative: str) -> dict[str, Any]:
path = root / relative
text = path.read_text(encoding="utf-8", errors="replace") if path.is_file() else ""
spdx = sorted(set(re.findall(r"SPDX-License-Identifier:\s*([^\s*]+)", text)))
notice = bool(re.search(r"\bNOTICE\b|copyright", text, re.IGNORECASE))
return {"spdx_identifiers": spdx, "copyright_or_notice_text": notice, "status": "declared" if spdx else "unassigned"}
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
lock, lock_error = read_json(root, CONTEXT)
license_report, license_report_error = read_json(root, LICENSE_REPORT)
gate, gate_error = read_json(root, GATE)
intake, intake_error = read_json(root, INTAKE)
license_rows, license_index_error = read_jsonl(root, LICENSE_INDEX)
errors = {
path.as_posix(): error
for path, error in ((CONTEXT, lock_error), (LICENSE_REPORT, license_report_error), (GATE, gate_error), (INTAKE, intake_error), (LICENSE_INDEX, license_index_error))
if error
}
implementation = [row for row in license_rows if row.get("source_root") in {"wasm-core", "web"}]
declarations = {row.get("source_path"): source_declaration(root, str(row.get("source_path"))) for row in implementation}
root_license_files = [path.relative_to(root).as_posix() for path in sorted(root.glob("LICENSE*")) if path.is_file()]
root_notice_files = [path.relative_to(root).as_posix() for path in sorted(root.glob("NOTICE*")) if path.is_file()]
source_hash_ok = all(row.get("source_hash_status") == "verified" for row in implementation)
all_unassigned = all(row.get("license_status") == "blocked_unassigned" and row.get("license_spdx") is None for row in implementation)
no_declarations = not root_license_files and not root_notice_files and all(not value["spdx_identifiers"] for value in declarations.values())
stop_ok = (
gate.get("release_action") == "STOP_RELEASE"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and intake.get("release_action") == "STOP_RELEASE"
and intake.get("candidate_eligible") is False
)
checks = {
"implementation_entry_coverage": {"status": "verified" if len(implementation) == 57 else "blocked", "entries": len(implementation)},
"source_hashes": {"status": "verified" if source_hash_ok else "blocked", "verified": sum(row.get("source_hash_status") == "verified" for row in implementation)},
"unassigned_status_preserved": {"status": "verified" if all_unassigned else "blocked", "blocked_unassigned": sum(row.get("license_status") == "blocked_unassigned" for row in implementation)},
"no_unapproved_declaration": {"status": "verified" if no_declarations else "blocked", "root_license_files": root_license_files, "root_notice_files": root_notice_files, "files_with_spdx": sum(bool(value["spdx_identifiers"]) for value in declarations.values())},
"upstream_not_inherited": {"status": "verified" if all(row.get("owner_source") == "implementation_license_decision" for row in implementation) else "blocked"},
"context_consistency": {"status": "verified" if lock.get("context_id") and license_report.get("context_id") == lock.get("context_id") and gate.get("context_id") == lock.get("context_id") else "blocked", "context_id": lock.get("context_id")},
"release_stop_preserved": {"status": "verified" if stop_ok else "blocked", "release_action": "STOP_RELEASE" if stop_ok else None},
}
checks_ok = not errors and all(value["status"] == "verified" for value in checks.values())
rows = [
{
"id": row.get("id"),
"source": row.get("source"),
"source_path": row.get("source_path"),
"source_sha256": row.get("source_sha256"),
"owner": row.get("owner"),
"review_owner": "license-audit-reviewer",
"license_status": row.get("license_status"),
"license_spdx": row.get("license_spdx"),
"declaration_audit": declarations.get(row.get("source_path")),
"remediation_status": "blocked_pending_owner_decision",
}
for row in sorted(implementation, key=lambda value: str(value.get("id")))
]
report: dict[str, Any] = {
"schema": "implementation-license-remediation.v1",
"context_id": lock.get("context_id"),
"task_id": "K8-REL-13",
"status": "complete" if checks_ok else "blocked",
"remediation_status": "blocked_pending_owner_decision",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"counts": {"implementation_entries": len(rows), "blocked_unassigned": sum(row["license_status"] == "blocked_unassigned" for row in rows), "approved_spdx": sum(bool(row["license_spdx"]) for row in rows), "approved_by_this_artifact": 0},
"checks": checks,
"inputs": [artifact(root, CONTEXT, "context_lock"), artifact(root, LICENSE_REPORT, "license_closure"), artifact(root, LICENSE_INDEX, "license_source_index"), artifact(root, DECISION, "license_decision"), artifact(root, GATE, "release_candidate_gate"), artifact(root, INTAKE, "remediation_intake")],
"entries": rows,
"decision_policy": {
"required_to_unblock": ["repository owner decision", "approved SPDX expression", "copyright/NOTICE policy", "distribution obligations review", "new context if metadata changes"],
"current_decision": "do not inherit LinuxCNC license declarations for wasm-core/web",
"forbidden": ["infer_upstream_license", "add_unapproved_spdx", "promote_blocked_unassigned", "delete_entries", "overwrite_context_lock_in_place"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("implementation_license_remediation=" + report["status"] + " release=" + report["release_action"] + " blocked=" + str(report["counts"]["blocked_unassigned"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""Generate the deterministic K8 milestone closeout record.
Closeout completion means the evidence package was audited and frozen with an
explicit release stop. It does not turn the blocked package into a candidate.
"""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
AUDIT = Path("coverage/post-release-audit.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
CLOSURE = Path("coverage/release-closure.report.json")
INDEX = Path("coverage/release-evidence-index.json")
OUTPUT = Path("coverage/k8-milestone-closeout.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> dict[str, Any]:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
errors: dict[str, str] = {}
documents: dict[Path, dict[str, Any]] = {}
for path in (CONTEXT, AUDIT, GATE, CLOSURE, INDEX):
try:
documents[path] = read_json(root, path)
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
documents[path] = {}
errors[path.as_posix()] = str(exc)
lock = documents[CONTEXT]
audit = documents[AUDIT]
gate = documents[GATE]
closure = documents[CLOSURE]
index = documents[INDEX]
context_id = lock.get("context_id")
hash_rows = []
for path in (CONTEXT, AUDIT, GATE, CLOSURE, INDEX):
actual = file_digest(root / path)
hash_rows.append({"path": path.as_posix(), "sha256": actual, "status": "verified" if actual else "blocked"})
hash_ok = all(row["status"] == "verified" for row in hash_rows)
audit_report_hash = audit.get("report_sha256")
audit_report_hash_ok = bool(audit_report_hash) and audit_report_hash == digest({key: value for key, value in audit.items() if key != "report_sha256"})
stop_ok = (
gate.get("release_action") == "STOP_RELEASE"
and gate.get("release_decision") == "blocked_not_release_candidate"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and audit.get("release_action") == "STOP_RELEASE"
and audit.get("candidate_eligible") is False
and audit.get("blocked_count") == 6
)
context_ok = context_id and all(
report.get("context_id") in {None, context_id}
for report in (audit, gate, closure, index)
)
schema_versions = lock.get("schema_versions", {})
schema_ok = bool(schema_versions) and all(value == 1 for value in schema_versions.values())
audit_checks = audit.get("checks", {})
audit_checks_ok = all(value.get("status") == "verified" for value in audit_checks.values())
blocker_ids = {row.get("id") for row in audit.get("blockers", [])}
blocker_ok = len(blocker_ids) == 6 and all(row.get("status") == "blocked" for row in audit.get("blockers", []))
checks = {
"input_hashes": {"status": "verified" if hash_ok else "blocked", "artifacts": hash_rows},
"audit_self_hash": {"status": "verified" if audit_report_hash_ok else "blocked", "declared": audit_report_hash},
"context_consistency": {"status": "verified" if context_ok else "blocked", "context_id": context_id},
"locked_schema_contracts": {"status": "verified" if schema_ok else "blocked", "schema_versions": schema_versions},
"audit_checks": {"status": "verified" if audit_checks_ok else "blocked", "checks": {key: value.get("status") for key, value in audit_checks.items()}},
"stop_decision": {"status": "verified" if stop_ok else "blocked", "blocked_count": gate.get("blocked_count")},
"blocker_preservation": {"status": "verified" if blocker_ok else "blocked", "count": len(audit.get("blockers", [])), "ids": sorted(str(value) for value in blocker_ids)},
}
checks_ok = all(value["status"] == "verified" for value in checks.values()) and not errors
report: dict[str, Any] = {
"schema": "k8-milestone-closeout.v1",
"context_id": context_id,
"milestone": "K8",
"task_id": "K8-REL-06",
"status": "complete" if checks_ok else "blocked",
"milestone_status": "closed_with_release_blockers" if checks_ok else "closeout_blocked",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"source_baseline": {
"linuxcnc_version": lock.get("linuxcnc_version"),
"linuxcnc_source_sha": lock.get("linuxcnc_source_sha"),
"context_lock": artifact(root, CONTEXT, "context_lock"),
},
"inputs": [
artifact(root, AUDIT, "post_release_audit"),
artifact(root, GATE, "release_candidate_gate"),
artifact(root, CLOSURE, "release_closure"),
artifact(root, INDEX, "release_evidence_index"),
artifact(root, CONTEXT, "context_lock"),
],
"checks": checks,
"blockers": audit.get("blockers", []),
"blocked_count": len(audit.get("blockers", [])),
"rebuild": {
"commands": [
"python3 scripts/generate_release_closure.py",
"python3 scripts/generate_post_release_audit.py",
"python3 scripts/generate_k8_milestone_closeout.py",
"node web/worker/test_replay.mjs",
"node web/workspace/test_recovery.mjs",
"node web/workspace/test_migration.mjs",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v",
],
"deterministic_inputs": [CONTEXT.as_posix(), AUDIT.as_posix(), GATE.as_posix(), CLOSURE.as_posix(), INDEX.as_posix()],
},
"failure_policy": {
"on_missing_input": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"forbidden": ["delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED", "promote_blocked_to_accepted"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("k8_closeout=" + report["status"] + " action=" + report["release_action"] + " blockers=" + str(report["blocked_count"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,273 @@
#!/usr/bin/env python3
"""Generate deterministic ownership assignments for unassigned manifest seeds.
This report is deliberately additive. Manifest v1 has no owner field, so the
assignment is a reviewable workstream proposal and does not mutate the locked
manifest or context. Release eligibility remains blocked until the proposal
is reviewed and encoded in a new, explicitly versioned context.
"""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
SOURCE_INDEX = Path("coverage/source-index.jsonl")
MANIFEST = Path("coverage/manifest.jsonl")
GATE = Path("coverage/release-candidate-gate.report.json")
INTAKE = Path("coverage/release-remediation-intake.report.json")
OUTPUT = Path("coverage/manifest-ownership-remediation.report.json")
OWNER_POLICIES = {
"ws-0-baseline-owner": {
"workstream": "WS-0",
"review_owner": "release-closure-owner",
"basis": "baseline, source inventory, or documentation path",
},
"ws-1-core-runtime-owner": {
"workstream": "WS-1",
"review_owner": "native-wasm-reviewer",
"basis": "core interpreter, task, TP, posemath, motion, or kinematics path",
},
"ws-2-worker-runtime-owner": {
"workstream": "WS-2",
"review_owner": "worker-runtime-reviewer",
"basis": "HAL, IO, virtual-device, or Worker path",
},
"ws-3-workspace-owner": {
"workstream": "WS-3",
"review_owner": "workspace-recovery-reviewer",
"basis": "workspace, journal, migration, or recovery path",
},
"ws-4-python-compat-owner": {
"workstream": "WS-4",
"review_owner": "python-security-reviewer",
"basis": "Python, remap, filter, or VCP compatibility path",
},
"ws-5-frontend-owner": {
"workstream": "WS-5",
"review_owner": "frontend-compatibility-reviewer",
"basis": "frontend or browser workflow path",
},
"ws-6-reference-test-owner": {
"workstream": "WS-6",
"review_owner": "reference-test-reviewer",
"basis": "test or unit-test path",
},
"ws-7-release-owner": {
"workstream": "WS-7",
"review_owner": "release-gate-owner",
"basis": "release-only evidence path",
},
}
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def read_jsonl(root: Path, path: Path) -> tuple[list[dict[str, Any]], str | None]:
try:
rows = []
with (root / path).open(encoding="utf-8") as stream:
for line_number, line in enumerate(stream, 1):
if not line.strip():
continue
value = json.loads(line)
if not isinstance(value, dict):
raise ValueError(f"{path}:{line_number} must contain an object")
rows.append(value)
return rows, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return [], str(exc)
def classify(source: str, kind: str) -> tuple[str, str]:
path = source.split(":", 1)[1] if ":" in source else source
if source.startswith(("build:", "doc:")):
return "ws-0-baseline-owner", "build/doc source prefix"
if source.startswith(("test:", "unit_test:")) or kind == "test":
return "ws-6-reference-test-owner", "test source prefix"
if path.startswith("wasm-core/"):
return "ws-1-core-runtime-owner", "wasm-core source prefix"
if path.startswith("web/worker/"):
return "ws-2-worker-runtime-owner", "web/worker source prefix"
if path.startswith("web/workspace/"):
return "ws-3-workspace-owner", "web/workspace source prefix"
if path.startswith("web/"):
return "ws-5-frontend-owner", "web source prefix"
if "/python/" in path or "/remap/" in path or "/vcp/" in path or path.startswith("linuxcnc/src/emc/python/"):
return "ws-4-python-compat-owner", "Python/remap/VCP path"
if (
path.startswith("linuxcnc/src/hal/")
or "/iotask/" in path
or "/io/" in path
or "/motion/" in path
or "/motioncontrol/" in path
):
return "ws-2-worker-runtime-owner", "HAL/IO/motion virtual-device path"
if path.startswith("linuxcnc/src/emc/") or path.startswith("linuxcnc/src/libnml/"):
return "ws-1-core-runtime-owner", "LinuxCNC core path"
if path.startswith("linuxcnc/src/"):
return "ws-1-core-runtime-owner", "LinuxCNC source path"
if path.startswith("linuxcnc/" ):
return "ws-0-baseline-owner", "LinuxCNC non-runtime path"
return "ws-7-release-owner", "unmatched release evidence path"
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
lock, lock_error = read_json(root, CONTEXT)
gate, gate_error = read_json(root, GATE)
intake, intake_error = read_json(root, INTAKE)
source_rows, source_error = read_jsonl(root, SOURCE_INDEX)
manifest_rows, manifest_error = read_jsonl(root, MANIFEST)
errors = {
path.as_posix(): error
for path, error in (
(CONTEXT, lock_error),
(GATE, gate_error),
(INTAKE, intake_error),
(SOURCE_INDEX, source_error),
(MANIFEST, manifest_error),
)
if error
}
source_by_id = {str(row.get("source_id")): row for row in source_rows}
rows: list[dict[str, Any]] = []
owner_counts: dict[str, int] = {}
all_valid = not errors and len(source_rows) == len(manifest_rows)
for manifest in manifest_rows:
if manifest.get("mapping_status") != "needs_owner":
continue
source = str(manifest.get("source", ""))
owner, rule = classify(source, str(manifest.get("kind", "")))
policy = OWNER_POLICIES[owner]
source_row = source_by_id.get(source)
source_match = bool(source_row) and source_row.get("sha256") == manifest.get("source_sha")
valid = bool(manifest.get("id")) and bool(source_row) and source_match
row = {
"id": manifest.get("id"),
"source": source,
"source_sha": manifest.get("source_sha"),
"kind": manifest.get("kind"),
"owner": owner,
"owner_type": "workstream_role",
"workstream": policy["workstream"],
"review_owner": policy["review_owner"],
"assignment_rule": rule,
"assignment_basis": policy["basis"],
"original_mapping_status": manifest.get("mapping_status"),
"assignment_status": "proposed_blocked_review" if valid else "blocked_invalid_source",
"release_status": "blocked",
"evidence_status": "source_hash_verified" if valid else "blocked",
}
rows.append(row)
owner_counts[owner] = owner_counts.get(owner, 0) + 1
all_valid = all_valid and valid
rows.sort(key=lambda row: str(row["id"]))
expected_ids = sorted(str(row.get("id")) for row in manifest_rows if row.get("mapping_status") == "needs_owner")
observed_ids = sorted(str(row.get("id")) for row in rows)
coverage_ok = expected_ids == observed_ids and len(observed_ids) == len(set(observed_ids))
stop_ok = (
gate.get("release_action") == "STOP_RELEASE"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and intake.get("release_action") == "STOP_RELEASE"
and intake.get("candidate_eligible") is False
)
checks = {
"manifest_source_coverage": {"status": "verified" if coverage_ok else "blocked", "expected": len(expected_ids), "observed": len(observed_ids)},
"source_hashes": {"status": "verified" if all_valid else "blocked", "verified": sum(row["evidence_status"] == "source_hash_verified" for row in rows)},
"deterministic_owner_policy": {"status": "verified" if all(row["owner"] in OWNER_POLICIES for row in rows) else "blocked", "owners": owner_counts},
"context_consistency": {"status": "verified" if lock.get("context_id") and intake.get("context_id") == lock.get("context_id") else "blocked", "context_id": lock.get("context_id")},
"release_stop_preserved": {"status": "verified" if stop_ok else "blocked", "release_action": "STOP_RELEASE" if stop_ok else None},
"manifest_unchanged": {"status": "verified", "reason": "manifest v1 has no owner field; proposal is additive and requires review before encoding"},
}
checks_ok = not errors and all(value["status"] == "verified" for value in checks.values())
report: dict[str, Any] = {
"schema": "manifest-ownership-remediation.v1",
"context_id": lock.get("context_id"),
"task_id": "K8-REL-08",
"status": "complete" if checks_ok else "blocked",
"remediation_status": "blocked_pending_manifest_review",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"source_baseline": {"linuxcnc_version": lock.get("linuxcnc_version"), "linuxcnc_source_sha": lock.get("linuxcnc_source_sha")},
"inputs": [
artifact(root, CONTEXT, "context_lock"),
artifact(root, SOURCE_INDEX, "source_index"),
artifact(root, MANIFEST, "manifest_seed"),
artifact(root, GATE, "release_candidate_gate"),
artifact(root, INTAKE, "remediation_intake"),
],
"counts": {"manifest_total": len(manifest_rows), "needs_owner": len(rows), "mapped": len(rows), "accepted_by_this_artifact": 0},
"owner_counts": dict(sorted(owner_counts.items())),
"checks": checks,
"assignments": rows,
"review_policy": {
"owner_semantics": "workstream role proposal; not a named human approval",
"required_before_manifest_update": ["feature owner confirmation", "feature_id/test_id/oracle evidence", "new context_id if manifest hash changes"],
"forbidden": ["promote_to_accepted", "delete_unassigned_rows", "reclassify_as_REAL-EXCLUDED", "overwrite_context_lock_in_place"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("manifest_ownership_remediation=" + report["status"] + " release=" + report["release_action"] + " mapped=" + str(report["counts"]["mapped"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""Audit locked official kinematics/recovery oracle inputs for K8-REL-10."""
from __future__ import annotations
import argparse
import hashlib
import json
import subprocess
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
KIN_CLOSURE = Path("coverage/kinematics-source-closure.report.json")
KIN_ORACLE = Path("coverage/kinematics-oracle.report.json")
RECOVERY = Path("coverage/workspace-recovery.report.json")
COMPATIBILITY = Path("coverage/compatibility-matrix.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
INTAKE = Path("coverage/release-remediation-intake.report.json")
OUTPUT = Path("coverage/official-oracle-remediation.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def locked_path_exists(root: Path, relative: str) -> bool:
try:
result = subprocess.run(
["git", "-C", str(root / "linuxcnc"), "cat-file", "-e", f"HEAD:{relative}"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
except OSError:
return False
return result.returncode == 0
def locked_head(root: Path) -> str | None:
try:
return subprocess.check_output(["git", "-C", str(root / "linuxcnc"), "rev-parse", "HEAD"], text=True).strip()
except (OSError, subprocess.CalledProcessError):
return None
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
paths = (CONTEXT, KIN_CLOSURE, KIN_ORACLE, RECOVERY, COMPATIBILITY, GATE, INTAKE)
documents: dict[Path, dict[str, Any]] = {}
errors: dict[str, str] = {}
for path in paths:
documents[path], error = read_json(root, path)
if error:
errors[path.as_posix()] = error
lock = documents[CONTEXT]
closure = documents[KIN_CLOSURE]
oracle = documents[KIN_ORACLE]
recovery = documents[RECOVERY]
compatibility = documents[COMPATIBILITY]
gate = documents[GATE]
intake = documents[INTAKE]
context_id = lock.get("context_id")
source_sha = lock.get("linuxcnc_source_sha")
actual_head = locked_head(root)
kin_root = "linuxcnc/unit_tests/kinematics"
recovery_roots = ["linuxcnc/unit_tests/recovery", "linuxcnc/tests/recovery"]
kin_present = locked_path_exists(root, kin_root)
recovery_present = {path: locked_path_exists(root, path) for path in recovery_roots}
matrixkins = closure.get("tests", {}).get("files", []) if isinstance(closure.get("tests"), dict) else []
matrixkins_candidate = [
{"path": row.get("path"), "sha256": row.get("sha256"), "official_unit_test": row.get("official_unit_test")}
for row in matrixkins
]
recovery_oracle = recovery.get("oracle", {})
checks = {
"locked_source_commit": {"status": "verified" if source_sha and actual_head == source_sha else "blocked", "expected": source_sha, "actual": actual_head},
"kinematics_official_root": {"status": "verified" if not kin_present and kin_root in closure.get("missing_required_test_roots", []) else "blocked", "path": kin_root, "present": kin_present},
"recovery_official_root": {"status": "verified" if not any(recovery_present.values()) and recovery_oracle.get("status") == "blocked" else "blocked", "paths": recovery_present},
"candidate_tests_not_promoted": {"status": "verified" if matrixkins_candidate and all(row.get("official_unit_test") is False for row in matrixkins_candidate) else "blocked", "count": len(matrixkins_candidate)},
"no_official_goldens_generated": {"status": "verified" if oracle.get("ready_count") == 0 and oracle.get("status") == "blocked" else "blocked", "ready_count": oracle.get("ready_count"), "oracle_status": oracle.get("status")},
"release_stop_preserved": {"status": "verified" if gate.get("release_action") == "STOP_RELEASE" and gate.get("candidate_eligible") is False and gate.get("blocked_count") == 6 and intake.get("release_action") == "STOP_RELEASE" else "blocked", "release_action": "STOP_RELEASE" if gate.get("release_action") == "STOP_RELEASE" else None},
"context_consistency": {"status": "verified" if context_id and compatibility.get("context_id") == context_id and gate.get("context_id") == context_id else "blocked", "context_id": context_id},
}
checks_ok = not errors and all(value["status"] == "verified" for value in checks.values())
report: dict[str, Any] = {
"schema": "official-oracle-remediation.v1",
"context_id": context_id,
"task_id": "K8-REL-10",
"status": "complete" if checks_ok else "blocked",
"remediation_status": "blocked_missing_locked_inputs",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"locked_source": {
"linuxcnc_source_sha": source_sha,
"actual_head": actual_head,
"kinematics_root": {"path": kin_root, "present": kin_present},
"recovery_roots": recovery_present,
},
"oracle_inventory": {
"kinematics_samples": oracle.get("sample_count", 0),
"kinematics_ready": oracle.get("ready_count", 0),
"kinematics_blocked": oracle.get("blocked_count", 0),
"recovery_blocked_samples": recovery_oracle.get("blocked_samples", []),
"matrixkins_candidate_files": matrixkins_candidate,
"official_inputs_added": 0,
"official_goldens_added": 0,
},
"checks": checks,
"inputs": [artifact(root, path, path.stem.replace("-", "_")) for path in paths],
"review_policy": {
"required_to_unblock": ["official kinematics unit-test root in a locked source", "official recovery oracle inputs or approved contract replacement", "native/WASM comparison rerun", "new context_id if source or schema changes"],
"forbidden": ["promote_matrixkins_to_official", "generate_fake_goldens", "delete_blocked_samples", "lower_tolerances", "overwrite_context_lock_in_place"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("official_oracle_remediation=" + report["status"] + " release=" + report["release_action"] + " added=" + str(report["oracle_inventory"]["official_inputs_added"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Record the K8-REL-15 owner-review gate refresh and fail closed if inputs are absent."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
ROUNDUP = Path("coverage/remediation-evidence-roundup.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
AUDIT = Path("coverage/post-release-audit.report.json")
OUTPUT = Path("coverage/owner-review-gate-refresh.report.json")
DECISIONS = (
("manifest_seed_unassigned", "approve manifest ownership mapping and any new context"),
("accepted_entries_missing", "supply acceptance evidence with a ready official oracle"),
("official_oracle_missing", "add or approve locked kinematics and recovery oracle inputs"),
("configuration_resources_missing", "review and resolve configuration resource candidates"),
("browser_matrix_unverified", "approve minimums and execute the three-browser matrix"),
("license_closure_incomplete", "approve SPDX/NOTICE policy for Web/WASM implementation"),
)
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
lock, lock_error = read_json(root, CONTEXT)
roundup, roundup_error = read_json(root, ROUNDUP)
gate, gate_error = read_json(root, GATE)
audit, audit_error = read_json(root, AUDIT)
errors = {path.as_posix(): error for path, error in ((CONTEXT, lock_error), (ROUNDUP, roundup_error), (GATE, gate_error), (AUDIT, audit_error)) if error}
blocker_ids = {row.get("id") for row in audit.get("blockers", []) if isinstance(row, dict)}
decisions = [
{"blocker": blocker, "required_action": action, "owner_input": "missing", "status": "blocked"}
for blocker, action in DECISIONS
]
checks = {
"roundup_complete": {"status": "verified" if roundup.get("status") == "complete" and roundup.get("roundup_status") == "blocked_pending_owner_actions" else "blocked"},
"six_decisions_located": {"status": "verified" if len(decisions) == 6 and blocker_ids == {row["blocker"] for row in decisions} else "blocked", "count": len(decisions)},
"owner_inputs_present": {"status": "blocked", "present": 0, "required": 6, "reason": "no owner approval or new locked input is present in the workspace"},
"gate_refresh_allowed": {"status": "blocked", "reason": "refresh is forbidden until owner inputs and any new context are reviewed"},
"release_stop_preserved": {"status": "verified" if gate.get("release_action") == "STOP_RELEASE" and audit.get("release_action") == "STOP_RELEASE" and gate.get("candidate_eligible") is False and audit.get("candidate_eligible") is False else "blocked", "release_action": "STOP_RELEASE" if gate.get("release_action") == "STOP_RELEASE" else None},
"context_consistency": {"status": "verified" if lock.get("context_id") and roundup.get("context_id") == lock.get("context_id") else "blocked", "context_id": lock.get("context_id")},
}
report: dict[str, Any] = {
"schema": "owner-review-gate-refresh.v1",
"context_id": lock.get("context_id"),
"task_id": "K8-REL-15",
"status": "blocked",
"review_status": "owner_input_required",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"inputs": [artifact(root, CONTEXT, "context_lock"), artifact(root, ROUNDUP, "remediation_roundup"), artifact(root, GATE, "release_candidate_gate"), artifact(root, AUDIT, "post_release_audit")],
"checks": checks,
"decisions": decisions,
"next_transition": "collect owner decisions, create a new context when locked inputs change, then regenerate closure/audit/gate",
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("owner_review_gate_refresh=" + report["status"] + " action=" + report["release_action"] + " decisions=" + str(len(report["decisions"])))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,422 @@
#!/usr/bin/env python3
"""Generate the deterministic K8-REL-05 post-release audit.
The audit is deliberately additive. It reads the K8-REL-04 gate and its
inputs, compares every recorded artifact hash with the current workspace,
and records drift as a blocker. It never rewrites release evidence.
"""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
GATE = Path("coverage/release-candidate-gate.report.json")
AUDIT = Path("coverage/post-release-audit.report.json")
BLOCKER_METADATA: dict[str, dict[str, Any]] = {
"manifest_seed_unassigned": {
"owner": "manifest-feature-ownership",
"review_owner": "release-closure-owner",
"evidence": [
"coverage/release-evidence-index.json",
"coverage/manifest.jsonl",
],
"remediation": "assign an accountable feature owner to every needs_owner manifest seed and regenerate the evidence index",
"review_trigger": "manifest mapping changes or the release closure is regenerated",
},
"accepted_entries_missing": {
"owner": "release-acceptance-owner",
"review_owner": "release-gate-owner",
"evidence": [
"coverage/release-evidence-index.json",
"coverage/release-candidate-gate.report.json",
],
"remediation": "complete acceptance evidence for eligible entries; do not promote blocked or derived entries by relabeling",
"review_trigger": "any entry changes to accepted or the acceptance policy changes",
},
"official_oracle_missing": {
"owner": "upstream-oracle-owner",
"review_owner": "kinematics-recovery-reviewer",
"evidence": [
"coverage/compatibility-matrix.report.json",
"coverage/kinematics-oracle.report.json",
"coverage/workspace-recovery.report.json",
],
"remediation": "add the locked official kinematics and recovery oracle inputs, then rerun the native/WASM comparison",
"review_trigger": "the locked source gains or changes an official oracle root",
},
"configuration_resources_missing": {
"owner": "configuration-closure-owner",
"review_owner": "configuration-regression-reviewer",
"evidence": [
"coverage/compatibility-matrix.report.json",
"coverage/configuration-regression.report.json",
],
"remediation": "resolve missing and unindexed configuration resources and unregistered startup references without dropping rows",
"review_trigger": "configuration graph or startup preflight counts change",
},
"browser_matrix_unverified": {
"owner": "browser-compatibility-owner",
"review_owner": "release-gate-owner",
"evidence": [
"coverage/compatibility-matrix.report.json",
"context-lock.json",
],
"remediation": "pin minimum Chromium, Firefox and Safari versions in the locked browser matrix and execute the required checks",
"review_trigger": "browser minimums or cross-origin isolation policy changes",
},
"license_closure_incomplete": {
"owner": "implementation-license-owner",
"review_owner": "license-audit-reviewer",
"evidence": [
"coverage/license-closure.report.json",
"coverage/license-source-index.jsonl",
"docs/linuxcnc-web-wasm-license-decision.md",
],
"remediation": "approve SPDX/NOTICE metadata for all 57 Web/WASM implementation entries; upstream declarations do not substitute for it",
"review_trigger": "implementation license metadata or source-root ownership changes",
},
}
REQUIRED_CONTEXT_REPORTS = (
GATE,
Path("coverage/release-closure.report.json"),
Path("coverage/release-evidence-index.json"),
Path("coverage/compatibility-matrix.report.json"),
Path("coverage/license-closure.report.json"),
Path("coverage/workspace-recovery.report.json"),
Path("coverage/workspace-migration.report.json"),
)
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> dict[str, Any]:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value
def try_read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
return read_json(root, path), None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
actual = file_digest(full)
if actual is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {
"path": path.as_posix(),
"role": role,
"status": "present",
"bytes": full.stat().st_size,
"sha256": actual,
}
def report_hash_check(path: Path, report: dict[str, Any], error: str | None) -> dict[str, Any]:
declared = report.get("report_sha256")
if error is None and declared is None and report:
return {
"path": path.as_posix(),
"declared": None,
"actual": None,
"status": "verified",
"hash_policy": "not_declared_by_legacy_schema",
}
actual = digest({key: value for key, value in report.items() if key != "report_sha256"}) if report else None
status = "verified" if error is None and declared and declared == actual else "blocked"
return {
"path": path.as_posix(),
"declared": declared,
"actual": actual,
"status": status,
**({"error": error} if error else {}),
}
def compare_gate_inputs(root: Path, gate: dict[str, Any]) -> tuple[list[dict[str, Any]], bool]:
if not gate or not isinstance(gate.get("inputs"), list):
return [], False
comparisons: list[dict[str, Any]] = []
all_match = True
for expected in gate.get("inputs", []):
path_text = expected.get("path")
if not isinstance(path_text, str):
comparisons.append({"path": None, "status": "blocked", "reason": "gate input has no path"})
all_match = False
continue
path = Path(path_text)
full = root / path
actual_sha = file_digest(full)
actual_bytes = full.stat().st_size if full.is_file() else None
matches = (
actual_sha is not None
and actual_sha == expected.get("sha256")
and actual_bytes == expected.get("bytes")
)
comparison = {
"path": path.as_posix(),
"role": expected.get("role"),
"expected_sha256": expected.get("sha256"),
"actual_sha256": actual_sha,
"expected_bytes": expected.get("bytes"),
"actual_bytes": actual_bytes,
"status": "verified" if matches else "blocked",
}
comparisons.append(comparison)
all_match = all_match and matches
return comparisons, all_match
def context_check(root: Path, lock: dict[str, Any], reports: dict[Path, dict[str, Any]]) -> dict[str, Any]:
context_id = lock.get("context_id")
observations = []
all_match = bool(context_id)
for path in REQUIRED_CONTEXT_REPORTS:
report = reports.get(path, {})
observed = report.get("context_id")
declared = observed is not None
matches = not declared or observed == context_id
observations.append({
"path": path.as_posix(),
"context_id": observed,
"context_declared": declared,
"status": "verified" if matches else "blocked",
})
all_match = all_match and matches
return {
"context_id": context_id,
"reports": observations,
"status": "verified" if all_match else "blocked",
"all_declared_context_ids_match": all_match,
"absent_context_id_policy": "allowed only for legacy reports covered by the K8-REL-04 gate",
}
def cross_reference_check(
root: Path,
lock: dict[str, Any],
gate: dict[str, Any],
closure: dict[str, Any],
index: dict[str, Any],
compatibility: dict[str, Any],
license_report: dict[str, Any],
migration: dict[str, Any],
) -> dict[str, Any]:
checks: list[dict[str, Any]] = []
def check(name: str, expected: Any, actual: Any, evidence: str) -> None:
checks.append({
"name": name,
"expected": expected,
"actual": actual,
"evidence": evidence,
"status": "verified" if expected == actual else "blocked",
})
check("gate_context_id", lock.get("context_id"), gate.get("context_id"), GATE.as_posix())
check("closure_context_id", lock.get("context_id"), closure.get("context_id"), "coverage/release-closure.report.json")
check("evidence_index_context_id", lock.get("context_id"), index.get("context_id"), "coverage/release-evidence-index.json")
check("compatibility_context_id", lock.get("context_id"), compatibility.get("context_id"), "coverage/compatibility-matrix.report.json")
check("license_context_id", lock.get("context_id"), license_report.get("context_id"), "coverage/license-closure.report.json")
check("migration_context_id", lock.get("context_id"), migration.get("context_id"), "coverage/workspace-migration.report.json")
check("context_lock_hash", file_digest(root / CONTEXT), index.get("context_lock", {}).get("sha256"), "coverage/release-evidence-index.json")
check("closure_compatibility_report_hash", compatibility.get("report_sha256"), closure.get("compatibility", {}).get("sha256"), "coverage/release-closure.report.json")
check("closure_license_report_hash", license_report.get("report_sha256"), closure.get("license", {}).get("sha256"), "coverage/release-closure.report.json")
check("closure_evidence_index_report_hash", index.get("report_sha256"), closure.get("evidence_index", {}).get("sha256"), "coverage/release-closure.report.json")
check("index_compatibility_report_hash", compatibility.get("report_sha256"), index.get("compatibility_report", {}).get("sha256"), "coverage/release-evidence-index.json")
check("index_license_report_hash", license_report.get("report_sha256"), index.get("license_report", {}).get("sha256"), "coverage/release-evidence-index.json")
check("index_migration_report_hash", file_digest(root / Path("coverage/workspace-migration.report.json")), index.get("migration_report", {}).get("sha256"), "coverage/release-evidence-index.json")
check("recovery_hash_lock", lock.get("workspace_recovery_report_sha256"), file_digest(root / Path("coverage/workspace-recovery.report.json")), GATE.as_posix())
check("gate_release_action", "STOP_RELEASE", gate.get("release_action"), GATE.as_posix())
check("gate_candidate_eligible", False, gate.get("candidate_eligible"), GATE.as_posix())
check("gate_release_decision", "blocked_not_release_candidate", gate.get("release_decision"), GATE.as_posix())
check("gate_blocked_count", 6, gate.get("blocked_count"), GATE.as_posix())
status = "verified" if all(item["status"] == "verified" for item in checks) else "blocked"
return {"status": status, "checks": checks}
def blocker_review(root: Path, gate: dict[str, Any]) -> tuple[list[dict[str, Any]], dict[str, Any]]:
blockers = []
all_locatable = True
gate_blockers = gate.get("blockers", [])
for blocker in gate_blockers:
blocker_id = blocker.get("id")
metadata = BLOCKER_METADATA.get(blocker_id, {})
evidence = metadata.get("evidence", [])
evidence_status = all((root / Path(path)).is_file() for path in evidence)
row = {
"id": blocker_id,
"count": blocker.get("count"),
"status": blocker.get("status"),
"reason": blocker.get("reason"),
"owner": metadata.get("owner"),
"review_owner": metadata.get("review_owner"),
"evidence": [
{"path": path, "status": "present" if (root / Path(path)).is_file() else "missing"}
for path in evidence
],
"remediation": metadata.get("remediation"),
"review_trigger": metadata.get("review_trigger"),
"status_check": "verified" if blocker.get("status") == "blocked" else "blocked",
"evidence_check": "verified" if evidence_status else "blocked",
}
blockers.append(row)
all_locatable = all_locatable and bool(blocker_id) and blocker.get("status") == "blocked" and bool(metadata) and evidence_status
expected_ids = set(BLOCKER_METADATA)
actual_ids = {row.get("id") for row in gate_blockers}
identity_preserved = len(gate_blockers) == 6 and actual_ids == expected_ids
return blockers, {
"status": "verified" if all_locatable and identity_preserved else "blocked",
"all_blockers_locatable": all_locatable,
"identity_preserved": identity_preserved,
"expected_count": 6,
"observed_count": len(gate_blockers),
"observed_ids": sorted(str(value) for value in actual_ids),
}
def generate(root: Path, output: Path = AUDIT) -> dict[str, Any]:
root = root.resolve()
gate, gate_error = try_read_json(root, GATE)
lock, lock_error = try_read_json(root, CONTEXT)
reports: dict[Path, dict[str, Any]] = {}
report_errors: dict[Path, str | None] = {}
for path in REQUIRED_CONTEXT_REPORTS:
reports[path], report_errors[path] = try_read_json(root, path)
closure = reports.get(Path("coverage/release-closure.report.json"), {})
index = reports.get(Path("coverage/release-evidence-index.json"), {})
compatibility = reports.get(Path("coverage/compatibility-matrix.report.json"), {})
license_report = reports.get(Path("coverage/license-closure.report.json"), {})
migration = reports.get(Path("coverage/workspace-migration.report.json"), {})
input_comparisons, inputs_match = compare_gate_inputs(root, gate)
context = context_check(root, lock, reports)
cross_refs = cross_reference_check(root, lock, gate, closure, index, compatibility, license_report, migration)
blocker_rows, blocker_checks = blocker_review(root, gate)
report_hashes = [
report_hash_check(path, reports[path], report_errors[path])
for path in REQUIRED_CONTEXT_REPORTS
] + [report_hash_check(GATE, gate, gate_error)]
report_hashes_ok = all(item["status"] == "verified" for item in report_hashes)
gate_checks = gate.get("checks", {}) if gate else {}
gate_check_integrity = {
"context_consistency": gate_checks.get("context_consistency", {}).get("status") == "verified",
"locked_schema_contracts": gate_checks.get("locked_schema_contracts", {}).get("status") == "verified",
"evidence_integrity": gate_checks.get("evidence_integrity", {}).get("status") == "verified",
"license_source_index": gate_checks.get("license_source_index", {}).get("status") == "verified",
"migration_rollback_offline": gate_checks.get("migration_rollback_offline", {}).get("status") == "verified",
"release_prerequisites_blocked": gate_checks.get("release_prerequisites", {}).get("status") == "blocked",
}
gate_check_status = "verified" if all(gate_check_integrity.values()) else "blocked"
generated_at = lock.get("generated_at") if lock else None
checks = {
"input_hashes": {
"status": "verified" if inputs_match else "blocked",
"all_match": inputs_match,
"comparisons": input_comparisons,
},
"report_self_hashes": {
"status": "verified" if report_hashes_ok else "blocked",
"reports": report_hashes,
},
"context_consistency": context,
"cross_references": cross_refs,
"gate_checks": {
"status": gate_check_status,
"checks": gate_check_integrity,
},
"blocker_review": blocker_checks,
}
check_statuses = [value.get("status") for value in checks.values()]
audit_checks_pass = not any(value != "verified" for value in check_statuses)
gate_stop = gate.get("release_action") == "STOP_RELEASE" or gate.get("candidate_eligible") is False
status = "blocked" if not audit_checks_pass or gate_stop else "passed"
report: dict[str, Any] = {
"schema": "post-release-audit.v1",
"context_id": lock.get("context_id"),
"audit_id": "K8-REL-05",
"generated_at": generated_at,
"status": status,
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"source_baseline": {
"linuxcnc_version": lock.get("linuxcnc_version"),
"linuxcnc_source_sha": lock.get("linuxcnc_source_sha"),
"context_lock": artifact(root, CONTEXT, "context_lock"),
},
"inputs": [artifact(root, GATE, "release_candidate_gate"), artifact(root, CONTEXT, "context_lock")],
"input_hash_comparison": input_comparisons,
"checks": checks,
"blockers": blocker_rows,
"blocked_count": len(blocker_rows),
"rebuild": {
"commands": [
"python3 scripts/generate_post_release_audit.py",
"python3 scripts/generate_release_closure.py",
"node web/worker/test_replay.mjs",
"node web/workspace/test_recovery.mjs",
"node web/workspace/test_migration.mjs",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v",
],
"deterministic_inputs": [path.as_posix() for path in REQUIRED_CONTEXT_REPORTS]
+ [GATE.as_posix(), CONTEXT.as_posix(), "coverage/license-source-index.jsonl", "coverage/workspace-rollback.package.json", "coverage/offline-cache.package.json", "docs/linuxcnc-web-wasm-license-decision.md"],
},
"failure_policy": {
"on_missing_input": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"on_context_drift": "STOP_RELEASE",
"forbidden": ["delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED", "promote_blocked_to_accepted"],
},
"errors": {path.as_posix(): error for path, error in [(GATE, gate_error), (CONTEXT, lock_error), *report_errors.items()] if error},
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=AUDIT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("post_release_audit=" + report["status"] + " action=" + report["release_action"] + " blockers=" + str(report["blocked_count"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Generate deterministic acceptance-readiness evidence for K8-REL-09."""
from __future__ import annotations
import argparse
import hashlib
import json
from collections import Counter
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
MANIFEST = Path("coverage/manifest.jsonl")
INDEX = Path("coverage/release-evidence-index.json")
GATE = Path("coverage/release-candidate-gate.report.json")
INTAKE = Path("coverage/release-remediation-intake.report.json")
OWNERSHIP = Path("coverage/manifest-ownership-remediation.report.json")
OUTPUT = Path("coverage/release-acceptance-remediation.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def read_jsonl(root: Path, path: Path) -> tuple[list[dict[str, Any]], str | None]:
try:
rows = []
with (root / path).open(encoding="utf-8") as stream:
for line_number, line in enumerate(stream, 1):
if not line.strip():
continue
value = json.loads(line)
if not isinstance(value, dict):
raise ValueError(f"{path}:{line_number} must contain an object")
rows.append(value)
return rows, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return [], str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
lock, lock_error = read_json(root, CONTEXT)
index, index_error = read_json(root, INDEX)
gate, gate_error = read_json(root, GATE)
intake, intake_error = read_json(root, INTAKE)
ownership, ownership_error = read_json(root, OWNERSHIP)
manifest, manifest_error = read_jsonl(root, MANIFEST)
errors = {
path.as_posix(): error
for path, error in (
(CONTEXT, lock_error),
(INDEX, index_error),
(GATE, gate_error),
(INTAKE, intake_error),
(OWNERSHIP, ownership_error),
(MANIFEST, manifest_error),
)
if error
}
affected = index.get("affected_manifest_entries", []) if isinstance(index.get("affected_manifest_entries"), list) else []
manifest_accepted = [
row for row in manifest
if row.get("sim_status") == "accepted" or row.get("web_status") == "accepted"
]
readiness_reasons: Counter[str] = Counter()
ready_rows = []
blocked_rows = []
for row in affected:
reasons = []
if not row.get("owner"):
reasons.append("owner_missing")
if not row.get("inputs"):
reasons.append("inputs_missing")
if not row.get("outputs"):
reasons.append("outputs_missing")
if not row.get("test_id"):
reasons.append("test_missing")
if row.get("oracle_status") != "ready":
reasons.append("oracle_blocked")
if row.get("context_id") != lock.get("context_id"):
reasons.append("context_missing_or_mismatch")
if reasons:
blocked_rows.append({"id": row.get("id"), "reasons": reasons})
readiness_reasons.update(reasons)
else:
ready_rows.append(row.get("id"))
unassigned = sum(1 for row in manifest if row.get("mapping_status") == "needs_owner")
ownership_count = ownership.get("counts", {}).get("mapped")
coverage_ok = (
len(manifest) == 8053
and len(affected) == 1804
and unassigned == 6249
and ownership_count == 6249
and len(ready_rows) == 0
and len(blocked_rows) == len(affected)
)
stop_ok = (
gate.get("release_action") == "STOP_RELEASE"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and intake.get("release_action") == "STOP_RELEASE"
and intake.get("candidate_eligible") is False
)
no_promotion = len(manifest_accepted) == 0 and all(row.get("oracle_status") == "blocked" for row in affected)
checks = {
"input_shape": {"status": "verified" if coverage_ok else "blocked", "manifest_total": len(manifest), "affected": len(affected)},
"acceptance_readiness": {"status": "verified" if len(ready_rows) == 0 else "blocked", "eligible_count": len(ready_rows), "blocked_count": len(blocked_rows), "reasons": dict(sorted(readiness_reasons.items()))},
"no_promotion": {"status": "verified" if no_promotion else "blocked", "manifest_accepted": len(manifest_accepted), "affected_oracle_blocked": all(row.get("oracle_status") == "blocked" for row in affected)},
"ownership_dependency": {"status": "verified" if ownership_count == 6249 and ownership.get("remediation_status") == "blocked_pending_manifest_review" else "blocked", "mapped": ownership_count},
"context_consistency": {"status": "verified" if lock.get("context_id") and index.get("context_id") == lock.get("context_id") else "blocked", "context_id": lock.get("context_id")},
"release_stop_preserved": {"status": "verified" if stop_ok else "blocked", "release_action": "STOP_RELEASE" if stop_ok else None},
}
checks_ok = not errors and all(value["status"] == "verified" for value in checks.values())
report: dict[str, Any] = {
"schema": "release-acceptance-remediation.v1",
"context_id": lock.get("context_id"),
"task_id": "K8-REL-09",
"status": "complete" if checks_ok else "blocked",
"remediation_status": "blocked_no_eligible_entries",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"inputs": [
artifact(root, CONTEXT, "context_lock"),
artifact(root, MANIFEST, "manifest_seed"),
artifact(root, INDEX, "release_evidence_index"),
artifact(root, OWNERSHIP, "manifest_ownership_remediation"),
artifact(root, GATE, "release_candidate_gate"),
artifact(root, INTAKE, "remediation_intake"),
],
"counts": {
"manifest_total": len(manifest),
"manifest_accepted": len(manifest_accepted),
"manifest_needs_owner": unassigned,
"affected_entries": len(affected),
"eligible_for_acceptance": len(ready_rows),
"blocked_for_acceptance": len(blocked_rows),
},
"blocked_reason_counts": dict(sorted(readiness_reasons.items())),
"blocked_entry_ids": sorted(str(row["id"]) for row in blocked_rows),
"checks": checks,
"acceptance_policy": {
"required": ["owner", "inputs", "outputs", "test_id", "ready oracle", "matching context_id", "implementation and difference evidence"],
"forbidden": ["promote_blocked_to_accepted", "promote_derived_to_accepted", "delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("release_acceptance_remediation=" + report["status"] + " release=" + report["release_action"] + " eligible=" + str(report["counts"]["eligible_for_acceptance"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,729 @@
#!/usr/bin/env python3
"""Build deterministic K8 compatibility, license and release evidence reports.
The reports intentionally distinguish a verified implementation slice from a
release-acceptable result. Missing upstream oracles, source metadata, browser
coverage and unassigned manifest seeds stay visible blockers; no derived
configuration result is promoted to an accepted golden.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
from collections import Counter
from fnmatch import fnmatchcase
from pathlib import Path
from typing import Any, Iterable
CONTEXT = Path("context-lock.json")
SOURCE_INDEX = Path("coverage/source-index.jsonl")
MANIFEST = Path("coverage/manifest.jsonl")
CONFIGURATION = Path("coverage/configuration-regression.report.json")
PYTHON_MANIFEST = Path("python-runtime/manifest.json")
PYTHON_REPORT = Path("coverage/python-runtime.report.json")
DOWNLOAD_INDEX = Path("vendor/downloads/index.jsonl")
FULL_PORT_DOC = Path("docs/linuxcnc-web-wasm-full-port-implementation.md")
LICENSE_INDEX = Path("coverage/license-source-index.jsonl")
IMPLEMENTATION_LICENSE_DECISION = Path("docs/linuxcnc-web-wasm-license-decision.md")
REPORT_PATHS = (
"coverage/configuration-regression.report.json",
"coverage/corexy-hal.report.json",
"coverage/coupled-kinematics.report.json",
"coverage/delta-kinematics.report.json",
"coverage/fiveaxis-kinematics.report.json",
"coverage/golden-schema.json",
"coverage/genhex-kinematics.report.json",
"coverage/genser-kinematics.report.json",
"coverage/hal-runtime.report.json",
"coverage/interpreter-runtime.report.json",
"coverage/io-runtime.report.json",
"coverage/kinematics-abi-contract.json",
"coverage/kinematics-feedback.report.json",
"coverage/kinematics-golden-schema.json",
"coverage/kinematics-home-limit.report.json",
"coverage/kinematics-motion.report.json",
"coverage/kinematics-mapping-contract.json",
"coverage/kinematics-numeric-policy.json",
"coverage/kinematics-oracle.report.json",
"coverage/kinematics-registry.report.json",
"coverage/kinematics-runtime.report.json",
"coverage/kinematics-source-closure.report.json",
"coverage/motion-runtime.report.json",
"coverage/native-runner.report.json",
"coverage/posemath-runtime.report.json",
"coverage/robot-kinematics.report.json",
"coverage/runtime-mapping.report.json",
"coverage/switchkins-kinematics.report.json",
"coverage/test-entries.report.json",
"coverage/tripod-kinematics.report.json",
"coverage/trivkins-integration.report.json",
"coverage/trt-kinematics.report.json",
"coverage/userkins-kinematics.report.json",
"coverage/workspace-recovery.report.json",
"coverage/workspace-migration.report.json",
"coverage/workspace-rollback.package.json",
"coverage/offline-cache.package.json",
)
DOMAIN_REPORTS = {
"configuration": "coverage/configuration-regression.report.json",
"protocol": "coverage/runtime-mapping.report.json",
"posemath": "coverage/posemath-runtime.report.json",
"interpreter": "coverage/interpreter-runtime.report.json",
"motion": "coverage/motion-runtime.report.json",
"kinematics": "coverage/kinematics-runtime.report.json",
"hal": "coverage/hal-runtime.report.json",
"io": "coverage/io-runtime.report.json",
"python": "coverage/python-runtime.report.json",
"recovery": "coverage/workspace-recovery.report.json",
}
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str:
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> dict[str, Any]:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value
def read_jsonl(root: Path, path: Path) -> list[dict[str, Any]]:
rows = []
for number, line in enumerate((root / path).read_text(encoding="utf-8").splitlines(), 1):
if not line.strip():
continue
value = json.loads(line)
if not isinstance(value, dict):
raise ValueError(f"{path}:{number} must contain an object")
rows.append(value)
return rows
LICENSE_SPDX = {
"BSD-3-Clause": "BSD-3-Clause",
"Boost-1.0": "BSL-1.0",
"CC-BY-3.0": "CC-BY-3.0",
"GFDL-1.1+": "GFDL-1.1-or-later",
"GNU-All-Permissive": "FSFAP",
"GPL-2": "GPL-2.0-only",
"GPL-2+": "GPL-2.0-or-later",
"GPL-3+": "GPL-3.0-or-later",
"ISC": "ISC",
"LGPL-2": "LGPL-2.0-only",
"LGPL-2.1+": "LGPL-2.1-or-later",
"MIT": "MIT",
}
def parse_copyright_scopes(text: str) -> list[dict[str, Any]]:
"""Parse Debian copyright file scopes without interpreting license text."""
scopes: list[dict[str, Any]] = []
for order, paragraph in enumerate(re.split(r"\n\s*\n", text)):
fields: dict[str, list[str]] = {}
current: str | None = None
for line in paragraph.splitlines():
match = re.match(r"^([A-Za-z][A-Za-z-]*):\s*(.*)$", line)
if match:
current = match.group(1)
fields[current] = [match.group(2).strip()] if match.group(2).strip() else []
elif current and line.startswith(" "):
value = line.strip()
if value:
fields[current].append(value)
patterns = [pattern for value in fields.get("Files", []) for pattern in value.split()]
license_id = fields.get("License", [""])[0].strip()
if patterns and license_id:
scopes.append({
"order": order,
"patterns": patterns,
"license": license_id,
"spdx": LICENSE_SPDX.get(license_id),
})
return scopes
def match_copyright_scope(relative_path: str, scopes: list[dict[str, Any]]) -> dict[str, Any] | None:
matches: list[tuple[int, int, str, dict[str, Any]]] = []
for scope in scopes:
for pattern in scope["patterns"]:
if fnmatchcase(relative_path, pattern):
specificity = sum(character not in "*?[" for character in pattern)
matches.append((specificity, scope["order"], pattern, scope))
if not matches:
return None
_, _, pattern, scope = max(matches, key=lambda item: (item[0], item[1], item[2]))
return {"pattern": pattern, **scope}
def write_jsonl(path: Path, rows: Iterable[dict[str, Any]]) -> dict[str, Any]:
content = "".join(f"{canonical(row)}\n" for row in rows)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return {"path": path.as_posix(), "bytes": len(content.encode("utf-8")), "sha256": file_digest(path), "status": "present"}
def write_report(path: Path, report: dict[str, Any]) -> dict[str, Any]:
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
if not full.is_file():
return {"path": path.as_posix(), "role": role, "status": "missing"}
value: dict[str, Any] = {
"path": path.as_posix(),
"role": role,
"status": "present",
"bytes": full.stat().st_size,
"sha256": file_digest(full),
}
if full.suffix == ".json":
try:
parsed = json.loads(full.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
parsed = None
if isinstance(parsed, dict):
for key in ("schema", "$id", "status", "implementation_status"):
if key in parsed:
value[key] = parsed[key]
if isinstance(parsed.get("oracle"), dict):
value["oracle_status"] = parsed["oracle"].get("status")
return value
def report_status(report: dict[str, Any]) -> str:
if report.get("implementation_status") == "verified":
return "verified"
status = report.get("status")
return str(status) if status else "unknown"
def nested_report_value(report: dict[str, Any], key: str, nested: str) -> Any:
value = report.get(key)
return value.get(nested) if isinstance(value, dict) else None
def source_observation(root: Path) -> dict[str, Any]:
lock = read_json(root, CONTEXT)
observed: dict[str, dict[str, Any]] = {
"sim_abi": {"value": lock["schema_versions"].get("sim_abi"), "source": "context-lock.json"},
"protocol": {"value": lock["schema_versions"].get("protocol"), "source": "context-lock.json"},
"snapshot": {"value": lock["schema_versions"].get("snapshot"), "source": "context-lock.json"},
"event": {"value": lock["schema_versions"].get("event"), "source": "context-lock.json"},
"workspace": {"value": lock["schema_versions"].get("workspace"), "source": "context-lock.json"},
"manifest": {"value": lock["schema_versions"].get("manifest"), "source": "context-lock.json"},
}
checks = (
("protocol", Path("web/worker/sim-session.mjs"), r"PROTOCOL_SCHEMA\s*=\s*1"),
("event", Path("web/worker/replay.mjs"), r'event-log\.v1'),
("snapshot", Path("web/worker/replay.mjs"), r'snapshot\.v1'),
("workspace", Path("web/workspace/recovery.mjs"), r'simulation-recovery\.v1'),
)
for name, path, pattern in checks:
text = (root / path).read_text(encoding="utf-8") if (root / path).is_file() else ""
observed[name]["source_observed"] = bool(re.search(pattern, text))
observed[name]["source"] = path.as_posix()
observed["manifest"]["source_observed"] = (root / MANIFEST).is_file()
observed["sim_abi"]["source_observed"] = (root / Path("coverage/kinematics-abi-contract.json")).is_file()
for value in observed.values():
value["status"] = "verified" if value.get("value") == 1 and value.get("source_observed") else "blocked"
return observed
def generate_compatibility(root: Path, out: Path) -> dict[str, Any]:
lock = read_json(root, CONTEXT)
config = read_json(root, CONFIGURATION)
migration = read_json(root, Path("coverage/workspace-migration.report.json"))
reports = {name: read_json(root, Path(path)) for name, path in DOMAIN_REPORTS.items() if (root / Path(path)).is_file()}
domains = []
for name, path in DOMAIN_REPORTS.items():
report = reports.get(name, {})
domains.append({
"domain": name,
"artifact": path,
"implementation_status": report_status(report),
"runtime_status": report.get("status", "missing"),
"oracle_status": report.get("oracle", {}).get("status") if isinstance(report.get("oracle"), dict) else None,
"test_evidence": report.get("native_tests") or report.get("native_wasm") or report.get("native_and_wasm") or nested_report_value(report, "runtime", "native_wasm"),
"status": "blocked" if nested_report_value(report, "oracle", "status") == "blocked" or report.get("status") in {"partial", "blocked"} else report_status(report),
})
browser = lock.get("browser_matrix", {})
browser_ready = browser.get("status") == "verified" and all(browser.get("minimum", {}).values())
contract = source_observation(root)
migration_ready = migration.get("status") == "passed" and migration.get("implementation_status") == "verified" and migration.get("migration", {}).get("round_trip_equal") is True and migration.get("rollback", {}).get("status") == "passed" and migration.get("offline", {}).get("status") == "passed" and (root / Path("coverage/workspace-rollback.package.json")).is_file() and (root / Path("coverage/offline-cache.package.json")).is_file()
hash_checks = []
for key, path in (
("configuration_regression_report_sha256", CONFIGURATION),
("python_runtime_report_sha256", PYTHON_REPORT),
("source_index_sha256", SOURCE_INDEX),
("manifest_seed_sha256", MANIFEST),
):
expected = lock.get(key)
actual = file_digest(root / path) if (root / path).is_file() else None
hash_checks.append({"lock_key": key, "path": path.as_posix(), "expected": expected, "actual": actual, "status": "verified" if expected == actual else "blocked"})
report = {
"schema": "release-compatibility-matrix.v1",
"context_id": lock.get("context_id"),
"status": "blocked",
"release_decision": "blocked_not_release_candidate",
"source_baseline": {
"linuxcnc_version": lock.get("linuxcnc_version"),
"linuxcnc_source_sha": lock.get("linuxcnc_source_sha"),
"linuxcnc_version_sha256": lock.get("linuxcnc_version_sha256"),
"context_lock": {"path": CONTEXT.as_posix(), "sha256": file_digest(root / CONTEXT)},
},
"contracts": {
"schema_versions": lock.get("schema_versions", {}),
"observed": contract,
"status": "verified" if all(value["status"] == "verified" for value in contract.values()) else "blocked",
},
"hash_checks": hash_checks,
"domains": domains,
"configuration": {
"entries": config.get("counts", {}).get("registered_configuration_entries"),
"entrypoints": config.get("counts", {}).get("config_entrypoints"),
"startup_preflight": config.get("counts", {}).get("startup_preflight"),
"resource_status": config.get("counts", {}).get("resource_status"),
"derived_results_are_not_official": config.get("oracle", {}).get("derived_configuration_results_are_not_official"),
},
"migration_rollback_offline": {
"status": "verified" if migration_ready else "blocked",
"artifact": artifact(root, Path("coverage/workspace-migration.report.json"), "migration_rollback_offline"),
"rollback_package": artifact(root, Path("coverage/workspace-rollback.package.json"), "rollback_package"),
"offline_package": artifact(root, Path("coverage/offline-cache.package.json"), "offline_package"),
"format_schema": migration.get("format", {}).get("schema"),
"supported_versions": migration.get("format", {}).get("supported_versions", []),
"round_trip_equal": migration.get("migration", {}).get("round_trip_equal"),
"rollback_recovery": migration.get("rollback", {}).get("recovery_status"),
"offline_start": migration.get("offline", {}).get("offline_start"),
"no_silent_downgrade": migration.get("recovery_endpoint", {}).get("no_silent_downgrade") is True,
},
"browser_matrix": {
"status": "verified" if browser_ready else "blocked",
"declared_status": browser.get("status"),
"minimum": browser.get("minimum", {}),
"reason": "minimum Chromium/Firefox/Safari versions are TBD" if not browser_ready else None,
},
"oracle": {
"status": "blocked",
"official_root": "linuxcnc/unit_tests/kinematics",
"present": (root / "linuxcnc/unit_tests/kinematics").is_dir(),
"reason": "official kinematics and recovery oracle is absent from the locked source",
},
"real_excluded": [
{"id": "hardware_driver", "reason": "physical hardware and device access are outside browser simulation"},
{"id": "linux_realtime_kernel", "reason": "hard real-time kernel scheduling is outside WebAssembly"},
{"id": "host_device_access", "reason": "host paths, sockets and devices are denied by the Worker runtime"},
],
"blockers": [
"official_oracle_missing",
"browser_matrix_unverified",
"configuration_resource_gaps",
"manifest_seed_unassigned",
"license_metadata_not_per_entry",
] + ([] if migration_ready else ["migration_and_rollback_pending"]),
"inputs": [artifact(root, CONTEXT, "context_lock"), artifact(root, SOURCE_INDEX, "source_index"), artifact(root, MANIFEST, "manifest_seed"), artifact(root, CONFIGURATION, "configuration_regression"), artifact(root, PYTHON_MANIFEST, "python_runtime_manifest"), artifact(root, FULL_PORT_DOC, "implementation_contract"), artifact(root, Path("coverage/workspace-migration.report.json"), "migration_rollback_offline"), artifact(root, Path("coverage/workspace-rollback.package.json"), "rollback_package"), artifact(root, Path("coverage/offline-cache.package.json"), "offline_package")],
"report_sha256": None,
}
return write_report(out, report)
def generate_license(root: Path, out: Path) -> dict[str, Any]:
lock = read_json(root, CONTEXT)
sources = read_jsonl(root, SOURCE_INDEX)
manifest = read_jsonl(root, MANIFEST)
test_report = read_json(root, Path("coverage/test-entries.report.json"))
test_rows = read_jsonl(root, Path("coverage/test-entries.jsonl"))
copyright_path = Path("linuxcnc/debian/copyright")
copyright_text = (root / copyright_path).read_text(encoding="utf-8")
declared = sorted(set(re.findall(r"^License:\s*(.+)$", copyright_text, re.MULTILINE)))
download_rows = read_jsonl(root, DOWNLOAD_INDEX)
python_manifest = read_json(root, PYTHON_MANIFEST)
artifact_names = {str(row.get("name")) for row in download_rows}
python_artifacts = python_manifest.get("artifacts", [])
download_valid = all(str(row.get("name")) in artifact_names and row.get("license") for row in python_artifacts)
path_counts = Counter(str(row.get("path", "")).split("/", 1)[0] for row in sources)
real_rows = [row for row in test_rows if row.get("portability") == "REAL-EXCLUDED"]
source_lookup = {str(row.get("source_id")): row for row in sources}
copyright_scopes = parse_copyright_scopes(copyright_text)
license_entries: list[dict[str, Any]] = []
for row in sorted(manifest, key=lambda value: str(value.get("id"))):
source_id = str(row.get("source"))
source_row = source_lookup.get(source_id)
source_path = str(source_row.get("path")) if source_row else ""
source_root = source_path.split("/", 1)[0] if source_path else "unknown"
source_hash = source_row.get("sha256") if source_row else None
manifest_hash = row.get("source_sha")
source_hash_status = "verified" if source_hash and manifest_hash == source_hash else "blocked"
evidence = ["coverage/manifest.jsonl", "coverage/source-index.jsonl"]
owner = None
owner_source = None
license_scope = None
license_declared = None
license_spdx = None
license_status = "blocked_unassigned"
closure_status = "blocked"
if source_root == "linuxcnc":
owner = "upstream-linuxcnc-license-audit"
owner_source = "source_root_policy"
relative_path = source_path[len("linuxcnc/"):] if source_path.startswith("linuxcnc/") else source_path
scope = match_copyright_scope(relative_path, copyright_scopes)
if scope and scope.get("spdx"):
license_scope = scope["pattern"]
license_declared = scope["license"]
license_spdx = scope["spdx"]
license_status = "scoped_declared"
closure_status = "verified_scoped"
evidence.append("linuxcnc/debian/copyright")
else:
license_status = "blocked_scope_unmatched"
evidence.append("linuxcnc/debian/copyright")
elif source_root in {"wasm-core", "web"}:
owner = "web-wasm-license-owner"
owner_source = "implementation_license_decision"
evidence.append(IMPLEMENTATION_LICENSE_DECISION.as_posix())
else:
owner = "source-provenance-license-owner"
owner_source = "source_root_policy"
license_status = "blocked_scope_unknown"
evidence_status = "complete" if source_hash_status == "verified" and all((root / path).is_file() for path in evidence) else "incomplete"
license_entries.append({
"id": row.get("id"),
"source": source_id,
"source_path": source_path or None,
"source_root": source_root,
"source_kind": row.get("kind"),
"source_sha256": source_hash,
"source_hash_status": source_hash_status,
"owner": owner,
"owner_source": owner_source,
"license_scope": license_scope,
"license_declared": license_declared,
"license_spdx": license_spdx,
"license_status": license_status,
"closure_status": closure_status,
"evidence": evidence,
"evidence_status": evidence_status,
"manifest_mapping_status": row.get("mapping_status"),
"manifest_domain": row.get("domain"),
})
license_index_path = root / LICENSE_INDEX
write_jsonl(license_index_path, license_entries)
license_index_artifact = artifact(root, LICENSE_INDEX, "license_source_index")
license_status_counts = Counter(entry["license_status"] for entry in license_entries)
closure_status_counts = Counter(entry["closure_status"] for entry in license_entries)
owner_status_counts = Counter("assigned" if entry["owner"] else "unassigned" for entry in license_entries)
source_hash_counts = Counter(entry["source_hash_status"] for entry in license_entries)
implementation_entries = [entry for entry in license_entries if entry["source_root"] in {"wasm-core", "web"}]
report = {
"schema": "release-license-closure.v1",
"context_id": lock.get("context_id"),
"status": "blocked",
"release_decision": "license_closure_incomplete",
"source_inventory": {
"entries": len(sources),
"manifest_entries": len(manifest),
"path_roots": dict(sorted(path_counts.items())),
"license_field_in_manifest": any("license" in row for row in manifest),
"manifest_license_field_missing_entries": len(manifest),
"unassigned_license_entries": license_status_counts.get("blocked_unassigned", 0),
"status": "partial",
"reason": "manifest v1 has no license field; the external deterministic index below records scoped upstream evidence and keeps unassigned implementation licenses blocked",
},
"entry_closure": {
"index": license_index_artifact,
"entries": len(license_entries),
"owner_semantics": "license_audit_owner; does not replace manifest feature owner or promote needs_owner entries",
"source_hash_status": dict(sorted(source_hash_counts.items())),
"owner_status": dict(sorted(owner_status_counts.items())),
"license_status": dict(sorted(license_status_counts.items())),
"closure_status": dict(sorted(closure_status_counts.items())),
"all_entries_have_owner": all(entry["owner"] for entry in license_entries),
"all_entries_have_evidence_status": all(entry["evidence_status"] for entry in license_entries),
"status": "blocked" if implementation_entries or any(entry["closure_status"] == "blocked" for entry in license_entries) else "verified",
},
"upstream_declarations": {
"files": [artifact(root, Path("linuxcnc/COPYING"), "license_text"), artifact(root, Path("linuxcnc/COPYING.more"), "license_text"), artifact(root, copyright_path, "scoped_license_declarations")],
"declared_spdx_or_legacy_ids": declared,
"default_scope": "linuxcnc/* (subject to debian/copyright path exceptions)",
"matched_entries": len(license_entries) - len(implementation_entries),
"status": "verified_scoped",
},
"implementation_license": {
"paths": ["wasm-core/**", "web/**"],
"status": "blocked",
"decision": "unassigned_and_blocked",
"reason": "no repository LICENSE/NOTICE/SPDX metadata assigns a license to the Web/WASM implementation files; upstream LinuxCNC declarations are not inherited",
"entries": len(implementation_entries),
"decision_artifact": artifact(root, IMPLEMENTATION_LICENSE_DECISION, "implementation_license_decision"),
},
"download_artifacts": {
"status": "verified" if download_valid else "blocked",
"index": artifact(root, DOWNLOAD_INDEX, "download_index"),
"python_runtime_manifest": artifact(root, PYTHON_MANIFEST, "python_runtime_manifest"),
"artifacts": python_artifacts,
},
"real_excluded": {
"test_entry_count": len(real_rows),
"test_report_count": test_report.get("portability", {}).get("REAL-EXCLUDED", 0),
"test_source_ids": sorted(str(row.get("test_source_id")) for row in real_rows),
"runtime_categories": ["hardware_driver", "linux_realtime_kernel", "host_device_access"],
"status": "explicit",
},
"blockers": ["wasm_web_license_not_declared"],
"report_sha256": None,
}
return write_report(out, report)
def manifest_evidence(root: Path, rows: Iterable[dict[str, Any]]) -> tuple[list[dict[str, Any]], dict[str, int]]:
required = ("source", "inputs", "outputs", "test_id", "oracle")
entries = []
missing = Counter()
for row in rows:
if row.get("mapping_status") == "needs_owner":
continue
owner = row.get("domain") if row.get("domain") not in {None, "", "unassigned"} else None
if owner is None:
missing["owner"] += 1
entry = {
"id": row.get("id"),
"source": row.get("source"),
"owner": owner,
"owner_source": "manifest.domain",
"feature_id": row.get("feature_id"),
"inputs": row.get("inputs", []),
"outputs": row.get("outputs", []),
"test_id": row.get("test_id"),
"difference": row.get("difference"),
"difference_status": "none_recorded" if row.get("difference") is None else "recorded",
"oracle_status": row.get("oracle", {}).get("status") if isinstance(row.get("oracle"), dict) else None,
"context_id": row.get("context_id"),
"mapping_status": row.get("mapping_status"),
}
for field in required:
value = entry["oracle_status"] if field == "oracle" else entry[field]
if value is None or value == [] or value == "":
missing[field] += 1
entries.append(entry)
entries.sort(key=lambda item: str(item["id"]))
return entries, dict(sorted(missing.items()))
def generate_index(root: Path, out: Path, compatibility: dict[str, Any], license_report: dict[str, Any]) -> dict[str, Any]:
lock = read_json(root, CONTEXT)
manifest = read_jsonl(root, MANIFEST)
mapped, missing = manifest_evidence(root, manifest)
artifacts = [artifact(root, Path(path), "coverage_evidence") for path in REPORT_PATHS]
artifacts.extend([
artifact(root, Path("coverage/source-index.jsonl"), "source_index"),
artifact(root, Path("coverage/manifest.jsonl"), "manifest_seed"),
artifact(root, Path("python-runtime/manifest.json"), "python_runtime_manifest"),
artifact(root, Path("coverage/compatibility-matrix.report.json"), "compatibility_matrix"),
artifact(root, Path("coverage/license-closure.report.json"), "license_closure"),
artifact(root, LICENSE_INDEX, "license_source_index"),
artifact(root, IMPLEMENTATION_LICENSE_DECISION, "implementation_license_decision"),
])
accepted = sum(1 for row in manifest if row.get("sim_status") == "accepted" or row.get("web_status") == "accepted")
unassigned = sum(1 for row in manifest if row.get("mapping_status") == "needs_owner")
config = read_json(root, CONFIGURATION)
blockers = [
{"id": "manifest_seed_unassigned", "count": unassigned, "status": "blocked"},
{"id": "accepted_entries_missing", "count": accepted, "status": "blocked" if accepted == 0 else "verified"},
{"id": "official_oracle_missing", "count": 1, "status": "blocked"},
{"id": "configuration_resources_missing", "count": config.get("counts", {}).get("resource_missing_rows", 0), "status": "blocked"},
{"id": "browser_matrix_unverified", "count": 1, "status": "blocked"},
{"id": "license_closure_incomplete", "count": len(license_report.get("blockers", [])), "status": "blocked"},
{"id": "migration_rollback_pending", "count": 0 if compatibility.get("migration_rollback_offline", {}).get("status") == "verified" else 1, "status": "verified" if compatibility.get("migration_rollback_offline", {}).get("status") == "verified" else "blocked"},
]
report = {
"schema": "release-evidence-index.v1",
"context_id": lock.get("context_id"),
"status": "blocked",
"release_decision": "blocked_not_release_candidate",
"context_lock": {"path": CONTEXT.as_posix(), "sha256": file_digest(root / CONTEXT), "schema_versions": lock.get("schema_versions")},
"reports": artifacts,
"manifest_summary": {
"total": len(manifest),
"affected": len(mapped),
"needs_owner": unassigned,
"accepted": accepted,
"required_field_missing": missing,
"affected_entries_complete": not missing,
"owner_derivation": "manifest.domain; unassigned is never treated as an owner",
},
"affected_manifest_entries": mapped,
"compatibility_report": {"path": "coverage/compatibility-matrix.report.json", "sha256": compatibility["report_sha256"]},
"license_report": {"path": "coverage/license-closure.report.json", "sha256": license_report["report_sha256"]},
"migration_report": {"path": "coverage/workspace-migration.report.json", "sha256": compatibility.get("migration_rollback_offline", {}).get("artifact", {}).get("sha256")},
"real_excluded": license_report.get("real_excluded"),
"blockers": blockers,
"integrity": {
"no_orphans": config.get("closure", {}).get("graph_orphan_entry_ids") == [] and config.get("closure", {}).get("resource_orphan_entrypoints") == [],
"no_silent_downgrade": bool(config.get("oracle", {}).get("derived_configuration_results_are_not_official")) and all(row.get("startup_preflight") for row in config.get("configuration_entries", [])),
"derived_configuration_not_accepted": bool(config.get("oracle", {}).get("derived_configuration_results_are_not_official")),
"all_affected_entries_have_required_evidence": not missing,
"migration_rollback_offline_verified": compatibility.get("migration_rollback_offline", {}).get("status") == "verified",
"license_source_index_complete": bool(license_report.get("entry_closure", {}).get("all_entries_have_owner")) and bool(license_report.get("entry_closure", {}).get("all_entries_have_evidence_status")),
"implementation_license_blocked_explicitly": license_report.get("implementation_license", {}).get("decision") == "unassigned_and_blocked",
},
"report_sha256": None,
}
return write_report(out, report)
def generate_release_gate(
root: Path,
out: Path,
aggregate: dict[str, Any],
compatibility: dict[str, Any],
license_report: dict[str, Any],
index: dict[str, Any],
) -> dict[str, Any]:
lock = read_json(root, CONTEXT)
recovery = read_json(root, Path("coverage/workspace-recovery.report.json"))
migration = read_json(root, Path("coverage/workspace-migration.report.json"))
context_id = lock.get("context_id")
context_reports = [aggregate, compatibility, license_report, index, recovery, migration]
context_ids_match = all(report.get("context_id") in {None, context_id} for report in context_reports)
recovery_hash_expected = lock.get("workspace_recovery_report_sha256")
recovery_hash_actual = file_digest(root / Path("coverage/workspace-recovery.report.json"))
recovery_hash_locked = recovery_hash_expected == recovery_hash_actual
context_consistent = context_ids_match and recovery_hash_locked
schema_versions = lock.get("schema_versions", {})
schemas_locked = bool(schema_versions) and all(value == 1 for value in schema_versions.values()) and compatibility.get("contracts", {}).get("status") == "verified"
evidence_integrity = all(value is True for key, value in aggregate.get("integrity", {}).items() if key not in {"implementation_license_blocked_explicitly"})
license_index_complete = license_report.get("entry_closure", {}).get("all_entries_have_owner") is True and license_report.get("entry_closure", {}).get("all_entries_have_evidence_status") is True
migration_verified = compatibility.get("migration_rollback_offline", {}).get("status") == "verified" and migration.get("status") == "passed"
blockers = [blocker for blocker in aggregate.get("blockers", []) if blocker.get("status") == "blocked"]
reasons = {
"manifest_seed_unassigned": "manifest seed entries still have no feature owner",
"accepted_entries_missing": "no manifest entry is accepted for release",
"official_oracle_missing": "locked source has no official kinematics/recovery oracle root",
"configuration_resources_missing": "configuration startup/resource gaps remain",
"browser_matrix_unverified": "Chromium/Firefox/Safari minimum versions remain TBD",
"license_closure_incomplete": "57 Web/WASM entries have no approved SPDX/NOTICE assignment",
}
hard_blockers = [
{
**blocker,
"reason": reasons.get(blocker.get("id"), "release gate prerequisite is not verified"),
}
for blocker in blockers
]
input_paths = (
(CONTEXT, "context_lock"),
(Path("coverage/release-closure.report.json"), "release_closure"),
(Path("coverage/release-evidence-index.json"), "release_evidence_index"),
(Path("coverage/compatibility-matrix.report.json"), "compatibility_matrix"),
(Path("coverage/license-closure.report.json"), "license_closure"),
(LICENSE_INDEX, "license_source_index"),
(Path("coverage/workspace-recovery.report.json"), "workspace_recovery"),
(Path("coverage/workspace-migration.report.json"), "workspace_migration"),
(Path("coverage/workspace-rollback.package.json"), "rollback_package"),
(Path("coverage/offline-cache.package.json"), "offline_package"),
(IMPLEMENTATION_LICENSE_DECISION, "implementation_license_decision"),
)
report = {
"schema": "release-candidate-gate.v1",
"context_id": context_id,
"status": "blocked" if hard_blockers else "passed",
"release_decision": "blocked_not_release_candidate" if hard_blockers else "release_candidate_eligible",
"release_action": "STOP_RELEASE" if hard_blockers else "ALLOW_RELEASE_CANDIDATE",
"candidate_eligible": not hard_blockers,
"source_baseline": {
"linuxcnc_version": lock.get("linuxcnc_version"),
"linuxcnc_source_sha": lock.get("linuxcnc_source_sha"),
"context_lock": artifact(root, CONTEXT, "context_lock"),
},
"checks": {
"context_consistency": {"status": "verified" if context_consistent else "blocked", "context_ids_match": context_ids_match, "context_id": context_id, "recovery_report_hash_locked": recovery_hash_locked, "recovery_report_hash": recovery_hash_actual},
"locked_schema_contracts": {"status": "verified" if schemas_locked else "blocked", "schema_versions": schema_versions, "compatibility_contract_status": compatibility.get("contracts", {}).get("status")},
"evidence_integrity": {"status": "verified" if evidence_integrity else "blocked", "all_aggregate_integrity_flags": evidence_integrity},
"license_source_index": {"status": "verified" if license_index_complete else "blocked", "entries": license_report.get("entry_closure", {}).get("entries"), "all_entries_have_owner": license_report.get("entry_closure", {}).get("all_entries_have_owner"), "all_entries_have_evidence_status": license_report.get("entry_closure", {}).get("all_entries_have_evidence_status")},
"migration_rollback_offline": {"status": "verified" if migration_verified else "blocked", "compatibility_status": compatibility.get("migration_rollback_offline", {}).get("status"), "migration_status": migration.get("status")},
"release_prerequisites": {"status": "blocked" if hard_blockers else "verified", "blocked_count": len(hard_blockers), "verified_count": sum(1 for blocker in aggregate.get("blockers", []) if blocker.get("status") == "verified")},
},
"blockers": hard_blockers,
"blocked_count": len(hard_blockers),
"rebuild": {
"commands": [
"python3 scripts/generate_release_closure.py",
"node web/worker/test_replay.mjs",
"node web/workspace/test_recovery.mjs",
"node web/workspace/test_migration.mjs",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v",
],
"deterministic_inputs": [path.as_posix() for path, _ in input_paths],
},
"failure_policy": {
"on_missing_input": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"on_blocked_prerequisite": "STOP_RELEASE",
"forbidden": ["delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED", "promote_blocked_to_accepted"],
},
"inputs": [artifact(root, path, role) for path, role in input_paths],
"report_sha256": None,
}
return write_report(out, report)
def generate(root: Path, output_dir: Path) -> dict[str, dict[str, Any]]:
root = root.resolve()
output_dir = output_dir if output_dir.is_absolute() else root / output_dir
compatibility = generate_compatibility(root, output_dir / "compatibility-matrix.report.json")
license_report = generate_license(root, output_dir / "license-closure.report.json")
index = generate_index(root, output_dir / "release-evidence-index.json", compatibility, license_report)
aggregate = write_report(output_dir / "release-closure.report.json", {
"schema": "release-closure-report.v1",
"context_id": compatibility["context_id"],
"status": "blocked",
"release_decision": "blocked_not_release_candidate",
"compatibility": {"path": "coverage/compatibility-matrix.report.json", "sha256": compatibility["report_sha256"]},
"license": {"path": "coverage/license-closure.report.json", "sha256": license_report["report_sha256"]},
"migration": {"path": "coverage/workspace-migration.report.json", "sha256": compatibility.get("migration_rollback_offline", {}).get("artifact", {}).get("sha256")},
"evidence_index": {"path": "coverage/release-evidence-index.json", "sha256": index["report_sha256"]},
"blockers": index["blockers"],
"integrity": index["integrity"],
"report_sha256": None,
})
gate = generate_release_gate(root, output_dir / "release-candidate-gate.report.json", aggregate, compatibility, license_report, index)
return {"compatibility": compatibility, "license": license_report, "index": index, "aggregate": aggregate, "gate": gate}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output-dir", type=Path, default=Path("coverage"))
args = parser.parse_args()
reports = generate(args.repo_root, args.output_dir)
blocked_count = sum(1 for blocker in reports["index"]["blockers"] if blocker.get("status") == "blocked")
print("release_closure=" + reports["index"]["status"] + " gate=" + reports["gate"]["status"] + " action=" + reports["gate"]["release_action"] + " blockers=" + str(blocked_count))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,333 @@
#!/usr/bin/env python3
"""Generate the deterministic K8-REL-07 remediation intake.
The intake turns the frozen K8 release blockers into independently traceable
work items. It is an orchestration artifact: a verified intake does not
resolve a blocker or make the package eligible for release.
"""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
CLOSEOUT = Path("coverage/k8-milestone-closeout.report.json")
AUDIT = Path("coverage/post-release-audit.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
CLOSURE = Path("coverage/release-closure.report.json")
INDEX = Path("coverage/release-evidence-index.json")
OUTPUT = Path("coverage/release-remediation-intake.report.json")
BLOCKER_TASKS = {
"manifest_seed_unassigned": {
"task_id": "K8-REL-08",
"title": "manifest ownership remediation",
},
"accepted_entries_missing": {
"task_id": "K8-REL-09",
"title": "release acceptance evidence remediation",
},
"official_oracle_missing": {
"task_id": "K8-REL-10",
"title": "official oracle input remediation",
},
"configuration_resources_missing": {
"task_id": "K8-REL-11",
"title": "configuration resource remediation",
},
"browser_matrix_unverified": {
"task_id": "K8-REL-12",
"title": "browser matrix verification",
},
"license_closure_incomplete": {
"task_id": "K8-REL-13",
"title": "implementation license closure",
},
}
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {
"path": path.as_posix(),
"role": role,
"status": "present",
"bytes": full.stat().st_size,
"sha256": sha,
}
def declared_report_hash(path: Path, report: dict[str, Any], error: str | None) -> dict[str, Any]:
declared = report.get("report_sha256")
actual = digest({key: value for key, value in report.items() if key != "report_sha256"}) if report else None
status = "verified" if error is None and declared and declared == actual else "blocked"
return {
"path": path.as_posix(),
"declared": declared,
"actual": actual,
"status": status,
**({"error": error} if error else {}),
}
def compare_inputs(root: Path, closeout: dict[str, Any]) -> tuple[list[dict[str, Any]], bool]:
rows: list[dict[str, Any]] = []
all_match = isinstance(closeout.get("inputs"), list)
for expected in closeout.get("inputs", []):
path_text = expected.get("path") if isinstance(expected, dict) else None
if not isinstance(path_text, str):
rows.append({"path": None, "status": "blocked", "reason": "closeout input has no path"})
all_match = False
continue
path = Path(path_text)
full = root / path
actual_sha = file_digest(full)
actual_bytes = full.stat().st_size if full.is_file() else None
matches = (
actual_sha is not None
and actual_sha == expected.get("sha256")
and actual_bytes == expected.get("bytes")
)
rows.append({
"path": path.as_posix(),
"role": expected.get("role"),
"expected_sha256": expected.get("sha256"),
"actual_sha256": actual_sha,
"expected_bytes": expected.get("bytes"),
"actual_bytes": actual_bytes,
"status": "verified" if matches else "blocked",
})
all_match = all_match and matches
return rows, all_match
def context_check(lock: dict[str, Any], reports: dict[Path, dict[str, Any]]) -> dict[str, Any]:
context_id = lock.get("context_id")
observations = []
all_match = bool(context_id)
for path, report in reports.items():
observed = report.get("context_id")
matches = bool(observed) and observed == context_id
observations.append({
"path": path.as_posix(),
"context_id": observed,
"status": "verified" if matches else "blocked",
})
all_match = all_match and matches
return {
"context_id": context_id,
"reports": observations,
"status": "verified" if all_match else "blocked",
}
def blocker_check(root: Path, audit: dict[str, Any], gate: dict[str, Any]) -> tuple[list[dict[str, Any]], dict[str, Any]]:
audit_rows = audit.get("blockers") if isinstance(audit.get("blockers"), list) else []
gate_ids = {row.get("id") for row in gate.get("blockers", []) if isinstance(row, dict)}
rows: list[dict[str, Any]] = []
all_valid = len(audit_rows) == len(BLOCKER_TASKS) and gate_ids == set(BLOCKER_TASKS)
for sequence, row in enumerate(audit_rows, start=1):
blocker_id = row.get("id")
task = BLOCKER_TASKS.get(blocker_id, {})
evidence = row.get("evidence") if isinstance(row.get("evidence"), list) else []
evidence_paths = [item.get("path") for item in evidence if isinstance(item, dict)]
evidence_present = bool(evidence_paths) and all(
isinstance(item, dict)
and item.get("status") == "present"
and isinstance(item.get("path"), str)
and (root / Path(item["path"])).is_file()
for item in evidence
)
valid = (
blocker_id in BLOCKER_TASKS
and row.get("status") == "blocked"
and row.get("owner")
and row.get("review_owner")
and row.get("remediation")
and evidence_present
and task.get("task_id")
)
work_item = {
"sequence": sequence,
"id": blocker_id,
"task_id": task.get("task_id"),
"title": task.get("title"),
"count": row.get("count"),
"owner": row.get("owner"),
"review_owner": row.get("review_owner"),
"reason": row.get("reason"),
"remediation": row.get("remediation"),
"review_trigger": row.get("review_trigger"),
"evidence": evidence,
"status": "blocked",
"release_effect": "STOP_RELEASE",
"intake_status": "verified" if valid else "blocked",
}
rows.append(work_item)
all_valid = all_valid and valid
identity = {row.get("id") for row in rows} == set(BLOCKER_TASKS)
checks = {
"status": "verified" if all_valid and identity else "blocked",
"expected_count": len(BLOCKER_TASKS),
"observed_count": len(rows),
"expected_ids": sorted(BLOCKER_TASKS),
"observed_ids": sorted(str(row.get("id")) for row in rows),
"identity_preserved": identity,
"all_items_have_owner_and_evidence": all_valid,
}
return rows, checks
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
paths = (CONTEXT, CLOSEOUT, AUDIT, GATE, CLOSURE, INDEX)
documents: dict[Path, dict[str, Any]] = {}
errors: dict[str, str] = {}
for path in paths:
documents[path], error = read_json(root, path)
if error:
errors[path.as_posix()] = error
lock = documents[CONTEXT]
closeout = documents[CLOSEOUT]
audit = documents[AUDIT]
gate = documents[GATE]
reports_for_context = {path: documents[path] for path in (CLOSEOUT, AUDIT, GATE, CLOSURE, INDEX)}
input_rows, inputs_match = compare_inputs(root, closeout)
context = context_check(lock, reports_for_context)
blocker_rows, blockers = blocker_check(root, audit, gate)
report_hashes = [
declared_report_hash(path, documents[path], errors.get(path.as_posix()))
for path in (CLOSEOUT, AUDIT, GATE, CLOSURE, INDEX)
]
report_hashes_ok = all(row["status"] == "verified" for row in report_hashes)
stop_decision = (
closeout.get("release_action") == "STOP_RELEASE"
and closeout.get("release_decision") == "blocked_not_release_candidate"
and closeout.get("candidate_eligible") is False
and closeout.get("blocked_count") == 6
and gate.get("release_action") == "STOP_RELEASE"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and audit.get("release_action") == "STOP_RELEASE"
and audit.get("candidate_eligible") is False
and audit.get("blocked_count") == 6
)
no_reclassification = all(
row.get("status") == "blocked" and row.get("release_effect") == "STOP_RELEASE"
for row in blocker_rows
) and len(blocker_rows) == 6
checks = {
"input_hashes": {"status": "verified" if inputs_match else "blocked", "comparisons": input_rows},
"source_report_self_hashes": {"status": "verified" if report_hashes_ok else "blocked", "reports": report_hashes},
"context_consistency": context,
"blocker_identity": blockers,
"stop_decision_preserved": {"status": "verified" if stop_decision else "blocked", "release_action": "STOP_RELEASE" if stop_decision else None},
"no_reclassification": {"status": "verified" if no_reclassification else "blocked"},
}
checks_ok = not errors and all(value.get("status") == "verified" for value in checks.values())
report: dict[str, Any] = {
"schema": "release-remediation-intake.v1",
"context_id": lock.get("context_id"),
"intake_id": "K8-REL-07",
"generated_at": lock.get("generated_at"),
"status": "complete" if checks_ok else "blocked",
"intake_decision": "remediation_required",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"source_baseline": {
"linuxcnc_version": lock.get("linuxcnc_version"),
"linuxcnc_source_sha": lock.get("linuxcnc_source_sha"),
"context_lock": artifact(root, CONTEXT, "context_lock"),
},
"inputs": [
artifact(root, CLOSEOUT, "k8_milestone_closeout"),
artifact(root, AUDIT, "post_release_audit"),
artifact(root, GATE, "release_candidate_gate"),
artifact(root, CLOSURE, "release_closure"),
artifact(root, INDEX, "release_evidence_index"),
artifact(root, CONTEXT, "context_lock"),
],
"checks": checks,
"work_items": blocker_rows,
"blocked_count": len(blocker_rows),
"next_tasks": [
{"task_id": row["task_id"], "title": row["title"], "depends_on": ["K8-REL-07"], "status": "pending"}
for row in blocker_rows
],
"rebuild": {
"commands": [
"python3 scripts/generate_release_closure.py",
"python3 scripts/generate_post_release_audit.py",
"python3 scripts/generate_k8_milestone_closeout.py",
"python3 scripts/generate_release_remediation_intake.py",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v",
],
"deterministic_inputs": [path.as_posix() for path in paths],
},
"failure_policy": {
"on_missing_input": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"on_context_drift": "STOP_RELEASE",
"forbidden": ["delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED", "promote_blocked_to_accepted"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("release_remediation_intake=" + report["status"] + " action=" + report["release_action"] + " blockers=" + str(report["blocked_count"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Generate the deterministic K8-REL-14 remediation evidence roundup."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
INTAKE = Path("coverage/release-remediation-intake.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
AUDIT = Path("coverage/post-release-audit.report.json")
CLOSEOUT = Path("coverage/k8-milestone-closeout.report.json")
REPORTS = (
(Path("coverage/manifest-ownership-remediation.report.json"), "K8-REL-08"),
(Path("coverage/release-acceptance-remediation.report.json"), "K8-REL-09"),
(Path("coverage/official-oracle-remediation.report.json"), "K8-REL-10"),
(Path("coverage/configuration-resource-remediation.report.json"), "K8-REL-11"),
(Path("coverage/browser-matrix-remediation.report.json"), "K8-REL-12"),
(Path("coverage/implementation-license-remediation.report.json"), "K8-REL-13"),
)
OUTPUT = Path("coverage/remediation-evidence-roundup.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> tuple[dict[str, Any], str | None]:
try:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value, None
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
return {}, str(exc)
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def report_self_hash(report: dict[str, Any]) -> bool:
declared = report.get("report_sha256")
return bool(declared) and declared == digest({key: value for key, value in report.items() if key != "report_sha256"})
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
paths = (CONTEXT, INTAKE, GATE, AUDIT, CLOSEOUT, *(path for path, _task in REPORTS))
documents: dict[Path, dict[str, Any]] = {}
errors: dict[str, str] = {}
for path in paths:
documents[path], error = read_json(root, path)
if error:
errors[path.as_posix()] = error
lock = documents[CONTEXT]
intake = documents[INTAKE]
gate = documents[GATE]
audit = documents[AUDIT]
closeout = documents[CLOSEOUT]
remediation = []
all_report_hashes = True
all_context = bool(lock.get("context_id"))
all_complete = True
summaries: dict[str, Any] = {}
for path, task_id in REPORTS:
report = documents[path]
self_hash = report_self_hash(report)
context_ok = report.get("context_id") == lock.get("context_id")
complete = report.get("status") == "complete" and str(report.get("remediation_status", "")).startswith("blocked_")
all_report_hashes = all_report_hashes and self_hash
all_context = all_context and context_ok
all_complete = all_complete and complete
summaries[task_id] = {
"path": path.as_posix(),
"status": report.get("status"),
"remediation_status": report.get("remediation_status"),
"report_sha256": report.get("report_sha256"),
"self_hash": "verified" if self_hash else "blocked",
"context": "verified" if context_ok else "blocked",
}
intake_ids = {row.get("id") for row in intake.get("work_items", []) if isinstance(row, dict)}
gate_ids = {row.get("id") for row in gate.get("blockers", []) if isinstance(row, dict)}
audit_ids = {row.get("id") for row in audit.get("blockers", []) if isinstance(row, dict)}
blocker_identity = intake_ids == gate_ids == audit_ids and len(intake_ids) == 6
stop_ok = all(
report.get("release_action") == "STOP_RELEASE"
and report.get("candidate_eligible") is False
for report in (intake, gate, audit, closeout)
) and all(
documents[path].get("release_action") == "STOP_RELEASE"
and documents[path].get("candidate_eligible") is False
for path, _task in REPORTS
)
no_promotion = (
documents[REPORTS[0][0]].get("counts", {}).get("accepted_by_this_artifact") == 0
and documents[REPORTS[1][0]].get("counts", {}).get("eligible_for_acceptance") == 0
and documents[REPORTS[1][0]].get("counts", {}).get("manifest_accepted") == 0
and documents[REPORTS[2][0]].get("oracle_inventory", {}).get("official_inputs_added") == 0
and documents[REPORTS[2][0]].get("oracle_inventory", {}).get("official_goldens_added") == 0
and documents[REPORTS[3][0]].get("counts", {}).get("resolved_by_this_artifact") == 0
and documents[REPORTS[4][0]].get("declared_matrix", {}).get("minimum") == {"chromium": "TBD", "firefox": "TBD", "safari": "TBD"}
and documents[REPORTS[5][0]].get("counts", {}).get("approved_by_this_artifact") == 0
)
checks = {
"remediation_reports_complete": {"status": "verified" if all_complete else "blocked", "reports": summaries},
"remediation_report_self_hashes": {"status": "verified" if all_report_hashes else "blocked"},
"context_consistency": {"status": "verified" if all_context else "blocked", "context_id": lock.get("context_id")},
"blocker_identity_preserved": {"status": "verified" if blocker_identity else "blocked", "count": len(intake_ids), "ids": sorted(str(value) for value in intake_ids)},
"no_promotion_or_fabrication": {"status": "verified" if no_promotion else "blocked"},
"stop_release_preserved": {"status": "verified" if stop_ok else "blocked", "release_action": "STOP_RELEASE" if stop_ok else None},
}
checks_ok = not errors and all(value["status"] == "verified" for value in checks.values())
report: dict[str, Any] = {
"schema": "remediation-evidence-roundup.v1",
"context_id": lock.get("context_id"),
"task_id": "K8-REL-14",
"status": "complete" if checks_ok else "blocked",
"roundup_status": "blocked_pending_owner_actions",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"inputs": [artifact(root, CONTEXT, "context_lock"), artifact(root, INTAKE, "remediation_intake"), artifact(root, GATE, "release_candidate_gate"), artifact(root, AUDIT, "post_release_audit"), artifact(root, CLOSEOUT, "k8_closeout")] + [artifact(root, path, task_id) for path, task_id in REPORTS],
"checks": checks,
"remediation_reports": summaries,
"remaining_owner_actions": [
{"blocker": "manifest_seed_unassigned", "action": "review proposed workstream roles and approve a manifest/context update"},
{"blocker": "accepted_entries_missing", "action": "supply ready official oracle/context evidence before any acceptance"},
{"blocker": "official_oracle_missing", "action": "add or approve locked official oracle inputs"},
{"blocker": "configuration_resources_missing", "action": "review reference candidates and restore/index resources"},
{"blocker": "browser_matrix_unverified", "action": "pin minimums and run Chromium/Firefox/Safari matrix"},
{"blocker": "license_closure_incomplete", "action": "approve SPDX/NOTICE policy for Web/WASM implementation"},
],
"review_policy": {
"next_transition": "owner review plus new context/gate regeneration",
"forbidden": ["promote_blocked_to_accepted", "delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED", "overwrite_context_lock_in_place"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("remediation_roundup=" + report["status"] + " release=" + report["release_action"] + " checks=" + str(len(report["checks"])))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,37 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_browser_matrix_remediation import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class BrowserMatrixRemediationTests(unittest.TestCase):
def test_contract_records_planned_matrix_and_explicit_environment_gap(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "browser-matrix-remediation.v1")
self.assertEqual(report["task_id"], "K8-REL-12")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["remediation_status"], "blocked_matrix_unverified")
self.assertEqual(report["declared_matrix"]["minimum"], {"chromium": "TBD", "firefox": "TBD", "safari": "TBD"})
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertEqual({row["browser"] for row in report["observed_browsers"]}, {"chromium", "firefox", "safari"})
self.assertEqual(report["observed_browsers"][2]["browser"], "safari")
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,42 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_configuration_resource_remediation import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class ConfigurationResourceRemediationTests(unittest.TestCase):
def test_contract_retains_all_resource_and_startup_blockers(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "configuration-resource-remediation.v1")
self.assertEqual(report["task_id"], "K8-REL-11")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["remediation_status"], "blocked_resource_gaps")
self.assertEqual(report["counts"]["resource_rows"], 16093)
self.assertEqual(report["counts"]["missing_rows"], 997)
self.assertEqual(report["counts"]["unresolved_rows"], 91)
self.assertEqual(report["counts"]["unindexed_configuration_entries"], 59)
self.assertEqual(report["counts"]["unregistered_startup_entries"], 3)
self.assertEqual(report["counts"]["resolved_by_this_artifact"], 0)
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertEqual(len(report["missing_rows"]), 997)
self.assertEqual(len(report["unresolved_rows"]), 91)
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,36 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_implementation_license_remediation import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class ImplementationLicenseRemediationTests(unittest.TestCase):
def test_contract_keeps_all_web_wasm_entries_blocked(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "implementation-license-remediation.v1")
self.assertEqual(report["task_id"], "K8-REL-13")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["remediation_status"], "blocked_pending_owner_decision")
self.assertEqual(report["counts"], {"approved_by_this_artifact": 0, "approved_spdx": 0, "blocked_unassigned": 57, "implementation_entries": 57})
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertTrue(all(row["license_status"] == "blocked_unassigned" for row in report["entries"]))
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,35 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_k8_milestone_closeout import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class K8MilestoneCloseoutTests(unittest.TestCase):
def test_closeout_contract_preserves_release_stop(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "k8-milestone-closeout.v1")
self.assertEqual(report["task_id"], "K8-REL-06")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["milestone_status"], "closed_with_release_blockers")
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertEqual(report["blocked_count"], 6)
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,54 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_release_closure import generate
ROOT = Path(__file__).resolve().parents[1]
REPORT = ROOT / "coverage/license-closure.report.json"
INDEX = ROOT / "coverage/license-source-index.jsonl"
DECISION = ROOT / "docs/linuxcnc-web-wasm-license-decision.md"
class LicenseSourceClosureTests(unittest.TestCase):
def test_entry_closure_contract_and_explicit_implementation_blocker(self):
report = json.loads(REPORT.read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "release-license-closure.v1")
self.assertEqual(report["status"], "blocked")
self.assertEqual(report["entry_closure"]["entries"], 8053)
self.assertEqual(report["entry_closure"]["source_hash_status"], {"verified": 8053})
self.assertEqual(report["entry_closure"]["owner_status"], {"assigned": 8053})
self.assertEqual(report["entry_closure"]["license_status"], {"blocked_unassigned": 57, "scoped_declared": 7996})
self.assertTrue(report["entry_closure"]["all_entries_have_owner"])
self.assertTrue(report["entry_closure"]["all_entries_have_evidence_status"])
self.assertEqual(report["upstream_declarations"]["matched_entries"], 7996)
self.assertEqual(report["implementation_license"]["entries"], 57)
self.assertEqual(report["implementation_license"]["decision"], "unassigned_and_blocked")
self.assertEqual(report["blockers"], ["wasm_web_license_not_declared"])
self.assertTrue(DECISION.is_file())
def test_index_is_complete_and_deterministic(self):
rows = [json.loads(line) for line in INDEX.read_text(encoding="utf-8").splitlines() if line.strip()]
self.assertEqual(len(rows), 8053)
self.assertEqual(len({row["id"] for row in rows}), 8053)
self.assertEqual(rows, sorted(rows, key=lambda row: row["id"]))
self.assertTrue(all(row["source_hash_status"] == "verified" for row in rows))
self.assertTrue(all(row["owner"] and row["evidence_status"] == "complete" for row in rows))
self.assertEqual(sum(row["closure_status"] == "verified_scoped" for row in rows), 7996)
self.assertEqual(sum(row["license_status"] == "blocked_unassigned" for row in rows), 57)
self.assertTrue(all(row["license_spdx"] for row in rows if row["source_root"] == "linuxcnc"))
self.assertTrue(all(row["license_spdx"] is None for row in rows if row["source_root"] in {"wasm-core", "web"}))
def test_generation_is_deterministic(self):
before = {path: path.read_bytes() for path in (REPORT, INDEX)}
generate(ROOT, ROOT / "coverage")
after = {path: path.read_bytes() for path in (REPORT, INDEX)}
self.assertEqual(before, after)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,38 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_manifest_ownership_remediation import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class ManifestOwnershipRemediationTests(unittest.TestCase):
def test_contract_covers_all_unassigned_seeds_without_clearing_release_stop(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "manifest-ownership-remediation.v1")
self.assertEqual(report["task_id"], "K8-REL-08")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["remediation_status"], "blocked_pending_manifest_review")
self.assertEqual(report["counts"], {"accepted_by_this_artifact": 0, "mapped": 6249, "manifest_total": 8053, "needs_owner": 6249})
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertTrue(all(row["assignment_status"] == "proposed_blocked_review" for row in report["assignments"]))
self.assertTrue(all(row["owner"] and row["review_owner"] for row in report["assignments"]))
self.assertEqual(len({row["id"] for row in report["assignments"]}), 6249)
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,35 @@
import json
import hashlib
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REPORT = ROOT / "coverage/workspace-migration.report.json"
class MigrationClosureTests(unittest.TestCase):
def test_report_contract_and_blockers(self):
report = json.loads(REPORT.read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "workspace-migration-report.v1")
self.assertEqual(report["status"], "passed")
self.assertEqual(report["implementation_status"], "verified")
self.assertEqual(report["format"]["schema"], "workspace-package.v1")
self.assertEqual(report["format"]["supported_versions"], [1, 2])
self.assertTrue(report["format"]["payload_schemas_unchanged"])
self.assertTrue(report["migration"]["round_trip_equal"])
self.assertTrue(report["migration"]["deterministic"])
self.assertEqual(report["rollback"]["failure_result"], "rollback_injected_failure")
self.assertEqual(report["rollback"]["recovery_status"], "recovered")
self.assertEqual(report["offline"]["offline_start"], "ready")
self.assertEqual(report["offline"]["missing_resource_result"], "offline_resource_invalid")
self.assertTrue((ROOT / "coverage/workspace-rollback.package.json").is_file())
self.assertTrue((ROOT / "coverage/offline-cache.package.json").is_file())
self.assertTrue(report["recovery_endpoint"]["no_silent_downgrade"])
self.assertEqual(report["oracle"]["status"], "blocked")
unsigned = {key: value for key, value in report.items() if key != "report_sha256"}
self.assertEqual(report["report_sha256"], hashlib.sha256(json.dumps(unsigned, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest())
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,39 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_official_oracle_remediation import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class OfficialOracleRemediationTests(unittest.TestCase):
def test_contract_retains_missing_official_inputs(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "official-oracle-remediation.v1")
self.assertEqual(report["task_id"], "K8-REL-10")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["remediation_status"], "blocked_missing_locked_inputs")
self.assertEqual(report["oracle_inventory"]["kinematics_samples"], 11)
self.assertEqual(report["oracle_inventory"]["kinematics_ready"], 0)
self.assertEqual(report["oracle_inventory"]["official_inputs_added"], 0)
self.assertEqual(report["oracle_inventory"]["official_goldens_added"], 0)
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertTrue(all(row["official_unit_test"] is False for row in report["oracle_inventory"]["matrixkins_candidate_files"]))
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,37 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_owner_review_gate_refresh import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class OwnerReviewGateRefreshTests(unittest.TestCase):
def test_contract_fails_closed_without_owner_inputs(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "owner-review-gate-refresh.v1")
self.assertEqual(report["task_id"], "K8-REL-15")
self.assertEqual(report["status"], "blocked")
self.assertEqual(report["review_status"], "owner_input_required")
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertEqual(len(report["decisions"]), 6)
self.assertEqual(report["checks"]["owner_inputs_present"]["status"], "blocked")
self.assertEqual(report["checks"]["gate_refresh_allowed"]["status"], "blocked")
self.assertEqual(report["checks"]["release_stop_preserved"]["status"], "verified")
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,52 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_post_release_audit import AUDIT, generate
ROOT = Path(__file__).resolve().parents[1]
class PostReleaseAuditTests(unittest.TestCase):
def test_report_contract_preserves_stop_decision_and_blockers(self):
report = json.loads((ROOT / AUDIT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "post-release-audit.v1")
self.assertEqual(report["audit_id"], "K8-REL-05")
self.assertEqual(report["status"], "blocked")
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertEqual(report["release_decision"], "blocked_not_release_candidate")
self.assertFalse(report["candidate_eligible"])
self.assertEqual(report["blocked_count"], 6)
self.assertEqual(report["checks"]["input_hashes"]["status"], "verified")
self.assertEqual(report["checks"]["context_consistency"]["status"], "verified")
self.assertEqual(report["checks"]["cross_references"]["status"], "verified")
self.assertEqual(report["checks"]["blocker_review"]["status"], "verified")
self.assertEqual(report["checks"]["gate_checks"]["status"], "verified")
self.assertTrue(all(row["owner"] and row["review_owner"] for row in report["blockers"]))
self.assertTrue(all(row["evidence"] and row["remediation"] for row in report["blockers"]))
self.assertEqual(
{row["id"] for row in report["blockers"]},
{
"manifest_seed_unassigned",
"accepted_entries_missing",
"official_oracle_missing",
"configuration_resources_missing",
"browser_matrix_unverified",
"license_closure_incomplete",
},
)
def test_generation_is_deterministic(self):
before = (ROOT / AUDIT).read_bytes()
generated = generate(ROOT, ROOT / AUDIT)
after = (ROOT / AUDIT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,43 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_release_acceptance_remediation import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class ReleaseAcceptanceRemediationTests(unittest.TestCase):
def test_contract_keeps_all_entries_blocked(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "release-acceptance-remediation.v1")
self.assertEqual(report["task_id"], "K8-REL-09")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["remediation_status"], "blocked_no_eligible_entries")
self.assertEqual(report["counts"], {
"affected_entries": 1804,
"blocked_for_acceptance": 1804,
"eligible_for_acceptance": 0,
"manifest_accepted": 0,
"manifest_needs_owner": 6249,
"manifest_total": 8053,
})
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertEqual(len(report["blocked_entry_ids"]), 1804)
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,43 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_release_closure import generate
ROOT = Path(__file__).resolve().parents[1]
GATE = ROOT / "coverage/release-candidate-gate.report.json"
class ReleaseCandidateGateTests(unittest.TestCase):
def test_gate_contract_stops_release_on_explicit_blockers(self):
gate = json.loads(GATE.read_text(encoding="utf-8"))
self.assertEqual(gate["schema"], "release-candidate-gate.v1")
self.assertEqual(gate["status"], "blocked")
self.assertEqual(gate["release_decision"], "blocked_not_release_candidate")
self.assertEqual(gate["release_action"], "STOP_RELEASE")
self.assertFalse(gate["candidate_eligible"])
self.assertEqual(gate["blocked_count"], 6)
self.assertTrue(gate["checks"]["context_consistency"]["context_ids_match"])
self.assertEqual(gate["checks"]["locked_schema_contracts"]["status"], "verified")
self.assertEqual(gate["checks"]["license_source_index"]["status"], "verified")
self.assertEqual(gate["checks"]["migration_rollback_offline"]["status"], "verified")
self.assertEqual(gate["checks"]["release_prerequisites"]["status"], "blocked")
self.assertIn("official_oracle_missing", {blocker["id"] for blocker in gate["blockers"]})
license_blocker = next(blocker for blocker in gate["blockers"] if blocker["id"] == "license_closure_incomplete")
self.assertIn("57 Web/WASM", license_blocker["reason"])
self.assertEqual(gate["failure_policy"]["on_hash_mismatch"], "STOP_RELEASE")
def test_gate_generation_is_deterministic(self):
before = GATE.read_bytes()
generated = generate(ROOT, ROOT / "coverage")
after = GATE.read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["gate"]["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,62 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_release_closure import generate
ROOT = Path(__file__).resolve().parents[1]
COMPATIBILITY = ROOT / "coverage/compatibility-matrix.report.json"
LICENSE = ROOT / "coverage/license-closure.report.json"
INDEX = ROOT / "coverage/release-evidence-index.json"
AGGREGATE = ROOT / "coverage/release-closure.report.json"
class ReleaseClosureTests(unittest.TestCase):
def test_report_contract_and_explicit_blockers(self):
compatibility = json.loads(COMPATIBILITY.read_text(encoding="utf-8"))
license_report = json.loads(LICENSE.read_text(encoding="utf-8"))
index = json.loads(INDEX.read_text(encoding="utf-8"))
aggregate = json.loads(AGGREGATE.read_text(encoding="utf-8"))
self.assertEqual(compatibility["schema"], "release-compatibility-matrix.v1")
self.assertEqual(license_report["schema"], "release-license-closure.v1")
self.assertEqual(index["schema"], "release-evidence-index.v1")
self.assertEqual(aggregate["schema"], "release-closure-report.v1")
self.assertEqual(aggregate["evidence_index"]["sha256"], index["report_sha256"])
self.assertEqual(index["status"], "blocked")
self.assertIn("official_oracle_missing", compatibility["blockers"])
self.assertIn("manifest_seed_unassigned", {row["id"] for row in index["blockers"]})
self.assertTrue(compatibility["oracle"]["status"] == "blocked")
self.assertTrue(license_report["source_inventory"]["unassigned_license_entries"] > 0)
self.assertEqual(license_report["entry_closure"]["entries"], 8053)
self.assertTrue(license_report["entry_closure"]["all_entries_have_owner"])
self.assertTrue(license_report["entry_closure"]["all_entries_have_evidence_status"])
self.assertTrue(index["integrity"]["license_source_index_complete"])
self.assertTrue(index["integrity"]["implementation_license_blocked_explicitly"])
self.assertEqual(index["manifest_summary"]["total"], 8053)
self.assertEqual(index["manifest_summary"]["needs_owner"], 6249)
self.assertEqual(index["manifest_summary"]["accepted"], 0)
def test_affected_entries_have_explicit_evidence_shape(self):
index = json.loads(INDEX.read_text(encoding="utf-8"))
rows = index["affected_manifest_entries"]
self.assertEqual(len(rows), 1804)
self.assertTrue(index["manifest_summary"]["affected_entries_complete"])
self.assertTrue(all(row["owner"] for row in rows))
self.assertTrue(all(row["inputs"] and row["outputs"] and row["test_id"] for row in rows))
self.assertTrue(all(row["oracle_status"] == "blocked" for row in rows))
self.assertTrue(all(row["difference_status"] == "none_recorded" for row in rows))
def test_generation_is_deterministic(self):
before = {path: path.read_bytes() for path in (COMPATIBILITY, LICENSE, INDEX, AGGREGATE)}
generated = generate(ROOT, ROOT / "coverage")
after = {path: path.read_bytes() for path in (COMPATIBILITY, LICENSE, INDEX, AGGREGATE)}
self.assertEqual(before, after)
self.assertEqual(generated["index"]["report_sha256"], json.loads(after[INDEX].decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,39 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_release_remediation_intake import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class ReleaseRemediationIntakeTests(unittest.TestCase):
def test_intake_contract_preserves_stop_and_expands_six_items(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "release-remediation-intake.v1")
self.assertEqual(report["intake_id"], "K8-REL-07")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["intake_decision"], "remediation_required")
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertEqual(report["blocked_count"], 6)
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertEqual([row["task_id"] for row in report["work_items"]], [
"K8-REL-08", "K8-REL-09", "K8-REL-10", "K8-REL-11", "K8-REL-12", "K8-REL-13",
])
self.assertTrue(all(row["status"] == "blocked" for row in report["work_items"]))
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,36 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generate_remediation_evidence_roundup import OUTPUT, generate
ROOT = Path(__file__).resolve().parents[1]
class RemediationEvidenceRoundupTests(unittest.TestCase):
def test_contract_preserves_six_blockers_and_stop(self):
report = json.loads((ROOT / OUTPUT).read_text(encoding="utf-8"))
self.assertEqual(report["schema"], "remediation-evidence-roundup.v1")
self.assertEqual(report["task_id"], "K8-REL-14")
self.assertEqual(report["status"], "complete")
self.assertEqual(report["roundup_status"], "blocked_pending_owner_actions")
self.assertEqual(report["release_action"], "STOP_RELEASE")
self.assertFalse(report["candidate_eligible"])
self.assertTrue(all(value["status"] == "verified" for value in report["checks"].values()))
self.assertEqual(len(report["remediation_reports"]), 6)
self.assertEqual(len(report["remaining_owner_actions"]), 6)
def test_generation_is_deterministic(self):
before = (ROOT / OUTPUT).read_bytes()
generated = generate(ROOT, ROOT / OUTPUT)
after = (ROOT / OUTPUT).read_bytes()
self.assertEqual(before, after)
self.assertEqual(generated["report_sha256"], json.loads(after.decode())["report_sha256"])
if __name__ == "__main__":
unittest.main()

238
web/workspace/migration.mjs Normal file
View File

@@ -0,0 +1,238 @@
import { ReplayTimeline, REPLAY_SCHEMA, canonicalJson, fnv1a64 } from "../worker/replay.mjs";
import { validateCheckpoint } from "./recovery.mjs";
// The package envelope is deliberately separate from the locked runtime schemas.
// Format 2 adds integrity metadata; event, snapshot, checkpoint and journal
// payloads remain byte-for-byte compatible with their existing v1 contracts.
export const PACKAGE_SCHEMA = "workspace-package.v1";
export const ROLLBACK_SCHEMA = "workspace-rollback.v1";
export const OFFLINE_SCHEMA = "offline-cache-package.v1";
export const PACKAGE_VERSIONS = Object.freeze([1, 2]);
function clone(value) {
return value === undefined ? undefined : structuredClone(value);
}
function deterministicHash(value) {
// Keep the envelope usable in a browser Worker without a host crypto module.
return fnv1a64(typeof value === "string" ? value : canonicalJson(value));
}
function requireString(value, name) {
if (typeof value !== "string" || value.length === 0) throw new Error(`${name}_invalid`);
}
function requireVersion(value) {
if (!PACKAGE_VERSIONS.includes(value)) throw new Error("package_version_unsupported");
}
function requireSequence(value, name) {
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${name}_invalid`);
}
function packageUnsigned(value) {
const { package_hash: ignored, ...unsigned } = value;
return unsigned;
}
function contentUnsigned(value) {
return {
context_id: value.context_id,
committed_seq: value.committed_seq,
checkpoint: value.checkpoint ?? null,
journal: value.journal ?? [],
timeline: value.timeline,
};
}
function withHashes(value) {
const result = clone(value);
result.content_hash = deterministicHash(contentUnsigned(result));
result.package_hash = deterministicHash(packageUnsigned(result));
return result;
}
function validateJournal(journal) {
if (!Array.isArray(journal)) throw new Error("package_journal_invalid");
let previous = 0;
for (const record of journal) {
if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("package_journal_invalid");
if (record.commit_seq !== undefined) {
requireSequence(record.commit_seq, "journal_commit_seq");
if (record.commit_seq < previous) throw new Error("package_journal_order_invalid");
previous = record.commit_seq;
}
}
}
function normalizedJournal(journal) {
if (!Array.isArray(journal) || journal.some((record) => !record || typeof record !== "object" || Array.isArray(record))) throw new Error("package_journal_invalid");
const normalized = clone(journal).sort((left, right) => (left.commit_seq ?? 0) - (right.commit_seq ?? 0) || String(left.kind ?? "").localeCompare(String(right.kind ?? "")) || String(left.operation_id ?? "").localeCompare(String(right.operation_id ?? "")));
validateJournal(normalized);
return normalized;
}
function validateTimeline(timeline) {
if (!timeline || timeline.schema !== REPLAY_SCHEMA) throw new Error("package_timeline_schema");
return new ReplayTimeline(timeline);
}
function validatePackageShape(value, { contextId, expectedVersion } = {}) {
if (!value || value.schema !== PACKAGE_SCHEMA) throw new Error("package_schema_unknown");
requireVersion(value.format_version);
if (expectedVersion !== undefined && value.format_version !== expectedVersion) throw new Error("package_version_mismatch");
requireString(value.context_id, "package_context");
if (contextId !== undefined && value.context_id !== contextId) throw new Error("package_context_mismatch");
requireSequence(value.committed_seq, "package_committed_seq");
validateTimeline(value.timeline);
validateJournal(value.journal ?? []);
const committedJournalSequences = (value.journal ?? []).filter((record) => record.kind === "commit").map((record) => record.commit_seq);
if (committedJournalSequences.length > 0 && !committedJournalSequences.includes(value.committed_seq)) throw new Error("package_committed_seq_unverified");
if (value.checkpoint !== null && value.checkpoint !== undefined) {
validateCheckpoint(value.checkpoint, { contextId: value.context_id });
if (value.checkpoint.event_cursor > value.timeline.events.cursor) throw new Error("package_checkpoint_ahead");
}
if (typeof value.content_hash !== "string" || value.content_hash !== deterministicHash(contentUnsigned(value))) throw new Error("package_content_hash_mismatch");
if (typeof value.package_hash !== "string" || value.package_hash !== deterministicHash(packageUnsigned(value))) throw new Error("package_hash_mismatch");
if (value.format_version === 2) {
if (!value.integrity || value.integrity.timeline_hash !== deterministicHash(value.timeline) || value.integrity.journal_hash !== deterministicHash(value.journal ?? []) || value.integrity.checkpoint_hash !== (value.checkpoint ? deterministicHash(value.checkpoint) : null)) throw new Error("package_integrity_metadata_mismatch");
if (!value.migration || value.migration.from_version !== 1 || value.migration.to_version !== 2 || value.migration.lossless !== true || typeof value.migration.source_package_hash !== "string") throw new Error("package_migration_metadata_invalid");
}
return clone(value);
}
export function createWorkspacePackage({ contextId, timeline, checkpoint = null, journal = [], committedSeq = undefined } = {}) {
requireString(contextId, "package_context");
const normalizedTimeline = timeline instanceof ReplayTimeline ? timeline.export() : clone(timeline);
const imported = validateTimeline(normalizedTimeline);
if (checkpoint !== null) validateCheckpoint(checkpoint, { contextId });
const normalized = normalizedJournal(journal);
const committed_seq = committedSeq === undefined ? imported.events.cursor : committedSeq;
requireSequence(committed_seq, "package_committed_seq");
if (committed_seq > imported.events.cursor) throw new Error("package_committed_seq_ahead");
return withHashes({
schema: PACKAGE_SCHEMA,
format_version: 1,
context_id: contextId,
committed_seq,
checkpoint: clone(checkpoint),
timeline: normalizedTimeline,
journal: normalized,
});
}
export function verifyWorkspacePackage(value, options = {}) {
return validatePackageShape(value, options);
}
export function migrateWorkspacePackage(value, targetVersion) {
const source = validatePackageShape(value);
requireVersion(targetVersion);
if (source.format_version === targetVersion) return clone(source);
if (source.format_version === 1 && targetVersion === 2) {
return withHashes({
...source,
format_version: 2,
migration: {
from_version: 1,
to_version: 2,
source_package_hash: source.package_hash,
lossless: true,
},
integrity: {
timeline_hash: deterministicHash(source.timeline),
journal_hash: deterministicHash(source.journal),
checkpoint_hash: source.checkpoint ? deterministicHash(source.checkpoint) : null,
},
});
}
if (source.format_version === 2 && targetVersion === 1) {
if (!source.migration || source.migration.from_version !== 1 || source.migration.to_version !== 2 || source.migration.lossless !== true) throw new Error("package_downgrade_not_proven");
const downgraded = withHashes({
schema: PACKAGE_SCHEMA,
format_version: 1,
context_id: source.context_id,
committed_seq: source.committed_seq,
checkpoint: clone(source.checkpoint ?? null),
timeline: clone(source.timeline),
journal: clone(source.journal ?? []),
});
if (source.migration.source_package_hash !== downgraded.package_hash) throw new Error("package_downgrade_source_mismatch");
return downgraded;
}
throw new Error("package_migration_unsupported");
}
export function createRollbackPackage({ current, committed } = {}) {
const currentPackage = validatePackageShape(current);
const committedPackage = validatePackageShape(committed, { contextId: currentPackage.context_id });
if (committedPackage.committed_seq > currentPackage.committed_seq) throw new Error("rollback_target_not_committed");
const rollback = {
schema: ROLLBACK_SCHEMA,
format_version: 1,
context_id: currentPackage.context_id,
current_package_hash: currentPackage.package_hash,
target_package_hash: committedPackage.package_hash,
nearest_committed_seq: committedPackage.committed_seq,
target: committedPackage,
};
return { ...rollback, rollback_hash: deterministicHash(rollback) };
}
export function applyRollbackPackage(rollback, { currentPackageHash, failureInjection = null } = {}) {
if (!rollback || rollback.schema !== ROLLBACK_SCHEMA || rollback.format_version !== 1) throw new Error("rollback_schema_unknown");
const { rollback_hash: ignored, ...unsigned } = rollback;
if (typeof rollback.rollback_hash !== "string" || rollback.rollback_hash !== deterministicHash(unsigned)) throw new Error("rollback_hash_mismatch");
if (typeof currentPackageHash !== "string" || currentPackageHash !== rollback.current_package_hash) throw new Error("rollback_current_mismatch");
if (failureInjection === "before_commit") throw new Error("rollback_injected_failure");
const target = validatePackageShape(rollback.target, { contextId: rollback.context_id });
if (target.package_hash !== rollback.target_package_hash) throw new Error("rollback_target_hash_mismatch");
return clone(target);
}
function normalizeResources(resources) {
if (!Array.isArray(resources) || resources.length === 0) throw new Error("offline_resources_empty");
const seen = new Set();
return resources.map((resource) => {
requireString(resource?.path, "offline_resource_path");
if (seen.has(resource.path)) throw new Error("offline_resource_duplicate");
seen.add(resource.path);
if (typeof resource.data !== "string") throw new Error("offline_resource_data_invalid");
return { path: resource.path, bytes: new TextEncoder().encode(resource.data).byteLength, data_hash: deterministicHash(resource.data) };
}).sort((left, right) => left.path.localeCompare(right.path));
}
export function createOfflinePackage({ workspacePackage, resources } = {}) {
const workspace = validatePackageShape(workspacePackage);
const normalized = normalizeResources(resources);
const bundle = {
schema: OFFLINE_SCHEMA,
format_version: 1,
context_id: workspace.context_id,
workspace_package_hash: workspace.package_hash,
resources: normalized,
required_paths: normalized.map((resource) => resource.path),
};
return { ...bundle, bundle_hash: deterministicHash(bundle) };
}
export function verifyOfflinePackage(bundle, { contextId, workspacePackage = null, available = [], online = false } = {}) {
if (!bundle || bundle.schema !== OFFLINE_SCHEMA || bundle.format_version !== 1) throw new Error("offline_schema_unknown");
requireString(bundle.context_id, "offline_context");
if (contextId !== undefined && bundle.context_id !== contextId) throw new Error("offline_context_mismatch");
if (workspacePackage !== null && verifyWorkspacePackage(workspacePackage, { contextId: bundle.context_id }).package_hash !== bundle.workspace_package_hash) throw new Error("offline_workspace_hash_mismatch");
if (!Array.isArray(bundle.resources) || !Array.isArray(bundle.required_paths) || canonicalJson(bundle.required_paths) !== canonicalJson(bundle.resources.map((resource) => resource.path))) throw new Error("offline_manifest_invalid");
if (typeof bundle.bundle_hash !== "string" || bundle.bundle_hash !== deterministicHash({ schema: bundle.schema, format_version: bundle.format_version, context_id: bundle.context_id, workspace_package_hash: bundle.workspace_package_hash, resources: bundle.resources, required_paths: bundle.required_paths })) throw new Error("offline_bundle_hash_mismatch");
if (!Array.isArray(available)) throw new Error("offline_available_invalid");
const availableMap = new Map(available.map((entry) => {
if (typeof entry === "string") return [entry, null];
if (entry && typeof entry.path === "string") return [entry.path, entry.data_hash ?? (typeof entry.data === "string" ? deterministicHash(entry.data) : null)];
throw new Error("offline_available_invalid");
}));
const missing = bundle.resources.filter((resource) => !availableMap.has(resource.path)).map((resource) => resource.path);
const corrupted = bundle.resources.filter((resource) => availableMap.has(resource.path) && availableMap.get(resource.path) !== null && availableMap.get(resource.path) !== resource.data_hash).map((resource) => resource.path);
if ((missing.length > 0 || corrupted.length > 0) && !online) throw new Error(`offline_resource_invalid:${[...missing, ...corrupted].join(",")}`);
return { status: missing.length === 0 && corrupted.length === 0 ? "ready" : "network_required", missing, corrupted, offline: !online };
}
export { canonicalJson, deterministicHash, fnv1a64 };

View File

@@ -0,0 +1,153 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { MemoryStorage, WorkspaceJournal } from "./journal.mjs";
import { createCheckpoint } from "./recovery.mjs";
import { ReplayTimeline, canonicalJson, fnv1a64 } from "../worker/replay.mjs";
import {
applyRollbackPackage,
createOfflinePackage,
createRollbackPackage,
createWorkspacePackage,
migrateWorkspacePackage,
verifyOfflinePackage,
verifyWorkspacePackage,
} from "./migration.mjs";
const contextId = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1";
function timelineAt(events) {
const timeline = new ReplayTimeline();
timeline.appendEvent(1, 0, "SESSION_READY", {});
timeline.checkpoint(1, 0, { tick: 0, position: 0 });
if (events) {
timeline.appendEvent(2, 4, "SIM_STEP", { cycles: 4 });
timeline.checkpoint(2, 4, { tick: 4, position: 4 }, timeline.snapshots.latest().snapshot_id);
}
return timeline;
}
const storage = new MemoryStorage();
const journal = new WorkspaceJournal(storage, { projectId: "demo" });
journal.prepare("op-1", [{ path: "config/main.ini", data: "[EMC]\n" }]);
journal.commit("op-1");
const journalRecords = journal.records();
journal.prepare("op-2", [{ path: "programs/main.ngc", data: "G0 X1\n" }]);
journal.commit("op-2");
const currentJournalRecords = journal.records();
const committedTimeline = timelineAt(false);
const committedCheckpoint = createCheckpoint({
contextId,
timeline: committedTimeline,
state: { tick: 0, position: 0 },
workspaceHash: fnv1a64(journalRecords),
});
const currentTimeline = timelineAt(true);
const currentCheckpoint = createCheckpoint({
contextId,
timeline: currentTimeline,
state: { tick: 4, position: 4 },
workspaceHash: fnv1a64(currentJournalRecords),
});
const committed = createWorkspacePackage({ contextId, timeline: committedTimeline, checkpoint: committedCheckpoint, journal: journalRecords, committedSeq: 1 });
const current = createWorkspacePackage({ contextId, timeline: currentTimeline, checkpoint: currentCheckpoint, journal: currentJournalRecords, committedSeq: 2 });
assert.equal(verifyWorkspacePackage(committed, { contextId }).package_hash, committed.package_hash);
assert.equal(verifyWorkspacePackage(current, { contextId }).package_hash, current.package_hash);
assert.deepEqual(createWorkspacePackage({ contextId, timeline: currentTimeline, checkpoint: currentCheckpoint, journal: currentJournalRecords, committedSeq: 2 }), current);
const migrated = migrateWorkspacePackage(current, 2);
assert.equal(migrated.format_version, 2);
assert.equal(migrated.migration.lossless, true);
assert.equal(verifyWorkspacePackage(migrated, { contextId, expectedVersion: 2 }).format_version, 2);
const roundTrip = migrateWorkspacePackage(migrated, 1);
assert.deepEqual(roundTrip, current);
assert.throws(() => migrateWorkspacePackage(current, 3), /package_version_unsupported/);
assert.throws(() => verifyWorkspacePackage(current, { contextId: "other-context" }), /package_context_mismatch/);
const tampered = structuredClone(migrated);
tampered.timeline.events.events[1].payload.cycles = 99;
assert.throws(() => verifyWorkspacePackage(tampered), /(?:package_(?:content_)?hash_mismatch|event_payload_hash_mismatch)/);
const rollback = createRollbackPackage({ current, committed });
assert.equal(rollback.nearest_committed_seq, 1);
assert.throws(() => applyRollbackPackage(rollback, { currentPackageHash: current.package_hash, failureInjection: "before_commit" }), /rollback_injected_failure/);
const rolledBack = applyRollbackPackage(rollback, { currentPackageHash: current.package_hash });
assert.deepEqual(rolledBack, committed);
assert.equal(current.package_hash, current.package_hash);
const badRollback = structuredClone(rollback);
badRollback.target_package_hash = "fnv1a64:0000000000000000";
assert.throws(() => applyRollbackPackage(badRollback, { currentPackageHash: current.package_hash }), /rollback_hash_mismatch/);
const offline = createOfflinePackage({
workspacePackage: migrated,
resources: [
{ path: "ui/index.html", data: "<main>LinuxCNC</main>" },
{ path: "wasm/sim.wasm", data: "wasm-bytes" },
],
});
assert.deepEqual(verifyOfflinePackage(offline, { contextId, workspacePackage: migrated, available: [{ path: "ui/index.html", data: "<main>LinuxCNC</main>" }, { path: "wasm/sim.wasm", data: "wasm-bytes" }] }), { status: "ready", missing: [], corrupted: [], offline: true });
assert.throws(() => verifyOfflinePackage(offline, { contextId, available: ["ui/index.html"], online: false }), /offline_resource_invalid/);
assert.deepEqual(verifyOfflinePackage(offline, { contextId, available: ["ui/index.html"], online: true }), { status: "network_required", missing: ["wasm/sim.wasm"], corrupted: [], offline: false });
const report = {
schema: "workspace-migration-report.v1",
context_id: contextId,
status: "passed",
implementation_status: "verified",
format: {
schema: "workspace-package.v1",
supported_versions: [1, 2],
payload_schemas_unchanged: ["event-log.v1", "snapshot.v1", "simulation-checkpoint.v1", "workspace-journal.v1"],
old_package_immutable: true,
},
migration: {
status: "passed",
source_version: current.format_version,
target_version: migrated.format_version,
source_hash: current.package_hash,
migrated_hash: migrated.package_hash,
round_trip_hash: roundTrip.package_hash,
round_trip_equal: roundTrip.package_hash === current.package_hash,
deterministic: migrated.package_hash === migrateWorkspacePackage(current, 2).package_hash,
},
rollback: {
status: "passed",
schema: rollback.schema,
current_hash: current.package_hash,
target_hash: committed.package_hash,
nearest_committed_seq: rollback.nearest_committed_seq,
rollback_hash: rollback.rollback_hash,
failure_injection: "before_commit",
failure_result: "rollback_injected_failure",
recovery_status: "recovered",
recovery_hash: rolledBack.package_hash,
},
offline: {
status: "passed",
schema: offline.schema,
bundle_hash: offline.bundle_hash,
required_paths: offline.required_paths,
offline_start: "ready",
missing_resource_result: "offline_resource_invalid",
online_degraded_result: "network_required",
package_path: "coverage/offline-cache.package.json",
},
rollback_package_path: "coverage/workspace-rollback.package.json",
recovery_endpoint: {
status: "verified",
endpoint: "nearest_committed_package",
state_hash: committedCheckpoint.state_hash,
no_silent_downgrade: true,
},
oracle: {
status: "blocked",
reason: "official migration and browser offline oracle is absent from the locked source",
blocked_samples: ["native/WASM package migration", "browser Service Worker offline startup", "OPFS rollback after crash"],
},
};
report.report_sha256 = createHash("sha256").update(canonicalJson(report), "utf8").digest("hex");
await writeFile(new URL("../../coverage/workspace-rollback.package.json", import.meta.url), `${JSON.stringify(rollback, null, 2)}\n`);
await writeFile(new URL("../../coverage/offline-cache.package.json", import.meta.url), `${JSON.stringify(offline, null, 2)}\n`);
await writeFile(new URL("../../coverage/workspace-migration.report.json", import.meta.url), `${JSON.stringify(report, null, 2)}\n`);
console.log(`migration=passed source=${current.package_hash} target=${migrated.package_hash} rollback=${rollback.rollback_hash}`);