diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/IMPLEMENTATION.md b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/IMPLEMENTATION.md index a46d108..a2166f3 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/IMPLEMENTATION.md +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/IMPLEMENTATION.md @@ -577,3 +577,133 @@ failures respectively; all earlier evidence remains unchanged. This table records the required implementation targets, not completed validation. No native or formal validation is authorized for this worker. + + +## S5 implementation (repair 10; EQ_S5_SCALE_PLAN r01/r02) + +Implemented by Claude Opus 5.5 in the Desktop control session after the user moved +implementation from Codex to Opus. The exact-S native of 6f75c9a failed in R-A and stays a +failure; this stage fixes the harness defects that only the real engine exposed and makes +the full R scope (E1-E5, 39 scenarios) fit the unchanged limits. All earlier text below and +above this section is retained history. + +### Defects found only by the real engine (scratchpad diagnostics, not formal evidence) + +- write_evidence created `user://evidence` with the user:// path. Under the guard's runtime + user-dir switch this fails; the directory is now created from the globalized path, as the + RESTART helpers do. +- typed_info encoded get_typed_script(), a null Object for unscripted typed containers, as an + unsupported Variant 24. It is now transported as NIL ([0], which the Python side already + required); a scripted element type still fails. +- Package manifest mutation re-serialized a JSON-parsed manifest. Godot parses every JSON + number as float, so untouched integers became 390.0, 1.0 and so on. Only the one key line is + now edited as text; the Python rule already rejects any other change (new negative test). +- The guard receipt was recorded after JSON parsing, with the same float problem. The exact + receipt text is now kept beside it; Python parses that text strictly, compares it with the + independently saved guard file and binds its SHA. +- Godot instance IDs are signed 64-bit and RefCounted IDs are negative. The cache identity + check accepts the sign. Raw values stay in raw; only the SPEC 3.2 exclusion (process-local + instance ID) is applied across PIDs, with both values and the reason recorded. +- A large E4 return is a blob; its ok/code facts are taken from the live return value. +- After the first harness error, later scenarios are no longer run (the run already failed). + +### Product behavior recorded as an existing defect, separate from harness capture + +autoload/simulation_runtime.gd:788 and :1346: a successful restore replaces domain_events +with a new DomainEventStream and does not carry subscribers. The probe's wildcard +subscription stayed on the old stream after E4 restore, so SAVE_LOADED and all later events +were not delivered. follow_event_stream() runs after every E3 call and after E4 restore and +load. When the stream changed it records the new stream's subscriber IDs (no instance IDs), +recovers the unobserved events from recent_events() in publication order, and subscribes to +the live stream again. check_e3/check_e45 record event_stream_replaced_subscribers_not_carried +as an existing defect (never PASS). Completeness is still proven only by the counter delta, +the published_ids delta and the actual event IDs; an event outside the 256-entry +recent_events window would remain missing and fail. lifecycle.event_stream_replacements must +equal the per-scenario facts. No event is renumbered, no product file or hook is changed. + +### Evidence representation (complete and reversible) + +- Trees larger than 64 KiB, raw S/K/C bytes and package files are written once as + evidence/blobs/.gz, named by the SHA-256 of the uncompressed bytes; records hold + {"blob": sha}. Small trees stay inline. lifecycle.blobs lists every blob with its size, + compressed size and compressed SHA; verify_eq binds that list to the files exactly and + decompresses every blob to check name and size. +- Godot's JSON text of a tree equals json_bytes(tree) (checked on the R fixture tree), so a + blob name is the tree SHA. var_to_bytes distinguishes typed metadata, String/StringName, + int/float and key order (checked in the engine), so transport reuses the proof for equal + bytes. Every checkpoint carries a complete tree; the diff chain is gone (tree_diff took 5.4 s + per R checkpoint). Report and lifecycle hold references only, which removes the memory and + swap growth seen in the failed native. + +### Python: same results, less work + +- A blob is accepted only when its uncompressed bytes hash to its name, and its tree is used + only after the full validate_tree (types, arity, metadata, keys) has passed on first use; + only then is its SHA recorded as valid. A failed blob is never recorded and fails again. + A matching SHA is never taken as proof of types (Root's repro of [2, true] / [24, 0] in a + correctly named blob is a regression test). Valid blob trees are shared, never mutated; + checkpoints are loaded one at a time. Equal SHA means equal; otherwise Python == decides, which is exact JSON equality for + validated trees (INT/BOOL payloads are int, floats are 16-hex strings). +- Normalization runs the original closed policy unchanged on a skeleton holding only + systems.runtime.domain_events.recent_events, read directly from the tree text. The rest of + the text is proven free of the profile root, the reserved token, _us/_ms names and + save_service; otherwise the original full walk runs. Restore runs the original reversible + check on the skeleton. +- normalized_digest builds the same canonical JSON without per-node copies and is memoized by + SHA (17.4 s to about 1.2 s on an R tree, identical value). +- Expected inputs, X(B, event) and the E5 expectation copy only the dictionaries on the + changed path. Large derived trees are recorded by SHA descriptor in joined facts and checker + results; compare_reports rebuilds them from the blobs when SHAs differ. +- test_eq.S5 proves equivalence against the retained S1-S4 algorithms (compare_trees, + canonical digest, deepcopy builders, original normalization including violations, tokens and + non-array recent_events), that every one of 31 blob checkpoints is verified alone exactly as + inline trees, and the new static requirements (full snapshot transport, blob write, gzip + roundtrip, overwrite refusal, stream following). + +### Snapshot signal rule corrected (EQ_S5_SCALE_PLAN r03) + +The S3a rule "exactly one snapshot_published/state_updated pair per measured minute" was a +harness assumption. Paused submits publish once each after their command events +(simulation_runtime.gd:486-493, :508-512, :5768-5793), besides the tick-end publish (:1745-1749) +and a successful restore (:1473); R minute 648 had 50 commands and 51 pairs (the r2 check +failure on the old rule is retained). The probe now stores, per signal, the domain_event +counter and _next_command_id at emission (read only). check_publishes orders signals and +events together and requires: complete pairs with equal counters; a command publish after +exactly one submit whose command is in its trailing batch; no submit before any other publish +and none left unpublished; every command event covered; and the remaining tick/restore +publishes equal to the operations performed (one per measured minute; E3 successful +restores; E4 1/1/1/1/1 with build only when applied; E5 2 or 3). Missing, extra and reordered +pairs, including a missing publish between adjacent commands, fail. Actual counts stay raw and +signal_order is compared exactly across versions. + +### Python speed, measured on retained R raw (r3, scratchpad diagnostic, not formal) + +One R phase checked in the phase_evidence order (lifecycle, observations, check_report, +join), with full stdout/stderr and Python's own exit kept for every run: +1270.3 s (first S5 code, with concurrent profiling) -> 552.9 s (memo of identical inputs, +copy-free builders) -> 301.8 s (labelled digest, parse-free restore, copy-free diffs) -> +259.7 s (facts derived at first parse) -> 58.3 s (cyclic GC off inside memo_scope; before +the blob validation fix, so not used as a result). With full blob validation, r3 + r4 checked +in one process took 136.5 + 132.0 s and their comparison 4.2 s with 0 differences; with the +validate_tree rewrite (same checks, messages and order; 4000-case differential test against +the S1 version) four phase checks in parallel processes took 131 s wall (R 111.9 / 112.1 s, +O 5.9 / 6.2 s), all ok. verify_eq therefore runs the four phase checks in parallel worker +processes of the same verification and does the same-role comparisons in the parent with the +workers' validated SHAs and normalization facts; the result equals the sequential mode, which +remains the fallback and the mode used by unit tests. The unchanged 300 s native-verify +limit is kept; the Runner configuration is not changed. + +- memo_scope: inside one verification call, identical check inputs (SHA of the exact JSON) + are checked once; check_lifecycle and check_report shared E3/E45/measurement work. It + also turns cyclic GC off for its duration; typed trees are acyclic lists, results do not + depend on GC, and GC is restored on exit. +- The joined harness digest is labelled `normalized-sha256:` (SHA of the exact normalized + JSON, computed by splicing the normalized events into the blob text). compare_reports + compares labelled digests exactly and, only when two differ, compares the canonical + (dictionary-order-insensitive) digests; equal canonical digests are reported as + dictionary-order-only, otherwise the difference is recorded. checker.normalized_digest + remains the canonical digest (same values as S1). +- Normalization facts, counters and next_command_id are derived when a blob tree is first + parsed and memoized by SHA; restore uses the memoized skeleton, so join and comparison do + not parse again. compare_trees records nodes shared with the immutable trees instead of + deep copies; keys use inline text. diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/PROGRESS.md b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/PROGRESS.md index 2e0d117..926e7de 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/PROGRESS.md +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/PROGRESS.md @@ -1,4 +1,48 @@ -# P28-02-EQ progress — S4 retained completion r03 +# P28-02-EQ progress — S5 (repair 10, Claude Opus 5.5 Desktop implementation) + +Implementer: Claude Opus 5.5 in the Desktop control session, by the user's delegation +("Codexの使用量上限いきそうなので、作業をひきとって"; Root relayed 「実装担当含めOpusに委任」). +The registered contract keeps worker codex; no Codex/Astra run was started for this stage. +Submission goes through the normal Runner submit, which makes the commit. 正式受入は保留、原本加算0。 + +- Exact-S native of 6f75c9a failed in R-A (user://evidence mkdir) and stays a failure; the + owner intake recorded it and the normal repair made repairs 10. +- Fixed the harness defects that only the real engine showed (evidence mkdir under the guard, + typed script metadata, manifest/guard JSON floats, signed instance IDs, large E4 returns) and + recorded the product's event-stream replacement on restore as an existing defect while the + probe keeps capturing every event (EQ_S5_SCALE_PLAN r02 §3-4). +- Content-addressed gzip blobs and exact Python fast paths keep R and O at full E1-E5 scope + (39 scenarios each) within the unchanged 1800 s phase and 300 s validation limits. +- Scratchpad diagnostics with the same guard and HOME redirected (not formal evidence): + see the S5 section of IMPLEMENTATION.md for measured times and sizes. +- Unit evidence (engine-free `python3 -B test_eq.py`; full raw outputs with SHA files): + - UNIT_OUTPUT_S5_ATTEMPT_01: 146 tests, 9 failures and 5 errors (the S5 evidence format + against old fixtures and one old static snippet list). Kept as a failure. + - UNIT_OUTPUT_S5_ATTEMPT_02: 156 tests OK in 211.416 s (run beside an R engine run). + - UNIT_OUTPUT_S5_ATTEMPT_03: 160 tests OK in 130.451 s, Python exit 0 (after the parallel + verifier fix). An intermediate full run failed once (160 tests, 1 error: closed worker + stdin); only its single-test rerun output was kept, and the fix is in phase_results. + - Class-targeted runs during development were displayed through tail and are not claimed + as retained raw results. + - UNIT_OUTPUT_S5_ATTEMPT_04: 151 tests, 3 errors, Python exit 1: the new differential test + imported random, outside the closed EQ import list, so the driver's closure check rejected + test_eq.py. The test now uses its own deterministic generator; the list is unchanged. + - UNIT_OUTPUT_S5_ATTEMPT_05 (final, all changes included): 161 tests OK in 121.627 s, + Python exit 0, outer 121.748 s; same bytes as UNIT_OUTPUT.txt, + SHA-256 7372a382cc2f3c090013874d8f28f17cad4cb1b965ad86c86de7a21fc2d851a8. +- Test bodies changed for the S5 format, each recorded here: E3 evidence and E4 restore/load + details carry event_stream; the S4 phase fixture lifecycle is S5; the S2 static list names + transport(snapshot_value) instead of the removed diff-chain snippet; s2_report signals carry + event_counter/command_counter and its runtime next_command_id; the E4/E5 fixture emits the + product's publishes; the accepted-restore E3 test adds the restore publish; the E4 load + fixture event uses a domain_event ID. All other earlier test bodies and raw outputs are + unchanged; 15 S5 tests were added. + +Next: Runner submit → exact-S native 7 phases → bundle → p28-02-eq-unit and +p28-02-eq-native-verify → fresh independent Opus review of all 6 conditions → normal +qualification, approval, integration and final → OPT/whole-unit. 23/88、4/30は不変。 + +## Retained S4 r03 progress (unchanged historical text) S4 の保持済み実装に対する残作業を完了し、局所unitの証拠として提出する。 正式受入は保留、原本加算0。 diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/S5_SUPPLEMENTARY.json b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/S5_SUPPLEMENTARY.json new file mode 100644 index 0000000..bfcccf4 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/S5_SUPPLEMENTARY.json @@ -0,0 +1,40 @@ +{ + "actual_commander_context": { + "implementer": "Claude Opus 5.5 (claude-opus-5-5), Desktop control session local_a7f85421-3eed-4482-8030-c0dca669573a", + "authority": "User delegation: Codexの使用量上限いきそうなので、作業をひきとって; Root relayed 実装担当含めOpusに委任. Contract worker field stays codex as registered; no Codex/Astra run.", + "stage": "S5 (repair 10): real-engine harness defects, content-addressed blob evidence, exact fast Python paths, publish binding, full R and O E1-E5 scope.", + "actual_documents": { + "EQ_S5_SCALE_PLAN.txt": { + "sha256": "4a4d52dd36ae2556e623c77afa17c718411f2161c3c2fec450f2016097b5794f", + "bytes": 6192, + "full_unedited": "EQ_S5_SCALE_PLAN(P28-02-EQ repair 10。実際のエンジンでの診断に基づいて、証拠の表現と R の範囲を確定する。SPEC、TASK、6条件、eq/references、eq/registration、補足 r01/r02/r03 は変更しない。実装は、ユーザーの委任により Claude Opus 5.5 が Desktop で行う)\n\n1. 実際の失敗と診断(全て scratchpad の診断。正式の証拠ではない)\n- 正確な S 6f75c9a の native は R-A で失敗した(user://evidence を作れない)。\n- 修正1: write_evidence は、guard が実行中に切り替えた user dir の下では、user:// のままの mkdir に失敗する(guard の無い普通の環境では成功する)。RESTART の helper と同じく globalize してから作る。\n- 修正2: 型付きの container の get_typed_script() は、script の無いとき null の Object を返す。encode はそれを Variant 24 として拒否していた(R の S の型付きの container 約1.5万個の全て)。Python の側は既に [0] を期待しているので、NIL として運ぶ。script のある型は拒否のまま。\n- 修正3: harness の error が出たら、それ以降の scenario を行わない(失敗は既に確定しているため)。\n- O の診断(HOME を scratchpad に向け、同じ guard で実行): 27秒で完走、lifecycle の error 0件、証拠105件、227MB + stdout 35MB。\n- 規模の実測:\n - R の S は 11.36MB で、O(96KB)の約118倍。O の証拠の量から、R は1回で約30GB、Godot の時間は約50分になる。空き容量(約11〜15GB)と phase の制限1800秒を超える。\n - R の測定区間の診断: checkpoint 1つに約20秒、1 tick の diff は約1MB。空き容量は、Godot の memory(全ての木を report と lifecycle に保持)によって5GB減った。\n - Godot の処理(R の S 1つ): create_snapshot 75ms、var_to_bytes 39ms、gzip 42ms、encode 0.94秒、decode 0.85秒、JSON 化 0.73秒、tree_diff 5.4秒。\n - Python の処理(R の木1つ): 正規化3.8秒、復元5.2秒、正規化 digest 13秒。木1組の比較に約22秒かかり、R-A と R-B の比較だけで数時間かかる。正式の validation の300秒(変えない)に収まらない。\n - 木の JSON は gzip で 13.4MB から 0.54MB になる。Godot の JSON の文字列と Python の json_bytes は、R の木で bytes が完全に一致した。var_to_bytes は、型付きの container の型、String と StringName、int と float、dict の順序の違いを全て区別した。\n - O の木102件で、正規化の entry 1256件は全て systems.runtime.domain_events.recent_events の下にあり、violation は0件だった。\n\n2. 証拠の表現(完全な状態・全イベントを落とさない。可逆)\n- 内容で番地を付けた blob: 大きい木の JSON(64KB を超えるもの)と、S/K/C の var_to_bytes の bytes を、evidence/blobs/<内容の SHA-256>.gz に gzip で1回だけ書く。記録には {\"blob\": SHA} を置く。小さい木(event、戻り値など)は従来どおり inline。\n- 木の SHA は、blob を展開した bytes の SHA-256 と一致する(JSON の bytes が Godot と Python で同じため)。Python は、木を読み込まずに C の速度で検証できる。\n- transport は、var_to_bytes の SHA が同じ値の木の証明を再利用する(var_to_bytes が上の全ての違いを区別するため、同じ bytes は同じ木)。\n- checkpoint の S は、diff ではなく、毎回完全な木の blob 参照にする(tree_diff の5.4秒を除く)。latest の bytes が S と同じときは、従来どおり same_as_S。\n- Godot の memory: report と lifecycle には参照だけを持ち、木を保持しない。\n\n3. Python(同じ意味の結果を、少ない計算で出す)\n- blob の読込は、SHA の検証つきで、同じ SHA を1回だけ読む。読んだ木は変更しない(共有する)。\n- 正規化の対象が無い木(JSON の bytes に profile root、予約の token、_us/_ms で終わる key が無い)は、正規化の結果が入力と同じで、sidecar の entry と violation が空であることが、関数の定義から決まる。その場合は複製せずに同じ木を返す。recent_events が配列であることの検査は残す。それ以外の木は、従来の関数で正規化する。\n- 正規化の digest は、entry がある latest の木だけで計算する(比較で使うのはその場合だけ)。\n- 従来の関数と新しい速い経路の結果が一致することを、試験で確かめる。\n\n4. R と O の範囲\n- R(1299 規模の fixture): E1/E2 の測定区間(B1 と30 tick の完全な S/K/C、全イベント、signal)。規模に依存する同等性は、ここで確かめる。\n- O: E1/E2 と、E3/E4/E5 の全て(39件の scenario)。\n- 理由: E3/E4/E5 は、snapshot の API の拒否・cache・所有権の振る舞いで、世界の大きさに依存しない。同じ code の経路を O で全て通る。R でこれらを行うと、木の比較の量が増え、正式の validation の300秒、phase の1800秒、空き容量のどれにも収まらない(上の実測)。\n- 失うもの: R でだけ適用していた E3 の duplicate_facility(O の B1 は施設0件なので NA)。これは記録し、PASS に補わない。published_ids の重複は O で適用する。\n- SPEC と6条件は、scenario を R で行うことを求めていない(EQ5 は、R と O の同じ版の2 PID の比較の差が0であること)。EQ_S3 / S3b の計画の「R と O」は、この計画で置き換える。\n\n5. 変えないもの\n- SPEC、TASK、6条件、両方の validation と300秒、900秒、権限、課金、API の fallback の無効。\n- 正確な S の native の7つの phase、bundle、両方の validation、fresh で独立した Opus の6条件のレビュー、通常の統合と final。原本の 23/88・4/30 への加算は0。\n- 既存の試験の本体と、全ての失敗の履歴は残す。形式の変更に必要な修正は、記録して行う。\n" + }, + "EQ_S5_SCALE_PLAN_R02.txt": { + "sha256": "ebee0ce76561a677b4454c59a091bafef242a406f7099485a508620dccfbb739", + "bytes": 6569, + "full_unedited": "EQ_S5_SCALE_PLAN r02(P28-02-EQ repair 10。EQ_S5_SCALE_PLAN(r01)の §1、§2、§5 は、そのまま有効。この r02 は、r01 の §3 と §4 を置き換え、実際のエンジンでの診断で見つかった harness の不具合の扱いを加える。r01 の §4(R の E3/E4/E5 を外す案)は取り下げる。SPEC、TASK、6条件、eq/references、eq/registration、補足 r01/r02/r03 は変更しない)\n\n1. 範囲(r01 §4 を置き換える)\n- 凍結した参照 eq/references/OPT_CONTRACT_T1.md の §3 は「同等性 E1〜E5(EQ で凍結する。R と O の両方で行う)」と定める。\n- R と O の両方で、E1/E2(B1 と30 tick の完全な S/K/C、全イベント、signal)と、E3 の18件、E4 の5件、E5 の16件(39件の scenario)を行う。R でだけ適用する E3 の duplicate_facility も、R で適用する。\n- 全ての状態・イベント・所有権の検査を、R と O のそれぞれで行う。O の実行や NA の記録で、R の代わりにしない。\n- 診断の実行は、正式の受入に補わない。\n\n2. Python の計算(r01 §3 を置き換える。結果は従来の関数と同じ)\n- blob の木は、展開した bytes の SHA-256 が名前と一致するものだけを受け付け、内容の SHA を持つ共有のリストとして読む。変更しない。checkpoint の列は、必要な時点で1つずつ読む(R の木を同時に全部 memory に置かない)。\n- 比較: SHA が等しければ等しい。そうでなければ Python の == で比べる。validate_tree は、INT/BOOL の値を int に限り、浮動小数を16桁の hex 文字列に限るので、検証した木では == が JSON の完全一致と同じになる。\n- 正規化: systems.runtime.domain_events.recent_events の部分木を、木の JSON の中から直接読み出し、その4つの辞書だけからなる骨組みの木に入れて、従来の正規化の関数そのものを実行する。それ以外の部分に、profile root、予約の token、_us/_ms で終わる名前、save_service が無いことを、文字列の出現数で証明する(root は印字可能な ASCII で、[ ] \" , \\ { } を含まない場合に限る)。証明できない場合は、従来の全体の走査を行う。復元も、骨組みの上で従来の可逆の検査を行う。\n- 正規化の digest は、要素ごとの deepcopy をやめ、同じ JSON の bytes を作る実装にし、SHA で memo する(R の木で17.4秒が約1.2秒、値は一致)。\n- 組み立てた期待の状態(E3 の入力、X(B, event)、E5 の期待)は、変更する経路の辞書だけを複製して作る(deepcopy の後に変更したものと同じ JSON)。大きいものは SHA つきの木として正規化する。\n- 結合した事実と検査の結果に入れる大きな木は、SHA の記述子(typed_tree_sha、source_tree_sha、normalized)で記録する。内容は raw の blob にある。版の間で SHA が違う場合は、blob から木を作り直して、dict の順序だけの違いを許す比較を行う。\n- 新しい速い経路が従来の関数と同じ結果を出すことを、O の診断の状態の木162件の全てで確かめた(正規化した木、sidecar、正規化した SHA、復元。全て bytes で一致)。局所の試験にも加える。\n\n3. 実際のエンジンで見つかった harness の不具合(合成の試験では出なかったもの)\n- package の manifest の改変: Godot の JSON は数値を全て float として読むので、読み直して書き戻すと、改変しない整数の欄も 390.0 などに変わっていた。改変する key の1行だけを文字列で編集する。\n- guard の受領: 同じ理由で、probe の記録の整数が float になっていた。受領の原文をそのまま記録し、Python はそれを厳密に読んで、独立に保存した guard と照合し、原文の SHA も照合する。\n- instance ID: Godot の instance ID は符号つきの64bit で、RefCounted は負の値になる。検査は符号を受け付ける。実際の値は raw に残す。版の間の比較から除くのは、SPEC §3.2 が許す「instance ID の数値そのもの」(理由: プロセスごとに変わる値)だけで、従来どおり除外の理由と両方の実際の値を記録する。\n- E4 の戻り値: 64KB を超える戻り値は blob になるので、probe の中では戻り値そのものから事実を取る。\n\n4. 製品の既存の挙動と、harness の捕捉の区別\n- 製品の _restore_snapshot_internal は、復元に成功すると domain_events を新しい DomainEventStream に置き換え、購読者を引き継がない(autoload/simulation_runtime.gd:788、:1346)。そのため、E4 の restore の後、probe の購読は古い stream に残り、E4 の load の SAVE_LOADED を含む以後のイベントが届かなかった。\n- harness の捕捉: E3 と E4 の操作の直後に、stream が置き換わったかを確かめる。置き換わっていれば、新しい stream の recent_events() から未観測のイベントを公開の順序で取り戻し、新しい stream に購読し直す。事実(置き換え、新しい stream の購読者の ID の一覧、取り戻したイベントの ID)を記録する。instance ID は入れない。\n- 製品の事実: 購読者が引き継がれなかったことを、その scenario の「既存の不具合」として記録する。PASS にしない。\n- 完全性: 全イベントの網羅は、従来どおり counter の差、published_ids の差、実際のイベントの ID の対応で検証する。recent_events の保持の上限(256件)などで取り戻せないイベントがあれば、省かずに不足として失敗のまま残す。イベントの再採番、製品の変更、hook の差し替えはしない。\n- lifecycle の置き換えの一覧は、各 scenario の記録をつなげたものと完全に一致することを検査する。\n\n5. 実測(診断)\n- O: 約11秒、lifecycle の error 0件、Python の全検査(lifecycle、観測、report、E3、E45、結合、自己比較)が約23秒で通った。\n- R(修正の前の probe): 39件の scenario で exit 0、936秒(phase の制限1800秒の内)、lifecycle の error 0件、証拠154MB(blob の展開後3.3GB、gzip 後141MB)。修正後の R の測定と、Python の検査の時間は、別に記録する。\n" + }, + "EQ_S5_SCALE_PLAN_R03.txt": { + "sha256": "8417c9044f87825453a66bae45f10cdf90428198bd2c5c44c7bce01b18de9f89", + "bytes": 3740, + "full_unedited": "EQ_S5_SCALE_PLAN r03(P28-02-EQ repair 10。r01 と r02 は、そのまま有効。この r03 は、snapshot の signal の検査の規則を訂正する。製品の仕様と値は変えない。誤っていた harness の前提の訂正である)\n\n1. 誤っていた前提\n- EQ_S3_STAGE_PLAN r02 §2 は「測定の区間の各分に、ちょうど1組の snapshot_published → state_updated」を要求した。これは、製品が1 tick に1回だけ公開するという読み取りに基づいていた。\n- 実際の R(r2 の診断、正式の証拠ではない)では、分648に51組が出た。同じ分に COMMAND_COMPLETED が50件あり、他の分はコマンドの完了が0件で1組だった。r2 の検査はこの規則で失敗し、その失敗はそのまま残す。\n\n2. 製品の source に基づく根拠\n- autoload/simulation_runtime.gd:12616-12621: _publish_snapshot は snapshot_published と state_updated を、間にイベントを挟まずに出す。\n- :486-493、:508-512: submit_command は、受け付けると _next_command_id を1つ進める。時刻の指定が無ければ、現在の分に実行する予定になる。clock が止まっていて予定が来ていれば、その場で _process_commands を実行し、公開する(手動の stepping では clock は止まっている)。\n- :5768-5793: _process_commands は、実行したコマンドの COMMAND_COMPLETED / COMMAND_FAILED を出した後に公開する(その場の実行の経路、または保留の公開)。\n- :1745-1749: tick の終わりの公開。:1473: 成功した restore の公開。\n\n3. 検査の規則(check_eq.check_publishes)\n- probe は、各 signal に、発行の時点の domain_event の counter と _next_command_id を記録する(読取りだけ)。これで signal とイベントが1つの順序に並ぶ。\n- 組: snapshot_published の直後に、同じ分・同じ counter の state_updated。順序(分、イベントの counter、コマンドの counter)は単調。区間の counter の範囲の内。\n- 2つの公開の間のイベントは、後の公開に属する。直前のイベントがその分のコマンドの完了である公開は、コマンドによる公開で、末尾に続くコマンドの完了を自分の処理分とする。直前の公開からちょうど1件 submit されていて、その submit のコマンドが処理分に含まれていなければならない。それ以外の公開の前には、submit が0件でなければならない。区間の終わりに、公開されていない submit が残ってはならない。全てのコマンドの完了が、どれかの公開の処理分に含まれていなければならない。\n- 残りの公開(tick の終わりと成功した restore)の数は、実際に行った操作の数と完全に一致しなければならない: 測定の区間は各分ちょうど1。E3 は成功した restore/load の数(不正な入力の受理は既存の不具合として記録)。E4 は build(適用した場合)1、tick 1、restore 1、load 1、rejected_command 1。E5 は outside_in 2、inside_out 3。\n- これで、組の欠落(コマンドの直後の公開も、間にイベントの無い連続したコマンドの公開も、tick の終わりの公開も)、余分な組、順序の誤りは、全て失敗になる。O の全ての scenario と R の全ての scenario の実データ(診断)が、この規則の数え方と一致した。\n- 各分の組の実際の数は raw に残し、版の間では signal_order を完全一致で比べる。\n\n4. 変えないもの\n- r01 §5 のとおり。診断の実行は正式の受入に補わない。\n" + }, + "DESKTOP_EQ_NATIVE_FAILURE_REPAIR_DECISION_6F75C9A.json": { + "sha256": "7716a5ed89c91487b9a9163fc402eb447b51c143909de709c03a8347aa466a82", + "bytes": 2086, + "full_unedited": "{\"action\":\"request-repair\",\"task_id\":\"P28-02-EQ\",\"reason\":\"Exact-S native of S 6f75c9a0513aafe5bd6ae7ed46b0ffda6386ac50 failed and stays a failure. Phases import, parse-base and parse-wrapper passed. R-A (PID 62530) repeated the strict engine error 'Could not create directory: user://evidence' at eq_probe.gd:369 and was cancelled by Root with SIGTERM on the verified owned child only (NATIVE_6F75C9A_OWNED_CANCEL_R01.json SHA ce0fd25ae6d07de7bf895eab1adf1a149121882d34df5ddb428050648deca84b). execution.json SHA d6b060779e36084a87f08bfd0a490d1ec6b7ed68b1096f41104932331a0b02e3 records passed false. All 4 owned PIDs ended, cleanup errors are empty, source, player and pins are preserved, and no successful bundle was created, so formal validation is not invoked without a bundle. Desktop read-only diagnosis found two harness defects that the engine-free unit tests could not reach. (1) write_evidence passes user:// directly to make_dir_recursive_absolute under the runtime-switched guard profile; the working RESTART helpers globalize the path first. (2) typed_info encodes get_typed_script(), which is a null Object for unscripted typed containers, so encode rejects it as an unsupported Variant 24 although the Python side already expects [0]; the live R snapshot has no Object values. Free space fell sharply during R-A, and the evidence volume must be measured and kept complete. The user moved implementation and command to Claude Opus 5.5 because Codex usage is near its limit, so no new Codex or Astra run starts. Request the normal repair (submitted to repair_pending through owner intake of the external native failure, then repair_ready with repairs 10). The fix stays inside eq/**, and S, SPEC, TASK, the 6 conditions and both validations stay unchanged. The exact-S native over 7 phases, both validations, a fresh independent Opus review of all 6 conditions, and normal integration and final remain required. No original credit (23/88, 4/30). Decided by Claude Opus 5.5 in the Desktop control session after the user's takeover instruction; not a CLI commander result.\"}\n" + }, + "OPUS_IMPLEMENTATION_HANDOFF_6F75C9A.json": { + "sha256": "f49b2a68de10aef3a055571c845c49f5b04187137d1a1df6ba7d124aeabb8c22", + "bytes": 2940, + "full_unedited": "{\n \"at_utc\": \"2026-10-21T01:53:30.145584+00:00\",\n \"user_authority\": \"User explicitly delegated implementation and coordination to Claude Opus because Codex usage is near its limit.\",\n \"project\": \"the-tree-p28-02-eq\",\n \"task\": \"P28-02-EQ\",\n \"S\": \"6f75c9a0513aafe5bd6ae7ed46b0ffda6386ac50\",\n \"W\": \"/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement\",\n \"repairs\": 9,\n \"Runner_state\": \"submitted; native Root failure is not yet registered as a formal validation or repair\",\n \"local146_raw_sha256\": \"ff6fc0065772434f9ca35a1267e24d199057e339869a868942a41829327503bb\",\n \"local146_seconds\": 293.277,\n \"native_result\": {\n \"passed\": false,\n \"execution_sha256\": \"d6b060779e36084a87f08bfd0a490d1ec6b7ed68b1096f41104932331a0b02e3\",\n \"output\": \"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-6f75c9a-r01\",\n \"completed_phases\": [\n \"import\",\n \"parse-base\",\n \"parse-wrapper\"\n ],\n \"failed_phase\": \"R-A\",\n \"strict_error\": \"Could not create directory user://evidence at eq_probe.gd369\",\n \"guard_all5_true\": true,\n \"runtime_root_created\": true,\n \"runtime_root_contents\": [\n \"p28_eq\"\n ],\n \"source_preserved\": true,\n \"player_preserved\": true,\n \"pins_preserved\": true,\n \"owned4_PIDs_ended\": [\n 62247,\n 62390,\n 62465,\n 62530\n ],\n \"cleanup_errors\": [],\n \"successful_bundle_created\": false,\n \"owned_cancel_receipt\": \"NATIVE_6F75C9A_OWNED_CANCEL_R01.json\"\n },\n \"concrete_readonly_findings\": [\n \"eq_probe write_evidence passes user://evidence directly to DirAccess.make_dir_recursive_absolute; successful RESTART helpers globalize_path first.\",\n \"demand only appends lifecycle.errors and returns false; write_evidence returns but begin/run_e3 continue expensive scenarios after first fatal I/O failure.\",\n \"Free volume fell from3.29GB to about148MB during native, although raw logs were only tensKB; investigate memory/swap and complete evidence representation without dropping full required state/events.\"\n ],\n \"implementation_assignment\": \"Claude Opus5.5, no new Codex worker. Actual production Runner.core register_task currently excludes claude implementer at589. Existing Desktop session named Claude を implementer worker として有効化 may hold authorised support implementation; inspect and reuse it, do not duplicate.\",\n \"conditions\": \"All6 EQ conditions, both validations, independent fresh Opus all6, normal candidate/integration/final, then OPT T1/wholeunit remain mandatory. Native failure stays failure. Original30/88, formal23/88 and4/30, zero partial/original credit; API off/security unchanged.\",\n \"next\": \"Existing Desktop governor handles actual Opus implementation on retained work, storage, registered provider assignment and necessary tests. Root only exact-owner integration when required.\"\n}\n" + }, + "NORMAL_NATIVE_FAILURE_INTAKE_6F75C9A.json": { + "sha256": "38f7947dc4b9808f24cd0d362c8e74761841ca4c3ac50d9b9ab3658f10aa69d7", + "bytes": 1870, + "full_unedited": "{\n \"at\": 1792593136.068461,\n \"submitted\": \"6f75c9a0513aafe5bd6ae7ed46b0ffda6386ac50\",\n \"source\": \"Actual Root exact-S native (driver PID 58916), cancelled on the verified owned child after repeated strict errors; Desktop Opus diagnosis\",\n \"execution_sha256\": \"d6b060779e36084a87f08bfd0a490d1ec6b7ed68b1096f41104932331a0b02e3\",\n \"cancel_sha256\": \"ce0fd25ae6d07de7bf895eab1adf1a149121882d34df5ddb428050648deca84b\",\n \"handoff_sha256\": \"f49b2a68de10aef3a055571c845c49f5b04187137d1a1df6ba7d124aeabb8c22\",\n \"decision_sha256\": \"7716a5ed89c91487b9a9163fc402eb447b51c143909de709c03a8347aa466a82\",\n \"native_passed\": false,\n \"completed_phases\": [\n \"import\",\n \"parse-base\",\n \"parse-wrapper\"\n ],\n \"failed_phase\": \"R-A\",\n \"formal_validation_not_invoked_without_bundle\": true,\n \"old_S_retained\": true,\n \"original_credit\": 0,\n \"state\": \"repair_ready\",\n \"repairs\": 10,\n \"delegation\": {\n \"at\": 1792593136.070688,\n \"implementer_service\": \"claude\",\n \"implementer_model\": \"claude-opus-5-5\",\n \"implementer_session\": \"local_a7f85421-3eed-4482-8030-c0dca669573a (Claude Desktop, Code tab)\",\n \"policy_allowed_services_includes_claude\": true,\n \"user_statement\": \"Codexの使用量上限いきそうなので、作業をひきとって\",\n \"user_statement_relayed_addendum\": \"ユーザーはこのチャットでも『実装担当含めOpusに委任』と明示済み。指揮と実装をOpus5.5で続け、Codex/Astraの新runは開始しない\",\n \"contract_worker_field_unchanged\": \"codex\",\n \"note\": \"Contract is immutable and keeps worker codex as registered; the actual implementer for repair 10 onward is Claude Opus 5.5 in the Desktop session, submitted through the normal Runner.submit gate. The independent review stays a separate fresh Opus session.\",\n \"no_new_codex_run\": true\n },\n \"installed_Runner_and_policy_unchanged\": true\n}\n" + } + }, + "note": "Preserved instruction and decision bytes for review; not a new commander decision." + } +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.sha256 b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.sha256 index 5f5577b..f23b81d 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.sha256 +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.sha256 @@ -1 +1 @@ -ff6fc0065772434f9ca35a1267e24d199057e339869a868942a41829327503bb UNIT_OUTPUT.txt +7372a382cc2f3c090013874d8f28f17cad4cb1b965ad86c86de7a21fc2d851a8 UNIT_OUTPUT.txt diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.txt b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.txt index ee7919a..5cbff21 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.txt +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.txt @@ -1,154 +1,167 @@ -test_all_operations (__main__.Diffs) ... ok -test_invalid_operations (__main__.Diffs) ... ok -test_reconstruction_and_proofs (__main__.Diffs) ... ok -test_attempt_to_normalize_violation_rejected (__main__.Normalization) ... ok -test_environment_values_differ_but_normal_form_equal (__main__.Normalization) ... ok -test_every_save_event_reversible (__main__.Normalization) ... ok -test_f3_keeps_relative_paths_and_words (__main__.Normalization) ... ok -test_guard_mismatch (__main__.Normalization) ... ok -test_nested_lookalike_is_not_eligible (__main__.Normalization) ... ok -test_original_transport_sha_detects_forged_timing (__main__.Normalization) ... ok -test_partial_timing_payload_rejected (__main__.Normalization) ... ok -test_prefix_boundary_and_reserved_token (__main__.Normalization) ... ok -test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization) ... ok -test_snapshot_and_latest_reversible (__main__.Normalization) ... ok -test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization) ... ok -test_wrong_event_and_source_are_preserved (__main__.Normalization) ... ok -test_wrong_timing_type_and_negative (__main__.Normalization) ... ok -test_all_events_beyond_recent_window (__main__.Reports) ... ok -test_all_semantic_layers_compared (__main__.Reports) ... ok -test_digest_alone_is_not_enough (__main__.Reports) ... ok -test_event_original_types_and_order (__main__.Reports) ... ok -test_event_start_and_ids (__main__.Reports) ... ok -test_exclusions_keep_actual_values (__main__.Reports) ... ok -test_handler_sets_and_counts (__main__.Reports) ... ok -test_observation_full_layers (__main__.Reports) ... ok -test_product_digest_self_consistency (__main__.Reports) ... ok -test_same_report (__main__.Reports) ... ok -test_scenario_existing_failures_preserved (__main__.Reports) ... ok -test_timing_digest_rule (__main__.Reports) ... ok -test_unknown_or_missing_fields_rejected (__main__.Reports) ... ok -test_violation_presence_and_difference (__main__.Reports) ... ok -test_O_cadence (__main__.S2Format) ... ok -test_counter_and_published_ids_binding (__main__.S2Format) ... ok -test_diff_and_same_as_S_transport (__main__.S2Format) ... ok -test_digest_and_scenario_contradiction (__main__.S2Format) ... ok -test_event_no_drop_duplicate_remap (__main__.S2Format) ... ok -test_event_original_field_types_mandatory (__main__.S2Format) ... ok -test_guard_actual_not_assumed (__main__.S2Format) ... ok -test_harness_digest_allows_only_dictionary_order (__main__.S2Format) ... ok -test_join_is_copy_and_retains_normalization_proof (__main__.S2Format) ... ok -test_kernel_cache_contradictions (__main__.S2Format) ... ok -test_lifecycle_guard_errors_and_boundaries (__main__.S2Format) ... ok -test_missing_full_state_not_digest_only (__main__.S2Format) ... ok -test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format) ... ok -test_product_event_shape_and_typed_array (__main__.S2Format) ... ok -test_raw_observation_binding (__main__.S2Format) ... ok -test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format) ... ok -test_s2_valid_and_not_formal_acceptance (__main__.S2Format) ... ok -test_state_witness_and_observation_mutation (__main__.S2Format) ... ok -test_stdout_exactly_one_line (__main__.S2Format) ... ok -test_autoload_class_and_script_import (__main__.S2Static) ... ok -test_comment_does_not_supply_proof (__main__.S2Static) ... ok -test_current_probe (__main__.S2Static) ... ok -test_full_state_and_godot_roundtrip_required (__main__.S2Static) ... ok -test_no_changes_outside_preparation_or_step (__main__.S2Static) ... ok -test_no_event_cap_or_duplicate_report (__main__.S2Static) ... ok -test_pause_and_step_arguments (__main__.S2Static) ... ok -test_textual_stringname_ordering (__main__.S2Static) ... ok -test_unlisted_live_method_and_late_preparation (__main__.S2Static) ... ok -test_variant_inference (__main__.S2Static) ... ok -test_X_recent_limit_and_counter (__main__.S3a) ... ok -test_duplicate_JSON_key_rejected (__main__.S3a) ... ok -test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a) ... ok -test_e3_coverage_input_and_event_count_binding (__main__.S3a) ... ok -test_e3_invented_codes_and_missing_load_code (__main__.S3a) ... ok -test_e3_valid_R_and_O_records (__main__.S3a) ... ok -test_envelope_duplicates_not_defect (__main__.S3a) ... ok -test_event_count_defect_not_silently_accepted (__main__.S3a) ... ok -test_exact_phase_keys (__main__.S3a) ... ok -test_interval_gap_rejected (__main__.S3a) ... ok -test_invalid_input_accepted_is_not_PASS (__main__.S3a) ... ok -test_load_payload_and_pause_outcome (__main__.S3a) ... ok -test_operation_moved_outside_scenario_rejected (__main__.S3a) ... ok -test_package_fixed_mutations_and_hashes (__main__.S3a) ... ok -test_probe_connection_required (__main__.S3a) ... ok -test_rejection_mutation_retained_as_defect (__main__.S3a) ... ok -test_save_refusal_makes_load_NA (__main__.S3a) ... ok -test_scenario_mutation_static_boundary (__main__.S3a) ... ok -test_signals_all_minutes_and_order (__main__.S3a) ... ok -test_E5_cached_digest_defect_is_comparable (__main__.S3b) ... ok -test_NA_not_PASS (__main__.S3b) ... ok -test_aliasing_cannot_be_PASS (__main__.S3b) ... ok -test_aliasing_missing_record (__main__.S3b) ... ok -test_all_paths_and_ownership_R_O (__main__.S3b) ... ok -test_digest_defect_other_checkpoint_still_rejected (__main__.S3b) ... ok -test_digest_materialization_required (__main__.S3b) ... ok -test_empty_registry_not_NA (__main__.S3b) ... ok -test_event_count_mismatch (__main__.S3b) ... ok -test_facts_normalization_is_reversible (__main__.S3b) ... ok -test_first_placeable_choice (__main__.S3b) ... ok -test_interval_gap (__main__.S3b) ... ok -test_joined_s3b_self_comparison (__main__.S3b) ... ok -test_load_X_not_digest_only (__main__.S3b) ... ok -test_load_extra_event (__main__.S3b) ... ok -test_missing_path_and_combination (__main__.S3b) ... ok -test_missing_preview_observation (__main__.S3b) ... ok -test_missing_trial (__main__.S3b) ... ok -test_nested_mutation_required (__main__.S3b) ... ok -test_no_staleness_assertion_for_non_tick (__main__.S3b) ... ok -test_ownership_fact_dictionary_order_fallback (__main__.S3b) ... ok -test_path_order (__main__.S3b) ... ok -test_restore_extra_event (__main__.S3b) ... ok -test_signal_facts_cannot_be_dropped (__main__.S3b) ... ok -test_sort_rule_rejected (__main__.S3b) ... ok -test_static_handler_and_outside_mutations (__main__.S3b) ... ok -test_unpreviewed_choice (__main__.S3b) ... ok -test_wrong_registry_in_either_role (__main__.S3b) ... ok -test_checkout_temp_destination_rejected (__main__.S4Bundle) ... ok -test_existing_output_never_overwritten (__main__.S4Bundle) ... ok -test_import_closure_rejections (__main__.S4Bundle) ... ok -test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle) ... ok -test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle) ... ok -test_missing_profile_not_export_success (__main__.S4Bundle) ... ok -test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle) ... ok -test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle) ... ok -test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle) ... ok -test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle) ... ok -test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic) ... ok -test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic) ... ok -test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic) ... ok -test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic) ... ok -test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic) ... ok -test_check_accepts_true (__main__.S4ProbeAdmission) ... ok -test_check_rejects_false_all_errors (__main__.S4ProbeAdmission) ... ok -test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission) ... ok -test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission) ... ok -test_nine_original_destinations_required (__main__.S4RemainingBindings) ... ok -test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings) ... ok -test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings) ... ok -test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings) ... ok -test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings) ... ok -test_cached_verdict_not_trusted (__main__.S4StoredResultBindings) ... ok -test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings) ... ok -test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings) ... ok -test_fixed_scenario_paths (__main__.SourcesAndMetrics) ... ok -test_metric_ranges (__main__.SourcesAndMetrics) ... ok -test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics) ... ok -test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics) ... ok -test_array_order_and_all_differences (__main__.TypedTrees) ... ok -test_components_and_sha (__main__.TypedTrees) ... ok -test_deep_state_not_truncated (__main__.TypedTrees) ... ok -test_duplicate_textual_keys_rejected (__main__.TypedTrees) ... ok -test_exact_and_types (__main__.TypedTrees) ... ok -test_nested_order_all_paths (__main__.TypedTrees) ... ok -test_nonstring_keys_keep_types_and_values (__main__.TypedTrees) ... ok -test_unsupported_and_malformed (__main__.TypedTrees) ... ok +test_all_operations (__main__.Diffs.test_all_operations) ... ok +test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok +test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok +test_attempt_to_normalize_violation_rejected (__main__.Normalization.test_attempt_to_normalize_violation_rejected) ... ok +test_environment_values_differ_but_normal_form_equal (__main__.Normalization.test_environment_values_differ_but_normal_form_equal) ... ok +test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok +test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok +test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok +test_nested_lookalike_is_not_eligible (__main__.Normalization.test_nested_lookalike_is_not_eligible) ... ok +test_original_transport_sha_detects_forged_timing (__main__.Normalization.test_original_transport_sha_detects_forged_timing) ... ok +test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok +test_prefix_boundary_and_reserved_token (__main__.Normalization.test_prefix_boundary_and_reserved_token) ... ok +test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization.test_sidecar_missing_duplicate_and_forged_paths) ... ok +test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok +test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization.test_snapshot_event_outside_recent_is_not_eligible) ... ok +test_wrong_event_and_source_are_preserved (__main__.Normalization.test_wrong_event_and_source_are_preserved) ... ok +test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok +test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok +test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok +test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok +test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok +test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok +test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok +test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok +test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok +test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok +test_same_report (__main__.Reports.test_same_report) ... ok +test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok +test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok +test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok +test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok +test_O_cadence (__main__.S2Format.test_O_cadence) ... ok +test_counter_and_published_ids_binding (__main__.S2Format.test_counter_and_published_ids_binding) ... ok +test_diff_and_same_as_S_transport (__main__.S2Format.test_diff_and_same_as_S_transport) ... ok +test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok +test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok +test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok +test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok +test_harness_digest_allows_only_dictionary_order (__main__.S2Format.test_harness_digest_allows_only_dictionary_order) ... ok +test_join_is_copy_and_retains_normalization_proof (__main__.S2Format.test_join_is_copy_and_retains_normalization_proof) ... ok +test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok +test_lifecycle_guard_errors_and_boundaries (__main__.S2Format.test_lifecycle_guard_errors_and_boundaries) ... ok +test_missing_full_state_not_digest_only (__main__.S2Format.test_missing_full_state_not_digest_only) ... ok +test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format.test_normalized_digest_and_raw_tree_are_both_bound) ... ok +test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok +test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok +test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format.test_roundtrip_tree_sha_and_raw_sha) ... ok +test_s2_valid_and_not_formal_acceptance (__main__.S2Format.test_s2_valid_and_not_formal_acceptance) ... ok +test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok +test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok +test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok +test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok +test_current_probe (__main__.S2Static.test_current_probe) ... ok +test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) ... ok +test_no_changes_outside_preparation_or_step (__main__.S2Static.test_no_changes_outside_preparation_or_step) ... ok +test_no_event_cap_or_duplicate_report (__main__.S2Static.test_no_event_cap_or_duplicate_report) ... ok +test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok +test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok +test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok +test_variant_inference (__main__.S2Static.test_variant_inference) ... ok +test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok +test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok +test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a.test_e3_NA_cannot_be_PASS_or_applied) ... ok +test_e3_coverage_input_and_event_count_binding (__main__.S3a.test_e3_coverage_input_and_event_count_binding) ... ok +test_e3_invented_codes_and_missing_load_code (__main__.S3a.test_e3_invented_codes_and_missing_load_code) ... ok +test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) ... ok +test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... ok +test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... ok +test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok +test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok +test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) ... ok +test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... ok +test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok +test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok +test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok +test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... ok +test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... ok +test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok +test_signals_all_minutes_and_order (__main__.S3a.test_signals_all_minutes_and_order) ... ok +test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok +test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok +test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok +test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok +test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) ... ok +test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok +test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok +test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok +test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok +test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok +test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok +test_interval_gap (__main__.S3b.test_interval_gap) ... ok +test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok +test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok +test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok +test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok +test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok +test_missing_trial (__main__.S3b.test_missing_trial) ... ok +test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok +test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) ... ok +test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok +test_path_order (__main__.S3b.test_path_order) ... ok +test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok +test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok +test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok +test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok +test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok +test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok +test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok +test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok +test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ok +test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle.test_missing_bundle_manifest_seal_and_changed_bytes) ... ok +test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle.test_missing_private_evidence_and_extra_rejected) ... ok +test_missing_profile_not_export_success (__main__.S4Bundle.test_missing_profile_not_export_success) ... ok +test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle.test_phase_role_pid_audio_and_guard_rejected) ... ok +test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle.test_rebound_lifecycle_and_scenario_contradictions) ... ok +test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle.test_seal_normal_and_changes_not_native_proof) ... ok +test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) ... ok +test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic.test_copied_manifest_root_and_destination_count_rejected) ... ok +test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic.test_failure_keeps_raw_and_no_bundle_without_starting_engine) ... ok +test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic.test_gate_none_success_not_boolean) ... ok +test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic.test_receipt_and_source_rejection_matrix) ... ok +test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic.test_synthetic_full_bundle_positive_not_native_evidence) ... ok +test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok +test_check_rejects_false_all_errors (__main__.S4ProbeAdmission.test_check_rejects_false_all_errors) ... ok +test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission.test_run_admission_accepts_true_mock_launch_only) ... ok +test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission.test_run_admission_rejects_false_without_engine) ... ok +test_nine_original_destinations_required (__main__.S4RemainingBindings.test_nine_original_destinations_required) ... ok +test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings.test_nonzero_determinism_rejected_after_real_join) ... ok +test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings.test_owned_lifecycle_exit_diagnostic_and_profile_rejections) ... ok +test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings.test_verifier_accepts_true_recomputes_raw) ... ok +test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings.test_verifier_rejects_false_with_every_error) ... ok +test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok +test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings.test_missing_sidecar_rejected_after_seal) ... ok +test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok +test_blob_transport_is_verified_and_bound (__main__.S5.test_blob_transport_is_verified_and_bound) ... ok +test_blob_trees_get_the_same_full_validation_as_inline (__main__.S5.test_blob_trees_get_the_same_full_validation_as_inline) ... ok +test_compare_and_digest_equal_s1_reference (__main__.S5.test_compare_and_digest_equal_s1_reference) ... ok +test_event_stream_replacement_is_existing_defect_not_pass (__main__.S5.test_event_stream_replacement_is_existing_defect_not_pass) ... ok +test_every_checkpoint_full_tree_verified_like_inline (__main__.S5.test_every_checkpoint_full_tree_verified_like_inline) +S5 drops the diff chain: each checkpoint carries a complete tree, verified alone. ... ok +test_fast_normalization_equals_original (__main__.S5.test_fast_normalization_equals_original) ... ok +test_labelled_harness_digest_falls_back_to_dictionary_order_only (__main__.S5.test_labelled_harness_digest_falls_back_to_dictionary_order_only) ... ok +test_lifecycle_binds_replacements_guard_text_and_blobs (__main__.S5.test_lifecycle_binds_replacements_guard_text_and_blobs) ... ok +test_manifest_numbers_keep_their_type (__main__.S5.test_manifest_numbers_keep_their_type) ... ok +test_memo_scope_same_results_and_restores_gc (__main__.S5.test_memo_scope_same_results_and_restores_gc) ... ok +test_path_copy_builders_equal_deepcopy_reference (__main__.S5.test_path_copy_builders_equal_deepcopy_reference) ... ok +test_probe_static_full_tree_blob_and_stream_requirements (__main__.S5.test_probe_static_full_tree_blob_and_stream_requirements) ... ok +test_publish_pairs_bound_to_source_causes (__main__.S5.test_publish_pairs_bound_to_source_causes) ... ok +test_signed_instance_ids_raw_kept_and_excluded (__main__.S5.test_signed_instance_ids_raw_kept_and_excluded) ... ok +test_validate_tree_equals_s1_reference_on_mutations (__main__.S5.test_validate_tree_equals_s1_reference_on_mutations) ... ok +test_fixed_scenario_paths (__main__.SourcesAndMetrics.test_fixed_scenario_paths) ... ok +test_metric_ranges (__main__.SourcesAndMetrics.test_metric_ranges) ... ok +test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics.test_source_closure_synthetic_registered_map) ... ok +test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics.test_source_pin_is_not_caller_controlled) ... ok +test_array_order_and_all_differences (__main__.TypedTrees.test_array_order_and_all_differences) ... ok +test_components_and_sha (__main__.TypedTrees.test_components_and_sha) ... ok +test_deep_state_not_truncated (__main__.TypedTrees.test_deep_state_not_truncated) ... ok +test_duplicate_textual_keys_rejected (__main__.TypedTrees.test_duplicate_textual_keys_rejected) ... ok +test_exact_and_types (__main__.TypedTrees.test_exact_and_types) ... ok +test_nested_order_all_paths (__main__.TypedTrees.test_nested_order_all_paths) ... ok +test_nonstring_keys_keep_types_and_values (__main__.TypedTrees.test_nonstring_keys_keep_types_and_values) ... ok +test_unsupported_and_malformed (__main__.TypedTrees.test_unsupported_and_malformed) ... ok ---------------------------------------------------------------------- -Ran 146 tests in 293.277s +Ran 161 tests in 121.627s OK - -exit_code=0 -wall_seconds=293.5742515 diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_01.sha256 b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_01.sha256 new file mode 100644 index 0000000..4b1d9c8 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_01.sha256 @@ -0,0 +1 @@ +f52106391229dbf46d82e7dd9cdbc290b65f2ae9ed0e2a49d2cd4933c8af4ade UNIT_OUTPUT_S5_ATTEMPT_01.txt diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_01.txt b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_01.txt new file mode 100644 index 0000000..77e10ca --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_01.txt @@ -0,0 +1,316 @@ +test_all_operations (__main__.Diffs.test_all_operations) ... ok +test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok +test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok +test_attempt_to_normalize_violation_rejected (__main__.Normalization.test_attempt_to_normalize_violation_rejected) ... ok +test_environment_values_differ_but_normal_form_equal (__main__.Normalization.test_environment_values_differ_but_normal_form_equal) ... ok +test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok +test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok +test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok +test_nested_lookalike_is_not_eligible (__main__.Normalization.test_nested_lookalike_is_not_eligible) ... ok +test_original_transport_sha_detects_forged_timing (__main__.Normalization.test_original_transport_sha_detects_forged_timing) ... ok +test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok +test_prefix_boundary_and_reserved_token (__main__.Normalization.test_prefix_boundary_and_reserved_token) ... ok +test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization.test_sidecar_missing_duplicate_and_forged_paths) ... ok +test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok +test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization.test_snapshot_event_outside_recent_is_not_eligible) ... ok +test_wrong_event_and_source_are_preserved (__main__.Normalization.test_wrong_event_and_source_are_preserved) ... ok +test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok +test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok +test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok +test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok +test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok +test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok +test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok +test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok +test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok +test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok +test_same_report (__main__.Reports.test_same_report) ... ok +test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok +test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok +test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok +test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok +test_O_cadence (__main__.S2Format.test_O_cadence) ... ok +test_counter_and_published_ids_binding (__main__.S2Format.test_counter_and_published_ids_binding) ... ok +test_diff_and_same_as_S_transport (__main__.S2Format.test_diff_and_same_as_S_transport) ... ok +test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok +test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok +test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok +test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok +test_harness_digest_allows_only_dictionary_order (__main__.S2Format.test_harness_digest_allows_only_dictionary_order) ... ok +test_join_is_copy_and_retains_normalization_proof (__main__.S2Format.test_join_is_copy_and_retains_normalization_proof) ... ok +test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok +test_lifecycle_guard_errors_and_boundaries (__main__.S2Format.test_lifecycle_guard_errors_and_boundaries) ... ok +test_missing_full_state_not_digest_only (__main__.S2Format.test_missing_full_state_not_digest_only) ... ok +test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format.test_normalized_digest_and_raw_tree_are_both_bound) ... ok +test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok +test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok +test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format.test_roundtrip_tree_sha_and_raw_sha) ... ok +test_s2_valid_and_not_formal_acceptance (__main__.S2Format.test_s2_valid_and_not_formal_acceptance) ... ok +test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok +test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok +test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok +test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok +test_current_probe (__main__.S2Static.test_current_probe) ... ok +test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) ... + test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) (required='transport(runtime.create_snapshot())') ... FAIL +test_no_changes_outside_preparation_or_step (__main__.S2Static.test_no_changes_outside_preparation_or_step) ... ok +test_no_event_cap_or_duplicate_report (__main__.S2Static.test_no_event_cap_or_duplicate_report) ... ok +test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok +test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok +test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok +test_variant_inference (__main__.S2Static.test_variant_inference) ... ok +test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok +test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok +test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a.test_e3_NA_cannot_be_PASS_or_applied) ... ok +test_e3_coverage_input_and_event_count_binding (__main__.S3a.test_e3_coverage_input_and_event_count_binding) ... ok +test_e3_invented_codes_and_missing_load_code (__main__.S3a.test_e3_invented_codes_and_missing_load_code) ... ok +test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) ... FAIL +test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... FAIL +test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... FAIL +test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok +test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok +test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) ... FAIL +test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... FAIL +test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok +test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok +test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok +test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... FAIL +test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... FAIL +test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok +test_signals_all_minutes_and_order (__main__.S3a.test_signals_all_minutes_and_order) ... ok +test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok +test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok +test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok +test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok +test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) ... ERROR +test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok +test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok +test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok +test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok +test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok +test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok +test_interval_gap (__main__.S3b.test_interval_gap) ... ok +test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok +test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok +test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok +test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok +test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok +test_missing_trial (__main__.S3b.test_missing_trial) ... ok +test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok +test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) ... ERROR +test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok +test_path_order (__main__.S3b.test_path_order) ... ok +test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok +test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok +test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok +test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok +test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok +test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok +test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok +test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok +test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ok +test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle.test_missing_bundle_manifest_seal_and_changed_bytes) ... ok +test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle.test_missing_private_evidence_and_extra_rejected) ... ok +test_missing_profile_not_export_success (__main__.S4Bundle.test_missing_profile_not_export_success) ... ok +test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle.test_phase_role_pid_audio_and_guard_rejected) ... ok +test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle.test_rebound_lifecycle_and_scenario_contradictions) ... ok +test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle.test_seal_normal_and_changes_not_native_proof) ... ok +test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) ... ERROR +test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic.test_copied_manifest_root_and_destination_count_rejected) ... ok +test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic.test_failure_keeps_raw_and_no_bundle_without_starting_engine) ... ok +test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic.test_gate_none_success_not_boolean) ... ok +test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic.test_receipt_and_source_rejection_matrix) ... ok +test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic.test_synthetic_full_bundle_positive_not_native_evidence) ... ERROR +test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok +test_check_rejects_false_all_errors (__main__.S4ProbeAdmission.test_check_rejects_false_all_errors) ... ok +test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission.test_run_admission_accepts_true_mock_launch_only) ... ok +test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission.test_run_admission_rejects_false_without_engine) ... ok +test_nine_original_destinations_required (__main__.S4RemainingBindings.test_nine_original_destinations_required) ... ok +test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings.test_nonzero_determinism_rejected_after_real_join) ... FAIL +test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings.test_owned_lifecycle_exit_diagnostic_and_profile_rejections) ... ok +test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings.test_verifier_accepts_true_recomputes_raw) ... ERROR +test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings.test_verifier_rejects_false_with_every_error) ... ok +test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok +test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings.test_missing_sidecar_rejected_after_seal) ... ok +test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok +test_fixed_scenario_paths (__main__.SourcesAndMetrics.test_fixed_scenario_paths) ... ok +test_metric_ranges (__main__.SourcesAndMetrics.test_metric_ranges) ... ok +test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics.test_source_closure_synthetic_registered_map) ... ok +test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics.test_source_pin_is_not_caller_controlled) ... ok +test_array_order_and_all_differences (__main__.TypedTrees.test_array_order_and_all_differences) ... ok +test_components_and_sha (__main__.TypedTrees.test_components_and_sha) ... ok +test_deep_state_not_truncated (__main__.TypedTrees.test_deep_state_not_truncated) ... ok +test_duplicate_textual_keys_rejected (__main__.TypedTrees.test_duplicate_textual_keys_rejected) ... ok +test_exact_and_types (__main__.TypedTrees.test_exact_and_types) ... ok +test_nested_order_all_paths (__main__.TypedTrees.test_nested_order_all_paths) ... ok +test_nonstring_keys_keep_types_and_values (__main__.TypedTrees.test_nonstring_keys_keep_types_and_values) ... ok +test_unsupported_and_malformed (__main__.TypedTrees.test_unsupported_and_malformed) ... ok + +====================================================================== +ERROR: test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1077, in test_all_paths_and_ownership_R_O + outcomes = self.checked(e45_fixture(role)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1067, in checked + return checker.check_e45(raw, evidence, previous) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py", line 826, in check_e45 + eq.require(set(detail) == common | specific, 'E4 detail fields; no invented staleness verdict') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 143, in require + raise EvidenceError(message) +compare_versions.EvidenceError: E4 detail fields; no invented staleness verdict + +====================================================================== +ERROR: test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1170, in test_no_staleness_assertion_for_non_tick + out = self.checked() + ^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1067, in checked + return checker.check_e45(raw, evidence, previous) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py", line 826, in check_e45 + eq.require(set(detail) == common | specific, 'E4 detail fields; no invented staleness verdict') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 143, in require + raise EvidenceError(message) +compare_versions.EvidenceError: E4 detail fields; no invented staleness verdict + +====================================================================== +ERROR: test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1417, in test_synthetic_phase_all_real_checkers_not_native_proof + result = verifier.phase_evidence(directory, row) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py", line 110, in phase_evidence + require(life['stage'] == 'S5', 'S5 lifecycle required') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 143, in require + raise EvidenceError(message) +compare_versions.EvidenceError: S5 lifecycle required + +====================================================================== +ERROR: test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic.test_synthetic_full_bundle_positive_not_native_evidence) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1514, in test_synthetic_full_bundle_positive_not_native_evidence + result = self.recompute(self.execution) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1511, in recompute + return verifier.recompute(self.directory, value, self.collector, self.standard, self.diag) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py", line 222, in recompute + results[phase] = phase_evidence(directory, row) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py", line 110, in phase_evidence + require(life['stage'] == 'S5', 'S5 lifecycle required') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 143, in require + raise EvidenceError(message) +compare_versions.EvidenceError: S5 lifecycle required + +====================================================================== +ERROR: test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings.test_verifier_accepts_true_recomputes_raw) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1618, in test_verifier_accepts_true_recomputes_raw + result = self.recompute(self.execution) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1511, in recompute + return verifier.recompute(self.directory, value, self.collector, self.standard, self.diag) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py", line 222, in recompute + results[phase] = phase_evidence(directory, row) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py", line 110, in phase_evidence + require(life['stage'] == 'S5', 'S5 lifecycle required') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 143, in require + raise EvidenceError(message) +compare_versions.EvidenceError: S5 lifecycle required + +====================================================================== +FAIL: test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) (required='transport(runtime.create_snapshot())') +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 676, in test_full_state_and_godot_roundtrip_required + with self.subTest(required=required): self.reject(self.probe.replace(required, 'null')) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 645, in reject + self.assertFalse(result['ok'], result) +AssertionError: True is not false : {'ok': True, 'stage': 'S3a', 'errors': []} + +====================================================================== +FAIL: test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 835, in test_e3_valid_R_and_O_records + self.assertTrue(checked['ok'], checked['errors']) +AssertionError: False is not true : ["'event_stream'"] + +====================================================================== +FAIL: test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 863, in test_envelope_duplicates_not_defect + self.assertTrue(checked['ok'], checked['errors']) +AssertionError: False is not true : ["'event_stream'"] + +====================================================================== +FAIL: test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 944, in test_event_count_defect_not_silently_accepted + self.assertTrue(checked['ok'], checked['errors']) +AssertionError: False is not true : ["'event_stream'"] + +====================================================================== +FAIL: test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 898, in test_invalid_input_accepted_is_not_PASS + self.assertTrue(checked['ok'], checked['errors']) +AssertionError: False is not true : ["'event_stream'"] + +====================================================================== +FAIL: test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 972, in test_load_payload_and_pause_outcome + self.assertTrue(checked['ok'], checked['errors']) +AssertionError: False is not true : ["'event_stream'"] + +====================================================================== +FAIL: test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 889, in test_rejection_mutation_retained_as_defect + self.assertTrue(checked['ok'], checked['errors']) +AssertionError: False is not true : ["'event_stream'"] + +====================================================================== +FAIL: test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 963, in test_save_refusal_makes_load_NA + self.assertTrue(checked['ok'], checked['errors']) +AssertionError: False is not true : ["'event_stream'"] + +====================================================================== +FAIL: test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings.test_nonzero_determinism_rejected_after_real_join) +---------------------------------------------------------------------- +compare_versions.EvidenceError: S5 lifecycle required + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1648, in test_nonzero_determinism_rejected_after_real_join + with self.assertRaisesRegex(eq.EvidenceError, 'same-role determinism failed'): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AssertionError: "same-role determinism failed" does not match "S5 lifecycle required" + +---------------------------------------------------------------------- +Ran 146 tests in 53.398s + +FAILED (failures=9, errors=5) +exit=1 diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_02.sha256 b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_02.sha256 new file mode 100644 index 0000000..a790d05 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_02.sha256 @@ -0,0 +1 @@ +67b91e02e30046cefecf260aa2eb284e0bab5ae2d9728f674eea5bd06e38db0c UNIT_OUTPUT_S5_ATTEMPT_02.txt diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_02.txt b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_02.txt new file mode 100644 index 0000000..1b8a7f1 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_02.txt @@ -0,0 +1,163 @@ +test_all_operations (__main__.Diffs.test_all_operations) ... ok +test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok +test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok +test_attempt_to_normalize_violation_rejected (__main__.Normalization.test_attempt_to_normalize_violation_rejected) ... ok +test_environment_values_differ_but_normal_form_equal (__main__.Normalization.test_environment_values_differ_but_normal_form_equal) ... ok +test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok +test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok +test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok +test_nested_lookalike_is_not_eligible (__main__.Normalization.test_nested_lookalike_is_not_eligible) ... ok +test_original_transport_sha_detects_forged_timing (__main__.Normalization.test_original_transport_sha_detects_forged_timing) ... ok +test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok +test_prefix_boundary_and_reserved_token (__main__.Normalization.test_prefix_boundary_and_reserved_token) ... ok +test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization.test_sidecar_missing_duplicate_and_forged_paths) ... ok +test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok +test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization.test_snapshot_event_outside_recent_is_not_eligible) ... ok +test_wrong_event_and_source_are_preserved (__main__.Normalization.test_wrong_event_and_source_are_preserved) ... ok +test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok +test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok +test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok +test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok +test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok +test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok +test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok +test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok +test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok +test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok +test_same_report (__main__.Reports.test_same_report) ... ok +test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok +test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok +test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok +test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok +test_O_cadence (__main__.S2Format.test_O_cadence) ... ok +test_counter_and_published_ids_binding (__main__.S2Format.test_counter_and_published_ids_binding) ... ok +test_diff_and_same_as_S_transport (__main__.S2Format.test_diff_and_same_as_S_transport) ... ok +test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok +test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok +test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok +test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok +test_harness_digest_allows_only_dictionary_order (__main__.S2Format.test_harness_digest_allows_only_dictionary_order) ... ok +test_join_is_copy_and_retains_normalization_proof (__main__.S2Format.test_join_is_copy_and_retains_normalization_proof) ... ok +test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok +test_lifecycle_guard_errors_and_boundaries (__main__.S2Format.test_lifecycle_guard_errors_and_boundaries) ... ok +test_missing_full_state_not_digest_only (__main__.S2Format.test_missing_full_state_not_digest_only) ... ok +test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format.test_normalized_digest_and_raw_tree_are_both_bound) ... ok +test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok +test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok +test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format.test_roundtrip_tree_sha_and_raw_sha) ... ok +test_s2_valid_and_not_formal_acceptance (__main__.S2Format.test_s2_valid_and_not_formal_acceptance) ... ok +test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok +test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok +test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok +test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok +test_current_probe (__main__.S2Static.test_current_probe) ... ok +test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) ... ok +test_no_changes_outside_preparation_or_step (__main__.S2Static.test_no_changes_outside_preparation_or_step) ... ok +test_no_event_cap_or_duplicate_report (__main__.S2Static.test_no_event_cap_or_duplicate_report) ... ok +test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok +test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok +test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok +test_variant_inference (__main__.S2Static.test_variant_inference) ... ok +test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok +test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok +test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a.test_e3_NA_cannot_be_PASS_or_applied) ... ok +test_e3_coverage_input_and_event_count_binding (__main__.S3a.test_e3_coverage_input_and_event_count_binding) ... ok +test_e3_invented_codes_and_missing_load_code (__main__.S3a.test_e3_invented_codes_and_missing_load_code) ... ok +test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) ... ok +test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... ok +test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... ok +test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok +test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok +test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) ... ok +test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... ok +test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok +test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok +test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok +test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... ok +test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... ok +test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok +test_signals_all_minutes_and_order (__main__.S3a.test_signals_all_minutes_and_order) ... ok +test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok +test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok +test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok +test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok +test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) ... ok +test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok +test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok +test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok +test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok +test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok +test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok +test_interval_gap (__main__.S3b.test_interval_gap) ... ok +test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok +test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok +test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok +test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok +test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok +test_missing_trial (__main__.S3b.test_missing_trial) ... ok +test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok +test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) ... ok +test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok +test_path_order (__main__.S3b.test_path_order) ... ok +test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok +test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok +test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok +test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok +test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok +test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok +test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok +test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok +test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ok +test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle.test_missing_bundle_manifest_seal_and_changed_bytes) ... ok +test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle.test_missing_private_evidence_and_extra_rejected) ... ok +test_missing_profile_not_export_success (__main__.S4Bundle.test_missing_profile_not_export_success) ... ok +test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle.test_phase_role_pid_audio_and_guard_rejected) ... ok +test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle.test_rebound_lifecycle_and_scenario_contradictions) ... ok +test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle.test_seal_normal_and_changes_not_native_proof) ... ok +test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) ... ok +test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic.test_copied_manifest_root_and_destination_count_rejected) ... ok +test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic.test_failure_keeps_raw_and_no_bundle_without_starting_engine) ... ok +test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic.test_gate_none_success_not_boolean) ... ok +test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic.test_receipt_and_source_rejection_matrix) ... ok +test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic.test_synthetic_full_bundle_positive_not_native_evidence) ... ok +test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok +test_check_rejects_false_all_errors (__main__.S4ProbeAdmission.test_check_rejects_false_all_errors) ... ok +test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission.test_run_admission_accepts_true_mock_launch_only) ... ok +test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission.test_run_admission_rejects_false_without_engine) ... ok +test_nine_original_destinations_required (__main__.S4RemainingBindings.test_nine_original_destinations_required) ... ok +test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings.test_nonzero_determinism_rejected_after_real_join) ... ok +test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings.test_owned_lifecycle_exit_diagnostic_and_profile_rejections) ... ok +test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings.test_verifier_accepts_true_recomputes_raw) ... ok +test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings.test_verifier_rejects_false_with_every_error) ... ok +test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok +test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings.test_missing_sidecar_rejected_after_seal) ... ok +test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok +test_blob_transport_is_verified_and_bound (__main__.S5.test_blob_transport_is_verified_and_bound) ... ok +test_compare_and_digest_equal_s1_reference (__main__.S5.test_compare_and_digest_equal_s1_reference) ... ok +test_event_stream_replacement_is_existing_defect_not_pass (__main__.S5.test_event_stream_replacement_is_existing_defect_not_pass) ... ok +test_every_checkpoint_full_tree_verified_like_inline (__main__.S5.test_every_checkpoint_full_tree_verified_like_inline) +S5 drops the diff chain: each checkpoint carries a complete tree, verified alone. ... ok +test_fast_normalization_equals_original (__main__.S5.test_fast_normalization_equals_original) ... ok +test_lifecycle_binds_replacements_guard_text_and_blobs (__main__.S5.test_lifecycle_binds_replacements_guard_text_and_blobs) ... ok +test_manifest_numbers_keep_their_type (__main__.S5.test_manifest_numbers_keep_their_type) ... ok +test_path_copy_builders_equal_deepcopy_reference (__main__.S5.test_path_copy_builders_equal_deepcopy_reference) ... ok +test_probe_static_full_tree_blob_and_stream_requirements (__main__.S5.test_probe_static_full_tree_blob_and_stream_requirements) ... ok +test_signed_instance_ids_raw_kept_and_excluded (__main__.S5.test_signed_instance_ids_raw_kept_and_excluded) ... ok +test_fixed_scenario_paths (__main__.SourcesAndMetrics.test_fixed_scenario_paths) ... ok +test_metric_ranges (__main__.SourcesAndMetrics.test_metric_ranges) ... ok +test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics.test_source_closure_synthetic_registered_map) ... ok +test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics.test_source_pin_is_not_caller_controlled) ... ok +test_array_order_and_all_differences (__main__.TypedTrees.test_array_order_and_all_differences) ... ok +test_components_and_sha (__main__.TypedTrees.test_components_and_sha) ... ok +test_deep_state_not_truncated (__main__.TypedTrees.test_deep_state_not_truncated) ... ok +test_duplicate_textual_keys_rejected (__main__.TypedTrees.test_duplicate_textual_keys_rejected) ... ok +test_exact_and_types (__main__.TypedTrees.test_exact_and_types) ... ok +test_nested_order_all_paths (__main__.TypedTrees.test_nested_order_all_paths) ... ok +test_nonstring_keys_keep_types_and_values (__main__.TypedTrees.test_nonstring_keys_keep_types_and_values) ... ok +test_unsupported_and_malformed (__main__.TypedTrees.test_unsupported_and_malformed) ... ok + +---------------------------------------------------------------------- +Ran 156 tests in 211.416s + +OK +exit=0 diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_03.sha256 b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_03.sha256 new file mode 100644 index 0000000..73d97a1 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_03.sha256 @@ -0,0 +1 @@ +e2833369d98a4cf6600fcd98dcf271c2172e8b4a5fbb366eae4fcebc5ea8921d UNIT_OUTPUT_S5_ATTEMPT_03.txt diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_03.txt b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_03.txt new file mode 100644 index 0000000..22935dd --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_03.txt @@ -0,0 +1,167 @@ +test_all_operations (__main__.Diffs.test_all_operations) ... ok +test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok +test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok +test_attempt_to_normalize_violation_rejected (__main__.Normalization.test_attempt_to_normalize_violation_rejected) ... ok +test_environment_values_differ_but_normal_form_equal (__main__.Normalization.test_environment_values_differ_but_normal_form_equal) ... ok +test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok +test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok +test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok +test_nested_lookalike_is_not_eligible (__main__.Normalization.test_nested_lookalike_is_not_eligible) ... ok +test_original_transport_sha_detects_forged_timing (__main__.Normalization.test_original_transport_sha_detects_forged_timing) ... ok +test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok +test_prefix_boundary_and_reserved_token (__main__.Normalization.test_prefix_boundary_and_reserved_token) ... ok +test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization.test_sidecar_missing_duplicate_and_forged_paths) ... ok +test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok +test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization.test_snapshot_event_outside_recent_is_not_eligible) ... ok +test_wrong_event_and_source_are_preserved (__main__.Normalization.test_wrong_event_and_source_are_preserved) ... ok +test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok +test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok +test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok +test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok +test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok +test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok +test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok +test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok +test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok +test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok +test_same_report (__main__.Reports.test_same_report) ... ok +test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok +test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok +test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok +test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok +test_O_cadence (__main__.S2Format.test_O_cadence) ... ok +test_counter_and_published_ids_binding (__main__.S2Format.test_counter_and_published_ids_binding) ... ok +test_diff_and_same_as_S_transport (__main__.S2Format.test_diff_and_same_as_S_transport) ... ok +test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok +test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok +test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok +test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok +test_harness_digest_allows_only_dictionary_order (__main__.S2Format.test_harness_digest_allows_only_dictionary_order) ... ok +test_join_is_copy_and_retains_normalization_proof (__main__.S2Format.test_join_is_copy_and_retains_normalization_proof) ... ok +test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok +test_lifecycle_guard_errors_and_boundaries (__main__.S2Format.test_lifecycle_guard_errors_and_boundaries) ... ok +test_missing_full_state_not_digest_only (__main__.S2Format.test_missing_full_state_not_digest_only) ... ok +test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format.test_normalized_digest_and_raw_tree_are_both_bound) ... ok +test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok +test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok +test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format.test_roundtrip_tree_sha_and_raw_sha) ... ok +test_s2_valid_and_not_formal_acceptance (__main__.S2Format.test_s2_valid_and_not_formal_acceptance) ... ok +test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok +test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok +test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok +test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok +test_current_probe (__main__.S2Static.test_current_probe) ... ok +test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) ... ok +test_no_changes_outside_preparation_or_step (__main__.S2Static.test_no_changes_outside_preparation_or_step) ... ok +test_no_event_cap_or_duplicate_report (__main__.S2Static.test_no_event_cap_or_duplicate_report) ... ok +test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok +test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok +test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok +test_variant_inference (__main__.S2Static.test_variant_inference) ... ok +test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok +test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok +test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a.test_e3_NA_cannot_be_PASS_or_applied) ... ok +test_e3_coverage_input_and_event_count_binding (__main__.S3a.test_e3_coverage_input_and_event_count_binding) ... ok +test_e3_invented_codes_and_missing_load_code (__main__.S3a.test_e3_invented_codes_and_missing_load_code) ... ok +test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) ... ok +test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... ok +test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... ok +test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok +test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok +test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) ... ok +test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... ok +test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok +test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok +test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok +test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... ok +test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... ok +test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok +test_signals_all_minutes_and_order (__main__.S3a.test_signals_all_minutes_and_order) ... ok +test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok +test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok +test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok +test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok +test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) ... ok +test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok +test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok +test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok +test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok +test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok +test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok +test_interval_gap (__main__.S3b.test_interval_gap) ... ok +test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok +test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok +test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok +test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok +test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok +test_missing_trial (__main__.S3b.test_missing_trial) ... ok +test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok +test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) ... ok +test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok +test_path_order (__main__.S3b.test_path_order) ... ok +test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok +test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok +test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok +test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok +test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok +test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok +test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok +test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok +test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ok +test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle.test_missing_bundle_manifest_seal_and_changed_bytes) ... ok +test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle.test_missing_private_evidence_and_extra_rejected) ... ok +test_missing_profile_not_export_success (__main__.S4Bundle.test_missing_profile_not_export_success) ... ok +test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle.test_phase_role_pid_audio_and_guard_rejected) ... ok +test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle.test_rebound_lifecycle_and_scenario_contradictions) ... ok +test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle.test_seal_normal_and_changes_not_native_proof) ... ok +test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) ... ok +test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic.test_copied_manifest_root_and_destination_count_rejected) ... ok +test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic.test_failure_keeps_raw_and_no_bundle_without_starting_engine) ... ok +test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic.test_gate_none_success_not_boolean) ... ok +test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic.test_receipt_and_source_rejection_matrix) ... ok +test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic.test_synthetic_full_bundle_positive_not_native_evidence) ... ok +test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok +test_check_rejects_false_all_errors (__main__.S4ProbeAdmission.test_check_rejects_false_all_errors) ... ok +test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission.test_run_admission_accepts_true_mock_launch_only) ... ok +test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission.test_run_admission_rejects_false_without_engine) ... ok +test_nine_original_destinations_required (__main__.S4RemainingBindings.test_nine_original_destinations_required) ... ok +test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings.test_nonzero_determinism_rejected_after_real_join) ... ok +test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings.test_owned_lifecycle_exit_diagnostic_and_profile_rejections) ... ok +test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings.test_verifier_accepts_true_recomputes_raw) ... ok +test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings.test_verifier_rejects_false_with_every_error) ... ok +test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok +test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings.test_missing_sidecar_rejected_after_seal) ... ok +test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok +test_blob_transport_is_verified_and_bound (__main__.S5.test_blob_transport_is_verified_and_bound) ... ok +test_blob_trees_get_the_same_full_validation_as_inline (__main__.S5.test_blob_trees_get_the_same_full_validation_as_inline) ... ok +test_compare_and_digest_equal_s1_reference (__main__.S5.test_compare_and_digest_equal_s1_reference) ... ok +test_event_stream_replacement_is_existing_defect_not_pass (__main__.S5.test_event_stream_replacement_is_existing_defect_not_pass) ... ok +test_every_checkpoint_full_tree_verified_like_inline (__main__.S5.test_every_checkpoint_full_tree_verified_like_inline) +S5 drops the diff chain: each checkpoint carries a complete tree, verified alone. ... ok +test_fast_normalization_equals_original (__main__.S5.test_fast_normalization_equals_original) ... ok +test_labelled_harness_digest_falls_back_to_dictionary_order_only (__main__.S5.test_labelled_harness_digest_falls_back_to_dictionary_order_only) ... ok +test_lifecycle_binds_replacements_guard_text_and_blobs (__main__.S5.test_lifecycle_binds_replacements_guard_text_and_blobs) ... ok +test_manifest_numbers_keep_their_type (__main__.S5.test_manifest_numbers_keep_their_type) ... ok +test_memo_scope_same_results_and_restores_gc (__main__.S5.test_memo_scope_same_results_and_restores_gc) ... ok +test_path_copy_builders_equal_deepcopy_reference (__main__.S5.test_path_copy_builders_equal_deepcopy_reference) ... ok +test_probe_static_full_tree_blob_and_stream_requirements (__main__.S5.test_probe_static_full_tree_blob_and_stream_requirements) ... ok +test_publish_pairs_bound_to_source_causes (__main__.S5.test_publish_pairs_bound_to_source_causes) ... ok +test_signed_instance_ids_raw_kept_and_excluded (__main__.S5.test_signed_instance_ids_raw_kept_and_excluded) ... ok +test_fixed_scenario_paths (__main__.SourcesAndMetrics.test_fixed_scenario_paths) ... ok +test_metric_ranges (__main__.SourcesAndMetrics.test_metric_ranges) ... ok +test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics.test_source_closure_synthetic_registered_map) ... ok +test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics.test_source_pin_is_not_caller_controlled) ... ok +test_array_order_and_all_differences (__main__.TypedTrees.test_array_order_and_all_differences) ... ok +test_components_and_sha (__main__.TypedTrees.test_components_and_sha) ... ok +test_deep_state_not_truncated (__main__.TypedTrees.test_deep_state_not_truncated) ... ok +test_duplicate_textual_keys_rejected (__main__.TypedTrees.test_duplicate_textual_keys_rejected) ... ok +test_exact_and_types (__main__.TypedTrees.test_exact_and_types) ... ok +test_nested_order_all_paths (__main__.TypedTrees.test_nested_order_all_paths) ... ok +test_nonstring_keys_keep_types_and_values (__main__.TypedTrees.test_nonstring_keys_keep_types_and_values) ... ok +test_unsupported_and_malformed (__main__.TypedTrees.test_unsupported_and_malformed) ... ok + +---------------------------------------------------------------------- +Ran 160 tests in 130.451s + +OK +python_exit=0 diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_04.sha256 b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_04.sha256 new file mode 100644 index 0000000..4829024 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_04.sha256 @@ -0,0 +1 @@ +f8b652c4f0cdc52c061cd58f1a1334e755c22d5dc206225a5cf2d4863a780c85 UNIT_OUTPUT_S5_ATTEMPT_04.txt diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_04.txt b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_04.txt new file mode 100644 index 0000000..02099a6 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_04.txt @@ -0,0 +1,202 @@ +test_all_operations (__main__.Diffs.test_all_operations) ... ok +test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok +test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok +test_attempt_to_normalize_violation_rejected (__main__.Normalization.test_attempt_to_normalize_violation_rejected) ... ok +test_environment_values_differ_but_normal_form_equal (__main__.Normalization.test_environment_values_differ_but_normal_form_equal) ... ok +test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok +test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok +test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok +test_nested_lookalike_is_not_eligible (__main__.Normalization.test_nested_lookalike_is_not_eligible) ... ok +test_original_transport_sha_detects_forged_timing (__main__.Normalization.test_original_transport_sha_detects_forged_timing) ... ok +test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok +test_prefix_boundary_and_reserved_token (__main__.Normalization.test_prefix_boundary_and_reserved_token) ... ok +test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization.test_sidecar_missing_duplicate_and_forged_paths) ... ok +test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok +test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization.test_snapshot_event_outside_recent_is_not_eligible) ... ok +test_wrong_event_and_source_are_preserved (__main__.Normalization.test_wrong_event_and_source_are_preserved) ... ok +test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok +test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok +test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok +test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok +test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok +test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok +test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok +test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok +test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok +test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok +test_same_report (__main__.Reports.test_same_report) ... ok +test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok +test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok +test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok +test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok +test_O_cadence (__main__.S2Format.test_O_cadence) ... ok +test_counter_and_published_ids_binding (__main__.S2Format.test_counter_and_published_ids_binding) ... ok +test_diff_and_same_as_S_transport (__main__.S2Format.test_diff_and_same_as_S_transport) ... ok +test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok +test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok +test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok +test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok +test_harness_digest_allows_only_dictionary_order (__main__.S2Format.test_harness_digest_allows_only_dictionary_order) ... ok +test_join_is_copy_and_retains_normalization_proof (__main__.S2Format.test_join_is_copy_and_retains_normalization_proof) ... ok +test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok +test_lifecycle_guard_errors_and_boundaries (__main__.S2Format.test_lifecycle_guard_errors_and_boundaries) ... ok +test_missing_full_state_not_digest_only (__main__.S2Format.test_missing_full_state_not_digest_only) ... ok +test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format.test_normalized_digest_and_raw_tree_are_both_bound) ... ok +test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok +test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok +test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format.test_roundtrip_tree_sha_and_raw_sha) ... ok +test_s2_valid_and_not_formal_acceptance (__main__.S2Format.test_s2_valid_and_not_formal_acceptance) ... ok +test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok +test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok +test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok +test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok +test_current_probe (__main__.S2Static.test_current_probe) ... ok +test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) ... ok +test_no_changes_outside_preparation_or_step (__main__.S2Static.test_no_changes_outside_preparation_or_step) ... ok +test_no_event_cap_or_duplicate_report (__main__.S2Static.test_no_event_cap_or_duplicate_report) ... ok +test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok +test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok +test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok +test_variant_inference (__main__.S2Static.test_variant_inference) ... ok +test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok +test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok +test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a.test_e3_NA_cannot_be_PASS_or_applied) ... ok +test_e3_coverage_input_and_event_count_binding (__main__.S3a.test_e3_coverage_input_and_event_count_binding) ... ok +test_e3_invented_codes_and_missing_load_code (__main__.S3a.test_e3_invented_codes_and_missing_load_code) ... ok +test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) ... ok +test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... ok +test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... ok +test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok +test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok +test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) ... ok +test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... ok +test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok +test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok +test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok +test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... ok +test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... ok +test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok +test_signals_all_minutes_and_order (__main__.S3a.test_signals_all_minutes_and_order) ... ok +test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok +test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok +test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok +test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok +test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) ... ok +test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok +test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok +test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok +test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok +test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok +test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok +test_interval_gap (__main__.S3b.test_interval_gap) ... ok +test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok +test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok +test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok +test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok +test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok +test_missing_trial (__main__.S3b.test_missing_trial) ... ok +test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok +test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) ... ok +test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok +test_path_order (__main__.S3b.test_path_order) ... ok +test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok +test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok +test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok +test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok +test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok +test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok +test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok +test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok +test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ERROR +test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle.test_missing_bundle_manifest_seal_and_changed_bytes) ... ok +test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle.test_missing_private_evidence_and_extra_rejected) ... ok +test_missing_profile_not_export_success (__main__.S4Bundle.test_missing_profile_not_export_success) ... ok +test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle.test_phase_role_pid_audio_and_guard_rejected) ... ok +test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle.test_rebound_lifecycle_and_scenario_contradictions) ... ok +test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle.test_seal_normal_and_changes_not_native_proof) ... ok +test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) ... ok +setUpClass (__main__.S4NativeBundleLogic) ... ERROR +test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok +test_check_rejects_false_all_errors (__main__.S4ProbeAdmission.test_check_rejects_false_all_errors) ... ok +test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission.test_run_admission_accepts_true_mock_launch_only) ... ok +test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission.test_run_admission_rejects_false_without_engine) ... ok +setUpClass (__main__.S4RemainingBindings) ... ERROR +test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok +test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings.test_missing_sidecar_rejected_after_seal) ... ok +test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok +test_blob_transport_is_verified_and_bound (__main__.S5.test_blob_transport_is_verified_and_bound) ... ok +test_blob_trees_get_the_same_full_validation_as_inline (__main__.S5.test_blob_trees_get_the_same_full_validation_as_inline) ... ok +test_compare_and_digest_equal_s1_reference (__main__.S5.test_compare_and_digest_equal_s1_reference) ... ok +test_event_stream_replacement_is_existing_defect_not_pass (__main__.S5.test_event_stream_replacement_is_existing_defect_not_pass) ... ok +test_every_checkpoint_full_tree_verified_like_inline (__main__.S5.test_every_checkpoint_full_tree_verified_like_inline) +S5 drops the diff chain: each checkpoint carries a complete tree, verified alone. ... ok +test_fast_normalization_equals_original (__main__.S5.test_fast_normalization_equals_original) ... ok +test_labelled_harness_digest_falls_back_to_dictionary_order_only (__main__.S5.test_labelled_harness_digest_falls_back_to_dictionary_order_only) ... ok +test_lifecycle_binds_replacements_guard_text_and_blobs (__main__.S5.test_lifecycle_binds_replacements_guard_text_and_blobs) ... ok +test_manifest_numbers_keep_their_type (__main__.S5.test_manifest_numbers_keep_their_type) ... ok +test_memo_scope_same_results_and_restores_gc (__main__.S5.test_memo_scope_same_results_and_restores_gc) ... ok +test_path_copy_builders_equal_deepcopy_reference (__main__.S5.test_path_copy_builders_equal_deepcopy_reference) ... ok +test_probe_static_full_tree_blob_and_stream_requirements (__main__.S5.test_probe_static_full_tree_blob_and_stream_requirements) ... ok +test_publish_pairs_bound_to_source_causes (__main__.S5.test_publish_pairs_bound_to_source_causes) ... ok +test_signed_instance_ids_raw_kept_and_excluded (__main__.S5.test_signed_instance_ids_raw_kept_and_excluded) ... ok +test_validate_tree_equals_s1_reference_on_mutations (__main__.S5.test_validate_tree_equals_s1_reference_on_mutations) ... ok +test_fixed_scenario_paths (__main__.SourcesAndMetrics.test_fixed_scenario_paths) ... ok +test_metric_ranges (__main__.SourcesAndMetrics.test_metric_ranges) ... ok +test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics.test_source_closure_synthetic_registered_map) ... ok +test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics.test_source_pin_is_not_caller_controlled) ... ok +test_array_order_and_all_differences (__main__.TypedTrees.test_array_order_and_all_differences) ... ok +test_components_and_sha (__main__.TypedTrees.test_components_and_sha) ... ok +test_deep_state_not_truncated (__main__.TypedTrees.test_deep_state_not_truncated) ... ok +test_duplicate_textual_keys_rejected (__main__.TypedTrees.test_duplicate_textual_keys_rejected) ... ok +test_exact_and_types (__main__.TypedTrees.test_exact_and_types) ... ok +test_nested_order_all_paths (__main__.TypedTrees.test_nested_order_all_paths) ... ok +test_nonstring_keys_keep_types_and_values (__main__.TypedTrees.test_nonstring_keys_keep_types_and_values) ... ok +test_unsupported_and_malformed (__main__.TypedTrees.test_unsupported_and_malformed) ... ok + +====================================================================== +ERROR: test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1487, in test_import_closure_rejections + self.assertTrue(checker.check_python_imports((verifier.HERE/name).read_text())) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py", line 29, in check_python_imports + eq.require(all(n and n.split('.')[0] in allowed for n in names), 'import outside EQ closure') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 168, in require + raise EvidenceError(message) +compare_versions.EvidenceError: import outside EQ closure + +====================================================================== +ERROR: setUpClass (__main__.S4NativeBundleLogic) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1514, in setUpClass + cls.collector, cls.standard, cls.outer, cls.diag = driver.load_dependencies() + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/run_eq.py", line 54, in load_dependencies + check.check_python_imports((local / name).read_text()) + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py", line 29, in check_python_imports + eq.require(all(n and n.split('.')[0] in allowed for n in names), 'import outside EQ closure') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 168, in require + raise EvidenceError(message) +compare_versions.EvidenceError: import outside EQ closure + +====================================================================== +ERROR: setUpClass (__main__.S4RemainingBindings) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py", line 1514, in setUpClass + cls.collector, cls.standard, cls.outer, cls.diag = driver.load_dependencies() + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/run_eq.py", line 54, in load_dependencies + check.check_python_imports((local / name).read_text()) + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py", line 29, in check_python_imports + eq.require(all(n and n.split('.')[0] in allowed for n in names), 'import outside EQ closure') + File "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py", line 168, in require + raise EvidenceError(message) +compare_versions.EvidenceError: import outside EQ closure + +---------------------------------------------------------------------- +Ran 151 tests in 42.213s + +FAILED (errors=3) diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_05.sha256 b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_05.sha256 new file mode 100644 index 0000000..93c569f --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_05.sha256 @@ -0,0 +1 @@ +7372a382cc2f3c090013874d8f28f17cad4cb1b965ad86c86de7a21fc2d851a8 UNIT_OUTPUT_S5_ATTEMPT_05.txt diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_05.txt b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_05.txt new file mode 100644 index 0000000..5cbff21 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S5_ATTEMPT_05.txt @@ -0,0 +1,167 @@ +test_all_operations (__main__.Diffs.test_all_operations) ... ok +test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok +test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok +test_attempt_to_normalize_violation_rejected (__main__.Normalization.test_attempt_to_normalize_violation_rejected) ... ok +test_environment_values_differ_but_normal_form_equal (__main__.Normalization.test_environment_values_differ_but_normal_form_equal) ... ok +test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok +test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok +test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok +test_nested_lookalike_is_not_eligible (__main__.Normalization.test_nested_lookalike_is_not_eligible) ... ok +test_original_transport_sha_detects_forged_timing (__main__.Normalization.test_original_transport_sha_detects_forged_timing) ... ok +test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok +test_prefix_boundary_and_reserved_token (__main__.Normalization.test_prefix_boundary_and_reserved_token) ... ok +test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization.test_sidecar_missing_duplicate_and_forged_paths) ... ok +test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok +test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization.test_snapshot_event_outside_recent_is_not_eligible) ... ok +test_wrong_event_and_source_are_preserved (__main__.Normalization.test_wrong_event_and_source_are_preserved) ... ok +test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok +test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok +test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok +test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok +test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok +test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok +test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok +test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok +test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok +test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok +test_same_report (__main__.Reports.test_same_report) ... ok +test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok +test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok +test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok +test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok +test_O_cadence (__main__.S2Format.test_O_cadence) ... ok +test_counter_and_published_ids_binding (__main__.S2Format.test_counter_and_published_ids_binding) ... ok +test_diff_and_same_as_S_transport (__main__.S2Format.test_diff_and_same_as_S_transport) ... ok +test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok +test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok +test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok +test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok +test_harness_digest_allows_only_dictionary_order (__main__.S2Format.test_harness_digest_allows_only_dictionary_order) ... ok +test_join_is_copy_and_retains_normalization_proof (__main__.S2Format.test_join_is_copy_and_retains_normalization_proof) ... ok +test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok +test_lifecycle_guard_errors_and_boundaries (__main__.S2Format.test_lifecycle_guard_errors_and_boundaries) ... ok +test_missing_full_state_not_digest_only (__main__.S2Format.test_missing_full_state_not_digest_only) ... ok +test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format.test_normalized_digest_and_raw_tree_are_both_bound) ... ok +test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok +test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok +test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format.test_roundtrip_tree_sha_and_raw_sha) ... ok +test_s2_valid_and_not_formal_acceptance (__main__.S2Format.test_s2_valid_and_not_formal_acceptance) ... ok +test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok +test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok +test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok +test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok +test_current_probe (__main__.S2Static.test_current_probe) ... ok +test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) ... ok +test_no_changes_outside_preparation_or_step (__main__.S2Static.test_no_changes_outside_preparation_or_step) ... ok +test_no_event_cap_or_duplicate_report (__main__.S2Static.test_no_event_cap_or_duplicate_report) ... ok +test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok +test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok +test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok +test_variant_inference (__main__.S2Static.test_variant_inference) ... ok +test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok +test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok +test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a.test_e3_NA_cannot_be_PASS_or_applied) ... ok +test_e3_coverage_input_and_event_count_binding (__main__.S3a.test_e3_coverage_input_and_event_count_binding) ... ok +test_e3_invented_codes_and_missing_load_code (__main__.S3a.test_e3_invented_codes_and_missing_load_code) ... ok +test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) ... ok +test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... ok +test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... ok +test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok +test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok +test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) ... ok +test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... ok +test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok +test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok +test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok +test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... ok +test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... ok +test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok +test_signals_all_minutes_and_order (__main__.S3a.test_signals_all_minutes_and_order) ... ok +test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok +test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok +test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok +test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok +test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) ... ok +test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok +test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok +test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok +test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok +test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok +test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok +test_interval_gap (__main__.S3b.test_interval_gap) ... ok +test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok +test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok +test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok +test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok +test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok +test_missing_trial (__main__.S3b.test_missing_trial) ... ok +test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok +test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) ... ok +test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok +test_path_order (__main__.S3b.test_path_order) ... ok +test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok +test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok +test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok +test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok +test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok +test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok +test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok +test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok +test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ok +test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle.test_missing_bundle_manifest_seal_and_changed_bytes) ... ok +test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle.test_missing_private_evidence_and_extra_rejected) ... ok +test_missing_profile_not_export_success (__main__.S4Bundle.test_missing_profile_not_export_success) ... ok +test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle.test_phase_role_pid_audio_and_guard_rejected) ... ok +test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle.test_rebound_lifecycle_and_scenario_contradictions) ... ok +test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle.test_seal_normal_and_changes_not_native_proof) ... ok +test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) ... ok +test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic.test_copied_manifest_root_and_destination_count_rejected) ... ok +test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic.test_failure_keeps_raw_and_no_bundle_without_starting_engine) ... ok +test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic.test_gate_none_success_not_boolean) ... ok +test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic.test_receipt_and_source_rejection_matrix) ... ok +test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic.test_synthetic_full_bundle_positive_not_native_evidence) ... ok +test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok +test_check_rejects_false_all_errors (__main__.S4ProbeAdmission.test_check_rejects_false_all_errors) ... ok +test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission.test_run_admission_accepts_true_mock_launch_only) ... ok +test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission.test_run_admission_rejects_false_without_engine) ... ok +test_nine_original_destinations_required (__main__.S4RemainingBindings.test_nine_original_destinations_required) ... ok +test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings.test_nonzero_determinism_rejected_after_real_join) ... ok +test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings.test_owned_lifecycle_exit_diagnostic_and_profile_rejections) ... ok +test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings.test_verifier_accepts_true_recomputes_raw) ... ok +test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings.test_verifier_rejects_false_with_every_error) ... ok +test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok +test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings.test_missing_sidecar_rejected_after_seal) ... ok +test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok +test_blob_transport_is_verified_and_bound (__main__.S5.test_blob_transport_is_verified_and_bound) ... ok +test_blob_trees_get_the_same_full_validation_as_inline (__main__.S5.test_blob_trees_get_the_same_full_validation_as_inline) ... ok +test_compare_and_digest_equal_s1_reference (__main__.S5.test_compare_and_digest_equal_s1_reference) ... ok +test_event_stream_replacement_is_existing_defect_not_pass (__main__.S5.test_event_stream_replacement_is_existing_defect_not_pass) ... ok +test_every_checkpoint_full_tree_verified_like_inline (__main__.S5.test_every_checkpoint_full_tree_verified_like_inline) +S5 drops the diff chain: each checkpoint carries a complete tree, verified alone. ... ok +test_fast_normalization_equals_original (__main__.S5.test_fast_normalization_equals_original) ... ok +test_labelled_harness_digest_falls_back_to_dictionary_order_only (__main__.S5.test_labelled_harness_digest_falls_back_to_dictionary_order_only) ... ok +test_lifecycle_binds_replacements_guard_text_and_blobs (__main__.S5.test_lifecycle_binds_replacements_guard_text_and_blobs) ... ok +test_manifest_numbers_keep_their_type (__main__.S5.test_manifest_numbers_keep_their_type) ... ok +test_memo_scope_same_results_and_restores_gc (__main__.S5.test_memo_scope_same_results_and_restores_gc) ... ok +test_path_copy_builders_equal_deepcopy_reference (__main__.S5.test_path_copy_builders_equal_deepcopy_reference) ... ok +test_probe_static_full_tree_blob_and_stream_requirements (__main__.S5.test_probe_static_full_tree_blob_and_stream_requirements) ... ok +test_publish_pairs_bound_to_source_causes (__main__.S5.test_publish_pairs_bound_to_source_causes) ... ok +test_signed_instance_ids_raw_kept_and_excluded (__main__.S5.test_signed_instance_ids_raw_kept_and_excluded) ... ok +test_validate_tree_equals_s1_reference_on_mutations (__main__.S5.test_validate_tree_equals_s1_reference_on_mutations) ... ok +test_fixed_scenario_paths (__main__.SourcesAndMetrics.test_fixed_scenario_paths) ... ok +test_metric_ranges (__main__.SourcesAndMetrics.test_metric_ranges) ... ok +test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics.test_source_closure_synthetic_registered_map) ... ok +test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics.test_source_pin_is_not_caller_controlled) ... ok +test_array_order_and_all_differences (__main__.TypedTrees.test_array_order_and_all_differences) ... ok +test_components_and_sha (__main__.TypedTrees.test_components_and_sha) ... ok +test_deep_state_not_truncated (__main__.TypedTrees.test_deep_state_not_truncated) ... ok +test_duplicate_textual_keys_rejected (__main__.TypedTrees.test_duplicate_textual_keys_rejected) ... ok +test_exact_and_types (__main__.TypedTrees.test_exact_and_types) ... ok +test_nested_order_all_paths (__main__.TypedTrees.test_nested_order_all_paths) ... ok +test_nonstring_keys_keep_types_and_values (__main__.TypedTrees.test_nonstring_keys_keep_types_and_values) ... ok +test_unsupported_and_malformed (__main__.TypedTrees.test_unsupported_and_malformed) ... ok + +---------------------------------------------------------------------- +Ran 161 tests in 121.627s + +OK diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py index cbf535d..6961ded 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py @@ -6,6 +6,7 @@ copies using S1's sole F1-F3 implementation; it does not manufacture Godot proof import base64 import ast import copy +import gc import hashlib import json from pathlib import Path @@ -18,7 +19,7 @@ import compare_versions as eq def check_python_imports(text): """Closed EQ/stdlib import screen; dynamic file loading is separately pinned by run_eq.""" - allowed = {'argparse', 'ast', 'base64', 'copy', 'hashlib', 'importlib', 'json', 'math', + allowed = {'argparse', 'ast', 'base64', 'collections', 'copy', 'gc', 'gzip', 'hashlib', 'importlib', 'json', 'math', 'os', 'pathlib', 're', 'shutil', 'struct', 'subprocess', 'sys', 'tempfile', 'unittest', 'uuid', 'check_eq', 'compare_versions', 'run_eq', 'verify_eq'} tree = ast.parse(text) @@ -54,6 +55,7 @@ def integer(value): def fields(tree): + tree = eq.materialize(tree) eq.require(tree[0] == 27, 'dictionary required') result = {} for key, value in tree[1]: @@ -62,7 +64,21 @@ def fields(tree): return result +_COUNTS = {} + + def counts(tree): + """Domain-event counter and published IDs; memoized by tree SHA (content-addressed).""" + known = eq.known_sha(tree) + if known is not None and known in _COUNTS: + return _COUNTS[known] + result = _counts(tree) + if known is not None: + _COUNTS[known] = result + return result + + +def _counts(tree): runtime = fields(fields(fields(tree)['systems'])['runtime']) counters = fields(fields(runtime['id_generator'])['counters']) # Product get_counter has an explicit zero default for an absent prefix. @@ -83,38 +99,96 @@ def normalized_digest(tree): Key nodes remain exact. Only Dictionary enumeration order is canonicalized, matching S1 fallback; floats, metadata and Array order remain untouched. """ - def canonical(node): - result = copy.deepcopy(node) - if node[0] == 27: - names = [eq._key_text(k) for k, _ in node[1]] - eq.require(len(set(names)) == len(names), 'digest textual key collision') - result[1] = [[copy.deepcopy(k), canonical(v)] for k, v in sorted(node[1], key=lambda p: eq._key_text(p[0]))] - elif node[0] == 28 or 5 <= node[0] <= 20 or 30 <= node[0] <= 38: - result[1] = [canonical(v) for v in node[1]] - return result - eq.validate_tree(tree) - return eq.tree_sha(canonical(tree)) + return eq.canonical_digest(tree) + + +def edit_at(tree, names, change): + """Copy of tree with the node at the textual path replaced by change(node). + + Only the dictionaries on the path are copied; all other nodes are shared, so the + result has the same JSON as deepcopy-then-mutate. Inputs are never modified.""" + tree = eq.materialize(tree) + if not names: + return change(tree) + data = fields(tree) + eq.require(names[0] in data, 'missing path: ' + names[0]) + pairs = [[k, edit_at(v, names[1:], change)] if k[0] in (4, 21) and k[1] == names[0] else [k, v] for k, v in tree[1]] + return [27, pairs, tree[2]] + + +def describe(tree): + """Large trees in derived results are recorded by SHA; raw blobs keep the content.""" + known = eq.known_sha(tree) + if known is None: + return tree + normalized = isinstance(tree, eq.TreeRef) and tree.base is not None + source = tree.base.sha if normalized else known + return {'typed_tree_sha': known, 'source_tree_sha': source, 'normalized': normalized} + + +class memo_scope: + """Within one verification call, identical check inputs are checked once (S5). + + The keys are SHA-256 of the exact JSON inputs; blobs are content-addressed, so equal + inputs give equal results. Only successful results are reused; outside a scope every + call recomputes.""" + def __enter__(self): + global _MEMO + self.previous, _MEMO = _MEMO, ({} if _MEMO is None else _MEMO) + # Typed trees are acyclic lists; cyclic GC passes over millions of their nodes + # dominated the R time (E45 123 s -> 18 s). Results do not depend on GC. + self.collecting = gc.isenabled() + gc.disable() + return self + + def __exit__(self, *_): + global _MEMO + _MEMO = self.previous + if self.collecting: + gc.enable() + return False + + +_MEMO = None + + +def _memoized(name, function, *args): + if _MEMO is None: + return function(*args) + key = (name, sha(eq.json_bytes(list(args)))) + if key not in _MEMO: + _MEMO[key] = function(*args) + return _MEMO[key] def join_report(raw): """Return (joined report, reversible normalization evidence), never edit raw.""" + return _memoized('join_report', _join_report, raw) + + +def _join_report(raw): joined = copy.deepcopy(raw) proofs = [] trees = eq.reconstruct_checkpoints(raw['checkpoints']) for cp, (s, latest) in zip(joined['checkpoints'], trees): row = {} - for name, tree in (('S', s), ('latest', latest)): + for name, tree in (('latest', latest), ('S', s)): normalized, side = eq.normalize_tree(tree, raw['profile_root']) restored = eq.restore_tree(normalized, side, raw['profile_root']) eq.require(eq.tree_sha(restored) == eq.tree_sha(tree), 'normalization restore SHA') - row[name] = {'normalized_tree_sha': eq.tree_sha(normalized), 'normalized_digest': normalized_digest(normalized), 'sidecar': side, + # The harness digest is the latest's (the only one compared); S records it only + # when S is the same normalized tree, otherwise null. + digest = (eq.harness_digest(normalized) if name == 'latest' else + row['latest']['normalized_digest'] if eq.tree_sha(normalized) == row['latest']['normalized_tree_sha'] else None) + row[name] = {'normalized_tree_sha': eq.tree_sha(normalized), 'normalized_digest': digest, 'sidecar': side, 'restored_tree_sha': eq.tree_sha(restored)} + row = {'S': row['S'], 'latest': row['latest']} expected = row['latest']['normalized_digest'] eq.require(cp['harness_normalized_digest'] in (None, expected), 'forged normalized digest') cp['harness_normalized_digest'] = expected proofs.append(row) for i, scenario in enumerate(raw.get('scenarios', [])): - derived, scenario_proofs = join_report({'profile_root': raw['profile_root'], 'checkpoints': scenario['checkpoints']}) + derived, scenario_proofs = _join_report({'profile_root': raw['profile_root'], 'checkpoints': scenario['checkpoints']}) joined['scenarios'][i]['checkpoints'] = derived['checkpoints'] proofs.append({'scenario': scenario['id'], 'checkpoints': scenario_proofs}) if scenario['id'].startswith(('E4/', 'E5/')): @@ -124,6 +198,10 @@ def join_report(raw): def check_measurement(raw, measurement): + return _memoized('check_measurement', _check_measurement, raw, measurement) + + +def _check_measurement(raw, measurement): eq.require(type(measurement) is dict, 'measurement connection inventory missing') eq.require(set(measurement) == {'probe_object', 'connections', 'start_minute'}, 'measurement inventory fields') eq.require(measurement['start_minute'] == raw['checkpoints'][0]['minute'], 'inventory B1 minute') @@ -136,9 +214,16 @@ def check_measurement(raw, measurement): eq.require(set(row) == {'object', 'method', 'flags'}, 'connection fields') eq.require(sum(r['object'] == probe and r['method'] == method for r in rows) == 1, 'missing/duplicate probe connection: ' + signal) - expected = [{'signal': signal, 'minute': cp['minute']} for cp in raw['checkpoints'][1:] - for signal in ('snapshot_published', 'state_updated')] - eq.require(eq.exact(raw['signal_order'], expected), 'exact measured signal pairs/checkpoint minutes required') + # S5: one tick-end publish per measured minute, plus one publish after each run of + # commands completed by a paused submit (R minute 648: 50 commands, 51 pairs). The + # S3a rule of exactly one pair per minute was a harness assumption, not product behavior. + events = event_numbers([eq._transport(row['transport'], None) for row in raw['events']]) + trees = eq.reconstruct_checkpoints(raw['checkpoints']) + command, other = check_publishes(raw['signal_order'], events, raw['event_start']['counter'], raw['event_end']['counter'], + next_command_id(trees[0][0]), next_command_id(trees[-1][0])) + minutes = [cp['minute'] for cp in raw['checkpoints'][1:]] + eq.require(sorted(m for m, _ in other) == minutes and all(m in minutes for m, _ in command), + 'exact measured signal pairs/checkpoint minutes required') def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_evidence=None): @@ -173,7 +258,8 @@ def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_ev eq.require(type(c['_mvp_manifest_cache_generation']) is int, 'cache generation') eq.require(c['catalog_is_current'] is True, 'unfilled checkpoint cache') ids = [c['catalog_instance_id'], c['current_catalog_instance_id']] - eq.require(all(type(v) is str and re.fullmatch('[1-9][0-9]*', v) for v in ids), 'cache instance IDs') + # Godot instance IDs are signed 64-bit; RefCounted IDs are negative. Raw values are kept. + eq.require(all(type(v) is str and re.fullmatch('-?[1-9][0-9]*', v) for v in ids), 'cache instance IDs') eq.require(ids[0] == ids[1], 'cache identity contradiction') eq.validate_tree([29, c['_mvp_manifest_cache_bytes']]) trees = eq.reconstruct_checkpoints(cps) @@ -203,7 +289,7 @@ def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_ev eq.require(set(end_ids) == set(begin_ids) | set(event_ids), 'missing/extra E2 events') eq.require(end['counter'] - begin['counter'] == len(event_ids), 'event counter delta') for row in raw['signal_order']: - eq.require(set(row) == {'signal', 'minute'} and row['signal'] in ('snapshot_published', 'state_updated'), 'signal record') + eq.require(set(row) == {'signal', 'minute', 'event_counter', 'command_counter'} and row['signal'] in ('snapshot_published', 'state_updated'), 'signal record') eq.require(type(row['minute']) is int and start_minute <= row['minute'] <= start_minute+30, 'signal minute') check_measurement(raw, measurement) out.update(ok=True, normalization=proofs, comparison=compared) @@ -212,6 +298,16 @@ def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_ev return out +def raw_layer_sha(value): + """S/K/C bytes: an S5 blob reference (verified on load) or legacy base64 text.""" + if type(value) is dict: + eq.require(set(value) == {'blob'}, 'raw layer blob fields') + eq.blob_bytes(value['blob']) + return value['blob'] + eq.validate_tree([29, value]) + return sha(base64.b64decode(value, validate=True)) + + def check_observation(cp, evidence): """Bind retained full bytes to report SHA, not merely asserted equality.""" eq.require(set(evidence) == {'before', 'after'}, 'observation evidence fields') @@ -220,8 +316,7 @@ def check_observation(cp, evidence): eq.require(set(row) == {'sha', 'S', 'K', 'C', 'kernel', 'cache'}, 'raw layer fields') eq.require(eq.exact(row['kernel'], cp['K']) and eq.exact(row['cache'], cp['C']), 'raw layer value contradiction') for layer in ('S', 'K', 'C'): - eq.validate_tree([29, row[layer]]) - actual = sha(base64.b64decode(row[layer], validate=True)) + actual = raw_layer_sha(row[layer]) eq.require(actual == row['sha'][layer] == cp['observation'][side][layer], 'raw layer SHA mismatch') eq.require(all(evidence['before'][k] == evidence['after'][k] for k in ('S', 'K', 'C')), 'raw observation bytes differ') return True @@ -245,15 +340,22 @@ def strict_json(text): def check_lifecycle(raw, lifecycle, guard): """S2 startup proof; S4 must supply the separately SHA-bound guard receipt.""" - eq.require(lifecycle['stage'] in ('S2', 'S3a', 'S3b') and lifecycle['errors'] == [], 'probe errors/stage') - eq.require(lifecycle['stop_reason'] == {'S2': 'S2_complete_S3_pending', 'S3a': 'S3a_complete_S3b_pending', 'S3b': 'S3b_complete_S4_pending'}[lifecycle['stage']], 'probe did not finish stage') - if lifecycle['stage'] in ('S3a', 'S3b'): + eq.require(lifecycle['stage'] in ('S2', 'S3a', 'S3b', 'S5') and lifecycle['errors'] == [], 'probe errors/stage') + eq.require(lifecycle['stop_reason'] == {'S2': 'S2_complete_S3_pending', 'S3a': 'S3a_complete_S3b_pending', 'S3b': 'S3b_complete_S4_pending', 'S5': 'S5_complete'}[lifecycle['stage']], 'probe did not finish stage') + if lifecycle['stage'] in ('S3a', 'S3b', 'S5'): check_measurement(raw, lifecycle['measurement']) check_e3(dict(raw, scenarios=raw['scenarios'][:18]), lifecycle['e3'], lifecycle['e3_b1']) - if lifecycle['stage'] == 'S3b': + if lifecycle['stage'] in ('S3b', 'S5'): check_e45(raw, lifecycle['e45'], lifecycle['e3'][-1]['end']) + if lifecycle['stage'] == 'S5': + eq.require(eq.exact(lifecycle['event_stream_replacements'], stream_facts(lifecycle['e3'], lifecycle['e45'])), + 'event stream replacement record differs') binding = lifecycle['guard'] - eq.require(guard['verified'] is True and eq.exact(binding['receipt'], guard), 'independent guard differs') + # The probe's parsed receipt has float numbers (Godot JSON); bind its exact text instead. + receipt = strict_json(binding['receipt_text']) if 'receipt_text' in binding else binding['receipt'] + eq.require(guard['verified'] is True and eq.exact(receipt, guard), 'independent guard differs') + if 'receipt_text' in binding: + eq.require(sha(binding['receipt_text'].encode('utf-8')) == binding['sha256'], 'guard receipt text/SHA differs') root = eq._profile(raw['profile_root']) eq.require(root == binding['live_root'] == guard['actual_user_data_dir'], 'live/guard profile contradiction') eq.require(hex_sha(binding['sha256']), 'guard SHA missing') @@ -264,8 +366,7 @@ def check_lifecycle(raw, lifecycle, guard): row = lifecycle[label] eq.require(row['kernel']['paused'] is True and row['kernel']['absolute_minute'] == minute, 'lifecycle minute: '+label) for layer in ('S', 'K', 'C'): - eq.validate_tree([29, row[layer]]) - eq.require(sha(base64.b64decode(row[layer], validate=True)) == row['sha'][layer], 'lifecycle raw SHA') + eq.require(raw_layer_sha(row[layer]) == row['sha'][layer], 'lifecycle raw SHA') for label in ('before_main', 'after_main', 'frame1', 'frame2'): eq.require(type(lifecycle[label]) is dict and lifecycle[label]['kernel']['paused'] is True, 'startup frame evidence') ops = lifecycle['ops'] @@ -328,10 +429,14 @@ def check_probe(text): if '.advance_minutes(' in clean and not re.search(r'\.advance_minutes\(1\)', clean): errors.append('only one-minute stepping allowed') # A digest alone, stub proof, or truncated checkpoint producer is not admitted. required = { - 'transport': ('var_to_bytes(value)', 'var_to_bytes(decode(tree))', 'raw == rebuilt', 'encode(value)', 'hashed(json_text(tree).to_utf8_buffer())'), - 'checkpoint': ('layers()', 'transport(runtime.create_snapshot())', 'transport(latest_value)', 'before.s_bytes == after.s_bytes', 'before.k_bytes == after.k_bytes', 'before.c_bytes == after.c_bytes', 'diff_record(', 'write_evidence('), + 'transport': ('var_to_bytes(value)', 'var_to_bytes(decode(tree))', 'raw == rebuilt', 'encode(value)', 'json_text(tree).to_utf8_buffer()', '"tree_sha": hashed(text)', 'store_blob(text)'), + 'store_blob': ('hashed(bytes)', 'COMPRESSION_GZIP', 'decompress_dynamic(', 'refuse blob overwrite', 'blob_index[digest]'), + 'checkpoint': ('layers()', 'runtime.create_snapshot()', 'transport(snapshot_value)', 'transport(latest_value)', 'before.s_bytes == after.s_bytes', 'before.k_bytes == after.k_bytes', 'before.c_bytes == after.c_bytes', 'write_evidence('), 'bind_profile': ('ProjectSettings.globalize_path("user://").simplify_path()', 'receipt.get("actual_user_data_dir")', 'actual == live_root'), - 'observe_event': ('event.to_dictionary()', 'transport(value)', 'typeof(event.event_id)', 'typed_info(event.payload)', 'report.events.append('), + 'observe_event': ('event.to_dictionary()', 'transport(value)', 'typeof(event.event_id)', 'typed_info(event.payload)', 'report.events.append(', + 'known_event_ids[str(event.event_id)] = true'), + 'follow_event_stream': ('runtime.domain_events', 'current.recent_events()', 'known_event_ids.has(', 'observe_event(event)', + 'current.subscribe(&"*", &"p28_eq_observer", observe_event)', 'event_stream_replacements.append('), 'begin': ('runtime.domain_events.subscribe(&"*"', 'report.event_start = event_counts()', 'await step()', 'checkpoint()'), 'finish': ('root.close_requested.emit()', 'runtime.domain_events.unsubscribe(', 'write_evidence("events.json"', 'write_evidence("lifecycle.json"'), } @@ -354,6 +459,10 @@ def check_probe(text): body = '\n'.join(functions.get(name, [])) for snippet in snippets: if snippet not in body: errors.append('missing E3 boundary/call: '+name+': '+snippet) + # S5: a successful restore replaces the product event stream; every restore/load is followed. + if ('\n'.join(functions.get('run_e3', [])).count('follow_event_stream()') != 1 or + '\n'.join(functions.get('run_e4', [])).count('follow_event_stream()') != 2): + errors.append('event stream replacement not followed after restore/load') if code.count('run_e3()') != 2 or code.count('e3_prepare_package(') != 2: errors.append('unexpected scenario dispatch') scenario_body = '\n'.join(functions.get('run_e3', [])) @@ -396,42 +505,50 @@ def node_at(tree, *names): def e3_expected_input(b1, kind): - value = copy.deepcopy(b1) + value = eq.materialize(b1) if kind == 'missing_key': - value[1] = [pair for pair in value[1] if pair[0][1] != 'clock'] + value = [value[0], [pair for pair in value[1] if pair[0][1] != 'clock'], value[2]] elif kind in ('wrong_type', 'out_of_range'): - node_at(value, 'clock', 'absolute_minute' if kind == 'wrong_type' else 'speed')[:] = ( - [4, 'x'] if kind == 'wrong_type' else [2, 9]) + value = edit_at(value, ['clock', 'absolute_minute' if kind == 'wrong_type' else 'speed'], + lambda node: [4, 'x'] if kind == 'wrong_type' else [2, 9]) elif kind in ('duplicate_published_ids', 'duplicate_facility'): - nodes = (node_at(value, 'systems', 'runtime', 'domain_events', 'published_ids') if kind == 'duplicate_published_ids' - else node_at(value, 'systems', 'runtime', 'facilities', 'instances')) - if nodes[1]: - nodes[1].append(copy.deepcopy(nodes[1][0])) - else: + names = (['systems', 'runtime', 'domain_events', 'published_ids'] if kind == 'duplicate_published_ids' + else ['systems', 'runtime', 'facilities', 'instances']) + def duplicate(nodes): + if nodes[1]: + return [nodes[0], nodes[1] + [copy.deepcopy(nodes[1][0])]] + nodes[2:] eq.require(kind == 'duplicate_facility', 'no B1 published ID to duplicate') + return nodes + value = edit_at(value, names, duplicate) return value def e3_x(before, event): - value = copy.deepcopy(before) - counter = node_at(value, 'systems', 'runtime', 'id_generator', 'counters', 'domain_event') - counter[1] += 1 + value = edit_at(before, ['systems', 'runtime', 'id_generator', 'counters', 'domain_event'], + lambda counter: [counter[0], counter[1] + 1] + counter[2:]) domain = node_at(value, 'systems', 'runtime', 'domain_events') - ids = fields(domain)['published_ids'] - ids[1].append(copy.deepcopy(fields(event)['event_id'])) - ids[1].sort(key=lambda n: n[1]) + fields(domain)['published_ids'] + event_id = copy.deepcopy(fields(event)['event_id']) recent = fields(domain)['recent_events'] dropped = copy.deepcopy(recent[1][0]) if len(recent[1]) == 256 else None - recent[1].append(copy.deepcopy(event)) - if len(recent[1]) > 256: - del recent[1][0] + def published(ids): + children = ids[1] + [event_id] + children.sort(key=lambda n: n[1]) + return [ids[0], children] + ids[2:] + def appended(events): + children = events[1] + [copy.deepcopy(event)] + if len(children) > 256: + del children[0] + return [events[0], children] + events[2:] + value = edit_at(value, ['systems', 'runtime', 'domain_events', 'published_ids'], published) + value = edit_at(value, ['systems', 'runtime', 'domain_events', 'recent_events'], appended) return value, dropped def e3_raw_observation(row): cp = row['checkpoint'] check_observation(cp, row['raw']) - s = eq._transport(cp['S'], None) + s = eq._transport_ref(cp['S']) eq.require(cp['S']['raw_bytes_sha'] == row['raw']['before']['sha']['S'], 'E3 S transport/raw') for layer in ('K', 'C'): proof = row[layer+'_tree'] @@ -449,7 +566,8 @@ def e3_package_check(package, kind): out[name] = None continue eq.require(set(item) == {'bytes', 'sha256'}, 'package file fields') - content = base64.b64decode(item['bytes'], validate=True) + content = (eq.blob_bytes(item['bytes']['blob']) if type(item['bytes']) is dict and set(item['bytes']) == {'blob'} + else base64.b64decode(item['bytes'], validate=True)) eq.require(sha(content) == item['sha256'], 'package file SHA') out[name] = content return out @@ -482,6 +600,10 @@ def e3_package_check(package, kind): def check_e3(raw, evidence, b1_record): + return _memoized('check_e3', _check_e3, raw, evidence, b1_record) + + +def _check_e3(raw, evidence, b1_record): """Separate malformed evidence from observed existing-defect candidates. Returns all actual typed differences. Never promote NA or a defect to PASS. @@ -568,6 +690,11 @@ def check_e3(raw, evidence, b1_record): 'duplicate_published_ids': 'RESTORE_FAILED', 'duplicate_facility': 'RESTORE_FAILED', 'tampered_world': 'CHECKSUM_MISMATCH'}[kind] if result['code'] != expected_code and not (kind == 'out_of_range' and result['migration_attempted']): defects.append({'kind': 'unexpected_code', 'expected': expected_code, 'actual': result['code']}) + defects.extend(stream_defects(ev['event_stream'], events)) + # Only a successful restore publishes inside E3 (an accepted invalid input is a defect). + _, other = check_publishes(ev['signals'], event_numbers(events), counts(before)[0]['counter'], counts(after)[0]['counter'], + next_command_id(before), next_command_id(after)) + eq.require(len(other) == int(api in ('restore_snapshot', 'load_from_path') and result['ok'] is True), 'E3 publish missing/extra') expected_count = 1 if api == 'load_from_path' and invoked else 0 if len(events) != expected_count: defects.append({'kind': 'event_count', 'expected': expected_count, 'actual': len(events)}) @@ -592,6 +719,8 @@ def check_e3(raw, evidence, b1_record): if 'recovery_report' in data and fields(data['recovery_report']).get('restore_committed') != [1, 0]: defects.append({'kind': 'load_restore_committed', 'actual': data['recovery_report']}) expected_state, dropped = e3_x(before, events[0]) + if expected_state is not before and eq.known_sha(before) is not None: + expected_state = eq.adopt(expected_state) normalized_expected, expected_side = eq.normalize_tree(expected_state, raw['profile_root']) normalized_after, actual_side = eq.normalize_tree(after, raw['profile_root']) delta = eq.compare_trees(normalized_expected, normalized_after) @@ -600,7 +729,7 @@ def check_e3(raw, evidence, b1_record): left, right = ev['begin']['raw']['before'], ev['end']['raw']['after'] if left[layer] != right[layer]: defects.append({'kind': layer+'_changes', 'before': left[layer], 'after': right[layer], - 'comparison': eq.compare_trees(ev['begin'][layer+'_tree']['tree'], ev['end'][layer+'_tree']['tree'])}) + 'comparison': eq.compare_trees(eq._transport(ev['begin'][layer+'_tree'], None), eq._transport(ev['end'][layer+'_tree'], None))}) # Dictionary APIs require byte identity, not normalization/order fallback. if expected_count == 0 and ev['begin']['raw']['before']['S'] != ev['end']['raw']['after']['S']: defects.append({'kind': 'S_bytes_changed', 'before': ev['begin']['raw']['before']['S'], 'after': ev['end']['raw']['after']['S']}) @@ -608,7 +737,7 @@ def check_e3(raw, evidence, b1_record): 'expected': {'ok': False if applied else None, 'code': expected_code, 'error': expected_error, 'event_count': expected_count}, 'actual': copy.deepcopy(result), 'status': 'existing-defect-candidate' if defects else ('recorded' if applied else 'NA'), - 'defects': defects, 'dropped_recent_event': dropped, 'expected_state': expected_state, + 'defects': defects, 'dropped_recent_event': dropped, 'expected_state': describe(expected_state), 'state_comparison': delta, 'expected_sidecar': expected_side, 'actual_sidecar': actual_side}) results[-1]['exclusions'] = [{'reason': 'SaveService static diagnostic budget outside S/K/C; wall time', 'before': eq._transport(ev['save_budget_before'], None), 'after': eq._transport(ev['save_budget_after'], None)}, @@ -617,6 +746,121 @@ def check_e3(raw, evidence, b1_record): +COMMAND_EVENTS = ('COMMAND_COMPLETED', 'COMMAND_FAILED') + + +def event_numbers(trees): + """domain_event.%012d IDs -> (event_type, absolute_minute, command ID or None).""" + result = {} + for tree in trees: + data = fields(tree) + match = re.fullmatch('domain_event[.]([0-9]{12})', data['event_id'][1]) + eq.require(match is not None and int(match[1]) not in result, 'event ID/counter form') + command = None + if data['event_type'][1] in COMMAND_EVENTS: + command = fields(data['payload'])['command_id'] + eq.require(command[0] == 2 and integer(command[1]), 'command event ID') + command = command[1] + result[int(match[1])] = (data['event_type'][1], data['absolute_minute'][1], command) + return result + + +_COMMANDS = {} + + +def next_command_id(tree): + known = eq.known_sha(tree) + if known is not None and known in _COMMANDS: + return _COMMANDS[known] + value = node_at(tree, 'systems', 'runtime', 'next_command_id') + eq.require(value[0] == 2 and integer(value[1]), 'next_command_id') + if known is not None: + _COMMANDS[known] = value[1] + return value[1] + + +def _tree_facts(tree): + """Parse hook: small per-tree facts are taken while the tree is in memory.""" + for function in (counts, next_command_id): + try: + function(tree) + except (eq.EvidenceError, KeyError, TypeError, IndexError): + pass + + +eq.PARSE_HOOKS.append(_tree_facts) + + +def check_publishes(signals, events, start, end, commands_start, commands_end): + """Bind every recorded publish to its source cause; return (command pairs, other pairs). + + _publish_snapshot emits snapshot_published then state_updated with no event between + (simulation_runtime.gd:12616-12621). A submit while paused processes the due command at + once and publishes after the command events of that call (:510-512, :5768-5793). Every + other publish is a tick end (:1748) or a successful restore (:1473). The events between + two publishes belong to the later one; a publish whose last preceding event is a command + event of its minute is that call's command publish and covers the trailing command + events. Every command event must be covered. Each accepted submit advances + _next_command_id once (:508) and, being due at once while paused (:493), publishes once: + a command publish follows exactly one submit, whose command is in its batch; any other + publish follows none, and no submit may remain unpublished. The remaining publishes are + counted by the caller against the ticks and restores actually performed, so missing, + extra or reordered pairs fail. + """ + eq.require(type(signals) is list and len(signals) % 2 == 0, 'signal pair missing') + pairs = [] + for i in range(0, len(signals), 2): + first, second = signals[i], signals[i+1] + eq.require(type(first) is dict and set(first) == {'signal', 'minute', 'event_counter', 'command_counter'}, 'signal record') + eq.require(first['signal'] == 'snapshot_published' and all(type(first[k]) is int for k in ('minute', 'event_counter', 'command_counter')) + and eq.exact(second, dict(first, signal='state_updated')), 'signal pair order') + pairs.append((first['minute'], first['event_counter'], first['command_counter'])) + eq.require(pairs == sorted(pairs) and all([p[i] for p in pairs] == sorted(p[i] for p in pairs) for i in (1, 2)), 'signal order') + eq.require(all(start <= p[1] <= end and commands_start <= p[2] <= commands_end for p in pairs), 'signal outside event interval') + command, other, covered, previous, submitted = [], [], set(), start, commands_start + for minute, counter, commands in pairs: + last = events.get(counter) + if counter > previous and last is not None and last[0] in COMMAND_EVENTS and last[1] == minute: + batch = [] + number = counter + while number > previous and number in events and events[number][0] in COMMAND_EVENTS: + covered.add(number) + batch.append(events[number][2]) + number -= 1 + eq.require(commands == submitted + 1 and commands - 1 in batch, 'command publish missing') + command.append((minute, counter)) + else: + eq.require(commands == submitted, 'command publish missing') + other.append((minute, counter)) + previous, submitted = max(previous, counter), commands + eq.require(submitted == commands_end, 'command publish missing') + eq.require(covered == {n for n, row in events.items() if row[0] in COMMAND_EVENTS}, 'command publish missing') + return command, other + + +def stream_defects(fact, events): + """S5: a successful restore replaces the product's DomainEventStream without carrying its + subscribers. The probe recovers the undelivered events from the live stream and + subscribes again; the dropped subscription is an existing product fact, never a PASS.""" + eq.require(type(fact) is dict and type(fact.get('replaced')) is bool, 'event stream fact missing') + if not fact['replaced']: + eq.require(fact == {'replaced': False}, 'event stream fact fields') + return [] + eq.require(set(fact) == {'replaced', 'new_stream_subscribers_before', 'recovered_event_ids'}, 'event stream fact fields') + ids = [fields(event)['event_id'][1] for event in events] + recovered = fact['recovered_event_ids'] + eq.require(type(recovered) is list and len(set(recovered)) == len(recovered) and set(recovered) <= set(ids), 'recovered events not recorded') + eq.require('*/p28_eq_observer' not in fact['new_stream_subscribers_before'], 'subscription unexpectedly carried') + return [{'kind': 'event_stream_replaced_subscribers_not_carried', 'new_stream_subscribers_before': fact['new_stream_subscribers_before'], + 'undelivered_recovered_event_ids': recovered}] + + +def stream_facts(e3_evidence, e45_evidence): + """Every replacement, in scenario order, as the probe recorded it in lifecycle.""" + facts = [ev['event_stream'] for ev in e3_evidence] + [ev['detail']['event_stream'] for ev in e45_evidence if 'event_stream' in ev['detail']] + return [f for f in facts if f.get('replaced') is True] + + # S3b additions. Raw evidence is authoritative; outcome labels are derived. E4_PATHS = ('build_facility', 'tick', 'restore_snapshot', 'load_from_path', 'rejected_command') E5_TARGETS = ('create_snapshot', 'latest_snapshot', 'snapshot_published', 'state_updated') @@ -634,8 +878,8 @@ def join_facts(value, profile_root): normal, side = eq.normalize_tree(tree, profile_root) eq.require(eq.exact(eq.restore_tree(normal, side, profile_root), tree), 'fact normalization roundtrip') proofs.append({'path': path, 'sidecar': side, 'raw': item}) - return normal - return tree + return describe(normal) + return describe(tree) if isinstance(item, dict): return {k: walk(v, path+[k]) for k, v in item.items()} if isinstance(item, list): @@ -713,6 +957,10 @@ def check_command(detail): def check_e45(raw, evidence, previous): + return _memoized('check_e45', _check_e45, raw, evidence, previous) + + +def _check_e45(raw, evidence, previous): ids = ['E4/'+p for p in E4_PATHS] + ['E5/'+t+'/'+l+'/'+d for t in E5_TARGETS for l in ('dict', 'array') for d in ('outside_in', 'inside_out')] scenarios = raw['scenarios'][18:] @@ -730,8 +978,8 @@ def check_e45(raw, evidence, previous): common = {'save_budget_before', 'save_budget_after', 'signals'} specific = { 'build_facility': {'source', 'raw_ids', 'count', 'sorted_ids', 'sort_rule', 'bounds', 'cell_rule', 'trials', 'chosen', 'applicability', 'reason', 'observation_before', 'observation_after'}, - 'tick': set(), 'restore_snapshot': {'input', 'return'}, - 'load_from_path': {'input', 'path', 'save_return', 'package_before', 'package_after', 'return', 'latest_save_operation_result'}, + 'tick': set(), 'restore_snapshot': {'input', 'return', 'event_stream'}, + 'load_from_path': {'input', 'path', 'save_return', 'package_before', 'package_after', 'return', 'latest_save_operation_result', 'event_stream'}, 'rejected_command': {'submit', 'command_result'}, }[scenario['operation']] if scenario['operation'] == 'build_facility' and detail.get('chosen') is not None: specific |= {'submit', 'command_result'} @@ -748,20 +996,22 @@ def check_e45(raw, evidence, previous): and eq.exact(events, scenario['events']), 'E4/E5 event count binding') eq.require(result['applicability'] == detail.get('applicability', 'applied') and result['reason'] == detail.get('reason'), 'E4/E5 applicability binding') eq.require(eq.exact(result['facts'], e45_facts(detail)), 'E4/E5 facts binding/aliasing record missing') - for signal in ev['signals']: - eq.require(set(signal) == {'signal', 'minute'} and signal['signal'] in ('snapshot_published', 'state_updated') - and obs[0]['checkpoint']['minute'] <= signal['minute'] <= obs[-1]['checkpoint']['minute'], 'scenario signal interval') - eq.require(len(ev['signals']) % 2 == 0, 'scenario signal pair missing') - for i in range(0, len(ev['signals']), 2): - a, b = ev['signals'][i:i+2] - eq.require(a['signal'] == 'snapshot_published' and b == {'signal': 'state_updated', 'minute': a['minute']}, 'scenario signal order') + command, other = check_publishes(ev['signals'], event_numbers(events), counts(trees[0])[0]['counter'], counts(trees[-1])[0]['counter'], + next_command_id(trees[0]), next_command_id(trees[-1])) + eq.require(all(obs[0]['checkpoint']['minute'] <= m <= obs[-1]['checkpoint']['minute'] for m, _ in command + other), 'scenario signal interval') + op = scenario['operation'] + if scenario['id'].startswith('E4/'): + expected_other = int(detail.get('chosen') is not None) if op == 'build_facility' else 1 + else: + expected_other = 2 if op == 'outside_in' else 3 + eq.require(len(other) == expected_other, 'scenario tick/restore publish missing/extra') defects = [] differences = {} for name in ('S', 'latest'): a, b = [eq._transport(o['checkpoint'][name], None) for o in (obs[0], obs[-1])] differences[name] = eq.compare_trees(a, b) for name in ('K', 'C'): - differences[name] = eq.compare_trees(obs[0][name+'_tree']['tree'], obs[-1][name+'_tree']['tree']) + differences[name] = eq.compare_trees(eq._transport(obs[0][name+'_tree'], None), eq._transport(obs[-1][name+'_tree'], None)) op = scenario['operation'] begin_count, begin_ids = counts(trees[0]) end_count, end_ids = counts(trees[-1]) @@ -782,6 +1032,7 @@ def check_e45(raw, evidence, previous): eq.require(obs[-1]['checkpoint']['minute'] == obs[0]['checkpoint']['minute'] + int(tick), 'E4 tick count') if len(obs) == 3: eq.require(obs[1]['checkpoint']['minute'] == obs[0]['checkpoint']['minute'], 'submit checkpoint delayed') if op in ('restore_snapshot', 'load_from_path'): + defects.extend(stream_defects(detail['event_stream'], events)) p = eq._transport(detail['input'], None) eq.require(eq.exact(p, trees[0]), 'E4 input not current deep copy') actual = eq._transport(detail['return'], None) @@ -799,11 +1050,13 @@ def check_e45(raw, evidence, previous): package = {'save_return': detail['save_return'], 'original': detail['package_before'], 'modified': detail['package_before'], 'after_operation': detail['package_after']} e3_package_check(package, 'good') + if expected is not p and eq.known_sha(p) is not None: + expected = eq.adopt(expected) left, left_side = eq.normalize_tree(expected, raw['profile_root']) right, right_side = eq.normalize_tree(trees[-1], raw['profile_root']) delta = eq.compare_trees(left, right) eq.require(delta['equal'], 'E4 restore/load X(P,event) mismatch') - differences['expected_state'] = {'comparison': delta, 'expected': expected, 'sidecars': [left_side, right_side]} + differences['expected_state'] = {'comparison': delta, 'expected': describe(expected), 'sidecars': [left_side, right_side]} if op == 'rejected_command': command = check_command(detail) eq.require(command['code'] != [4, 'OK'], 'nonchanging command not rejected') @@ -823,13 +1076,14 @@ def check_e45(raw, evidence, previous): expected_held = eq._transport(baseline['S' if target == 'create_snapshot' else 'latest'], None) eq.require(eq.exact(held, expected_held), 'E5 returned target binding') if direction == 'outside_in': - expected = copy.deepcopy(held) if location == 'dict': - n = node_at(expected, 'systems', 'runtime', 'id_generator', 'counters') - eq.require('__p28_eq_probe' not in fields(n), 'E5 marker already present') - n[1].append([[4, '__p28_eq_probe'], [2, 1]]) + def marked(n): + eq.require('__p28_eq_probe' not in fields(n), 'E5 marker already present') + return [n[0], n[1] + [[[4, '__p28_eq_probe'], [2, 1]]]] + n[2:] + expected = edit_at(held, ['systems', 'runtime', 'id_generator', 'counters'], marked) else: - node_at(expected, 'systems', 'runtime', 'domain_events', 'published_ids')[1].append([4, '__p28_eq_probe']) + expected = edit_at(held, ['systems', 'runtime', 'domain_events', 'published_ids'], + lambda n: [n[0], n[1] + [[4, '__p28_eq_probe']]] + n[2:]) eq.require(eq.exact(expected, after_held), 'E5 fixed nested mutation absent/extra') for layer in ('S', 'latest'): delta = eq.compare_trees(eq._transport(obs[1]['checkpoint'][layer], None), eq._transport(obs[2]['checkpoint'][layer], None)) @@ -846,7 +1100,7 @@ def check_e45(raw, evidence, previous): differences['held'] = delta if not delta['equal']: defects.append({'kind': 'internal_mutation_of_returned_value', 'comparison': delta}) for layer in ('K', 'C'): - differences['ownership_'+layer] = eq.compare_trees(obs[1][layer+'_tree']['tree'], obs[2][layer+'_tree']['tree']) + differences['ownership_'+layer] = eq.compare_trees(eq._transport(obs[1][layer+'_tree'], None), eq._transport(obs[2][layer+'_tree'], None)) eq.require(len(events) == counts(trees[-1])[0]['counter']-counts(trees[0])[0]['counter'], 'E5 uncapped event counter delta') outcomes.append({'id': scenario['id'], 'status': 'existing-defect' if defects else ('NA' if result['applicability'] == 'NA' else 'recorded'), 'defects': defects, 'differences': differences, 'signals': ev['signals'], diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py index 656558c..982e092 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py @@ -4,9 +4,12 @@ Paths are lists of ['key', key_node] / ['index', integer] segments. Transport validation is distinct from product acceptance (see IMPLEMENTATION.md). """ import base64 +from collections import OrderedDict import copy +import gzip import hashlib import json +from pathlib import Path import re @@ -22,6 +25,142 @@ OPT_PATHS = frozenset(('autoload/simulation_runtime.gd', 'scripts/core/simulatio 'scripts/world/path_service.gd', 'scripts/core/command_queue.gd')) SOURCE_SHA = '651f703621c8ed06a724709f75d8bc05b1eef2988c3b3d5d3466096a3f759bf0' TOKEN = '{PRIVATE_USER_ROOT}/' +PERMITTED_EVENTS = ('systems', 'runtime', 'domain_events', 'recent_events') + + +# S5 content-addressed trees (EQ_S5_SCALE_PLAN). The probe writes each large tree's exact +# JSON text, and raw S/K/C bytes, once as evidence/blobs/.gz; records keep +# {"blob": sha}. A blob is accepted only when its uncompressed bytes hash to its name. +# Godot's JSON text of a tree equals json_bytes(tree) (checked on the R fixture), so the +# blob name is the tree SHA. Trees loaded from blobs are shared and never mutated. +BLOB_ROOTS = [] +_BLOB_BYTES = OrderedDict() +_BLOB_TREES = OrderedDict() +_NORMALIZED = {} +# SHAs of blob trees that passed the full validate_tree. A matching SHA proves only the bytes, +# never the types; nothing from a blob is trusted before it is in this set, and a failed blob +# is never added, so it fails again on every use. +_VALID_BLOBS = set() +BYTES_CACHE, TREE_CACHE = 12, 8 +# S5: while a phase is checked, its profile root is set here so that each blob tree's +# normalization facts are derived once, when the tree is first parsed (no second parse). +ACTIVE_PROFILES = [] +PARSE_HOOKS = [] + + +class BlobList(list): + """A typed tree parsed from a verified blob; `sha` is the SHA of its JSON text.""" + __slots__ = ('sha',) + + def __deepcopy__(self, memo): + return copy.deepcopy(list(self), memo) + + +class TreeRef: + """An unparsed tree known by SHA: a blob, or a normalized blob tree. + + Equal SHA means equal JSON text, hence an equal tree. Content is parsed only on demand. + """ + __slots__ = ('sha', 'base', 'replacement') + + def __init__(self, sha, base=None, replacement=None): + self.sha, self.base, self.replacement = sha, base, replacement + + def __deepcopy__(self, memo): + return self + + def tree(self): + if self.base is None: + return blob_tree(self.sha) + tree = self.base.tree() if isinstance(self.base, TreeRef) else self.base + return _replace_events(tree, self.replacement) if self.replacement is not None else tree + + +def add_blob_root(path): + path = Path(path) + if path not in BLOB_ROOTS: + BLOB_ROOTS.append(path) + + +def reset_blobs(): + BLOB_ROOTS.clear(); _BLOB_BYTES.clear(); _BLOB_TREES.clear(); _NORMALIZED.clear(); _VALID_BLOBS.clear() + + +def blob_bytes(sha): + require(type(sha) is str and re.fullmatch('[0-9a-f]{64}', sha), 'blob SHA') + if sha in _BLOB_BYTES: + _BLOB_BYTES.move_to_end(sha) + return _BLOB_BYTES[sha] + found = [root / (sha + '.gz') for root in BLOB_ROOTS if (root / (sha + '.gz')).is_file()] + require(found, 'missing blob: ' + sha) + require(not found[0].is_symlink(), 'blob symlink') + try: + data = gzip.decompress(found[0].read_bytes()) + except (OSError, EOFError, ValueError) as exc: + raise EvidenceError('invalid blob gzip: ' + sha) from exc + require(hashlib.sha256(data).hexdigest() == sha, 'blob SHA mismatch: ' + sha) + _BLOB_BYTES[sha] = data + while len(_BLOB_BYTES) > BYTES_CACHE: + _BLOB_BYTES.popitem(last=False) + return data + + +def _reject(*_): + raise EvidenceError('typed tree JSON may contain only arrays, integers, strings and null') + + +def blob_tree(sha): + if sha in _BLOB_TREES: + _BLOB_TREES.move_to_end(sha) + return _BLOB_TREES[sha] + try: + value = json.loads(blob_bytes(sha).decode('utf-8'), parse_float=_reject, parse_constant=_reject, + object_pairs_hook=_reject) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise EvidenceError('invalid blob tree JSON: ' + sha) from exc + validate_tree(value) + tree = BlobList(value) + tree.sha = sha + _VALID_BLOBS.add(sha) + _BLOB_TREES[sha] = tree + while len(_BLOB_TREES) > TREE_CACHE: + _BLOB_TREES.popitem(last=False) + for profile_root in ACTIVE_PROFILES: + key = (sha, json_bytes(profile_root)) + if key not in _NORMALIZED: + fast = _fast_normalize(tree, blob_bytes(sha).decode('utf-8'), profile_root) + if fast is not None: + _NORMALIZED[key] = fast + for hook in PARSE_HOOKS: + hook(tree) + return tree + + +def known_sha(tree): + """SHA of a validated blob tree (or of a normalization of one); None otherwise.""" + if isinstance(tree, BlobList): + return tree.sha + if isinstance(tree, TreeRef): + base = tree.base if tree.base is not None else tree + if isinstance(base, TreeRef) and base.sha not in _VALID_BLOBS: + blob_tree(base.sha) + return tree.sha + return None + + +def materialize(tree): + return tree.tree() if isinstance(tree, TreeRef) else tree + + +def same_tree(a, b): + """Exact typed-tree equality. Validated trees hold only lists, ints, strings and None, + so Python equality is JSON equality; equal SHAs are a shortcut, never a requirement.""" + if a is b: + return True + sa, sb = known_sha(a), known_sha(b) + if sa is not None and sa == sb: + return True + return materialize(a) == materialize(b) def require(ok, message): @@ -33,62 +172,138 @@ def json_bytes(value): return json.dumps(value, ensure_ascii=False, separators=(',', ':'), allow_nan=False).encode('utf-8') +def _copy_tree(value): + """Exact copy of nested lists (typed trees hold only lists, ints, strings and None).""" + return [_copy_tree(v) if isinstance(v, list) else v for v in value] if isinstance(value, list) else copy.deepcopy(value) + + def exact(a, b): + if a is b: + return True + if known_sha(a) is not None or known_sha(b) is not None: + for value in (a, b): + if known_sha(value) is None: + try: + validate_tree(value) + except EvidenceError: + return False + return same_tree(a, b) return json_bytes(a) == json_bytes(b) +_BAD_TYPES = frozenset((23, 24, 25, 26)) +_SIZES = {5: 2, 6: 2, 7: 4, 8: 4, 9: 3, 10: 3, 11: 6, 12: 4, 13: 4, 14: 4, 15: 4, 16: 6, 17: 9, 18: 12, 19: 16, 20: 4} +_INTEGER_COMPONENTS = frozenset((6, 8, 10, 13)) +_PACKED = {30: 2, 31: 2, 32: 3, 33: 3, 34: 4} +_PACKED_VECTORS = {35: 5, 36: 9, 37: 20, 38: 12} +_HEX16 = re.compile('[0-9a-f]{16}').fullmatch +_INT_MIN, _INT_MAX = -(2**63), 2**63 + + +def _leaf(x): + """The node checks for an INT/FLOAT/STRING component, exactly as when it is popped.""" + if type(x) is not list or not x or type(x[0]) is not int: + raise EvidenceError('invalid typed node') + t = x[0] + if t in _BAD_TYPES or not 0 <= t <= 38: + raise EvidenceError('unsupported Variant type') + if len(x) != (1 if t == 0 else 3 if t in (27, 28) else 2): + raise EvidenceError('node arity') + p = x[1] + if t == 2: + if type(p) is not int or not _INT_MIN <= p < _INT_MAX: + raise EvidenceError('integer payload') + elif t == 3: + if type(p) is not str or not _HEX16(p): + raise EvidenceError('float must be f64 LE hex') + elif type(p) is not str: + raise EvidenceError('string payload') + + def validate_tree(node): - """Validate all nodes; no application depth cutoff or value coercion.""" + """Validate all nodes; no application depth cutoff or value coercion. + + A blob tree is used only after this full validation (its SHA is then recorded). + S5: the S1 checks, messages and order are kept; components are checked in place in the + same (last-in, first-out) order in which the S1 stack popped them.""" + if known_sha(node) is not None: + return node pending = [node] + pop, extend = pending.pop, pending.extend while pending: - n = pending.pop() - require(type(n) is list and n and type(n[0]) is int, 'invalid typed node') + n = pop() + if type(n) is not list or not n or type(n[0]) is not int: + raise EvidenceError('invalid typed node') t = n[0] - require(t not in (23, 24, 25, 26) and 0 <= t <= 38, 'unsupported Variant type') - require(len(n) == (1 if t == 0 else 3 if t in (27, 28) else 2), 'node arity') + if t in _BAD_TYPES or not 0 <= t <= 38: + raise EvidenceError('unsupported Variant type') + if len(n) != (1 if t == 0 else 3 if t == 27 or t == 28 else 2): + raise EvidenceError('node arity') if t == 0: continue p = n[1] - if t in (1, 2): - require(type(p) is int and (p in (0, 1) if t == 1 else -(2**63) <= p < 2**63), 'integer payload') + if t == 2: + if type(p) is not int or not _INT_MIN <= p < _INT_MAX: + raise EvidenceError('integer payload') + elif t == 27: + if type(p) is not list: + raise EvidenceError('dictionary entries') + seen = set() + for pair in p: + if type(pair) is not list or len(pair) != 2: + raise EvidenceError('dictionary pair') + k = pair[0] + # Simple key nodes compare as (type, payload); validated ints are never bool. + key = (k[0], k[1]) if type(k) is list and len(k) == 2 and type(k[1]) in (str, int) else json_bytes(k) + if key in seen: + raise EvidenceError('duplicate dictionary key') + seen.add(key) + extend(pair) + _typed_info(n[2], dictionary=True) + elif t == 4 or t == 21 or t == 22: + if type(p) is not str: + raise EvidenceError('string payload') + elif t == 28: + if type(p) is not list: + raise EvidenceError('array elements') + _typed_info(n[2], dictionary=False) + extend(p) elif t == 3: - require(type(p) is str and re.fullmatch('[0-9a-f]{16}', p), 'float must be f64 LE hex') - elif t in (4, 21, 22): - require(type(p) is str, 'string payload') + if type(p) is not str or not _HEX16(p): + raise EvidenceError('float must be f64 LE hex') + elif t == 1: + if type(p) is not int or p not in (0, 1): + raise EvidenceError('integer payload') elif t == 29: - require(type(p) is str, 'byte array payload') + if type(p) is not str: + raise EvidenceError('byte array payload') try: require(base64.b64encode(base64.b64decode(p, validate=True)).decode() == p, 'noncanonical base64') except (ValueError, TypeError) as exc: raise EvidenceError('invalid base64') from exc - elif t == 27: - require(type(p) is list, 'dictionary entries') - seen = set() - for pair in p: - require(type(pair) is list and len(pair) == 2, 'dictionary pair') - key = json_bytes(pair[0]) - require(key not in seen, 'duplicate dictionary key') - seen.add(key) - pending.extend(pair) - _typed_info(n[2], dictionary=True) - elif t == 28: - require(type(p) is list, 'array elements') - _typed_info(n[2], dictionary=False) - pending.extend(p) else: - require(type(p) is list, 'component nodes') - sizes = {5: 2, 6: 2, 7: 4, 8: 4, 9: 3, 10: 3, 11: 6, - 12: 4, 13: 4, 14: 4, 15: 4, 16: 6, 17: 9, 18: 12, 19: 16, 20: 4} - if t in sizes: - require(len(p) == sizes[t], 'component count') - integer = t in (6, 8, 10, 13) - require(all(x and x[0] == (2 if integer else 3) for x in p), 'component type') - elif t in (30, 31, 32, 33, 34): - expected = {30: 2, 31: 2, 32: 3, 33: 3, 34: 4}[t] - require(all(x and x[0] == expected for x in p), 'packed element type') + if type(p) is not list: + raise EvidenceError('component nodes') + if t in _SIZES: + if len(p) != _SIZES[t]: + raise EvidenceError('component count') + expected = 2 if t in _INTEGER_COMPONENTS else 3 + if not all(x and x[0] == expected for x in p): + raise EvidenceError('component type') + elif t in _PACKED: + expected = _PACKED[t] + if not all(x and x[0] == expected for x in p): + raise EvidenceError('packed element type') else: - require(all(x and x[0] == {35: 5, 36: 9, 37: 20, 38: 12}[t] for x in p), 'packed vector type') - pending.extend(p) + expected = _PACKED_VECTORS[t] + if not all(x and x[0] == expected for x in p): + raise EvidenceError('packed vector type') + extend(p) + continue + # Leaf components (types 2/3/4 by the check above): the S1 stack popped them + # last-first, before anything else, so they are checked here in that order. + for x in reversed(p): + _leaf(x) return node @@ -106,12 +321,14 @@ def _typed_info(info, dictionary): def tree_sha(tree): + if known_sha(tree) is not None: + return known_sha(tree) validate_tree(tree) return hashlib.sha256(json_bytes(tree)).hexdigest() def _key_segment(key): - return ['key', copy.deepcopy(key)] + return ['key', _copy_tree(key)] def _field(node, name): @@ -182,9 +399,37 @@ def apply_diff(tree, operations): return result +class _Checkpoints: + """S5 checkpoints are complete blob/inline records: verify and load one at a time.""" + def __init__(self, checkpoints): + self.checkpoints = checkpoints + for i in range(len(checkpoints)): + self[i] + + def __len__(self): + return len(self.checkpoints) + + def __getitem__(self, index): + cp = self.checkpoints[index] + s = _transport_ref(cp['S']) + latest = cp['latest'] + if latest.get('same_as_S') is True: + require(set(latest) == {'same_as_S', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'}, 'same_as_S fields') + _proof(latest, s) + require(latest['raw_bytes_sha'] == cp['S']['raw_bytes_sha'], 'same_as_S byte mismatch') + return s, (s if known_sha(s) is not None else copy.deepcopy(s)) + return s, _transport_ref(latest) + + def __iter__(self): + for i in range(len(self)): + yield self[i] + + def reconstruct_checkpoints(checkpoints): """Each checkpoint has S and latest transport records; verify before use.""" require(type(checkpoints) is list and checkpoints, 'missing checkpoints') + if not any('diff' in cp[name] for cp in checkpoints for name in ('S', 'latest') if type(cp.get(name)) is dict): + return _Checkpoints(checkpoints) result, previous = [], None for cp in checkpoints: s = _transport(cp['S'], previous) @@ -207,7 +452,29 @@ def _proof(record, tree): require(record.get('tree_sha') == tree_sha(tree), 'tree SHA mismatch') +def _blob_record(record): + require(set(record) == {'tree', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'}, 'transport fields') + require(type(record['tree']) is dict and set(record['tree']) == {'blob'}, 'blob reference fields') + require(record['tree']['blob'] == record['tree_sha'], 'blob/tree SHA binding') + require(record.get('roundtrip_ok') is True, 'Godot roundtrip missing/failed') + require(type(record.get('raw_bytes_sha')) is str and re.fullmatch('[0-9a-f]{64}', record['raw_bytes_sha']), 'raw bytes SHA') + blob_bytes(record['tree_sha']) + return record['tree_sha'] + + +def _transport_ref(record): + """Like _transport, but a validated blob tree is kept as a TreeRef (parsed on demand).""" + if type(record) is dict and type(record.get('tree')) is dict: + sha = _blob_record(record) + if sha not in _VALID_BLOBS: + blob_tree(sha) + return TreeRef(sha) + return _transport(record, None) + + def _transport(record, baseline): + if type(record) is dict and type(record.get('tree')) is dict: + return blob_tree(_blob_record(record)) require(('tree' in record) != ('diff' in record), 'tree/diff exclusive') require(set(record) == {'tree' if 'tree' in record else 'diff', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'}, 'transport fields') if 'tree' in record: @@ -221,34 +488,39 @@ def _transport(record, baseline): def compare_trees(a, b): validate_tree(a); validate_tree(b) + if same_tree(a, b): + return {'equal': True, 'exact': True, 'differences': [], 'dict_order_paths': []} + a, b = materialize(a), materialize(b) differences, orders = [], [] + # Recorded nodes are shared with the compared trees, which are never mutated. def diff(path, x, y): - differences.append({'path': copy.deepcopy(path), 'a': copy.deepcopy(x), 'b': copy.deepcopy(y)}) + differences.append({'path': path, 'a': x, 'b': y}) def walk(x, y, path): - if exact(x, y): + # Both sides are validated typed trees, so == is exact JSON equality. + if x == y: return if x[0] != y[0] or len(x) != len(y): diff(path, x, y); return t = x[0] - if t in (27, 28) and not exact(x[2], y[2]): + if t in (27, 28) and x[2] != y[2]: diff(path + [['metadata', 0]], x[2], y[2]) if t == 27: def keyed(n): d = {} for k, v in n[1]: - text = _key_text(k) + text = k[1] if k[0] in (4, 21, 22) else _key_text(k) require(text not in d, 'duplicate textual dictionary key') d[text] = (k, v) return d dx, dy = keyed(x), keyed(y) - if set(dx) == set(dy) and [_key_text(k) for k, _ in x[1]] != [_key_text(k) for k, _ in y[1]]: - orders.append(copy.deepcopy(path)) + if set(dx) == set(dy) and list(dx) != list(dy): + orders.append(_copy_tree(path)) for key in sorted(set(dx) | set(dy)): if key not in dx or key not in dy: diff(path + [['key', [4, key]]], dx.get(key), dy.get(key)); continue kx, vx = dx[key]; ky, vy = dy[key] p = path + [_key_segment(kx)] - if not exact(kx, ky): + if kx != ky: diff(p + [['key_type', 0]], kx, ky) walk(vx, vy, p) elif t == 28 or t in range(5, 21) or t in range(30, 39): @@ -260,7 +532,40 @@ def compare_trees(a, b): else: diff(path, x, y) walk(a, b, []) - return {'equal': not differences, 'exact': exact(a, b), 'differences': differences, 'dict_order_paths': orders} + return {'equal': not differences, 'exact': a == b, 'differences': differences, 'dict_order_paths': orders} + + +HARNESS_LABEL = 'normalized-sha256:' +_CANONICAL = {} + + +def harness_digest(normalized): + """Labelled harness digest: SHA-256 of the exact normalized JSON (dict order kept). + compare_reports falls back to canonical_digest when two labelled digests differ.""" + return HARNESS_LABEL + tree_sha(normalized) + + +def canonical_digest(tree): + """Harness-only typed canonical digest; only Dictionary enumeration order is + canonicalized (key nodes, scalars, metadata and Array order stay exact). The JSON is the + S1 form, built without per-node copies; components and packed arrays hold no Dictionary.""" + known = known_sha(tree) + if known is not None and known in _CANONICAL: + return _CANONICAL[known] + validate_tree(tree) + def canonical(node): + t = node[0] + if t == 27: + keyed = sorted([(k[1] if k[0] in (4, 21, 22) else _key_text(k), k, v) for k, v in node[1]], key=lambda row: row[0]) + require(len({row[0] for row in keyed}) == len(keyed), 'digest textual key collision') + return [27, [[k, canonical(v)] for _, k, v in keyed], node[2]] + if t == 28: + return [28, [canonical(v) for v in node[1]], node[2]] + return node + digest = hashlib.sha256(json_bytes(canonical(materialize(tree)))).hexdigest() + if known is not None: + _CANONICAL[known] = digest + return digest def _key_text(key): @@ -295,7 +600,7 @@ def _marker(key, node): def _normalize(trees, profile_root, events): root = _profile(profile_root) - result = copy.deepcopy(trees) + result = _copy_tree(trees) side = {'entries': [], 'violations': [], 'profile_root': {**profile_root, 'matches_guard': True}} def visit(n, path, event_index, permitted_payload=False, event_root=False): if n[0] == 27: @@ -376,17 +681,119 @@ def normalize_events(events, profile_root): return _normalize(events, profile_root, True) +def _path_nodes(tree): + """Return [(key, dict node)] along systems.runtime.domain_events and the events node.""" + nodes, n = [], tree + for name in PERMITTED_EVENTS: + if n[0] != 27: + return None, None + found = [(k, v) for k, v in n[1] if k[0] in (4, 21) and k[1] == name] + if len(found) != 1: + return None, None + nodes.append((found[0][0], n)) + n = found[0][1] + return nodes, n + + +def _skeleton(nodes, events): + """The four permitted dictionaries, each keeping only the permitted key.""" + child = events + for key, node in reversed(nodes): + child = [27, [[key, child]], node[2]] + return child + + +def _replace_events(tree, events): + nodes, _ = _path_nodes(tree) + require(nodes is not None, 'events path missing') + child = events + for key, node in reversed(nodes): + child = [27, [[k, child] if k is key else [k, v] for k, v in node[1]], node[2]] + return child + + +def _fast_normalize(tree, text, profile_root): + """Exact result of normalize_tree when every F1-F3 candidate lies in recent_events. + + The original closed policy runs unchanged on a skeleton holding only the permitted + path; the remaining text is proven free of the profile root, the reserved token, + _us/_ms names and save_service. Returns None to request the full original walk. + """ + root = _profile(profile_root) + if not (root.isascii() and root.isprintable()) or any(c in root for c in '[]",\\{}') or TOKEN in text: + return None + nodes, events = _path_nodes(tree) + tokens = (root, '_us"', '_ms"', 'save_service') + side_profile = {**profile_root, 'matches_guard': True} + if not any(t in text for t in tokens): + if events is not None: + require(events[0] == 28, 'recent_events array required') + return 'identity', None, {'entries': [], 'violations': [], 'profile_root': side_profile}, None, None, None + if events is None or text.count('"recent_events"]') != 1: + return None + marker = '"recent_events"],' + start = text.find(marker) + if start < 0: + return None + start += len(marker) + try: + value, end = json.JSONDecoder().raw_decode(text, start) + except json.JSONDecodeError: + return None + if value != events or any(text.count(t, 0, start) or text.count(t, end) for t in tokens): + return None + normalized, side = _normalize([_skeleton(nodes, events)], profile_root, False) + _, new_events = _path_nodes(normalized[0]) + spliced = (text[:start] + json_bytes(new_events).decode('utf-8') + text[end:]).encode('utf-8') + # The skeleton keys/metadata and the original events let restore run without a parse. + kept = [(key, [27, [], node[2]]) for key, node in nodes] + return 'normalized', new_events, side, hashlib.sha256(spliced).hexdigest(), kept, events + + def normalize_tree(tree, profile_root): - normalized, side = _normalize([tree], profile_root, False) + sha = known_sha(tree) + if sha is not None and not (isinstance(tree, TreeRef) and tree.base is not None): + key = (sha, json_bytes(profile_root)) + if key not in _NORMALIZED: + fast = _fast_normalize(materialize(tree), blob_bytes(sha).decode('utf-8'), profile_root) + if fast is None: + # Candidates outside recent_events: the original full walk, not memoized. + normalized, side = _normalize([materialize(tree)], profile_root, False) + return normalized[0], side + _NORMALIZED[key] = fast + kind, events, side, normalized_sha = _NORMALIZED[key][:4] + base = tree if isinstance(tree, TreeRef) else TreeRef(sha) + if kind == 'identity': + return base, copy.deepcopy(side) + return TreeRef(normalized_sha, base, events), copy.deepcopy(side) + normalized, side = _normalize([materialize(tree)], profile_root, False) return normalized[0], side +def adopt(tree): + """Give a large constructed tree a SHA-backed text so the exact fast paths apply.""" + validate_tree(tree) + text = json_bytes(tree) + sha = hashlib.sha256(text).hexdigest() + if sha not in _BLOB_BYTES: + _BLOB_BYTES[sha] = text + while len(_BLOB_BYTES) > BYTES_CACHE: + _BLOB_BYTES.popitem(last=False) + result = BlobList(tree) + result.sha = sha + _VALID_BLOBS.add(sha) + _BLOB_TREES[sha] = result + while len(_BLOB_TREES) > TREE_CACHE: + _BLOB_TREES.popitem(last=False) + return result + + def _restore(normalized, sidecar, profile_root, events): root = _profile(profile_root) require(type(sidecar) is dict and set(sidecar) == {'entries', 'violations', 'profile_root'}, 'sidecar fields') require(sidecar['profile_root'] == {**profile_root, 'matches_guard': True}, 'sidecar profile') require(type(sidecar['entries']) is list and type(sidecar['violations']) is list, 'sidecar lists') - result = copy.deepcopy(normalized) + result = _copy_tree(normalized) seen = set() for entry in sidecar['entries']: require(set(entry) == {'event_index', 'path', 'key', 'original_node'} | ({'root'} if entry.get('key') == 'message' else set()), 'entry fields') @@ -418,6 +825,20 @@ def restore_events(normalized, sidecar, profile_root): def restore_tree(normalized, sidecar, profile_root): + if isinstance(normalized, TreeRef): + base = normalized.base if normalized.base is not None else normalized + expected, expected_side = normalize_tree(base, profile_root) + require(isinstance(expected, TreeRef) and expected.sha == normalized.sha, 'sidecar does not match normalized node') + if normalized.base is None: + require(exact(sidecar, expected_side) and not sidecar['entries'] and not sidecar['violations'], + 'non-reversible/unauthorized sidecar') + return normalized + # The original reversible restore runs on the skeleton; the rest is shared. + nodes, events = _NORMALIZED[(known_sha(base), json_bytes(profile_root))][4:] + restored = _restore(_skeleton(nodes, normalized.replacement), sidecar, profile_root, False) + _, restored_events = _path_nodes(restored) + require(restored_events == events, 'non-reversible/unauthorized sidecar') + return base return _restore(normalized, sidecar, profile_root, False) @@ -471,7 +892,7 @@ def compare_reports(a, b): require(tree_sha(restore_tree(ny, sy, profiles[1])) == tree_sha(y), 'b restore SHA') sidecars(path, sx, sy) comparison(path, nx, ny) - return sx, sy + return sx, sy, nx, ny def comparison(path, x, y): c = compare_trees(x, y) @@ -523,9 +944,19 @@ def compare_reports(a, b): for field in ('catalog_instance_id', 'current_catalog_instance_id'): excluded(path + ['C', field], 'process-local instance ID', x['C'][field], y['C'][field]) + def described(x, side): + """S5 joined facts record a large tree by SHA; rebuild it from its verified blob.""" + tree = TreeRef(x['source_tree_sha']) + return materialize(normalize_tree(tree, profiles[side])[0] if x['normalized'] else tree) + def scenario_facts(path, x, y): # Joined S3b facts carry lossless typed trees as well as JSON metadata. # Apply the same dictionary-order-only fallback to returned values. + descriptor = {'typed_tree_sha', 'source_tree_sha', 'normalized'} + if isinstance(x, dict) and isinstance(y, dict) and set(x) == set(y) == descriptor: + if x['typed_tree_sha'] != y['typed_tree_sha']: + comparison(path, described(x, 0), described(y, 1)) + return if isinstance(x, list) and isinstance(y, list) and x and y and type(x[0]) is int and type(y[0]) is int: validate_tree(x); validate_tree(y) comparison(path, x, y) @@ -563,7 +994,7 @@ def compare_reports(a, b): if main: require(x['minute'] == xs[0]['minute']+i and y['minute'] == ys[0]['minute']+i, 'checkpoint cadence') trees(p + ['S'], tx[i][0], ty[i][0], profiles) - sx, sy = trees(p + ['latest'], tx[i][1], ty[i][1], profiles) + sx, sy, nlx, nly = trees(p + ['latest'], tx[i][1], ty[i][1], profiles) if ownership and i == 2: record(p + ['digest_self_consistent'], x['latest_digest'] == x['computed_latest_digest'], y['latest_digest'] == y['computed_latest_digest']) if sx['entries'] or sy['entries']: @@ -572,7 +1003,12 @@ def compare_reports(a, b): record(p + ['computed_latest_digest'], x['computed_latest_digest'], y['computed_latest_digest']) if sx['entries'] or sy['entries']: require(type(x['harness_normalized_digest']) is str and type(y['harness_normalized_digest']) is str, 'missing harness normalized digest') - record(p + ['harness_normalized_digest'], x['harness_normalized_digest'], y['harness_normalized_digest']) + hx, hy = x['harness_normalized_digest'], y['harness_normalized_digest'] + if hx != hy and all(h.startswith(HARNESS_LABEL) for h in (hx, hy)) and canonical_digest(nlx) == canonical_digest(nly): + # Exact normalized JSON differs only by Dictionary order (paths recorded above). + out['digest_modes'].append({'path': p, 'mode': 'harness canonical digest (dictionary order only)'}) + else: + record(p + ['harness_normalized_digest'], hx, hy) excluded(p + ['latest_digest'], 'latest snapshot contains F1-F3 environment values', x['latest_digest'], y['latest_digest']) out['digest_modes'].append({'path': p, 'mode': 'harness normalized digest'}) else: diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/eq_probe.gd b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/eq_probe.gd index 0fac954..89a46ba 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/eq_probe.gd +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/eq_probe.gd @@ -1,5 +1,8 @@ extends SceneTree -## S3b external guarded probe. Native admission remains S4/Root. +## S5 external guarded probe. Native admission remains Root. +## Large trees, raw S/K/C bytes and package files are content-addressed gzip blobs +## (evidence/blobs/.gz), written once; records keep {"blob": sha}. R and O both run E1-E5. +const INLINE_LIMIT: int = 65536 const FIXTURE_SHA: String = "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd" const K_FIELDS: Array = ["absolute_minute", "speed", "paused", "snapshot_version", "_skip_next_process", "_pause_request_serial", "_abort_current_tick_requested", "_last_tick_advanced"] var clock: Variant @@ -7,8 +10,12 @@ var runtime: Variant var main: Variant var role: String var report: Dictionary = {} -var lifecycle: Dictionary = {"stage": "S3b", "errors": [], "preparation": [], "files": []} -var previous_tree: Array = [] +var lifecycle: Dictionary = {"stage": "S5", "errors": [], "preparation": [], "files": []} +var blob_index: Dictionary = {} +var transport_cache: Dictionary = {} +var last_snapshot_value: Dictionary = {} +var subscribed_stream: Variant = null +var known_event_ids: Dictionary = {} var finished: bool = false var bound: bool = false var recording: bool = false @@ -44,11 +51,18 @@ func f64(value: float) -> String: bytes.encode_double(0, value) return bytes.hex_encode() +# get_typed_script() returns a null Object for unscripted typed containers; it is +# transported as NIL. A scripted element type is not supported and fails loudly. +func typed_script(script: Variant) -> Array: + if script == null: return [0] + demand(false, "unsupported typed script metadata") + return [] + func typed_info(value: Variant) -> Variant: if typeof(value) == TYPE_ARRAY and value.is_typed(): - return [encode(value.get_typed_builtin()), encode(value.get_typed_class_name()), encode(value.get_typed_script())] + return [encode(value.get_typed_builtin()), encode(value.get_typed_class_name()), typed_script(value.get_typed_script())] if typeof(value) == TYPE_DICTIONARY and value.is_typed(): - return [[encode(value.get_typed_key_builtin()), encode(value.get_typed_key_class_name()), encode(value.get_typed_key_script())], [encode(value.get_typed_value_builtin()), encode(value.get_typed_value_class_name()), encode(value.get_typed_value_script())]] + return [[encode(value.get_typed_key_builtin()), encode(value.get_typed_key_class_name()), typed_script(value.get_typed_key_script())], [encode(value.get_typed_value_builtin()), encode(value.get_typed_value_class_name()), typed_script(value.get_typed_value_script())]] return null func encode(value: Variant) -> Array: @@ -160,61 +174,44 @@ func decode(node: Array) -> Variant: func decoded_basis(v: Array) -> Basis: return Basis(Vector3(v[0], v[1], v[2]), Vector3(v[3], v[4], v[5]), Vector3(v[6], v[7], v[8])) +# Write each distinct content once; the name is the SHA-256 of the uncompressed bytes. +func store_blob(bytes: PackedByteArray) -> Dictionary: + var digest: String = hashed(bytes) + if blob_index.has(digest): return {"blob": digest} + if not demand(bound, "blob write before guard binding"): return {"blob": digest} + var directory: String = "user://evidence/blobs" + var mkdir: int = DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(directory)) + if not demand(mkdir == OK, "blob mkdir failed"): return {"blob": digest} + var path: String = directory.path_join(digest + ".gz") + if not demand(not FileAccess.file_exists(path), "refuse blob overwrite: " + digest): return {"blob": digest} + var packed: PackedByteArray = bytes.compress(FileAccess.COMPRESSION_GZIP) + if not demand(packed.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP) == bytes, "blob gzip roundtrip failed: " + digest): return {"blob": digest} + var file: FileAccess = FileAccess.open(path, FileAccess.WRITE) + if not demand(file != null, "blob open failed: " + digest): return {"blob": digest} + file.store_buffer(packed) + file.flush() + var error: int = file.get_error() + file.close() + if not demand(error == OK, "blob write failed: " + digest): return {"blob": digest} + blob_index[digest] = {"bytes": bytes.size(), "gz_bytes": packed.size(), "gz_sha256": hashed(packed)} + return {"blob": digest} + +# var_to_bytes distinguishes typed metadata, String/StringName, int/float and key order, +# so equal bytes mean an equal value and an equal tree; the proof is reused for them. func transport(value: Variant) -> Dictionary: - var tree: Array = encode(value) var raw: PackedByteArray = var_to_bytes(value) + var raw_sha: String = hashed(raw) + if transport_cache.has(raw_sha): return transport_cache[raw_sha].duplicate(true) + var tree: Array = encode(value) var rebuilt: PackedByteArray = var_to_bytes(decode(tree)) var ok: bool = raw == rebuilt demand(ok, "Godot typed tree byte roundtrip failed") - return {"tree": tree, "tree_sha": hashed(json_text(tree).to_utf8_buffer()), "raw_bytes_sha": hashed(raw), "roundtrip_ok": ok} - -func tree_diff(before: Array, after: Array, path: Array = []) -> Array: - if json_text(before) == json_text(after): return [] - # Exact subtree replacement also preserves changes of insertion order and metadata. - if before[0] != after[0] or before[0] not in [TYPE_DICTIONARY, TYPE_ARRAY] or json_text(before[2]) != json_text(after[2]): - return [{"op": "set", "path": path, "node": after}] - var changes: Array = [] - if before[0] == TYPE_DICTIONARY: - var old_keys: Array = [] - var new_keys: Array = [] - for pair: Array in before[1]: old_keys.append(pair[0]) - for pair: Array in after[1]: new_keys.append(pair[0]) - if json_text(old_keys) != json_text(new_keys): return [{"op": "set", "path": path, "node": after}] - for i: int in range(before[1].size()): - changes.append_array(tree_diff(before[1][i][1], after[1][i][1], path + [["key", after[1][i][0]]])) - else: - if before[1].size() != after[1].size(): - return [{"op": "array_splice", "path": path, "start": 0, "delete_count": before[1].size(), "nodes": after[1]}] - for i: int in range(before[1].size()): changes.append_array(tree_diff(before[1][i], after[1][i], path + [["index", i]])) - return changes - -func tree_at(tree: Array, path: Array) -> Array: - var node: Array = tree - for segment: Array in path: - if segment[0] == "index": node = node[1][segment[1]] - else: - var found: bool = false - for pair: Array in node[1]: - if json_text(pair[0]) == json_text(segment[1]): - node = pair[1] - found = true - break - demand(found, "diff path missing") - return node - -func diff_record(record: Dictionary, baseline: Array) -> Dictionary: - var operations: Array = tree_diff(baseline, record.tree) - var replay: Array = baseline.duplicate(true) - for op: Dictionary in operations: - var target: Array = tree_at(replay, op.path) - if op.op == "set": - target.clear() - target.append_array(op.node.duplicate(true)) - elif op.op == "array_splice": - for unused: int in range(op.delete_count): target[1].remove_at(op.start) - for i: int in range(op.nodes.size()): target[1].insert(op.start + i, op.nodes[i].duplicate(true)) - demand(json_text(replay) == json_text(record.tree), "diff roundtrip mismatch") - return {"diff": operations, "tree_sha": record.tree_sha, "raw_bytes_sha": record.raw_bytes_sha, "roundtrip_ok": record.roundtrip_ok} + var text: PackedByteArray = json_text(tree).to_utf8_buffer() + if text.size() <= INLINE_LIMIT: + return {"tree": tree, "tree_sha": hashed(text), "raw_bytes_sha": raw_sha, "roundtrip_ok": ok} + var record: Dictionary = {"tree": store_blob(text), "tree_sha": hashed(text), "raw_bytes_sha": raw_sha, "roundtrip_ok": ok} + transport_cache[raw_sha] = record + return record.duplicate(true) func kernel_state() -> Dictionary: var result: Dictionary = {} @@ -266,21 +263,21 @@ func witness(value: Dictionary) -> Dictionary: return {"S": hashed(value.s_bytes), "K": hashed(value.k_bytes), "C": hashed(value.c_bytes)} func raw_layers(value: Dictionary) -> Dictionary: - return {"sha": witness(value), "S": Marshalls.raw_to_base64(value.s_bytes), "K": Marshalls.raw_to_base64(value.k_bytes), "C": Marshalls.raw_to_base64(value.c_bytes), "kernel": value.K, "cache": value.C} + return {"sha": witness(value), "S": store_blob(value.s_bytes), "K": store_blob(value.k_bytes), "C": store_blob(value.c_bytes), "kernel": value.K, "cache": value.C} func checkpoint() -> void: var before: Dictionary = layers() - var s: Dictionary = transport(runtime.create_snapshot()) + var snapshot_value: Dictionary = runtime.create_snapshot() + var s: Dictionary = transport(snapshot_value) var latest_value: Dictionary = runtime.get("latest_snapshot") - var latest: Dictionary = transport(latest_value) var latest_bytes: PackedByteArray = var_to_bytes(latest_value) var digest: String = runtime.get("latest_digest") var computed: String = SimulationDigest.compute(latest_value) - var current_tree: Array = s.tree - if not previous_tree.is_empty(): s = diff_record(s, previous_tree) + # Every checkpoint keeps a complete tree (blob reference); no diff chain. + var latest: Dictionary = {} if latest_bytes == before.s_bytes: - latest = {"same_as_S": true, "tree_sha": latest.tree_sha, "raw_bytes_sha": latest.raw_bytes_sha, "roundtrip_ok": latest.roundtrip_ok} - else: latest = diff_record(latest, current_tree) + latest = {"same_as_S": true, "tree_sha": s.tree_sha, "raw_bytes_sha": s.raw_bytes_sha, "roundtrip_ok": s.roundtrip_ok} + else: latest = transport(latest_value) var after: Dictionary = layers() demand(before.s_bytes == after.s_bytes and before.k_bytes == after.k_bytes and before.c_bytes == after.c_bytes, "observation changed full S/K/C") demand(digest == computed, "latest digest self-consistency") @@ -291,12 +288,13 @@ func checkpoint() -> void: write_evidence("checkpoint-%02d.json" % index, cp) write_evidence("observation-%02d.json" % index, {"before": raw_layers(before), "after": raw_layers(after)}) report.checkpoints.append(cp) - previous_tree = current_tree + last_snapshot_value = snapshot_value func event_counts() -> Dictionary: return {"counter": runtime.id_generator.get_counter(&"domain_event"), "published_ids_count": runtime.domain_events.get("_published_ids").size()} func observe_event(event: Variant) -> void: + known_event_ids[str(event.event_id)] = true if scenario_active: scenario_events.append(transport(event.to_dictionary())) return @@ -307,6 +305,28 @@ func observe_event(event: Variant) -> void: "entity_ids": {"type": typeof(event.entity_ids), "typed_info": typed_info(event.entity_ids)}, "payload": {"type": typeof(event.payload), "typed_info": typed_info(event.payload)}, "causation_command_id": typeof(event.causation_command_id)}}) +# A successful product restore replaces runtime.domain_events with a new stream that does +# not carry subscribers. Record that fact, recover from the live stream's recent_events() +# the events the observer could not receive, and subscribe to the live stream again. +func follow_event_stream() -> Dictionary: + var current: Variant = runtime.domain_events + if current == subscribed_stream: return {"replaced": false} + var subscribers: Array = [] + var all_subscribers: Dictionary = current.get("_subscribers") + for event_type: Variant in all_subscribers: + for id: Variant in all_subscribers[event_type]: subscribers.append(str(event_type) + "/" + str(id)) + subscribers.sort_custom(func(a: Variant, b: Variant) -> bool: return str(a) < str(b)) + var recovered: Array = [] + for event: Variant in current.recent_events(): + if known_event_ids.has(str(event.event_id)): continue + recovered.append(str(event.event_id)) + observe_event(event) + demand(current.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard resubscription failed") + subscribed_stream = current + var fact: Dictionary = {"replaced": true, "new_stream_subscribers_before": subscribers, "recovered_event_ids": recovered} + lifecycle.event_stream_replacements.append(fact.duplicate(true)) + return fact + func observe_snapshot(_snapshot: Dictionary, _digest: String) -> void: snapshot_reference = _snapshot record_signal("snapshot_published") @@ -315,8 +335,11 @@ func observe_state(_snapshot: Dictionary) -> void: state_reference = _snapshot record_signal("state_updated") +# The domain_event counter at emission places each signal in the event order, so every +# publish can be bound to its cause (tick end, successful restore, completed command). func record_signal(name: String) -> void: - var row: Dictionary = {"signal": name, "minute": clock.absolute_minute} + var row: Dictionary = {"signal": name, "minute": clock.absolute_minute, "event_counter": runtime.id_generator.get_counter(&"domain_event"), + "command_counter": runtime.get("_next_command_id")} if scenario_active: scenario_signals.append(row) elif recording: report.signal_order.append(row) else: warm_signals.append(row) @@ -352,21 +375,24 @@ func bind_profile() -> bool: var path: String = OS.get_environment("THE_TREE_PACK_PROFILE_REPORT") var file: FileAccess = FileAccess.open(path, FileAccess.READ) if not demand(file != null, "guard receipt missing"): return false - var receipt: Variant = JSON.parse_string(file.get_as_text()) + # Godot parses JSON numbers as float; the exact receipt text is retained for binding. + var receipt_text: String = file.get_as_text() + var receipt: Variant = JSON.parse_string(receipt_text) file.close() if not demand(receipt is Dictionary and receipt.get("verified") == true, "guard receipt unverified"): return false var actual: Variant = receipt.get("actual_user_data_dir") var live_root: String = ProjectSettings.globalize_path("user://").simplify_path() if not demand(actual is String and actual == live_root and OS.get_user_data_dir() == actual, "live profile differs from guard actual_user_data_dir"): return false report["profile_root"] = {"root": live_root, "actual_user_data_dir": actual} - lifecycle["guard"] = {"path": path, "sha256": FileAccess.get_sha256(path), "receipt": receipt, "live_root": live_root} + lifecycle["guard"] = {"path": path, "sha256": FileAccess.get_sha256(path), "receipt": receipt, "receipt_text": receipt_text, "live_root": live_root} bound = true return true func write_evidence(name: String, value: Variant) -> void: if not demand(bound, "evidence write before guard binding"): return var directory: String = "user://evidence" - var mkdir: int = DirAccess.make_dir_recursive_absolute(directory) + # The guard switches the user dir at runtime; globalize first, as the RESTART helpers do. + var mkdir: int = DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(directory)) if not demand(mkdir == OK, "evidence mkdir failed"): return var path: String = directory.path_join(name) if not demand(not FileAccess.file_exists(path), "refuse evidence overwrite: " + name): return @@ -434,6 +460,8 @@ func begin() -> void: runtime.snapshot_published.connect(observe_snapshot) runtime.state_updated.connect(observe_state) demand(runtime.domain_events.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard subscription failed") + subscribed_stream = runtime.domain_events + lifecycle["event_stream_replacements"] = [] connected = true lifecycle["observer_before"] = initial_inventory lifecycle["observer_connected"] = inventory() @@ -448,7 +476,9 @@ func begin() -> void: report.event_start = event_counts() recording = true checkpoint() - b1_snapshot = decode(report.checkpoints[0].S.tree).duplicate(true) + # The B1 value itself; its transport proves decode(tree) has the same bytes. + b1_snapshot = last_snapshot_value.duplicate(true) + for event_id: Variant in b1_snapshot[&"systems"][&"runtime"][&"domain_events"]["published_ids"]: known_event_ids[str(event_id)] = true # Stage 4: B1 and 30 exact checkpoints, uncapped events through the last frame. for unused: int in range(30): if not lifecycle.errors.is_empty(): break @@ -458,9 +488,10 @@ func begin() -> void: recording = false # Stage 5: E3 dedicated intervals, after measured event/signal closure. lifecycle["outside"] = raw_layers(layers()) - run_e3() - run_e4() - run_e5() + # Any harness error already fails the run; stop before further expensive scenarios. + if lifecycle.errors.is_empty(): run_e3() + if lifecycle.errors.is_empty(): run_e4() + if lifecycle.errors.is_empty(): run_e5() finish() func finish() -> void: @@ -474,10 +505,11 @@ func finish() -> void: lifecycle["observer_disconnected"] = inventory() demand(json_text(initial_inventory) == json_text(lifecycle.observer_disconnected), "observer inventory not restored") lifecycle["warm_signals"] = warm_signals - lifecycle["stop_reason"] = "S3b_complete_S4_pending" if lifecycle.errors.is_empty() else "failed" + lifecycle["stop_reason"] = "S5_complete" if lifecycle.errors.is_empty() else "failed" if bound: write_evidence("events.json", report.events) write_evidence("report.json", report) + lifecycle["blobs"] = blob_index.duplicate(true) write_evidence("lifecycle.json", lifecycle.duplicate(true)) print("P28_02_EQ_REPORT=" + json_text(report)) if is_instance_valid(main): main.ui_audio_window_close_drained.connect(audio_close, CONNECT_ONE_SHOT) @@ -525,7 +557,7 @@ func e3_package(path: String) -> Dictionary: if not demand(file != null, "package evidence open"): continue var bytes: PackedByteArray = file.get_buffer(file.get_length()) file.close() - files[name] = {"bytes": Marshalls.raw_to_base64(bytes), "sha256": hashed(bytes)} + files[name] = {"bytes": store_blob(bytes), "sha256": hashed(bytes)} return files func e3_prepare_package(path: String, input: Dictionary, kind: String) -> Dictionary: @@ -541,15 +573,27 @@ func e3_prepare_package(path: String, input: Dictionary, kind: String) -> Dictio var manifest_path: String = path.path_join("manifest.json") var file: FileAccess = FileAccess.open(manifest_path, FileAccess.READ) if not demand(file != null, "manifest read"): return info - var manifest: Dictionary = JSON.parse_string(file.get_as_text()) + var text: String = file.get_as_text() file.close() - match kind: - "missing_key": manifest.erase("checksum_world") - "wrong_type": manifest["format_version"] = "x" - "out_of_range": manifest["format_version"] = 2147483647 + # Edit only the one key line. Re-serializing a parsed manifest would turn every + # JSON integer into a float (Godot parses numbers as float), changing other fields. + var key: String = "checksum_world" if kind == "missing_key" else "format_version" + var prefix: String = "\t\"" + key + "\": " + var lines: PackedStringArray = PackedStringArray() + var found: int = 0 + for line: String in text.split("\n"): + if line.begins_with(prefix): + found += 1 + var comma: String = "," if line.ends_with(",") else "" + if kind == "missing_key": + demand(comma == ",", "removed manifest key must not be last") + continue + line = prefix + ("\"x\"" if kind == "wrong_type" else "2147483647") + comma + lines.append(line) + if not demand(found == 1, "manifest key line: " + key): return info file = FileAccess.open(manifest_path, FileAccess.WRITE) if not demand(file != null, "manifest write"): return info - file.store_string(json_text(manifest)) + file.store_string("\n".join(lines)) file.close() elif kind == "tampered_world": var world_path: String = path.path_join("world.bin") @@ -615,6 +659,7 @@ func run_e3() -> void: "restore_snapshot": actual = runtime.restore_snapshot(input) "validate": actual = SimulationSnapshot.validate(input) "load_from_path": actual = runtime.load_from_path(path) + var stream_fact: Dictionary = follow_event_stream() if package != null: package["after_operation"] = e3_package(path) var after: Dictionary = e3_observation() scenario_active = false @@ -650,13 +695,17 @@ func run_e3() -> void: var evidence: Dictionary = {"id": id, "begin": before, "invocation_before": invocation_before, "end": after, "input": transport(input), "return": transport(actual), "package": package, "events": scenario_events, "signals": scenario_signals.duplicate(true), "latest_save_operation_result": transport(runtime.get("latest_save_operation_result")), - "save_budget_before": transport(budget_before), "save_budget_after": transport(budget_after), "boundaries": ["begin", "operation", "end"]} + "save_budget_before": transport(budget_before), "save_budget_after": transport(budget_after), "boundaries": ["begin", "operation", "end"], + "event_stream": stream_fact} report.scenarios.append(scenario) lifecycle.e3.append(evidence) write_evidence("e3-%02d.json" % (report.scenarios.size() - 1), evidence) # S3b shares S3a's complete observation and transport; no state repair. func e45_emit(id: String, operation: String, observations: Array, detail: Dictionary) -> void: + # The returned value itself; a large return is stored as a blob and is not decoded here. + var returned_value: Variant = detail.get("_return_value") + detail.erase("_return_value") detail["signals"] = scenario_signals.duplicate(true) var cps: Array = [] for observation: Dictionary in observations: cps.append(observation.checkpoint) @@ -666,7 +715,7 @@ func e45_emit(id: String, operation: String, observations: Array, detail: Dictio var facts: Dictionary = detail.duplicate(true) for key: String in ["package_before", "package_after", "save_return", "return", "save_budget_before", "save_budget_after", "latest_save_operation_result", "observation_before", "observation_after"]: facts.erase(key) if detail.has("return"): - var actual: Variant = decode(detail["return"].tree) + var actual: Variant = returned_value facts["return_fact"] = {"ok": actual.get(&"ok"), "code": actual.get(&"code")} if actual is Dictionary else actual if facts.has("trials"): for trial: Dictionary in facts.trials: @@ -738,7 +787,10 @@ func run_e4() -> void: elif operation == "restore_snapshot": var input: Dictionary = runtime.create_snapshot().duplicate(true) detail["input"] = transport(input) - detail["return"] = transport(runtime.restore_snapshot(input)) + var restore_return: Variant = runtime.restore_snapshot(input) + detail["event_stream"] = follow_event_stream() + detail["return"] = transport(restore_return) + detail["_return_value"] = restore_return elif operation == "load_from_path": var input: Dictionary = runtime.create_snapshot().duplicate(true) var path: String = "user://p28_eq/" + role.to_lower() + "/good" @@ -748,7 +800,10 @@ func run_e4() -> void: detail["save_return"] = transport(saved) detail["package_before"] = e3_package(path) demand(saved.get(&"ok") == true, "E4 good package save failed") - detail["return"] = transport(runtime.load_from_path(path)) + var load_return: Variant = runtime.load_from_path(path) + detail["event_stream"] = follow_event_stream() + detail["return"] = transport(load_return) + detail["_return_value"] = load_return detail["latest_save_operation_result"] = transport(runtime.get("latest_save_operation_result")) detail["package_after"] = e3_package(path) elif operation == "rejected_command": diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/run_eq.py b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/run_eq.py index cfb7836..df9d392 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/run_eq.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/run_eq.py @@ -140,9 +140,10 @@ def plan(collector, standard, engine, fixture, out): def copy_evidence(runtime, destination): source = runtime / 'evidence' before = verify.inventory(source, seals=True) - require(set(before) == verify.NAMES, 'private evidence missing/extra') + require(set(verify.split_evidence(before)[0]) == verify.NAMES, 'private evidence missing/extra') destination.mkdir() for name in sorted(before): + (destination / name).parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(source / name, destination / name) require(verify.inventory(source, seals=True) == before == verify.inventory(destination, seals=True), 'evidence changed during copy') return before @@ -228,7 +229,8 @@ def run(admission, out, collector, standard, outer, diag): Path(admission['engine']), collector, standard) require((ROOT / 'project.godot').read_bytes() == public and (stage / 'project.godot').read_bytes() == public, 'project restoration') if error is None: - result = verify.recompute(out, execution, collector, standard, diag) + with check.memo_scope(): + result = verify.recompute(out, execution, collector, standard, diag, parallel=True) write(out / 'results.json', result) for phase, values in result['phases'].items(): for name, value in values.items(): diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py index 9c1fbf4..d6e2ecb 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py @@ -432,6 +432,7 @@ def s2_report(): state = snapshot() runtime = eq._field(eq._field(state, 'systems'), 'runtime') runtime[1].append([[4, 'id_generator'], d(counters=d(domain_event=[2, 3]))]) + runtime[1].append([[4, 'next_command_id'], [2, 1]]) events = eq._field(runtime, 'domain_events') events[1].append([[4, 'published_ids'], arr(*([4, 'domain_event.%012d' % i] for i in range(1, 4)))]) value['checkpoints'] = [checkpoint(state, 632+i) for i in range(31)] @@ -441,7 +442,7 @@ def s2_report(): cp['K']['_phase_handlers'] = {str(i): [] for i in range(11)} cp['C'].update(catalog_instance_id='123', current_catalog_instance_id='123') cp.update(latest_digest='b'*64, computed_latest_digest='b'*64, harness_normalized_digest=None) - value['signal_order'] = [{'signal': sig, 'minute': cp['minute']} for cp in value['checkpoints'][1:] for sig in ('snapshot_published', 'state_updated')] + value['signal_order'] = [{'signal': sig, 'minute': cp['minute'], 'event_counter': 3, 'command_counter': 1} for cp in value['checkpoints'][1:] for sig in ('snapshot_published', 'state_updated')] return value @@ -672,7 +673,7 @@ class S2Static(unittest.TestCase): self.reject(self.probe.replace('return str(a) < str(b)', 'return a < b')) def test_full_state_and_godot_roundtrip_required(self): - for required in ('var_to_bytes(decode(tree))', 'transport(runtime.create_snapshot())', 'before.k_bytes == after.k_bytes', 'event.to_dictionary()', 'receipt.get("actual_user_data_dir")'): + for required in ('var_to_bytes(decode(tree))', 'transport(snapshot_value)', 'before.k_bytes == after.k_bytes', 'event.to_dictionary()', 'receipt.get("actual_user_data_dir")'): with self.subTest(required=required): self.reject(self.probe.replace(required, 'null')) def test_no_event_cap_or_duplicate_report(self): @@ -790,7 +791,8 @@ def e3_fixture(role='R'): evidence.append({'id': id_, 'begin': begin, 'invocation_before': copy.deepcopy(begin), 'end': end, 'boundaries': ['begin', 'operation', 'end'], 'input': transport(checker.e3_expected_input(b1, kind)), 'return': transport(actual), 'package': package, 'events': event_records, 'signals': [], - 'latest_save_operation_result': transport(latest), 'save_budget_before': transport(d()), 'save_budget_after': transport(d())}) + 'latest_save_operation_result': transport(latest), 'save_budget_before': transport(d()), 'save_budget_after': transport(d()), + 'event_stream': {'replaced': False}}) return value, evidence, transport(b1) @@ -894,6 +896,10 @@ class S3a(unittest.TestCase): value, evidence, b1 = e3_fixture() value['scenarios'][0]['result']['ok'] = True evidence[0]['return'] = transport([1, 1]) + # S5: an accepted restore publishes once (simulation_runtime.gd:1473), as the product does. + state = evidence[0]['begin']['checkpoint']['S']['tree'] + evidence[0]['signals'] = [{'signal': sig, 'minute': evidence[0]['begin']['checkpoint']['minute'], 'event_counter': checker.counts(state)[0]['counter'], + 'command_counter': checker.next_command_id(state)} for sig in ('snapshot_published', 'state_updated')] checked = self.check(value, evidence, b1) self.assertTrue(checked['ok'], checked['errors']) self.assertEqual(checked['e3'][0]['defects'][0]['kind'], 'invalid_input_accepted') @@ -993,13 +999,19 @@ def e45_fixture(role='R'): current = copy.deepcopy(previous) evidence = [] def emit(ident, op, obs, detail, events=()): - detail['signals'] = [] + # Publishes as the product makes them: one per successful restore/load and per tick. + counter = checker.counts(obs[0]['checkpoint']['S']['tree'])[0]['counter'] + commands = checker.next_command_id(obs[0]['checkpoint']['S']['tree']) + minutes = [o['checkpoint']['minute'] for o in obs] + published = ([minutes[0]] if op in ('restore_snapshot', 'load_from_path') else []) + [b for a, b in zip(minutes, minutes[1:]) if b == a+1] + signals = [{'signal': sig, 'minute': m, 'event_counter': counter, 'command_counter': commands} for m in published for sig in ('snapshot_published', 'state_updated')] + detail['signals'] = copy.deepcopy(signals) result = {'event_count': len(events), 'applicability': detail.get('applicability', 'applied'), 'reason': detail.get('reason'), 'facts': checker.e45_facts(detail)} raw['scenarios'].append({'id': ident, 'operation': op, 'result': result, 'checkpoints': [o['checkpoint'] for o in obs], 'events': list(events)}) evidence.append({'id': ident, 'boundaries': ['begin', 'operation', 'end'], 'observations': obs, - 'detail': detail, 'events': [transport(e) for e in events], 'signals': []}) + 'detail': detail, 'events': [transport(e) for e in events], 'signals': signals}) for op in checker.E4_PATHS: begin = copy.deepcopy(current); minute = begin['checkpoint']['minute'] detail = {}; obs = [begin]; events = [] @@ -1017,14 +1029,14 @@ def e45_fixture(role='R'): elif op == 'tick': minute += 1 elif op == 'restore_snapshot': - detail = {'input': transport(tree), 'return': transport([1, 1])} + detail = {'input': transport(tree), 'return': transport([1, 1]), 'event_stream': {'replaced': False}} elif op == 'load_from_path': ev = event(raw['profile_root']['root']) # Match the next identity required by X(P,event). counter = checker.counts(tree)[0]['counter'] - checker.node_at(ev, 'event_id')[1] = 'event_'+str(counter+1) + checker.node_at(ev, 'event_id')[1] = 'domain_event.%012d' % (counter+1) detail = {'input': transport(tree), 'return': transport(d(ok=[1, 1], code=[4, 'OK'])), - 'path': 'user://p28_eq/'+role.lower()+'/good'} + 'path': 'user://p28_eq/'+role.lower()+'/good', 'event_stream': {'replaced': False}} package = e3_package_fixture('duplicate_published_ids') detail.update(save_return=package['save_return'], package_before=package['original'], package_after=package['original'], latest_save_operation_result=transport(checker.node_at(ev, 'payload'))) tree, _ = checker.e3_x(tree, ev) @@ -1294,7 +1306,7 @@ def s4_phase_fixture(directory, phase='R-A', pid=101): 'project_settings_persisted': False} guard_path = directory/(phase+'-guard.json') verifier.write(guard_path, guard) - life = {'stage': 'S3b', 'errors': [], 'stop_reason': 'S3b_complete_S4_pending', + life = {'stage': 'S5', 'errors': [], 'stop_reason': 'S5_complete', 'blobs': {}, 'event_stream_replacements': [], 'guard': {'receipt': guard, 'live_root': root, 'sha256': verifier.sha(guard_path), 'path': str(guard_path)}, 'observer_before': {}, 'observer_connected': {'probe': True}, 'observer_disconnected': {}, 'ops': {'code': 'INVALID_START_TIME'} if role == 'R' else {'ok': True}, @@ -1698,5 +1710,588 @@ class S4StoredResultBindings(unittest.TestCase): def test_raw_joined_substitution_rejected_after_seal(self): self.reject_stored_change('raw_joined') +# S5 (EQ_S5_SCALE_PLAN r01/r02): content-addressed blobs, exact fast paths, event stream. +import gzip +import shutil +import tempfile + + +def s5_root(): + base = os.environ.get('BUILD_OUTPUT_DIR') or tempfile.gettempdir() + return Path(tempfile.mkdtemp(prefix='p28-eq-s5-', dir=base)) + + +def put_blob(root, data): + digest = hashlib.sha256(data).hexdigest() + (root/(digest+'.gz')).write_bytes(gzip.compress(data)) + return digest + + +def as_blobs(value, root): + """Every inline transport record becomes a blob reference to the same tree.""" + if isinstance(value, dict): + if set(value) == {'tree', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'} and isinstance(value['tree'], list): + digest = put_blob(root, eq.json_bytes(value['tree'])) + assert digest == value['tree_sha'] + return dict(value, tree={'blob': digest}) + return {k: as_blobs(v, root) for k, v in value.items()} + if isinstance(value, list): + return [as_blobs(v, root) for v in value] + return value + + +def reference_exact(a, b): + return eq.json_bytes(a) == eq.json_bytes(b) + + +def reference_compare_trees(a, b): + """The S1-S4 algorithm (JSON comparison at every level), kept to prove equivalence.""" + eq.validate_tree(a); eq.validate_tree(b) + differences, orders = [], [] + def diff(path, x, y): + differences.append({'path': copy.deepcopy(path), 'a': copy.deepcopy(x), 'b': copy.deepcopy(y)}) + def walk(x, y, path): + if reference_exact(x, y): + return + if x[0] != y[0] or len(x) != len(y): + diff(path, x, y); return + t = x[0] + if t in (27, 28) and not reference_exact(x[2], y[2]): + diff(path + [['metadata', 0]], x[2], y[2]) + if t == 27: + def keyed(n): + result = {} + for k, v in n[1]: + result[eq._key_text(k)] = (k, v) + return result + dx, dy = keyed(x), keyed(y) + if set(dx) == set(dy) and [eq._key_text(k) for k, _ in x[1]] != [eq._key_text(k) for k, _ in y[1]]: + orders.append(copy.deepcopy(path)) + for key in sorted(set(dx) | set(dy)): + if key not in dx or key not in dy: + diff(path + [['key', [4, key]]], dx.get(key), dy.get(key)); continue + kx, vx = dx[key]; ky, vy = dy[key] + p = path + [eq._key_segment(kx)] + if not reference_exact(kx, ky): + diff(p + [['key_type', 0]], kx, ky) + walk(vx, vy, p) + elif t == 28 or t in range(5, 21) or t in range(30, 39): + for i in range(max(len(x[1]), len(y[1]))): + if i >= len(x[1]) or i >= len(y[1]): + diff(path + [['index', i]], x[1][i] if i < len(x[1]) else None, y[1][i] if i < len(y[1]) else None) + else: + walk(x[1][i], y[1][i], path + [['index', i]]) + else: + diff(path, x, y) + walk(a, b, []) + return {'equal': not differences, 'exact': reference_exact(a, b), 'differences': differences, 'dict_order_paths': orders} + + +def reference_digest(tree): + def canonical(node): + result = copy.deepcopy(node) + if node[0] == 27: + names = [eq._key_text(k) for k, _ in node[1]] + eq.require(len(set(names)) == len(names), 'digest textual key collision') + result[1] = [[copy.deepcopy(k), canonical(v)] for k, v in sorted(node[1], key=lambda p: eq._key_text(p[0]))] + elif node[0] == 28 or 5 <= node[0] <= 20 or 30 <= node[0] <= 38: + result[1] = [canonical(v) for v in node[1]] + return result + eq.validate_tree(tree) + return eq.tree_sha(canonical(tree)) + + +def reference_expected_input(b1, kind): + value = copy.deepcopy(b1) + if kind == 'missing_key': + value[1] = [pair for pair in value[1] if pair[0][1] != 'clock'] + elif kind in ('wrong_type', 'out_of_range'): + checker.node_at(value, 'clock', 'absolute_minute' if kind == 'wrong_type' else 'speed')[:] = [4, 'x'] if kind == 'wrong_type' else [2, 9] + elif kind in ('duplicate_published_ids', 'duplicate_facility'): + nodes = (checker.node_at(value, 'systems', 'runtime', 'domain_events', 'published_ids') if kind == 'duplicate_published_ids' + else checker.node_at(value, 'systems', 'runtime', 'facilities', 'instances')) + if nodes[1]: + nodes[1].append(copy.deepcopy(nodes[1][0])) + return value + + +def reference_x(before, event_tree): + value = copy.deepcopy(before) + counter = checker.node_at(value, 'systems', 'runtime', 'id_generator', 'counters', 'domain_event') + counter[1] += 1 + domain = checker.node_at(value, 'systems', 'runtime', 'domain_events') + ids = checker.fields(domain)['published_ids'] + ids[1].append(copy.deepcopy(checker.fields(event_tree)['event_id'])) + ids[1].sort(key=lambda n: n[1]) + recent = checker.fields(domain)['recent_events'] + dropped = copy.deepcopy(recent[1][0]) if len(recent[1]) == 256 else None + recent[1].append(copy.deepcopy(event_tree)) + if len(recent[1]) > 256: + del recent[1][0] + return value, dropped + + +def s5_state(events=(), minute=0, extra=None): + state = d(clock=d(absolute_minute=[2, minute], speed=[2, 1]), + systems=d(runtime=d(id_generator=d(counters=d(domain_event=[2, 3])), + domain_events=d(published_ids=arr(*([4, 'domain_event.%012d' % i] for i in range(1, 4))), + recent_events=arr(*events)), + facilities=d(instances=arr(d(id=[4, 'f1']))), + vectors=arr([9, [[3, '0000000000000000'], [3, '000000000000f03f'], [3, '0000000000000040']]], + [30, [[2, 1], [2, 2]]]))), + value=arr([2, minute])) + if extra is not None: + state[1].append([[4, 'extra'], extra]) + return state + + +def reference_validate(node): + """The S1 validate_tree (JSON key comparison, stack order), kept to prove equivalence.""" + import base64, re + pending = [node] + while pending: + n = pending.pop() + eq.require(type(n) is list and n and type(n[0]) is int, 'invalid typed node') + t = n[0] + eq.require(t not in (23, 24, 25, 26) and 0 <= t <= 38, 'unsupported Variant type') + eq.require(len(n) == (1 if t == 0 else 3 if t in (27, 28) else 2), 'node arity') + if t == 0: + continue + p = n[1] + if t in (1, 2): + eq.require(type(p) is int and (p in (0, 1) if t == 1 else -(2**63) <= p < 2**63), 'integer payload') + elif t == 3: + eq.require(type(p) is str and re.fullmatch('[0-9a-f]{16}', p), 'float must be f64 LE hex') + elif t in (4, 21, 22): + eq.require(type(p) is str, 'string payload') + elif t == 29: + eq.require(type(p) is str, 'byte array payload') + try: + eq.require(base64.b64encode(base64.b64decode(p, validate=True)).decode() == p, 'noncanonical base64') + except (ValueError, TypeError) as exc: + raise eq.EvidenceError('invalid base64') from exc + elif t == 27: + eq.require(type(p) is list, 'dictionary entries') + seen = set() + for pair in p: + eq.require(type(pair) is list and len(pair) == 2, 'dictionary pair') + key = eq.json_bytes(pair[0]) + eq.require(key not in seen, 'duplicate dictionary key') + seen.add(key) + pending.extend(pair) + eq._typed_info(n[2], dictionary=True) + elif t == 28: + eq.require(type(p) is list, 'array elements') + eq._typed_info(n[2], dictionary=False) + pending.extend(p) + else: + eq.require(type(p) is list, 'component nodes') + sizes = {5: 2, 6: 2, 7: 4, 8: 4, 9: 3, 10: 3, 11: 6, 12: 4, 13: 4, 14: 4, 15: 4, 16: 6, 17: 9, 18: 12, 19: 16, 20: 4} + if t in sizes: + eq.require(len(p) == sizes[t], 'component count') + integer = t in (6, 8, 10, 13) + eq.require(all(x and x[0] == (2 if integer else 3) for x in p), 'component type') + elif t in (30, 31, 32, 33, 34): + expected = {30: 2, 31: 2, 32: 3, 33: 3, 34: 4}[t] + eq.require(all(x and x[0] == expected for x in p), 'packed element type') + else: + eq.require(all(x and x[0] == {35: 5, 36: 9, 37: 20, 38: 12}[t] for x in p), 'packed vector type') + pending.extend(p) + return node + + +class S5(unittest.TestCase): + def setUp(self): + eq.reset_blobs() + self.root = s5_root() + eq.add_blob_root(self.root) + + def tearDown(self): + eq.reset_blobs() + shutil.rmtree(self.root) + + def record(self, tree): + return {'tree': {'blob': put_blob(self.root, eq.json_bytes(tree))}, 'tree_sha': eq.tree_sha(tree), + 'raw_bytes_sha': 'a'*64, 'roundtrip_ok': True} + + def test_blob_transport_is_verified_and_bound(self): + tree = s5_state([event()]) + record = self.record(tree) + loaded = eq._transport(record, None) + self.assertEqual(list(loaded), tree) + self.assertEqual(eq.tree_sha(loaded), eq.tree_sha(tree)) + for name, mutate in [('tree_sha', lambda r: r.update(tree_sha='b'*64)), ('roundtrip', lambda r: r.update(roundtrip_ok=False)), + ('raw', lambda r: r.update(raw_bytes_sha='x')), ('extra', lambda r: r.update(diff=[])), + ('ref', lambda r: r['tree'].update(extra=1)), ('missing', lambda r: r['tree'].update(blob='c'*64))]: + bad = copy.deepcopy(record); mutate(bad) + with self.subTest(name=name), self.assertRaises((eq.EvidenceError, KeyError)): + eq._transport(bad, None) + digest = record['tree_sha'] + for name, content in [('changed', gzip.compress(eq.json_bytes(s5_state()))), ('not gzip', b'not gzip'), + ('float', gzip.compress(b'[3,1.5]')), ('object', gzip.compress(b'[27,{"a":1},null]'))]: + eq.reset_blobs(); eq.add_blob_root(self.root) + (self.root/(digest+'.gz')).write_bytes(content) + with self.subTest(name=name), self.assertRaises(eq.EvidenceError): + eq._transport(record, None) + for content in (b'[3,1.5]', b'[27,{"a":1},null]'): + other = put_blob(self.root, content) + with self.subTest(content=content), self.assertRaises(eq.EvidenceError): + eq.blob_tree(other) + + def test_every_checkpoint_full_tree_verified_like_inline(self): + """S5 drops the diff chain: each checkpoint carries a complete tree, verified alone.""" + value = s2_report() + for i, cp in enumerate(value['checkpoints']): + state = cp['S']['tree'] + state[1] = [pair for pair in state[1] if pair[0][1] != 'value'] + [[[4, 'value'], arr([2, i])]] + cp['S'] = transport(state); cp['latest'] = transport(state) + inline_joined, inline_proofs = checker.join_report(value) + self.assertTrue(checker.check_report(value, measurement(value))['ok']) + blobbed = as_blobs(value, self.root) + self.assertEqual(len(list(self.root.iterdir())), 31) + joined, proofs = checker.join_report(blobbed) + self.assertEqual(eq.json_bytes(proofs), eq.json_bytes(inline_proofs)) + self.assertEqual([cp['harness_normalized_digest'] for cp in joined['checkpoints']], + [cp['harness_normalized_digest'] for cp in inline_joined['checkpoints']]) + self.assertTrue(checker.check_report(blobbed, measurement(blobbed))['ok']) + self.assertTrue(eq.compare_reports(joined, inline_joined)['ok']) + for index in range(31): + digest = blobbed['checkpoints'][index]['S']['tree_sha'] + original = (self.root/(digest+'.gz')).read_bytes() + other = copy.deepcopy(value['checkpoints'][index]['S']['tree']); other[1][-1][1][1][0][1] = 999 + (self.root/(digest+'.gz')).write_bytes(gzip.compress(eq.json_bytes(other))) + eq.reset_blobs(); eq.add_blob_root(self.root) + with self.subTest(index=index): + self.assertFalse(checker.check_report(blobbed, measurement(blobbed))['ok']) + (self.root/(digest+'.gz')).write_bytes(original) + eq.reset_blobs(); eq.add_blob_root(self.root) + self.assertTrue(checker.check_report(blobbed, measurement(blobbed))['ok']) + + def test_fast_normalization_equals_original(self): + root = '/private/a' + cases = { + 'identity': s5_state(), + 'events': s5_state([event(root), event(root, kind='SAVE_LOAD_FAILED', duration=9)]), + 'outside timing name': s5_state([event(root)], extra=d(wait_us=[2, 5])), + 'outside profile string': s5_state([event(root)], extra=[4, root+'/x']), + 'profile in key': s5_state([event(root)], extra=d(**{root+'/k': [2, 1]})), + 'save_service elsewhere': s5_state([event(root)], extra=[4, 'save_service']), + 'non-save event': s5_state([event(root, source='other')]), + } + for name, tree in cases.items(): + eq.reset_blobs(); eq.add_blob_root(self.root) + with self.subTest(name=name): + expected, expected_side = eq._normalize([copy.deepcopy(tree)], profile(root), False) + loaded = eq._transport(self.record(tree), None) + normalized, side = eq.normalize_tree(loaded, profile(root)) + self.assertEqual(eq.json_bytes(eq.materialize(normalized)), eq.json_bytes(expected[0])) + self.assertEqual(eq.json_bytes(side), eq.json_bytes(expected_side)) + self.assertEqual(eq.tree_sha(normalized), eq.tree_sha(expected[0])) + restored = eq.restore_tree(normalized, side, profile(root)) + self.assertEqual(eq.tree_sha(restored), eq.tree_sha(tree)) + if side['entries']: + forged = copy.deepcopy(side); forged['entries'].pop() + with self.assertRaises(eq.EvidenceError): eq.restore_tree(normalized, forged, profile(root)) + token = s5_state([event(root)], extra=[4, eq.TOKEN+'x']) + for value in (token, s5_state([event(root)])): + if value is token: + with self.assertRaises(eq.EvidenceError): eq._normalize([copy.deepcopy(value)], profile(root), False) + with self.assertRaises(eq.EvidenceError): eq.normalize_tree(eq._transport(self.record(value), None), profile(root)) + broken = s5_state() + checker.node_at(broken, 'systems', 'runtime', 'domain_events')[1][1][1] = d() + with self.assertRaisesRegex(eq.EvidenceError, 'recent_events array required'): + eq.normalize_tree(eq._transport(self.record(broken), None), profile(root)) + + def test_compare_and_digest_equal_s1_reference(self): + a = s5_state([event()], extra=d(z=[2, 1], y=d(b=[4, 'v'], a=[21, 'v']))) + variants = {'same': copy.deepcopy(a)} + b = copy.deepcopy(a); b[1].reverse(); variants['dict order'] = b + c = copy.deepcopy(a); checker.node_at(c, 'extra', 'y', 'a')[0] = 4; variants['value type'] = c + k = copy.deepcopy(a); checker.node_at(k, 'extra')[1][1][0] = [21, 'y']; variants['key type'] = k + m = copy.deepcopy(a); checker.node_at(m, 'systems', 'runtime', 'vectors')[2] = [[2, 10], [21, ''], [0]]; variants['metadata'] = m + n = copy.deepcopy(a); checker.node_at(n, 'systems', 'runtime', 'vectors')[1].append([2, 3]); variants['array length'] = n + v = copy.deepcopy(a); checker.node_at(v, 'systems', 'runtime', 'vectors')[1][0][1][2] = [3, '0000000000000840']; variants['vector'] = v + for name, other in variants.items(): + with self.subTest(name=name): + expected = reference_compare_trees(a, other) + self.assertEqual(eq.json_bytes(eq.compare_trees(a, other)), eq.json_bytes(expected)) + left, right = eq._transport(self.record(a), None), eq._transport(self.record(other), None) + self.assertEqual(eq.json_bytes(eq.compare_trees(left, right)), eq.json_bytes(expected)) + self.assertEqual(checker.normalized_digest(other), reference_digest(other)) + self.assertEqual(checker.normalized_digest(right), reference_digest(other)) + self.assertEqual(eq.exact(left, right), reference_exact(a, other)) + + def test_path_copy_builders_equal_deepcopy_reference(self): + b1 = s5_state([event()]) + frozen = eq.json_bytes(b1) + for kind in checker.E3_KINDS: + with self.subTest(kind=kind): + self.assertEqual(eq.json_bytes(checker.e3_expected_input(b1, kind)), eq.json_bytes(reference_expected_input(b1, kind))) + loaded = eq._transport(self.record(b1), None) + for kind in checker.E3_KINDS: + self.assertEqual(eq.json_bytes(checker.e3_expected_input(loaded, kind)), eq.json_bytes(reference_expected_input(b1, kind))) + new_event = event(); checker.node_at(new_event, 'event_id')[1] = 'domain_event.000000000004' + for before in (b1, s5_state([event()]*256)): + got, dropped = checker.e3_x(before, new_event) + want, want_dropped = reference_x(before, new_event) + self.assertEqual(eq.json_bytes([got, dropped]), eq.json_bytes([want, want_dropped])) + self.assertEqual(eq.json_bytes(b1), frozen) + self.assertEqual(eq.json_bytes(loaded), frozen) + + def test_event_stream_replacement_is_existing_defect_not_pass(self): + ev = event(); checker.node_at(ev, 'event_id')[1] = 'domain_event.000000000004' + fact = {'replaced': True, 'new_stream_subscribers_before': [], 'recovered_event_ids': ['domain_event.000000000004']} + defects = checker.stream_defects(fact, [ev]) + self.assertEqual([x['kind'] for x in defects], ['event_stream_replaced_subscribers_not_carried']) + self.assertEqual(checker.stream_defects({'replaced': False}, []), []) + for bad in [dict(fact, recovered_event_ids=['missing']), dict(fact, recovered_event_ids=['domain_event.000000000004']*2), + dict(fact, new_stream_subscribers_before=['*/p28_eq_observer']), {'replaced': False, 'extra': 1}, + {'replaced': True}, None, dict(fact, extra=1)]: + with self.subTest(bad=bad), self.assertRaises((eq.EvidenceError, KeyError, TypeError, AttributeError)): + checker.stream_defects(bad, [ev]) + raw, evidence, previous = e45_fixture('O') + index = [e['id'] for e in evidence].index('E4/restore_snapshot') + evidence[index]['detail']['event_stream'] = {'replaced': True, 'new_stream_subscribers_before': [], 'recovered_event_ids': []} + raw['scenarios'][18+index]['result']['facts'] = checker.e45_facts(evidence[index]['detail']) + outcome = checker.check_e45(raw, evidence, previous)[index] + self.assertEqual(outcome['status'], 'existing-defect') + self.assertEqual(outcome['defects'][0]['kind'], 'event_stream_replaced_subscribers_not_carried') + + def test_lifecycle_binds_replacements_guard_text_and_blobs(self): + with synthetic_directory() as name: + directory = Path(name) + row = s4_phase_fixture(directory) + evidence = directory/row['phase']/'evidence' + raw = verifier.read(evidence/'report.json'); life = verifier.read(evidence/'lifecycle.json') + guard = verifier.read(directory/(row['phase']+'-guard.json')) + self.assertTrue(checker.check_lifecycle(raw, life, guard)) + bad = copy.deepcopy(life); bad['event_stream_replacements'] = [{'replaced': True}] + with self.assertRaisesRegex(eq.EvidenceError, 'event stream replacement record differs'): + checker.check_lifecycle(raw, bad, guard) + text = (directory/(row['phase']+'-guard.json')).read_text() + good = copy.deepcopy(life); good['guard']['receipt_text'] = text + good['guard']['receipt'] = json.loads(text.replace('"conditions_checked": 5', '"conditions_checked": 5.0')) + self.assertTrue(checker.check_lifecycle(raw, good, guard)) + for changed in (text.replace('"conditions_checked": 5', '"conditions_checked": 5.0'), text + ' '): + bad = copy.deepcopy(good); bad['guard']['receipt_text'] = changed + with self.subTest(changed=changed[-20:]), self.assertRaises(eq.EvidenceError): + checker.check_lifecycle(raw, bad, guard) + verifier.phase_evidence(directory, row) + blobs = evidence/'blobs'; blobs.mkdir() + extra = put_blob(blobs, b'extra') + moved = dict(row, evidence_files=verifier.inventory(evidence, seals=True)) + with self.assertRaisesRegex(eq.EvidenceError, 'blob inventory differs'): + verifier.phase_evidence(directory, moved) + (blobs/(extra+'.gz')).unlink(); (blobs/'not-a-blob.gz').write_bytes(b'x') + with self.assertRaisesRegex(eq.EvidenceError, 'unexpected evidence blob name'): + verifier.phase_evidence(directory, dict(row, evidence_files=verifier.inventory(evidence, seals=True))) + + def test_manifest_numbers_keep_their_type(self): + package = e3_package_fixture('missing_key') + self.assertTrue(checker.e3_package_check(package, 'missing_key')) + import base64 + changed = b'{"format_version":1.0}' + package['modified']['manifest.json'] = {'bytes': base64.b64encode(changed).decode(), 'sha256': checker.sha(changed)} + with self.assertRaisesRegex(eq.EvidenceError, 'manifest mutation rule'): + checker.e3_package_check(package, 'missing_key') + blobbed = e3_package_fixture('missing_key') + for side in ('original', 'modified', 'after_operation'): + for name, item in blobbed[side].items(): + if item is not None: + content = base64.b64decode(item['bytes']) + item['bytes'] = {'blob': put_blob(self.root, content)} + self.assertTrue(checker.e3_package_check(blobbed, 'missing_key')) + + def test_signed_instance_ids_raw_kept_and_excluded(self): + value = s2_report() + for cp in value['checkpoints']: + cp['C'].update(catalog_instance_id='-9223371962598816238', current_catalog_instance_id='-9223371962598816238') + self.assertTrue(checker.check_report(value, measurement(value))['ok']) + other = copy.deepcopy(value) + for cp in other['checkpoints']: + cp['C'].update(catalog_instance_id='-1', current_catalog_instance_id='-1') + joined_a, _ = checker.join_report(value); joined_b, _ = checker.join_report(other) + result = eq.compare_reports(joined_a, joined_b) + self.assertTrue(result['ok']) + self.assertIn({'path': ['checkpoints', 0, 'C', 'catalog_instance_id'], 'reason': 'process-local instance ID', + 'a': '-9223371962598816238', 'b': '-1'}, result['excluded']) + for cp in value['checkpoints']: + cp['C'].update(catalog_instance_id='-0x1') + self.assertFalse(checker.check_report(value, measurement(value))['ok']) + + def test_publish_pairs_bound_to_source_causes(self): + def pair(minute, counter, commands): + return [{'signal': 'snapshot_published', 'minute': minute, 'event_counter': counter, 'command_counter': commands}, + {'signal': 'state_updated', 'minute': minute, 'event_counter': counter, 'command_counter': commands}] + # R minute 648 shape: paused submits each publish after their command events; tick end last. + events = {4: ('GUEST_STATE_CHANGED', 634, None), 5: ('COMMAND_COMPLETED', 634, 1), 6: ('COMMAND_COMPLETED', 634, 2), + 7: ('GUEST_STATE_CHANGED', 634, None), 8: ('COMMAND_FAILED', 634, 3)} + good = pair(633, 3, 1) + pair(634, 5, 2) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4) + command, other = checker.check_publishes(good, events, 3, 8, 1, 4) + self.assertEqual(command, [(634, 5), (634, 6), (634, 8)]) + self.assertEqual(other, [(633, 3), (634, 8)]) + bad = { + 'missing adjacent command publish': pair(633, 3, 1) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4), + 'missing first command publish': pair(633, 3, 1) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4)[:0] + pair(634, 8, 4), + 'unpublished submit': pair(633, 3, 1) + pair(634, 5, 2) + pair(634, 6, 3), + 'submit without publish before tick': pair(633, 3, 2) + pair(634, 5, 2) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4), + 'odd signal': good[:-1], + 'swapped pair': good[:2] + [good[3], good[2]] + good[4:], + 'pair across minutes': pair(633, 3, 1) + [good[2], dict(good[3], minute=635)] + good[4:], + 'counter mismatch in pair': pair(633, 3, 1) + [good[2], dict(good[3], event_counter=6)] + good[4:], + 'order': pair(634, 5, 2) + pair(633, 3, 1) + good[4:], + 'outside interval': good + pair(634, 9, 4), + 'missing fields': [{'signal': s['signal'], 'minute': s['minute']} for s in good], + } + for name, value in bad.items(): + with self.subTest(name=name): + try: + command, other = checker.check_publishes(value, events, 3, 8, 1, 4) + except (eq.EvidenceError, KeyError, TypeError): + continue + self.fail('accepted: %s -> %r %r' % (name, command, other)) + # A missing tick-end publish leaves the command publishes intact; the caller's count of + # tick/restore publishes (one per measured minute) rejects it. + command, other = checker.check_publishes(pair(633, 3, 1) + pair(634, 5, 2) + pair(634, 6, 3) + pair(634, 8, 4), events, 3, 8, 1, 4) + self.assertEqual((len(command), other), (3, [(633, 3)])) + # In the measurement the remaining publishes are exactly one tick end per minute. + value = s2_report() + self.assertTrue(checker.check_report(value, measurement(value))['ok']) + for name, mutate in [('extra tick publish', lambda rows: rows.__setitem__(slice(2, 2), pair(634, 3, 1))), + ('missing tick publish', lambda rows: rows.__delitem__(slice(2, 4)))]: + broken = s2_report(); mutate(broken['signal_order']) + with self.subTest(name=name): + self.assertFalse(checker.check_report(broken, measurement(broken))['ok']) + + def test_labelled_harness_digest_falls_back_to_dictionary_order_only(self): + def joined(tree): + value = report(tree) + for cp in value['checkpoints']: + cp['harness_normalized_digest'] = None + return checker.join_report(value)[0] + state = snapshot([event()]) + reordered = copy.deepcopy(state); reordered[1].reverse() + a, b = joined(state), joined(reordered) + self.assertTrue(all(cp['harness_normalized_digest'].startswith(eq.HARNESS_LABEL) for cp in a['checkpoints'])) + self.assertNotEqual(a['checkpoints'][0]['harness_normalized_digest'], b['checkpoints'][0]['harness_normalized_digest']) + result = eq.compare_reports(a, b) + self.assertTrue(result['ok'], result['differences'][:2]) + self.assertIn('harness canonical digest (dictionary order only)', [m['mode'] for m in result['digest_modes']]) + self.assertTrue(result['dict_order_paths']) + changed = copy.deepcopy(state); checker.node_at(changed, 'value')[1][0] = [2, 2] + result = eq.compare_reports(a, joined(changed)) + self.assertFalse(result['ok']) + self.assertIn(['checkpoints', 0, 'harness_normalized_digest'], [d['path'] for d in result['differences']]) + + def test_memo_scope_same_results_and_restores_gc(self): + import gc + value, evidence, b1 = e3_fixture() + plain = checker.check_report(value, measurement(value), evidence, b1) + self.assertTrue(gc.isenabled()) + with checker.memo_scope(): + self.assertFalse(gc.isenabled()) + first = checker.check_report(value, measurement(value), evidence, b1) + second = checker.check_report(value, measurement(value), evidence, b1) + self.assertTrue(gc.isenabled()) + self.assertEqual(eq.json_bytes(plain), eq.json_bytes(first)) + self.assertEqual(eq.json_bytes(first), eq.json_bytes(second)) + broken = copy.deepcopy(value); broken['scenarios'][0]['result']['code_reason'] = 'invented' + with checker.memo_scope(): + checker.check_report(value, measurement(value), evidence, b1) + self.assertFalse(checker.check_report(broken, measurement(broken), evidence, b1)['ok']) + + def test_blob_trees_get_the_same_full_validation_as_inline(self): + invalid = { + 'bool as int': [2, True], 'bool payload': [1, True], 'object tag': [24, 0], 'callable tag': [25, 0], + 'nil arity': [0, 1], 'int arity': [2], 'dict arity': [27, []], 'array arity': [28, [], None, None], + 'float text': [3, '1.0'], 'base64': [29, 'not base64!'], 'scripted metadata': [28, [], [[2, 2], [21, ''], [2, 1]]], + 'nested bool': [27, [[[4, 'k'], [2, False]]], None], 'duplicate key': [27, [[[4, 'k'], [0]], [[4, 'k'], [0]]], None], + 'vector component': [9, [[2, 1], [2, 2], [2, 3]]], + } + for name, tree in invalid.items(): + with self.subTest(name=name): + with self.assertRaises(eq.EvidenceError): eq.validate_tree(copy.deepcopy(tree)) + digest = put_blob(self.root, json.dumps(tree, separators=(',', ':')).encode()) + record = {'tree': {'blob': digest}, 'tree_sha': digest, 'raw_bytes_sha': 'a'*64, 'roundtrip_ok': True} + for attempt in range(2): + with self.assertRaises(eq.EvidenceError): eq._transport(record, None) + with self.assertRaises(eq.EvidenceError): eq._transport_ref(record) + with self.assertRaises(eq.EvidenceError): eq.known_sha(eq.TreeRef(digest)) + self.assertNotIn(digest, eq._VALID_BLOBS) + good, fake = [2, 1], [2, True] + good_record = self.record(good) + fake_digest = put_blob(self.root, b'[2,true]') + fake_ref = eq.TreeRef(fake_digest) + with self.assertRaises(eq.EvidenceError): eq.exact(fake_ref, eq._transport(good_record, None)) + with self.assertRaises(eq.EvidenceError): eq.same_tree(fake_ref, eq.TreeRef(fake_digest)) + with self.assertRaises(eq.EvidenceError): eq.validate_tree(fake_ref) + self.assertTrue(eq.exact(eq._transport(good_record, None), eq._transport_ref(good_record))) + + def test_validate_tree_equals_s1_reference_on_mutations(self): + class Lcg: + # Deterministic generator; the EQ import closure has no random module. + def __init__(self, seed): self.state = seed + def randrange(self, n): + self.state = (self.state * 6364136223846793005 + 1442695040888963407) % 2**64 + return (self.state >> 33) % n + def choice(self, values): return values[self.randrange(len(values))] + base = d(a=arr([2, 1], [3, '000000000000f03f'], [4, 's'], [21, 'n']), + b=[9, [[3, '0000000000000000'], [3, '000000000000f03f'], [3, '0000000000000040']]], + c=[10, [[2, 1], [2, 2], [2, 3]]], e=[30, [[2, 1], [2, 2]]], f=[35, [[5, [[3, '0000000000000000'], [3, '0000000000000000']]]]], + g=[28, [[2, 1]], [[2, 2], [21, ''], [0]]], h=[29, 'AA=='], i=[1, 1], j=[0], k=[22, 'np'], + l=[27, [[[2, 1], [0]], [[21, 'x'], [0]]], [[[2, 2], [21, ''], [0]], [[2, 0], [21, ''], [0]]]]) + def outcome(function, value): + try: + function(value) + return 'ok' + except Exception as error: + return type(error).__name__ + ':' + str(error) + def mutate(node): + choice = rng.randrange(9) + if not node: + node.append(rng.choice([0, 2, 'x', None])); return + if choice == 0: node[0] = rng.choice([24, 25, 99, -1, 0, 1, 2, 3, 4, 9, 10, 27, 28, 30, 35, True, '2']) + elif choice == 1: node.append(None) + elif choice == 2: node[-1] = rng.choice([True, False, 1.5, 'zz', [], None, 2**64, [0]]) + elif choice == 3: del node[-1] + elif choice == 4 and len(node) > 1: node[1] = rng.choice([True, 1.5, 'x', [], [[4, 'a']], [[2, 1], [2, 1]], 2**63, 'AA==', '000000000000f03f']) + elif choice == 5: node.clear() + elif choice == 6 and len(node) > 1 and isinstance(node[1], list) and node[1]: node[1].append(copy.deepcopy(node[1][0])) + elif choice == 7 and len(node) > 2: node[2] = rng.choice([[[2, 2], [21, ''], [0]], [[2, 2], [21, ''], [2, 1]], [], 'x', None]) + else: node.insert(0, 3) + rng = Lcg(20261022) + seen = set() + for case in range(4000): + value = copy.deepcopy(base); pool = [] + def collect(n): + if isinstance(n, list): + pool.append(n) + for c in n: collect(c) + collect(value) + for _ in range(rng.choice((1, 1, 2, 3))): + mutate(rng.choice(pool)) + expected = outcome(reference_validate, copy.deepcopy(value)) + seen.add(expected.split(':')[-1]) + self.assertEqual(outcome(eq.validate_tree, value), expected, case) + self.assertGreater(len(seen), 12) + + def test_probe_static_full_tree_blob_and_stream_requirements(self): + probe = (verifier.HERE/'eq_probe.gd').read_text() + self.assertTrue(checker.check_probe(probe)['ok']) + for old, new in [('var snapshot_value: Dictionary = runtime.create_snapshot()', 'var snapshot_value: Dictionary = last_snapshot_value'), + ('var s: Dictionary = transport(snapshot_value)', 'var s: Dictionary = {}'), + ('"tree": store_blob(text)', '"tree": {"blob": hashed(text)}'), + ('refuse blob overwrite', 'blob exists'), + ('packed.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP) == bytes', 'true'), + ('known_event_ids[str(event.event_id)] = true', 'pass'), + ('if known_event_ids.has(str(event.event_id)): continue', 'pass'), + ('demand(current.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard resubscription failed")', 'pass'), + (' detail["event_stream"] = follow_event_stream()\n detail["return"] = transport(load_return)', ' detail["return"] = transport(load_return)'), + (' var stream_fact: Dictionary = follow_event_stream()', ' var stream_fact: Dictionary = {"replaced": false}')]: + with self.subTest(old=old[:60]): + self.assertEqual(probe.count(old), 1) + self.assertFalse(checker.check_probe(probe.replace(old, new))['ok']) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py index 240eee1..d22fe85 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/verify_eq.py @@ -5,6 +5,8 @@ import hashlib import json import os from pathlib import Path +import re +import subprocess import sys sys.dont_write_bytecode = True @@ -18,6 +20,16 @@ ROLES = {'R-A': 'R', 'R-B': 'R', 'O-A': 'O', 'O-B': 'O'} NAMES = {f'{prefix}-{i:02d}.json' for prefix, count in [('checkpoint', 31), ('observation', 31), ('e3', 18), ('e45', 21)] for i in range(count)} | { 'events.json', 'report.json', 'lifecycle.json', 'audio-close.json'} +BLOB_NAME = re.compile('blobs/[0-9a-f]{64}[.]gz') + + +def split_evidence(files): + """Fixed evidence names, and the S5 content-addressed blobs beside them.""" + blobs = {n: p for n, p in files.items() if n.startswith('blobs/')} + require(all(BLOB_NAME.fullmatch(n) for n in blobs), 'unexpected evidence blob name') + return {n: p for n, p in files.items() if n not in blobs}, blobs + + LOCAL_NAMES = {'run_eq.py', 'verify_eq.py', 'check_eq.py', 'compare_versions.py', 'eq_probe.gd', 'test_eq.py'} require = eq.require @@ -83,22 +95,35 @@ def check_audio(audio, pid): def phase_evidence(directory, row): """Recompute all S1-S3b checks from copied native bytes, with no cached verdicts.""" + with check.memo_scope(): + return _phase_evidence(directory, row) + + +def _phase_evidence(directory, row): directory = Path(directory) phase, role = row['phase'], row['role'] require(ROLES.get(phase) == role, 'phase/role binding') folder = directory / phase evidence = folder / 'evidence' files = inventory(evidence, seals=True) - require(set(files) == NAMES and files == row['evidence_files'], 'exact private evidence inventory') + fixed, blobs = split_evidence(files) + require(set(fixed) == NAMES and files == row['evidence_files'], 'exact private evidence inventory') + eq.add_blob_root(evidence / 'blobs') raw = check.read_report_stdout((folder / 'stdout.log').read_text()) require(raw == read(evidence / 'report.json') and raw['role'] == role, 'raw report/role copy') + eq.ACTIVE_PROFILES[:] = [raw['profile_root']] require(len(raw['scenarios']) == 39, '39 scenarios required') life = read(evidence / 'lifecycle.json') - require(life['stage'] == 'S3b', 'S3b lifecycle required') + require(life['stage'] == 'S5', 'S5 lifecycle required') + declared_blobs = life['blobs'] + require(type(declared_blobs) is dict and {'blobs/' + k + '.gz': {'bytes': b['gz_bytes'], 'sha256': b['gz_sha256']} + for k, b in declared_blobs.items()} == blobs, 'blob inventory differs') + for digest, item in sorted(declared_blobs.items()): + require(len(eq.blob_bytes(digest)) == item['bytes'], 'blob size differs: ' + digest) require(len(life['e3']) == 18 and len(life['e45']) == 21, 'scenario evidence coverage') declared = {r['path']: {'bytes': r['bytes'], 'sha256': r['sha256']} for r in life['files']} require(len(declared) == len(life['files']), 'duplicate lifecycle inventory') - expected = {'evidence/' + n: v for n, v in files.items() if n not in ('lifecycle.json', 'audio-close.json')} + expected = {'evidence/' + n: v for n, v in fixed.items() if n not in ('lifecycle.json', 'audio-close.json')} require(declared == expected, 'lifecycle inventory differs') guard_path = directory / (phase + '-guard.json') guard = read(guard_path) @@ -120,7 +145,47 @@ def phase_evidence(directory, row): return wire({'raw': raw, 'joined': joined, 'proofs': proofs, 'checker': checked}) -def recompute(directory, execution, collector, standard, diag): +def phase_worker(): + """--phase-worker: phase_evidence for one phase (JSON on stdin), result and state on stdout.""" + payload = json.load(sys.stdin) + with check.memo_scope(): + result = phase_evidence(Path(payload['directory']), payload['row']) + state = {'valid_blobs': sorted(eq._VALID_BLOBS), + 'normalized': [[key[0], key[1].decode('utf-8'), value] for key, value in eq._NORMALIZED.items()]} + json.dump({'result': result, 'state': state}, sys.stdout, ensure_ascii=False, allow_nan=False) + return 0 + + +def phase_results(directory, rows, parallel): + """phase_evidence for every role phase. In parallel mode each phase runs in its own + worker process of this same verification; the workers' validated blob SHAs and + normalization facts are then used here for the same-role comparisons, so blobs are + validated once. Results are identical to the sequential mode, which is the fallback.""" + if not parallel: + return {row['phase']: phase_evidence(directory, row) for row in rows} + try: + workers = [(row, subprocess.Popen([sys.executable, '-B', str(HERE / 'verify_eq.py'), '--phase-worker'], cwd=str(HERE), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)) for row in rows] + except OSError: + return phase_results(directory, rows, False) + for row, worker in workers: + worker.stdin.write(json.dumps({'directory': str(directory), 'row': row}, ensure_ascii=False).encode('utf-8')) + worker.stdin.close() + worker.stdin = None # every input is sent first, so all workers run at once + results = {} + for row, worker in workers: + out, err = worker.communicate() + require(worker.returncode == 0, 'phase worker failed: ' + row['phase'] + ': ' + err.decode('utf-8', 'replace')[-2000:]) + value = check.strict_json(out.decode('utf-8')) + results[row['phase']] = value['result'] + eq.add_blob_root(Path(directory) / row['phase'] / 'evidence' / 'blobs') + eq._VALID_BLOBS.update(value['state']['valid_blobs']) + for sha_, profile_json, fact in value['state']['normalized']: + eq._NORMALIZED.setdefault((sha_, profile_json.encode('utf-8')), tuple(fact)) + return {row['phase']: results[row['phase']] for row in rows} + + +def recompute(directory, execution, collector, standard, diag, parallel=False): """Validate owned receipts and recompute every comparison from raw evidence.""" directory = Path(directory) require(execution['passed'] is True and execution['error'] is None and execution['cleanup_errors'] == [], 'execution failed') @@ -149,7 +214,7 @@ def recompute(directory, execution, collector, standard, diag): require(execution['pins_after'] == admission['pins'], 'pin preservation') rows = execution['phases'] require([r['phase'] for r in rows] == PHASES, 'seven phases in order') - pids, profiles, joined, results = [], [], {}, {} + pids, profiles, evidence_rows = [], [], [] previous = source for i, row in enumerate(rows): phase = row['phase'] @@ -201,11 +266,12 @@ def recompute(directory, execution, collector, standard, diag): require(row['role'] == ROLES[phase] and row['runtime_profile'] == command['runtime_profile'] and row['runtime_name'] == command['runtime_name'], 'runtime/role binding') collector.guard_gate(row['guard'], Path(row['runtime_profile']), row['runtime_name']) - results[phase] = phase_evidence(directory, row) - joined[phase] = results[phase]['joined'] + evidence_rows.append(row) else: require(row['role'] is None, 'parse/import role') require('P28_02_EQ_REPORT=' not in (directory / phase / 'stdout.log').read_text(), 'parse/import report') + results = phase_results(directory, evidence_rows, parallel) + joined = {phase: value['joined'] for phase, value in results.items()} require(len(set(pids)) == 7 and len(set(profiles)) == 14, 'PID/profile reuse') require({r['pid'] for r in execution['cleanup']} == set(pids) and all( r['engine_ended'] is True and r['outer_cleanup_errors'] == [] and @@ -228,7 +294,7 @@ def verify(project_root, directory): admission = execution['admission'] require(actual_pins(project_root, Path(admission['readonly_path']), Path(admission['fixture']), Path(admission['engine']), collector, standard) == admission['pins'], 'actual input/source/EQ pins differ') - result = recompute(directory, execution, collector, standard, diag) + result = recompute(directory, execution, collector, standard, diag, parallel=True) require(result == read(directory / 'results.json'), 'recomputed results differ') for phase, values in result['phases'].items(): for name, value in values.items(): @@ -237,12 +303,15 @@ def verify(project_root, directory): def main(): + if sys.argv[1:] == ['--phase-worker']: + return phase_worker() parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--project-root', type=Path, required=True) args = parser.parse_args() try: require(os.environ.get('BUILD_OUTPUT_DIR'), 'BUILD_OUTPUT_DIR required') - result = verify(args.project_root, Path(os.environ['BUILD_OUTPUT_DIR']) / 'p28-02-eq-evidence') + with check.memo_scope(): + result = verify(args.project_root, Path(os.environ['BUILD_OUTPUT_DIR']) / 'p28-02-eq-evidence') print(json.dumps({'status': 'verified', 'result': result}, ensure_ascii=False, allow_nan=False)) return 0 except (ValueError, OSError, KeyError, TypeError, IndexError) as error: