From 94a54f0695799caaa9e430a6f672ad9988bb821b Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:23:49 +0000 Subject: [PATCH 01/70] Add fail-closed registered application evidence runner without modifying verified packages --- qa/application/registered_gate.py | 121 ++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 qa/application/registered_gate.py diff --git a/qa/application/registered_gate.py b/qa/application/registered_gate.py new file mode 100644 index 0000000..f6fdb1d --- /dev/null +++ b/qa/application/registered_gate.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Fail-closed candidate registration and GitHub-stage evidence collector. +Never claims VM, GUI, tray, host or release acceptance. +""" +import argparse, hashlib, json, os, pathlib, subprocess, sys, time + +ROOT = pathlib.Path(__file__).resolve().parents[2] +JOBS = ROOT / "qa/application/jobs" +OUT = ROOT / "qa-stage-evidence" +VALID = {"github", "vm", "host"} +REQUIRED = {"install", "upgrade", "rollback", "gui", "tray", "recovery", "security"} + +def digest(p): + h = hashlib.sha256() + with p.open("rb") as stream: + for block in iter(lambda: stream.read(1024*1024), b""): + h.update(block) + return h.hexdigest() + +def fail(s): + raise ValueError(s) + +def register(manifest): + if not isinstance(manifest, dict) or set(manifest) != {"job_id", "candidate", "sha256", "checks"}: + fail("invalid registration keys") + job = manifest["job_id"] + if not isinstance(job, str) or not job or not all(c.isalnum() or c in "-_" for c in job): + fail("invalid job id") + candidate = JOBS / job / "candidate.zip" + if manifest["candidate"] != f"qa/application/jobs/{job}/candidate.zip": + fail("candidate must use registered immutable path") + if not candidate.is_file() or digest(candidate) != manifest["sha256"]: + fail("missing candidate or SHA256 mismatch") + checks = manifest["checks"] + if not isinstance(checks, list) or not checks: + fail("empty checklist") + ids = set() + for check in checks: + if not isinstance(check, dict) or set(check) != {"id", "stage", "argv"}: + fail("invalid check schema") + cid, stage, argv = check["id"], check["stage"], check["argv"] + if not isinstance(cid, str) or not cid or cid in ids: + fail("duplicate/empty check ID") + ids.add(cid) + if stage not in VALID or not isinstance(argv, list): + fail("invalid stage/argv") + if stage == "github": + if not argv or not all(isinstance(x,str) and x for x in argv): + fail("invalid GitHub executable") + if pathlib.PurePath(argv[0]).is_absolute() or ".." in pathlib.PurePath(argv[0]).parts: + fail("unsafe command path") + elif argv: + fail("VM/host checks must be performed by trusted downstream executor") + if not REQUIRED.issubset(ids): + fail("mandatory lifecycle checklist incomplete") + return job, candidate, checks + +def run_one(job, check): + # Exact command argv, no shell interpolation, no inherited secrets. + env = {"PATH": os.environ.get("PATH", "/usr/bin:/bin"), + "HOME": os.environ.get("RUNNER_TEMP", "/tmp"), + "LANG": "C.UTF-8"} + work = JOBS / job + start = time.monotonic() + try: + cp = subprocess.run(check["argv"], cwd=work, env=env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + timeout=180, check=False) + return {"id":check["id"], "status":"PASS" if cp.returncode==0 else "FAIL", + "exit_code":cp.returncode, "duration_ms":int((time.monotonic()-start)*1000), + "output_sha256":hashlib.sha256(cp.stdout).hexdigest(), + "output":cp.stdout.decode("utf-8","replace")[-20000:]} + except (OSError,subprocess.TimeoutExpired) as exc: + return {"id":check["id"],"status":"BLOCKED","error":type(exc).__name__} + +def run(manifest_path): + m=json.loads(manifest_path.read_text()) + job,candidate,checks=register(m) + if OUT.exists(): + fail("evidence directory already exists; no stale reuse") + results=[run_one(job,c) if c["stage"]=="github" else + {"id":c["id"],"status":"NOT_TESTED","stage":c["stage"]} + for c in checks] + receipt={"schema":1,"job_id":job,"candidate_sha256":digest(candidate), + "manifest_sha256":digest(manifest_path),"github_sha":os.environ.get("GITHUB_SHA"), + "checks":results,"full_acceptance":False,"release_approved":False} + p=OUT/job + p.mkdir(parents=True,exist_ok=False) + (p/"receipt.json").write_text(json.dumps(receipt,sort_keys=True,indent=2)+"\n") + if any(x["status"]!="PASS" for x in results if x.get("stage","github")=="github"): + fail("GitHub-stage check failure") + print("GITHUB_STAGE: PASS; VM/HOST/RELEASE: NOT APPROVED") + +def verify(manifest_path): + m=json.loads(manifest_path.read_text()) + job,candidate,checks=register(m) + p=OUT/job/"receipt.json" + d=json.loads(p.read_text()) + if d["candidate_sha256"]!=digest(candidate) or d["manifest_sha256"]!=digest(manifest_path): + fail("receipt provenance mismatch") + if d["full_acceptance"] is not False or d["release_approved"] is not False: + fail("untrusted approval") + if [x["id"] for x in d["checks"]] != [x["id"] for x in checks]: + fail("incomplete checklist") + for result,check in zip(d["checks"],checks): + if check["stage"]=="github" and result["status"]!="PASS": + fail("GitHub check not passed") + if check["stage"]!="github" and result["status"]!="NOT_TESTED": + fail("VM/host acceptance cannot be certified here") + print("GITHUB_RECEIPT: SCOPE_LIMITED_VERIFIED") + +if __name__=="__main__": + ap=argparse.ArgumentParser() + ap.add_argument("mode",choices=["run","verify"]) + ap.add_argument("manifest",type=pathlib.Path) + args=ap.parse_args() + try: + (run if args.mode=="run" else verify)(args.manifest) + except (ValueError,KeyError,OSError,json.JSONDecodeError) as exc: + print("QA BLOCKED:",exc,file=sys.stderr) + sys.exit(1) From d9933dc40a5b13618edb8c2d8e632cd075fbba31 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:24:01 +0000 Subject: [PATCH 02/70] Test registered QA gate failure conditions --- qa/application/test_registered_gate.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 qa/application/test_registered_gate.py diff --git a/qa/application/test_registered_gate.py b/qa/application/test_registered_gate.py new file mode 100644 index 0000000..feb08cf --- /dev/null +++ b/qa/application/test_registered_gate.py @@ -0,0 +1,26 @@ +import importlib.util, pathlib, tempfile, unittest +p=pathlib.Path(__file__).with_name("registered_gate.py") +s=importlib.util.spec_from_file_location("registered_gate",p) +g=importlib.util.module_from_spec(s) +s.loader.exec_module(g) + +class FailClosedTests(unittest.TestCase): + def test_missing_candidate(self): + with self.assertRaises(ValueError): + g.register({"job_id":"absent","candidate":"qa/application/jobs/absent/candidate.zip", + "sha256":"0"*64,"checks":[]}) + def test_invalid_job(self): + with self.assertRaises(ValueError): + g.register({"job_id":"../escape","candidate":"qa/application/jobs/../escape/candidate.zip", + "sha256":"0"*64,"checks":[]}) + def test_no_manifest(self): + with tempfile.TemporaryDirectory() as d: + with self.assertRaises((ValueError,KeyError)): + g.register({}) + def test_required_lifecycle(self): + self.assertEqual(len(g.REQUIRED),7) + def test_valid_stages(self): + self.assertEqual(g.VALID,{"github","vm","host"}) + +if __name__=="__main__": + unittest.main() From c8753254320b94c852de33cb3e1d5d936d8aed5d Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:24:09 +0000 Subject: [PATCH 03/70] Replace broken missing-extension workflow with fail-closed registered candidate stage --- .github/workflows/devnet-application-qa.yml | 113 ++++---------------- 1 file changed, 22 insertions(+), 91 deletions(-) diff --git a/.github/workflows/devnet-application-qa.yml b/.github/workflows/devnet-application-qa.yml index 1028094..91f9e71 100644 --- a/.github/workflows/devnet-application-qa.yml +++ b/.github/workflows/devnet-application-qa.yml @@ -1,114 +1,45 @@ -name: Devnet Master QA — registered application GitHub first +name: Devnet registered application GitHub stage (fail closed) on: + workflow_dispatch: push: - branches: [qa/master-unified-v0.2.4-github-first] + branches: [qa/registered-app-executor-fix-v0.1.0] paths: - - 'qa/application/devnet-master-qa-registered-application-stages-v0.1.0-QA-BLOCKED.zip' - - 'qa/application/jobs/**' - workflow_dispatch: + - 'qa/application/**' + - '.github/workflows/devnet-application-qa.yml' permissions: contents: read jobs: - execute-application-tests: + application-github: runs-on: ubuntu-24.04 - timeout-minutes: 15 - permissions: - contents: read + timeout-minutes: 30 steps: - uses: actions/checkout@v5 with: persist-credentials: false - - name: Verify and extract exact immutable QA extension - shell: bash - run: | - set -euo pipefail - f=qa/application/devnet-master-qa-registered-application-stages-v0.1.0-QA-BLOCKED.zip - echo "10bb63d065a264a9870c24a9b11dae045e654cef4e4e8b2c6e07a12ec4946411 $f" | sha256sum -c - - test ! -e qa/application/jobs/selftest - test ! -e qa/application/registered_gate.py - unzip -nq "$f" - uses: actions/setup-python@v6 with: python-version: '3.13' - - name: Exercise entire registered application gate + - name: Validate fail-closed gate + run: python3 -m unittest discover -s qa/application -p 'test_*.py' -v + - name: Execute registered candidate GitHub checklist shell: bash run: | set -euo pipefail - export DEVNET_PINNED_MASTER_ZIP="$PWD/qa/master/devnet-master-qa-unified-v0.3.8-QA-BLOCKED.zip" - python3 -m unittest discover -s qa/application -p 'test_*.py' -v - python3 qa/application/ci_all.py --mode run - python3 qa/application/ci_all.py --mode verify-local + shopt -s nullglob + manifests=(qa/application/jobs/*/manifest.json) + if (( ${#manifests[@]} != 1 )); then + echo 'BLOCKED: exactly one actual candidate must be registered' >&2 + exit 1 + fi + python3 qa/application/registered_gate.py run "${manifests[0]}" + python3 qa/application/registered_gate.py verify "${manifests[0]}" - uses: actions/upload-artifact@v4 + if: always() with: name: registered-application-github-evidence path: qa-stage-evidence/ - if-no-files-found: error + if-no-files-found: ignore retention-days: 90 - - name: Report fail-closed stage + - name: Preserve release block if: always() - run: echo 'GitHub application stage only. Real VM, GUI, host, install/rollback and full release remain BLOCKED.' >> "$GITHUB_STEP_SUMMARY" - - attest-application-tests: - needs: execute-application-tests - runs-on: ubuntu-24.04 - permissions: - contents: read - id-token: write - attestations: write - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - name: Verify and extract exact immutable QA extension - shell: bash - run: | - set -euo pipefail - f=qa/application/devnet-master-qa-registered-application-stages-v0.1.0-QA-BLOCKED.zip - echo "10bb63d065a264a9870c24a9b11dae045e654cef4e4e8b2c6e07a12ec4946411 $f" | sha256sum -c - - test ! -e qa/application/jobs/selftest - test ! -e qa/application/registered_gate.py - unzip -nq "$f" - - uses: actions/download-artifact@v4 - with: - name: registered-application-github-evidence - path: qa-stage-evidence - - name: Verify all log hashes before signing - run: python3 qa/application/ci_all.py --mode verify-local - - uses: actions/attest@v4 - with: - subject-path: qa/application/jobs/*/candidate.zip - - uses: actions/attest@v4 - with: - subject-path: qa-stage-evidence/*/receipt.json - - verify-signed-application-tests: - needs: attest-application-tests - runs-on: ubuntu-24.04 - permissions: - contents: read - attestations: read - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - name: Verify and extract exact immutable QA extension - shell: bash - run: | - set -euo pipefail - f=qa/application/devnet-master-qa-registered-application-stages-v0.1.0-QA-BLOCKED.zip - echo "10bb63d065a264a9870c24a9b11dae045e654cef4e4e8b2c6e07a12ec4946411 $f" | sha256sum -c - - test ! -e qa/application/jobs/selftest - test ! -e qa/application/registered_gate.py - unzip -nq "$f" - - uses: actions/download-artifact@v4 - with: - name: registered-application-github-evidence - path: qa-stage-evidence - - name: Independently verify signed exact GitHub-stage evidence - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - python3 qa/application/ci_all.py --mode verify-signed - echo 'SIGNED_REGISTERED_APPLICATION_GITHUB_STAGE: VERIFIED_SCOPE_LIMITED' >> "$GITHUB_STEP_SUMMARY" + run: echo 'GitHub-only stage; VM, host and operator acceptance NOT verified. RELEASE BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From f0c449edea701a5449df0ad4c0e14cef246cfb63 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:25:16 +0000 Subject: [PATCH 04/70] Harden registered candidate receipt verification and require actual GitHub checks --- qa/application/registered_gate.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/qa/application/registered_gate.py b/qa/application/registered_gate.py index f6fdb1d..a6d2254 100644 --- a/qa/application/registered_gate.py +++ b/qa/application/registered_gate.py @@ -39,7 +39,7 @@ def register(manifest): if not isinstance(check, dict) or set(check) != {"id", "stage", "argv"}: fail("invalid check schema") cid, stage, argv = check["id"], check["stage"], check["argv"] - if not isinstance(cid, str) or not cid or cid in ids: + if not isinstance(cid, str) or not cid or not all(z.isalnum() or z in "-_" for z in cid) or cid in ids: fail("duplicate/empty check ID") ids.add(cid) if stage not in VALID or not isinstance(argv, list): @@ -51,6 +51,8 @@ def register(manifest): fail("unsafe command path") elif argv: fail("VM/host checks must be performed by trusted downstream executor") + if not any(x["stage"]=="github" for x in checks): + fail("no GitHub-stage test registered") if not REQUIRED.issubset(ids): fail("mandatory lifecycle checklist incomplete") return job, candidate, checks @@ -96,6 +98,10 @@ def verify(manifest_path): job,candidate,checks=register(m) p=OUT/job/"receipt.json" d=json.loads(p.read_text()) + if set(d)!={"schema","job_id","candidate_sha256","manifest_sha256","github_sha","checks","full_acceptance","release_approved"} or d["schema"]!=1 or d["job_id"]!=job: + fail("invalid receipt schema/job") + if d["github_sha"]!=os.environ.get("GITHUB_SHA") or not isinstance(d["github_sha"],str) or len(d["github_sha"])!=40: + fail("wrong/missing GitHub commit identity") if d["candidate_sha256"]!=digest(candidate) or d["manifest_sha256"]!=digest(manifest_path): fail("receipt provenance mismatch") if d["full_acceptance"] is not False or d["release_approved"] is not False: @@ -103,8 +109,12 @@ def verify(manifest_path): if [x["id"] for x in d["checks"]] != [x["id"] for x in checks]: fail("incomplete checklist") for result,check in zip(d["checks"],checks): - if check["stage"]=="github" and result["status"]!="PASS": - fail("GitHub check not passed") + if check["stage"]=="github": + if result["status"]!="PASS" or result.get("exit_code")!=0 or not isinstance(result.get("output_sha256"),str): + fail("GitHub check not passed") + # Do not confuse a captured log excerpt with complete output evidence. + if "output" not in result or not isinstance(result["output"],str): + fail("missing GitHub output excerpt") if check["stage"]!="github" and result["status"]!="NOT_TESTED": fail("VM/host acceptance cannot be certified here") print("GITHUB_RECEIPT: SCOPE_LIMITED_VERIFIED") From f79f133533e166a17803115a50a4666267af0915 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:25:31 +0000 Subject: [PATCH 05/70] Restore signed candidate/receipt attestations and separate verification stage --- .github/workflows/devnet-application-qa.yml | 61 +++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/.github/workflows/devnet-application-qa.yml b/.github/workflows/devnet-application-qa.yml index 91f9e71..b235209 100644 --- a/.github/workflows/devnet-application-qa.yml +++ b/.github/workflows/devnet-application-qa.yml @@ -43,3 +43,64 @@ jobs: - name: Preserve release block if: always() run: echo 'GitHub-only stage; VM, host and operator acceptance NOT verified. RELEASE BLOCKED.' >> "$GITHUB_STEP_SUMMARY" + + sign-candidate-evidence: + needs: application-github + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: registered-application-github-evidence + path: qa-stage-evidence + - name: Recheck candidate and stage receipt before signing + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + manifests=(qa/application/jobs/*/manifest.json) + test "${#manifests[@]}" -eq 1 + python3 qa/application/registered_gate.py verify "${manifests[0]}" + - uses: actions/attest@v4 + with: + subject-path: qa/application/jobs/*/candidate.zip + - uses: actions/attest@v4 + with: + subject-path: qa-stage-evidence/*/receipt.json + independent-signature-check: + needs: sign-candidate-evidence + runs-on: ubuntu-24.04 + permissions: + contents: read + attestations: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: registered-application-github-evidence + path: qa-stage-evidence + - name: Verify candidate-bound GitHub-issued signatures + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + shopt -s nullglob + manifests=(qa/application/jobs/*/manifest.json) + test "${#manifests[@]}" -eq 1 + python3 qa/application/registered_gate.py verify "${manifests[0]}" + zips=(qa/application/jobs/*/candidate.zip) + receipts=(qa-stage-evidence/*/receipt.json) + test "${#zips[@]}" -eq 1 + test "${#receipts[@]}" -eq 1 + gh attestation verify "${zips[0]}" --repo "${GITHUB_REPOSITORY}" + gh attestation verify "${receipts[0]}" --repo "${GITHUB_REPOSITORY}" + echo 'SIGNED_GITHUB_STAGE_VERIFIED. REAL VM/HOST/RELEASE STILL BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From 2a2aada00788229fe169d6a22199cc78426325d5 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:41:36 +0000 Subject: [PATCH 06/70] =?UTF-8?q?Add=20candidate-bound=20GitHub=E2=86=92VM?= =?UTF-8?q?=E2=86=92host=20trusted=20evidence=20acceptance=20coordinator?= =?UTF-8?q?=20(fail=20closed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qa/application/release_coordinator.py | 235 ++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 qa/application/release_coordinator.py diff --git a/qa/application/release_coordinator.py b/qa/application/release_coordinator.py new file mode 100644 index 0000000..5094f84 --- /dev/null +++ b/qa/application/release_coordinator.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +"""Devnet Master QA: independently verify a registered candidate's stage chain. + +This is a fail-closed release-acceptance *gate*, not a privileged executor. +The existing v0.4.4 fixed-scope controller remains untouched. +No AI result, systemd exit code or 13-check PASS_LIMITED result grants acceptance. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + +SCHEMA = "devnet-trusted-stage-v1" +MANDATORY = frozenset(("install", "upgrade", "rollback", "gui", "tray", + "recovery", "security")) +ID = re.compile(r"^[A-Za-z0-9_-]{1,100}$") +SHA256 = re.compile(r"^[a-f0-9]{64}$") +GIT_SHA = re.compile(r"^[a-f0-9]{40}$") +WORKFLOW = "devnet-application-qa.yml" +STAGES = ("github", "vm", "host") + + +class Blocked(Exception): + pass + + +def sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def load(path: Path): + return json.loads(path.read_text(encoding="utf-8")) + + +def require(condition, reason): + if not condition: + raise Blocked(reason) + + +def manifest_for(job_dir: Path): + manifest_file = job_dir / "manifest.json" + manifest = load(manifest_file) + require(isinstance(manifest, dict), "invalid manifest type") + require(set(manifest) == {"job_id", "candidate", "sha256", "checks"}, + "manifest schema mismatch") + job_id = manifest["job_id"] + require(isinstance(job_id, str) and ID.fullmatch(job_id), "invalid job ID") + require(job_dir.name == job_id, "job directory mismatch") + require(manifest["candidate"] == + "qa/application/jobs/" + job_id + "/candidate.zip", + "candidate path is not exact") + candidate_sha = manifest["sha256"] + require(isinstance(candidate_sha, str) and SHA256.fullmatch(candidate_sha), + "invalid candidate SHA256") + candidate = job_dir / "candidate.zip" + require(candidate.is_file() and not candidate.is_symlink(), + "candidate ZIP missing or symlink") + require(sha256(candidate) == candidate_sha, "candidate ZIP bytes changed") + checks = manifest["checks"] + require(isinstance(checks, list) and checks, "empty checklist") + by_stage = {s: [] for s in STAGES} + all_ids = set() + for check in checks: + require(isinstance(check, dict) and set(check) == + {"id", "stage", "argv"}, "check schema mismatch") + cid, stage, argv = check["id"], check["stage"], check["argv"] + require(isinstance(cid, str) and ID.fullmatch(cid) and cid not in all_ids, + "invalid or duplicate check ID") + require(stage in STAGES and isinstance(argv, list), "invalid check stage") + if stage != "github": + require(argv == [], "VM and host commands must come from trusted executors") + else: + require(len(argv) > 0 and all(isinstance(a, str) and a for a in argv), + "GitHub test has no valid executable") + by_stage[stage].append(cid) + all_ids.add(cid) + require(MANDATORY.issubset(all_ids), "required lifecycle checks missing") + require(by_stage["github"], "no candidate-specific GitHub tests") + require(by_stage["vm"], "no actual VM acceptance checks registered") + return manifest, sha256(manifest_file), by_stage + + +def github_receipt(job_dir, evidence_dir, manifest_hash, by_stage): + path = evidence_dir / "github" / "receipt.json" + r = load(path) + require(isinstance(r, dict), "GitHub receipt invalid") + require(r.get("schema") == 1, "wrong GitHub receipt schema") + require(r.get("job_id") == job_dir.name, "wrong GitHub job") + require(r.get("candidate_sha256") == sha256(job_dir / "candidate.zip"), + "GitHub candidate mismatch") + require(r.get("manifest_sha256") == manifest_hash, "GitHub checklist mismatch") + require(r.get("full_acceptance") is False and + r.get("release_approved") is False, "GitHub receipt claims acceptance") + source = r.get("github_sha") + require(isinstance(source, str) and GIT_SHA.fullmatch(source), + "missing exact GitHub source commit") + results = r.get("checks") + require(isinstance(results, list) and len(results) == + sum(map(len, by_stage.values())), "incomplete GitHub checklist") + # Order and stages are fixed by the manifest, not by AI-produced records. + expected = [(c["id"], c["stage"]) for c in load(job_dir / "manifest.json")["checks"]] + for item, (cid, stage) in zip(results, expected): + require(isinstance(item, dict) and item.get("id") == cid, + "GitHub check identity mismatch") + if stage == "github": + require(item.get("status") == "PASS" and + item.get("exit_code") == 0 and + isinstance(item.get("output_sha256"), str) and + SHA256.fullmatch(item["output_sha256"]), + "GitHub test missing successful execution evidence") + else: + require(item.get("stage") == stage and + item.get("status") == "NOT_TESTED", + "GitHub attempted to certify later stages") + return path, source + + +def verify_github_signature(path: Path, repo: str, source: str): + require(bool(re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repo)), + "invalid pinned GitHub repository") + args = ["gh", "attestation", "verify", str(path), "--repo", repo, + "--signer-workflow", repo + "/.github/workflows/" + WORKFLOW, + "--source-digest", source] + try: + p = subprocess.run(args, stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + timeout=45, check=False) + except (OSError, subprocess.TimeoutExpired) as exc: + raise Blocked("GitHub independent attestation unavailable") from exc + require(p.returncode == 0, "GitHub signature/source verification failed") + + +def verify_trusted_signature(path: Path, signature: Path, key: Path): + # Only protected, root-provisioned signing identities can verify VM/host. + require(key.is_file() and not key.is_symlink() and + key.stat().st_uid == 0 and + (key.stat().st_mode & 0o022) == 0, + "trusted stage public key missing or unprotected") + try: + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + public = serialization.load_pem_public_key(key.read_bytes()) + require(isinstance(public, Ed25519PublicKey), + "trusted stage key is not Ed25519") + public.verify(signature.read_bytes(), path.read_bytes()) + except (ImportError, OSError, ValueError, TypeError) as exc: + raise Blocked("trusted signature verification unavailable") from exc + except Exception as exc: + raise Blocked("trusted stage signature failed") from exc + + +def signed_stage(stage, job_dir, evidence_dir, trust_dir, source, manifest_hash, + check_ids, previous_receipt_sha, verify_signature=verify_trusted_signature): + path = evidence_dir / stage / "receipt.json" + signature = evidence_dir / stage / "receipt.sig" + key = trust_dir / (stage + ".pub") + require(path.is_file() and signature.is_file(), + stage.upper() + " signed execution receipt absent") + verify_signature(path, signature, key) + r = load(path) + require(isinstance(r, dict) and set(r) == + {"schema", "stage", "job_id", "candidate_sha256", "manifest_sha256", + "github_sha", "previous_receipt_sha256", "checks", "release_approved"}, + stage.upper() + " signed receipt schema mismatch") + require(r["schema"] == SCHEMA and r["stage"] == stage and + r["job_id"] == job_dir.name, "stage identity mismatch") + require(r["candidate_sha256"] == sha256(job_dir / "candidate.zip") and + r["manifest_sha256"] == manifest_hash and + r["github_sha"] == source, + stage.upper() + " wrong application/checklist/revision") + require(r["previous_receipt_sha256"] == previous_receipt_sha and + r["release_approved"] is False, "broken evidence chain/unsafe approval") + checks = r["checks"] + require(isinstance(checks, dict) and set(checks) == set(check_ids), + stage.upper() + " incomplete registered checklist") + require(all(v == "PASS" for v in checks.values()), + stage.upper() + " contains non-PASS checks") + return sha256(path) + + +def assess(job_dir: Path, evidence_dir: Path, trust_dir: Path, repo: str, + github_verifier=verify_github_signature, + stage_verifier=verify_trusted_signature): + # Every invocation independently rebuilds identity and evidence; never + # inherits a PASS or persists an AI-issued approval. + m, manifest_hash, by_stage = manifest_for(job_dir) + gh_path, source = github_receipt(job_dir, evidence_dir, manifest_hash, by_stage) + github_verifier(gh_path, repo, source) + previous = sha256(gh_path) + previous = signed_stage("vm", job_dir, evidence_dir, trust_dir, source, + manifest_hash, by_stage["vm"], previous, stage_verifier) + if by_stage["host"]: + previous = signed_stage("host", job_dir, evidence_dir, trust_dir, source, + manifest_hash, by_stage["host"], previous, stage_verifier) + return {"schema": "devnet-acceptance-gate-v1", + "job_id": m["job_id"], "candidate_sha256": m["sha256"], + "manifest_sha256": manifest_hash, "github_sha": source, + "github": "PASS_SIGNED", "vm": "PASS_SIGNED", + "host": "PASS_SIGNED" if by_stage["host"] else "NOT_APPLICABLE", + "decision": "READY_FOR_OPERATOR_ACCEPTANCE", + "release_approved": False} + + +def main(): + p = argparse.ArgumentParser() + p.add_argument("--job-dir", type=Path, required=True) + p.add_argument("--evidence-dir", type=Path, required=True) + p.add_argument("--trust-dir", type=Path, + default=Path("/etc/devnet-qa/trust")) + p.add_argument("--repo", default="devnet82/vr-stack-control") + args = p.parse_args() + try: + result = assess(args.job_dir.resolve(), args.evidence_dir.resolve(), + args.trust_dir.resolve(), args.repo) + except (Blocked, OSError, KeyError, TypeError, ValueError, + json.JSONDecodeError) as exc: + print(json.dumps({"decision": "BLOCKED", + "reason": str(exc), "release_approved": False})) + return 2 + print(json.dumps(result, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 6f51eeedd7e9f37b08f1b3116062a200cb2c3230 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:42:05 +0000 Subject: [PATCH 07/70] Add negative/positive tests for signed exact-candidate GitHub VM host evidence chain --- qa/application/test_release_coordinator.py | 182 +++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 qa/application/test_release_coordinator.py diff --git a/qa/application/test_release_coordinator.py b/qa/application/test_release_coordinator.py new file mode 100644 index 0000000..6fe8a4c --- /dev/null +++ b/qa/application/test_release_coordinator.py @@ -0,0 +1,182 @@ +import hashlib +import importlib.util +import json +from pathlib import Path +import tempfile +import unittest + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + +source = Path(__file__).with_name("release_coordinator.py") +spec = importlib.util.spec_from_file_location("release_coordinator", source) +gate = importlib.util.module_from_spec(spec) +spec.loader.exec_module(gate) + + +class StageChain(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + root = Path(self.tmp.name) + self.job = root / "demo" + self.job.mkdir() + self.evidence = root / "evidence" + self.trust = root / "trust" + self.trust.mkdir() + (self.job / "candidate.zip").write_bytes(b"fixed-registered-app-candidate") + checks = [{"id": "package_integrity", "stage": "github", "argv": ["python3", "-V"]}] + checks += [{"id": x, "stage": "vm", "argv": []} for x in + ("install", "upgrade", "rollback", "gui", "tray", "recovery", "security")] + checks += [{"id": "vr_host", "stage": "host", "argv": []}] + self.manifest = {"job_id": "demo", + "candidate": "qa/application/jobs/demo/candidate.zip", + "sha256": gate.sha256(self.job / "candidate.zip"), + "checks": checks} + self.save_manifest() + self.source = "a" * 40 + self.privates = {s: Ed25519PrivateKey.generate() for s in ("vm", "host")} + for stage, key in self.privates.items(): + (self.trust / (stage + ".pub")).write_bytes( + key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo)) + self.make_github() + self.make_signed_stages() + + def save_manifest(self): + (self.job / "manifest.json").write_text(json.dumps(self.manifest, sort_keys=True)) + + def write_receipt(self, stage, body): + p = self.evidence / stage + p.mkdir(exist_ok=True, parents=True) + raw = json.dumps(body, sort_keys=True).encode() + (p / "receipt.json").write_bytes(raw) + if stage in self.privates: + (p / "receipt.sig").write_bytes(self.privates[stage].sign(raw)) + + def make_github(self): + results = [] + for check in self.manifest["checks"]: + if check["stage"] == "github": + results.append({"id": check["id"], "status": "PASS", + "exit_code": 0, "output_sha256": "e" * 64}) + else: + results.append({"id": check["id"], "stage": check["stage"], + "status": "NOT_TESTED"}) + body = {"schema": 1, "job_id": "demo", + "candidate_sha256": self.manifest["sha256"], + "manifest_sha256": gate.sha256(self.job / "manifest.json"), + "github_sha": self.source, "checks": results, + "full_acceptance": False, "release_approved": False} + self.write_receipt("github", body) + + def make_signed_stages(self): + prev = gate.sha256(self.evidence / "github" / "receipt.json") + for stage in ("vm", "host"): + checks = {c["id"]: "PASS" for c in self.manifest["checks"] + if c["stage"] == stage} + body = {"schema": gate.SCHEMA, "stage": stage, "job_id": "demo", + "candidate_sha256": self.manifest["sha256"], + "manifest_sha256": gate.sha256(self.job / "manifest.json"), + "github_sha": self.source, + "previous_receipt_sha256": prev, "checks": checks, + "release_approved": False} + self.write_receipt(stage, body) + prev = gate.sha256(self.evidence / stage / "receipt.json") + + def signature_verifier(self, receipt, signature, pubkey): + public = serialization.load_pem_public_key(pubkey.read_bytes()) + public.verify(signature.read_bytes(), receipt.read_bytes()) + + def assess(self): + return gate.assess(self.job, self.evidence, self.trust, + "devnet82/vr-stack-control", + github_verifier=lambda path, repo, sha: ( + self.assertEqual(sha, self.source), + self.assertEqual(repo, "devnet82/vr-stack-control")), + stage_verifier=self.signature_verifier) + + def blocked(self): + with self.assertRaises((gate.Blocked, ValueError)): + self.assess() + + def test_exact_signed_chain_reaches_operator_only(self): + r = self.assess() + self.assertEqual(r["decision"], "READY_FOR_OPERATOR_ACCEPTANCE") + self.assertIs(r["release_approved"], False) + self.assertEqual(r["host"], "PASS_SIGNED") + + def test_changed_candidate_cannot_inherit_evidence(self): + (self.job / "candidate.zip").write_bytes(b"changed") + self.blocked() + + def test_changed_checklist_invalidates_every_stage(self): + self.manifest["checks"].append({"id": "new_regression", "stage": "vm", "argv": []}) + self.save_manifest() + self.blocked() + + def test_missing_github_is_blocked(self): + (self.evidence / "github" / "receipt.json").unlink() + with self.assertRaises((OSError, gate.Blocked)): + self.assess() + + def test_missing_vm_is_blocked(self): + (self.evidence / "vm" / "receipt.sig").unlink() + self.blocked() + + def test_tampered_vm_evidence_is_blocked(self): + p = self.evidence / "vm" / "receipt.json" + p.write_bytes(p.read_bytes() + b" ") + with self.assertRaises(Exception): + self.assess() + + def test_wrong_vm_revision_is_blocked_even_with_valid_signature(self): + p = self.evidence / "vm" / "receipt.json" + d = json.loads(p.read_text()) + d["github_sha"] = "b" * 40 + self.write_receipt("vm", d) + self.blocked() + + def test_vm_fail_does_not_advance(self): + p = self.evidence / "vm" / "receipt.json" + d = json.loads(p.read_text()) + d["checks"]["gui"] = "FAIL" + self.write_receipt("vm", d) + self.blocked() + + def test_host_cannot_inherit_different_vm_receipt(self): + p = self.evidence / "host" / "receipt.json" + d = json.loads(p.read_text()) + d["previous_receipt_sha256"] = "0" * 64 + self.write_receipt("host", d) + self.blocked() + + def test_no_host_receipt_does_not_grant_acceptance(self): + (self.evidence / "host" / "receipt.json").unlink() + self.blocked() + + def test_wrong_github_execution_exit_is_blocked(self): + p = self.evidence / "github" / "receipt.json" + d = json.loads(p.read_text()) + d["checks"][0]["exit_code"] = 1 + self.write_receipt("github", d) + self.blocked() + + def test_13_smokes_cannot_replace_full_checklist(self): + self.manifest["checks"] = self.manifest["checks"][:1] + self.save_manifest() + self.blocked() + + def test_unsigned_host_pass_cannot_advance(self): + (self.evidence / "host" / "receipt.sig").write_bytes(b"fake-signature") + with self.assertRaises(Exception): + self.assess() + + def test_missing_installed_candidate(self): + (self.job / "candidate.zip").unlink() + self.blocked() + + +if __name__ == "__main__": + unittest.main() From f98ac1dffb1dc1f978fb3894b8b31db45e49f472 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:42:22 +0000 Subject: [PATCH 08/70] Run complete new acceptance coordinator regression suite on GitHub first; do not approve releases --- .../devnet-coordinator-regressions.yml | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/devnet-coordinator-regressions.yml diff --git a/.github/workflows/devnet-coordinator-regressions.yml b/.github/workflows/devnet-coordinator-regressions.yml new file mode 100644 index 0000000..4fffb33 --- /dev/null +++ b/.github/workflows/devnet-coordinator-regressions.yml @@ -0,0 +1,31 @@ +name: Devnet acceptance coordinator regression tests (not release approval) +on: + push: + branches: + - qa/registered-app-executor-fix-v0.1.0 + paths: + - 'qa/application/**' + - '.github/workflows/devnet-coordinator-regressions.yml' + workflow_dispatch: +permissions: + contents: read +jobs: + coordinator-regressions: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + - name: Install isolated signature-test dependency + run: python -m pip install --disable-pip-version-check 'cryptography==50.0.2' + - name: Compile all QA coordinator Python + run: python -m compileall -q qa/application + - name: Exercise ALL coordinator positive and negative tests + run: python -m unittest discover -s qa/application -p 'test_*.py' -v + - name: Preserve correct scope + if: always() + run: echo 'Coordinator regressions only. No application has passed GitHub→VM→host. Release remains BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From 3541546ff895ea6a3cd0ba1762149a04141311bf Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:42:49 +0000 Subject: [PATCH 09/70] Bind every signed VM/host PASS to an independently hashed execution log --- qa/application/release_coordinator.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/qa/application/release_coordinator.py b/qa/application/release_coordinator.py index 5094f84..2a7cdce 100644 --- a/qa/application/release_coordinator.py +++ b/qa/application/release_coordinator.py @@ -128,7 +128,10 @@ def github_receipt(job_dir, evidence_dir, manifest_hash, by_stage): def verify_github_signature(path: Path, repo: str, source: str): require(bool(re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repo)), "invalid pinned GitHub repository") - args = ["gh", "attestation", "verify", str(path), "--repo", repo, + gh = Path("/usr/bin/gh") + require(gh.is_file() and not gh.is_symlink() and gh.stat().st_uid == 0 and + (gh.stat().st_mode & 0o022) == 0, "trusted GitHub verifier binary unavailable") + args = [str(gh), "attestation", "verify", str(path), "--repo", repo, "--signer-workflow", repo + "/.github/workflows/" + WORKFLOW, "--source-digest", source] try: @@ -183,8 +186,20 @@ def signed_stage(stage, job_dir, evidence_dir, trust_dir, source, manifest_hash, checks = r["checks"] require(isinstance(checks, dict) and set(checks) == set(check_ids), stage.upper() + " incomplete registered checklist") - require(all(v == "PASS" for v in checks.values()), - stage.upper() + " contains non-PASS checks") + for cid in check_ids: + item = checks[cid] + require(isinstance(item, dict) and set(item) == + {"status", "log", "log_sha256"}, + stage.upper() + " missing per-check execution evidence") + require(item["status"] == "PASS" and + item["log"] == "logs/" + cid + ".log" and + isinstance(item["log_sha256"], str) and + SHA256.fullmatch(item["log_sha256"]), + stage.upper() + " invalid check result/evidence path") + log = path.parent / "logs" / (cid + ".log") + require(log.is_file() and not log.is_symlink() and + sha256(log) == item["log_sha256"], + stage.upper() + " missing or tampered execution log") return sha256(path) From fb393ff450790c026492eba41a7afddef44be812 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:42:55 +0000 Subject: [PATCH 10/70] Test per-check evidence hashes and tamper rejection for signed stages --- qa/application/test_release_coordinator.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/qa/application/test_release_coordinator.py b/qa/application/test_release_coordinator.py index 6fe8a4c..88e3499 100644 --- a/qa/application/test_release_coordinator.py +++ b/qa/application/test_release_coordinator.py @@ -74,8 +74,17 @@ def make_github(self): def make_signed_stages(self): prev = gate.sha256(self.evidence / "github" / "receipt.json") for stage in ("vm", "host"): - checks = {c["id"]: "PASS" for c in self.manifest["checks"] - if c["stage"] == stage} + checks = {} + logdir = self.evidence / stage / "logs" + logdir.mkdir(parents=True, exist_ok=True) + for check in self.manifest["checks"]: + if check["stage"] != stage: + continue + cid = check["id"] + log = logdir / (cid + ".log") + log.write_text("actual executor placeholder evidence " + cid) + checks[cid] = {"status": "PASS", "log": "logs/" + cid + ".log", + "log_sha256": gate.sha256(log)} body = {"schema": gate.SCHEMA, "stage": stage, "job_id": "demo", "candidate_sha256": self.manifest["sha256"], "manifest_sha256": gate.sha256(self.job / "manifest.json"), @@ -141,7 +150,7 @@ def test_wrong_vm_revision_is_blocked_even_with_valid_signature(self): def test_vm_fail_does_not_advance(self): p = self.evidence / "vm" / "receipt.json" d = json.loads(p.read_text()) - d["checks"]["gui"] = "FAIL" + d["checks"]["gui"]["status"] = "FAIL" self.write_receipt("vm", d) self.blocked() @@ -173,6 +182,10 @@ def test_unsigned_host_pass_cannot_advance(self): with self.assertRaises(Exception): self.assess() + def test_tampered_vm_check_log_is_blocked(self): + (self.evidence / "vm" / "logs" / "gui.log").write_text("tampered") + self.blocked() + def test_missing_installed_candidate(self): (self.job / "candidate.zip").unlink() self.blocked() From fb6b2da733ee4b608dfddceaa85c8ed0a1c0ebce Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:43:04 +0000 Subject: [PATCH 11/70] Run acceptance-coordinator security regression tests on draft PR commits --- .github/workflows/devnet-coordinator-regressions.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/devnet-coordinator-regressions.yml b/.github/workflows/devnet-coordinator-regressions.yml index 4fffb33..69b6f06 100644 --- a/.github/workflows/devnet-coordinator-regressions.yml +++ b/.github/workflows/devnet-coordinator-regressions.yml @@ -7,6 +7,12 @@ on: - 'qa/application/**' - '.github/workflows/devnet-coordinator-regressions.yml' workflow_dispatch: + pull_request: + branches: + - qa/master-unified-v0.2.4-github-first + paths: + - 'qa/application/**' + - '.github/workflows/devnet-coordinator-regressions.yml' permissions: contents: read jobs: From 4fa58971d1e86f6b588668cbdc2e5dbb2afed1a3 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:43:38 +0000 Subject: [PATCH 12/70] Require independently signed operator-approved complete checklist before any release-stage advance --- qa/application/release_coordinator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qa/application/release_coordinator.py b/qa/application/release_coordinator.py index 2a7cdce..d9e28f0 100644 --- a/qa/application/release_coordinator.py +++ b/qa/application/release_coordinator.py @@ -208,6 +208,10 @@ def assess(job_dir: Path, evidence_dir: Path, trust_dir: Path, repo: str, stage_verifier=verify_trusted_signature): # Every invocation independently rebuilds identity and evidence; never # inherits a PASS or persists an AI-issued approval. + # The operator-approved manifest is a separate trust root. A model cannot + # shorten the mandatory checklist or declare host work non-applicable. + stage_verifier(job_dir / "manifest.json", job_dir / "manifest.sig", + trust_dir / "operator.pub") m, manifest_hash, by_stage = manifest_for(job_dir) gh_path, source = github_receipt(job_dir, evidence_dir, manifest_hash, by_stage) github_verifier(gh_path, repo, source) From 0398d1df5a50f2de4f171b991e537415941c47c0 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:43:44 +0000 Subject: [PATCH 13/70] Test independent signed operator checklist and block scope tampering --- qa/application/test_release_coordinator.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/qa/application/test_release_coordinator.py b/qa/application/test_release_coordinator.py index 88e3499..040e8fe 100644 --- a/qa/application/test_release_coordinator.py +++ b/qa/application/test_release_coordinator.py @@ -35,12 +35,14 @@ def setUp(self): "checks": checks} self.save_manifest() self.source = "a" * 40 - self.privates = {s: Ed25519PrivateKey.generate() for s in ("vm", "host")} + self.privates = {s: Ed25519PrivateKey.generate() for s in ("operator", "vm", "host")} for stage, key in self.privates.items(): (self.trust / (stage + ".pub")).write_bytes( key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)) + (self.job / "manifest.sig").write_bytes( + self.privates["operator"].sign((self.job / "manifest.json").read_bytes())) self.make_github() self.make_signed_stages() @@ -182,6 +184,12 @@ def test_unsigned_host_pass_cannot_advance(self): with self.assertRaises(Exception): self.assess() + def test_unsigned_scope_change_is_blocked(self): + self.manifest["checks"] = self.manifest["checks"][:-1] + self.save_manifest() + with self.assertRaises(Exception): + self.assess() + def test_tampered_vm_check_log_is_blocked(self): (self.evidence / "vm" / "logs" / "gui.log").write_text("tampered") self.blocked() From 89cb70ba98f626e476a32a33e17203b7a500ae1b Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:43:59 +0000 Subject: [PATCH 14/70] Cover Ed25519 signature failure in scope-tamper regression assertions --- qa/application/test_release_coordinator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qa/application/test_release_coordinator.py b/qa/application/test_release_coordinator.py index 040e8fe..929cd75 100644 --- a/qa/application/test_release_coordinator.py +++ b/qa/application/test_release_coordinator.py @@ -5,6 +5,7 @@ import tempfile import unittest +from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey @@ -109,7 +110,7 @@ def assess(self): stage_verifier=self.signature_verifier) def blocked(self): - with self.assertRaises((gate.Blocked, ValueError)): + with self.assertRaises((gate.Blocked, ValueError, InvalidSignature)): self.assess() def test_exact_signed_chain_reaches_operator_only(self): From 8b0e42d82610403c886ef35474b4050d04d82e6d Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:44:25 +0000 Subject: [PATCH 15/70] Document strict protected stage-signature contract, real missing executors and release block --- qa/application/README.md | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 qa/application/README.md diff --git a/qa/application/README.md b/qa/application/README.md new file mode 100644 index 0000000..bd9782c --- /dev/null +++ b/qa/application/README.md @@ -0,0 +1,81 @@ +# Devnet registered application acceptance — QA BLOCKED + +This is a **candidate-specific acceptance gate** for the existing Devnet Master QA. +It is not another router, a replacement for the fixed-scope controller v0.4.4, +or a licence for AI self-approval. It cannot execute real VM GUI tests yet. + +## Confirmed installed-system fault (30 October 2026) +The existing `~/.local/lib/devnet-qa-automation/controller.py` checks exactly +13 fixed `vr-stack-v0.6.132` tests, returns `PASS_LIMITED` upon completion +and is called by `agent.py once`. Agent returns exit 0 on `PASS_LIMITED`. +This only proves the *bounded smoke-test scope*. It does not register other +applications, run exact-candidate GitHub CI, execute upgrade/rollback/GUI +tests, or independently close full Master QA. + +## New coordinator and scope +- `registered_gate.py`: runs the candidate's GitHub-stage tests; requires an + exact ZIP, full required lifecycle IDs and a hash-bound manifest. +- `release_coordinator.py`: rechecks ALL provenance every invocation, refuses + inherited results, requires independently signed operator checklist, + independently attested GitHub stage, and separately signed VM and host + receipts with per-check SHA256 logs. +- `test_release_coordinator.py`: positive test and negative cases covering + missing signatures, changed candidate/checklist/revision, missing stages, + FAIL statuses, altered logs and broken chain. +- `.github/workflows/devnet-coordinator-regressions.yml`: tests this new + code on GitHub; testing the coordinator does NOT certify a real application. +- `.github/workflows/devnet-application-qa.yml`: candidate GitHub stage; + currently deliberately BLOCKED until a real candidate is registered. + +## Exact release-closure contract +1. Operator signs `qa/application/jobs/ID/manifest.json` using an **offline + operator private key**; commit signature to `manifest.sig`. Public key at + `/etc/devnet-qa/trust/operator.pub` must be root-owned/non-writable. +2. Register the exact application ZIP at `qa/application/jobs/ID/candidate.zip`. + Manifest binds the ZIP SHA256, complete applicable checks and stages. + Do not substitute old router/Master QA ZIPs for an application. +3. Execute real candidate-specific tests in GitHub FIRST. The scope-limited + `qa-stage-evidence/ID/receipt.json` must be independently GitHub-attested + by the designated workflow, then placed at + `EVIDENCE/github/receipt.json`. The coordinator validates the exact source + commit with `/usr/bin/gh attestation verify`. +4. A trusted CachyOS VM executor (still missing) must install the **same** + candidate and run every registered VM check. Its `EVIDENCE/vm/receipt.json` + must be signed as `receipt.sig` with a private key inaccessible to OpenCode, + the regular user process and the AI. Root-owned public key: + `/etc/devnet-qa/trust/vm.pub`. +5. If any host checks are required, a separately authorised host executor + must run them with the gaming guard honoured, and sign + `EVIDENCE/host/receipt.json` with its *different* protected key. Root-owned + public key: `/etc/devnet-qa/trust/host.pub`. Omission of host checks + requires a genuinely applicable, operator-signed checklist scope. +6. VM/host receipt schema: `schema=devnet-trusted-stage-v1`, `stage`, + `job_id`, `candidate_sha256`, `manifest_sha256`, `github_sha`, + `previous_receipt_sha256`, `checks`, `release_approved=false`. + Every `checks[ID]` has `status=PASS`, `log=logs/ID.log` and the + SHA256 digest of the real file in `EVIDENCE/STAGE/logs/ID.log`. + VM receipt chains to the GitHub receipt; host receipt chains to VM. +7. Only a verified result of `READY_FOR_OPERATOR_ACCEPTANCE` permits + *requesting* the user's final acceptance. It **never** grants release + approval itself. Any changed ZIP/checklist/source revision invalidates + all prior receipts; run GitHub→VM→host again. + +Evaluate (read-only; requires actual signed evidence and trusted public keys): + +```sh +python3 qa/application/release_coordinator.py \ + --job-dir qa/application/jobs/ID \ + --evidence-dir /mnt/devnet-ai/qa/evidence/ID +``` + +## Current blockers — do NOT misreport as passed +- No genuine candidate ZIP and complete operator-approved registration exists + on the current QA branch. +- No trusted, candidate-specific CachyOS VM install/GUI/tray/rollback executor + or separate signer has been deployed. +- No protected host acceptance executor or signer has been deployed. +- No full-scope receipt chain has been generated or validated. +- No new coordinator GitHub Actions PASS has been independently confirmed yet. + +**Release remains BLOCKED.** The original v0.4.4 runner, router v0.2.4, +Master QA v0.3.8, original ZIPs, existing VM and gaming guard are preserved. From f89d46200f7807d0d82435e3d22acd4561f28231 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:45:32 +0000 Subject: [PATCH 16/70] Repair application GitHub workflow missing signature-test dependency; retain no-candidate release block --- .github/workflows/devnet-application-qa.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/devnet-application-qa.yml b/.github/workflows/devnet-application-qa.yml index b235209..364a240 100644 --- a/.github/workflows/devnet-application-qa.yml +++ b/.github/workflows/devnet-application-qa.yml @@ -19,6 +19,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: '3.13' + - name: Install pinned cryptographic test dependency + run: python -m pip install --disable-pip-version-check 'cryptography==50.0.2' - name: Validate fail-closed gate run: python3 -m unittest discover -s qa/application -p 'test_*.py' -v - name: Execute registered candidate GitHub checklist From 9b373ed8cfd2d007e2091de5dfb8bcbd46c0452f Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:46:06 +0000 Subject: [PATCH 17/70] Record verified 21-test GitHub CI result and expected unregistered-candidate failure --- qa/application/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/application/README.md b/qa/application/README.md index bd9782c..8c425ef 100644 --- a/qa/application/README.md +++ b/qa/application/README.md @@ -75,7 +75,7 @@ python3 qa/application/release_coordinator.py \ or separate signer has been deployed. - No protected host acceptance executor or signer has been deployed. - No full-scope receipt chain has been generated or validated. -- No new coordinator GitHub Actions PASS has been independently confirmed yet. +- GitHub coordinator regression run 41140097961: 21/21 PASS on commit\n 8b0e42d82610403c886ef35474b4050d04d82e6d.\n- GitHub application-stage run 41140214160: 21/21 regression PASS, then\n **expected BLOCKED** because no real candidate registration exists.\n This is NOT completed application acceptance. **Release remains BLOCKED.** The original v0.4.4 runner, router v0.2.4, Master QA v0.3.8, original ZIPs, existing VM and gaming guard are preserved. From 5a8ea8ab171269bd7196e715523506ca46b49009 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:49:02 +0000 Subject: [PATCH 18/70] Register exact archived v0.6.132 draft asset for GitHub-first limited checks; VM/operator acceptance remains blocked --- .../jobs/vr-stack-v06132/manifest.json | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 qa/application/jobs/vr-stack-v06132/manifest.json diff --git a/qa/application/jobs/vr-stack-v06132/manifest.json b/qa/application/jobs/vr-stack-v06132/manifest.json new file mode 100644 index 0000000..3911af8 --- /dev/null +++ b/qa/application/jobs/vr-stack-v06132/manifest.json @@ -0,0 +1,157 @@ +{ + "job_id": "vr-stack-v06132", + "candidate": "qa/application/jobs/vr-stack-v06132/candidate.zip", + "sha256": "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1", + "checks": [ + { + "id": "github_zip_integrity", + "stage": "github", + "argv": [ + "python3", + "../../candidate_checks.py", + "integrity", + "candidate.zip" + ] + }, + { + "id": "github_archive_safety", + "stage": "github", + "argv": [ + "python3", + "../../candidate_checks.py", + "safety", + "candidate.zip" + ] + }, + { + "id": "github_python_syntax", + "stage": "github", + "argv": [ + "python3", + "../../candidate_checks.py", + "python_syntax", + "candidate.zip" + ] + }, + { + "id": "package_integrity", + "stage": "vm", + "argv": [] + }, + { + "id": "source_integrity", + "stage": "vm", + "argv": [] + }, + { + "id": "script_syntax", + "stage": "vm", + "argv": [] + }, + { + "id": "tray_lock", + "stage": "vm", + "argv": [] + }, + { + "id": "package_python_tests", + "stage": "vm", + "argv": [] + }, + { + "id": "package_shell_tests", + "stage": "vm", + "argv": [] + }, + { + "id": "gui_process_smoke", + "stage": "vm", + "argv": [] + }, + { + "id": "tray_process_smoke", + "stage": "vm", + "argv": [] + }, + { + "id": "gui_tab_navigation", + "stage": "vm", + "argv": [] + }, + { + "id": "gui_clean_close", + "stage": "vm", + "argv": [] + }, + { + "id": "tray_menu_wiring", + "stage": "vm", + "argv": [] + }, + { + "id": "gui_sidebar_activation", + "stage": "vm", + "argv": [] + }, + { + "id": "gui_pointer_navigation", + "stage": "vm", + "argv": [] + }, + { + "id": "install", + "stage": "vm", + "argv": [] + }, + { + "id": "upgrade", + "stage": "vm", + "argv": [] + }, + { + "id": "rollback", + "stage": "vm", + "argv": [] + }, + { + "id": "gui", + "stage": "vm", + "argv": [] + }, + { + "id": "tray", + "stage": "vm", + "argv": [] + }, + { + "id": "recovery", + "stage": "vm", + "argv": [] + }, + { + "id": "security", + "stage": "vm", + "argv": [] + }, + { + "id": "real_service_lifecycle", + "stage": "vm", + "argv": [] + }, + { + "id": "tray_icon_visibility", + "stage": "vm", + "argv": [] + }, + { + "id": "tray_menu_pointer", + "stage": "vm", + "argv": [] + }, + { + "id": "failure_injection", + "stage": "vm", + "argv": [] + } + ] +} From bf9bb095f08d10eed06fe2c9b4024bcdaf58f1a1 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:49:16 +0000 Subject: [PATCH 19/70] Add exact-candidate read-only GitHub archive integrity and static source checks --- qa/application/candidate_checks.py | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 qa/application/candidate_checks.py diff --git a/qa/application/candidate_checks.py b/qa/application/candidate_checks.py new file mode 100644 index 0000000..77dd8ab --- /dev/null +++ b/qa/application/candidate_checks.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Static checks for the exact archived application ZIP; never execute ZIP code. + +GitHub first-stage only. These checks do not certify install, GUI, tray or rollback. +""" +import argparse +import pathlib +import stat +import subprocess +import sys +import tempfile +import zipfile + +MAX_ARCHIVE_BYTES = 50_000_000 +MAX_UNPACKED_BYTES = 150_000_000 +MAX_MEMBER_COUNT = 2000 + +def inspect(path): + if not path.is_file() or path.is_symlink(): + raise ValueError("missing candidate or unsafe symlink") + if path.stat().st_size > MAX_ARCHIVE_BYTES: + raise ValueError("candidate too large") + with zipfile.ZipFile(path) as archive: + members = archive.infolist() + if not members or len(members) > MAX_MEMBER_COUNT: + raise ValueError("invalid member count") + names = set() + total = 0 + for item in members: + name = item.filename + parts = pathlib.PurePosixPath(name) + if (not name or name.startswith("/") or "\\" in name or + any(part in (".", "..") for part in name.split("/")) or + str(parts) != name.rstrip("/") or name in names or + "\x00" in name): + raise ValueError("unsafe archive path or duplicate") + names.add(name) + kind = stat.S_IFMT(item.external_attr >> 16) + if kind not in (0, stat.S_IFDIR, stat.S_IFREG): + raise ValueError("links and special files prohibited") + total += item.file_size + if total > MAX_UNPACKED_BYTES: + raise ValueError("unpacked size limit exceeded") + if archive.testzip() is not None: + raise ValueError("ZIP member CRC error") + return members + +def run(mode, path): + members = inspect(path) + if mode in ("integrity", "safety"): + print(mode.upper(), "PASS", "members", len(members)) + return + syntax = 0 + with zipfile.ZipFile(path) as z: + for m in members: + if m.is_dir(): + continue + data = z.read(m) + if m.filename.endswith(".py"): + compile(data, m.filename, "exec") + syntax += 1 + elif m.filename.endswith((".sh", ".bash")): + # bash -n parses from standard input without executing application code. + result = subprocess.run(["/usr/bin/bash", "-n"], input=data, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, timeout=10) + if result.returncode: + raise ValueError("shell syntax error: " + m.filename) + syntax += 1 + if not syntax: + raise ValueError("candidate has no supported source files for syntax checks") + print("STATIC_SYNTAX_PASS", syntax) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("mode", choices=["integrity", "safety", "python_syntax"]) + parser.add_argument("candidate", type=pathlib.Path) + args = parser.parse_args() + try: + run(args.mode, args.candidate) + except (ValueError, OSError, RuntimeError, SyntaxError, + zipfile.BadZipFile, subprocess.TimeoutExpired) as ex: + print("QA BLOCKED:", ex, file=sys.stderr) + raise SystemExit(2) From 9f63d5aa7e62e58aae76660ed0da5fd65e680c9a Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:49:27 +0000 Subject: [PATCH 20/70] Run GitHub first against original pinned v0.6.132 draft ZIP and verify source-bound attestations --- .github/workflows/devnet-application-qa.yml | 37 +++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/devnet-application-qa.yml b/.github/workflows/devnet-application-qa.yml index 364a240..5528ec3 100644 --- a/.github/workflows/devnet-application-qa.yml +++ b/.github/workflows/devnet-application-qa.yml @@ -21,6 +21,17 @@ jobs: python-version: '3.13' - name: Install pinned cryptographic test dependency run: python -m pip install --disable-pip-version-check 'cryptography==50.0.2' + - name: Fetch exact archived application ZIP without rebuilding + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + f="qa/application/jobs/vr-stack-v06132/candidate.zip" + test ! -e "$f" + gh api -H 'Accept: application/octet-stream' \ + 'repos/devnet82/vr-stack-control/releases/assets/630444309' > "$f" + echo "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1 $f" | sha256sum -c - - name: Validate fail-closed gate run: python3 -m unittest discover -s qa/application -p 'test_*.py' -v - name: Execute registered candidate GitHub checklist @@ -61,6 +72,17 @@ jobs: with: name: registered-application-github-evidence path: qa-stage-evidence + - name: Fetch exact archived application ZIP without rebuilding + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + f="qa/application/jobs/vr-stack-v06132/candidate.zip" + test ! -e "$f" + gh api -H 'Accept: application/octet-stream' \ + 'repos/devnet82/vr-stack-control/releases/assets/630444309' > "$f" + echo "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1 $f" | sha256sum -c - - name: Recheck candidate and stage receipt before signing shell: bash run: | @@ -89,6 +111,17 @@ jobs: with: name: registered-application-github-evidence path: qa-stage-evidence + - name: Fetch exact archived application ZIP without rebuilding + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + f="qa/application/jobs/vr-stack-v06132/candidate.zip" + test ! -e "$f" + gh api -H 'Accept: application/octet-stream' \ + 'repos/devnet82/vr-stack-control/releases/assets/630444309' > "$f" + echo "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1 $f" | sha256sum -c - - name: Verify candidate-bound GitHub-issued signatures shell: bash env: @@ -103,6 +136,6 @@ jobs: receipts=(qa-stage-evidence/*/receipt.json) test "${#zips[@]}" -eq 1 test "${#receipts[@]}" -eq 1 - gh attestation verify "${zips[0]}" --repo "${GITHUB_REPOSITORY}" - gh attestation verify "${receipts[0]}" --repo "${GITHUB_REPOSITORY}" + gh attestation verify "${zips[0]}" --repo "${GITHUB_REPOSITORY}" --signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/devnet-application-qa.yml" --source-digest "$GITHUB_SHA" + gh attestation verify "${receipts[0]}" --repo "${GITHUB_REPOSITORY}" --signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/devnet-application-qa.yml" --source-digest "$GITHUB_SHA" echo 'SIGNED_GITHUB_STAGE_VERIFIED. REAL VM/HOST/RELEASE STILL BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From da4841d4656ff201278220e20d06f8b6b516623f Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:50:54 +0000 Subject: [PATCH 21/70] Add candidate-bound VM/host stage test executor with protected driver and unsigned real command logs --- qa/application/stage_executor.py | 174 +++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 qa/application/stage_executor.py diff --git a/qa/application/stage_executor.py b/qa/application/stage_executor.py new file mode 100644 index 0000000..a79b8bd --- /dev/null +++ b/qa/application/stage_executor.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Registered Devnet QA stage executor (unprivileged, unsigned, fail closed). + +Trust assumptions: + * Complete operator checklist is signed by an offline private key. + * The root-owned, non-writable driver defines real app-specific commands. + * GitHub source/ZIP/receipt are independently attested before VM execution. + * This process cannot sign evidence or approve a release. + * A separate, privileged verifier must decide whether evidence is genuine. +""" +from __future__ import annotations +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys +import time + +import release_coordinator as gate + +MAX_OUTPUT = 2_000_000 +MAX_TIMEOUT = 300 + +def safe_driver(path: Path, ids): + gate.require(path.is_file() and not path.is_symlink() and + path.stat().st_uid == 0 and + not (path.stat().st_mode & 0o022), + "trusted root-controlled driver unavailable") + driver = gate.load(path) + gate.require(isinstance(driver, dict) and set(driver) == + {"schema", "stage", "job_id", "checks"}, + "invalid driver schema") + checks = driver["checks"] + gate.require(driver["schema"] == "devnet-stage-driver-v1" and + isinstance(checks, dict) and set(checks) == set(ids), + "driver does not cover EXACT registered stage checklist") + for cid, test in checks.items(): + gate.require(isinstance(test, dict) and set(test) == + {"argv", "timeout"}, "invalid trusted test schema") + argv = test["argv"] + timeout = test["timeout"] + gate.require(isinstance(argv, list) and bool(argv) and + all(isinstance(x, str) and bool(x) and "\x00" not in x + for x in argv) and + isinstance(timeout, int) and not isinstance(timeout, bool) and + 1 <= timeout <= MAX_TIMEOUT, "invalid trusted command") + executable = Path(argv[0]) + gate.require(executable.is_absolute() and executable.is_file() and + not executable.is_symlink(), "not a fixed absolute executable") + return driver + +def game_guard(): + # The host broker is separately required to pause VM dispatch when gaming. + # For direct host-only tests this guard must be present and callable. + p = Path("/home/dave/.local/lib/devnet-qa-automation/agent.py") + gate.require(p.is_file() and not p.is_symlink(), "host game guard unavailable") + import importlib.util + spec = importlib.util.spec_from_file_location("devnet_gameguard", p) + gate.require(spec is not None and spec.loader is not None, + "host game guard cannot be loaded") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + gate.require(not module.gaming_now(), "gaming pause active") + +def run_stage(job_dir: Path, evidence_dir: Path, driver_path: Path, trust_dir: Path, + repo: str, stage: str, *, + github_verifier=gate.verify_github_signature, + signature_verifier=gate.verify_trusted_signature, + pause=game_guard): + gate.require(stage in ("vm", "host"), "unsupported stage") + signature_verifier(job_dir/"manifest.json", job_dir/"manifest.sig", + trust_dir/"operator.pub") + manifest, manifest_hash, by_stage = gate.manifest_for(job_dir) + gh_receipt, source = gate.github_receipt(job_dir, evidence_dir, manifest_hash, by_stage) + github_verifier(gh_receipt, repo, source) + previous = gate.sha256(gh_receipt) + if stage == "host": + previous = gate.signed_stage("vm", job_dir, evidence_dir, trust_dir, + source, manifest_hash, by_stage["vm"], + previous, signature_verifier) + ids = by_stage[stage] + gate.require(ids, "empty stage; never invent non-applicability") + driver = safe_driver(driver_path, ids) + gate.require(driver["job_id"] == manifest["job_id"] and + driver["stage"] == stage, "driver pinned to a different job/stage") + candidate = job_dir/"candidate.zip" + gate.require(candidate.is_file() and not candidate.is_symlink() and + gate.sha256(candidate) == manifest["sha256"], + "exact candidate missing or changed") + # Never inherit a prior PASS, even if the previous build appears identical. + root = evidence_dir/stage + gate.require(not root.exists(), "existing stage evidence: start a fresh revision/run") + root.mkdir(parents=True, mode=0o700) + (root/"logs").mkdir(mode=0o700) + results = {} + all_pass = True + for cid in ids: + # On host, a live guard is mandatory before EVERY command. + if stage == "host": + pause() + command = driver["checks"][cid] + log_path = root/"logs"/(cid+".log") + start = time.monotonic() + with log_path.open("xb") as log: + os.chmod(log_path, 0o600) + try: + # No shell interpolation, no inherited credentials, no home writes. + cp = subprocess.run(command["argv"], cwd=job_dir, + env={"PATH":"/usr/bin:/bin", "LANG":"C.UTF-8", + "HOME":"/tmp", "DEVNET_QA_CANDIDATE":str(candidate)}, + stdin=subprocess.DEVNULL, stdout=log, + stderr=subprocess.STDOUT, + timeout=command["timeout"], check=False) + ok = cp.returncode == 0 + code = cp.returncode + except (subprocess.TimeoutExpired, OSError) as exc: + ok = False + code = -1 + log.write(("QA BLOCKED: "+type(exc).__name__+"\n").encode()) + log.flush() + os.fsync(log.fileno()) + if log_path.stat().st_size > MAX_OUTPUT: + ok = False + all_pass = all_pass and ok + results[cid] = {"status":"PASS" if ok else "FAIL", + "log":"logs/"+cid+".log", + "log_sha256":gate.sha256(log_path)} + if not ok: + # Stop after failure rather than running destructive follow-on tests. + break + if not all_pass: + (root/"blocked.json").write_text(json.dumps({ + "stage":stage, "job_id":manifest["job_id"], + "result":"BLOCKED", "checks":results, + "release_approved":False},sort_keys=True)+"\n") + raise gate.Blocked("stage failed: no signed PASS receipt created") + receipt = {"schema":gate.SCHEMA, "stage":stage, + "job_id":manifest["job_id"], + "candidate_sha256":manifest["sha256"], + "manifest_sha256":manifest_hash, + "github_sha":source, + "previous_receipt_sha256":previous, + "checks":results,"release_approved":False} + (root/"receipt.json").write_text(json.dumps(receipt,sort_keys=True,indent=2)+"\n") + # Deliberately NO receipt.sig: a distinct trusted executor/signer must + # independently inspect real logs and sign from a protected identity. + return {"stage":stage,"result":"EXECUTED_UNSIGNED", + "checks_passed":len(ids), "release_approved":False} + +def main(): + p = argparse.ArgumentParser() + p.add_argument("--job-dir", type=Path, required=True) + p.add_argument("--evidence-dir", type=Path, required=True) + p.add_argument("--driver", type=Path, required=True) + p.add_argument("--trust-dir", type=Path, default=Path("/etc/devnet-qa/trust")) + p.add_argument("--repo", default="devnet82/vr-stack-control") + p.add_argument("--stage", choices=["vm","host"], required=True) + a = p.parse_args() + try: + d = run_stage(a.job_dir.resolve(), a.evidence_dir.resolve(), + a.driver, a.trust_dir, a.repo, a.stage) + except (gate.Blocked, OSError, ValueError, KeyError, + TypeError, json.JSONDecodeError) as exc: + print(json.dumps({"stage":a.stage, "result":"BLOCKED", + "reason":str(exc), "release_approved":False})) + return 2 + print(json.dumps(d, sort_keys=True)) + return 0 + +if __name__ == "__main__": + sys.exit(main()) From 8867c0a94f653f8379b2f75a5307491a59f36f34 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:51:20 +0000 Subject: [PATCH 22/70] Regression-test candidate-bound VM executor real subprocess receipts, failure stops, gaming pause and no signer access --- qa/application/test_stage_executor.py | 129 ++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 qa/application/test_stage_executor.py diff --git a/qa/application/test_stage_executor.py b/qa/application/test_stage_executor.py new file mode 100644 index 0000000..6262dd5 --- /dev/null +++ b/qa/application/test_stage_executor.py @@ -0,0 +1,129 @@ +import json +from pathlib import Path +import sys +import tempfile +import unittest +from unittest import mock + +import stage_executor as stage +import release_coordinator as gate + +class RestrictedStageExecution(unittest.TestCase): + def setUp(self): + tmp = tempfile.TemporaryDirectory() + self.addCleanup(tmp.cleanup) + root = Path(tmp.name) + self.job = root/"demo" + self.job.mkdir() + self.ev = root/"evidence" + (self.ev/"github").mkdir(parents=True) + self.trust = root/"trust" + self.trust.mkdir() + self.driver_path = root/"protected-driver.json" + self.candidate = self.job/"candidate.zip" + self.candidate.write_bytes(b"fixture-candidate") + ids = ["install", "upgrade", "rollback", "gui", "tray", "recovery", "security"] + self.vm_ids = ids + checks = [{"id":"github_integrity","stage":"github", + "argv":["python3", "check.py"]}] + checks += [{"id":cid,"stage":"vm","argv":[]} for cid in ids] + checks += [{"id":"host_gpu","stage":"host","argv":[]}] + self.manifest = {"job_id":"demo", + "candidate":"qa/application/jobs/demo/candidate.zip", + "sha256":gate.sha256(self.candidate), "checks":checks} + (self.job/"manifest.json").write_text(json.dumps(self.manifest)) + (self.job/"manifest.sig").write_text("fixture-untrusted") + self.sha = "a" * 40 + receipt = {"schema":1,"job_id":"demo", + "candidate_sha256":self.manifest["sha256"], + "manifest_sha256":gate.sha256(self.job/"manifest.json"), + "github_sha":self.sha, "release_approved":False, + "full_acceptance":False,"checks":[]} + for check in checks: + if check["stage"]=="github": + receipt["checks"].append({"id":check["id"], + "status":"PASS", "exit_code":0, + "output_sha256":"0"*64}) + else: + receipt["checks"].append({"id":check["id"], + "stage":check["stage"], + "status":"NOT_TESTED"}) + (self.ev/"github"/"receipt.json").write_text(json.dumps(receipt)) + self.driver = {"schema":"devnet-stage-driver-v1","stage":"vm", + "job_id":"demo", "checks":{ + cid:{"argv":[sys.executable,"-c", + "print('FIXTURE REAL SUBPROCESS EXECUTED')"], + "timeout":10} for cid in ids}} + self.driver_path.write_text(json.dumps(self.driver)) + self.params = {"job_dir":self.job,"evidence_dir":self.ev, + "driver_path":self.driver_path,"trust_dir":self.trust, + "repo":"devnet82/vr-stack-control","stage":"vm", + "github_verifier":lambda *args: None, + "signature_verifier":lambda *args: None} + + def run_vm(self): + with mock.patch.object(stage,"safe_driver",return_value=self.driver): + return stage.run_stage(**self.params) + + def test_real_subprocesses_run_and_receipt_stays_unsigned(self): + result = self.run_vm() + self.assertEqual(result["checks_passed"],7) + self.assertEqual(result["result"],"EXECUTED_UNSIGNED") + self.assertFalse((self.ev/"vm"/"receipt.sig").exists()) + receipt = json.loads((self.ev/"vm"/"receipt.json").read_text()) + self.assertEqual(set(receipt["checks"]),set(self.vm_ids)) + self.assertTrue(all(v["status"]=="PASS" for v in receipt["checks"].values())) + self.assertIn("FIXTURE REAL SUBPROCESS EXECUTED", + (self.ev/"vm"/"logs"/"gui.log").read_text()) + self.assertIs(receipt["release_approved"],False) + + def test_check_failure_never_creates_pass_receipt(self): + self.driver["checks"]["install"]["argv"] = [ + sys.executable, "-c", "raise SystemExit(2)"] + with self.assertRaises(gate.Blocked): + self.run_vm() + self.assertTrue((self.ev/"vm"/"blocked.json").exists()) + self.assertFalse((self.ev/"vm"/"receipt.json").exists()) + self.assertFalse((self.ev/"vm"/"logs"/"upgrade.log").exists()) + + def test_changed_candidate_must_restart_github(self): + self.candidate.write_bytes(b"changed") + with self.assertRaises(gate.Blocked): + self.run_vm() + + def test_existing_evidence_cannot_be_reused(self): + self.run_vm() + with self.assertRaises(gate.Blocked): + self.run_vm() + + def test_no_missing_github_handoff_allowed(self): + (self.ev/"github"/"receipt.json").unlink() + with self.assertRaises(OSError): + self.run_vm() + + def test_driver_must_be_root_controlled(self): + with self.assertRaises(gate.Blocked): + stage.safe_driver(self.driver_path, self.vm_ids) + + def test_different_driver_scope_is_blocked(self): + self.driver["checks"].pop("gui") + with self.assertRaises(gate.Blocked): + self.run_vm() if False else stage.safe_driver(self.driver_path, self.vm_ids) + + def test_host_pauses_before_first_real_command(self): + self.params["stage"]="host" + driver={"schema":"devnet-stage-driver-v1","stage":"host", + "job_id":"demo","checks":{"host_gpu":{ + "argv":[sys.executable,"-c","print('must never run')"], + "timeout":10}}} + def pause(): + raise gate.Blocked("gaming") + with mock.patch.object(stage,"safe_driver",return_value=driver): + with mock.patch.object(gate,"signed_stage",return_value="a"*64): + with self.assertRaises(gate.Blocked): + stage.run_stage(**self.params,pause=pause) + self.assertFalse((self.ev/"host"/"logs"/"host_gpu.log").exists()) + self.assertFalse((self.ev/"host"/"receipt.json").exists()) + +if __name__=="__main__": + unittest.main() From 43bbec25b8eb11e2f3a984f35243876a56d28904 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:52:27 +0000 Subject: [PATCH 23/70] Withdraw incorrect full-application registration: v0.6.132 draft ZIP is a version-sync hotfix --- .../jobs/vr-stack-v06132/manifest.json | 157 ------------------ 1 file changed, 157 deletions(-) delete mode 100644 qa/application/jobs/vr-stack-v06132/manifest.json diff --git a/qa/application/jobs/vr-stack-v06132/manifest.json b/qa/application/jobs/vr-stack-v06132/manifest.json deleted file mode 100644 index 3911af8..0000000 --- a/qa/application/jobs/vr-stack-v06132/manifest.json +++ /dev/null @@ -1,157 +0,0 @@ -{ - "job_id": "vr-stack-v06132", - "candidate": "qa/application/jobs/vr-stack-v06132/candidate.zip", - "sha256": "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1", - "checks": [ - { - "id": "github_zip_integrity", - "stage": "github", - "argv": [ - "python3", - "../../candidate_checks.py", - "integrity", - "candidate.zip" - ] - }, - { - "id": "github_archive_safety", - "stage": "github", - "argv": [ - "python3", - "../../candidate_checks.py", - "safety", - "candidate.zip" - ] - }, - { - "id": "github_python_syntax", - "stage": "github", - "argv": [ - "python3", - "../../candidate_checks.py", - "python_syntax", - "candidate.zip" - ] - }, - { - "id": "package_integrity", - "stage": "vm", - "argv": [] - }, - { - "id": "source_integrity", - "stage": "vm", - "argv": [] - }, - { - "id": "script_syntax", - "stage": "vm", - "argv": [] - }, - { - "id": "tray_lock", - "stage": "vm", - "argv": [] - }, - { - "id": "package_python_tests", - "stage": "vm", - "argv": [] - }, - { - "id": "package_shell_tests", - "stage": "vm", - "argv": [] - }, - { - "id": "gui_process_smoke", - "stage": "vm", - "argv": [] - }, - { - "id": "tray_process_smoke", - "stage": "vm", - "argv": [] - }, - { - "id": "gui_tab_navigation", - "stage": "vm", - "argv": [] - }, - { - "id": "gui_clean_close", - "stage": "vm", - "argv": [] - }, - { - "id": "tray_menu_wiring", - "stage": "vm", - "argv": [] - }, - { - "id": "gui_sidebar_activation", - "stage": "vm", - "argv": [] - }, - { - "id": "gui_pointer_navigation", - "stage": "vm", - "argv": [] - }, - { - "id": "install", - "stage": "vm", - "argv": [] - }, - { - "id": "upgrade", - "stage": "vm", - "argv": [] - }, - { - "id": "rollback", - "stage": "vm", - "argv": [] - }, - { - "id": "gui", - "stage": "vm", - "argv": [] - }, - { - "id": "tray", - "stage": "vm", - "argv": [] - }, - { - "id": "recovery", - "stage": "vm", - "argv": [] - }, - { - "id": "security", - "stage": "vm", - "argv": [] - }, - { - "id": "real_service_lifecycle", - "stage": "vm", - "argv": [] - }, - { - "id": "tray_icon_visibility", - "stage": "vm", - "argv": [] - }, - { - "id": "tray_menu_pointer", - "stage": "vm", - "argv": [] - }, - { - "id": "failure_injection", - "stage": "vm", - "argv": [] - } - ] -} From aa8921e604b08ed152b2b8be4719d5cf1ee31603 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:52:35 +0000 Subject: [PATCH 24/70] Remove inaccessible draft hotfix fetch; only run real registered full-application CI when supplied --- .github/workflows/devnet-application-qa.yml | 37 ++------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/.github/workflows/devnet-application-qa.yml b/.github/workflows/devnet-application-qa.yml index 5528ec3..7085f50 100644 --- a/.github/workflows/devnet-application-qa.yml +++ b/.github/workflows/devnet-application-qa.yml @@ -4,7 +4,9 @@ on: push: branches: [qa/registered-app-executor-fix-v0.1.0] paths: - - 'qa/application/**' + - 'qa/application/jobs/**' + - 'qa/application/registered_gate.py' + - 'qa/application/candidate_checks.py' - '.github/workflows/devnet-application-qa.yml' permissions: contents: read @@ -21,17 +23,6 @@ jobs: python-version: '3.13' - name: Install pinned cryptographic test dependency run: python -m pip install --disable-pip-version-check 'cryptography==50.0.2' - - name: Fetch exact archived application ZIP without rebuilding - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - f="qa/application/jobs/vr-stack-v06132/candidate.zip" - test ! -e "$f" - gh api -H 'Accept: application/octet-stream' \ - 'repos/devnet82/vr-stack-control/releases/assets/630444309' > "$f" - echo "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1 $f" | sha256sum -c - - name: Validate fail-closed gate run: python3 -m unittest discover -s qa/application -p 'test_*.py' -v - name: Execute registered candidate GitHub checklist @@ -72,17 +63,6 @@ jobs: with: name: registered-application-github-evidence path: qa-stage-evidence - - name: Fetch exact archived application ZIP without rebuilding - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - f="qa/application/jobs/vr-stack-v06132/candidate.zip" - test ! -e "$f" - gh api -H 'Accept: application/octet-stream' \ - 'repos/devnet82/vr-stack-control/releases/assets/630444309' > "$f" - echo "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1 $f" | sha256sum -c - - name: Recheck candidate and stage receipt before signing shell: bash run: | @@ -111,17 +91,6 @@ jobs: with: name: registered-application-github-evidence path: qa-stage-evidence - - name: Fetch exact archived application ZIP without rebuilding - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - f="qa/application/jobs/vr-stack-v06132/candidate.zip" - test ! -e "$f" - gh api -H 'Accept: application/octet-stream' \ - 'repos/devnet82/vr-stack-control/releases/assets/630444309' > "$f" - echo "04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1 $f" | sha256sum -c - - name: Verify candidate-bound GitHub-issued signatures shell: bash env: From 48b3e2ad7464d5090898bc8e2fb9f7c66549badf Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:52:49 +0000 Subject: [PATCH 25/70] Separate protected driver ownership check from exact test-scope validation for real negative test coverage --- qa/application/stage_executor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qa/application/stage_executor.py b/qa/application/stage_executor.py index a79b8bd..456a639 100644 --- a/qa/application/stage_executor.py +++ b/qa/application/stage_executor.py @@ -29,7 +29,10 @@ def safe_driver(path: Path, ids): path.stat().st_uid == 0 and not (path.stat().st_mode & 0o022), "trusted root-controlled driver unavailable") - driver = gate.load(path) + return validate_driver(gate.load(path), ids) + +def validate_driver(driver, ids): + """Check exact driver scope independently of filesystem ownership.""" gate.require(isinstance(driver, dict) and set(driver) == {"schema", "stage", "job_id", "checks"}, "invalid driver schema") From 801a93812e09fd69c341da8c9b89a6c82b25c086 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:52:54 +0000 Subject: [PATCH 26/70] Make driver-scope test actually check incomplete coverage rather than only file ownership --- qa/application/test_stage_executor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qa/application/test_stage_executor.py b/qa/application/test_stage_executor.py index 6262dd5..d69a26d 100644 --- a/qa/application/test_stage_executor.py +++ b/qa/application/test_stage_executor.py @@ -108,7 +108,10 @@ def test_driver_must_be_root_controlled(self): def test_different_driver_scope_is_blocked(self): self.driver["checks"].pop("gui") with self.assertRaises(gate.Blocked): - self.run_vm() if False else stage.safe_driver(self.driver_path, self.vm_ids) + stage.validate_driver(self.driver, self.vm_ids) + + def test_exact_driver_scope_validation(self): + self.assertEqual(stage.validate_driver(self.driver,self.vm_ids),self.driver) def test_host_pauses_before_first_real_command(self): self.params["stage"]="host" From a1435f7b7cdee7dbc13e574df68d37e6941ebb4e Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:53:24 +0000 Subject: [PATCH 27/70] Correct archive provenance (v0.6.132 is a hotfix), document 403 and tested unsigned VM engine blockers --- qa/application/README.md | 43 +++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/qa/application/README.md b/qa/application/README.md index 8c425ef..af36ec1 100644 --- a/qa/application/README.md +++ b/qa/application/README.md @@ -2,7 +2,8 @@ This is a **candidate-specific acceptance gate** for the existing Devnet Master QA. It is not another router, a replacement for the fixed-scope controller v0.4.4, -or a licence for AI self-approval. It cannot execute real VM GUI tests yet. +or a licence for AI self-approval. The candidate-aware VM-stage engine has +passed its regression tests but has NOT been deployed or used to run a real app. ## Confirmed installed-system fault (30 October 2026) The existing `~/.local/lib/devnet-qa-automation/controller.py` checks exactly @@ -19,6 +20,15 @@ tests, or independently close full Master QA. inherited results, requires independently signed operator checklist, independently attested GitHub stage, and separately signed VM and host receipts with per-check SHA256 logs. +- `stage_executor.py`: executes only the protected, exact per-app VM/host driver + commands after checking the signed operator manifest, matching candidate bytes + and signed GitHub handoff. Captures actual subprocess logs, stops on failure, + prevents evidence reuse and intentionally cannot sign or approve itself. + VM/host protected drivers, isolated signing services and host broker are not + yet provisioned. +- `test_stage_executor.py`: verified regressions for real fixture subprocesses, + failure aborts, driver scope, fresh evidence and gaming pause; these are + fixture tests, NOT actual CachyOS GUI acceptance. - `test_release_coordinator.py`: positive test and negative cases covering missing signatures, changed candidate/checklist/revision, missing stages, FAIL statuses, altered logs and broken chain. @@ -39,7 +49,8 @@ tests, or independently close full Master QA. by the designated workflow, then placed at `EVIDENCE/github/receipt.json`. The coordinator validates the exact source commit with `/usr/bin/gh attestation verify`. -4. A trusted CachyOS VM executor (still missing) must install the **same** +4. Deploy the new stage executor with a root-controlled, job-specific VM + test driver in the CachyOS guest. The driver must install the **same** candidate and run every registered VM check. Its `EVIDENCE/vm/receipt.json` must be signed as `receipt.sig` with a private key inaccessible to OpenCode, the regular user process and the AI. Root-owned public key: @@ -69,13 +80,31 @@ python3 qa/application/release_coordinator.py \ ``` ## Current blockers — do NOT misreport as passed -- No genuine candidate ZIP and complete operator-approved registration exists - on the current QA branch. -- No trusted, candidate-specific CachyOS VM install/GUI/tray/rollback executor - or separate signer has been deployed. +- No current FULL application ZIP and complete operator-approved registration + exists on this QA branch. We found an unpublished draft release asset + `vr-stack-control-v0.6.132.zip` (SHA256 + `04118623d828de96526e1c185409c586bf9ed1b4b1318451e4301277ff69baf1`, + 4,675 bytes), but the 30 Sep QA engineering report identifies it as a + **version-sync hotfix**, NOT the full application. A provisional full-app + registration was withdrawn; the original draft ZIP was NOT touched. +- GitHub Actions cannot read that draft-release asset using its current + token: `Resource not accessible by integration (HTTP 403)`. + Do not publish the draft or weaken token restrictions just to make CI green. +- The full-app preview v0.6.135 ZIP was not uploaded to GitHub according + to its own archived STATUS.md; no exact full-app candidate is supplied. +- There is no protected, complete, operator-signed per-application checklist. +- `stage_executor.py` exists, but has NOT been installed on the VM. Real + candidate-specific install/GUI/tray/rollback driver and independent signer + have not been deployed or verified. - No protected host acceptance executor or signer has been deployed. - No full-scope receipt chain has been generated or validated. -- GitHub coordinator regression run 41140097961: 21/21 PASS on commit\n 8b0e42d82610403c886ef35474b4050d04d82e6d.\n- GitHub application-stage run 41140214160: 21/21 regression PASS, then\n **expected BLOCKED** because no real candidate registration exists.\n This is NOT completed application acceptance. +- GitHub coordinator regression run 41140833119: **29/29 PASS** on commit + 8867c0a94f653f8379b2f75a5307491a59f36f34 (including real fixture + subprocesses, not real application execution). +- GitHub application-stage run 41140630122: blocked with HTTP 403 when + attempting to read the inaccessible unpublished draft asset. +- GitHub application-stage run 41140960760: intentionally BLOCKED because + no complete application is registered. Neither is a full acceptance run. **Release remains BLOCKED.** The original v0.4.4 runner, router v0.2.4, Master QA v0.3.8, original ZIPs, existing VM and gaming guard are preserved. From 69b7b22467ae8c99a686da040b32a4ace74c8a4b Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:53:43 +0000 Subject: [PATCH 28/70] Fix trusted-driver positive regression to use non-symlink absolute executable --- qa/application/test_stage_executor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qa/application/test_stage_executor.py b/qa/application/test_stage_executor.py index d69a26d..ea27e44 100644 --- a/qa/application/test_stage_executor.py +++ b/qa/application/test_stage_executor.py @@ -111,6 +111,10 @@ def test_different_driver_scope_is_blocked(self): stage.validate_driver(self.driver, self.vm_ids) def test_exact_driver_scope_validation(self): + # GitHub setup-python can expose sys.executable as a symlink; driver + # policy intentionally requires a direct root-managed executable. + for test in self.driver["checks"].values(): + test["argv"] = ["/usr/bin/printf", "TEST_OK"] self.assertEqual(stage.validate_driver(self.driver,self.vm_ids),self.driver) def test_host_pauses_before_first_real_command(self): From aceb24cf89cf22078f5cbeaaaf365a928e92e138 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:54:09 +0000 Subject: [PATCH 29/70] Record final 30-of-30 GitHub stage-engine regression pass; preserve full-app QA block --- qa/application/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/application/README.md b/qa/application/README.md index af36ec1..86dc818 100644 --- a/qa/application/README.md +++ b/qa/application/README.md @@ -98,8 +98,8 @@ python3 qa/application/release_coordinator.py \ have not been deployed or verified. - No protected host acceptance executor or signer has been deployed. - No full-scope receipt chain has been generated or validated. -- GitHub coordinator regression run 41140833119: **29/29 PASS** on commit - 8867c0a94f653f8379b2f75a5307491a59f36f34 (including real fixture +- GitHub coordinator regression run 41141079307: **30/30 PASS** on commit + 69b7b22467ae8c99a686da040b32a4ace74c8a4b (including real fixture subprocesses, not real application execution). - GitHub application-stage run 41140630122: blocked with HTTP 403 when attempting to read the inaccessible unpublished draft asset. From f84c04c02a7ef48db45725480d00f48e49c4d06f Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:58:50 +0000 Subject: [PATCH 30/70] Bind immutable operator-approved QA checklist separately from changing candidate ZIP revision --- qa/application/release_coordinator.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/qa/application/release_coordinator.py b/qa/application/release_coordinator.py index d9e28f0..2634562 100644 --- a/qa/application/release_coordinator.py +++ b/qa/application/release_coordinator.py @@ -90,6 +90,23 @@ def manifest_for(job_dir: Path): return manifest, sha256(manifest_file), by_stage +def verified_policy(job_dir, trust_dir, signature_verifier=verify_trusted_signature): + """Check stable approved scope separately from the changing candidate revision.""" + policy_path = job_dir / "policy.json" + signature_verifier(policy_path, job_dir / "policy.sig", trust_dir / "operator.pub") + policy = load(policy_path) + require(isinstance(policy, dict) and + set(policy) == {"schema", "job_id", "checks"} and + policy.get("schema") == "devnet-qa-checklist-v1" and + policy.get("job_id") == job_dir.name, + "approved QA policy schema/job mismatch") + manifest = load(job_dir / "manifest.json") + require(isinstance(manifest, dict) and + policy["checks"] == manifest.get("checks"), + "candidate checklist changed without operator policy approval") + return sha256(policy_path) + + def github_receipt(job_dir, evidence_dir, manifest_hash, by_stage): path = evidence_dir / "github" / "receipt.json" r = load(path) @@ -208,10 +225,9 @@ def assess(job_dir: Path, evidence_dir: Path, trust_dir: Path, repo: str, stage_verifier=verify_trusted_signature): # Every invocation independently rebuilds identity and evidence; never # inherits a PASS or persists an AI-issued approval. - # The operator-approved manifest is a separate trust root. A model cannot - # shorten the mandatory checklist or declare host work non-applicable. - stage_verifier(job_dir / "manifest.json", job_dir / "manifest.sig", - trust_dir / "operator.pub") + # Signed stable checklist persists across candidate revisions; exact + # candidate SHA/revision and all actual results MUST be retested afresh. + verified_policy(job_dir, trust_dir, stage_verifier) m, manifest_hash, by_stage = manifest_for(job_dir) gh_path, source = github_receipt(job_dir, evidence_dir, manifest_hash, by_stage) github_verifier(gh_path, repo, source) From 4bfa3fb8ce8c904945e26851b72ba31b982d3995 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:58:56 +0000 Subject: [PATCH 31/70] Make stable operator policy verifier explicit and avoid forward-definition dependency --- qa/application/release_coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/application/release_coordinator.py b/qa/application/release_coordinator.py index 2634562..15cd718 100644 --- a/qa/application/release_coordinator.py +++ b/qa/application/release_coordinator.py @@ -90,7 +90,7 @@ def manifest_for(job_dir: Path): return manifest, sha256(manifest_file), by_stage -def verified_policy(job_dir, trust_dir, signature_verifier=verify_trusted_signature): +def verified_policy(job_dir, trust_dir, signature_verifier): """Check stable approved scope separately from the changing candidate revision.""" policy_path = job_dir / "policy.json" signature_verifier(policy_path, job_dir / "policy.sig", trust_dir / "operator.pub") From d44b9ebbebb47373f73091f097ee4cf407a2b953 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:59:02 +0000 Subject: [PATCH 32/70] Require signed stable QA checklist for stage execution, retaining per-revision ZIP verification --- qa/application/stage_executor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qa/application/stage_executor.py b/qa/application/stage_executor.py index 456a639..6d8128f 100644 --- a/qa/application/stage_executor.py +++ b/qa/application/stage_executor.py @@ -74,8 +74,7 @@ def run_stage(job_dir: Path, evidence_dir: Path, driver_path: Path, trust_dir: P signature_verifier=gate.verify_trusted_signature, pause=game_guard): gate.require(stage in ("vm", "host"), "unsupported stage") - signature_verifier(job_dir/"manifest.json", job_dir/"manifest.sig", - trust_dir/"operator.pub") + gate.verified_policy(job_dir, trust_dir, signature_verifier) manifest, manifest_hash, by_stage = gate.manifest_for(job_dir) gh_receipt, source = gate.github_receipt(job_dir, evidence_dir, manifest_hash, by_stage) github_verifier(gh_receipt, repo, source) From 369c9606171e43dffdaf2a65b67285a9db895902 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:59:11 +0000 Subject: [PATCH 33/70] Test signed stable QA policy and disallow reuse of evidence after candidate revision --- qa/application/test_release_coordinator.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/qa/application/test_release_coordinator.py b/qa/application/test_release_coordinator.py index 929cd75..75f6743 100644 --- a/qa/application/test_release_coordinator.py +++ b/qa/application/test_release_coordinator.py @@ -42,14 +42,23 @@ def setUp(self): key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)) - (self.job / "manifest.sig").write_bytes( - self.privates["operator"].sign((self.job / "manifest.json").read_bytes())) + self.sign_policy() self.make_github() self.make_signed_stages() def save_manifest(self): (self.job / "manifest.json").write_text(json.dumps(self.manifest, sort_keys=True)) + def sign_policy(self): + # Operator approves the checklist once; each candidate ZIP revision + # must still obtain fresh GitHub, VM and host receipts. + policy = {"schema": "devnet-qa-checklist-v1", "job_id": "demo", + "checks": self.manifest["checks"]} + path = self.job / "policy.json" + path.write_text(json.dumps(policy, sort_keys=True)) + (self.job / "policy.sig").write_bytes( + self.privates["operator"].sign(path.read_bytes())) + def write_receipt(self, stage, body): p = self.evidence / stage p.mkdir(exist_ok=True, parents=True) @@ -191,6 +200,14 @@ def test_unsigned_scope_change_is_blocked(self): with self.assertRaises(Exception): self.assess() + def test_unsigned_candidate_revision_never_inherits_pass(self): + # The operator-approved checklist stays signed; revision changes. + # Previous receipts for the old artifact must be rejected. + self.assertEqual(self.assess()["decision"], "READY_FOR_OPERATOR_ACCEPTANCE") + self.manifest["sha256"] = "f" * 64 + self.save_manifest() + self.blocked() + def test_tampered_vm_check_log_is_blocked(self): (self.evidence / "vm" / "logs" / "gui.log").write_text("tampered") self.blocked() From 0e2731221e385565ef77e5f0afc389324ff3d364 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:59:18 +0000 Subject: [PATCH 34/70] Update VM-stage regression fixture to reflect stable separately-signed checklist --- qa/application/test_stage_executor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qa/application/test_stage_executor.py b/qa/application/test_stage_executor.py index ea27e44..bde8674 100644 --- a/qa/application/test_stage_executor.py +++ b/qa/application/test_stage_executor.py @@ -32,7 +32,9 @@ def setUp(self): "candidate":"qa/application/jobs/demo/candidate.zip", "sha256":gate.sha256(self.candidate), "checks":checks} (self.job/"manifest.json").write_text(json.dumps(self.manifest)) - (self.job/"manifest.sig").write_text("fixture-untrusted") + (self.job/"policy.json").write_text(json.dumps({ + "schema":"devnet-qa-checklist-v1","job_id":"demo","checks":checks})) + (self.job/"policy.sig").write_text("fixture-untrusted") self.sha = "a" * 40 receipt = {"schema":1,"job_id":"demo", "candidate_sha256":self.manifest["sha256"], From d14f225549782fbb0e31318f289b6bfae12047b8 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:59:33 +0000 Subject: [PATCH 35/70] Require independent GitHub source-bound attestation on both exact ZIP and QA receipt --- qa/application/release_coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qa/application/release_coordinator.py b/qa/application/release_coordinator.py index 15cd718..851dba3 100644 --- a/qa/application/release_coordinator.py +++ b/qa/application/release_coordinator.py @@ -231,6 +231,7 @@ def assess(job_dir: Path, evidence_dir: Path, trust_dir: Path, repo: str, m, manifest_hash, by_stage = manifest_for(job_dir) gh_path, source = github_receipt(job_dir, evidence_dir, manifest_hash, by_stage) github_verifier(gh_path, repo, source) + github_verifier(job_dir / "candidate.zip", repo, source) previous = sha256(gh_path) previous = signed_stage("vm", job_dir, evidence_dir, trust_dir, source, manifest_hash, by_stage["vm"], previous, stage_verifier) From 02e0cf37666d49bb1e58e1606cbed05d194fefd8 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:59:37 +0000 Subject: [PATCH 36/70] Require independent GitHub source-bound attestation on both exact ZIP and QA receipt --- qa/application/stage_executor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qa/application/stage_executor.py b/qa/application/stage_executor.py index 6d8128f..ad2b0ef 100644 --- a/qa/application/stage_executor.py +++ b/qa/application/stage_executor.py @@ -78,6 +78,7 @@ def run_stage(job_dir: Path, evidence_dir: Path, driver_path: Path, trust_dir: P manifest, manifest_hash, by_stage = gate.manifest_for(job_dir) gh_receipt, source = gate.github_receipt(job_dir, evidence_dir, manifest_hash, by_stage) github_verifier(gh_receipt, repo, source) + github_verifier(job_dir/"candidate.zip", repo, source) previous = gate.sha256(gh_receipt) if stage == "host": previous = gate.signed_stage("vm", job_dir, evidence_dir, trust_dir, From 60b97e0983223fe51dc70ed669f47a5111a08268 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:00:54 +0000 Subject: [PATCH 37/70] Add exact-candidate signed GitHub evidence handoff for private VM validation --- qa/application/handoff.py | 131 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 qa/application/handoff.py diff --git a/qa/application/handoff.py b/qa/application/handoff.py new file mode 100644 index 0000000..c52ca78 --- /dev/null +++ b/qa/application/handoff.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Assemble and independently verify an exact-candidate CI -> VM handoff. + +A handoff is transport, NOT a release and not proof the application was run in +a VM. A private-VM broker must verify it before calling the stage executor. +No SSH, private VM access, installer execution or signature creation here. +""" +from __future__ import annotations +import argparse +import json +from pathlib import Path +import shutil +import sys + +import release_coordinator as gate + + +def assemble(job_dir: Path, evidence_dir: Path, target: Path): + manifest, manifest_hash, checks = gate.manifest_for(job_dir) + receipt, source = gate.github_receipt(job_dir, evidence_dir, manifest_hash, checks) + policy = job_dir / "policy.json" + policy_sig = job_dir / "policy.sig" + gate.require(policy.is_file() and not policy.is_symlink() + and policy_sig.is_file() and not policy_sig.is_symlink(), + "operator policy/signature missing") + p = gate.load(policy) + gate.require(isinstance(p, dict) and + set(p) == {"schema", "job_id", "checks"} and + p["schema"] == "devnet-qa-checklist-v1" and + p["job_id"] == manifest["job_id"] and + p["checks"] == manifest["checks"], + "operator-approved checklist mismatch") + gate.require(not target.exists(), "handoff exists: never reuse or overwrite") + target.mkdir(parents=True,mode=0o700) + job_out = target / manifest["job_id"] + job_out.mkdir(mode=0o700) + gh_out = target / "evidence" / "github" + gh_out.mkdir(mode=0o700,parents=True) + inputs = [(job_dir / "candidate.zip", job_out / "candidate.zip"), + (job_dir / "manifest.json", job_out / "manifest.json"), + (policy, job_out / "policy.json"), + (policy_sig, job_out / "policy.sig"), + (receipt, gh_out / "receipt.json")] + for src, dst in inputs: + gate.require(src.is_file() and not src.is_symlink(), "missing/symlink source") + shutil.copyfile(src,dst) + details = {"schema":"devnet-qa-handoff-v1", + "job_id":manifest["job_id"], + "candidate_sha256":manifest["sha256"], + "manifest_sha256":manifest_hash, + "policy_sha256":gate.sha256(policy), + "github_receipt_sha256":gate.sha256(receipt), + "github_sha":source, + "release_approved":False} + (target/"HANDOFF.json").write_text(json.dumps(details,sort_keys=True,indent=2)+"\n") + return details + + +def verify(target: Path, trust_dir: Path, repo: str, + signature_verifier=gate.verify_trusted_signature, + github_verifier=gate.verify_github_signature): + gate.require(target.is_dir() and not target.is_symlink(), "invalid handoff") + d=gate.load(target/"HANDOFF.json") + gate.require(isinstance(d,dict) and set(d) == + {"schema","job_id","candidate_sha256","manifest_sha256", + "policy_sha256","github_receipt_sha256","github_sha", + "release_approved"} and + d["schema"]=="devnet-qa-handoff-v1" and + d["release_approved"] is False, "handoff schema/approval violation") + job_id=d["job_id"] + gate.require(isinstance(job_id,str) and gate.ID.fullmatch(job_id), + "invalid handoff job id") + job_dir=target/job_id + evidence_dir=target/"evidence" + gate.require(job_dir.is_dir() and not job_dir.is_symlink(), + "missing/unsafe job directory") + for relative in ["candidate.zip","manifest.json","policy.json","policy.sig"]: + path=job_dir/relative + gate.require(path.is_file() and not path.is_symlink(), + "missing/unsafe handoff file "+relative) + receipt_path=evidence_dir/"github"/"receipt.json" + gate.require(receipt_path.is_file() and not receipt_path.is_symlink(), + "missing/unsafe GitHub receipt") + manifest, manifest_hash, checks=gate.manifest_for(job_dir) + gate.require(d["candidate_sha256"] == manifest["sha256"] and + d["manifest_sha256"] == manifest_hash and + d["policy_sha256"] == gate.sha256(job_dir/"policy.json") and + d["github_receipt_sha256"] == gate.sha256(receipt_path), + "handoff hashes do not match exact input bytes") + gate.verified_policy(job_dir,trust_dir,signature_verifier) + observed, source=gate.github_receipt(job_dir,evidence_dir,manifest_hash,checks) + gate.require(d["github_sha"] == source and observed == receipt_path, + "handoff revision/receipt mismatch") + # Verify BOTH GitHub-issued attestations; a JSON PASS is insufficient. + github_verifier(receipt_path,repo,source) + github_verifier(job_dir/"candidate.zip",repo,source) + return {"schema":"devnet-qa-verified-handoff-v1", + "job_id":job_id,"candidate_sha256":manifest["sha256"], + "github_sha":source,"github":"PASS_SIGNED", + "vm":"NOT_TESTED","host":"NOT_TESTED", + "release_approved":False} + + +def main(): + parser=argparse.ArgumentParser() + parser.add_argument("action",choices=["assemble","verify"]) + parser.add_argument("--job-dir",type=Path) + parser.add_argument("--evidence-dir",type=Path) + parser.add_argument("--handoff-dir",type=Path,required=True) + parser.add_argument("--trust-dir",type=Path,default=Path("/etc/devnet-qa/trust")) + parser.add_argument("--repo",default="devnet82/vr-stack-control") + args=parser.parse_args() + try: + if args.action=="assemble": + gate.require(args.job_dir is not None and + args.evidence_dir is not None, + "assemble requires job-dir and evidence-dir") + result=assemble(args.job_dir,args.evidence_dir,args.handoff_dir) + else: + result=verify(args.handoff_dir,args.trust_dir,args.repo) + except (gate.Blocked,OSError,ValueError,KeyError,TypeError, + json.JSONDecodeError) as exc: + print(json.dumps({"decision":"BLOCKED","reason":str(exc), + "release_approved":False})) + return 2 + print(json.dumps(result,sort_keys=True)) + return 0 + + +if __name__=="__main__": + sys.exit(main()) From b8e8bab0e9734669cf8fc506d831402dd1cac549 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:01:15 +0000 Subject: [PATCH 38/70] Add 10 tests of exact GitHub-to-VM handoff, signatures, tampering, provenance and release block --- qa/application/test_handoff.py | 149 +++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 qa/application/test_handoff.py diff --git a/qa/application/test_handoff.py b/qa/application/test_handoff.py new file mode 100644 index 0000000..c5ca1be --- /dev/null +++ b/qa/application/test_handoff.py @@ -0,0 +1,149 @@ +import json +from pathlib import Path +import shutil +import tempfile +import unittest + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +import handoff +import release_coordinator as gate + + +class HandoffTests(unittest.TestCase): + def setUp(self): + tmp=tempfile.TemporaryDirectory() + self.addCleanup(tmp.cleanup) + base=Path(tmp.name) + self.job=base/"example_job" + self.job.mkdir() + self.evidence=base/"original_evidence" + (self.evidence/"github").mkdir(parents=True) + self.target=base/"transport" + self.trust=base/"trust" + self.trust.mkdir() + (self.job/"candidate.zip").write_bytes(b"candidate original bytes") + checks=[{"id":"github_archive","stage":"github","argv":["python3","-V"]}] + checks.extend({"id":i,"stage":"vm","argv":[]} for i in + ("install","upgrade","rollback","gui","tray","recovery","security")) + checks.append({"id":"host_hw","stage":"host","argv":[]}) + manifest={"job_id":"example_job", + "candidate":"qa/application/jobs/example_job/candidate.zip", + "sha256":gate.sha256(self.job/"candidate.zip"), + "checks":checks} + (self.job/"manifest.json").write_text(json.dumps(manifest)) + (self.job/"policy.json").write_text(json.dumps({ + "schema":"devnet-qa-checklist-v1","job_id":"example_job","checks":checks})) + self.private=Ed25519PrivateKey.generate() + (self.job/"policy.sig").write_bytes( + self.private.sign((self.job/"policy.json").read_bytes())) + (self.trust/"operator.pub").write_bytes( + self.private.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo)) + self.source="a"*40 + results=[{"id":c["id"],"status":"PASS","exit_code":0, + "output_sha256":"e"*64} if c["stage"]=="github" else + {"id":c["id"],"stage":c["stage"],"status":"NOT_TESTED"} + for c in checks] + receipt={"schema":1,"job_id":"example_job", + "candidate_sha256":manifest["sha256"], + "manifest_sha256":gate.sha256(self.job/"manifest.json"), + "github_sha":self.source,"checks":results, + "full_acceptance":False,"release_approved":False} + (self.evidence/"github"/"receipt.json").write_text(json.dumps(receipt)) + self.calls=[] + + def signatures(self,path,signature,key_path): + public=serialization.load_pem_public_key(key_path.read_bytes()) + public.verify(signature.read_bytes(),path.read_bytes()) + + def github_attest(self,path,repo,sha): + self.calls.append(path.name) + self.assertEqual(sha,self.source) + self.assertEqual(repo,"devnet82/vr-stack-control") + + def make(self): + return handoff.assemble(self.job,self.evidence,self.target) + + def check(self): + return handoff.verify(self.target,self.trust, + "devnet82/vr-stack-control", + signature_verifier=self.signatures, + github_verifier=self.github_attest) + + def test_valid_handoff_verifies_both_attestations_scope_limited(self): + self.make() + result=self.check() + self.assertEqual(result["github"],"PASS_SIGNED") + self.assertEqual(result["vm"],"NOT_TESTED") + self.assertEqual(result["host"],"NOT_TESTED") + self.assertIs(result["release_approved"],False) + self.assertEqual(set(self.calls),{"candidate.zip","receipt.json"}) + + def test_handoff_never_overwrites(self): + self.make() + with self.assertRaises(gate.Blocked): + self.make() + + def test_changed_candidate_is_blocked(self): + self.make() + (self.target/"example_job"/"candidate.zip").write_bytes(b"tampered") + with self.assertRaises(gate.Blocked): + self.check() + + def test_changed_manifest_is_blocked(self): + self.make() + (self.target/"example_job"/"manifest.json").write_text("{}") + with self.assertRaises((gate.Blocked,KeyError)): + self.check() + + def test_changed_policy_signature_is_blocked(self): + self.make() + (self.target/"example_job"/"policy.sig").write_bytes(b"fake") + with self.assertRaises(Exception): + self.check() + + def test_missing_attestation_is_blocked(self): + self.make() + def deny(*_): + raise gate.Blocked("no valid GitHub attestation") + with self.assertRaises(gate.Blocked): + handoff.verify(self.target,self.trust,"devnet82/vr-stack-control", + signature_verifier=self.signatures, + github_verifier=deny) + + def test_missing_receipt_is_blocked(self): + self.make() + (self.target/"evidence"/"github"/"receipt.json").unlink() + with self.assertRaises(gate.Blocked): + self.check() + + def test_handoff_fake_release_approval_is_blocked(self): + self.make() + p=self.target/"HANDOFF.json" + data=json.loads(p.read_text()) + data["release_approved"]=True + p.write_text(json.dumps(data)) + with self.assertRaises(gate.Blocked): + self.check() + + def test_symlink_candidate_is_blocked(self): + self.make() + p=self.target/"example_job"/"candidate.zip" + p.unlink() + p.symlink_to(self.job/"candidate.zip") + with self.assertRaises(gate.Blocked): + self.check() + + def test_mismatched_source_commit_is_blocked(self): + self.make() + p=self.target/"HANDOFF.json" + data=json.loads(p.read_text()) + data["github_sha"]="b"*40 + p.write_text(json.dumps(data)) + with self.assertRaises(gate.Blocked): + self.check() + +if __name__=="__main__": + unittest.main() From 312b3fec3ca1a0c6bbfda34b661eb1cec15de7c1 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:02:04 +0000 Subject: [PATCH 39/70] Add post-attestation candidate-bound GitHub-to-private-VM handoff artifact stage --- .github/workflows/devnet-application-qa.yml | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/.github/workflows/devnet-application-qa.yml b/.github/workflows/devnet-application-qa.yml index 7085f50..06b1f28 100644 --- a/.github/workflows/devnet-application-qa.yml +++ b/.github/workflows/devnet-application-qa.yml @@ -108,3 +108,47 @@ jobs: gh attestation verify "${zips[0]}" --repo "${GITHUB_REPOSITORY}" --signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/devnet-application-qa.yml" --source-digest "$GITHUB_SHA" gh attestation verify "${receipts[0]}" --repo "${GITHUB_REPOSITORY}" --signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/devnet-application-qa.yml" --source-digest "$GITHUB_SHA" echo 'SIGNED_GITHUB_STAGE_VERIFIED. REAL VM/HOST/RELEASE STILL BLOCKED.' >> "$GITHUB_STEP_SUMMARY" + package-private-vm-handoff: + # Only after independent GitHub signature verification. + # This transport artifact is NOT VM test evidence or release approval. + needs: independent-signature-check + runs-on: ubuntu-24.04 + permissions: + contents: read + attestations: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + - uses: actions/download-artifact@v4 + with: + name: registered-application-github-evidence + path: qa-stage-evidence + - name: Prepare candidate-specific handoff (no VM execution) + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + manifests=(qa/application/jobs/*/manifest.json) + receipts=(qa-stage-evidence/*/receipt.json) + test "${#manifests[@]}" -eq 1 + test "${#receipts[@]}" -eq 1 + work="$(mktemp -d)" + mkdir -p "$work/github" + cp -- "${receipts[0]}" "$work/github/receipt.json" + jobdir="$(dirname -- "${manifests[0]}")" + test -f "$jobdir/policy.json" -a -f "$jobdir/policy.sig" + python3 qa/application/handoff.py assemble \ + --job-dir "$jobdir" \ + --evidence-dir "$work" \ + --handoff-dir "$RUNNER_TEMP/private-vm-handoff" + echo 'GitHub handoff prepared; VM/host NOT_TESTED, release BLOCKED.' >> "$GITHUB_STEP_SUMMARY" + - uses: actions/upload-artifact@v4 + with: + name: devnet-private-vm-handoff + path: ${{ runner.temp }}/private-vm-handoff + retention-days: 30 + if-no-files-found: error From c5fba5c05f31263cf329326fc765386a843fffd7 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:03:16 +0000 Subject: [PATCH 40/70] Document stable policy, verified 41-test handoff and explicit GitHub-native VM and local deployment blockers --- qa/application/README.md | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/qa/application/README.md b/qa/application/README.md index 86dc818..adc6214 100644 --- a/qa/application/README.md +++ b/qa/application/README.md @@ -20,6 +20,12 @@ tests, or independently close full Master QA. inherited results, requires independently signed operator checklist, independently attested GitHub stage, and separately signed VM and host receipts with per-check SHA256 logs. +- `handoff.py`: assembles and checks a SHA256-bound transport bundle containing + the exact ZIP, the unmodified approved checklist and signature, and the + independently GitHub-attested receipt. Neither handoff creation nor checking + can grant VM PASS or release approval. +- `test_handoff.py`: covers signed policy, two independent GitHub attestations, + altered packages, altered checklists, symlinks, missing files and false approval. - `stage_executor.py`: executes only the protected, exact per-app VM/host driver commands after checking the signed operator manifest, matching candidate bytes and signed GitHub handoff. Captures actual subprocess logs, stops on failure, @@ -38,8 +44,12 @@ tests, or independently close full Master QA. currently deliberately BLOCKED until a real candidate is registered. ## Exact release-closure contract -1. Operator signs `qa/application/jobs/ID/manifest.json` using an **offline - operator private key**; commit signature to `manifest.sig`. Public key at +1. Operator signs the **stable** `qa/application/jobs/ID/policy.json` + checklist using an offline private key and saves `policy.sig`. A changing + candidate `manifest.json` must contain the EXACT same approved checks but + binds the new source ZIP SHA256. The existing policy may be reused only + when that approved checklist is unchanged; every new candidate revision + still requires all applicable tests to be run again. Public key at `/etc/devnet-qa/trust/operator.pub` must be root-owned/non-writable. 2. Register the exact application ZIP at `qa/application/jobs/ID/candidate.zip`. Manifest binds the ZIP SHA256, complete applicable checks and stages. @@ -98,9 +108,9 @@ python3 qa/application/release_coordinator.py \ have not been deployed or verified. - No protected host acceptance executor or signer has been deployed. - No full-scope receipt chain has been generated or validated. -- GitHub coordinator regression run 41141079307: **30/30 PASS** on commit - 69b7b22467ae8c99a686da040b32a4ace74c8a4b (including real fixture - subprocesses, not real application execution). +- GitHub coordinator regression run 41141854109: **41/41 PASS** on commit + b8e8bab0e9734669cf8fc506d831402dd1cac549 (includes transport tampering + protection and real fixture subprocesses, not real application execution). - GitHub application-stage run 41140630122: blocked with HTTP 403 when attempting to read the inaccessible unpublished draft asset. - GitHub application-stage run 41140960760: intentionally BLOCKED because @@ -108,3 +118,20 @@ python3 qa/application/release_coordinator.py \ **Release remains BLOCKED.** The original v0.4.4 runner, router v0.2.4, Master QA v0.3.8, original ZIPs, existing VM and gaming guard are preserved. + +## Implementation boundary + +The newly added `package-private-vm-handoff` GitHub job depends on the +independent GitHub attestation verification job. It cannot trigger until an +actual candidate completes GitHub checks. Its output is an unsigned transport +artifact and does **not** execute real CachyOS VM, host GUI or rollback checks. +The local broker, real application-specific VM test driver, protected signer +and automatic dispatch are still missing. The earlier full native CachyOS +GitHub VM workflow was specific to a small hotfix and is NOT a substitute for +full application GitHub-first testing. Real GitHub-hosted VM acceptance remains +NOT_TESTED for any full application candidate. + +A preserved `devnet-master-qa-registered-application-stages-v0.1.0-QA-BLOCKED.zip` +exists in the ChatGPT Library, but its original bytes could not be materialised +through the present connection. Do not represent it as inspected or assume +that its contents were installed or integrated. From d42e460795397cd47b342e9b326a250761929335 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:07:39 +0000 Subject: [PATCH 41/70] Probe genuine published v0.6.85 full-app bytes and layout on GitHub first, fail closed --- .../devnet-fullapp-v0685-github-first.yml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/devnet-fullapp-v0685-github-first.yml diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml new file mode 100644 index 0000000..24b1da0 --- /dev/null +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -0,0 +1,51 @@ +name: Devnet full-application baseline v0.6.85 — GitHub FIRST (release blocked) +on: + push: + branches: [qa/registered-app-executor-fix-v0.1.0] + paths: + - '.github/workflows/devnet-fullapp-v0685-github-first.yml' + - 'qa/application/candidate_checks.py' + - 'qa/application/v0685_baseline_inspect.py' + workflow_dispatch: +permissions: + contents: read +jobs: + exact-candidate-baseline: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Fetch preserved published full application (never rebuild) + shell: bash + run: | + set -euo pipefail + mkdir -p baseline-evidence + curl --fail --location --silent --show-error --retry 3 --connect-timeout 15 --max-time 180 \ + 'https://github.com/devnet82/vr-stack-control/releases/download/v0.6.85/vr-stack-control-v0.6.85.zip' \ + -o baseline-evidence/candidate.zip + echo 'db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3 baseline-evidence/candidate.zip' | sha256sum --check - + test "$(stat --format=%s baseline-evidence/candidate.zip)" -eq 53403 + - name: Run genuine archive integrity, safety and source-syntax checks + shell: bash + run: | + set -euo pipefail + python3 qa/application/candidate_checks.py integrity baseline-evidence/candidate.zip + python3 qa/application/candidate_checks.py safety baseline-evidence/candidate.zip + python3 qa/application/candidate_checks.py python_syntax baseline-evidence/candidate.zip + python3 qa/application/v0685_baseline_inspect.py \ + baseline-evidence/candidate.zip baseline-evidence/inspection.json + python3 -m json.tool baseline-evidence/inspection.json + - uses: actions/upload-artifact@v4 + if: always() + with: + name: v0685-original-github-input-and-structure + path: baseline-evidence/ + retention-days: 30 + if-no-files-found: warn + - name: Fail closed after input checks + if: always() + run: | + echo 'Only original archived full-app ZIP integrity/static checks are within this job scope.' >> "$GITHUB_STEP_SUMMARY" + echo 'Real GitHub-hosted CachyOS install/upgrade/rollback/GUI and user VM/host checks NOT_TESTED. RELEASE BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From e0c31c0fbaf9850151d1d38396c01d1ac6945c8f Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:07:50 +0000 Subject: [PATCH 42/70] Independently verify complete application layout in original v0.6.85 GitHub ZIP --- qa/application/v0685_baseline_inspect.py | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 qa/application/v0685_baseline_inspect.py diff --git a/qa/application/v0685_baseline_inspect.py b/qa/application/v0685_baseline_inspect.py new file mode 100644 index 0000000..e6de988 --- /dev/null +++ b/qa/application/v0685_baseline_inspect.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Read-only inventory of the ORIGINAL published full-application release ZIP. + +All findings are about package bytes, NOT install, GUI, rollback or release QA. +""" +import argparse +import hashlib +import json +from pathlib import Path, PurePosixPath +import zipfile + +APP_FILES = {"install.sh", "uninstall.sh", "bin/vr-control", + "bin/vr-control-gui", "bin/vr-stack-tray", "bin/vr-stackd"} + +def inspect(path: Path): + with zipfile.ZipFile(path) as z: + names = [i.filename for i in z.infolist() if not i.is_dir()] + leading = {name.split("/", 1)[0] for name in names if "/" in name} + prefix = (next(iter(leading)) + "/") if len(leading) == 1 and all( + name.startswith(next(iter(leading)) + "/") for name in names) else "" + normalized = [n[len(prefix):] if prefix else n for n in names] + missing = sorted(APP_FILES - set(normalized)) + if missing: + raise ValueError("Not a verified full-app package; required files absent: " + repr(missing)) + scripts = sorted(x for x in normalized if x.startswith("tests/")) + result = {"schema":"devnet-original-zip-inventory-v1", + "zip_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + "entries":len(names),"root_prefix":prefix, + "required_app_files":sorted(APP_FILES), + "packaged_test_paths":scripts, + "packaged_test_count":len(scripts), + "full_app_layout":"PASS", + "real_installed_system":"NOT_TESTED", + "gui":"NOT_TESTED","tray":"NOT_TESTED", + "upgrade":"NOT_TESTED","rollback":"NOT_TESTED", + "release_approved":False} + return result + +def main(): + p = argparse.ArgumentParser() + p.add_argument("candidate",type=Path) + p.add_argument("receipt",type=Path) + a = p.parse_args() + result = inspect(a.candidate) + a.receipt.write_text(json.dumps(result,indent=2,sort_keys=True)+"\n") + print("FULL_APP_LAYOUT: PASS; INSTALLED_SYSTEM: NOT_TESTED; RELEASE: BLOCKED") + +if __name__ == "__main__": + main() From 5fe48cee0db53c4da251b53f672ff44e0aa25917 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:08:50 +0000 Subject: [PATCH 43/70] Test real original v0.6.85 install/reinstall/uninstall and data preservation in disposable CI home --- qa/application/v0685_installed_lifecycle.py | 121 ++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 qa/application/v0685_installed_lifecycle.py diff --git a/qa/application/v0685_installed_lifecycle.py b/qa/application/v0685_installed_lifecycle.py new file mode 100644 index 0000000..d0f53b1 --- /dev/null +++ b/qa/application/v0685_installed_lifecycle.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Execute original VR Stack Control v0.6.85 installer in disposable CI HOME. + +This is an Ubuntu GitHub runner smoke/lifecycle stage only, NEVER a CachyOS +installed-VM, GUI, tray, hardware, rollback or user acceptance certification. +No sudo, no release writes, no modification to host user's actual HOME. +""" +import hashlib +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import zipfile + + +def sha(p): + return hashlib.sha256(p.read_bytes()).hexdigest() + +def main(candidate, evidence): + evidence.mkdir(parents=True, exist_ok=True) + report = {"schema":"devnet-v0685-disposable-ubuntu-lifecycle-v1", + "candidate_sha256": sha(candidate), + "checks":{}, "release_approved":False, + "installed_cachyos_vm":"NOT_TESTED", + "real_gui":"NOT_TESTED", + "real_tray":"NOT_TESTED", + "upgrade_to_new_revision":"NOT_TESTED", + "transactional_rollback":"NOT_TESTED"} + with tempfile.TemporaryDirectory(prefix="devnet-v0685-", dir="/tmp") as tmp: + root=Path(tmp) + home=root/"home" + home.mkdir(mode=0o700) + src=root/"source" + src.mkdir() + with zipfile.ZipFile(candidate) as z: + z.extractall(src) + packages=list(src.glob("*/install.sh")) + if len(packages)!=1: + raise ValueError("exactly one full-app install.sh required") + app=packages[0].parent + sentinel=home/"outside-app-preserve.txt" + sentinel.write_text("DO_NOT_TOUCH") + config=home/".config/vr-stack/stack.conf" + config.parent.mkdir(parents=True) + config.write_text("DEVNET_PRESERVE_SENTINEL=yes\n") + env={**os.environ, + "HOME":str(home), + "XDG_CONFIG_HOME":str(home/".config"), + "XDG_DATA_HOME":str(home/".local/share"), + "XDG_RUNTIME_DIR":str(root/"runtime"), + "PATH":os.environ.get("PATH","/usr/bin:/bin")} + (root/"runtime").mkdir(mode=0o700) + expected={ + "vr-control":"bin/vr-control", + "vr-control-gui":"bin/vr-control-gui", + "vr-doctor":"bin/vr-doctor", + "vr-stackd":"bin/vr-stackd", + "vr-stack-tray":"bin/vr-stack-tray"} + def test(id, callback): + try: + detail=callback() + report["checks"][id]={"status":"PASS","detail":str(detail)[:300]} + print(id,"PASS",flush=True) + except Exception as e: + report["checks"][id]={"status":"FAIL","detail":repr(e)[:350]} + print(id,"FAIL",str(e),flush=True) + raise + def command(name): + p=subprocess.run(["/usr/bin/bash",name], + cwd=app,env=env,stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE,stderr=subprocess.STDOUT, + text=True,timeout=45,check=False) + (evidence/(name+".log")).write_text(p.stdout) + if p.returncode: + raise RuntimeError(f"{name} returned {p.returncode}; review log artifact") + return f"{name} returned 0" + def installed(): + for name,original in expected.items(): + observed=home/".local/bin"/name + if not observed.is_file(): + raise ValueError("missing installed "+name) + if sha(observed)!=sha(app/original): + raise ValueError("installed file does not match ZIP "+name) + return f"{len(expected)} exact installed executable hashes verified" + try: + test("fresh_install",lambda:command("install.sh")) + test("exact_install_hashes",installed) + test("reinstall",lambda:command("install.sh")) + test("reinstalled_file_hashes",installed) + def config_preserved(): + if config.read_text()!="DEVNET_PRESERVE_SENTINEL=yes\n": + raise ValueError("pre-existing user configuration changed") + return "original config bytes unchanged" + test("preserve_existing_config",config_preserved) + test("uninstall",lambda:command("uninstall.sh")) + def uninstalled(): + present=[name for name in expected if (home/".local/bin"/name).exists()] + if present: + raise ValueError("installed app files remain after uninstall: "+str(present)) + return "app executables removed" + test("uninstall_removes_app_files",uninstalled) + test("uninstall_preserves_user_config",config_preserved) + def outside_preserved(): + if sentinel.read_text()!="DO_NOT_TOUCH": + raise ValueError("unrelated user data changed") + return "unrelated data unchanged" + test("outside_user_data_preserved",outside_preserved) + except Exception: + report["result"]="FAIL" + else: + report["result"]="PASS_LIMITED" + finally: + (evidence/"lifecycle-receipt.json").write_text(json.dumps(report,indent=2,sort_keys=True)+"\n") + return 0 if report["result"]=="PASS_LIMITED" else 2 + +if __name__=="__main__": + if len(sys.argv)!=3: + raise SystemExit("usage: v0685_installed_lifecycle.py CANDIDATE_ZIP EVIDENCE_DIR") + sys.exit(main(Path(sys.argv[1]).resolve(),Path(sys.argv[2]).resolve())) From cdd3b43d5d0ab7ea329b41763a57aa06eeced161 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:09:00 +0000 Subject: [PATCH 44/70] Execute original v0.6.85 installer lifecycle in isolated GitHub CI HOME, retain limited-scope evidence --- .../workflows/devnet-fullapp-v0685-github-first.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index 24b1da0..75793bb 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -6,6 +6,7 @@ on: - '.github/workflows/devnet-fullapp-v0685-github-first.yml' - 'qa/application/candidate_checks.py' - 'qa/application/v0685_baseline_inspect.py' + - 'qa/application/v0685_installed_lifecycle.py' workflow_dispatch: permissions: contents: read @@ -37,6 +38,13 @@ jobs: python3 qa/application/v0685_baseline_inspect.py \ baseline-evidence/candidate.zip baseline-evidence/inspection.json python3 -m json.tool baseline-evidence/inspection.json + - name: Install and uninstall EXACT ZIP in disposable unprivileged HOME + shell: bash + run: | + set -euo pipefail + python3 qa/application/v0685_installed_lifecycle.py \ + baseline-evidence/candidate.zip baseline-evidence/disposable-lifecycle + python3 -m json.tool baseline-evidence/disposable-lifecycle/lifecycle-receipt.json - uses: actions/upload-artifact@v4 if: always() with: @@ -47,5 +55,5 @@ jobs: - name: Fail closed after input checks if: always() run: | - echo 'Only original archived full-app ZIP integrity/static checks are within this job scope.' >> "$GITHUB_STEP_SUMMARY" + echo 'Original full-app ZIP integrity and Ubuntu disposable-home install/reinstall/uninstall checks only.' >> "$GITHUB_STEP_SUMMARY" echo 'Real GitHub-hosted CachyOS install/upgrade/rollback/GUI and user VM/host checks NOT_TESTED. RELEASE BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From 7df6769a5dae8bca60e3794178ab5a0981cee588 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:09:38 +0000 Subject: [PATCH 45/70] Run exact original full v0.6.85 ZIP inside real isolated GitHub-hosted CachyOS QEMU live guest; capture fail-closed evidence --- .../devnet-fullapp-v0685-github-first.yml | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index 75793bb..1e89cc5 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -57,3 +57,123 @@ jobs: run: | echo 'Original full-app ZIP integrity and Ubuntu disposable-home install/reinstall/uninstall checks only.' >> "$GITHUB_STEP_SUMMARY" echo 'Real GitHub-hosted CachyOS install/upgrade/rollback/GUI and user VM/host checks NOT_TESTED. RELEASE BLOCKED.' >> "$GITHUB_STEP_SUMMARY" + native-cachyos-liveiso: + needs: exact-candidate-baseline + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: v0685-original-github-input-and-structure + path: original-input + - name: Verify exact v0.6.85 artifact and disposable KVM capabilities + shell: bash + run: | + set -euo pipefail + echo 'db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3 original-input/candidate.zip' | sha256sum -c - + test -c /dev/kvm + test -r /dev/kvm -a -w /dev/kvm + echo 'GITHUB_HOST_KVM_AVAILABLE (not yet a guest test)' + - name: Install isolated QEMU and FAT-disk tools on GitHub runner + shell: bash + run: | + set -euo pipefail + sudo apt-get update -qq + sudo apt-get install -qq -y --no-install-recommends qemu-system-x86 qemu-utils mtools dosfstools + - name: Prepare independently verified CachyOS ISO and pinned guest harness + shell: bash + run: | + set -euo pipefail + mkdir -p native-evidence /tmp/devnet-native-v0685 + base=https://iso.cachyos.org/desktop/260809 + name=cachyos-desktop-linux-260809.iso + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 \ + "$base/$name.sha256" -o "/tmp/devnet-native-v0685/$name.sha256" + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 \ + "$base/$name" -o "/tmp/devnet-native-v0685/$name" + (cd /tmp/devnet-native-v0685 && sha256sum -c "$name.sha256") | tee native-evidence/iso-sha256.txt + root="https://raw.githubusercontent.com/devnet82/devnet-cachyos-repair/d8e4447d19e77a77dc355dc07e72c0c5c3edf741/qa/portfolio-cachyos-vm-v0.1.0" + curl -fLsS "$root/guest_package_qa.py" -o /tmp/devnet-native-v0685/guest_package_qa.py + curl -fLsS "$root/qemu_control.py" -o /tmp/devnet-native-v0685/qemu_control.py + python3 -m py_compile /tmp/devnet-native-v0685/guest_package_qa.py /tmp/devnet-native-v0685/qemu_control.py + truncate -s 128M /tmp/devnet-native-v0685/control.img + mkfs.vfat -F 32 -n DEVNETQA /tmp/devnet-native-v0685/control.img >/dev/null + img=/tmp/devnet-native-v0685/control.img + mcopy -i "$img" /tmp/devnet-native-v0685/guest_package_qa.py ::/guest_package_qa.py + mcopy -i "$img" original-input/candidate.zip ::/candidate.zip + cat > /tmp/devnet-native-v0685/run-guest.sh <<'GUEST' + #!/usr/bin/env bash + set -Eeuo pipefail + exec >/mnt/controller.log 2>&1 + trap 'rc=$?; echo VM_GUEST_CONTROLLER_EXIT=$rc; sync' EXIT + echo "DEVNET_NATIVE_CACHYOS_ORIGINAL_v0685_QA_START" + cat /etc/os-release + uname -r + python3 /mnt/guest_package_qa.py /mnt/candidate.zip \ + db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3 \ + 0.6.85 /mnt/qa-result.json /mnt/package-tests.log + GUEST + bash -n /tmp/devnet-native-v0685/run-guest.sh + mcopy -i "$img" /tmp/devnet-native-v0685/run-guest.sh ::/install-guest.sh + mdir -i "$img" ::/ > native-evidence/control-manifest.txt + - name: Boot genuine CachyOS LIVE ISO in isolated QEMU and capture checks + shell: bash + run: | + set -euo pipefail + root=/tmp/devnet-native-v0685 + qemu-system-x86_64 -machine q35,accel=kvm -cpu host -m 4096 -smp 2 \ + -drive file="$root/cachyos-desktop-linux-260809.iso",media=cdrom,if=ide \ + -drive if=none,id=ctl,file="$root/control.img",format=raw \ + -device virtio-blk-pci,drive=ctl,serial=DEVNETCONTROL \ + -nic none -boot order=d -vga std -display none \ + -serial file:native-evidence/serial.log \ + -monitor unix:"$root/qemu-monitor.sock",server=on,wait=off \ + -daemonize -pidfile "$root/qemu.pid" + sleep 110 + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" framebuffer \ + --image native-evidence/guest-before-tests.ppm + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" begin-install + captured=0 + for attempt in $(seq 1 75); do + mcopy -o -i "$root/control.img" ::/controller.log native-evidence/controller.log >/dev/null 2>&1 || true + mcopy -o -i "$root/control.img" ::/package-tests.log native-evidence/package-tests.log >/dev/null 2>&1 || true + if mcopy -o -i "$root/control.img" ::/qa-result.json native-evidence/qa-result.json >/dev/null 2>&1; then + if python3 -c 'import json; json.load(open("native-evidence/qa-result.json"))' 2>/dev/null; then + captured=1 + break + fi + fi + sleep 8 + done + test "$captured" -eq 1 + python3 - <<'PY' + import json + r=json.load(open("native-evidence/qa-result.json")) + assert r["format"]=="devnet-native-cachyos-vm-qa-v0.1.0" + required=["native_cachyos","control_disk_identity","exact_sha256", + "zip_integrity","package_version","native_syntax"] + for check in required: + print("CACHYOS_GUEST",check,r["checks"].get(check)) + assert r["checks"][check]["status"]=="PASS", check + assert r["release_decision"]=="BLOCKED" + assert r["overall"]=="PARTIAL" # Installed OS/GUI remain unverified + print("LIVE_CACHYOS_PACKAGE_GUEST: PASS_LIMITED; MASTER_QA: BLOCKED") + PY + - name: Preserve authentic native guest evidence on every outcome + if: always() + uses: actions/upload-artifact@v4 + with: + name: fullapp-v0685-native-cachyos-liveiso-evidence + path: native-evidence/ + retention-days: 30 + if-no-files-found: warn + - name: Enforce precise stage scope + if: always() + run: | + echo 'A live CachyOS ISO guest is NOT an installed CachyOS KDE application acceptance environment.' >> "$GITHUB_STEP_SUMMARY" + echo 'Install/upgrade/rollback, genuine KDE GUI/tray and physical host hardware remain NOT_TESTED; release BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From 6d820b4d1b46e52567a183b853edbab5958ab522 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:10:15 +0000 Subject: [PATCH 46/70] Diagnose actual GitHub KVM access and grant disposable-runner-only permissions when device exists --- .../workflows/devnet-fullapp-v0685-github-first.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index 1e89cc5..ce0a5fc 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -76,7 +76,15 @@ jobs: run: | set -euo pipefail echo 'db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3 original-input/candidate.zip' | sha256sum -c - - test -c /dev/kvm + echo '=== GitHub runner KVM device ===' + id + ls -l /dev/kvm || true + test -c /dev/kvm || { echo 'BLOCKED: GitHub hosted runner has NO /dev/kvm' >&2; exit 2; } + if ! test -r /dev/kvm -a -w /dev/kvm; then + # Grant only this disposable GitHub runner process access. + sudo chown "$(id -u):$(id -g)" /dev/kvm + sudo chmod 0600 /dev/kvm + fi test -r /dev/kvm -a -w /dev/kvm echo 'GITHUB_HOST_KVM_AVAILABLE (not yet a guest test)' - name: Install isolated QEMU and FAT-disk tools on GitHub runner From 832f09a385dcb3a0d14129163e3a3fb11eb41d9c Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:11:20 +0000 Subject: [PATCH 47/70] Add real unprivileged original-app install/reinstall/uninstall and config-preservation tests inside native CachyOS VM --- qa/application/guest_v0685_lifecycle.py | 151 ++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 qa/application/guest_v0685_lifecycle.py diff --git a/qa/application/guest_v0685_lifecycle.py b/qa/application/guest_v0685_lifecycle.py new file mode 100644 index 0000000..bb1c433 --- /dev/null +++ b/qa/application/guest_v0685_lifecycle.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Real v0.6.85 install/reinstall/uninstall in a disposable native CachyOS guest. + +Runs untrusted application installer only via unprivileged 'nobody' user in a +temporary HOME; the guest itself is disposable, without network or host mounts. +NOT a real installed-CachyOS/KDE GUI/VR/host/release-acceptance test. +""" +from __future__ import annotations +import hashlib +import json +import os +from pathlib import Path, PurePosixPath +import pwd +import shutil +import stat +import subprocess +import sys +import tempfile +import zipfile + +SHA="db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3" +EXES={"vr-control":"bin/vr-control","vr-control-gui":"bin/vr-control-gui", + "vr-doctor":"bin/vr-doctor","vr-stackd":"bin/vr-stackd", + "vr-stack-tray":"bin/vr-stack-tray"} + +def digest(p): + return hashlib.sha256(p.read_bytes()).hexdigest() + +def main(candidate, result_path, log_path): + results={"schema":"devnet-v0685-native-cachyos-lifecycle-v1", + "candidate_sha256":digest(candidate), + "checks":{}, "release_approved":False, + "installed_cachyos_vm":"NOT_TESTED", "kde_gui":"NOT_TESTED", + "tray_visibility":"NOT_TESTED", + "transactional_upgrade":"NOT_TESTED", + "transactional_rollback":"NOT_TESTED", + "physical_hardware":"NOT_TESTED"} + log=[] + def record(check, status, detail): + results["checks"][check]={"status":status,"detail":str(detail)[:400]} + log.append(f"{check} {status}: {detail}") + def verify(check, action): + try: + detail=action() + record(check,"PASS",detail) + return True + except Exception as ex: + record(check,"FAIL",repr(ex)) + return False + try: + if not any(x.strip()=="ID=cachyos" for x in Path("/etc/os-release").read_text().splitlines()): + raise RuntimeError("not a CachyOS guest") + if os.geteuid()!=0: + raise RuntimeError("guest setup must run as root; app runs as nobody") + p=subprocess.run(["lsblk","-dn","-o","SERIAL","/dev/vda"], + capture_output=True,text=True,timeout=10) + if p.returncode or p.stdout.strip()!="DEVNETCONTROL": + raise RuntimeError("wrong VM evidence disk; stop") + if results["candidate_sha256"]!=SHA: + raise RuntimeError("original application ZIP hash mismatch") + nobody=pwd.getpwnam("nobody") + with tempfile.TemporaryDirectory(prefix="devnet-fullapp-guest-",dir="/var/tmp") as d: + root=Path(d) + home=root/"home" + src=root/"source" + home.mkdir() + src.mkdir() + with zipfile.ZipFile(candidate) as z: + infos=z.infolist() + for item in infos: + name=item.filename + path=PurePosixPath(name) + kind=stat.S_IFMT(item.external_attr >> 16) + if (not name or path.is_absolute() or + ".." in path.parts or "\\" in name or + kind not in (0,stat.S_IFDIR,stat.S_IFREG)): + raise RuntimeError("unsafe archive item") + if z.testzip() is not None: + raise RuntimeError("bad ZIP CRC") + z.extractall(src) + app_dirs=list(src.glob("*/install.sh")) + if len(app_dirs)!=1: + raise RuntimeError("full app installer missing/ambiguous") + app=app_dirs[0].parent + for folder,dirs,files in os.walk(root): + os.chown(folder,nobody.pw_uid,nobody.pw_gid) + for entry in dirs+files: + path=Path(folder)/entry + if not path.is_symlink(): + os.chown(path,nobody.pw_uid,nobody.pw_gid) + config=home/".config"/"vr-stack"/"stack.conf" + config.parent.mkdir(parents=True) + os.chown(config.parent,nobody.pw_uid,nobody.pw_gid) + config.write_text("ORIGINAL_GUEST_CONFIG=preserve\n") + os.chown(config,nobody.pw_uid,nobody.pw_gid) + unrelated=home/"KEEP_UNRELATED" + unrelated.write_text("keep") + os.chown(unrelated,nobody.pw_uid,nobody.pw_gid) + env={"HOME":str(home),"PATH":"/usr/bin:/bin", + "XDG_CONFIG_HOME":str(home/".config"), + "XDG_DATA_HOME":str(home/".local/share"), + "LANG":"C.UTF-8"} + def command(script): + cmd=["/usr/bin/runuser","-u","nobody","--","/usr/bin/env","-i", + *[f"{k}={v}" for k,v in env.items()], + "/usr/bin/bash",str(app/script)] + p=subprocess.run(cmd,cwd=app,capture_output=True, + text=True,timeout=60,check=False) + log.append("EXECUTED "+script+" exit="+str(p.returncode)) + log.append((p.stdout+"\n"+p.stderr)[-5000:]) + if p.returncode: + raise RuntimeError(script+" exit "+str(p.returncode)) + return "actual unprivileged installer exit 0" + def hashes(): + for name,original in EXES.items(): + target=home/".local/bin"/name + if not target.is_file() or digest(target)!=digest(app/original): + raise RuntimeError("incorrect installed artifact: "+name) + return str(len(EXES))+" installed executable hashes match ZIP" + def preserved(): + if config.read_text()!="ORIGINAL_GUEST_CONFIG=preserve\n": + raise RuntimeError("pre-existing config changed") + if unrelated.read_text()!="keep": + raise RuntimeError("unrelated HOME data changed") + return "config and unrelated user file retained" + def removed(): + leftovers=[name for name in EXES if (home/".local/bin"/name).exists()] + if leftovers: + raise RuntimeError("installer left files: "+repr(leftovers)) + return "all registered app executables removed" + if verify("fresh_install",lambda:command("install.sh")): + verify("exact_install_hashes",hashes) + if verify("reinstall",lambda:command("install.sh")): + verify("reinstalled_hashes",hashes) + verify("configuration_preserved",preserved) + if verify("uninstall",lambda:command("uninstall.sh")): + verify("uninstall_removes_app_files",removed) + verify("uninstall_preserves_unrelated_data",preserved) + except Exception as ex: + record("harness","FAIL",repr(ex)) + finally: + statuses=[x["status"] for x in results["checks"].values()] + results["overall"]="FAIL" if not statuses or "FAIL" in statuses else "PASS_LIMITED" + result_path.write_text(json.dumps(results,indent=2,sort_keys=True)+"\n") + log_path.write_text("\n".join(log)+"\n") + return 2 if results["overall"]=="FAIL" else 0 + +if __name__=="__main__": + if len(sys.argv)!=4: + raise SystemExit("usage: guest_v0685_lifecycle.py candidate.zip result.json log.txt") + sys.exit(main(Path(sys.argv[1]),Path(sys.argv[2]),Path(sys.argv[3]))) From 4a7bfb847aae824bd3cb7d5c77254d62af8378a4 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:11:38 +0000 Subject: [PATCH 48/70] Execute genuine app installer lifecycle inside native GitHub CachyOS guest in addition to hash and syntax checks --- .../devnet-fullapp-v0685-github-first.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index ce0a5fc..331ca07 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -113,6 +113,8 @@ jobs: mkfs.vfat -F 32 -n DEVNETQA /tmp/devnet-native-v0685/control.img >/dev/null img=/tmp/devnet-native-v0685/control.img mcopy -i "$img" /tmp/devnet-native-v0685/guest_package_qa.py ::/guest_package_qa.py + python3 -m py_compile qa/application/guest_v0685_lifecycle.py + mcopy -i "$img" qa/application/guest_v0685_lifecycle.py ::/guest_v0685_lifecycle.py mcopy -i "$img" original-input/candidate.zip ::/candidate.zip cat > /tmp/devnet-native-v0685/run-guest.sh <<'GUEST' #!/usr/bin/env bash @@ -125,6 +127,8 @@ jobs: python3 /mnt/guest_package_qa.py /mnt/candidate.zip \ db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3 \ 0.6.85 /mnt/qa-result.json /mnt/package-tests.log + python3 /mnt/guest_v0685_lifecycle.py /mnt/candidate.zip \ + /mnt/lifecycle-result.json /mnt/lifecycle-tests.log GUEST bash -n /tmp/devnet-native-v0685/run-guest.sh mcopy -i "$img" /tmp/devnet-native-v0685/run-guest.sh ::/install-guest.sh @@ -150,6 +154,8 @@ jobs: for attempt in $(seq 1 75); do mcopy -o -i "$root/control.img" ::/controller.log native-evidence/controller.log >/dev/null 2>&1 || true mcopy -o -i "$root/control.img" ::/package-tests.log native-evidence/package-tests.log >/dev/null 2>&1 || true + mcopy -o -i "$root/control.img" ::/lifecycle-result.json native-evidence/lifecycle-result.json >/dev/null 2>&1 || true + mcopy -o -i "$root/control.img" ::/lifecycle-tests.log native-evidence/lifecycle-tests.log >/dev/null 2>&1 || true if mcopy -o -i "$root/control.img" ::/qa-result.json native-evidence/qa-result.json >/dev/null 2>&1; then if python3 -c 'import json; json.load(open("native-evidence/qa-result.json"))' 2>/dev/null; then captured=1 @@ -170,6 +176,16 @@ jobs: assert r["checks"][check]["status"]=="PASS", check assert r["release_decision"]=="BLOCKED" assert r["overall"]=="PARTIAL" # Installed OS/GUI remain unverified + lifecycle=json.load(open("native-evidence/lifecycle-result.json")) + required_lifecycle=["fresh_install","exact_install_hashes","reinstall", + "reinstalled_hashes","configuration_preserved", + "uninstall","uninstall_removes_app_files", + "uninstall_preserves_unrelated_data"] + for check in required_lifecycle: + print("CACHYOS_GUEST_REAL_INSTALL",check,lifecycle["checks"].get(check)) + assert lifecycle["checks"][check]["status"]=="PASS",check + assert lifecycle["overall"]=="PASS_LIMITED" + assert lifecycle["release_approved"] is False print("LIVE_CACHYOS_PACKAGE_GUEST: PASS_LIMITED; MASTER_QA: BLOCKED") PY - name: Preserve authentic native guest evidence on every outcome From a2dde802879404d2aab8e1e25be8d49dddef294e Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:13:49 +0000 Subject: [PATCH 49/70] Replace incompatible VERSION-file guest assumption with original v0.6.85 source version and native CachyOS package checks --- qa/application/guest_v0685_package.py | 115 ++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 qa/application/guest_v0685_package.py diff --git a/qa/application/guest_v0685_package.py b/qa/application/guest_v0685_package.py new file mode 100644 index 0000000..d1eca50 --- /dev/null +++ b/qa/application/guest_v0685_package.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Exact unchanged v0.6.85 full-application archive checks INSIDE a real CachyOS guest. + +NO artificial VERSION file. Original v0.6.85 release encodes version in its +GUI, control CLI and tray source. This stage verifies actual native guest, +identity of disposable disk, original ZIP hash, CRC, paths, script syntax, and +original version markers. It never grants installed-GUI/host/release approval. +""" +from __future__ import annotations +import hashlib +import json +from pathlib import Path, PurePosixPath +import re +import stat +import subprocess +import sys +import zipfile + +EXPECTED="db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3" +PREFIX="vr-stack-control-v0.6.85/" +REQUIRED={"install.sh","uninstall.sh","bin/vr-control","bin/vr-control-gui", + "bin/vr-stack-tray","bin/vr-stackd","bin/vr-doctor"} +VERSION_FILES={"bin/vr-control","bin/vr-control-gui","bin/vr-stack-tray"} + +def sha(path): + h=hashlib.sha256() + with path.open("rb") as s: + for b in iter(lambda:s.read(1024*1024),b""): + h.update(b) + return h.hexdigest() + +def analyze(candidate): + report={"schema":"devnet-native-cachyos-v0685-package-v1", + "candidate_sha256":sha(candidate), + "checks":{}, "release_approved":False, + "installed_cachyos_system":"NOT_TESTED", + "kde_gui":"NOT_TESTED","tray_visibility":"NOT_TESTED", + "transactional_upgrade":"NOT_TESTED", + "transactional_rollback":"NOT_TESTED","host_hardware":"NOT_TESTED"} + def check(name, ok, detail): + report["checks"][name]={"status":"PASS" if ok else "FAIL", + "detail":str(detail)[:400]} + if not ok: + raise ValueError(name+": "+str(detail)) + try: + osrel=Path("/etc/os-release").read_text() + check("native_cachyos",any(x.strip()=="ID=cachyos" for x in osrel.splitlines()), + "real CachyOS /etc/os-release") + blk=subprocess.run(["/usr/bin/lsblk","-dn","-o","SERIAL","/dev/vda"], + capture_output=True,text=True,timeout=10) + check("control_disk_identity",blk.returncode==0 and + blk.stdout.strip()=="DEVNETCONTROL", + "pinned disposable virtio control disk") + check("exact_sha256",report["candidate_sha256"]==EXPECTED, + report["candidate_sha256"]) + with zipfile.ZipFile(candidate) as z: + infos=z.infolist() + names=set() + total=0 + for entry in infos: + name=entry.filename + parts=PurePosixPath(name) + mode=stat.S_IFMT(entry.external_attr >> 16) + if (not name or name in names or not name.startswith(PREFIX) or + "\\" in name or parts.is_absolute() or + ".." in parts.parts or + mode not in (0,stat.S_IFDIR,stat.S_IFREG)): + raise ValueError("unsafe/duplicated archive member") + names.add(name) + total+=entry.file_size + if total>150_000_000 or len(names)>2000: + raise ValueError("ZIP limits exceeded") + check("zip_integrity",z.testzip() is None,"real CRC of every ZIP member") + check("full_application_layout", + REQUIRED.issubset({n[len(PREFIX):] for n in names}), + "required app executables and installer present") + count=0 + for member in infos: + if member.is_dir(): + continue + data=z.read(member) + head=data.split(b"\n",1)[0] + if head.startswith(b"#!/usr/bin/env python3") or member.filename.endswith(".py"): + compile(data,member.filename,"exec") + count+=1 + elif b"bash" in head and head.startswith(b"#!"): + p=subprocess.run(["/usr/bin/bash","-n"],input=data, + capture_output=True,timeout=12) + if p.returncode: + raise ValueError("invalid Bash syntax "+member.filename) + count+=1 + check("native_syntax",count>=7, + str(count)+" actual Python/Bash source files parsed") + for item in VERSION_FILES: + data=z.read(PREFIX+item) + check("version_"+item.replace("/","_"), + b"v0.6.85" in data, + "original v0.6.85 application source marker") + except Exception as ex: + report["harness_error"]=repr(ex)[:500] + statuses=[x["status"] for x in report["checks"].values()] + report["overall"]="PARTIAL" if statuses and all(x=="PASS" for x in statuses) else "FAIL" + return report + +if __name__=="__main__": + if len(sys.argv)!=3: + raise SystemExit("usage: guest_v0685_package.py /mnt/candidate.zip /mnt/qa-result.json") + try: + result=analyze(Path(sys.argv[1])) + Path(sys.argv[2]).write_text(json.dumps(result,sort_keys=True,indent=2)+"\n") + print(json.dumps({"overall":result["overall"],"checks":result["checks"]},sort_keys=True)) + raise SystemExit(0 if result["overall"]=="PARTIAL" else 2) + except (OSError,ValueError,zipfile.BadZipFile) as ex: + print("CACHYOS_GUEST_BLOCKED",repr(ex),file=sys.stderr) + raise SystemExit(2) From 8c191a1d24468997401836ea14b4e72ef8a7d9c2 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:14:02 +0000 Subject: [PATCH 50/70] Use original-v0.6.85-compatible native guest checker and actual unprivileged install lifecycle, no artificial VERSION file --- .../devnet-fullapp-v0685-github-first.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index 331ca07..778a467 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -106,13 +106,13 @@ jobs: "$base/$name" -o "/tmp/devnet-native-v0685/$name" (cd /tmp/devnet-native-v0685 && sha256sum -c "$name.sha256") | tee native-evidence/iso-sha256.txt root="https://raw.githubusercontent.com/devnet82/devnet-cachyos-repair/d8e4447d19e77a77dc355dc07e72c0c5c3edf741/qa/portfolio-cachyos-vm-v0.1.0" - curl -fLsS "$root/guest_package_qa.py" -o /tmp/devnet-native-v0685/guest_package_qa.py curl -fLsS "$root/qemu_control.py" -o /tmp/devnet-native-v0685/qemu_control.py - python3 -m py_compile /tmp/devnet-native-v0685/guest_package_qa.py /tmp/devnet-native-v0685/qemu_control.py + python3 -m py_compile /tmp/devnet-native-v0685/qemu_control.py + python3 -m py_compile qa/application/guest_v0685_package.py truncate -s 128M /tmp/devnet-native-v0685/control.img mkfs.vfat -F 32 -n DEVNETQA /tmp/devnet-native-v0685/control.img >/dev/null img=/tmp/devnet-native-v0685/control.img - mcopy -i "$img" /tmp/devnet-native-v0685/guest_package_qa.py ::/guest_package_qa.py + mcopy -i "$img" qa/application/guest_v0685_package.py ::/guest_v0685_package.py python3 -m py_compile qa/application/guest_v0685_lifecycle.py mcopy -i "$img" qa/application/guest_v0685_lifecycle.py ::/guest_v0685_lifecycle.py mcopy -i "$img" original-input/candidate.zip ::/candidate.zip @@ -124,9 +124,7 @@ jobs: echo "DEVNET_NATIVE_CACHYOS_ORIGINAL_v0685_QA_START" cat /etc/os-release uname -r - python3 /mnt/guest_package_qa.py /mnt/candidate.zip \ - db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3 \ - 0.6.85 /mnt/qa-result.json /mnt/package-tests.log + python3 /mnt/guest_v0685_package.py /mnt/candidate.zip /mnt/qa-result.json python3 /mnt/guest_v0685_lifecycle.py /mnt/candidate.zip \ /mnt/lifecycle-result.json /mnt/lifecycle-tests.log GUEST @@ -153,7 +151,6 @@ jobs: captured=0 for attempt in $(seq 1 75); do mcopy -o -i "$root/control.img" ::/controller.log native-evidence/controller.log >/dev/null 2>&1 || true - mcopy -o -i "$root/control.img" ::/package-tests.log native-evidence/package-tests.log >/dev/null 2>&1 || true mcopy -o -i "$root/control.img" ::/lifecycle-result.json native-evidence/lifecycle-result.json >/dev/null 2>&1 || true mcopy -o -i "$root/control.img" ::/lifecycle-tests.log native-evidence/lifecycle-tests.log >/dev/null 2>&1 || true if mcopy -o -i "$root/control.img" ::/qa-result.json native-evidence/qa-result.json >/dev/null 2>&1; then @@ -168,13 +165,15 @@ jobs: python3 - <<'PY' import json r=json.load(open("native-evidence/qa-result.json")) - assert r["format"]=="devnet-native-cachyos-vm-qa-v0.1.0" + assert r["schema"]=="devnet-native-cachyos-v0685-package-v1" required=["native_cachyos","control_disk_identity","exact_sha256", - "zip_integrity","package_version","native_syntax"] + "zip_integrity","full_application_layout","native_syntax", + "version_bin_vr-control","version_bin_vr-control-gui", + "version_bin_vr-stack-tray"] for check in required: print("CACHYOS_GUEST",check,r["checks"].get(check)) assert r["checks"][check]["status"]=="PASS", check - assert r["release_decision"]=="BLOCKED" + assert r["release_approved"] is False assert r["overall"]=="PARTIAL" # Installed OS/GUI remain unverified lifecycle=json.load(open("native-evidence/lifecycle-result.json")) required_lifecycle=["fresh_install","exact_install_hashes","reinstall", From 14cdef962a25ddab913d44ffa6de45ad93aa5ef1 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:14:11 +0000 Subject: [PATCH 51/70] Re-run entire GitHub first and native CachyOS guest suite whenever either real guest test changes --- .github/workflows/devnet-fullapp-v0685-github-first.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index 778a467..837c1f2 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -7,6 +7,8 @@ on: - 'qa/application/candidate_checks.py' - 'qa/application/v0685_baseline_inspect.py' - 'qa/application/v0685_installed_lifecycle.py' + - 'qa/application/guest_v0685_package.py' + - 'qa/application/guest_v0685_lifecycle.py' workflow_dispatch: permissions: contents: read From ba14fcfcf1a07d37e77e560519e15b1e7269df1b Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:15:07 +0000 Subject: [PATCH 52/70] Wait for BOTH real native guest ZIP and installed lifecycle receipts before evaluating full current suite --- .github/workflows/devnet-fullapp-v0685-github-first.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index 837c1f2..0315c99 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -155,8 +155,9 @@ jobs: mcopy -o -i "$root/control.img" ::/controller.log native-evidence/controller.log >/dev/null 2>&1 || true mcopy -o -i "$root/control.img" ::/lifecycle-result.json native-evidence/lifecycle-result.json >/dev/null 2>&1 || true mcopy -o -i "$root/control.img" ::/lifecycle-tests.log native-evidence/lifecycle-tests.log >/dev/null 2>&1 || true - if mcopy -o -i "$root/control.img" ::/qa-result.json native-evidence/qa-result.json >/dev/null 2>&1; then - if python3 -c 'import json; json.load(open("native-evidence/qa-result.json"))' 2>/dev/null; then + if mcopy -o -i "$root/control.img" ::/qa-result.json native-evidence/qa-result.json >/dev/null 2>&1 \ + && mcopy -o -i "$root/control.img" ::/lifecycle-result.json native-evidence/lifecycle-result.json >/dev/null 2>&1; then + if python3 -c 'import json; json.load(open("native-evidence/qa-result.json")); json.load(open("native-evidence/lifecycle-result.json"))' 2>/dev/null; then captured=1 break fi From 97f166e14e5a32240457977a7a289f664b71f49c Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:16:38 +0000 Subject: [PATCH 53/70] Add independent candidate-hash and 26-check native/Ubuntu evidence gate without granting full acceptance --- qa/application/v0685_stage_gate.py | 105 +++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 qa/application/v0685_stage_gate.py diff --git a/qa/application/v0685_stage_gate.py b/qa/application/v0685_stage_gate.py new file mode 100644 index 0000000..6a018dc --- /dev/null +++ b/qa/application/v0685_stage_gate.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Independent fail-closed scope-limited v0.6.85 GitHub + native guest evidence check. + +Never turns a genuine CachyOS LIVE ISO run into installed-KDE/host acceptance. +""" +import hashlib +import json +import os +from pathlib import Path +import re +import sys + +EXPECTED="db9af6ff47f3abcf07cf452ddd91e91cbd75a10296c0c6050bb5c612980d9cf3" +UBUNTU={"fresh_install","exact_install_hashes","reinstall", + "reinstalled_file_hashes","preserve_existing_config","uninstall", + "uninstall_removes_app_files","uninstall_preserves_user_config", + "outside_user_data_preserved"} +GUEST_PACKAGE={"native_cachyos","control_disk_identity","exact_sha256", + "zip_integrity","full_application_layout","native_syntax", + "version_bin_vr-control","version_bin_vr-control-gui", + "version_bin_vr-stack-tray"} +GUEST_LIFECYCLE={"fresh_install","exact_install_hashes","reinstall", + "reinstalled_hashes","configuration_preserved","uninstall", + "uninstall_removes_app_files","uninstall_preserves_unrelated_data"} + +class Blocked(Exception): + pass + +def sha(path): + h=hashlib.sha256() + with path.open("rb") as f: + for b in iter(lambda:f.read(1048576),b""): + h.update(b) + return h.hexdigest() + +def must(cond,msg): + if not cond: raise Blocked(msg) + +def verify_stage(doc,ids,overall,scope): + must(isinstance(doc,dict) and doc.get("release_approved") is False,scope+" unsafe approval") + must(doc.get("overall",doc.get("result"))==overall,scope+" incomplete outcome") + checks=doc.get("checks") + must(isinstance(checks,dict) and set(checks)==ids,scope+" incomplete check registry") + for key in sorted(ids): + item=checks[key] + must(isinstance(item,dict) and item.get("status")=="PASS", + scope+" check "+key+" not PASS") + return len(ids) + +def evaluate(candidate,ubuntu,guest,guest_lifecycle,source_sha,run_id): + must(bool(re.fullmatch("[a-f0-9]{40}",source_sha)),"invalid exact GitHub commit") + must(str(run_id).isdigit() and int(run_id)>0,"invalid GitHub run") + must(candidate.is_file() and not candidate.is_symlink() + and sha(candidate)==EXPECTED,"wrong candidate ZIP bytes") + docs=[] + for path in (ubuntu,guest,guest_lifecycle): + must(path.is_file() and not path.is_symlink(),"required original evidence missing") + docs.append(json.loads(path.read_text())) + u,g,l=docs + for idx,doc in enumerate(docs): + must(doc.get("candidate_sha256")==EXPECTED, + "check evidence candidate SHA mismatch: "+str(idx)) + n1=verify_stage(u,UBUNTU,"PASS_LIMITED","ubuntu-disposable") + n2=verify_stage(g,GUEST_PACKAGE,"PARTIAL","real-cachyos-liveiso-package") + n3=verify_stage(l,GUEST_LIFECYCLE,"PASS_LIMITED","real-cachyos-liveiso-lifecycle") + must(g.get("schema")=="devnet-native-cachyos-v0685-package-v1", + "not the correct native guest") + must(l.get("schema")=="devnet-v0685-native-cachyos-lifecycle-v1", + "not the correct native lifecycle") + must(u.get("schema")=="devnet-v0685-disposable-ubuntu-lifecycle-v1", + "not the correct Ubuntu lifecycle") + must(g.get("installed_cachyos_system")=="NOT_TESTED" + and l.get("installed_cachyos_vm")=="NOT_TESTED" + and u.get("real_gui")=="NOT_TESTED", + "missing explicit untested limits") + return {"schema":"devnet-v0685-github-scope-receipt-v1", + "github_sha":source_sha,"github_run_id":str(run_id), + "candidate_sha256":EXPECTED, + "original_candidate":"vr-stack-control-v0.6.85.zip", + "candidate_type":"original-full-app-historical-baseline", + "ubuntu_disposable_install_pass":n1, + "real_cachyos_liveiso_package_pass":n2, + "real_cachyos_liveiso_lifecycle_pass":n3, + "source_evidence_sha256":{str(i):sha(p) for i,p in enumerate( + (ubuntu,guest,guest_lifecycle))}, + "decision":"PASS_LIMITED", + "installed_cachyos_kde_gui":"NOT_TESTED", + "tray_pointer_and_visibility":"NOT_TESTED", + "upgrade_rollback_revision":"NOT_TESTED", + "physical_vr_host":"NOT_TESTED", + "full_master_qa":"BLOCKED", + "release_approved":False} + +if __name__=="__main__": + if len(sys.argv)!=6: + raise SystemExit("usage: v0685_stage_gate.py candidate.zip ubuntu.json guest.json guest_lifecycle.json output.json") + try: + paths=list(map(Path,sys.argv[1:])) + receipt=evaluate(*paths[:4],os.environ["GITHUB_SHA"],os.environ["GITHUB_RUN_ID"]) + must(not paths[4].exists(),"cannot overwrite previously accepted evidence") + paths[4].write_text(json.dumps(receipt,indent=2,sort_keys=True)+"\n") + print("EXACT_GITHUB_AND_LIVE_CACHYOS_STAGE: PASS_LIMITED; MASTER_QA: BLOCKED") + except (OSError,KeyError,ValueError,TypeError,Blocked,json.JSONDecodeError) as ex: + print("EVIDENCE_BLOCKED:",repr(ex),file=sys.stderr) + raise SystemExit(2) From c307f6692cb811a4ab395553dd4cc1b701b1f00d Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:17:01 +0000 Subject: [PATCH 54/70] Add independent 26-check native/Ubuntu evidence verifier, GitHub attestation and separate signature verification without release approval --- .../devnet-fullapp-v0685-github-first.yml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/.github/workflows/devnet-fullapp-v0685-github-first.yml b/.github/workflows/devnet-fullapp-v0685-github-first.yml index 0315c99..6fe0d8f 100644 --- a/.github/workflows/devnet-fullapp-v0685-github-first.yml +++ b/.github/workflows/devnet-fullapp-v0685-github-first.yml @@ -9,6 +9,7 @@ on: - 'qa/application/v0685_installed_lifecycle.py' - 'qa/application/guest_v0685_package.py' - 'qa/application/guest_v0685_lifecycle.py' + - 'qa/application/v0685_stage_gate.py' workflow_dispatch: permissions: contents: read @@ -203,3 +204,89 @@ jobs: run: | echo 'A live CachyOS ISO guest is NOT an installed CachyOS KDE application acceptance environment.' >> "$GITHUB_STEP_SUMMARY" echo 'Install/upgrade/rollback, genuine KDE GUI/tray and physical host hardware remain NOT_TESTED; release BLOCKED.' >> "$GITHUB_STEP_SUMMARY" + sign-v0685-native-scope: + needs: [exact-candidate-baseline, native-cachyos-liveiso] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: v0685-original-github-input-and-structure + path: original-input + - uses: actions/download-artifact@v4 + with: + name: fullapp-v0685-native-cachyos-liveiso-evidence + path: native-evidence + - name: Independently verify every in-scope real executed check + shell: bash + run: | + set -euo pipefail + python3 qa/application/v0685_stage_gate.py \ + original-input/candidate.zip \ + original-input/disposable-lifecycle/lifecycle-receipt.json \ + native-evidence/qa-result.json \ + native-evidence/lifecycle-result.json \ + native-evidence/verified-scope.json + - uses: actions/attest@v4 + with: + subject-path: original-input/candidate.zip + - uses: actions/attest@v4 + with: + subject-path: native-evidence/verified-scope.json + - uses: actions/upload-artifact@v4 + with: + name: v0685-signed-liveiso-scope-receipt + path: native-evidence/verified-scope.json + retention-days: 90 + if-no-files-found: error + - name: Preserve release gate + run: echo 'Signed evidence is ONLY for historical v0.6.85 Ubuntu and CachyOS LIVE ISO. Full Master QA BLOCKED.' >> "$GITHUB_STEP_SUMMARY" + independent-v0685-provenance-verification: + needs: sign-v0685-native-scope + runs-on: ubuntu-24.04 + permissions: + contents: read + attestations: read + steps: + - uses: actions/download-artifact@v4 + with: + name: v0685-original-github-input-and-structure + path: original-input + - uses: actions/download-artifact@v4 + with: + name: v0685-signed-liveiso-scope-receipt + path: signed-evidence + - name: Verify exact original application and source-bound test receipt signatures + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + workflow="$GITHUB_REPOSITORY/.github/workflows/devnet-fullapp-v0685-github-first.yml" + gh attestation verify original-input/candidate.zip --repo "$GITHUB_REPOSITORY" \ + --signer-workflow "$workflow" --source-digest "$GITHUB_SHA" + gh attestation verify signed-evidence/verified-scope.json --repo "$GITHUB_REPOSITORY" \ + --signer-workflow "$workflow" --source-digest "$GITHUB_SHA" + python3 - <<'PY' + import hashlib,json,os,pathlib + d=json.loads(pathlib.Path("signed-evidence/verified-scope.json").read_text()) + z=pathlib.Path("original-input/candidate.zip") + assert hashlib.sha256(z.read_bytes()).hexdigest()==d["candidate_sha256"] + assert d["github_sha"]==os.environ["GITHUB_SHA"] + assert d["github_run_id"]==os.environ["GITHUB_RUN_ID"] + assert (d["ubuntu_disposable_install_pass"], + d["real_cachyos_liveiso_package_pass"], + d["real_cachyos_liveiso_lifecycle_pass"]) == (9,9,8) + assert d["decision"]=="PASS_LIMITED" + assert d["full_master_qa"]=="BLOCKED" + assert d["release_approved"] is False + print("SIGNED_GITHUB_NATIVE_LIVEISO: VERIFIED_SCOPE_LIMITED") + PY + echo 'SIGNED LIVE ISO scope verified independently; FULL MASTER QA BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From 64220c315ca44814c6855878499d3ffb6ceca219 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:18:04 +0000 Subject: [PATCH 55/70] Fix genuine CachyOS VM test HOME ownership bug: allow unprivileged app to create .config/systemd --- qa/application/guest_v0685_lifecycle.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qa/application/guest_v0685_lifecycle.py b/qa/application/guest_v0685_lifecycle.py index bb1c433..7468ed6 100644 --- a/qa/application/guest_v0685_lifecycle.py +++ b/qa/application/guest_v0685_lifecycle.py @@ -90,7 +90,13 @@ def verify(check, action): os.chown(path,nobody.pw_uid,nobody.pw_gid) config=home/".config"/"vr-stack"/"stack.conf" config.parent.mkdir(parents=True) + # The parent .config was created AFTER the earlier ownership + # walk. Leaving it root-owned would incorrectly block a genuine + # unprivileged installer from creating ~/.config/systemd/user. + os.chown(config.parent.parent,nobody.pw_uid,nobody.pw_gid) os.chown(config.parent,nobody.pw_uid,nobody.pw_gid) + if config.parent.parent.stat().st_uid != nobody.pw_uid: + raise RuntimeError("QA fixture config HOME ownership incorrect") config.write_text("ORIGINAL_GUEST_CONFIG=preserve\n") os.chown(config,nobody.pw_uid,nobody.pw_gid) unrelated=home/"KEEP_UNRELATED" From bb6aa75f0122a7f5ce2d30354da42c53346a4ad3 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 09:22:17 +0000 Subject: [PATCH 56/70] Prepare pinned exact-ZIP real CachyOS QEMU GitHub-first unit QA for router v0.1.0; fail closed without binary --- .../devnet-router-v010-native-cachyos.yml | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/devnet-router-v010-native-cachyos.yml diff --git a/.github/workflows/devnet-router-v010-native-cachyos.yml b/.github/workflows/devnet-router-v010-native-cachyos.yml new file mode 100644 index 0000000..997048a --- /dev/null +++ b/.github/workflows/devnet-router-v010-native-cachyos.yml @@ -0,0 +1,231 @@ +name: Devnet router v0.1.0 exact ZIP native CachyOS — GitHub FIRST + +on: + push: + branches: [qa/registered-app-executor-fix-v0.1.0] + paths: + - 'qa/router-stage-v0.1.0/candidate.zip' + workflow_dispatch: + +permissions: + contents: read + +jobs: + verify-exact-zip: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Verify exact unchanged router archive, safety, file identities + shell: bash + run: | + set -euo pipefail + candidate=qa/router-stage-v0.1.0/candidate.zip + test -f "$candidate" + echo '719f0d8a44d6431993654975d49555db19b7c5885c243f07fe3bcea6a09463cd qa/router-stage-v0.1.0/candidate.zip' | sha256sum -c - + python3 - <<'PY' + import hashlib,io,pathlib,stat,zipfile + p=pathlib.Path('qa/router-stage-v0.1.0/candidate.zip') + with zipfile.ZipFile(p) as z: + entries=z.infolist() + assert len(entries)==5 + expected={'devnet-router-stage-v0.1.0/'+x for x in + ('README.md','LOCAL_DEVELOPMENT_TEST_RESULTS.txt', + 'SHA256SUMS.txt','orchestrator.py','test_orchestrator.py')} + assert {x.filename for x in entries}==expected + assert z.testzip() is None + assert all(not x.is_dir() and stat.S_IFMT(x.external_attr >> 16) + in (0,stat.S_IFREG) for x in entries) + hashes=dict(line.split() for line in + z.read('devnet-router-stage-v0.1.0/SHA256SUMS.txt').decode().splitlines()) + for name,expected_sha in hashes.items(): + data=z.read('devnet-router-stage-v0.1.0/'+name) + assert hashlib.sha256(data).hexdigest()==expected_sha,name + for name in ('orchestrator.py','test_orchestrator.py'): + compile(z.read('devnet-router-stage-v0.1.0/'+name),name,'exec') + print('EXACT_ROUTER_ZIP_SAFE: PASS; NATIVE_VM: NOT_TESTED') + PY + - uses: actions/upload-artifact@v4 + with: + name: router-v010-exact-original-zip + path: qa/router-stage-v0.1.0/candidate.zip + if-no-files-found: error + retention-days: 30 + + native-cachyos-unit-tests: + needs: verify-exact-zip + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: router-v010-exact-original-zip + path: input + - name: Confirm original candidate + virtualisation capabilities + shell: bash + run: | + set -euo pipefail + echo '719f0d8a44d6431993654975d49555db19b7c5885c243f07fe3bcea6a09463cd input/candidate.zip' | sha256sum -c - + test -c /dev/kvm + if ! test -r /dev/kvm -a -w /dev/kvm; then + sudo chown "$(id -u):$(id -g)" /dev/kvm + sudo chmod 0600 /dev/kvm + fi + test -r /dev/kvm -a -w /dev/kvm + - name: Prepare pinned genuine CachyOS guest (no network or host directory mounts) + shell: bash + run: | + set -euo pipefail + sudo apt-get update -qq + sudo apt-get install -qq -y --no-install-recommends qemu-system-x86 qemu-utils mtools dosfstools + root=/tmp/devnet-router-native + mkdir -p "$root" evidence + base=https://iso.cachyos.org/desktop/260809 + name=cachyos-desktop-linux-260809.iso + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 "$base/$name.sha256" -o "$root/$name.sha256" + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 "$base/$name" -o "$root/$name" + (cd "$root" && sha256sum -c "$name.sha256") | tee evidence/iso-checksum.log + pinned=https://raw.githubusercontent.com/devnet82/devnet-cachyos-repair/d8e4447d19e77a77dc355dc07e72c0c5c3edf741/qa/portfolio-cachyos-vm-v0.1.0 + curl -fLsS "$pinned/qemu_control.py" -o "$root/qemu_control.py" + python3 -m py_compile "$root/qemu_control.py" + truncate -s 128M "$root/control.img" + mkfs.vfat -F 32 -n DEVNETQA "$root/control.img" >/dev/null + mcopy -i "$root/control.img" input/candidate.zip ::/candidate.zip + cat > "$root/guest-test.sh" <<'GUEST' + #!/usr/bin/env bash + set -Eeuo pipefail + exec > /mnt/controller.log 2>&1 + trap 'rc=$?; echo ROUTER_GUEST_EXIT=$rc; sync' EXIT + grep -Eq '^ID=cachyos$' /etc/os-release + echo "REAL_CACHYOS_GUEST=$(uname -r)" + python3 - <<'PY' + import hashlib,pathlib,zipfile + p=pathlib.Path('/mnt/candidate.zip') + assert hashlib.sha256(p.read_bytes()).hexdigest()=='719f0d8a44d6431993654975d49555db19b7c5885c243f07fe3bcea6a09463cd' + with zipfile.ZipFile(p) as z: + assert z.testzip() is None + z.extractall('/tmp/router-tests') + print('REAL_CACHYOS_EXACT_ZIP_PASS') + PY + cd /tmp/router-tests/devnet-router-stage-v0.1.0 + python3 -m unittest -v test_orchestrator > /mnt/unit-tests.log 2>&1 + cat /mnt/unit-tests.log + grep -q 'Ran 23 tests' /mnt/unit-tests.log + grep -qx 'OK' /mnt/unit-tests.log + python3 - <<'PY' + import json,pathlib + result={'schema':'devnet-v010-native-cachyos-units-v1', + 'candidate_sha256':'719f0d8a44d6431993654975d49555db19b7c5885c243f07fe3bcea6a09463cd', + 'native_cachyos':True,'unit_tests_pass':23, + 'ollama_live_model_test':'NOT_TESTED', + 'application_creation_and_repair':'NOT_TESTED', + 'master_qa':'BLOCKED','release_approved':False} + pathlib.Path('/mnt/native-result.json').write_text(json.dumps(result,sort_keys=True)+'\n') + print('CACHYOS_NATIVE_ROUTER_23_23: PASS_LIMITED') + PY + GUEST + bash -n "$root/guest-test.sh" + mcopy -i "$root/control.img" "$root/guest-test.sh" ::/install-guest.sh + - name: Boot real CachyOS live ISO and execute full 23-test suite + shell: bash + run: | + set -euo pipefail + root=/tmp/devnet-router-native + qemu-system-x86_64 -machine q35,accel=kvm -cpu host -m 4096 -smp 2 \ + -drive file="$root/cachyos-desktop-linux-260809.iso",media=cdrom,if=ide \ + -drive if=none,id=ctl,file="$root/control.img",format=raw \ + -device virtio-blk-pci,drive=ctl,serial=DEVNETCONTROL \ + -nic none -boot order=d -vga std -display none \ + -serial file:evidence/serial.log \ + -monitor unix:"$root/qemu-monitor.sock",server=on,wait=off \ + -daemonize -pidfile "$root/qemu.pid" + sleep 110 + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" framebuffer --image evidence/guest-before.ppm + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" begin-install + captured=0 + for attempt in $(seq 1 75); do + mcopy -o -i "$root/control.img" ::/controller.log evidence/controller.log >/dev/null 2>&1 || true + mcopy -o -i "$root/control.img" ::/unit-tests.log evidence/unit-tests.log >/dev/null 2>&1 || true + if mcopy -o -i "$root/control.img" ::/native-result.json evidence/native-result.json >/dev/null 2>&1; then + if python3 -m json.tool evidence/native-result.json >/dev/null 2>&1; then + captured=1; break + fi + fi + sleep 8 + done + test "$captured" -eq 1 + python3 - <<'PY' + import json + r=json.load(open('evidence/native-result.json')) + assert r['schema']=='devnet-v010-native-cachyos-units-v1' + assert r['candidate_sha256']=='719f0d8a44d6431993654975d49555db19b7c5885c243f07fe3bcea6a09463cd' + assert r['native_cachyos'] is True + assert r['unit_tests_pass']==23 + assert r['master_qa']=='BLOCKED' and r['release_approved'] is False + print('REAL_GITHUB_CACHYOS_ROUTER_UNITS: 23/23 PASS_LIMITED; FULL MASTER QA BLOCKED') + PY + - uses: actions/upload-artifact@v4 + if: always() + with: + name: router-v010-genuine-cachyos-evidence + path: evidence/ + retention-days: 30 + if-no-files-found: warn + + independent-scope-gate: + needs: [verify-exact-zip, native-cachyos-unit-tests] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + attestations: write + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: router-v010-exact-original-zip + path: input + - uses: actions/download-artifact@v4 + with: + name: router-v010-genuine-cachyos-evidence + path: evidence + - name: Independently verify candidate and guest result scope + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + import hashlib,json,pathlib + zipfile=pathlib.Path('input/candidate.zip') + h=hashlib.sha256(zipfile.read_bytes()).hexdigest() + assert h=='719f0d8a44d6431993654975d49555db19b7c5885c243f07fe3bcea6a09463cd' + r=json.loads(pathlib.Path('evidence/native-result.json').read_text()) + assert r['native_cachyos'] and r['unit_tests_pass']==23 + assert r['candidate_sha256']==h + assert r['ollama_live_model_test']=='NOT_TESTED' + assert r['application_creation_and_repair']=='NOT_TESTED' + assert r['master_qa']=='BLOCKED' and r['release_approved'] is False + assert 'Ran 23 tests' in pathlib.Path('evidence/unit-tests.log').read_text() + pathlib.Path('scope-receipt.json').write_text(json.dumps({ + 'schema':'devnet-native-router-scope-v1', + 'original_sha256':h,'native_cachyos_tests_pass':23, + 'scope':'UNIT_TESTS_ONLY_WITH_MOCK_OLLAMA', + 'full_master_qa':'BLOCKED','release_approved':False},sort_keys=True)+'\n') + print('INDEPENDENT_NATIVE_CACHYOS_SCOPE_PASS_LIMITED') + PY + - uses: actions/attest@v4 + with: + subject-path: input/candidate.zip + - uses: actions/attest@v4 + with: + subject-path: scope-receipt.json + - uses: actions/upload-artifact@v4 + with: + name: router-v010-signed-limited-scope + path: scope-receipt.json + if-no-files-found: error + - run: echo 'Signed scope is native unit tests with mocked Ollama. NO host model/integration/app repair. Full Master QA BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From 6984d4e15f8eba7e299fc8e35024246fed36bd8d Mon Sep 17 00:00:00 2001 From: David Coates <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 09:40:52 +0000 Subject: [PATCH 57/70] Add candidate ZIP for GitHub-first validation --- qa/router-stage-v0.1.0/candidate.zip | Bin 0 -> 9790 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 qa/router-stage-v0.1.0/candidate.zip diff --git a/qa/router-stage-v0.1.0/candidate.zip b/qa/router-stage-v0.1.0/candidate.zip new file mode 100644 index 0000000000000000000000000000000000000000..b4645aae2f93bf43dab1ad8af918090cc3218584 GIT binary patch literal 9790 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 literal 0 HcmV?d00001 From d2cbd9d6db9939df7f4ef1a317fb0e68fb3034e9 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 09:51:43 +0000 Subject: [PATCH 58/70] Fix SHA256SUMS digest/filename parsing and trigger unchanged candidate Github-native QA --- .github/workflows/devnet-router-v010-native-cachyos.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/devnet-router-v010-native-cachyos.yml b/.github/workflows/devnet-router-v010-native-cachyos.yml index 997048a..11906f8 100644 --- a/.github/workflows/devnet-router-v010-native-cachyos.yml +++ b/.github/workflows/devnet-router-v010-native-cachyos.yml @@ -5,6 +5,7 @@ on: branches: [qa/registered-app-executor-fix-v0.1.0] paths: - 'qa/router-stage-v0.1.0/candidate.zip' + - '.github/workflows/devnet-router-v010-native-cachyos.yml' workflow_dispatch: permissions: @@ -38,8 +39,9 @@ jobs: assert z.testzip() is None assert all(not x.is_dir() and stat.S_IFMT(x.external_attr >> 16) in (0,stat.S_IFREG) for x in entries) - hashes=dict(line.split() for line in - z.read('devnet-router-stage-v0.1.0/SHA256SUMS.txt').decode().splitlines()) + hashes={name: digest for digest,name in + (line.split() for line in + z.read('devnet-router-stage-v0.1.0/SHA256SUMS.txt').decode().splitlines())} for name,expected_sha in hashes.items(): data=z.read('devnet-router-stage-v0.1.0/'+name) assert hashlib.sha256(data).hexdigest()==expected_sha,name From b1985b46dca063439f680b8b06d0a3696be39035 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:29:25 +0000 Subject: [PATCH 59/70] QA only: stage privacy-reviewed Devnet Safe AI v0.1.1 exact candidate (base64 ZIP, no release) --- qa/devnet-safe-ai-v0.1.1/candidate.zip.b64 | 1 + 1 file changed, 1 insertion(+) create mode 100644 qa/devnet-safe-ai-v0.1.1/candidate.zip.b64 diff --git a/qa/devnet-safe-ai-v0.1.1/candidate.zip.b64 b/qa/devnet-safe-ai-v0.1.1/candidate.zip.b64 new file mode 100644 index 0000000..8ab6061 --- /dev/null +++ b/qa/devnet-safe-ai-v0.1.1/candidate.zip.b64 @@ -0,0 +1 @@ +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 From c613041104bc8434cbdec544b8425d1395f9b616 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:31:30 +0000 Subject: [PATCH 60/70] CI: run exact Devnet Safe AI v0.1.1 in genuine GitHub CachyOS QEMU, independent fail-closed scope gate --- .../devnet-safe-ai-v011-native-cachyos.yml | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 .github/workflows/devnet-safe-ai-v011-native-cachyos.yml diff --git a/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml b/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml new file mode 100644 index 0000000..8213ca1 --- /dev/null +++ b/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml @@ -0,0 +1,226 @@ +name: Devnet Safe AI v0.1.1 exact archive — GitHub-first native CachyOS QA + +on: + push: + branches: [qa/registered-app-executor-fix-v0.1.0] + paths: + - 'qa/devnet-safe-ai-v0.1.1/**' + - '.github/workflows/devnet-safe-ai-v011-native-cachyos.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + verify-exact-private-safe-package: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Decode exact original candidate; integrity, scope and privacy preflight + shell: bash + run: | + set -Eeuo pipefail + mkdir -p input + base64 -d qa/devnet-safe-ai-v0.1.1/candidate.zip.b64 > input/candidate.zip + echo 'cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1 input/candidate.zip' | sha256sum -c - + python3 - <<'PY' + import hashlib,io,pathlib,re,stat,zipfile + p=pathlib.Path('input/candidate.zip') + expected={'devnet-safe-ai-v0.1.1.py','test_devnet_safe_ai_v0.1.1.py','SHA256SUMS','DEVNET-SAFE-AI-v0.1.1-README.md'} + with zipfile.ZipFile(p) as z: + entries=z.infolist() + assert len(entries)==4 and {i.filename for i in entries}==expected + assert z.testzip() is None + assert all(not i.is_dir() and stat.S_IFMT(i.external_attr >> 16) in (0,stat.S_IFREG) for i in entries) + sums=z.read('SHA256SUMS').decode().splitlines() + assert len(sums)==3 + for line in sums: + digest,name=line.split() + assert name in expected-{'SHA256SUMS'} + assert hashlib.sha256(z.read(name)).hexdigest()==digest,name + for name in ('devnet-safe-ai-v0.1.1.py','test_devnet_safe_ai_v0.1.1.py'): + compile(z.read(name),name,'exec') + all_text='\n'.join(z.read(name).decode() for name in expected if name!='SHA256SUMS') + assert not re.search(r'/home/(?!agent\b)[a-zA-Z0-9_.-]+',all_text) + assert not re.search(r'-----BEGIN [^-]*PRIVATE KEY|github_pat_[A-Za-z0-9_]{15,}|gh[psu]_[A-Za-z0-9]{15,}',all_text) + print('EXACT_ARCHIVE_SHA256_PASS; PRIVACY_PREFLIGHT_PASS; GITHUB_VM_NOT_YET_TESTED') + PY + - uses: actions/upload-artifact@v4 + with: + name: devnet-safe-v011-exact-candidate + path: input/candidate.zip + if-no-files-found: error + retention-days: 30 + + real-cachyos-vm-30-regressions: + needs: verify-exact-private-safe-package + runs-on: ubuntu-24.04 + timeout-minutes: 35 + steps: + - uses: actions/download-artifact@v4 + with: + name: devnet-safe-v011-exact-candidate + path: input + - name: KVM preflight and genuine CachyOS live guest preparation + shell: bash + run: | + set -Eeuo pipefail + echo 'cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1 input/candidate.zip' | sha256sum -c - + test -c /dev/kvm + if ! test -r /dev/kvm -a -w /dev/kvm; then + sudo chown "$(id -u):$(id -g)" /dev/kvm + sudo chmod 0600 /dev/kvm + fi + test -r /dev/kvm -a -w /dev/kvm + sudo apt-get update -qq + sudo apt-get install -qq -y --no-install-recommends qemu-system-x86 qemu-utils mtools dosfstools + root=/tmp/devnet-safe-v011-native + mkdir -p "$root" evidence + base=https://iso.cachyos.org/desktop/260809 + name=cachyos-desktop-linux-260809.iso + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 "$base/$name.sha256" -o "$root/$name.sha256" + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 "$base/$name" -o "$root/$name" + (cd "$root" && sha256sum -c "$name.sha256") | tee evidence/iso-checksum.log + pinned=https://raw.githubusercontent.com/devnet82/devnet-cachyos-repair/d8e4447d19e77a77dc355dc07e72c0c5c3edf741/qa/portfolio-cachyos-vm-v0.1.0 + curl -fLsS "$pinned/qemu_control.py" -o "$root/qemu_control.py" + python3 -m py_compile "$root/qemu_control.py" + truncate -s 128M "$root/control.img" + mkfs.vfat -F 32 -n DEVNETQA "$root/control.img" >/dev/null + mcopy -i "$root/control.img" input/candidate.zip ::/candidate.zip + cat > "$root/guest-test.sh" <<'GUEST' + #!/usr/bin/env bash + set -Eeuo pipefail + exec > /mnt/controller.log 2>&1 + trap 'rc=$?; echo SAFEAI_GUEST_EXIT=$rc; sync' EXIT + grep -Eq '^ID=cachyos$' /etc/os-release + echo "REAL_CACHYOS_GUEST_KERNEL=$(uname -r)" + python3 - <<'PY' + import hashlib,pathlib,zipfile + p=pathlib.Path('/mnt/candidate.zip') + assert hashlib.sha256(p.read_bytes()).hexdigest()=='cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1' + with zipfile.ZipFile(p) as z: + assert z.testzip() is None + z.extractall('/tmp/devnet-safe-ai-v011') + print('REAL_CACHYOS_EXACT_ZIP_PASS') + PY + cd /tmp/devnet-safe-ai-v011 + python3 test_devnet_safe_ai_v0.1.1.py > /mnt/unit-tests.log 2>&1 + cat /mnt/unit-tests.log + grep -q 'Ran 30 tests' /mnt/unit-tests.log + grep -qx 'OK' /mnt/unit-tests.log + python3 - <<'PY' + import json,pathlib + result={'schema':'devnet-safe-ai-v011-github-cachyos-unit-scope-v1', + 'candidate_sha256':'cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1', + 'native_cachyos':True,'unit_tests_pass':30,'unit_tests_fail':0, + 'ollama_host_live':'NOT_TESTED','opencode_full_runtime':'NOT_TESTED', + 'vm_host_master_qa':'NOT_TESTED','install_upgrade_rollback':'NOT_TESTED', + 'master_qa':'BLOCKED','release_approved':False} + pathlib.Path('/mnt/native-result.json').write_text(json.dumps(result,sort_keys=True)+'\n') + print('REAL_GITHUB_NATIVE_CACHYOS_30_REGRESSIONS_PASS_LIMITED') + PY + GUEST + bash -n "$root/guest-test.sh" + mcopy -i "$root/control.img" "$root/guest-test.sh" ::/install-guest.sh + - name: Boot real CachyOS guest with isolated networking and execute fresh 30-test suite + shell: bash + run: | + set -Eeuo pipefail + root=/tmp/devnet-safe-v011-native + qemu-system-x86_64 -machine q35,accel=kvm -cpu host -m 4096 -smp 2 \ + -drive file="$root/cachyos-desktop-linux-260809.iso",media=cdrom,if=ide \ + -drive if=none,id=ctl,file="$root/control.img",format=raw \ + -device virtio-blk-pci,drive=ctl,serial=DEVNETCONTROL \ + -nic none -boot order=d -vga std -display none \ + -serial file:evidence/serial.log \ + -monitor unix:"$root/qemu-monitor.sock",server=on,wait=off \ + -daemonize -pidfile "$root/qemu.pid" + sleep 110 + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" framebuffer --image evidence/guest-before.ppm + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" begin-install + captured=0 + for attempt in $(seq 1 75); do + mcopy -o -i "$root/control.img" ::/controller.log evidence/controller.log >/dev/null 2>&1 || true + mcopy -o -i "$root/control.img" ::/unit-tests.log evidence/unit-tests.log >/dev/null 2>&1 || true + if mcopy -o -i "$root/control.img" ::/native-result.json evidence/native-result.json >/dev/null 2>&1; then + if python3 -m json.tool evidence/native-result.json >/dev/null 2>&1; then + captured=1; break + fi + fi + sleep 8 + done + test "$captured" -eq 1 + python3 - <<'PY' + import json + r=json.load(open('evidence/native-result.json')) + assert r['schema']=='devnet-safe-ai-v011-github-cachyos-unit-scope-v1' + assert r['candidate_sha256']=='cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1' + assert r['native_cachyos'] is True and r['unit_tests_pass']==30 + assert r['master_qa']=='BLOCKED' and r['release_approved'] is False + assert 'Ran 30 tests' in open('evidence/unit-tests.log').read() + print('GITHUB_CACHYOS_30_OF_30_TESTS_PASS_LIMITED_FULL_MASTER_QA_BLOCKED') + PY + - uses: actions/upload-artifact@v4 + if: always() + with: + name: devnet-safe-v011-real-cachyos-evidence + path: evidence/ + retention-days: 30 + if-no-files-found: warn + + independent-evidence-and-scope-gate: + needs: [verify-exact-private-safe-package, real-cachyos-vm-30-regressions] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + attestations: write + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: devnet-safe-v011-exact-candidate + path: input + - uses: actions/download-artifact@v4 + with: + name: devnet-safe-v011-real-cachyos-evidence + path: evidence + - name: Verify independent guest evidence; fail closed on missing scope + shell: bash + run: | + set -Eeuo pipefail + python3 - <<'PY' + import hashlib,json,pathlib + p=pathlib.Path('input/candidate.zip') + h=hashlib.sha256(p.read_bytes()).hexdigest() + assert h=='cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1' + r=json.loads(pathlib.Path('evidence/native-result.json').read_text()) + assert r['candidate_sha256']==h and r['native_cachyos'] is True + assert r['unit_tests_pass']==30 and r['unit_tests_fail']==0 + assert r['ollama_host_live']=='NOT_TESTED' and r['opencode_full_runtime']=='NOT_TESTED' + assert r['install_upgrade_rollback']=='NOT_TESTED' + assert r['master_qa']=='BLOCKED' and r['release_approved'] is False + log=pathlib.Path('evidence/unit-tests.log').read_text() + assert 'Ran 30 tests' in log and '\nOK\n' in log + assert 'REAL_CACHYOS_EXACT_ZIP_PASS' in pathlib.Path('evidence/controller.log').read_text() + pathlib.Path('scope-receipt.json').write_text(json.dumps({ + 'schema':'devnet-safe-v011-github-scope-v1','archive_sha256':h, + 'native_cachyos_unit_tests_pass':30,'full_master_qa':'BLOCKED', + 'runtime_gui_host_integration':'NOT_TESTED', 'release_approved':False},sort_keys=True)+'\n') + print('INDEPENDENT_SCOPE_GATE_PASS_LIMITED; NO_RELEASE') + PY + - uses: actions/attest@v4 + with: + subject-path: input/candidate.zip + - uses: actions/attest@v4 + with: + subject-path: scope-receipt.json + - uses: actions/upload-artifact@v4 + with: + name: devnet-safe-v011-signed-github-scope + path: scope-receipt.json + if-no-files-found: error + - run: echo '30/30 tests only in genuine GitHub-hosted CachyOS VM. Host install/upgrade/rollback, security runtime, and Master QA are NOT TESTED; release BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From f0b4100c44258bd66cf3289a66538e8fe344298c Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:33:35 +0000 Subject: [PATCH 61/70] QA traceability: publish hosted run ID on draft PR; preserve candidate and fail-closed gates --- .../workflows/devnet-safe-ai-v011-native-cachyos.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml b/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml index 8213ca1..a39f0f1 100644 --- a/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml +++ b/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml @@ -15,7 +15,19 @@ jobs: verify-exact-private-safe-package: runs-on: ubuntu-24.04 timeout-minutes: 10 + permissions: + contents: read + issues: write steps: + - name: Record exact QA run ID on existing draft PR + uses: actions/github-script@v7 + with: + script: | + const url = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, repo: context.repo.repo, issue_number: 3, + body: `DEVNET SAFE AI v0.1.1 GitHub-first QA run started (NOT a PASS): ${url}\\nHead commit: ${context.sha}\\nCandidate SHA256: cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1\\nFull Master QA remains BLOCKED.` + }); - uses: actions/checkout@v5 with: persist-credentials: false From f259a3784951b13b23ca880f894df3583078d7e7 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:36:00 +0000 Subject: [PATCH 62/70] Fix CI: remove run-ID comment step denied by restricted GitHub token; use read-only Actions run evidence --- .../workflows/devnet-safe-ai-v011-native-cachyos.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml b/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml index a39f0f1..8213ca1 100644 --- a/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml +++ b/.github/workflows/devnet-safe-ai-v011-native-cachyos.yml @@ -15,19 +15,7 @@ jobs: verify-exact-private-safe-package: runs-on: ubuntu-24.04 timeout-minutes: 10 - permissions: - contents: read - issues: write steps: - - name: Record exact QA run ID on existing draft PR - uses: actions/github-script@v7 - with: - script: | - const url = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - await github.rest.issues.createComment({ - owner: context.repo.owner, repo: context.repo.repo, issue_number: 3, - body: `DEVNET SAFE AI v0.1.1 GitHub-first QA run started (NOT a PASS): ${url}\\nHead commit: ${context.sha}\\nCandidate SHA256: cc9d7a183963a923c5f1b9bd4aa30d4f11156bd8ef81e4a2bc3f5b2907a9aba1\\nFull Master QA remains BLOCKED.` - }); - uses: actions/checkout@v5 with: persist-credentials: false From 3f8b7b2eee031d92b31d534e189fd245c1b7f24a Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:49:32 +0000 Subject: [PATCH 63/70] QA-blocked Devnet Safe AI v0.1.2 exact candidate; fixed overbroad home bind assertion --- qa/devnet-safe-ai-v0.1.2/candidate.zip.b64 | 1 + 1 file changed, 1 insertion(+) create mode 100644 qa/devnet-safe-ai-v0.1.2/candidate.zip.b64 diff --git a/qa/devnet-safe-ai-v0.1.2/candidate.zip.b64 b/qa/devnet-safe-ai-v0.1.2/candidate.zip.b64 new file mode 100644 index 0000000..5158704 --- /dev/null +++ b/qa/devnet-safe-ai-v0.1.2/candidate.zip.b64 @@ -0,0 +1 @@ +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 From f1e2a7389cd9074871b3866c2e2f5de572dbe503 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:51:16 +0000 Subject: [PATCH 64/70] GitHub-first CachyOS v0.1.2 QA: exact artifact, full fresh unit tests, independent scope gate --- .../devnet-safe-ai-v012-native-cachyos.yml | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 .github/workflows/devnet-safe-ai-v012-native-cachyos.yml diff --git a/.github/workflows/devnet-safe-ai-v012-native-cachyos.yml b/.github/workflows/devnet-safe-ai-v012-native-cachyos.yml new file mode 100644 index 0000000..eb89e95 --- /dev/null +++ b/.github/workflows/devnet-safe-ai-v012-native-cachyos.yml @@ -0,0 +1,226 @@ +name: Devnet Safe AI v0.1.2 exact archive — GitHub-first native CachyOS QA + +on: + push: + branches: [qa/registered-app-executor-fix-v0.1.0] + paths: + - 'qa/devnet-safe-ai-v0.1.2/**' + - '.github/workflows/devnet-safe-ai-v012-native-cachyos.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + verify-exact-private-safe-package: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Decode exact original candidate; integrity, scope and privacy preflight + shell: bash + run: | + set -Eeuo pipefail + mkdir -p input + base64 -d qa/devnet-safe-ai-v0.1.2/candidate.zip.b64 > input/candidate.zip + echo 'fd227dfc54e26e10198ebc0623088aea7884fa62840b7999b32d8a2d0618a8ae input/candidate.zip' | sha256sum -c - + python3 - <<'PY' + import hashlib,io,pathlib,re,stat,zipfile + p=pathlib.Path('input/candidate.zip') + expected={'devnet-safe-ai-v0.1.2.py','test_devnet_safe_ai_v0.1.2.py','SHA256SUMS','DEVNET-SAFE-AI-v0.1.2-README.md'} + with zipfile.ZipFile(p) as z: + entries=z.infolist() + assert len(entries)==4 and {i.filename for i in entries}==expected + assert z.testzip() is None + assert all(not i.is_dir() and stat.S_IFMT(i.external_attr >> 16) in (0,stat.S_IFREG) for i in entries) + sums=z.read('SHA256SUMS').decode().splitlines() + assert len(sums)==3 + for line in sums: + digest,name=line.split() + assert name in expected-{'SHA256SUMS'} + assert hashlib.sha256(z.read(name)).hexdigest()==digest,name + for name in ('devnet-safe-ai-v0.1.2.py','test_devnet_safe_ai_v0.1.2.py'): + compile(z.read(name),name,'exec') + all_text='\n'.join(z.read(name).decode() for name in expected if name!='SHA256SUMS') + assert not re.search(r'/home/(?!agent\b)[a-zA-Z0-9_.-]+',all_text) + assert not re.search(r'-----BEGIN [^-]*PRIVATE KEY|github_pat_[A-Za-z0-9_]{15,}|gh[psu]_[A-Za-z0-9]{15,}',all_text) + print('EXACT_ARCHIVE_SHA256_PASS; PRIVACY_PREFLIGHT_PASS; GITHUB_VM_NOT_YET_TESTED') + PY + - uses: actions/upload-artifact@v4 + with: + name: devnet-safe-v012-exact-candidate + path: input/candidate.zip + if-no-files-found: error + retention-days: 30 + + real-cachyos-vm-30-regressions: + needs: verify-exact-private-safe-package + runs-on: ubuntu-24.04 + timeout-minutes: 35 + steps: + - uses: actions/download-artifact@v4 + with: + name: devnet-safe-v012-exact-candidate + path: input + - name: KVM preflight and genuine CachyOS live guest preparation + shell: bash + run: | + set -Eeuo pipefail + echo 'fd227dfc54e26e10198ebc0623088aea7884fa62840b7999b32d8a2d0618a8ae input/candidate.zip' | sha256sum -c - + test -c /dev/kvm + if ! test -r /dev/kvm -a -w /dev/kvm; then + sudo chown "$(id -u):$(id -g)" /dev/kvm + sudo chmod 0600 /dev/kvm + fi + test -r /dev/kvm -a -w /dev/kvm + sudo apt-get update -qq + sudo apt-get install -qq -y --no-install-recommends qemu-system-x86 qemu-utils mtools dosfstools + root=/tmp/devnet-safe-v012-native + mkdir -p "$root" evidence + base=https://iso.cachyos.org/desktop/260809 + name=cachyos-desktop-linux-260809.iso + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 "$base/$name.sha256" -o "$root/$name.sha256" + curl -fLsS --retry 3 --connect-timeout 20 --max-time 1200 "$base/$name" -o "$root/$name" + (cd "$root" && sha256sum -c "$name.sha256") | tee evidence/iso-checksum.log + pinned=https://raw.githubusercontent.com/devnet82/devnet-cachyos-repair/d8e4447d19e77a77dc355dc07e72c0c5c3edf741/qa/portfolio-cachyos-vm-v0.1.0 + curl -fLsS "$pinned/qemu_control.py" -o "$root/qemu_control.py" + python3 -m py_compile "$root/qemu_control.py" + truncate -s 128M "$root/control.img" + mkfs.vfat -F 32 -n DEVNETQA "$root/control.img" >/dev/null + mcopy -i "$root/control.img" input/candidate.zip ::/candidate.zip + cat > "$root/guest-test.sh" <<'GUEST' + #!/usr/bin/env bash + set -Eeuo pipefail + exec > /mnt/controller.log 2>&1 + trap 'rc=$?; echo SAFEAI_GUEST_EXIT=$rc; sync' EXIT + grep -Eq '^ID=cachyos$' /etc/os-release + echo "REAL_CACHYOS_GUEST_KERNEL=$(uname -r)" + python3 - <<'PY' + import hashlib,pathlib,zipfile + p=pathlib.Path('/mnt/candidate.zip') + assert hashlib.sha256(p.read_bytes()).hexdigest()=='fd227dfc54e26e10198ebc0623088aea7884fa62840b7999b32d8a2d0618a8ae' + with zipfile.ZipFile(p) as z: + assert z.testzip() is None + z.extractall('/tmp/devnet-safe-ai-v012') + print('REAL_CACHYOS_EXACT_ZIP_PASS') + PY + cd /tmp/devnet-safe-ai-v012 + python3 test_devnet_safe_ai_v0.1.2.py > /mnt/unit-tests.log 2>&1 + cat /mnt/unit-tests.log + grep -q 'Ran 30 tests' /mnt/unit-tests.log + grep -qx 'OK' /mnt/unit-tests.log + python3 - <<'PY' + import json,pathlib + result={'schema':'devnet-safe-ai-v012-github-cachyos-unit-scope-v1', + 'candidate_sha256':'fd227dfc54e26e10198ebc0623088aea7884fa62840b7999b32d8a2d0618a8ae', + 'native_cachyos':True,'unit_tests_pass':30,'unit_tests_fail':0, + 'ollama_host_live':'NOT_TESTED','opencode_full_runtime':'NOT_TESTED', + 'vm_host_master_qa':'NOT_TESTED','install_upgrade_rollback':'NOT_TESTED', + 'master_qa':'BLOCKED','release_approved':False} + pathlib.Path('/mnt/native-result.json').write_text(json.dumps(result,sort_keys=True)+'\n') + print('REAL_GITHUB_NATIVE_CACHYOS_30_REGRESSIONS_PASS_LIMITED') + PY + GUEST + bash -n "$root/guest-test.sh" + mcopy -i "$root/control.img" "$root/guest-test.sh" ::/install-guest.sh + - name: Boot real CachyOS guest with isolated networking and execute fresh 30-test suite + shell: bash + run: | + set -Eeuo pipefail + root=/tmp/devnet-safe-v012-native + qemu-system-x86_64 -machine q35,accel=kvm -cpu host -m 4096 -smp 2 \ + -drive file="$root/cachyos-desktop-linux-260809.iso",media=cdrom,if=ide \ + -drive if=none,id=ctl,file="$root/control.img",format=raw \ + -device virtio-blk-pci,drive=ctl,serial=DEVNETCONTROL \ + -nic none -boot order=d -vga std -display none \ + -serial file:evidence/serial.log \ + -monitor unix:"$root/qemu-monitor.sock",server=on,wait=off \ + -daemonize -pidfile "$root/qemu.pid" + sleep 110 + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" framebuffer --image evidence/guest-before.ppm + python3 "$root/qemu_control.py" "$root/qemu-monitor.sock" begin-install + captured=0 + for attempt in $(seq 1 75); do + mcopy -o -i "$root/control.img" ::/controller.log evidence/controller.log >/dev/null 2>&1 || true + mcopy -o -i "$root/control.img" ::/unit-tests.log evidence/unit-tests.log >/dev/null 2>&1 || true + if mcopy -o -i "$root/control.img" ::/native-result.json evidence/native-result.json >/dev/null 2>&1; then + if python3 -m json.tool evidence/native-result.json >/dev/null 2>&1; then + captured=1; break + fi + fi + sleep 8 + done + test "$captured" -eq 1 + python3 - <<'PY' + import json + r=json.load(open('evidence/native-result.json')) + assert r['schema']=='devnet-safe-ai-v012-github-cachyos-unit-scope-v1' + assert r['candidate_sha256']=='fd227dfc54e26e10198ebc0623088aea7884fa62840b7999b32d8a2d0618a8ae' + assert r['native_cachyos'] is True and r['unit_tests_pass']==30 + assert r['master_qa']=='BLOCKED' and r['release_approved'] is False + assert 'Ran 30 tests' in open('evidence/unit-tests.log').read() + print('GITHUB_CACHYOS_30_OF_30_TESTS_PASS_LIMITED_FULL_MASTER_QA_BLOCKED') + PY + - uses: actions/upload-artifact@v4 + if: always() + with: + name: devnet-safe-v012-real-cachyos-evidence + path: evidence/ + retention-days: 30 + if-no-files-found: warn + + independent-evidence-and-scope-gate: + needs: [verify-exact-private-safe-package, real-cachyos-vm-30-regressions] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + attestations: write + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: devnet-safe-v012-exact-candidate + path: input + - uses: actions/download-artifact@v4 + with: + name: devnet-safe-v012-real-cachyos-evidence + path: evidence + - name: Verify independent guest evidence; fail closed on missing scope + shell: bash + run: | + set -Eeuo pipefail + python3 - <<'PY' + import hashlib,json,pathlib + p=pathlib.Path('input/candidate.zip') + h=hashlib.sha256(p.read_bytes()).hexdigest() + assert h=='fd227dfc54e26e10198ebc0623088aea7884fa62840b7999b32d8a2d0618a8ae' + r=json.loads(pathlib.Path('evidence/native-result.json').read_text()) + assert r['candidate_sha256']==h and r['native_cachyos'] is True + assert r['unit_tests_pass']==30 and r['unit_tests_fail']==0 + assert r['ollama_host_live']=='NOT_TESTED' and r['opencode_full_runtime']=='NOT_TESTED' + assert r['install_upgrade_rollback']=='NOT_TESTED' + assert r['master_qa']=='BLOCKED' and r['release_approved'] is False + log=pathlib.Path('evidence/unit-tests.log').read_text() + assert 'Ran 30 tests' in log and '\nOK\n' in log + assert 'REAL_CACHYOS_EXACT_ZIP_PASS' in pathlib.Path('evidence/controller.log').read_text() + pathlib.Path('scope-receipt.json').write_text(json.dumps({ + 'schema':'devnet-safe-v012-github-scope-v1','archive_sha256':h, + 'native_cachyos_unit_tests_pass':30,'full_master_qa':'BLOCKED', + 'runtime_gui_host_integration':'NOT_TESTED', 'release_approved':False},sort_keys=True)+'\n') + print('INDEPENDENT_SCOPE_GATE_PASS_LIMITED; NO_RELEASE') + PY + - uses: actions/attest@v4 + with: + subject-path: input/candidate.zip + - uses: actions/attest@v4 + with: + subject-path: scope-receipt.json + - uses: actions/upload-artifact@v4 + with: + name: devnet-safe-v012-signed-github-scope + path: scope-receipt.json + if-no-files-found: error + - run: echo '30/30 tests only in genuine GitHub-hosted CachyOS VM. Host install/upgrade/rollback, security runtime, and Master QA are NOT TESTED; release BLOCKED.' >> "$GITHUB_STEP_SUMMARY" From e567ffa334c9e9b30d1d8b290c57a4b325928d1f Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:53:07 +0000 Subject: [PATCH 65/70] qa: add PR-triggered native CachyOS verification for exact safe AI v0.1.2 --- .github/workflows/devnet-safe-ai-v012-native-cachyos.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/devnet-safe-ai-v012-native-cachyos.yml b/.github/workflows/devnet-safe-ai-v012-native-cachyos.yml index eb89e95..63a12b5 100644 --- a/.github/workflows/devnet-safe-ai-v012-native-cachyos.yml +++ b/.github/workflows/devnet-safe-ai-v012-native-cachyos.yml @@ -6,6 +6,11 @@ on: paths: - 'qa/devnet-safe-ai-v0.1.2/**' - '.github/workflows/devnet-safe-ai-v012-native-cachyos.yml' + pull_request: + branches: [qa/master-unified-v0.2.4-github-first] + paths: + - 'qa/devnet-safe-ai-v0.1.2/**' + - '.github/workflows/devnet-safe-ai-v012-native-cachyos.yml' workflow_dispatch: permissions: From 95808318c891ccd03e82520acab2c94767a41b55 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:38:58 +0000 Subject: [PATCH 66/70] master QA: mandate fresh real runner/output testing and independent evidence after OpenCode v2 regression --- qa/master/QA_RUNNER_REALITY_RULE_v0.1.0.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 qa/master/QA_RUNNER_REALITY_RULE_v0.1.0.md diff --git a/qa/master/QA_RUNNER_REALITY_RULE_v0.1.0.md b/qa/master/QA_RUNNER_REALITY_RULE_v0.1.0.md new file mode 100644 index 0000000..48425ff --- /dev/null +++ b/qa/master/QA_RUNNER_REALITY_RULE_v0.1.0.md @@ -0,0 +1,20 @@ +# Devnet Master QA — additive runner reality gate v0.1.0 +**Status: QA candidate, NOT release approval.** 2026-10-31. + +This extends—not replaces—the preserved Devnet Master QA v0.3.8 baseline, its GitHub-first order, signed operator checklist, job-specific requirements, deterministic evidence gate, gaming controls, and the rule to execute 100% of applicable checks FRESH on EVERY revision. No prior PASS may be inherited. The QA runner, installer, updater and evidence collector count as software and require full applicable testing too. + +## Mandatory runner acceptance + +1. **Real-tool compatibility.** Execute the EXACT new/modified runner against actual dependency CLI/API output inside the genuine target OS/VM BEFORE relying on it for application acceptance. Record command argv, resolved binary path, binary SHA256, exit code, raw stdout/stderr, version, environment and timestamp. Bind the evidence to exact candidate ZIP SHA256, runner SHA256, job ID and revision. Mocks, simulated output and parser-only unit tests may supplement but NEVER replace live execution. +2. **Version and output matrix.** Test each parsed tool output against the tool's genuine current native formatting and boundaries. The real CachyOS output `opencode v2.2.4` must pass the OpenCode major-2 check, as must `2.2.4` and `v2.2.4`. Wrong major, malformed/foreign output, nonzero exit, missing binary, unexpected prefix, altered command, timeout, or incompatible tool version must fail closed. Test whitespace, warnings, stderr and suffixes where they occur in real output. Do not loosen validation to accept arbitrary text. +3. **Independent runner evidence.** A separate deterministic verifier must check the real executable identity, raw outcome, exact revision/artefact SHA256, complete scope, freshness, and tampering/negative cases. A runner's own PASS is not approval. Any missing, altered, stale, untrusted, inherited or simulated result must remain FAIL/BLOCKED/NOT_TESTED. +4. **ALL tests again on EVERY change.** Every changed runner/parser/dependency/configuration requires all applicable fresh GitHub-hosted tests FIRST; local CachyOS QA VM SECOND; authorised host tests THIRD. Rerun even previously passing checks. Cover install, upgrade, rollback, real runtime, sandbox/privacy/permissions, genuine GUI/tray when applicable, file writes, gaming guard and recovery, 9B/30B live switching and idle unloading when applicable. Never convert a limited unit test result into full acceptance. +5. **Prerequisites are blockers.** Missing `opencode`/`ollama`, package-mirror errors, sudo permission issues, unsupported versions, or unavailable GUI are blockers and cannot be bypassed with fake executables or inherited evidence. Fix the prerequisite safely and rerun the full stage. +6. **Signed checklist protection.** Add the check IDs `runner_real_dependency_output`, `runner_negative_contracts`, `runner_independent_evidence` to every signed job checklist for which runner validation is applicable. No altering or reducing the existing operator-approved checklist without renewed authorisation/signature. The independently protected Master QA stage/signing service remains authoritative. `release_approved=false` until all job-specific checks, signatures and final user acceptance are complete. +7. **Change control.** Preserve original baseline bytes and real evidence. Version this extension as a new QA candidate; perform GitHub-first tests before activating it locally. Never claim the existing immutable baseline/deployed runner was rewritten merely because this draft policy exists. + +## Incident record / permanent regression + +On 31 October 2026, Devnet Safe AI v0.1.2 local Master QA r2 rejected valid native OpenCode output `opencode v2.2.4` because the runner only recognised bare numeric versions. Its 30 unit regressions passed but the real VM stage was blocked. This was a defect in the QA runner, not evidence that the application was broken. Runner r3 changed the parser; its live VM/host gates remain unverified until executed. + +The associated executable `runner_reality_gate.py` captures real OpenCode CLI evidence and checks it again, bound to runner/candidate hashes. Its local and GitHub unit tests remain **development-only** until the true VM and independently signed Master QA acceptance are proven. No automatic release approval. From e2d09715befe25967cc1db01374bcc817534eaab Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:39:31 +0000 Subject: [PATCH 67/70] master QA: add fail-closed live OpenCode CLI gate with revision-bound reexecution --- qa/master/runner_reality_gate.py | 109 +++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 qa/master/runner_reality_gate.py diff --git a/qa/master/runner_reality_gate.py b/qa/master/runner_reality_gate.py new file mode 100644 index 0000000..4de10d0 --- /dev/null +++ b/qa/master/runner_reality_gate.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Additive Devnet Master QA real CLI contract check; unsigned, never approves release.""" +import argparse +from datetime import datetime, timedelta, timezone +import hashlib +import json +from pathlib import Path +import re +import shutil +import subprocess +import sys + +SCHEMA="devnet-runner-dependency-reality-v1" +VERSION=re.compile(r"\A(?:opencode[ \t]+)?v?2\.(\d+)\.(\d+)(?:[-+][a-zA-Z0-9.-]+)?\Z",re.I) + +def parse_opencode(raw): + m=VERSION.fullmatch(raw.strip()) + if not m: + raise ValueError("unsupported OpenCode v2 output") + return "2."+m.group(1)+"."+m.group(2) + +def digest(p): + h=hashlib.sha256() + with Path(p).open("rb") as fp: + for chunk in iter(lambda:fp.read(1024*1024),b""): + h.update(chunk) + return h.hexdigest() + +def live_target(): + exe=shutil.which("opencode") + if not exe: + raise RuntimeError("BLOCKED: real OpenCode missing") + exe=Path(exe).resolve(strict=True) + if not exe.is_file(): + raise RuntimeError("BLOCKED: OpenCode executable unavailable") + cp=subprocess.run([str(exe),"--version"],stdin=subprocess.DEVNULL, + capture_output=True,text=True,timeout=20,check=False) + if cp.returncode: + raise RuntimeError("BLOCKED: OpenCode --version exit "+str(cp.returncode)) + return exe,cp,parse_opencode(cp.stdout) + +def identity(p): + p=Path(p).resolve(strict=True) + if not p.is_file(): + raise RuntimeError("BLOCKED: runner/candidate not a file") + return digest(p) + +def capture(runner,candidate,job,revision): + if not re.fullmatch(r"[A-Za-z0-9_-]{1,64}",job) or not re.fullmatch(r"[A-Za-z0-9._-]{1,80}",revision): + raise ValueError("invalid job/revision identity") + exe,cp,version=live_target() + return dict(schema=SCHEMA,kind="LIVE_EXECUTION",argv=[str(exe),"--version"], + binary_sha256=digest(exe),runner_sha256=identity(runner), + candidate_sha256=identity(candidate),job_id=job,revision=revision, + stdout=cp.stdout,stderr=cp.stderr,exit_code=cp.returncode, + parsed_version=version,timestamp=datetime.now(timezone.utc).isoformat(), + release_approved=False,scope="CLI_ONLY") + +def verify(r,runner,candidate,job,revision): + mandatory={"schema","kind","argv","binary_sha256","runner_sha256","candidate_sha256", + "job_id","revision","stdout","stderr","exit_code","parsed_version", + "timestamp","release_approved","scope"} + if not isinstance(r,dict) or set(r)!=mandatory: + raise ValueError("incomplete evidence") + if r["schema"]!=SCHEMA or r["kind"]!="LIVE_EXECUTION" or r["scope"]!="CLI_ONLY" or r["release_approved"] is not False or r["exit_code"]!=0: + raise ValueError("untrusted evidence or approval") + when=datetime.fromisoformat(r["timestamp"]) + if when.tzinfo is None or not timedelta(seconds=-30)<=datetime.now(timezone.utc)-when<=timedelta(minutes=15): + raise ValueError("stale/future evidence") + if (r["runner_sha256"]!=identity(runner) or r["candidate_sha256"]!=identity(candidate) + or r["job_id"]!=job or r["revision"]!=revision): + raise ValueError("different candidate or runner revision") + exe,cp,version=live_target() + if r["argv"]!=[str(exe),"--version"] or r["binary_sha256"]!=digest(exe) or r["parsed_version"]!=version: + raise ValueError("executable/version mismatch") + if r["stdout"]!=cp.stdout or parse_opencode(r["stdout"])!=version: + raise ValueError("captured stdout does not match re-executed native CLI") + return True + +def main(): + ap=argparse.ArgumentParser() + ap.add_argument("mode",choices=["capture","verify"]) + ap.add_argument("evidence",type=Path) + ap.add_argument("--runner",type=Path,required=True) + ap.add_argument("--candidate",type=Path,required=True) + ap.add_argument("--job",required=True) + ap.add_argument("--revision",required=True) + a=ap.parse_args() + try: + if a.mode=="capture": + if a.evidence.exists(): + raise ValueError("existing evidence cannot be reused") + r=capture(a.runner,a.candidate,a.job,a.revision) + a.evidence.parent.mkdir(parents=True,exist_ok=True) + with a.evidence.open("x") as out: + json.dump(r,out,indent=2,sort_keys=True) + out.write("\n") + a.evidence.chmod(0o600) + else: + verify(json.loads(a.evidence.read_text()),a.runner,a.candidate,a.job,a.revision) + print("PASS_LIMITED: REAL_CLI; MASTER QA/RELEASE STILL BLOCKED") + return 0 + except (OSError,RuntimeError,ValueError,KeyError,TypeError,IndexError, + subprocess.TimeoutExpired,json.JSONDecodeError) as e: + print("BLOCKED:",e,file=sys.stderr) + return 2 + +if __name__=="__main__": + raise SystemExit(main()) From 4ab3e036130f2c07ab94e5ef633476671e7402b2 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:39:51 +0000 Subject: [PATCH 68/70] master QA: regression tests for real OpenCode prefix, wrong majors, malformed outputs and fake evidence --- qa/master/test_runner_reality_gate.py | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 qa/master/test_runner_reality_gate.py diff --git a/qa/master/test_runner_reality_gate.py b/qa/master/test_runner_reality_gate.py new file mode 100644 index 0000000..fad30b5 --- /dev/null +++ b/qa/master/test_runner_reality_gate.py @@ -0,0 +1,47 @@ +"""Development regressions only; real target VM must execute runner_reality_gate separately.""" +import unittest +from unittest import mock +import runner_reality_gate as g + +class RunnerContractTests(unittest.TestCase): + def test_exact_cachyos_v224_output(self): + self.assertEqual(g.parse_opencode("opencode v2.2.4\n"),"2.2.4") + def test_bare_version(self): + self.assertEqual(g.parse_opencode("2.2.4"),"2.2.4") + def test_v_version(self): + self.assertEqual(g.parse_opencode("v2.2.4"),"2.2.4") + def test_suffix(self): + self.assertEqual(g.parse_opencode("opencode v2.2.4-rc.1"),"2.2.4") + def test_whitespace(self): + self.assertEqual(g.parse_opencode(" opencode v2.2.4 \n"),"2.2.4") + def test_invalid_major_and_output_fail_closed(self): + for s in ("opencode v1.2.4","v3.0.0","2.2","opencode error", + "opencode v2.2.4\nbad","ollama v2.2.4","v2.2.4; rm -rf /",""): + with self.subTest(s=s),self.assertRaises(ValueError): + g.parse_opencode(s) + def test_missing_real_binary_blocks(self): + with mock.patch.object(g.shutil,"which",return_value=None): + with self.assertRaisesRegex(RuntimeError,"real OpenCode missing"): + g.live_target() + def test_nonzero_exit_blocks(self): + class Result: + returncode=2 + stdout="opencode v2.2.4" + stderr="failed" + with mock.patch.object(g.shutil,"which",return_value="/usr/bin/python3"): + with mock.patch.object(g.subprocess,"run",return_value=Result()): + with self.assertRaisesRegex(RuntimeError,"exit 2"): + g.live_target() + def test_incomplete_receipt_blocks(self): + with self.assertRaisesRegex(ValueError,"incomplete evidence"): + g.verify({},__file__,__file__,"x","r1") + def test_forged_approval_blocks(self): + e={k:None for k in ("schema","kind","argv","binary_sha256","runner_sha256", + "candidate_sha256","job_id","revision","stdout","stderr","exit_code", + "parsed_version","timestamp","release_approved","scope")} + e.update(schema=g.SCHEMA,kind="LIVE_EXECUTION",scope="CLI_ONLY", + release_approved=True,exit_code=0) + with self.assertRaisesRegex(ValueError,"untrusted evidence"): + g.verify(e,__file__,__file__,"x","r1") +if __name__=="__main__": + unittest.main() From fbdcd283fa03c5e39c90def892eadf1e513fd411 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:41:05 +0000 Subject: [PATCH 69/70] master QA policy: require real tool output, runner negative tests and revision-bound evidence --- qa/application/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/qa/application/README.md b/qa/application/README.md index adc6214..bdd63b1 100644 --- a/qa/application/README.md +++ b/qa/application/README.md @@ -135,3 +135,29 @@ A preserved `devnet-master-qa-registered-application-stages-v0.1.0-QA-BLOCKED.zi exists in the ChatGPT Library, but its original bytes could not be materialised through the present connection. Do not represent it as inspected or assume that its contents were installed or integrated. + +## Mandatory QA-runner reality gate — 31 October 2026 (additive, QA BLOCKED) + +The candidate Master QA extension is `qa/master/QA_RUNNER_REALITY_RULE_v0.1.0.md`. +This is a mandatory **planned acceptance requirement for all QA-runner modifications**, +not permission to skip the pre-existing protected checklist or a declaration that +the immutable Master QA v0.3.8 ZIP has been updated/deployed. + +**Incident:** The r2 Devnet Safe AI v0.1.2 runner rejected real CachyOS OpenCode +output `opencode v2.2.4` despite passing 30 unit regressions. Real dependency +output was not tested before handing over the runner. Mark r2 VM runtime BLOCKED. + +**New acceptance IDs when the runner/tooling changes:** +`runner_real_dependency_output`, `runner_negative_contracts`, +`runner_independent_evidence`. Run the exact runner with actual target VM +dependency output and capture binary/runner/candidate hashes and raw CLI +stdout/stderr. Reject wrong major versions, missing tools, nonzero exits, +malformed and reused output. Run 100% fresh job-specific tests after each +revision GitHub first, local CachyOS VM second, authorised host third. Every +unverified test stays FAIL/BLOCKED/NOT_TESTED, not inherited PASS. + +`qa/master/runner_reality_gate.py` is a scoped unsigned live-output probe and +re-execution checker, not a signing service and not the protected Master QA. +The new policy, probe and development tests are **staged on this QA branch only**. +Integration with the independent signer and protected Master QA baseline, +real host/VM tests, and final release acceptance remain BLOCKED. From 8de2296a8710a48600272e5b5eaf81750f7f45d8 Mon Sep 17 00:00:00 2001 From: devnet82 <42787601+devnet82@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:42:01 +0000 Subject: [PATCH 70/70] master QA: enforce runner real-output regression tests in existing coordinator GitHub CI --- qa/application/test_master_runner_reality.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 qa/application/test_master_runner_reality.py diff --git a/qa/application/test_master_runner_reality.py b/qa/application/test_master_runner_reality.py new file mode 100644 index 0000000..d6a5190 --- /dev/null +++ b/qa/application/test_master_runner_reality.py @@ -0,0 +1,11 @@ +"""Include additive Master QA runner-reality regressions in existing coordinator CI. + +This runs DEVELOPMENT contract tests only. Actual VM/host real output remains +a separate mandatory signed stage; these fixtures are NOT native VM PASS. +""" +import sys +from pathlib import Path + +MASTER = Path(__file__).resolve().parents[1] / "master" +sys.path.insert(0, str(MASTER)) +from test_runner_reality_gate import RunnerContractTests # noqa: F401