K8: record blocked owner review and release evidence
This commit is contained in:
188
coverage/browser-matrix-remediation.report.json
Normal file
188
coverage/browser-matrix-remediation.report.json
Normal 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"
|
||||||
|
}
|
||||||
367
coverage/compatibility-matrix.report.json
Normal file
367
coverage/compatibility-matrix.report.json
Normal 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"
|
||||||
|
}
|
||||||
15071
coverage/configuration-resource-remediation.report.json
Normal file
15071
coverage/configuration-resource-remediation.report.json
Normal file
File diff suppressed because it is too large
Load Diff
1024
coverage/implementation-license-remediation.report.json
Normal file
1024
coverage/implementation-license-remediation.report.json
Normal file
File diff suppressed because it is too large
Load Diff
310
coverage/k8-milestone-closeout.report.json
Normal file
310
coverage/k8-milestone-closeout.report.json
Normal 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"
|
||||||
|
}
|
||||||
189
coverage/license-closure.report.json
Normal file
189
coverage/license-closure.report.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
8053
coverage/license-source-index.jsonl
Normal file
8053
coverage/license-source-index.jsonl
Normal file
File diff suppressed because it is too large
Load Diff
100097
coverage/manifest-ownership-remediation.report.json
Normal file
100097
coverage/manifest-ownership-remediation.report.json
Normal file
File diff suppressed because it is too large
Load Diff
170
coverage/official-oracle-remediation.report.json
Normal file
170
coverage/official-oracle-remediation.report.json
Normal 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"
|
||||||
|
}
|
||||||
23
coverage/offline-cache.package.json
Normal file
23
coverage/offline-cache.package.json
Normal 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"
|
||||||
|
}
|
||||||
108
coverage/owner-review-gate-refresh.report.json
Normal file
108
coverage/owner-review-gate-refresh.report.json
Normal 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"
|
||||||
|
}
|
||||||
687
coverage/post-release-audit.report.json
Normal file
687
coverage/post-release-audit.report.json
Normal 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"
|
||||||
|
}
|
||||||
1925
coverage/release-acceptance-remediation.report.json
Normal file
1925
coverage/release-acceptance-remediation.report.json
Normal file
File diff suppressed because it is too large
Load Diff
225
coverage/release-candidate-gate.report.json
Normal file
225
coverage/release-candidate-gate.report.json
Normal 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"
|
||||||
|
}
|
||||||
69
coverage/release-closure.report.json
Normal file
69
coverage/release-closure.report.json
Normal 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"
|
||||||
|
}
|
||||||
37822
coverage/release-evidence-index.json
Normal file
37822
coverage/release-evidence-index.json
Normal file
File diff suppressed because it is too large
Load Diff
455
coverage/release-remediation-intake.report.json
Normal file
455
coverage/release-remediation-intake.report.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
258
coverage/remediation-evidence-roundup.report.json
Normal file
258
coverage/remediation-evidence-roundup.report.json
Normal 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"
|
||||||
|
}
|
||||||
72
coverage/workspace-migration.report.json
Normal file
72
coverage/workspace-migration.report.json
Normal 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"
|
||||||
|
}
|
||||||
138
coverage/workspace-rollback.package.json
Normal file
138
coverage/workspace-rollback.package.json
Normal 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"
|
||||||
|
}
|
||||||
22
docs/linuxcnc-web-wasm-browser-matrix-remediation.md
Normal file
22
docs/linuxcnc-web-wasm-browser-matrix-remediation.md
Normal 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
|
||||||
|
```
|
||||||
23
docs/linuxcnc-web-wasm-configuration-resource-remediation.md
Normal file
23
docs/linuxcnc-web-wasm-configuration-resource-remediation.md
Normal 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
|
||||||
|
```
|
||||||
21
docs/linuxcnc-web-wasm-implementation-license-remediation.md
Normal file
21
docs/linuxcnc-web-wasm-implementation-license-remediation.md
Normal 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
|
||||||
|
```
|
||||||
20
docs/linuxcnc-web-wasm-license-decision.md
Normal file
20
docs/linuxcnc-web-wasm-license-decision.md
Normal 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.
|
||||||
25
docs/linuxcnc-web-wasm-manifest-ownership-remediation.md
Normal file
25
docs/linuxcnc-web-wasm-manifest-ownership-remediation.md
Normal 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
|
||||||
|
```
|
||||||
23
docs/linuxcnc-web-wasm-official-oracle-remediation.md
Normal file
23
docs/linuxcnc-web-wasm-official-oracle-remediation.md
Normal 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
|
||||||
|
```
|
||||||
17
docs/linuxcnc-web-wasm-owner-review-gate-refresh.md
Normal file
17
docs/linuxcnc-web-wasm-owner-review-gate-refresh.md
Normal 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
|
||||||
|
```
|
||||||
22
docs/linuxcnc-web-wasm-release-acceptance-remediation.md
Normal file
22
docs/linuxcnc-web-wasm-release-acceptance-remediation.md
Normal 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.
|
||||||
41
docs/linuxcnc-web-wasm-release-closure.md
Normal file
41
docs/linuxcnc-web-wasm-release-closure.md
Normal 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.
|
||||||
37
docs/linuxcnc-web-wasm-release-remediation-intake.md
Normal file
37
docs/linuxcnc-web-wasm-release-remediation-intake.md
Normal 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.
|
||||||
22
docs/linuxcnc-web-wasm-remediation-evidence-roundup.md
Normal file
22
docs/linuxcnc-web-wasm-remediation-evidence-roundup.md
Normal 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
|
||||||
|
```
|
||||||
@@ -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` |
|
||||||
|
|
||||||
## 追溯规则
|
## 追溯规则
|
||||||
|
|
||||||
|
|||||||
48
nextTask.md
48
nextTask.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
187
scripts/generate_browser_matrix_remediation.py
Normal file
187
scripts/generate_browser_matrix_remediation.py
Normal 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())
|
||||||
230
scripts/generate_configuration_resource_remediation.py
Normal file
230
scripts/generate_configuration_resource_remediation.py
Normal 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())
|
||||||
173
scripts/generate_implementation_license_remediation.py
Normal file
173
scripts/generate_implementation_license_remediation.py
Normal 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())
|
||||||
174
scripts/generate_k8_milestone_closeout.py
Normal file
174
scripts/generate_k8_milestone_closeout.py
Normal 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())
|
||||||
273
scripts/generate_manifest_ownership_remediation.py
Normal file
273
scripts/generate_manifest_ownership_remediation.py
Normal 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())
|
||||||
170
scripts/generate_official_oracle_remediation.py
Normal file
170
scripts/generate_official_oracle_remediation.py
Normal 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())
|
||||||
119
scripts/generate_owner_review_gate_refresh.py
Normal file
119
scripts/generate_owner_review_gate_refresh.py
Normal 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())
|
||||||
422
scripts/generate_post_release_audit.py
Normal file
422
scripts/generate_post_release_audit.py
Normal 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())
|
||||||
202
scripts/generate_release_acceptance_remediation.py
Normal file
202
scripts/generate_release_acceptance_remediation.py
Normal 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())
|
||||||
729
scripts/generate_release_closure.py
Normal file
729
scripts/generate_release_closure.py
Normal 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())
|
||||||
333
scripts/generate_release_remediation_intake.py
Normal file
333
scripts/generate_release_remediation_intake.py
Normal 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())
|
||||||
182
scripts/generate_remediation_evidence_roundup.py
Normal file
182
scripts/generate_remediation_evidence_roundup.py
Normal 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())
|
||||||
37
scripts/test_browser_matrix_remediation.py
Normal file
37
scripts/test_browser_matrix_remediation.py
Normal 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()
|
||||||
42
scripts/test_configuration_resource_remediation.py
Normal file
42
scripts/test_configuration_resource_remediation.py
Normal 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()
|
||||||
36
scripts/test_implementation_license_remediation.py
Normal file
36
scripts/test_implementation_license_remediation.py
Normal 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()
|
||||||
35
scripts/test_k8_milestone_closeout.py
Normal file
35
scripts/test_k8_milestone_closeout.py
Normal 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()
|
||||||
54
scripts/test_license_source_closure.py
Normal file
54
scripts/test_license_source_closure.py
Normal 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()
|
||||||
38
scripts/test_manifest_ownership_remediation.py
Normal file
38
scripts/test_manifest_ownership_remediation.py
Normal 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()
|
||||||
35
scripts/test_migration_closure.py
Normal file
35
scripts/test_migration_closure.py
Normal 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()
|
||||||
39
scripts/test_official_oracle_remediation.py
Normal file
39
scripts/test_official_oracle_remediation.py
Normal 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()
|
||||||
37
scripts/test_owner_review_gate_refresh.py
Normal file
37
scripts/test_owner_review_gate_refresh.py
Normal 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()
|
||||||
52
scripts/test_post_release_audit.py
Normal file
52
scripts/test_post_release_audit.py
Normal 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()
|
||||||
43
scripts/test_release_acceptance_remediation.py
Normal file
43
scripts/test_release_acceptance_remediation.py
Normal 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()
|
||||||
43
scripts/test_release_candidate_gate.py
Normal file
43
scripts/test_release_candidate_gate.py
Normal 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()
|
||||||
62
scripts/test_release_closure.py
Normal file
62
scripts/test_release_closure.py
Normal 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()
|
||||||
39
scripts/test_release_remediation_intake.py
Normal file
39
scripts/test_release_remediation_intake.py
Normal 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()
|
||||||
36
scripts/test_remediation_evidence_roundup.py
Normal file
36
scripts/test_remediation_evidence_roundup.py
Normal 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
238
web/workspace/migration.mjs
Normal 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 };
|
||||||
153
web/workspace/test_migration.mjs
Normal file
153
web/workspace/test_migration.mjs
Normal 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}`);
|
||||||
Reference in New Issue
Block a user