P04-01: 同時発生の更新規約

This commit is contained in:
Agent Control Runner
2026-10-18 12:00:07 +09:00
parent 27b18b75c9
commit 8e25213e91
5 changed files with 323 additions and 8 deletions
@@ -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"
]
}
]
}
@@ -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.
@@ -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.
@@ -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,
@@ -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()