import hashlib import json import pathlib import subprocess from datetime import datetime, timezone root = pathlib.Path(__file__).resolve().parents[1] state_path = root / '.dryforge/execution.json' state = json.loads(state_path.read_text(encoding='utf-8')) def git(*args): return subprocess.check_output(['git', *args], cwd=root, text=True).strip() def digest(path): return hashlib.sha256(path.read_bytes()).hexdigest() assert git('branch', '--show-current') == state['feature_branch'] assert git('rev-parse', state['protected_base']) == state['initial_commit'] for name, expected in state['protected_product_hashes'].items(): assert digest(root / name) == expected, name diff = git('diff', '--name-status', state['initial_commit'] + '...HEAD').splitlines() assert len(diff) == 32, len(diff) for line in diff: status, name = line.split('\t', 1) assert status == 'A', line assert name.startswith(('SourceAssets/Models/Heroes/', 'SourceAssets/Models/Meadow/', 'Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Heroes/', 'Assets/MagnetismHeroes/UnityRuntime/Presentation/Models/Meadow/')) or name in ( 'tools/models/build_hero.py', 'tools/models/build_meadow.py'), name checked = 0 for group in ('Heroes', 'Meadow'): provenance = json.loads((root / f'SourceAssets/Models/{group}/provenance.json').read_text()) for record in provenance['files']: path = root / record['path'] assert path.stat().st_size == record['bytes'], record['path'] assert digest(path) == record['sha256'], record['path'] checked += 1 subprocess.run(['git', 'diff', '--check', state['initial_commit'] + '...HEAD'], cwd=root, check=True) assert git('status', '--porcelain') == '', 'Unexpected tracked/untracked change' evidence = { 'status': 'PASS', 'scope': 'Model candidates only; user approval pending', 'checkedUtc': datetime.now(timezone.utc).isoformat(), 'featureCommit': git('rev-parse', 'HEAD'), 'protectedBaseCommit': state['initial_commit'], 'protectedProductFilesUnchanged': len(state['protected_product_hashes']), 'candidateFilesAdded': len(diff), 'provenanceResourceHashesChecked': checked, 'commands': [ {'command': 'Blender --background --python-exit-code 1 --python tools/models/build_hero.py -- --verify-only', 'exitCode': 0, 'log': 'Heroes/integrated-verify.log', 'assertion': 'ASSERTIONS_OK and REOPEN_AND_FBX_REIMPORT_OK evaluated'}, {'command': 'Blender --background --python-exit-code 1 --python tools/models/build_meadow.py -- --verify-only --review-dir artifacts/verification/VisualUpgrade/ModelReview/Meadow', 'exitCode': 0, 'log': 'Meadow/integrated-verify.log', 'assertion': 'MEADOW_VERIFY_PASS evaluated; 21 assets and 23 resources'}, {'command': 'python .dryforge/verify-model-integration.py', 'exitCode': 0, 'assertion': '533 protected product hashes, 32 additions, candidate provenance hashes, clean tree and protected dev checked'}], 'userApproval': 'PENDING', 'unityGameplayAndroid': 'NOT_RUN; awaits explicit model approval' } review = root / 'artifacts/verification/VisualUpgrade/ModelReview' (review / 'integration-verification.json').write_text(json.dumps(evidence, ensure_ascii=False, indent=2), encoding='utf-8') state['tasks'].update(T1='completed_model_candidate', T2='completed_model_candidate', T3='awaiting_explicit_user_model_approval') state['model_integration'] = evidence state_path.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding='utf-8') print(json.dumps({k: evidence[k] for k in ('status', 'featureCommit', 'protectedProductFilesUnchanged', 'candidateFilesAdded', 'provenanceResourceHashesChecked', 'userApproval')}, ensure_ascii=False))