From 4ab7ab8712cc3b3d8bb0514ebb4b2c0927456443 Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Fri, 16 Oct 2026 09:19:25 +0900 Subject: [PATCH] =?UTF-8?q?P04-01:=20=E5=90=8C=E6=99=82=E7=99=BA=E7=94=9F?= =?UTF-8?q?=E3=81=AE=E6=9B=B4=E6=96=B0=E8=A6=8F=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../execution/p04-01/p04_native_series.py | 28 +- .../execution/p04-01/s4/S4_ROOT_RUN.md | 140 +++++++++- .../s4/engine_identity_negative_fixtures.py | 2 +- .../p04-01/s4/receipt_lifecycle_fixtures.py | 255 ++++++++++++++++++ .../execution/p04-01/s4/run_s4_native.py | 71 +++-- 5 files changed, 447 insertions(+), 49 deletions(-) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/s4/receipt_lifecycle_fixtures.py diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py index 8aa1aae..7134bd8 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py @@ -17,7 +17,7 @@ MANIFEST_SHA256 = 'a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c MANIFEST_BYTES = 1244135 PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1' RUNS = {'s1': 'p04-s1-native-r02', 's2a': 'p04-s2a-native-r03', - 's2b': 'p04-s2b-native-r02', 's3': 'p04-s3-native-r01', 's4': 'p04-s4-native-r24'} + 's2b': 'p04-s2b-native-r02', 's3': 'p04-s3-native-r01', 's4': 'p04-s4-native-r26'} # Native descriptors pinned from literal files, never ROOT result passed fields. NATIVE_PINS = { 's1': ('1e26d9585396da9415747d87d77b70f23425a3ee0714e4fca114748f9479fd4e', '01372beb0ffec181c892d3b4e88bce32001e08fb2007938d7bf19135511a7acc', '0b6d11eb34d8af64b22cb806caeb06eed4ec521fa9ee24d1204ea185345e655c'), @@ -1023,11 +1023,29 @@ class S4Series(Series): require(path.is_relative_to(self.build), 'S4 material outside own B') self.read_build(path.relative_to(self.build).as_posix(), pin) verify_s4_engine(inputs['engine'], execution['engine_identity']) - require(inputs['engine_identity'] == execution['engine_identity'], 'S4 input/execution engine identity') + initial = inputs['engine_identity_initial'] + require(inputs['engine_identity_before'] == inputs['engine'] == initial['before'] == + execution['engine_identity']['before'], 'S4 immutable initial engine descriptor') + require(set(initial) == {'before', 'during', 'after'} and initial['after'] is None and + isinstance(initial['during'], list) and + initial['during'] == execution['engine_identity']['during'][:len(initial['during'])], + 'S4 initial observations are an immutable prefix') observations = execution['engine_observations'] - require(any(row['stage'] == 'after-cleanup' for row in observations) and - all(row.get('descriptor') == inputs['engine'] for row in observations), - 'S4 runtime engine observations') + stages = [row['stage'] for row in observations] + required = ['before-import', 'before-parse', 'before-writer', 'before-reader', + 'after-collection', 'after-cleanup', 'after-output-collection'] + require(all(stages.count(stage) == 1 for stage in required) and + [stages.index(stage) for stage in required] == sorted(stages.index(stage) for stage in required) and + all(row.get('descriptor') == inputs['engine'] and 'error' not in row for row in observations), + 'S4 ordered runtime engine observations') + for phase in ('import', 'parse', 'writer', 'reader'): + index = stages.index('before-' + phase) + require(loads(self.read('engine-identity-before-' + phase + '.json')) == + dict(observations[index], observation_index=index), 'S4 immutable per-phase engine receipt') + require(loads(self.read('engine-identity.json')) == + dict(observations=observations, identity=execution['engine_identity']), 'S4 final engine receipt') + require(observations[stages.index('after-cleanup')]['descriptor'] == inputs['engine'], + 'S4 exact after-cleanup engine descriptor') require(execution['error'] is None and execution['guard_errors'] == [] and execution['cleanup_errors'] == [] and [p['phase'] for p in execution['phases']] == ['import', 'parse', 'writer', 'reader'], 'S4 native phase/guard failure') for phase in execution['phases']: diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/S4_ROOT_RUN.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/S4_ROOT_RUN.md index f55ab08..acbd1e0 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/S4_ROOT_RUN.md +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/S4_ROOT_RUN.md @@ -14,20 +14,24 @@ provenance only. Never open their original production locations. export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build export E=docs/plans/original-completion-2026-10-02/execution/p04-01 export PYTHONDONTWRITEBYTECODE=1 -mkdir "$B/audit-support/p04-s4-source-r24" -cp "$E/s4/run_s4_native.py" "$E/s4/p04_s4_power_probe.gd" "$E/s4/S4_ROOT_RUN.md" "$B/audit-support/p04-s4-source-r24/" -python3 "$B/audit-support/p04-s4-source-r24/run_s4_native.py" \ +python3 "$E/s4/engine_identity_negative_fixtures.py" +python3 "$E/s4/receipt_lifecycle_fixtures.py" +mkdir "$B/audit-support/p04-s4-source-r26" +cp "$E/s4/run_s4_native.py" "$E/s4/p04_s4_power_probe.gd" "$E/s4/S4_ROOT_RUN.md" "$B/audit-support/p04-s4-source-r26/" +python3 "$B/audit-support/p04-s4-source-r26/run_s4_native.py" \ --project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \ - --output "$B/p04-s4-native-r24" --plan "$B/p04-s4-plan-r24.json" \ + --output "$B/p04-s4-native-r26" --plan "$B/p04-s4-plan-r26.json" \ --godot /Applications/Godot.app/Contents/MacOS/Godot --check-only -python3 "$B/audit-support/p04-s4-source-r24/run_s4_native.py" \ +python3 "$B/audit-support/p04-s4-source-r26/run_s4_native.py" \ --project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \ - --output "$B/p04-s4-native-r24" --plan "$B/p04-s4-plan-r24.json" \ + --output "$B/p04-s4-native-r26" --plan "$B/p04-s4-plan-r26.json" \ --godot /Applications/Godot.app/Contents/MacOS/Godot ``` -The first invocation only prepares the pinned plan: zero engine launches. The -second is one collection, exactly four sequential owned processes: import, +Root must stop if either fixture or check-only fails; the commands are not an +unconditional batch. Both fixtures start zero engines. The check-only invocation +only prepares the pinned plan: zero engine launches. The final invocation +is one collection, exactly four sequential owned processes: import, parse, Writer, Reader. Each retains the framework's native diagnostics, ownership, exit/end checks, full stream SHA, default-player protection, private profiles, source-before/after guards and cleanup. Failure stops the collection; @@ -37,7 +41,7 @@ inputs. Reader uses only the existing IO1 formal package mapping. Exact input footprints: -- Current wrapper SHA256 is recorded in `../REPAIR24_STATUS.md`. The plan pins +- Historical wrapper hashes do not pin this repair. The fresh plan pins its exact bytes and Stage R checks them against the current source checkout. - `B/audit-support/p04-r17-full-inputs-r01/MANIFEST.json`, SHA256 `a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8`. @@ -48,7 +52,8 @@ Exact input footprints: - The three staged S4 files above, pinned by the plan, and the installed executable `/Applications/Godot.app/Contents/MacOS/Godot`, pinned directly against the constant SHA before launch, during collection, and explicitly after cleanup. - Before/during/after descriptors are retained in runner-input and execution. The only stage override + Runner-input retains an immutable initial descriptor and observation prefix; + execution and engine-identity retain the final before/during/after identity. The only stage override is `tests/s3/test_runtime_staff_assignment.gd` with the S4 probe. Harness and kitchen fixture are the unchanged product files; inherited sources are included in the full1338 map. The parse roster is all316 `.gd` inputs. @@ -64,8 +69,8 @@ Exact input footprints: proof and remains ineligible. Its diagnostics are at `audit-support/p04-root-d393-s4-r01/ENGINE_STAGING_DIAGNOSTICS.json`. -Exact output footprints are `B/p04-s4-plan-r24.json` and the fresh -`B/p04-s4-native-r24/**`: private selected source stage, complete phase receipts +Exact output footprints are `B/p04-s4-plan-r26.json` and the fresh +`B/p04-s4-native-r26/**`: private selected source stage, complete phase receipts and logs, raw JSON/Variant points and all phase observations, full source maps, PID and cleanup receipts. `formal/{writer,reader}/S4-{boundary,held,recovered}/` mirrors all four formal members (`journal.log`, `manifest.json`, `thumbnail.png`, @@ -107,8 +112,115 @@ restore and is not part of Runtime snapshot equality. The derived `related_facility_operating` flag is measured separately and checked after restore. This source-only dispatch authorizes no worker execution. Root stages the three -files, creates the r24 plan with --check-only, then collects once to the fresh -r24 output. Do not rerun S1-S3, C or full178. No engine copy, signing, xattr, +files, creates the r26 plan with --check-only, then collects once to the fresh +r26 output. Do not read or write r24 outputs, cleanup, stages or plans. +Do not rerun S1-S3, C or full178. No engine copy, signing, xattr, codesign call or security-setting change is part of this procedure. Native evidence, registered validation, independent review, support53cb rebind and formal acceptance remain pending. D05 remains pending. + +## REPAIR26 receipt lifecycle audit (static; NOT RUN) + +All names below are relative to the fresh output unless marked otherwise. +`r.write` is the unchanged exclusive `open('x')` writer in +`tools/run_all_tests.py`. No retry, overwrite, replace or atomic rename is used +for a JSON proof. A write attempt may fail or leave a partial file; it is never +retried. “At most once” describes wrapper control flow, not a durability claim. +The initial input is serialized once with `engine=engine_descriptor`, +`engine_identity_before=engine_descriptor` and a deep-copied initial identity. +`repin()` itself is unchanged (it returns None); each per-phase receipt uses +the descriptor it just appended and the index of that exact observation. + +| Writer / destination | Maximum attempts per run / uniqueness | Success and failure reachability | +| --- | --- | --- | +| `r.write(args.plan)` outside output | 1, check-only branch returns immediately | Native branch only reads the exact plan; existing plan rejected in check-only | +| `runner-started.json` | 1 | First write inside protected setup; exception enters cleanup | +| `source_before.json` | 1 | Setup before source copy; no fallback write | +| `selected-source-map.json` | 1 | Setup before source copy; no fallback write | +| `r.copy_source` to `stage/` | 1 per unique source-map key | Fresh stage; loop stops on setup failure; source files are not proof receipts | +| `write_bytes` to `bootstrap/` | 1 per pinned bootstrap key | The reviewed map names `profile_guard.gd` and `parse_full_dependencies.gd`; setup only | +| `/bin/cp -c` to `stage/.godot/` | 1 per cache inventory key | Optional eligible-cache branch only; fresh mode never accesses cache | +| `cache-reuse.json` | 0 or 1 | Only after all optional cache clones and guards | +| `parse-paths.json` | 1 | Setup after 316-script guard | +| `contracts.json` | 1 | Setup after unchanged suite-policy guard | +| `runner-input.json` | 1 | Setup; no final read/rewrite; absent if earlier setup fails | +| `engine-identity-before-.json` | 1 each for import, parse, writer, reader | Immediately after that phase's repin; failure stops loop, never resumes it | +| `write_bytes` to `writer-combined-raw.log`, `reader-combined-raw.log` | 1 each | Binary copy immediately after returned phase row, even a failed row if it has a log; later phases never reuse names | +| `-actual-phase.json` | 1 each for four unique phases | Before `require(row['passed'])`; mid-loop failure retains its row and stops later phases | +| `outer-owned-finally.json` | 1 | Single finally after setup/loop success or failure; cleanup called once; cleanup exception recorded; receipt write exception becomes guard error | +| `source_after.json` | 0 or 1 | Single post-cleanup guarded block, after fixed_guard/source_refs succeed; later guard failure does not re-enter or retry the write | +| `shutil.copyfile` to `formal///` | 1 per tuple | Only preliminary pass; two unique roles, unique slot map keys, four unique member keys, fresh target directory; exception stops collection | +| `formal-relocation.json` | 0 or 1 | Only after successful complete formal mirror | +| `raw-inventory.json` | 0 or 1 | Once after mirror branch, including setup/phase/cleanup failure; earlier output-collection exception can skip it | +| `PID-set.json` | 0 or 1 | Once after raw inventory; earlier output-collection exception can skip it | +| `engine-identity.json` | 1 | One final attempt after cleanup and both after-cleanup/after-output-collection repin attempts, including caught setup/phase/cleanup/collection failures; own write failure records guard error and makes both passed fields false | +| `execution.json` | 1 | After identity attempt, with final observations, identity and guard errors | +| `runner-ended.json` | 1 | After execution; no retry if final receipt storage itself fails | + +The wrapper has no `write_text` call. The unchanged `execute_phase` dependency +also writes the following. Its shared text log is an explicit exception to +write-once naming, and is not presented as two independent phase proofs. + +| Dependency destination | Writer and lifecycle | +| --- | --- | +| `-startup-profile-before.json` | Exclusive `write`, once per distinct phase before engine start | +| `-profile.json` | Guard report path for parse/writer/reader, one per phase; actual Guard checks file absence before writing | +| `starters/.gd` | `write_text`, once each for writer/reader | +| `/combined-raw.log` | `write_text`, once each for import/parse | +| `tests__s3__test_runtime_staff_assignment.gd.log` (general `out/.log`) | Both writer and reader call `write_text` on this same per-test name; reader replaces the shared text. Wrapper preserves separate binary copies as `writer-combined-raw.log` and `reader-combined-raw.log`, retaining `strict_runner_log_path` and the original log hash | +| `-receipt.json` | Exclusive `write`, once per phase after its finally and guards | +| `stage/project.godot` | `write_bytes` for private startup settings and restoration in each phase finally; mutable private source, not immutable proof | +| `/**`, private profiles, raw QA outputs | Delegated owned-native capture/engine/QA outputs; not wrapper `r.write`/`write_bytes`/`write_text` calls; their existing ownership/diagnostic/formal guards remain unchanged | + +Path coverage: success traverses each distinct phase once. A failed parse row is +recorded, then `require(row['passed'])` jumps to the single finally; no writer or +reader names are used. A protected setup failure also enters that finally, then +attempts post-cleanup guards and final identity/execution/end receipts. Cleanup +exceptions are caught separately, set `cleanup_errors`, and do not prevent final +receipts. A post-cleanup fixed/source guard failure is caught without retrying +`source_after`; formal/output-collection exceptions now become guard errors and +still reach the final repin and identity receipt. A final identity write failure +is caught without touching existing bytes and forces failure in execution/end. + +Admission failures before the protected setup (invalid plan/product/engine, +live engine, insufficient disk, existing output, directory creation failure) +remain fail-closed and do not promise final receipts. They have no repeated +wrapper proof write. Persistent storage failure writing execution/end can still +prevent a complete terminal receipt pair; no code can guarantee receipt storage +when that storage fails. Such runs cannot provide accepted evidence. + +## Fixture scope and remaining gaps + +`receipt_lifecycle_fixtures.py` imports the real wrapper and standard writer. +It builds 1338 synthetic source entries including 316 scripts in resolved fresh +temporary paths; engine identity, template_guard, disk/default-player, helpers +and phase execution are mocked. Four distinct fake PIDs and complete synthetic +formal slot members exercise wrapper guards and copying without starting an +engine. The actual identity section of `S4Series.__init__` is compiled from its +AST, and actual `verify_s4_engine` checks a non-executable temporary data file +under fixture-only patched constants. Other verifier sections are not claimed +as covered by this synthetic fixture. + +Cases cover success after check-only, parse failure, cleanup exception, setup +copy failure, post-cleanup collection failure and final identity write failure. +Every exclusive write attempt is counted. Input bytes are compared with their +creation bytes before negative mutation. Fresh-output refusal, each missing or +tampered phase receipt and mismatched initial-before are negative checks. The +fixture's deliberate proof mutations are confined to its disposable temporary +files. No registered plan/output/stage is read or changed. + +Reasoning only about the 7af6 wrapper: import writes `engine-identity.json` via +exclusive create. Before parse, the same name raises FileExistsError, so the +four-phase success assertion cannot pass. After cleanup the old final write +tries that same name again and escapes main, preventing final execution/end. +Even if that collision alone were removed, the old runner-input final rewrite +would hit the exclusive writer, and the required initial/per-phase receipts +are absent. No 7af6 execution was performed to establish this reasoning. + +Worker performed source inspection and file edits only: no fixtures, check-only, +tests, Git, engines, Runner, agents or commits. Root still must run both fixtures +and check-only before any native r26 collection, then exact-version verification +and independent review. The registered validation gate remains unchanged at +7af6, exit 2, fresh MANIFEST missing; this is the supplied gate record, not a new +worker run. Native/product proof, full formal checks, D05 and original P04-01 +acceptance remain pending. This repair is submitted preparation, not completion. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/engine_identity_negative_fixtures.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/engine_identity_negative_fixtures.py index f1a7403..126b29a 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/engine_identity_negative_fixtures.py +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/engine_identity_negative_fixtures.py @@ -33,7 +33,7 @@ def run(): # Same bytes/SHA cannot admit an own-B bare copy or another outside-B path. for label, path in [ - ("copied bare engine in B with same SHA", s4.BUILD / "audit-support/p04-s4-source-r24/Godot"), + ("copied bare engine in B with same SHA", s4.BUILD / "audit-support/p04-s4-source-r26/Godot"), ("other outside-B path", Path("/Applications/OtherGodot.app/Contents/MacOS/Godot")), ]: other = dict(pin, path=str(path)) diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/receipt_lifecycle_fixtures.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/receipt_lifecycle_fixtures.py new file mode 100644 index 0000000..4a26224 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/receipt_lifecycle_fixtures.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +"""Root-only synthetic receipt lifecycle fixtures. No engine/process is started. + +Uses the real main, exclusive JSON writer, source_refs and inventories. The +identity section is compiled directly from S4Series.__init__, not reimplemented. +This is not native/product/ownership/formal acceptance evidence. +""" +import ast +from collections import Counter +from contextlib import ExitStack, redirect_stdout +import copy +import importlib.util +import io +import json +from pathlib import Path +import shutil +import sys +import tempfile +from types import SimpleNamespace +from unittest.mock import patch + +sys.dont_write_bytecode = True +HERE = Path(__file__).resolve().parent +ROOT = next(p for p in HERE.parents if (p / 'tools/run_all_tests.py').is_file()) + + +def load(name, path): + spec = importlib.util.spec_from_file_location(name, path) + value = importlib.util.module_from_spec(spec) + spec.loader.exec_module(value) + return value + + +s4 = load('receipt_s4', HERE / 'run_s4_native.py') +r = load('receipt_standard', ROOT / 'tools/run_all_tests.py') +series = load('receipt_series', HERE.parent / 'p04_native_series.py') + + +def identity_code(): + tree = ast.parse((HERE.parent / 'p04_native_series.py').read_text()) + cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == 'S4Series') + init = next(n for n in cls.body if isinstance(n, ast.FunctionDef) and n.name == '__init__') + start = next(i for i, n in enumerate(init.body) if isinstance(n, ast.Expr) and + isinstance(n.value, ast.Call) and isinstance(n.value.func, ast.Name) and + n.value.func.id == 'verify_s4_engine') + end = next(i for i in range(start + 1, len(init.body)) + if "execution['error']" in ast.unparse(init.body[i])) + return compile(ast.Module(body=init.body[start:end], type_ignores=[]), '', 'exec') + + +def verify_identity(out, descriptor): + # The real engine verifier reads only the synthetic ordinary file here. + # Production constants/functions on disk are never changed. + with patch.object(series, 'S4_ENGINE_PATH', descriptor['path']), \ + patch.object(series, 'S4_ENGINE_SHA256', descriptor['sha256']): + namespace = dict(vars(series), inputs=r.read(out / 'runner-input.json'), + execution=r.read(out / 'execution.json'), + self=SimpleNamespace(read=lambda name: (out / name).read_bytes())) + exec(identity_code(), namespace) + + +def rejected(action): + try: + action() + except (ValueError, AssertionError, OSError, KeyError, TypeError): + return + raise AssertionError('negative fixture was accepted') + + +def case(base, failure=None): + product = base / 'product' + support = base / 'support' + staged = base / 'source' + for path in (product, support, staged): + path.mkdir(parents=True) + + def put(path, data): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + + # Real 1338-entry source_refs and 316-entry script roster, synthetic bytes. + scripts = [s4.TEST, 'tests/transport/test_runtime_transport.gd', + 'tests/staff/test_rescue_elevator.gd'] + scripts += ['scripts/fixture_%03d.gd' % i for i in range(313)] + for name in scripts: + put(product / name, b'extends Node\n') + for name in ('project.godot', 'export_presets.cfg', 'Makefile', 'AGENTS.md'): + put(product / name, b'fixture\n') + put(product / 'tools/run_all_tests.py', (ROOT / 'tools/run_all_tests.py').read_bytes()) + for i in range(1017): + put(product / ('data/fixture_%04d.txt' % i), b'fixture\n') + assert len(r.source_refs(product)) == 1338 + qa = staged / 'p04_s4_power_probe.gd' + put(qa, b'extends Node\n# synthetic override\n') + put(staged / 'run_s4_native.py', (HERE / 'run_s4_native.py').read_bytes()) + put(staged / 'S4_ROOT_RUN.md', b'synthetic fixture material\n') + contracts = base / 'contracts.json' + r.write(contracts, {}) + material = base / 'material.json' + r.write(material, {}) + r.write(support / 'suite-policy.json', {'suite_policies': {}}) + r.write(support / 'bootstrap-scripts.json', { + 'parse_full_dependencies.gd': {'text': 'fixture\n', 'sha256': r.sha(product / 'Makefile')}}) + engine = base / 'synthetic-engine-data' + put(engine, b'not an executable; never launched\n') + st = engine.stat() + descriptor = dict(path=str(engine), resolved_path=str(engine), bytes=st.st_size, + sha256=r.sha(engine), mtime_ns=st.st_mtime_ns, mode=st.st_mode, + device=st.st_dev, inode=st.st_ino, ctime_ns=st.st_ctime_ns) + out = base / 'p04-s4-native-r26' + plan = base / 'p04-s4-plan-r26.json' + attempts, created, phases = Counter(), {}, [] + cleanup_calls = [] + real_write, real_inventory = r.write, r.inventory + protected = dict(path=str(base / 'default-player'), existed=False, files={}, logs_and_locks_included=True) + + def write(path, value): + path = Path(path) + attempts[path] += 1 + if failure == 'identity-write' and path == out / 'engine-identity.json': + raise OSError('injected final identity write error') + real_write(path, value) # unchanged open('x'), including collisions + created[path] = path.read_bytes() + + def finish(owned): + cleanup_calls.append(list(owned)) + if failure == 'cleanup': + raise RuntimeError('injected cleanup error') + return [dict(pid=p.pid, engine_ended=True) for p in owned], [] + + outer = SimpleNamespace(native=SimpleNamespace(helper={'live_engines': lambda: []}), + OWNED=[SimpleNamespace(pid=91001+i) for i in range(4)], + finish_captured=finish, run_phase=lambda *a, **k: None) + + def execute(engine_arg, stage, output, phase, test, selected, expected, previous, + policy, contracts_arg, paths, public, player, root, *helpers): + phases.append(phase) + assert engine_arg == str(engine) and output == out and player == protected + assert r.source_refs(root) == selected['root_before'] + assert r.inventory(stage, stage=True) == dict(expected, **previous) + pid = outer.OWNED[len(phases)-1].pid + row = dict(phase=phase, passed=not (failure == 'parse' and phase == 'parse'), actual_pid=pid) + if test: + slots = {} + for slot in s4.SLOTS: + directory = base / 'profiles' / phase / slot + for member in ('journal.log', 'manifest.json', 'thumbnail.png', 'world.bin'): + put(directory / member, (slot + '/' + member).encode()) + slots[slot] = str(directory) + (out / 'raw').mkdir(exist_ok=True) + write(out / ('raw/' + phase + '.json'), dict(checks=1, failures=0, branches=['S4'], + slots=slots, pid=pid, profile=str(base / 'profiles' / phase))) + # Reproduce the runner's shared text log and let main copy its bytes. + log = test.replace('/', '__') + '.log' + (out / log).write_text(phase + '\n') + row['log'] = log + return row + + def inventory(path, **kwargs): + if failure == 'output' and Path(path) == out / 'raw': + raise OSError('injected post-cleanup inventory error') + return real_inventory(path, **kwargs) + + def copy_source(source, target): + if failure == 'setup': + raise OSError('injected setup copy error') + shutil.copyfile(source, target) + + def modules(name, path): + if name == 's4_standard_runner': + return r + if name == 's4_owned_outer': + return outer + if name == 's4_markers': + return SimpleNamespace(load_contracts=lambda path: {}) + if name in ('s4_diagnostics', 's4_owners', 's4_audio'): + return SimpleNamespace() + raise AssertionError('unexpected helper import: ' + name) + + with ExitStack() as stack: + for obj, values in [(s4, dict(BUILD=base, PRODUCT=product, HERE=staged, QA=qa, + ENGINE=engine, CONTRACTS=contracts, MATERIALS=[material], + __file__=str(staged / 'run_s4_native.py'), module=modules)), + (r, dict(SUPPORT=support, write=write, execute_phase=execute, + copy_source=copy_source, inventory=inventory))]: + for key, value in values.items(): + stack.enter_context(patch.object(obj, key, value)) + pin_mock = stack.enter_context(patch.object(s4, 'engine_pin', return_value=descriptor)) + guard_mock = stack.enter_context(patch.object(s4, 'template_guard', return_value=None)) + player_mock = stack.enter_context(patch.object(r, 'default_player', return_value=protected)) + disk_mock = stack.enter_context(patch.object(s4.shutil, 'disk_usage', + return_value=SimpleNamespace(free=10 * 1024**3))) + stack.enter_context(redirect_stdout(io.StringIO())) + argv = ['run_s4_native.py', '--project-root', str(product), '--output', str(out), + '--plan', str(plan), '--godot', str(engine)] + with patch.object(sys, 'argv', argv + ['--check-only']): + assert s4.main() == 0 + assert not phases and not cleanup_calls and not out.exists() + with patch.object(sys, 'argv', argv): + result = s4.main() + rejected(s4.main) # same output must be rejected before any new writes + assert pin_mock.called and guard_mock.called and player_mock.called and disk_mock.called + + assert result == (0 if failure is None else 1) + assert len(cleanup_calls) == 1 + assert all(count == 1 for count in attempts.values()), attempts + for leaf in ('outer-owned-finally.json', 'engine-identity.json', 'execution.json', 'runner-ended.json'): + assert attempts[out / leaf] == 1 + if failure != 'identity-write' or leaf != 'engine-identity.json': + assert (out / leaf).is_file() + if failure != 'setup': + assert (out / 'runner-input.json').read_bytes() == created[out / 'runner-input.json'] + assert r.read(out / 'runner-input.json')['engine_identity_initial']['after'] is None + execution = r.read(out / 'execution.json') + assert execution['passed'] is (failure is None) + assert 'FileExistsError' not in json.dumps(execution) + assert phases == ([] if failure == 'setup' else ['import', 'parse'] if failure == 'parse' + else ['import', 'parse', 'writer', 'reader']) + if failure == 'cleanup': + assert execution['cleanup_errors'] == ['injected cleanup error'] + if failure in ('output', 'identity-write'): + assert execution['guard_errors'] + if failure is None: + assert len(list(out.glob('engine-identity*.json'))) == 5 + verify_identity(out, descriptor) + assert (out / 'writer-combined-raw.log').read_bytes() == b'writer\n' + assert (out / 'reader-combined-raw.log').read_bytes() == b'reader\n' + # Mutations below affect only this disposable fixture's synthetic proofs. + for phase in ('import', 'parse', 'writer', 'reader'): + path = out / ('engine-identity-before-' + phase + '.json') + original = path.read_bytes() + path.write_text('{}') + rejected(lambda: verify_identity(out, descriptor)) + path.unlink() + rejected(lambda: verify_identity(out, descriptor)) + path.write_bytes(original) + path = out / 'runner-input.json' + initial = r.read(path) + changed = copy.deepcopy(initial) + changed['engine_identity_initial']['before']['inode'] += 1 + path.write_text(json.dumps(changed)) + rejected(lambda: verify_identity(out, descriptor)) + path.write_bytes(created[path]) + verify_identity(out, descriptor) + return dict(case=failure or 'success', passed=True, native_engine_launches=0) + + +def run(): + with tempfile.TemporaryDirectory(prefix='s4-receipt-fixtures-') as temporary: + return [case(Path(temporary).resolve() / (failure or 'success'), failure) + for failure in (None, 'parse', 'cleanup', 'setup', 'output', 'identity-write')] + + +if __name__ == '__main__': + print(json.dumps(dict(results=run(), formal_acceptance=False), indent=2)) diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/run_s4_native.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/run_s4_native.py index 27a19b4..330184e 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/run_s4_native.py +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s4/run_s4_native.py @@ -2,6 +2,7 @@ """S4-only native entry. --check-only never loads ownership helpers or starts Godot.""" from pathlib import Path import argparse +import copy import hashlib import importlib.util import json @@ -98,8 +99,8 @@ def engine_pin(path, expected=None): def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--project-root", type=Path, default=PRODUCT) - parser.add_argument("--output", type=Path, default=BUILD / "p04-s4-native-r24") - parser.add_argument("--plan", type=Path, default=BUILD / "p04-s4-plan-r24.json") + parser.add_argument("--output", type=Path, default=BUILD / "p04-s4-native-r26") + parser.add_argument("--plan", type=Path, default=BUILD / "p04-s4-plan-r26.json") parser.add_argument("--cache-run", type=Path, default=None, help="optional passed registered cache; default is fresh, without cache access") parser.add_argument("--godot", type=Path, default=ENGINE) @@ -222,9 +223,9 @@ def main(): env.update(THE_TREE_S4_EVIDENCE=str(out / "raw"), THE_TREE_S4_MODE=folder.name) return original_phase(argv, folder, timeout, env, **kwargs) outer.run_phase = run_phase - r.write(out / "runner-started.json", dict(started=r.now(), owner_runner_pid=os.getpid(), - plan=pin(args.plan), engines_launched=0)) try: + r.write(out / "runner-started.json", dict(started=r.now(), owner_runner_pid=os.getpid(), + plan=pin(args.plan), engines_launched=0)) r.write(out / "source_before.json", source_before) r.write(out / "selected-source-map.json", refs) # Private source stage first, then literal reviewed bootstrap. @@ -262,7 +263,9 @@ def main(): r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override") r.write(out / "contracts.json", contracts) r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(), - wrapper_sha256=r.sha(__file__), QA=pin(QA), engine=engine_descriptor, engine_identity=engine_identity, runner_sha256=r.sha(root / "tools/run_all_tests.py"), + wrapper_sha256=r.sha(__file__), QA=pin(QA), engine=engine_descriptor, + engine_identity_before=engine_descriptor, engine_identity_initial=copy.deepcopy(engine_identity), + runner_sha256=r.sha(root / "tools/run_all_tests.py"), default_player_before=protected, fixed_input_pins=fixed, functional_watchdog_seconds=1200, source_adoption=False)) diagnostics = module("s4_diagnostics", r.SUPPORT / "diagnostic_gate.py") @@ -278,12 +281,15 @@ def main(): r.require(not (stage / ".godot").exists() and not (stage / ".godot").is_symlink(), "fresh private .godot absent at actual import") repin("before-" + phase) - r.write(out / "engine-identity.json", engine_observations) + r.write(out / ("engine-identity-before-" + phase + ".json"), + dict(engine_observations[-1], observation_index=len(engine_observations)-1)) row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected, previous, policy, contracts, paths, public, protected, root, outer, markers, diagnostics, owners, audio) phases.append(row) - # execute_phase uses the same test log name; retain each phase's exact log too. + # execute_phase writer/reader share out/.log via write_text. + # Keep binary per-phase copies under these existing unique names. + # The shared runner log is not write-once; see S4_ROOT_RUN.md. if test and row.get("log"): (out / (phase + "-combined-raw.log")).write_bytes((out / row["log"]).read_bytes()) row["strict_runner_log_path"] = row["log"] @@ -349,36 +355,43 @@ def main(): # IO1 has already copied all four members into a separate Reader profile. # Mirror those exact complete directories to own B for read-only R verification. # Original profile paths are provenance after this step, never verifier inputs. - if passed: - relocated = {} - for role in ("writer", "reader"): - receipt = r.read(out / ("raw/" + role + ".json")) - relocated[role] = {} - for slot, original in receipt["slots"].items(): - target = out / "formal" / role / slot - target.mkdir(parents=True) - members = r.inventory(Path(original)) - r.require(set(members) == {"journal.log", "manifest.json", "thumbnail.png", "world.bin"}, "all formal members") - for member in members: - shutil.copyfile(Path(original) / member, target / member) - r.require(r.inventory(target) == members == formal_before[slot], "whole formal mirror") - relocated[role][slot] = dict(provenance=original, relative=str(target.relative_to(out)), members=members) - r.write(out / "formal-relocation.json", relocated) - r.write(out / "raw-inventory.json", r.inventory(out / "raw")) - r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids)) + try: + if passed: + relocated = {} + for role in ("writer", "reader"): + receipt = r.read(out / ("raw/" + role + ".json")) + relocated[role] = {} + for slot, original in receipt["slots"].items(): + target = out / "formal" / role / slot + target.mkdir(parents=True) + members = r.inventory(Path(original)) + r.require(set(members) == {"journal.log", "manifest.json", "thumbnail.png", "world.bin"}, "all formal members") + for member in members: + shutil.copyfile(Path(original) / member, target / member) + r.require(r.inventory(target) == members == formal_before[slot], "whole formal mirror") + relocated[role][slot] = dict(provenance=original, relative=str(target.relative_to(out)), members=members) + r.write(out / "formal-relocation.json", relocated) + r.write(out / "raw-inventory.json", r.inventory(out / "raw")) + r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids)) + except BaseException as caught: + guards.append("output collection: " + type(caught).__name__ + ": " + str(caught)) + passed = False + execution["passed"] = False try: repin("after-output-collection") except BaseException as caught: guards.append(str(caught)) passed = False execution["passed"] = False - r.write(out / "engine-identity.json", engine_observations) + try: + r.write(out / "engine-identity.json", + dict(observations=engine_observations, identity=engine_identity)) + except BaseException as caught: + guards.append("engine identity receipt: " + type(caught).__name__ + ": " + str(caught)) + passed = False + execution["passed"] = False execution["engine_observations"] = engine_observations execution["engine_identity"] = engine_identity - if (out / "runner-input.json").is_file(): - final_inputs = r.read(out / "runner-input.json") - final_inputs["engine_identity"] = engine_identity - r.write(out / "runner-input.json", final_inputs) r.write(out / "execution.json", execution) r.write(out / "runner-ended.json", dict(execution, ended=r.now())) print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))