` from the repository. It verifies deterministic ZIP and manifest bytes, rejects unsafe archive entries before extraction, creates a disposable extraction with spaces in its path, reproduces the public.2 failure, and executes the candidate's actual published PowerShell steps. These steps create a fresh sibling virtual environment and install the declared pinned dependencies from PyPI. The demonstration itself makes no model/network calls, leaves Silver pending and Gold unapplied, and exercises stale/missing context refusal. It resumes from the demo output in a fresh process, verifies literal traversal/existing-output refusal, then runs the unchanged 86 regressions plus seven distribution regressions and public link/path checks.
+
+`python -B scripts/build_candidate.py --check` checks reproducibility without writing. The builder refuses any existing candidate. ZIP metadata is deterministic. The adjacent `.sha256.json` records archive/manifest hashes and exact counts; the manifest authenticates bytes relative to its reviewed digest, not a trusted publisher signature. New findings require a new version, never silent replacement bytes.
+
+## CI and owner controls
+
+The workflow uses Windows and exact CPython 3.14.5 with pinned official actions, read-only repository permissions, a 20-minute limit and no production/private inputs. Installation/platform failures fail the check; they are not green runtime evidence. Proposed required check: **Package validation (Windows CPython 3.14.5)** from **Windows package validation**. An owner can add it to the existing PR ruleset after observing a successful run at the candidate SHA. No ruleset, Dependabot, secret scanning or CodeQL setting is changed. Existing main rules prevent deletion/force-push and require PRs but do not require this status yet.
+
+The first hosted run exposed a Windows runner setup difference: TEMP used the short `RUNNER~1` spelling while guarded paths resolved the long `runneradmin` spelling. Direct regression fixtures then failed relative-path comparisons. CI now resolves the runner-owned temporary directory and sets TMP/TEMP for that process before validation. This preserves the same test cases, package bytes and alias/traversal guards; it does not change machine settings or allow aliases in protected application paths.
+
+Scope is the dependency pins and package runtime regressions, not a comprehensive transitive advisory audit, upstream HTTP health check, hostile same-user filesystem confinement, authenticated approvals, installed private OS certification, hosted usability or production release. H3 and owner acceptance remain separate from local/CI validation. Failed or missing results remain unknown; no accepted private milestone is reopened.
+
+GitHub's [setup-python documentation](https://github.com/actions/setup-python) describes exact-version selection and downloading a missing tool-cache version. Availability of CPython 3.14.5 on the selected hosted runner is proved only by a successful candidate run.
diff --git a/scripts/build_candidate.py b/scripts/build_candidate.py
new file mode 100644
index 0000000..61d47cb
--- /dev/null
+++ b/scripts/build_candidate.py
@@ -0,0 +1,78 @@
+"""Build a new deterministic candidate; never rewrite historical releases."""
+from pathlib import Path
+import argparse
+import hashlib
+import io
+import json
+import zipfile
+
+ROOT = Path(__file__).resolve().parents[1]
+SOURCE = "agentic-engineering-os-2026.10.09-public.2"
+VERSION = "2026.10.12-public.4-candidate.1"
+NAME = "agentic-engineering-os-" + VERSION
+COMMANDS = """python -m venv ../aeos-env
+../aeos-env/Scripts/python -m pip install -r requirements.txt
+../aeos-env/Scripts/python -B scripts/check_package.py .
+$demoOutput = [System.IO.Path]::GetFullPath((Join-Path $PWD.Path '../aeos-first-run'))
+../aeos-env/Scripts/python -B scripts/demo.py --output $demoOutput"""
+
+
+def sha(raw):
+ return hashlib.sha256(raw).hexdigest()
+
+
+def render():
+ source = ROOT / "distribution" / SOURCE
+ old = json.loads((source / "MANIFEST.json").read_bytes())
+ payload = {row["path"]: (source / row["path"]).read_bytes() for row in old["files"]}
+ for row in old["files"]:
+ raw = payload[row["path"]]
+ if sha(raw) != row["sha256"] or len(raw) != row["bytes"]:
+ raise ValueError("Historical source drift: " + row["path"])
+ first = payload["FIRST_RUN.md"].decode("utf8")
+ start, end = first.index("```powershell\n") if "```powershell\n" in first else first.index("```powershell\r\n"), None
+ end = first.index("```", start + 3)
+ first = first[:start] + "```powershell\n" + COMMANDS + "\n" + first[end:]
+ first = first.replace("python -B vault/09_TOOLS/agent_runner_v1/unified_handoff/handoff.py review", "../aeos-env/Scripts/python -B vault/09_TOOLS/agent_runner_v1/unified_handoff/handoff.py review")
+ first = first.replace("Choose new folder names", "The output expression resolves the sibling folder to an absolute path; the traversal guard still rejects literal `..` segments. Choose new folder names")
+ payload["FIRST_RUN.md"] = first.encode("utf8")
+ payload["README.md"] = payload["README.md"].replace(b"2026.10.09-public.2", VERSION.encode())
+ manifest = {**old, "version": VERSION, "candidate_status": "UNPUBLISHED_REVIEW_CANDIDATE", "based_on": SOURCE}
+ manifest["files"] = [{**row, "sha256": sha(payload[row["path"]]), "bytes": len(payload[row["path"]])} for row in old["files"]]
+ payload["MANIFEST.json"] = (json.dumps(manifest, indent=2) + "\n").encode()
+ archive = io.BytesIO()
+ with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as z:
+ for path, raw in sorted(payload.items()):
+ info = zipfile.ZipInfo(path, date_time=(2026, 10, 12, 0, 0, 0))
+ info.compress_type = zipfile.ZIP_DEFLATED
+ info.create_system = 3
+ info.external_attr = 0o100644 << 16
+ z.writestr(info, raw, compresslevel=9)
+ raw_zip = archive.getvalue()
+ hashes = {"version": VERSION, "status": "UNPUBLISHED_REVIEW_CANDIDATE", "archive": NAME + ".zip", "sha256": sha(raw_zip), "bytes": len(raw_zip), "manifest_sha256": sha(payload["MANIFEST.json"]), "payload_files": len(payload) - 1}
+ return payload, raw_zip, (json.dumps(hashes, indent=2) + "\n").encode()
+
+
+def build(check=False):
+ payload, archive, hashes = render()
+ outputs = {ROOT / "distribution" / NAME / p: raw for p, raw in payload.items()}
+ outputs[ROOT / "distribution" / (NAME + ".zip")] = archive
+ outputs[ROOT / "distribution" / (NAME + ".sha256.json")] = hashes
+ if check:
+ for path, raw in outputs.items():
+ if not path.is_file() or path.read_bytes() != raw:
+ raise ValueError("Candidate differs from deterministic build: " + str(path.relative_to(ROOT)))
+ else:
+ if any(p.exists() for p in outputs) or (ROOT / "distribution" / NAME).exists():
+ raise ValueError("Candidate already exists; use --check or a new version, never overwrite")
+ for path, raw in outputs.items():
+ path.parent.mkdir(parents=True, exist_ok=True)
+ with path.open("xb") as f:
+ f.write(raw)
+ return json.loads(hashes)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--check", action="store_true")
+ print(json.dumps(build(parser.parse_args().check), indent=2))
diff --git a/scripts/validate_candidate.py b/scripts/validate_candidate.py
new file mode 100644
index 0000000..e894232
--- /dev/null
+++ b/scripts/validate_candidate.py
@@ -0,0 +1,200 @@
+"""Bounded Windows validation of the actual candidate ZIP and published journey."""
+from pathlib import Path
+import argparse
+import html
+from html.parser import HTMLParser
+import importlib.metadata
+import io
+import json
+import os
+import re
+import stat
+import subprocess
+import sys
+import tempfile
+import unittest
+from urllib.parse import unquote, urlsplit
+import zipfile
+from build_candidate import ROOT, NAME, SOURCE, COMMANDS, build, sha
+
+
+def archive_members(raw):
+ """Validate all names/identities/bounds before ANY extraction effect."""
+ with zipfile.ZipFile(io.BytesIO(raw)) as z:
+ infos = z.infolist()
+ if len(infos) > 100 or sum(i.file_size for i in infos) > 5_000_000:
+ raise ValueError("Archive exceeds candidate bounds")
+ seen, members = set(), {}
+ for i in infos:
+ p = i.orig_filename
+ if (not p or "\\" in p or "\x00" in p or p.startswith("/") or ":" in p
+ or any(part in {"", ".", ".."} or part.endswith((".", " "))
+ or getattr(os.path, "isreserved", lambda _: False)(part) for part in p.split("/"))
+ or p.casefold() in seen or i.is_dir()
+ or stat.S_IFMT(i.external_attr >> 16) not in {0, stat.S_IFREG}):
+ raise ValueError("Unsafe archive entry: " + p)
+ seen.add(p.casefold())
+ members[p] = z.read(i)
+ if "MANIFEST.json" not in members:
+ raise ValueError("Archive lacks manifest")
+ for p in members:
+ if any(parent.as_posix().casefold() in seen for parent in Path(p).parents if parent != Path(".")):
+ raise ValueError("Archive file/directory conflict")
+ manifest = json.loads(members["MANIFEST.json"])
+ rows = manifest["files"]
+ if len({r["path"].casefold() for r in rows}) != len(rows):
+ raise ValueError("Duplicate manifest entries")
+ if set(members) != {r["path"] for r in rows} | {"MANIFEST.json"}:
+ raise ValueError("Archive/manifest file set differs")
+ for row in rows:
+ raw = members[row["path"]]
+ if len(raw) != row["bytes"] or sha(raw) != row["sha256"]:
+ raise ValueError("Archive payload drift: " + row["path"])
+ return members
+
+
+def extract(raw, destination):
+ members = archive_members(raw)
+ if destination.exists():
+ raise ValueError("Use a new extraction directory")
+ destination.mkdir()
+ for path, content in members.items():
+ out = destination / path
+ out.parent.mkdir(parents=True, exist_ok=True)
+ with out.open("xb") as f:
+ f.write(content)
+ return members
+
+
+class Links(HTMLParser):
+ def __init__(self):
+ super().__init__()
+ self.links = []
+ def handle_starttag(self, tag, attrs):
+ self.links.extend(value for key, value in attrs if key in {"href", "src"} and value)
+
+
+def validate_links_and_privacy():
+ checked = 0
+ files = list((ROOT / "site").rglob("*.html"))
+ files += [ROOT / "README.md", ROOT / "docs/PACKAGE-CANDIDATE.md"]
+ candidate = ROOT / "distribution" / NAME
+ files += [p for p in candidate.rglob("*") if p.is_file()]
+ for path in files:
+ try:
+ text = path.read_text(encoding="utf8")
+ except UnicodeError:
+ continue
+ if re.search(r"(?i)(?:[a-z]:[/\\]users[/\\]|onedrive|file://|sk-proj-[A-Za-z0-9_-]{12,}|ghp_[A-Za-z0-9]{20,}|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----)", text):
+ raise ValueError("Private path/credential marker in " + str(path.relative_to(ROOT)))
+ if path.suffix == ".html":
+ parser = Links()
+ parser.feed(text)
+ links = parser.links
+ elif path.suffix == ".md":
+ links = re.findall(r"(? " + value)
+ if path.is_relative_to(ROOT / "site") and not target.is_relative_to(ROOT / "site"):
+ raise ValueError("Website publication-root escape")
+ checked += 1
+ return {"text_files_scanned": len(files), "local_links_checked": checked, "upstream_health": "NOT_RUN", "limit": "Bounded current payload/path/credential marker scan, not exhaustive secret audit"}
+
+
+def first_block(text):
+ return re.search(r"```powershell\s*\n(.*?)\n```", text, re.S).group(1).replace("\r\n", "\n")
+
+
+def run(command, cwd, records, expected=0, timeout=300):
+ p = subprocess.run(command, cwd=cwd, capture_output=True, text=True, timeout=timeout)
+ records.append({"command": command, "exit_code": p.returncode, "stdout": p.stdout, "stderr": p.stderr})
+ if p.returncode != expected:
+ raise RuntimeError(f"Expected exit {expected}, got {p.returncode}: {p.stdout}\n{p.stderr}")
+ return p
+
+
+def journey(extracted, records):
+ doc = (extracted / "FIRST_RUN.md").read_text(encoding="utf8")
+ block = first_block(doc)
+ if block != COMMANDS or first_block((ROOT / "README.md").read_text(encoding="utf8")) != block:
+ raise ValueError("README/FIRST_RUN command drift")
+ site = (ROOT / "site/first-run.html").read_text(encoding="utf8")
+ site_block = html.unescape(re.search(r"(.*?) ", site, re.S).group(1)).replace("\r\n", "\n")
+ if site_block != block:
+ raise ValueError("Website/FIRST_RUN command drift")
+ # Execute verbatim published steps, only bind 'python' to the selected 3.14.5.
+ lines = block.splitlines()
+ lines[0] = "& '" + sys.executable.replace("'", "''") + "'" + lines[0][len("python"):]
+ script = "$ErrorActionPreference = 'Stop'\n" + "\n".join(line + ("\nif ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }" if not line.startswith("$") else "") for line in lines)
+ run(["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script], extracted, records)
+ output = extracted.parent / "aeos-first-run"
+ receipt = json.loads((output / "RESULT.json").read_bytes())
+ if not (receipt["result"] == "DEMO_LOCAL_PASSED" and receipt["owner_decision"] == "pending"
+ and receipt["gold_applied"] is False and receipt["h3_verdict"] is None and receipt["stale_and_missing_refused"]):
+ raise ValueError("Demo receipt crossed acceptance boundary")
+ resume = re.search(r"To resume,.*?```powershell\s*\n(.*?)\n```", doc, re.S).group(1)
+ run(["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", resume + "\nexit $LASTEXITCODE"], output, records)
+ python = extracted.parent / "aeos-env/Scripts/python.exe"
+ # Fresh negative security evidence before effects, same extracted package.
+ rejected = output.parent / "refused-output"
+ p = subprocess.run([str(python), "-B", "scripts/demo.py", "--output", "../refused-output"], cwd=extracted, capture_output=True, text=True)
+ records.append({"command": "literal ../refused-output security regression", "exit_code": p.returncode, "stderr": p.stderr})
+ if p.returncode == 0 or "Parent traversal is forbidden" not in p.stderr or rejected.exists():
+ raise ValueError("Traversal guard weakened")
+ p = subprocess.run([str(python), "-B", "scripts/demo.py", "--output", str(output)], cwd=extracted, capture_output=True, text=True)
+ records.append({"command": "existing destination security regression", "exit_code": p.returncode, "stderr": p.stderr})
+ if p.returncode == 0 or "prior evidence is preserved" not in p.stderr:
+ raise ValueError("Existing-output refusal weakened")
+ return python, receipt
+
+
+def validate():
+ if os.name != "nt" or sys.version_info[:3] != (3, 14, 5):
+ raise ValueError("Intended validation requires Windows CPython 3.14.5")
+ hashes = build(check=True)
+ records = []
+ with tempfile.TemporaryDirectory(prefix="AEOS package with spaces ") as temp:
+ base = Path(temp)
+ old = base / "historical"
+ extract((ROOT / "distribution" / (SOURCE + ".zip")).read_bytes(), old)
+ old_cmd = first_block((old / "FIRST_RUN.md").read_text(encoding="utf8")).splitlines()[-1]
+ # Reproduce the actual historical documented demo destination in extraction.
+ p = subprocess.run([sys.executable, "-B", "scripts/demo.py", "--output", old_cmd.split("--output ")[1]], cwd=old, capture_output=True, text=True)
+ records.append({"command": old_cmd, "exit_code": p.returncode, "stderr": p.stderr})
+ if p.returncode == 0 or "Parent traversal is forbidden" not in p.stderr or (base / "aeos-first-run").exists():
+ raise ValueError("Historical failure not reproduced")
+ extracted = base / "candidate"
+ extract((ROOT / "distribution" / (NAME + ".zip")).read_bytes(), extracted)
+ python, receipt = journey(extracted, records)
+ suites = [("scripts", 7), ("vault/00_SYSTEM/AGENTIC_LIBRARIAN/tests", 38), ("vault/09_TOOLS/agent_runner_v1/unified_handoff", 40), ("vault/09_TOOLS/agent_runner_v1/scripts/tests", 1)]
+ for directory, count in suites:
+ result = run([str(python), "-B", "-m", "unittest", "discover", "-s", directory, "-p", "test_*.py"], extracted, records, timeout=600)
+ if f"Ran {count} tests" not in result.stderr and f"Ran {count} test" not in result.stderr:
+ raise ValueError("Existing suite count changed")
+ if "skipped=" in result.stderr:
+ raise ValueError("Required Windows security regression skipped")
+ run([str(python), "-B", "scripts/check_package.py", "."], extracted, records)
+ regression = run([str(python), "-B", "-m", "unittest", "discover", "-s", "tests", "-p", "test_*.py"], ROOT, records)
+ return {"state": "LOCAL_VERIFIED", "archive": hashes, "environment": {"os": sys.platform, "python": sys.version.split()[0]}, "baseline_tests": 86, "new_tests_output": regression.stderr, "packaged_journey": receipt, "public_boundaries": validate_links_and_privacy(), "commands": records, "ci": "NOT_RUN_BY_LOCAL_COMMAND", "independent_H3": "NOT_RUN_BY_IMPLEMENTER", "publication": "NOT_RUN_OWNER_GATED"}
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--receipt", type=Path)
+ args = parser.parse_args()
+ result = validate()
+ if args.receipt:
+ args.receipt.parent.mkdir(parents=True, exist_ok=True)
+ args.receipt.write_text(json.dumps(result, indent=2) + "\n", encoding="utf8")
+ print(json.dumps({key: value for key, value in result.items() if key != "commands"}, indent=2))
diff --git a/site/first-run.html b/site/first-run.html
index d568c80..e7d0531 100644
--- a/site/first-run.html
+++ b/site/first-run.html
@@ -1,4 +1,5 @@
First run — Agentic Engineering OS FIRST RUN
Start beside your work. Extract the setup into a new folder. Keep an existing vault unchanged while you try the synthetic demonstration.
Install the documented prerequisites Use CPython 3.14.5 and PowerShell. The verified environment is Windows with Python 3.14.5. Run these commands from the extracted package:
python -m venv ../aeos-env
../aeos-env/Scripts/python -m pip install -r requirements.txt
../aeos-env/Scripts/python -B scripts/check_package.py .
-../aeos-env/Scripts/python -B scripts/demo.py --output ([System.IO.Path]::GetFullPath((Join-Path $PWD.Path '../aeos-first-run')))Use new directory names if these already exist. The expression above resolves the sibling output folder to an absolute path without parent-traversal segments, as required by the demo's path check. Dependency installation uses PyPI. The demonstration itself makes no network or model calls.
Inspect what happened Open ../aeos-first-run/START_HERE.md. You will find an immutable source copy, a verified-but-pending knowledge proposal, a bounded task brief and a report counting two checked sample items.
The demo also changes and removes a disposable context file, checks that review refuses it, restores its exact bytes and checks again. It never edits owner decisions or applies Gold.
Continue in a fresh session Open PowerShell in ../aeos-first-run, the demonstration output folder containing vault/ and handoff/. Use the same virtual-environment interpreter as above; activation is not required:
../aeos-env/Scripts/python -B vault/09_TOOLS/agent_runner_v1/unified_handoff/handoff.py review --bundle handoff --vault-root vaultRead the candidate and its evidence. The worker's recommendation is separate from independent review and approval.
Make it your own Copy the neutral vault into a new location. Edit the project index, state and next actions; write down your real owner, repository and allowed scope. Use the packet builder to scaffold a draft. Give the bounded brief to your assistant manually, then return the result for checking and review.
Keep source code in its existing repository. Bring across selected templates and tools after a backup; do not replace a populated vault wholesale. The archive's FIRST_RUN.md includes commands for knowledge decisions, checks and rollback.
Download setup preview ↓
+$demoOutput = [System.IO.Path]::GetFullPath((Join-Path $PWD.Path '../aeos-first-run'))
+../aeos-env/Scripts/python -B scripts/demo.py --output $demoOutputUse new directory names if these already exist. The output expression above resolves the sibling output folder to an absolute path without parent-traversal segments, as required by the demo's path check. Dependency installation uses PyPI. The demonstration itself makes no network or model calls.
Inspect what happened Open ../aeos-first-run/START_HERE.md. You will find an immutable source copy, a verified-but-pending knowledge proposal, a bounded task brief and a report counting two checked sample items.
The demo also changes and removes a disposable context file, checks that review refuses it, restores its exact bytes and checks again. It never edits owner decisions or applies Gold.
Continue in a fresh session Open PowerShell in ../aeos-first-run, the demonstration output folder containing vault/ and handoff/. Use the same virtual-environment interpreter as above; activation is not required:
../aeos-env/Scripts/python -B vault/09_TOOLS/agent_runner_v1/unified_handoff/handoff.py review --bundle handoff --vault-root vaultRead the candidate and its evidence. The worker's recommendation is separate from independent review and approval.
Make it your own Copy the neutral vault into a new location. Edit the project index, state and next actions; write down your real owner, repository and allowed scope. Use the packet builder to scaffold a draft. Give the bounded brief to your assistant manually, then return the result for checking and review.
Keep source code in its existing repository. Bring across selected templates and tools after a backup; do not replace a populated vault wholesale. The archive's FIRST_RUN.md includes commands for knowledge decisions, checks and rollback.
The download remains the historical public.2 package; its bundled FIRST_RUN still has the relative-output defect. Use the absolute-output steps above. A new public.4-candidate.1 package is prepared in the repository for review and has not been published.
Download setup preview ↓