Files
EscapistsReintroduced/docs/evidence/config-supply-recovery-2026-10-03/archive.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

161 lines
14 KiB
Python

#!/usr/bin/env python3
"""Preview by default; --apply creates only this turn's new evidence archive.
Run after successful bound suite and before separate documentation edits.
No native launch, test execution, package rewrite or third-party payload copy.
"""
from pathlib import Path
import argparse,hashlib,json,subprocess,sys,shutil,tempfile
ROOT=Path('/home/jakub/devel/escapistsrereintroduced')
WORK=Path('/tmp/erri-gameplay-root-1509')
DEST=ROOT/'docs/evidence/config-supply-recovery-2026-10-03'
PROPOSALS={'config':Path('/tmp/erri-config-review-1509'),'supply':Path('/tmp/erri-ai-review-1509'),'optional-identity':Path('/tmp/erri-optional-identity-1509')}
ORIGINAL=Path('/home/jakub/devel/escapists')
REFERENCE=Path('/tmp/erri-heartbeat-20260930-1542-reference.json')
def sha(p):
return hashlib.file_digest(p.open('rb'),'sha256').hexdigest()
def load(p):return json.loads(p.read_text())
def require(ok,why):
if not ok:raise RuntimeError(why)
def capture_inputs():
sys.path.insert(0,str(ROOT/'tools'))
from source_manifest import addon_manifest
result=addon_manifest(ROOT)
for folder in ['tests','tools','terrain-profiles','terrain-adapters','terrain-integrations']:
result.update({str(p.relative_to(ROOT)):sha(p) for p in (ROOT/folder).rglob('*') if p.is_file() and '__pycache__' not in p.parts and p.suffix!='.pyc'})
result.update({str(p.relative_to(ROOT)):sha(p) for p in (ROOT/'docs').iterdir() if p.is_file()})
for name in ['README.md','.gitignore']:result[name]=sha(ROOT/name)
return result
def seal():
inventory={str(p.relative_to(DEST)):sha(p) for p in DEST.rglob('*') if p.is_file() and p.name!='artifact-sha256.json'}
(DEST/'artifact-sha256.json').write_text(json.dumps(inventory,indent=2)+'\n')
def finalize():
require(DEST.is_dir(),'Archive must exist before documentation finalization')
seal_before=load(DEST/'artifact-sha256.json')
require({str(p.relative_to(DEST)):sha(p) for p in DEST.rglob('*') if p.is_file() and p.name!='artifact-sha256.json'}==seal_before,'Archive changed before final seal')
changes=load(WORK/'post-suite-docs.json');inputs=load(WORK/'suite-inputs.json')
require(set(changes)=={'docs/current-status.md','docs/configuration.md','docs/infantry-resupply.md'},'Unexpected post-suite document scope')
expected=dict(inputs)
for name,values in changes.items():
require(inputs[name]==values['before'] and sha(ROOT/name)==values['after'],'Post-suite document binding mismatch '+name)
expected[name]=values['after']
require(capture_inputs()==expected,'Unreviewed post-suite input changes')
baseline=load(WORK/'baseline.json');integrated=load(WORK/'integration.json')
for name,digest in baseline['files'].items():
expected_hash=changes.get(name,{}).get('after',integrated.get(name,{}).get('after',digest))
require((ROOT/name).is_file() and sha(ROOT/name)==expected_hash,'Inherited path changed after docs '+name)
index=hashlib.sha256(subprocess.check_output(['git','diff','--cached','--binary'],cwd=ROOT)).hexdigest()
require(index==baseline['index_sha256'],'Index changed after docs')
original=load(REFERENCE);require(len(original)==533 and all(sha(ORIGINAL/n)==v for n,v in original.items()),'Original changed after docs')
for label in ['mounted-pair','mine-safety','ground-supply','depot-mines']:
candidate=ROOT/f'.build/playtest-2026-10-03-{label}'
require({str(p.relative_to(candidate)):sha(p) for p in candidate.rglob('*') if p.is_file()}==load(DEST/f'preservation/{label}-candidate-sha256.json'),'Candidate changed after docs '+label)
for name in ['post-suite-docs.json','update-docs.py']:
require(not (DEST/'root'/name).exists(),'Already finalized '+name)
shutil.copyfile(WORK/name,DEST/'root'/name)
(DEST/'finalization.json').write_text(json.dumps({'complete':True,'post_suite_document_changes':changes,'source_tests_tools_unchanged':True,'fresh_suite_after_prose_changes_claimed':False,'index_unchanged':True,'original533_unchanged':True,'four_sealed_candidates_unchanged':True},indent=2)+'\n')
seal();print('Final document delta archived and evidence resealed')
def main():
parser=argparse.ArgumentParser(description=__doc__);group=parser.add_mutually_exclusive_group();group.add_argument('--apply',action='store_true');group.add_argument('--finalize',action='store_true');args=parser.parse_args()
if args.finalize:return finalize()
require(not DEST.exists(),'Refusing existing evidence archive')
baseline=load(WORK/'baseline.json');integrated=load(WORK/'integration.json');suite=load(WORK/'suite-result.json');inputs=load(WORK/'suite-inputs.json')
require(suite['exit']==0 and suite['inputs_unchanged'] is True,'Full unchanged-input suite required')
programs=suite['programs'];require(programs and len(programs)==suite['regression_programs'] and all(p['exit']==0 for p in programs),'Incomplete/failed registered test list')
require(capture_inputs()==inputs,'Current suite inputs differ; do not archive as current pass')
for name,item in integrated.items():
require(sha(ROOT/name)==item['after']==inputs[name],'Integrated source/test binding differs: '+name)
for name,digest in baseline['files'].items():
expected=integrated.get(name,{}).get('after',digest)
require((ROOT/name).is_file() and sha(ROOT/name)==expected,'Unreviewed inherited change: '+name)
index=hashlib.sha256(subprocess.check_output(['git','diff','--cached','--binary'],cwd=ROOT)).hexdigest()
require(index==baseline['index_sha256'],'Index changed')
original=load(REFERENCE);require(len(original)==533,'Unexpected original reference scope')
require(all((ORIGINAL/name).is_file() and sha(ORIGINAL/name)==digest for name,digest in original.items()),'Original reference changed')
# Only manifests are archived; sealed source/ZIP/PAK/runtime payloads stay in place.
candidates=[];copies={}
for label in ['mounted-pair','mine-safety','ground-supply','depot-mines']:
sealpath=ROOT/f'docs/evidence/playtest-refresh-2026-10-03-{label}/candidate-sha256.json'
seal=load(sealpath);candidate=ROOT/f'.build/playtest-2026-10-03-{label}'
actual={str(p.relative_to(candidate)):sha(p) for p in candidate.rglob('*') if p.is_file()}
require(actual==seal,'Sealed candidate changed: '+label)
copies[f'preservation/{label}-candidate-sha256.json']=sealpath
candidates.append({'candidate':str(candidate),'files':len(seal),'manifest_sha256':sha(sealpath),'unchanged':True})
copies['preservation/original-reference.json']=REFERENCE
base_review=Path('/tmp/erri-base-review-1509/finding.json');require(base_review.is_file(),'Missing bounded base review');copies['base-gameplay-review.json']=base_review
root_required=['baseline.json','integration.json','suite-inputs.json','suite-result.json','full-suite.log','run-suite.py','native-deferred.json','optional-identity-audit.json','vanilla-character-audit.json']
for name in root_required:
require((WORK/name).is_file(),'Missing root evidence '+name);copies['root/'+name]=WORK/name
for name in ['integrate.py','host-idle-inspection.json','preflight.json','review.json','final-review.json']:
if (WORK/name).is_file():copies['root/'+name]=WORK/name
failed=load(WORK/'first-suite-failure/suite-result.json')
require(failed['exit']!=0,'Retained initial suite failure must remain a failure')
for name in ['full-suite.log','suite-inputs.json','suite-result.json']:
path=WORK/'first-suite-failure'/name
require(path.is_file() and not path.is_symlink(),'Missing original suite failure '+name)
copies['root/first-suite-failure/'+name]=path
proposal_bindings={}
# Explicit owned artifacts only. No recursive scratch copy or installed resources.
optional=['baseline.json','finding.json','report.json','result.json','test-results.json','independent-review.json','review.json','apply-check.log','baseline-red.log','proposal-green.log','previous-validation-red.log','red.log','green.log','mutation-red.log','baseline-test.log','proposed-test.log','prepare.py','build-test.py','prepare-test.py','optional-identity-audit.json','vanilla-character-audit.json','vanilla-identity-proof.json','test-source-inputs.json']
for label,folder in PROPOSALS.items():
manifest_path=folder/'source-manifest.json';manifest=load(manifest_path)
copies[label+'/source-manifest.json']=manifest_path
patch=folder/'fix.patch';require(patch.is_file(),'Missing patch '+label);copies[label+'/fix.patch']=patch
if 'patch_sha256' in manifest:require(sha(patch)==manifest['patch_sha256'],'Patch changed '+label)
files=manifest.get('files',manifest);selected={}
for name,item in files.items():
if not isinstance(item,dict):continue
digest=item.get('proposal_sha256',item.get('proposed'))
if not digest:continue
allowed=name.startswith(('addons/core/Scripts/Game/ERR/','tests/'))
if label=='optional-identity':allowed=name in {'tools/resource_references.py','tools/check.py','tools/source_bundle.py','tests/test_external_addons.py','docs/vanilla-resources.json'}
require(allowed and '..' not in Path(name).parts,'Unexpected proposal scope '+name)
require(name in integrated and integrated[name]['after']==digest and sha(folder/name)==digest,'Unbound proposal '+name)
copies[label+'/proposed/'+name]=folder/name;selected[name]=digest
base=folder/'baseline'/name
if base.is_file():
expected=item.get('baseline_sha256',item.get('before_working',item.get('before')))
require(expected and sha(base)==expected,'Proposal baseline mismatch '+name);copies[label+'/baseline/'+name]=base
require(selected,'Empty proposal '+label)
require((folder/'independent-review.json').is_file(),'Independent review required '+label)
independent=load(folder/'independent-review.json')
require(independent.get('approved') is True and independent['patch_sha256']==sha(patch) and independent['source_manifest_sha256']==sha(manifest_path),'Independent review binding differs '+label)
for name,digest in manifest.get('artifacts',{}).items():
require(Path(name).name==name and sha(folder/name)==digest,'Manifest artifact differs '+label+'/'+name)
if label=='optional-identity':
for name in ['optional-identity-audit.json','vanilla-character-audit.json']:
require(sha(folder/name)==manifest[name+'_sha256']==sha(WORK/name),'Audit evidence differs '+name)
audited=manifest['audited_source']
require(audited['modified'] is False and sha(ROOT/audited['path'])==audited['sha256'],'Optional audited source changed')
if 'test_results_sha256' in manifest:require(sha(folder/'test-results.json')==manifest['test_results_sha256'],'Test result binding differs '+label)
for name in optional:
if (folder/name).is_file():copies[label+'/'+name]=folder/name
for log in folder.glob('*.log'):
require(log.is_file() and not log.is_symlink(),'Unexpected log link '+str(log))
copies[label+'/'+log.name]=log
proposal_bindings[label]={'manifest_sha256':sha(manifest_path),'patch_sha256':sha(patch),'files':selected}
deferred=load(WORK/'native-deferred.json');require(deferred['native_check_started'] is False,'Unexpected native scope')
report={'complete':True,'scope':'Configuration and finite supply recovery plus bounded resource audit correction; no new native qualification or package change','suite':{'exit':0,'inputs_unchanged':True,'regression_programs':len(programs),'suite_inputs_sha256':sha(WORK/'suite-inputs.json'),'suite_result_sha256':sha(WORK/'suite-result.json')},'integration_sha256':sha(WORK/'integration.json'),'proposals':proposal_bindings,'preservation':{'baseline_paths':len(baseline['files']),'inherited_unchanged_except_integrated':True,'index_sha256':index,'original_files':533,'original_manifest_sha256':sha(REFERENCE),'original_unchanged':True,'sealed_candidates':candidates},'native':deferred,'retained_initial_suite_failure':{'exit':failed['exit'],'regression_programs':failed['regression_programs'],'suite_result_sha256':sha(WORK/'first-suite-failure/suite-result.json')},'archive_files':{name:sha(path) for name,path in sorted(copies.items())}}
print(json.dumps({'ready':True,'destination':str(DEST),'files':len(copies),'programs':len(programs),'apply':args.apply},indent=2))
if not args.apply:return
# All binding/preservation checks finish before the first destination mutation.
DEST.parent.mkdir(parents=True,exist_ok=True)
staging=Path(tempfile.mkdtemp(prefix='.config-supply-recovery-',dir=DEST.parent))
try:
for name,path in copies.items():
out=staging/name;out.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(path,out)
require(sha(out)==report['archive_files'][name],'Copy verification failed '+name)
(staging/'review.json').write_text(json.dumps(report,indent=2)+'\n')
shutil.copyfile(Path(__file__),staging/'archive.py')
(staging/'README.md').write_text('# Configuration and supply recovery — 3 October 2026\n\n'
'Malformed recognized configuration sections now prevent unrelated saves from replacing the only valid backup. Ordinary rifle resupply retains exact claims while its existing native transfer remains unresolved.\n\n'
+str(len(programs))+' registered regression programs passed with unchanged captured inputs. Focused baseline failures and corrected-method controls are retained under config/ and supply/. Read review.json for exact source, input and preservation bindings.\n\n'
'The initial suite failure is retained under root/first-suite-failure/. Shared scanners now distinguish one unchanged, hash-pinned optional prefab identity comparison from hard resource references; the actual vanilla Character_Base load has an ordinary audited resource entry. Optional runtime dependency closure is unchanged. The proposal and scanner baseline failures are retained under optional-identity/.\n\n'
'Native checks were deferred because an existing game was running. No new native compilation, physical transfer, manual escape, multiplayer or FPS qualification is claimed. Original missions and four previous sealed packages remain unchanged.\n')
require(not DEST.exists(),'Destination appeared during finalization')
staging.rename(DEST)
seal()
except BaseException:
shutil.rmtree(staging);raise
if __name__=='__main__':main()