From 52ce2f125c6a131cd3ffb096be4290488c37cc9e Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Fri, 16 Oct 2026 07:16:55 +0900 Subject: [PATCH] =?UTF-8?q?P18-REG-KITCHEN-LEGACY14:=20P18=E6=89=BF?= =?UTF-8?q?=E8=AA=8D=E6=B8=88=E3=81=BF=E4=BF=9D=E5=AD=98=E7=A7=BB=E8=A1=8C?= =?UTF-8?q?=E3=81=A8=E5=8E=A8=E6=88=BF=E6=97=A714=E5=9B=9E=E5=B8=B0?= =?UTF-8?q?=E3=81=AE=E6=95=B4=E5=90=88=EF=BC=88=E5=8E=9F88=E4=BB=B6?= =?UTF-8?q?=E3=81=AB=E5=8A=A0=E7=AE=97=E3=81=97=E3=81=AA=E3=81=84=E8=A3=9C?= =?UTF-8?q?=E5=8A=A9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../completion_marker_contracts.json | 4 +- .../delta-manifest.json | 65 ++++ .../p18-kitchen-legacy-support/plan.md | 120 ++++++++ .../root_diagnostic.py | 126 ++++++++ .../verify_support.py | 284 ++++++++++++++++++ tests/business/test_required_kitchen.gd | 105 ++++++- 6 files changed, 699 insertions(+), 5 deletions(-) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/delta-manifest.json create mode 100644 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/plan.md create mode 100644 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py create mode 100644 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/verify_support.py diff --git a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json index 98b10fd..3ae0fd5 100644 --- a/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json +++ b/docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json @@ -78,7 +78,7 @@ "template": "Required kitchen: checks, failures", "required": true, "fixed_check_count_used": false, - "source_sha256_at_schema_audit": "90e7639f5aa76e34b9ee14769f4ec1ae1317a647900b27877075dbf21ef1dfcf", + "source_sha256_at_schema_audit": "3a9c92f80c24064ef0858fba10bd7caab63203932942837f7e142349578f6b2f", "authority": { "path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p09-kitchen-single-link-candidate-r09/candidates/tests/business/test_required_kitchen.gd", "sha256": "90e7639f5aa76e34b9ee14769f4ec1ae1317a647900b27877075dbf21ef1dfcf", @@ -86,7 +86,7 @@ }, "source_marker_lines": [ { - "line": 281, + "line": 481, "text": "\tprint(\"Required kitchen: %d checks, %d failures\" % [checks, failures])" } ] diff --git a/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/delta-manifest.json b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/delta-manifest.json new file mode 100644 index 0000000..47e53db --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/delta-manifest.json @@ -0,0 +1,65 @@ +{ + "task_id": "P18-REG-KITCHEN-LEGACY14", + "base_commit": "9b1a18c1eb25ca1300dcd59da78423c0dda2f498", + "state": "code-ready; tests NOT RUN; Root native proof pending", + "canonical_source_delta": { + "authority_file": "MAIN9B_COMPLETE_SOURCE_REFS.json", + "authority_bytes": 376039, + "authority_sha256": "dc43e665be3f02d14513f0b8c4248d410175dcfb118171d8cbe0e62ef764a2e4", + "canonical_count": 1338, + "docs_in_canonical_map": false, + "path": "tests/business/test_required_kitchen.gd", + "old": {"bytes": 31533, "sha256": "5dcc86d66bdc47003472d6c30d6889eb514e016003265965bdd8496d123d5ca5"}, + "new": {"bytes": 38276, "sha256": "3a9c92f80c24064ef0858fba10bd7caab63203932942837f7e142349578f6b2f"}, + "other_entries_same_keys_and_hashes": 1337, + "added_keys": [], + "dropped_keys": [] + }, + "marker_delta": { + "path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json", + "separate_fixed_authority": true, + "old": {"bytes": 188089, "sha256": "6ce6b28c5985e8a4f13b78bfeb1f1e09f6216d874fb01e0656ec1633bfa01cd7"}, + "new": {"bytes": 188089, "sha256": "9c095df16eb6848e5b3b34cd78aef1c57a7724e875eb8371d3ff4c2c8cc349a1"}, + "changed_rows": [{ + "test": "tests/business/test_required_kitchen.gd", + "source_sha256_at_schema_audit": { + "old": "90e7639f5aa76e34b9ee14769f4ec1ae1317a647900b27877075dbf21ef1dfcf", + "new": "3a9c92f80c24064ef0858fba10bd7caab63203932942837f7e142349578f6b2f" + }, + "source_marker_lines": { + "old": [{"line": 281, "text": "\tprint(\"Required kitchen: %d checks, %d failures\" % [checks, failures])"}], + "new": [{"line": 481, "text": "\tprint(\"Required kitchen: %d checks, %d failures\" % [checks, failures])"}] + } + }], + "authority_records_changed": false, + "other_rows_changed": false, + "fixed_check_count_used": false, + "predicted_native_checks": 163, + "actual_native_checks": null + }, + "new_support_path_group": { + "path": "docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/", + "files": ["plan.md", "delta-manifest.json", "verify_support.py", "root_diagnostic.py"] + }, + "full_selected_map": { + "required_count": 1339, + "composition": "current canonical 1338 plus exactly the fixed reviewed P04 extra", + "extra_path": "tools/check_p04_order_evidence.py", + "extra_bytes": 78235, + "extra_sha256": "f5b44827958f2ae352fe5c4bc3a1ffd4211ec7a41fc4c0759c8b954891662324" + }, + "zero_changes": ["autoload", "scripts", "scenes", "resources", "project.godot", "export", "policy", "engine", "legacy_fixture"], + "grep": { + "pattern": "test_required_kitchen\\.gd", + "canonical_source_reference_search_exit": 1, + "canonical_source_reference_matches": [], + "repository_reference_matches": "docs only (including archived manifests); edited definition is the kitchen test itself", + "source_consumers_outside_the_test": 0 + }, + "existing_source_manifest_rewritten": false, + "generic_exclusions_added": false, + "tests_run": false, + "independent_review": "pending", + "integration": "pending", + "native_Windows": "not claimed" +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/plan.md b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/plan.md new file mode 100644 index 0000000..49d9dcf --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/plan.md @@ -0,0 +1,120 @@ +# P18-REG-KITCHEN-LEGACY14 source submission + +Requirement: `legacy_preservation`. Base remains +`9b1a18c1eb25ca1300dcd59da78423c0dda2f498`. This is uncounted support; +original30/88 and the acceptance denominator do not change. Product code, P04, +settings, thresholds, suite policy, fixture bytes and fixture authority are unchanged. + +## Diagnosis and exact migration + +The old14 writer is v1, while the approved P18 relationship service saves a v2 +container. The five typed differences are all below +`/systems/runtime/relationships`: version 1 becomes 2, `legacy` is added, +`original_relationships` is added, and `threshold_candidates` and +`next_candidate_sequence` move into `legacy`. The retained full native typed +inputs are 159552 and 159680 bytes, an increase of 128 bytes. Their SHA-256 values +are respectively `a2f8992853ba9497539cdfcb74d22c83c76011f2acf73f9e8e3e7241620ddca0` +and `a2f3e75181071f94946369df6f3a6256998dd7d776441766bccc9b9c3ccf5d8d`. +They diagnose the earlier failure; they are never substituted into a new run. + +The genuine local `write-checkpoint.var` contains the old v1 dictionary at byte +offsets [143668,143816): **148 bytes**, SHA-256 +`ed2cafb629f4d08249f8cd147c7ff9fc715ff641c24809a93e66a1229d4105c4`. +This was obtained by reading the original fixture bytes, including its dictionary +header, StringName keys, typed empty Dictionary arrays, and int values. The new +native proof independently hashes `var_to_bytes(old)` and the whole fixture file. + +The pure validator requires the exact old field set/key encoding, int version 1, +byte-identical nested legacy, int version 2, exact v2 field set, unchanged sequence, +byte-identical compatibility projection and typed empty original records. It builds +the container in the service's insertion order with StringName keys and typed +`Array[Dictionary]`. Original values stay unconfigured: no conversion from old +five-axis values and no guessed original initialization. + +After the existing identity/saveID-bound IO1 replacement, a reader copy with just +relationships reversed to v1 must equal the entire expected writer byte stream. +Only that subtree is then replaced. Whole typed bytes, Dictionary equality, +SimulationDigest, IO validity, envelope, replaced==1 and published IDs remain +required. Only legacy14 opts in. new14 has the same behavior and proof shape. + +Seventeen in-memory controls cover an outside change, each legacy field deleted +and changed, sequence +1, nonempty originals, actual versions 0/3/string, equal +reordered/reencoded dictionaries, and a byte-identical deep-copy positive control. +Every attempt checks both caller inputs and both attempted copies for mutation. +There are 17 new assertions plus one fresh proof-file assertion, so the native +success path is expected to report 163 checks (the existing 145 remain). This +arithmetic is a gate expectation, not an observed native result. The final print +wording and schema are unchanged; the marker contract continues to derive counts +from raw output (`fixed_check_count_used: false`). + +## Source map and authority + +`MAIN9B_COMPLETE_SOURCE_REFS.json` was read locally after its 376039-byte SHA-256 +`dc43e665be3f02d14513f0b8c4248d410175dcfb118171d8cbe0e62ef764a2e4` +was checked. Its canonical 1338 keys exclude docs. The only changed selected entry +is `tests/business/test_required_kitchen.gd`; all other 1337 keys and hashes match. +The selected map hash before this change is +`533a4177918a9da4d4982aa9862cb7420c25d30c2458e36aab9c071c4d3a7139`. +Absolute production paths in those records are provenance, never input paths. + +The completion-marker contract is a separately pinned, second changed existing +file. Its pre-change bytes were 188089 with SHA-256 +`6ce6b28c5985e8a4f13b78bfeb1f1e09f6216d874fb01e0656ec1633bfa01cd7`. +Only the kitchen source SHA and actual marker line position change; authority, +template, count policy and other entries remain byte-for-byte unchanged. +The newly added support directory is the third allowed path group. The tree as a +whole is not described as exactly two paths. See `delta-manifest.json`. + +Root's selected full-run map must be exactly these current 1338 files plus +`tools/check_p04_order_evidence.py`, 78235 bytes, SHA-256 +`f5b44827958f2ae352fe5c4bc3a1ffd4211ec7a41fc4c0759c8b954891662324`. +This is the fixed supplied reviewed P04 QA source, not permission to edit P04 or +read its build. The verifier compares local relative keys and full file pins, +rejecting additions, drops and stale sources. It introduces no selector exclusions. + +Static reference search: + +```sh +rg -l 'test_required_kitchen\.gd' autoload scripts scenes data tests assets tools project.godot export_presets.cfg Makefile AGENTS.md +rg -l 'test_required_kitchen\.gd' docs +``` + +The first command returned no matches (exit 1). The second finds documentation +and archived manifests only; there are no other source consumers of the test. +The test itself is the edited definition. This search does not rewrite manifests. + +## Root-only finite local diagnostic procedure + +Worker has run **no tests, engine, Runner, Git or agents**. Python support files +were compiled in memory only. GDScript import/parse and native results are pending. +Root must use the existing `godot` guarded launcher supported by the current tools; +do not point any command directly at a player profile. Run from this checkout in +one shell, stop on the first nonzero exit, and do not retry into an existing output. +If an output already exists, retain it and obtain a newly registered build before +a new collection. The helper uses the existing `execute_phase`, Guard, owned outer +launcher and `run_p18_relationship_tests.py` roster/archive APIs. It changes no +production collector, policy or threshold. All source copying is private. + +```sh +export BUILD_OUTPUT_DIR=/Users/korizatou114/Projects/TheTree-managed-worktrees/p18-kitchen-regression-r02/.runtime/P18-REG-KITCHEN-LEGACY14/build +set -e +python3 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py --phase parse +python3 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py --phase kitchen +python3 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py --phase affected +python3 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py --phase full +python3 docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/verify_support.py --verify-only --marker-contracts docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json +``` + +Order is import/all-script parse, required_kitchen alone, all business plus all +s4 relationship tests/all s6/P18 affected set, then the new standard full178. +Every phase retains import/parse and exact-source receipts; the full phase invokes +the unchanged `run_all_tests.main` with the explicit 1339 source input and marker +contract. `archive_profiles` retains all actual private profiles under the build, +including the kitchen typed proofs, checked against native inventories. The +read-only verifier reads those receipts, raw stdout/stderr/native logger streams, +and PID lifecycle evidence; it launches nothing and emits no PASS of its own. +Missing future evidence correctly exits nonzero and does not block writing code. + +Root will formally verify P04 separately in P04A repair. Independent Opus review, +integration and final verification remain required before any original-unit credit. +No native Windows behavior or whole-unit completion is claimed. diff --git a/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py new file mode 100644 index 0000000..ec0fe99 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/root_diagnostic.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Root-only finite diagnostics, using existing guarded native phase APIs. + +This is an execution procedure, not worker evidence or an acceptance gate. +""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import shutil +import sys +import uuid + +sys.dont_write_bytecode = True +ROOT = Path(__file__).resolve().parents[5] +sys.path.insert(0, str(ROOT / 'tools')) +import run_p18_relationship_tests as p18 + +s = p18.shared + + +def main(): + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument('--phase', required=True, choices=['parse', 'kitchen', 'affected', 'full']) + args = parser.parse_args() + build = Path(os.environ['BUILD_OUTPUT_DIR']).resolve(strict=True) + out = build / {'parse': 'native-parse', 'kitchen': 'native-kitchen', + 'affected': 'native-affected', 'full': 'native-full178'}[args.phase] + s.require(not out.exists(), 'fresh output; no automatic retry or evidence splice') + prior = {'kitchen': 'native-parse', 'affected': 'native-kitchen', 'full': 'native-affected'} + if args.phase in prior: + previous = s.read(build / prior[args.phase] / 'runner-ended.json') + s.require(previous['error'] is None and previous['cleanup_errors'] == [] + and previous['guard_errors'] == [], 'preceding diagnostic must finish successfully') + refs = s.source_refs(ROOT) + checker = build / 'audit-support/root-input-r01/P04_REVIEWED_CANDIDATE_CHECKER.py' + s.require(checker.stat().st_size == 78235 and s.sha(checker) + == 'f5b44827958f2ae352fe5c4bc3a1ffd4211ec7a41fc4c0759c8b954891662324', 'fixed local P04 extra') + extra = {'path': str(checker), 'bytes': 78235, 'sha256': s.sha(checker)} + s.require('tools/check_p04_order_evidence.py' not in refs + or {k: refs['tools/check_p04_order_evidence.py'][k] for k in ('bytes', 'sha256')} + == {k: extra[k] for k in ('bytes', 'sha256')}, 'no replacement of a different P04 source') + refs['tools/check_p04_order_evidence.py'] = extra + s.require(len(refs) == 1339, 'prospective full source set') + marker_path = ROOT / 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json' + if args.phase == 'full': + selected = build / 'kitchen-selected-1339.json' + s.write(selected, refs) + code = s.main(['--project-root', str(ROOT), '--output', str(out), '--marker-contracts', str(marker_path), + '--source-input-refs', str(selected)]) + # Retain the actual profiles under BUILD_OUTPUT_DIR while they still + # exist. Archive helper checks every byte against native phase inventory. + for row in s.read(out / 'runner-ended.json')['phases']: + p18.archive_profiles(out, row) + return code + root_before = s.source_refs(ROOT) + roster = [] if args.phase == 'parse' else ['tests/business/test_required_kitchen.gd'] + if args.phase == 'affected': + roster = sorted(set(n for n in refs if n.startswith('tests/business/test') and n.endswith('.gd')) + | set(p18.ROSTER) + | set(n for n in refs if n.startswith('tests/s6/test') and n.endswith('.gd')) + | set(n for n in refs if n.startswith('tests/s4/test') and n.endswith('.gd') and 'relationship' in n)) + markers = s.module('kitchen_diagnostic_markers', ROOT / 'tools/strict_test_markers.py') + contracts = markers.load_contracts(marker_path) + policy = s.read(s.SUPPORT / 'suite-policy.json') + s.policy_coverage(roster, refs, policy) + engine = shutil.which('godot') + s.require(engine, 'existing guarded native launcher required') + engine = str(Path(engine).resolve()) + outer = s.module('kitchen_diagnostic_outer', s.SUPPORT / 'owned_outer.py') + s.require(not outer.native.helper['live_engines'](), 'one owned engine; no concurrent run') + s.require(shutil.disk_usage(build).free >= sum(p['bytes'] for p in refs.values()) + s.MIN_SPACE, 'unchanged disk admission') + out.mkdir() + stage = out / 'stage'; stage.mkdir() + (out / 'starters').mkdir(); (out / 'bootstrap').mkdir() + for name, entry in s.read(s.SUPPORT / 'bootstrap-scripts.json').items(): + data = entry['text'].encode() + s.require(hashlib.sha256(data).hexdigest() == entry['sha256'], 'exact existing bootstrap') + (out / 'bootstrap' / name).write_bytes(data) + for name, entry in refs.items(): + target = stage / name; target.parent.mkdir(parents=True, exist_ok=True) + s.copy_source(Path(entry['path']), target) + expected = {n: {k: p[k] for k in ('bytes', 'sha256')} for n, p in refs.items()} + s.require(s.inventory(stage, stage=True) == expected, 'whole private source copy') + public = (ROOT / 'project.godot').read_bytes() + protected = s.default_player(ROOT) + paths = ['res://' + n for n in sorted(refs) if n.endswith('.gd')] + s.write(out / 'parse-paths.json', paths) + selected = {'root_before': root_before, 'map': {n: p['sha256'] for n, p in refs.items()}, 'timeout': 1200} + run_id = uuid.uuid4().hex + s.write(out / 'runner-input.json', {'protocol': 'kitchen-diagnostic-v1', 'run_id': run_id, + 'roster': roster, 'selected_source_map': selected['map'], 'default_player_before': protected}) + s.write(out / 'source-start.json', root_before) + diagnostic = s.module('kitchen_diag_gate', s.SUPPORT / 'diagnostic_gate.py') + owners = s.module('kitchen_diag_owners', s.SUPPORT / 'ordinary_proof_gate.py') + audio = s.module('kitchen_diag_audio', s.SUPPORT / 'audio_evidence_gate.py') + phases = []; rows = []; generated = {}; error = None; guard_errors = [] + try: + for phase, test in [('import', None), ('parse', None)] + [(f'{i:03}', n) for i, n in enumerate(roster)]: + row = s.execute_phase(engine, stage, out, phase, test, selected, expected, generated, + policy, contracts, paths, public, protected, ROOT, outer, markers, diagnostic, owners, audio) + p18.archive_profiles(out, row) + phases.append(row) + if test: + row.update(sequence=len(rows), godot=engine); rows.append(row) + (out / 'all-tests.json').write_text(json.dumps(rows, ensure_ascii=False, indent=2) + '\n') + s.require(row['passed'], 'first strict native failure stops: ' + phase) + except BaseException as caught: + error = type(caught).__name__ + ': ' + str(caught) + finally: + cleanup, cleanup_errors = outer.finish_captured(outer.OWNED) + try: + s.require(s.source_refs(ROOT) == root_before and s.default_player(ROOT) == protected, 'whole source/default unchanged') + s.require(s.inventory(stage, stage=True) == dict(expected, **generated), 'whole stage unchanged') + s.require(all(r['engine_ended'] for r in cleanup), 'all native PIDs ended') + except BaseException as caught: + guard_errors.append(str(caught)) + s.write(out / 'runner-ended.json', {'protocol': 'kitchen-diagnostic-v1', 'run_id': run_id, 'error': error, + 'cleanup_errors': cleanup_errors, 'guard_errors': guard_errors, 'final_owned_cleanup': cleanup, + 'phases': phases, 'default_player_after': s.default_player(ROOT)}) + return int(error is not None or bool(cleanup_errors) or bool(guard_errors)) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/verify_support.py b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/verify_support.py new file mode 100644 index 0000000..0fdba3d --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p18-kitchen-legacy-support/verify_support.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +"""Read-only Root evidence gate. No collection, engine launch, or PASS emission.""" +import argparse +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import sys + +sys.dont_write_bytecode = True +ROOT = Path(__file__).resolve().parents[5] +KITCHEN = 'tests/business/test_required_kitchen.gd' +MARKER = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json' +EXTRA = 'tools/check_p04_order_evidence.py' +EXTRA_PIN = {'bytes': 78235, 'sha256': 'f5b44827958f2ae352fe5c4bc3a1ffd4211ec7a41fc4c0759c8b954891662324'} +BASE_PIN = {'bytes': 376039, 'sha256': 'dc43e665be3f02d14513f0b8c4248d410175dcfb118171d8cbe0e62ef764a2e4'} +MARKER_PIN = {'bytes': 188089, 'sha256': '6ce6b28c5985e8a4f13b78bfeb1f1e09f6216d874fb01e0656ec1633bfa01cd7'} + + +def require(ok, reason): + if not ok: + raise ValueError(reason) + + +def pin(path): + data = path.read_bytes() + return {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()} + + +def unique(pairs): + result = {} + for key, value in pairs: + require(key not in result, 'duplicate JSON key: ' + key) + result[key] = value + return result + + +def read(path): + return json.loads(path.read_bytes(), object_pairs_hook=unique) + + +def local(base, name): + name = Path(name) + require(not name.is_absolute() and '..' not in name.parts, 'relative evidence key required') + path = base / name + require(path.resolve().is_relative_to(base.resolve()), 'evidence escapes build directory') + require(not any(p.is_symlink() for p in [path, *path.parents] if p != base.parent), 'evidence alias') + return path + + +def load(name, path): + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def pins(refs): + # Absolute production paths are provenance only. Never open them. + return {name: {key: value[key] for key in ('bytes', 'sha256')} for name, value in refs.items()} + + +def exited(pid): + require(type(pid) is int and pid > 0, 'native PID invalid') + try: + os.kill(pid, 0) + except ProcessLookupError: + return + raise ValueError('native PID still exists or cannot be confirmed exited: ' + str(pid)) + + +def cleanup(rows, expected): + require([r['pid'] for r in rows] == expected, 'captured cleanup PID set/order') + for row in rows: + require(row['engine_ended'] is True and row['outer_cleanup_errors'] == [] + and row['foreign_process_or_group_signals'] == 0 + and row['cleanup']['engine_ended'] is True and row['cleanup']['errors'] == [], + 'owned native cleanup incomplete') + exited(row['pid']) + + +def verify(args): + build = Path(os.environ['BUILD_OUTPUT_DIR']).resolve(strict=True) + out = local(build, args.full_output) + authority = local(build, 'audit-support/root-input-r01') + base_path = local(authority, 'MAIN9B_COMPLETE_SOURCE_REFS.json') + marker_base = local(authority, 'MAIN9B_COMPLETION_MARKER_CONTRACTS.json') + require(pin(base_path) == BASE_PIN and pin(marker_base) == MARKER_PIN, 'fixed Root authority bytes') + baseline = pins(read(base_path)) + require(len(baseline) == 1338, 'canonical map size') + require(hashlib.sha256(json.dumps({n: p['sha256'] for n, p in baseline.items()}, sort_keys=True).encode()).hexdigest() + == '533a4177918a9da4d4982aa9862cb7420c25d30c2458e36aab9c071c4d3a7139', 'canonical selected map hash') + delta = read(Path(__file__).with_name('delta-manifest.json')) + require(pin(ROOT / KITCHEN) == delta['canonical_source_delta']['new'], 'submitted kitchen source stale') + require(baseline[KITCHEN] == delta['canonical_source_delta']['old'], 'old kitchen pin') + expected = dict(baseline) + expected[KITCHEN] = pin(ROOT / KITCHEN) + expected[EXTRA] = EXTRA_PIN + require(len(expected) == 1339, 'exact fixed additional P04 QA source') + shared = load('kitchen_shared', ROOT / 'tools/run_all_tests.py') + submitted = pins(shared.source_refs(ROOT)) + require(set(submitted) in (set(baseline), set(expected)), 'submitted additions/drops') + require(all(p == expected[n] for n, p in submitted.items()), 'submitted source byte drift') + marker_path = Path(args.marker_contracts).resolve(strict=True) + require(pin(marker_path) == pin(ROOT / MARKER) == delta['marker_delta']['new'], 'explicit marker input stale') + old_contract = read(marker_base) + new_contract = read(marker_path) + require(set(old_contract) == set(new_contract), 'marker structure changed') + changes = [] + for old, new in zip(old_contract['rows'], new_contract['rows']): + if old != new: + require(old['test'] == new['test'] == KITCHEN, 'other marker row changed') + require({k for k in set(old) | set(new) if old.get(k) != new.get(k)} + == {'source_sha256_at_schema_audit', 'source_marker_lines'}, 'marker authority/count changed') + changes.append(new) + require(len(changes) == 1 and len(old_contract['rows']) == len(new_contract['rows']) + and all(old_contract[k] == new_contract[k] for k in old_contract if k != 'rows'), 'marker contract delta') + text = (ROOT / KITCHEN).read_text().splitlines() + require(changes[0]['source_sha256_at_schema_audit'] == expected[KITCHEN]['sha256'], 'marker source SHA') + require(changes[0]['source_marker_lines'] == [{'line': i, 'text': line} for i, line in enumerate(text, 1) + if 'print("Required kitchen:' in line], 'actual source marker line/text') + markers = load('kitchen_markers', ROOT / 'tools/strict_test_markers.py') + contracts = markers.load_contracts(marker_path) + start = read(local(out, 'runner-input.json')) + end = read(local(out, 'runner-ended.json')) + require(start['protocol'] == end['protocol'] == 'guarded-standard-v1' + and start['run_id'] == end['run_id'] and start['owner_runner_pid'] == end['owner_runner_pid'], 'native standard run identity') + require(Path(start['stage']) == out / 'stage', 'native build output identity') + require(end['passed'] is True and end['engine_ended'] is True and end['failed_suites'] == 0, 'native full run did not pass') + require(start['selected_source_map'] == {n: p['sha256'] for n, p in expected.items()}, 'exact 1339 selected map') + require(pins(read(local(out, 'source-start.json'))) == {n: p for n, p in expected.items() if n != EXTRA} + or pins(read(local(out, 'source-start.json'))) == expected, 'native submitted map stale') + require(start['marker_contract_sha256'] == pin(marker_path)['sha256'] + and start['runner_sha256'] == expected['tools/run_all_tests.py']['sha256'] + and start['marker_module_sha256'] == expected['tools/strict_test_markers.py']['sha256'], 'native authority pins') + roster = ['tests/run_tests.gd'] + sorted(n for n in expected if n.startswith('tests/') and n.endswith('.gd') + and Path(n).name.startswith('test') and 'export' not in Path(n).parts and n != 'tests/s0/test_kernel_and_save.gd') + require(len(roster) == 178 and start['roster'] == roster, 'new standard full 178 required') + rows = read(local(out, 'all-tests.json')) + require(markers.review_roster(roster, rows)['passed'], 'full exact ordered native roster') + require(pin(local(out, 'all-tests.json'))['sha256'] == end['all_tests_sha256'], 'full rows receipt hash') + phases = end['phases'] + require(len(phases) == 180 and [p['phase'] for p in phases] == ['import', 'parse'] + [f'{i:03}' for i in range(178)], 'import/parse/full finite phases') + paths = ['res://' + n for n in sorted(expected) if n.endswith('.gd')] + require(read(local(out, 'parse-paths.json')) == paths, 'all submitted scripts parsed') + diagnostics = load('kitchen_diagnostics', ROOT / 'tools/regression/diagnostic_gate.py') + native = load('kitchen_native', ROOT / 'tools/regression/owned_diagnostic_process.py') + policy = read(ROOT / 'tools/regression/suite-policy.json') + pids = [] + kitchen_raw = None + generated = {} + for source, source_pin in expected.items(): + require(pin(local(out, 'stage/' + source)) == source_pin, 'native selected bytes stale: ' + source) + owners = load('kitchen_owners', ROOT / 'tools/regression/ordinary_proof_gate.py') + audio = load('kitchen_audio', ROOT / 'tools/regression/audio_evidence_gate.py') + bootstraps = read(ROOT / 'tools/regression/bootstrap-scripts.json') + for name, entry in bootstraps.items(): + require(local(out, 'bootstrap/' + name).read_bytes() == entry['text'].encode() + and pin(local(out, 'bootstrap/' + name))['sha256'] == entry['sha256'], 'existing Guard/bootstrap bytes') + for index, phase in enumerate(phases): + name = phase['phase'] + recorded = read(local(out, name + '-receipt.json')) + # all-tests adds only sequence and godot after the immutable phase receipt. + require(all(phase[k] == v for k, v in recorded.items()), 'phase receipt binding') + test = roster[index - 2] if index >= 2 else None + if test: + require(phase == rows[index - 2] and phase['test'] == test, 'full row/phase identity') + require(phase['validation_error'] is None and phase['passed'] is True and phase['failed'] is False, 'native phase failed') + command = [start['engine'], '--headless', '--path', str(out / 'stage')] + if name == 'import': + command += ['--editor', '--import', '--quit', '--verbose'] + else: + script = out / ('bootstrap/parse_full_dependencies.gd' if name == 'parse' else 'starters/' + name + '.gd') + command += ['--script', str(script), '--verbose'] + require(pin(script)['sha256'] == phase['starter_sha256'], 'actual guarded starter pin') + if test: + expected_starter = ('extends ' + json.dumps('res://' + test) + + '\nconst Guard := preload(' + json.dumps(str(out / 'bootstrap/profile_guard.gd')) + + ')\nfunc _initialize() -> void:\n\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n') + require(script.read_text() == expected_starter, 'unchanged actual guarded suite entry') + command += ['--log-file', str(out / name / 'engine-native.log')] + require(phase['command'] == command, 'actual guarded launcher command') + stdout = local(out, name + '/stdout.log').read_text() + stderr = local(out, name + '/stderr.log').read_text() + raw = stdout + stderr + require(local(out, phase['log']).read_text() == raw and pin(local(out, phase['log']))['sha256'] == phase['log_sha256'], 'literal raw native transports') + logger = local(out, name + '/engine-native.log') + require(pin(logger) == {k: phase['raw_native_log'][k] for k in ('bytes', 'sha256')}, 'native logger hash') + review = diagnostics.review_streams(native, {'stdout': stdout, 'stderr': stderr, 'native_file': logger.read_text()}, test, + local(out, 'stage/' + test) if test else None) + process = read(local(out, name + '/process.json')) + require(process == phase['native_process'] and shared.process_gate(process, review['allowed_e31_error_lines']) + and diagnostics.diagnostic_result_gate(process, review), 'native process/strict diagnostics') + began = read(local(out, name + '/started.json')) + ended = read(local(out, name + '/ended.json')) + pid = phase['actual_pid']; pids.append(pid) + require(began['actual_pid'] == ended['actual_pid'] == process['pid'] == pid + and began['command'] == ended['command'] == phase['command'] + and began['owner_pid'] == ended['owner_pid'] == start['owner_runner_pid'] + and ended['engine_ended'] is True and ended['exit_code'] == 0, 'native START/END PID/command binding') + require(began['live_identity_exact'] is True or began['tracked_child_ended_before_initial_probe'] is True, 'owned native identity') + require(began == process['actual_start_binding'] and ended == process['actual_end_binding'] + and began['event'] == 'ENGINE_STARTED' and ended['event'] == 'ENGINE_ENDED' + and ended['monotonic_ns'] >= began['monotonic_ns'] + and ended['actual_process_exit_code'] == 0 and ended['timed_out'] is False + and ended['lifecycle_error'] is None, 'raw complete lifecycle binding') + require(read(local(out, name + '/adapter-owned-cleanup.json')) == process['adapter_owned_cleanup'], 'raw adapter cleanup binding') + capture = phase['outer_actual_capture'] + outer = read(local(out, name + '/outer-owned-finally.json')) + require(all(capture[k] == v for k, v in outer.items()) and capture['all_captured_ended'] is True + and capture['outer_receipt_written'] is True and capture['outer_receipt_error'] is None + and capture['adapter_exception'] is None and capture['outer_cleanup_errors'] == [] + and capture['foreign_process_or_group_signals'] == 0 and capture['actual_captured_pids'] == [pid], 'outer native ownership') + cleanup(capture['cleanup'], [pid]) + require(phase.get('guard_error') is None and phase['default_player_entire_profile_unchanged'] is True + and phase['private_project_fields'] == 2, 'unchanged guards/default profile') + public = (ROOT / 'project.godot').read_bytes() + require(hashlib.sha256(shared.private_project(public, phase['profile_startup_name'])).hexdigest() + == phase['private_project_sha256'], 'only the two existing private profile settings') + require(read(local(out, name + '-startup-profile-before.json'))['existed'] is False, 'fresh startup profile') + for kind in ('startup', 'runtime') if index else ('startup',): + whole = phase['whole_' + kind + '_profile_after'] + archive = local(out, 'profiles/' + name + '/' + kind) + require(archive.is_dir() and shared.inventory(archive) == whole['files'], 'retained actual whole private profile') + if index: + report = read(local(out, name + '-profile.json')) + require(report == phase['actual_private_profile']['report'] and report['verified'] is True + and report['conditions'] == [True] * 5 and report['conditions_checked'] == 5 + and report['fresh_before_parent_initialize'] is True and report['profile_writes_before_guard'] == 0 + and report['project_settings_persisted'] is False + and report['actual_user_data_dir'] == report['expected_user_data_dir'], 'unchanged five Guard conditions') + require(pin(local(out, name + '-profile.json'))['sha256'] == phase['actual_private_profile']['report_sha256'] + and phase['actual_private_profile']['whole_files'] == phase['whole_runtime_profile_after']['files'] + and report['globalized_probe'] == report['actual_user_data_dir'] + '/guard-probe' + and Path(report['actual_user_data_dir']).name == report['custom_user_dir_name'], 'whole actual profile/Guard binding') + if test: + require(phase['source_sha256'] == phase['source_sha256_end'] == expected[test]['sha256'], 'suite source stale') + parsed = markers.parse_raw(test, raw, contracts, expected[test]['sha256']) + require(parsed['passed'] and parsed['checks'] == phase['reported_checks'] + and parsed['marker'] == phase['completion_marker'], 'strict actual marker/count') + if test in policy['suite_policies']: + require(phase['policy'] == shared.phase_policy(test, local(out, 'stage/' + test), policy['suite_policies']), 'unchanged suite cleanup/threshold policy') + proof_policy = phase['policy'] + if proof_policy['ordinary_cleanup_prepared']: + require(owners.ordinary_gate(local(out, name + '-teardown'), pid, out, + proof_policy['ordinary_control_labels']) == phase['ordinary_owner_pairs'], 'actual ordinary cleanup proof') + if proof_policy['old_bounded_teardown'] or test == 'tests/ui/test_ui_audio_feedback.gd': + require(audio.teardown_gate(local(out, name + '-teardown'), pid, out) == phase['old_bounded_proof'], 'actual bounded cleanup proof') + if test == KITCHEN: + kitchen_raw = raw + require(parsed['checks'] == 163, '145 existing checks plus 17 controls and one fresh proof') + elif name == 'parse': + require(raw.count('FULL_DEPENDENCY_PARSE checks=%d failures=0\n' % len(paths)) == 1, 'strict all-script parse') + generated.update({n: {k: p[k] for k in ('bytes', 'sha256')} for n, p in phase['generated_reports'].items()}) + require(len(set(pids)) == 180 and end['actual_captured_PIDs'] == pids, 'all native PID bindings') + cleanup(end['final_owned_cleanup'], pids) + require(end['error'] is None and end['cleanup_errors'] == [] and end['guard_errors'] == [], 'full cleanup/guards') + require(start['default_player_before'] == end['default_player_after'] + and start['default_player_before']['logs_and_locks_included'] is True, 'whole default profile including locks/logs preserved') + require(shared.inventory(local(out, 'stage'), stage=True) == dict(expected, **generated), 'whole stage/source/report set preserved') + labels = ['outside'] + [verb + ' ' + field for field in ['version', 'relationships', 'threshold_candidates', 'next_candidate_sequence'] + for verb in ['delete', 'change']] + ['sequence +1', 'nonempty originals', 'version 0', 'version 3', 'version 2', + 'equal reordered keys', 'equal String reencoding', 'byte-identical deep copy'] + lines = kitchen_raw.splitlines() + for label in labels: + require(lines.count(' ✓ legacy migration control immutable ' + label) == 1, 'native negative/positive control missing: ' + label) + # Successful return is deliberately silent. This gate never produces its own PASS. + + +def main(): + parser = argparse.ArgumentParser(allow_abbrev=False, description=__doc__) + parser.add_argument('--verify-only', required=True, action='store_true') + parser.add_argument('--marker-contracts', required=True) + parser.add_argument('--full-output', default='native-full178', help='relative to BUILD_OUTPUT_DIR') + verify(parser.parse_args()) + + +if __name__ == '__main__': + try: + main() + except (OSError, ValueError, KeyError, TypeError, AssertionError) as error: + print(str(error), file=sys.stderr) + raise SystemExit(1) diff --git a/tests/business/test_required_kitchen.gd b/tests/business/test_required_kitchen.gd index 47b252a..df535f9 100644 --- a/tests/business/test_required_kitchen.gd +++ b/tests/business/test_required_kitchen.gd @@ -188,7 +188,93 @@ func _io_valid(event: Dictionary, kind: String, operation: StringName, path: Str var payload: Dictionary = event.get("payload", {}) return event.get("event_type") == kind and event.get("source_service") == "save_service" and event.get("causation_command_id") == 0 and event.get("entity_ids") == [] and not String(event.get("event_id", "")).is_empty() and payload.get(&"operation") == operation and typeof(payload.get(&"ok")) == TYPE_BOOL and payload[&"ok"] and payload.get(&"code") == &"OK" and payload.get(&"save_path") == path and payload.get(&"checkpoint_absolute_minute") == checkpoint_minute and payload.get(&"saved_absolute_minute") == checkpoint_minute and event.get("absolute_minute") == checkpoint_minute and not String(payload.get(&"save_id", "")).is_empty() and payload.get(&"request_id") == 0 and payload.get(&"reason") == (&"manual" if operation == &"save" else &"") and (operation != &"load" or typeof(payload.get(&"duration_us")) == TYPE_INT and payload[&"duration_us"] >= 0 and typeof(payload.get(&"runtime_duration_us")) == TYPE_INT and payload[&"runtime_duration_us"] >= 0) -func _compare_loaded(writer: Dictionary, write_path: String, read_path: String, label: String) -> void: +func _typed_sha(value: Variant) -> String: + var context := HashingContext.new() + context.start(HashingContext.HASH_SHA256) + context.update(var_to_bytes(value)) + return context.finish().hex_encode() + +func _legacy_relationship_v1_to_v2(expected_subtree: Variant, actual_subtree: Variant) -> Dictionary: + var rejected := {"ok": false, "expected": null, "proof": {}} + if not expected_subtree is Dictionary or not actual_subtree is Dictionary: return rejected + var old: Dictionary = expected_subtree + var actual: Dictionary = actual_subtree + # The genuine fixture uses StringName keys, in this exact serialization order. + if var_to_bytes(old.keys()) != var_to_bytes([&"version", &"relationships", &"threshold_candidates", &"next_candidate_sequence"]): return rejected + if typeof(old[&"version"]) != TYPE_INT or old[&"version"] != 1: return rejected + if not old[&"relationships"] is Array or not old[&"threshold_candidates"] is Array or typeof(old[&"next_candidate_sequence"]) != TYPE_INT: return rejected + if var_to_bytes(actual.keys()) != var_to_bytes([&"version", &"legacy", &"relationships", &"original_relationships"]): return rejected + if typeof(actual[&"version"]) != TYPE_INT or actual[&"version"] != 2: return rejected + if not actual[&"legacy"] is Dictionary or var_to_bytes(actual[&"legacy"]) != var_to_bytes(old): return rejected + if var_to_bytes(actual[&"legacy"].get(&"next_candidate_sequence")) != var_to_bytes(old[&"next_candidate_sequence"]): return rejected + if not actual[&"original_relationships"] is Array or not actual[&"original_relationships"].is_empty(): return rejected + # create_snapshot uses a typed compatibility collection even for empty v1 input. + var projection: Array[Dictionary] = [] + for entry: Variant in old[&"relationships"]: + if not entry is Dictionary: return rejected + projection.append(entry.duplicate(true)) + if var_to_bytes(actual[&"relationships"]) != var_to_bytes(projection): return rejected + var originals: Array[Dictionary] = [] + var migrated := {&"version": 2, &"legacy": old.duplicate(true), &"relationships": projection.duplicate(true), &"original_relationships": originals} + if var_to_bytes(migrated) != var_to_bytes(actual): return rejected + return {"ok": true, "expected": migrated, "proof": {"old_v1_bytes": var_to_bytes(old).size(), "old_v1_sha256": _typed_sha(old), "replaced_subtree_sha256": _typed_sha(migrated)}} + +func _legacy_checkpoint(expected: Dictionary, reader: Dictionary) -> Dictionary: + var migration := _legacy_relationship_v1_to_v2(expected[&"systems"][&"runtime"][&"relationships"], reader[&"systems"][&"runtime"][&"relationships"]) + if not migration["ok"]: return migration + # Reverse just this subtree in a copy of the reader: every other byte must + # already match the writer after the separately validated IO1 replacement. + var outside := reader.duplicate(true) + outside[&"systems"][&"runtime"][&"relationships"] = expected[&"systems"][&"runtime"][&"relationships"].duplicate(true) + if var_to_bytes(outside) != var_to_bytes(expected): return {"ok": false} + var result := expected.duplicate(true) + result[&"systems"][&"runtime"][&"relationships"] = migration["expected"].duplicate(true) + if var_to_bytes(result) != var_to_bytes(reader) or result != reader or SimulationDigest.compute(result) != SimulationDigest.compute(reader): return {"ok": false} + return {"ok": true, "expected": result, "proof": migration["proof"]} + +func _legacy_control(writer: Dictionary, reader: Dictionary, label: String, kind: String, field: StringName = &"", value: Variant = null) -> void: + var writer_bytes := var_to_bytes(writer); var reader_bytes := var_to_bytes(reader) + var candidate := writer.duplicate(true); var observed := reader.duplicate(true) + var old: Dictionary = candidate[&"systems"][&"runtime"][&"relationships"] + var actual: Dictionary = observed[&"systems"][&"runtime"][&"relationships"] + match kind: + "outside": candidate[&"legacy_control_outside"] = true + "delete": old.erase(field) + "change": old[field] = value + "sequence": actual[&"legacy"][&"next_candidate_sequence"] += 1 + "originals": actual[&"original_relationships"].append({&"unapproved": true}) + "version": actual[&"version"] = value + "reorder": + var reordered := {} + var keys := old.keys(); keys.reverse() + for key: Variant in keys: reordered[key] = old[key] + candidate[&"systems"][&"runtime"][&"relationships"] = reordered + "reencode": + var reencoded := {} + for key: Variant in old: reencoded[String(key)] = old[key] + candidate[&"systems"][&"runtime"][&"relationships"] = reencoded + var before_candidate := var_to_bytes(candidate); var before_observed := var_to_bytes(observed) + var encoding_control := true + if kind in ["reorder", "reencode"]: + encoding_control = candidate == writer and before_candidate != writer_bytes + var result := _legacy_checkpoint(candidate, observed) + expect(encoding_control and result["ok"] == (kind == "copy") and var_to_bytes(writer) == writer_bytes and var_to_bytes(reader) == reader_bytes and var_to_bytes(candidate) == before_candidate and var_to_bytes(observed) == before_observed, "legacy migration control immutable " + label) + +func _legacy_controls(writer: Dictionary, reader: Dictionary) -> void: + _legacy_control(writer, reader, "outside", "outside") + for field: StringName in [&"version", &"relationships", &"threshold_candidates", &"next_candidate_sequence"]: + _legacy_control(writer, reader, "delete " + String(field), "delete", field) + _legacy_control(writer, reader, "change " + String(field), "change", field, null) + _legacy_control(writer, reader, "sequence +1", "sequence") + _legacy_control(writer, reader, "nonempty originals", "originals") + for version: Variant in [0, 3, "2"]: + _legacy_control(writer, reader, "version " + str(version), "version", &"", version) + _legacy_control(writer, reader, "equal reordered keys", "reorder") + _legacy_control(writer, reader, "equal String reencoding", "reencode") + _legacy_control(writer, reader, "byte-identical deep copy", "copy") + _proof("legacy14-migration-controls", {"attempts": 17, "writer_sha256": _typed_sha(writer), "reader_sha256": _typed_sha(reader), "failures": failures}) + +func _compare_loaded(writer: Dictionary, write_path: String, read_path: String, label: String, migrate_relationships_v1: bool = false) -> void: var reader: Dictionary = runtime.create_snapshot() var old: Dictionary = writer[&"systems"][&"runtime"][&"domain_events"]["recent_events"].back().duplicate(true) var actual: Dictionary = reader[&"systems"][&"runtime"][&"domain_events"]["recent_events"].back().duplicate(true) @@ -199,9 +285,22 @@ func _compare_loaded(writer: Dictionary, write_path: String, read_path: String, if event.get("event_id") == old.get("event_id"): valid = valid and var_to_bytes(event) == var_to_bytes(old) event["event_type"] = actual["event_type"]; event["payload"] = actual["payload"].duplicate(true); replaced += 1 + var migration_proof := {} + if migrate_relationships_v1: + _legacy_controls(expected, reader) + var migration := _legacy_checkpoint(expected, reader) + valid = valid and migration["ok"] + if migration["ok"]: + expected = migration["expected"] + migration_proof = migration["proof"] + migration_proof["write_checkpoint_sha256"] = FileAccess.get_sha256(LEGACY.path_join("write-checkpoint.var")) valid = valid and replaced == 1 and expected[&"systems"][&"runtime"][&"domain_events"]["published_ids"].has(actual.get("event_id")) and var_to_bytes(expected) == var_to_bytes(reader) and expected == reader and SimulationDigest.compute(expected) == SimulationDigest.compute(reader) _proof(label + "-expected-io1", expected); _proof(label + "-actual", reader) - _proof(label + "-io1-binding", {"whole_typed_bytes_equal": valid, "writer_io": old, "reader_io": actual, "mapped_fields": ["event_type", "payload"], "event_deletion_count": 0, "write_path": write_path, "read_path": read_path, "copied_legacy_package": write_path != read_path}) + var binding := {"whole_typed_bytes_equal": valid, "writer_io": old, "reader_io": actual, "mapped_fields": ["event_type", "payload"], "event_deletion_count": 0, "write_path": write_path, "read_path": read_path, "copied_legacy_package": write_path != read_path} + if migrate_relationships_v1: + binding["mapped_fields"].append("relationships_v1_to_v2") + binding["relationships_v1_to_v2"] = migration_proof + _proof(label + "-io1-binding", binding) expect(valid, "whole typed loaded state unchanged except complete identity/saveID-bound IO1: " + label) func _new_saved_boundary() -> void: @@ -340,7 +439,7 @@ func _legacy_saved_boundary() -> void: expect(persisted is Dictionary and inspected.get(&"ok", false) and inspected.get(&"read_only", false) and var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(persisted) and var_to_bytes(live) == var_to_bytes(runtime.create_snapshot()) and _package(path) == hashes, "old14 inspection exact complete typed persisted state and read-only for every live byte/package") expect(runtime.load_from_path(path).get(&"ok", false), "ordinary strict old14 formal Load, schema stays14") _listen(true) - _compare_loaded(expected, contract["slot"], path, "legacy14-load") + _compare_loaded(expected, contract["slot"], path, "legacy14-load", true) var cafe: FacilityInstance = runtime.facility_service.get_instance(ids["cafe"]) var service: ServiceInstance = runtime.facility_service.get_service(ids["service"]) expect(cafe.related_facility_id.is_empty() and service.state == ServiceInstance.State.ACTIVE and service.elapsed_minutes == 5 and cafe.state == FacilityInstance.State.OPEN and not cafe.related_facility_operating and cafe.operation_state == FacilityInstance.OperationState.INACTIVE, "old raw OPEN/ACTIVE5 remains serialized immediately; derived missing kitchen false")