P08-02: 旧配置の安全な変更
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only native collection. Does not run regression or claim acceptance."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'
|
||||
CONDITIONS = ('mixed_construction', 'occupancy_change', 'actual_actor_positions',
|
||||
'combined_rescue_logistics_ev', 'no_overlap_orphan_loss', 'unsolvable_preservation')
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
obj = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(obj)
|
||||
return obj
|
||||
|
||||
def absolute(value):
|
||||
path = Path(value)
|
||||
if not path.is_absolute():
|
||||
raise argparse.ArgumentTypeError('absolute path required')
|
||||
return path.resolve()
|
||||
|
||||
def collect(args):
|
||||
root, out = args.project_root, args.output
|
||||
r = module('p08_standard_runner', root/'tools/run_all_tests.py')
|
||||
commit = subprocess.check_output(['git', '-C', str(root), 'rev-parse', 'HEAD'], text=True).strip()
|
||||
r.require(commit == args.commit, 'actual final submitted commit required')
|
||||
r.require(not out.exists(), 'fresh output; never overwrite earlier proof')
|
||||
refs = r.source_refs(root)
|
||||
outer = r.module('p08_owned_outer', r.SUPPORT/'owned_outer.py')
|
||||
r.require(not outer.native.helper['live_engines'](), 'one native engine')
|
||||
engine = shutil.which(args.godot)
|
||||
r.require(engine is not None, 'native engine exists')
|
||||
engine = str(Path(engine).resolve())
|
||||
out.mkdir(parents=True)
|
||||
stage = out/'stage'; stage.mkdir()
|
||||
(out/'starters').mkdir(); (out/'bootstrap').mkdir()
|
||||
for name, pin in r.read(r.SUPPORT/'bootstrap-scripts.json').items():
|
||||
data = pin['text'].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == pin['sha256'], 'bootstrap pin')
|
||||
(out/'bootstrap'/name).write_bytes(data)
|
||||
for name, pin in refs.items():
|
||||
target = stage/name; target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(pin['path']), target)
|
||||
r.require(r.sha(target) == pin['sha256'], 'whole copied source '+name)
|
||||
expected = {n: {'bytes':v['bytes'], 'sha256':v['sha256']} for n,v in refs.items()}
|
||||
selected = {'root_before':refs, 'map':{n:v['sha256'] for n,v in refs.items()}, 'timeout':180}
|
||||
r.require(r.inventory(stage, stage=True) == expected, 'complete private source')
|
||||
protected = r.default_player(root); public = (stage/'project.godot').read_bytes()
|
||||
paths = ['res://'+n for n in sorted(refs) if n.endswith('.gd')]
|
||||
r.write(out/'parse-paths.json', paths)
|
||||
markers = r.module('p08_strict_markers', root/'tools/strict_test_markers.py')
|
||||
contracts = markers.load_contracts(root/CONTRACT)
|
||||
for role in ('writer','reader'):
|
||||
name = 'tests/fixtures/p08-02/'+role+'.gd'
|
||||
contracts[name] = {'test':name, 'template':'P08 mixed '+role+': <checks> checks, <failures> failures', 'required':True}
|
||||
policy = r.read(r.SUPPORT/'suite-policy.json')
|
||||
diagnostics = r.module('p08_diagnostics', r.SUPPORT/'diagnostic_gate.py')
|
||||
owners = r.module('p08_owners', r.SUPPORT/'ordinary_proof_gate.py')
|
||||
audio = r.module('p08_audio', r.SUPPORT/'audio_evidence_gate.py')
|
||||
r.write(out/'runner-input.json', {'protocol':'p08-mixed-v1', 'commit':commit,
|
||||
'selected_source_map':selected['map'], 'engine':engine, 'engine_sha256':r.sha(engine),
|
||||
'owner_pid':os.getpid(), 'default_player':protected, 'diagnostic_seed':42588})
|
||||
phases = []; previous = {}; cases = []; error = None
|
||||
deadline = time.monotonic()+900
|
||||
saved_env = {k:os.environ.get(k) for k in ('P08_PAIR_ROOT','P08_OPERATIONS','P08_CASE')}
|
||||
def phase(label, test):
|
||||
remaining = int(deadline-time.monotonic())
|
||||
r.require(remaining >= (180 if label == 'import' else 1), '900s collection deadline')
|
||||
selected['timeout'] = min(180,remaining)
|
||||
phase_out = out/'runs'/label
|
||||
phase_out.mkdir(parents=True)
|
||||
(phase_out/'starters').mkdir(); (phase_out/'bootstrap').mkdir()
|
||||
for original in (out/'bootstrap').iterdir():
|
||||
r.copy_source(original,phase_out/'bootstrap'/original.name)
|
||||
r.require(r.sha(original)==r.sha(phase_out/'bootstrap'/original.name),'bootstrap full copy')
|
||||
r.write(phase_out/'parse-paths.json',paths)
|
||||
row = r.execute_phase(engine, stage, phase_out, label, test, selected, expected, previous,
|
||||
policy, contracts, paths, public, protected, root, outer, markers, diagnostics, owners, audio)
|
||||
phases.append('runs/'+label+'/'+label+'-receipt.json')
|
||||
r.require(not row['failed'], 'strict phase failed: '+label)
|
||||
try:
|
||||
phase('import', None); phase('parse', None)
|
||||
for operations in ('off','on'):
|
||||
for case in ('positive','refusal'):
|
||||
name = operations+'-'+case; pair = out/'pairs'/name
|
||||
os.environ.update(P08_PAIR_ROOT=str(pair), P08_OPERATIONS=operations, P08_CASE=case)
|
||||
case_phases = []
|
||||
preserved = {}
|
||||
for role in ('writer','reader'):
|
||||
label = name+'-'+role
|
||||
test = 'tests/fixtures/p08-02/'+role+'.gd'
|
||||
phase(label, test)
|
||||
actual = r.inventory(pair)
|
||||
r.require(all(actual.get(n) == pin for n,pin in preserved.items()), 'reader preserved every writer byte')
|
||||
preserved = actual
|
||||
case_phases.append(phases[-1])
|
||||
cases.append({'id':name, 'operations':operations, 'kind':case,
|
||||
'directory':'pairs/'+name, 'phases':case_phases, 'files':preserved})
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
for key,value in saved_env.items():
|
||||
if value is None: os.environ.pop(key, None)
|
||||
else: os.environ[key] = value
|
||||
r.require(r.source_refs(root) == refs and r.default_player(root) == protected, 'protected root/profile unchanged')
|
||||
r.write(out/'runner-ended.json', {'error':error, 'cleanup':cleanup, 'cleanup_errors':cleanup_errors,
|
||||
'phases':phases, 'formal_acceptance':False, 'native_Windows':'pending'})
|
||||
inputs = {str(p.relative_to(root)):r.sha(p) for p in (root/'docs/spec/inputs/p08-02').iterdir() if p.is_file()}
|
||||
# The collector is intentionally incomplete until Root supplies OS and regression artifacts.
|
||||
r.write(out/'index.json', {'schema':'p08-evidence-v1', 'commit':commit,
|
||||
'source_map':selected['map'], 'inputs':inputs, 'marker_contract_sha256':r.sha(root/CONTRACT),
|
||||
'spec_sha256':hashlib.sha256((root/'docs/spec/acceptance/P08-02.json').read_text().strip().encode()).hexdigest(),
|
||||
'cases':cases, 'phases':phases, 'conditions':{c:[x['id'] for x in cases if c != 'unsolvable_preservation' or x['kind']=='refusal'] for c in CONDITIONS},
|
||||
'artifacts':{str(p.relative_to(out)):{'bytes':p.stat().st_size,'sha256':r.sha(p)}
|
||||
for p in out.rglob('*') if p.is_file() and 'stage' not in p.relative_to(out).parts},
|
||||
'os_proof':None, 'regression':None,
|
||||
'acceptance':'pending', 'collection_error':error})
|
||||
return 1 if error or cleanup_errors else 0
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--project-root', type=absolute, required=True)
|
||||
parser.add_argument('--output', type=absolute, required=True)
|
||||
parser.add_argument('--commit', required=True)
|
||||
parser.add_argument('--godot', default='godot')
|
||||
parser.add_argument('--collect', action='store_true', required=True)
|
||||
return collect(parser.parse_args(argv))
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user