From 8e25213e91584c1b625369144408afbd88a0306e Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Sun, 18 Oct 2026 12:00:07 +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 --- .../p04-01/REPAIR36_STATIC_CHECKS.json | 41 ++++++ .../execution/p04-01/REPAIR36_STATUS.md | 28 +++++ .../execution/p04-01/s5/S5_ROOT_RUN.md | 42 ++++++- .../execution/p04-01/s5/run_s5_native.py | 101 ++++++++++++++- .../execution/p04-01/s5/s5_uid_controls.py | 119 ++++++++++++++++++ 5 files changed, 323 insertions(+), 8 deletions(-) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATIC_CHECKS.json create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATUS.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p04-01/s5/s5_uid_controls.py diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATIC_CHECKS.json b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATIC_CHECKS.json new file mode 100644 index 0000000..5453269 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATIC_CHECKS.json @@ -0,0 +1,41 @@ +{ + "py_compile_ast": [ + { + "path": "docs/plans/original-completion-2026-10-02/execution/p04-01/s5/run_s5_native.py", + "sha256": "e96b2793d7352ac52e16505c9c4cde3d151497763be3cb47926338df070b05e5", + "py_compile": "passed", + "ast": "passed", + "top_level_functions": [ + "r17_relocation_guard", + "probe_guard", + "module", + "pin", + "load_uid_helper", + "make_uid_policy", + "engine_pin", + "select_mode", + "fresh_q_guard", + "main" + ] + }, + { + "path": "docs/plans/original-completion-2026-10-02/execution/p04-01/s5/s5_uid_controls.py", + "sha256": "17069b87b98c385e1298152c9c5c2f23c8710b79eb19aef073c64ce215f6c902", + "py_compile": "passed", + "ast": "passed", + "top_level_functions": [ + "module", + "main" + ] + }, + { + "path": "docs/plans/original-completion-2026-10-02/execution/p04-01/s5/s5_wrapper_fixtures.py", + "sha256": "1a4daebf46051e975a9cc740660c4672371ea6a9e6cb04cbc1d458ece5f0151d", + "py_compile": "passed", + "ast": "passed", + "top_level_functions": [ + "main" + ] + } + ] +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATUS.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATUS.md new file mode 100644 index 0000000..631f3cd --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/REPAIR36_STATUS.md @@ -0,0 +1,28 @@ +# REPAIR36 — bounded preparation submitted, no acceptance credit + +Changes are limited to the S5 wrapper, new pure UID controls and these documents. +Actual-Q AST helper loading, finite derived UID planning, import sealing, +private-globals phase instrumentation, phase receipts and cleanup rechecks +supplement the existing expected inventory. Historical selection, existing +require gates, ownership/PID, engine, cache and formal mirror rules remain. +The helper is loaded from real Q before controls construct synthetic refs. + +Only py_compile and AST checks are executed and recorded in +REPAIR36_STATIC_CHECKS.json. Wrapper fixtures, UID controls, check-only plans, +Git negatives and every native diagnostic are unexecuted. No functional pass, +full regression, 15+EV batch, original credit or completion is claimed. +No Git, engine, Runner or other agent operations were performed. A shell +heredoc edit failed before Python execution; edits then used apply_patch. + +Context gap: Q runner API was read from +build/owner-handoff/p04-after-p26-closure-r01/q35-uid-diagnostic/run_all_tests.py.reference; +Root must confirm the actual Q runner matches that mirror (320ca668…) +at runtime. Helper implementation was read from the adjacent P13 reference; +runtime loading uses only the actual pinned Q, never either mirror. + +Pending: S5 destruction/midnight/exit drivers; S1–S4/collector fresh Q; +derived 184 suites / 329 parse; truthful index; Root exact-candidate qualification. +Root order is py_compile → wrapper fixtures → UID controls with explicit scratch +→ fresh check-only plan → exactly one new-name diagnostic. The failed r35 +diagnostic is preserved. All 7 domains + EV, formal 16/88, original 30/88, +P04 credit 0, all six D05 approvals pending, M c0ea009 and allowed_paths stay fixed. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/S5_ROOT_RUN.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/S5_ROOT_RUN.md index 4c4e9dd..11c07be 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/S5_ROOT_RUN.md +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/S5_ROOT_RUN.md @@ -15,13 +15,13 @@ all three full lowercase HEX40 inputs. R17 MANIFEST and repair22 power-functions JSON are historical_not_inputs in fresh-Q plans, never fresh-Q fixed pins. No old S1–S4 record is renamed or reused as fresh proof. -REPAIR35 is check-only only; no native/batch admission until S5 +REPAIR36 supplies a bounded UID diagnostic, not native/batch admission. Full admission still awaits S5 destruction/midnight/exit, S1–S4/collector fresh Q, 184 suites / 329 parse and the truthful index are ready. Root run order and exact-candidate qualification also remain pending. The native implementation is retained for later Root admission; this change and a check-only plan give no admission or acceptance credit. -After final S35, Root alone performs these steps (not performed by the worker): +After the final submission, Root alone prepares Q using these steps (not performed by the worker): 1. Set S to the final submission and M to `c0ea009b28a7649aca6a433a1a24dc0be5e7ab81`; derive Qt with @@ -31,15 +31,15 @@ After final S35, Root alone performs these steps (not performed by the worker): checkout carrier of Q, never normal Runner C. R34 and Root normal_c_join remain unchanged. 3. Stage the s5 directory under Root's own B, the registered BUILD directory. -4. Run only the check-only path, with fresh output and plan paths: +4. Follow the REPAIR36 order below, with fresh output and plan paths: ```sh BUILD_OUTPUT_DIR="$B" python3 "$B/.../run_s5_native.py" \ --project-root "$wt" --main "$M" --submission "$S" --tested-tree "$Qt" \ - --check-only --plan "$B/p04-s5-plan-r35.json" --output "$B/p04-s5-native-r35" + --check-only --plan "$B/p04-s5-plan-r36.json" --output "$B/p04-s5-DIAG-r36" ``` -5. Run `s5_wrapper_fixtures.py` separately. Its JSON always has accepted=false. +5. Pure fixtures and controls follow the order below. Their JSON has accepted=false. 6. In separate controlled check-only cases, require rejection for: a dirty file; an untracked file; HEAD tree unequal to --tested-tree; wrong --submission; --main unequal to the frozen anchor. Preserve each actual rejection receipt @@ -55,4 +55,34 @@ S5 destruction, midnight and exit cases remain unimplemented. The preserved `PENDING.md` is historical partial31 text; this document records only the added, unexecuted power pair. D05 six approvals, A01, frozen M, 16/88 and P04 credit 0 are unchanged. All 7 domains + EV and original 30/88 remain fixed. Root alone -performs later qualification; no engine is authorized by REPAIR35. +performs later qualification; no engine is authorized for this worker. + +## REPAIR36 UID diagnostic protocol and Root order + +Protocol `p04-s5-fresh-q-uid-diagnostic-v1` is a diagnostic composite with +standard_execute_phase_context=false, native_admissible=false and +fresh_q_evidence=false. The wrapper extracts only uid_plan and uid_snapshot +from the actual pinned Q helper using AST, binding shared to the actual Q +runner. It does not execute the helper module. Plans derive finite UID owners +from staged refs; import alone seals exact UID bytes. Private function globals +replace declared_reports while preserving the execute_phase code object. +All four phase UID receipts and the post-cleanup seal are required. +Historical mode has no UID instrumentation and remains read-only. + +Root order, all unexecuted by this worker: + +1. py_compile the wrapper and both pure control files. +2. Run s5_wrapper_fixtures.py. +3. Run `python3 "$B/.../s5_uid_controls.py" --project-root "$wt" --scratch "$B/p04-s5-uid-controls-r36"`. + Scratch must be absolute, absent, outside Q, with an existing parent in BUILD. +4. Produce a fresh check-only plan with the command above. +5. Perform exactly one fresh diagnostic with the identical command and paths, + removing only --check-only. Use a new output name; preserve + `p04-s5-DIAG-r35` as FAILED. This is a bounded diagnostic, not full admission. + +Root must confirm the actual Q run_all_tests.py matches the supplied reference +(sha256 prefix 320ca668); the worker read only the mirror for Q API context. +Runtime helper fingerprints and source/provenance guards bind the actual Q. +No full regression, 15+EV batch or original credit is claimed. The S5 +destruction/midnight/exit drivers, S1–S4/collector fresh Q, derived 184 suites / +329 parse, truthful index and Root exact-candidate qualification remain pending. diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/run_s5_native.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/run_s5_native.py index 93f59aa..6caaa17 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/run_s5_native.py +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/run_s5_native.py @@ -2,6 +2,10 @@ """S5-only native entry. --check-only never loads ownership helpers or starts Godot.""" from pathlib import Path import argparse +import ast +import builtins +import inspect +import types import copy import hashlib import importlib.util @@ -74,6 +78,74 @@ def pin(path): data = path.read_bytes() return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest()) +def load_uid_helper(root, r, refs): + hp = root / "tools/run_p13_reference_tests.py" + descriptor = pin(hp) + r.require(all(descriptor[k] == refs["tools/run_p13_reference_tests.py"][k] + for k in ("bytes", "sha256")), "actual Q UID helper pin") + src = hp.read_bytes() + r.require(len(src) == descriptor["bytes"] and + hashlib.sha256(src).hexdigest() == descriptor["sha256"], "stable helper read") + tree = ast.parse(src, filename=str(hp)) + nodes = [] + for name in ("uid_plan", "uid_snapshot"): + matches = [n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == name] + r.require(len(matches) == 1, "exactly one UID helper: " + name) + nodes.append(matches[0]) + ns = {"__builtins__": builtins, "shared": r, "re": re, "stat": stat} + exec(compile(ast.Module(body=nodes, type_ignores=[]), str(hp), "exec"), ns) + binding = dict(helper_path="tools/run_p13_reference_tests.py", + helper_sha256=descriptor["sha256"], helper_bytes=descriptor["bytes"], + functions={n.name: hashlib.sha256(ast.get_source_segment(src.decode(), n).encode()).hexdigest() + for n in nodes}, shared_module="tools/run_all_tests.py", + shared_sha256=r.sha(root / "tools/run_all_tests.py")) + return ns["uid_plan"], ns["uid_snapshot"], binding + + +def make_uid_policy(r, uid_snapshot, source, plan, out): + state = dict(source=source, plan=plan, accepted=None, phase=None) + + def reports(stage, exp, previous, requested): + r.require(not previous and not requested, "S5 has no generated reports") + snap = uid_snapshot(stage, source, plan) + if state["accepted"] is None: + r.require(state["phase"] == "import", "UID generation only during import") + state["accepted"] = snap + exp.update({n: {k: p[k] for k in ("bytes", "sha256")} for n, p in snap.items()}) + r.write(out / "accepted-uids.json", snap) + r.require(snap == state["accepted"], "sealed UID bytes unchanged") + return r.declared_reports(stage, exp, previous, requested) + + execute = types.FunctionType(r.execute_phase.__code__, dict(vars(r), declared_reports=reports), + "execute_phase", r.execute_phase.__defaults__, r.execute_phase.__closure__) + r.require(execute.__code__ is r.execute_phase.__code__, "unchanged execute_phase code object") + + def phase_call(*args): + stage, phase_out, phase = args[1:4] + r.require(phase_out == out, "UID receipt output binding") + state["phase"] = phase + before, after = None, None + try: + if phase == "import": + r.require(state["accepted"] is None and not (out / "uid-plan.json").exists(), "single UID import") + r.require(r.inventory(stage, stage=True) == source, "original stage before import") + before = {} + r.write(out / "uid-plan.json", plan) + else: + before = uid_snapshot(stage, source, plan) + r.require(before == state["accepted"], "UID set before phase unchanged") + row = execute(*args) + if row["passed"]: + snap = uid_snapshot(stage, source, plan) + r.require(snap == state["accepted"], "UID set after phase unchanged") + after = snap + return row + finally: + r.write(out / (phase + "-uids.json"), dict(before=before, after=after)) + + return reports, phase_call, state + + def engine_pin(path, expected=None): path = Path(path) if path != ENGINE or any(p.is_symlink() for p in (path, *path.parents)): @@ -206,6 +278,7 @@ def main(): r17_relocation_guard(root) probe_guard(root) expected = {n: dict(bytes=p["bytes"], sha256=p["sha256"]) for n, p in refs.items()} + uid_source = copy.deepcopy(expected) contracts_path = root / C_REL if mode == "fresh-q" else CONTRACTS fixed = [pin(p) for p in ([] if mode == "fresh-q" else MATERIALS) + [ QA, Path(__file__), HERE / "S5_ROOT_RUN.md", @@ -241,6 +314,19 @@ def main(): cache_receipts=cache_receipts, cache_access=cache_note, phases=["import", "parse", "writer", "reader"], native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False) + if mode == "fresh-q": + uid_plan, uid_snapshot, binding = load_uid_helper(root, r, source_before) + uid_plan_map = uid_plan(refs) + r.require(len(uid_plan_map) > 0, "nonempty derived UID plan") + r.require(all(p["owner"] in refs and p["owner_sha256"] == refs[p["owner"]]["sha256"] + for p in uid_plan_map.values()), "UID plan owner bindings") + plan.update(protocol="p04-s5-fresh-q-uid-diagnostic-v1", diagnostic_composite=True, + standard_execute_phase_context=False, uid_plan=uid_plan_map, + uid_plan_count=len(uid_plan_map), instrumentation=dict( + wrapper_sha256=r.sha(__file__), + execute_phase_source_sha256=hashlib.sha256(inspect.getsource(r.execute_phase).encode()).hexdigest(), + declared_reports_source_sha256=hashlib.sha256(inspect.getsource(r.declared_reports).encode()).hexdigest(), + callback="private-globals declared_reports + phase wrapper", uid_helper=binding)) # Check-only only reads source/materials and writes the plan in task build. # Reads engine identity; never launches Godot or inspects players/processes. if args.check_only: @@ -254,6 +340,7 @@ def main(): raise ValueError("historical R17 is read-only") r.require(len(source_before) == provenance["selected_file_count"], "fresh-q selected count") r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first") + reports, phase_call, uid_state = make_uid_policy(r, uid_snapshot, uid_source, plan["uid_plan"], out) engine_observations = [] engine_identity = dict(before=engine_descriptor, during=[], after=None) def repin(label): @@ -272,6 +359,9 @@ def main(): r.require(fresh_q_guard(root, args.main, args.submission, args.tested_tree) == provenance, "fresh-q provenance changed") probe_guard(root) + hp = pin(root / binding["helper_path"]) + r.require(hp["bytes"] == binding["helper_bytes"] and hp["sha256"] == binding["helper_sha256"], + "UID helper unchanged") for value in fixed + cache_receipts: r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"]) fixed_guard() @@ -361,6 +451,7 @@ def main(): 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, + uid_helper=binding, uid_plan=plan["uid_plan"], uid_source_count=len(uid_source), functional_watchdog_seconds=1200, source_adoption=False)) diagnostics = module("s5_diagnostics", r.SUPPORT / "diagnostic_gate.py") owners = module("s5_owners", r.SUPPORT / "ordinary_proof_gate.py") @@ -377,7 +468,7 @@ def main(): repin("before-" + phase) 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, + row = phase_call(str(engine), stage, out, phase, test, selected, expected, previous, policy, contracts, paths, public, protected, root, outer, markers, diagnostics, owners, audio) phases.append(row) @@ -424,6 +515,9 @@ def main(): # Fixed guard always follows ownership cleanup, even if setup or an engine failed. try: fixed_guard() + if uid_state["accepted"] is not None: + r.require(uid_snapshot(stage, uid_source, plan["uid_plan"]) == uid_state["accepted"], + "UID seal after cleanup") after = r.source_refs(root) r.write(out / "source_after.json", after) r.require(after == source_before, "whole product source unchanged") @@ -441,7 +535,10 @@ def main(): pids = [p.pid for p in outer.OWNED] passed = (error is None and not guards and not cleanup_errors and len(phases) == 4 and all(p["passed"] for p in phases) and len(pids) == len(set(pids)) == 4 - and all(p["engine_ended"] for p in cleanup)) + and all(p["engine_ended"] for p in cleanup) + and uid_state["accepted"] is not None + and set(uid_state["accepted"]) == set(plan["uid_plan"]) + and all((out / (phase + "-uids.json")).is_file() for phase in plan["phases"])) execution = dict(passed=passed, error=error, guard_errors=guards, phases=phases, actual_captured_PIDs=pids, cleanup=cleanup, cleanup_errors=cleanup_errors, formal_before=formal_before, formal_after=formal_after, diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/s5_uid_controls.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/s5_uid_controls.py new file mode 100644 index 0000000..73f73f0 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s5/s5_uid_controls.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Pure Root-only UID controls; no engine or environment access.""" +import argparse +import copy +import importlib.util +import json +import os +from pathlib import Path +import sys + +sys.dont_write_bytecode = True + + +def module(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 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--project-root', required=True, type=Path) + parser.add_argument('--scratch', required=True, type=Path) + args = parser.parse_args() + root = args.project_root.resolve(strict=True) + r = module('s5_uid_q_runner', root / 'tools/run_all_tests.py') + w = module('s5_uid_wrapper', Path(__file__).with_name('run_s5_native.py')) + # Actual Q pins precede all synthetic data. No synthetic refs enter the loader. + uid_plan, uid_snapshot, binding = w.load_uid_helper(root, r, r.source_refs(root)) + scratch = args.scratch + r.require(scratch.is_absolute() and not scratch.exists() and not scratch.is_symlink(), + 'absolute fresh scratch') + parent = scratch.parent.resolve(strict=True) + r.require(parent.is_dir() and parent.is_relative_to(w.BUILD.resolve()) + and not (parent / scratch.name).is_relative_to(root), 'scratch parent inside BUILD outside Q') + scratch = parent / scratch.name + scratch.mkdir() + results = [] + + def fixture(label): + stage = scratch / label + stage.mkdir() + files = {'project.godot': b'config_version=5\n', + 'scripts/special/peeping_room_service.gd': b'extends Node\n', + 'tests/special/test_a.gd': b'extends SceneTree\n', + 'scripts/other.gd': b'extends RefCounted\n', + 'scripts/other.gd.uid': b'uid://existing\n'} + for name, data in files.items(): + path = stage / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + source = r.inventory(stage, stage=True) + refs = {n: dict(p, path=str(stage / n)) for n, p in source.items()} + plan = uid_plan(refs) + r.require(bool(plan) and all(p['owner_sha256'] == refs[p['owner']]['sha256'] + for p in plan.values()), 'derived synthetic plan') + for i, name in enumerate(plan): + (stage / name).write_bytes(('uid://generated' + str(i) + '\n').encode()) + return stage, source, plan + + stage, source, plan = fixture('positive') + snap = uid_snapshot(stage, source, plan) + r.require(set(snap) == set(plan), 'exact positive planned UID set') + results.append('positive') + + def negative(label, mutate): + stage, source, plan = fixture(label) + names = list(plan) + mutate(stage, source, plan, names) + try: + uid_snapshot(stage, source, plan) + except AssertionError: + results.append(label) + else: + raise AssertionError('control did not reject: ' + label) + + negative('extra', lambda s, a, p, n: (s / 'extra.uid').write_bytes(b'uid://extra\n')) + negative('original', lambda s, a, p, n: (s / 'project.godot').write_bytes(b'changed\n')) + negative('owner', lambda s, a, p, n: (s / p[n[0]]['owner']).write_bytes(b'changed\n')) + negative('existing-collision', lambda s, a, p, n: (s / n[0]).write_bytes(b'uid://existing\n')) + negative('generated-collision', lambda s, a, p, n: (s / n[0]).write_bytes((s / n[1]).read_bytes())) + for label, raw in [('no-newline', b'uid://abc'), ('crlf', b'uid://abc\r\n'), + ('uppercase', b'uid://ABC\n'), ('too-long', b'uid://abcdefghijklmn\n')]: + negative(label, lambda s, a, p, n, raw=raw: (s / n[0]).write_bytes(raw)) + + def hardlink(stage, source, plan, names): + os.link(stage / names[0], scratch / 'hardlink-peer') + + def symlink(stage, source, plan, names): + path = stage / names[0] + path.unlink() + path.symlink_to(stage / names[1]) + + negative('hardlink', hardlink) + negative('symlink', symlink) + negative('missing', lambda s, a, p, n: (s / n[0]).unlink()) + stage, source, plan = fixture('post-seal') + out = scratch / 'policy-receipts' + out.mkdir() + reports, phase_call, state = w.make_uid_policy(r, uid_snapshot, source, plan, out) + expected = copy.deepcopy(source) + state['phase'] = 'import' + reports(stage, expected, {}, []) + r.require(state['accepted'] == uid_snapshot(stage, source, plan), 'import sealed') + state['phase'] = 'parse' + (stage / next(iter(plan))).write_bytes(b'uid://mutated\n') + try: + reports(stage, expected, {}, []) + except AssertionError: + results.append('post-seal-reports') + else: + raise AssertionError('post-seal mutation accepted') + print(json.dumps({'accepted': False, 'Root-only': True, 'controls': results, + 'uid_helper': binding, 'scratch': str(scratch)})) + + +if __name__ == '__main__': + main()