P28-02-EQ: 意味を保つ軽量化:同等性の先行固定(harness)

This commit is contained in:
Agent Control Runner
2026-10-22 03:38:46 +09:00
parent 6f75c9a051
commit b2dbda8c95
21 changed files with 3038 additions and 380 deletions
@@ -577,3 +577,133 @@ failures respectively; all earlier evidence remains unchanged.
This table records the required implementation targets, not completed validation. This table records the required implementation targets, not completed validation.
No native or formal validation is authorized for this worker. 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/<sha>.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.
@@ -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の証拠として提出する。 S4 の保持済み実装に対する残作業を完了し、局所unitの証拠として提出する。
正式受入は保留、原本加算0。 正式受入は保留、原本加算0。
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
ff6fc0065772434f9ca35a1267e24d199057e339869a868942a41829327503bb UNIT_OUTPUT.txt 7372a382cc2f3c090013874d8f28f17cad4cb1b965ad86c86de7a21fc2d851a8 UNIT_OUTPUT.txt
@@ -1,154 +1,167 @@
test_all_operations (__main__.Diffs) ... ok test_all_operations (__main__.Diffs.test_all_operations) ... ok
test_invalid_operations (__main__.Diffs) ... ok test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok
test_reconstruction_and_proofs (__main__.Diffs) ... ok test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok
test_attempt_to_normalize_violation_rejected (__main__.Normalization) ... 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) ... 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) ... ok test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok
test_f3_keeps_relative_paths_and_words (__main__.Normalization) ... ok test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok
test_guard_mismatch (__main__.Normalization) ... ok test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok
test_nested_lookalike_is_not_eligible (__main__.Normalization) ... 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) ... 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) ... ok test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok
test_prefix_boundary_and_reserved_token (__main__.Normalization) ... 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) ... 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) ... ok test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok
test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization) ... 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) ... 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) ... ok test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok
test_all_events_beyond_recent_window (__main__.Reports) ... ok test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok
test_all_semantic_layers_compared (__main__.Reports) ... ok test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok
test_digest_alone_is_not_enough (__main__.Reports) ... ok test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok
test_event_original_types_and_order (__main__.Reports) ... ok test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok
test_event_start_and_ids (__main__.Reports) ... ok test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok
test_exclusions_keep_actual_values (__main__.Reports) ... ok test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok
test_handler_sets_and_counts (__main__.Reports) ... ok test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok
test_observation_full_layers (__main__.Reports) ... ok test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok
test_product_digest_self_consistency (__main__.Reports) ... ok test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok
test_same_report (__main__.Reports) ... ok test_same_report (__main__.Reports.test_same_report) ... ok
test_scenario_existing_failures_preserved (__main__.Reports) ... ok test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok
test_timing_digest_rule (__main__.Reports) ... ok test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok
test_unknown_or_missing_fields_rejected (__main__.Reports) ... ok test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok
test_violation_presence_and_difference (__main__.Reports) ... ok test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok
test_O_cadence (__main__.S2Format) ... ok test_O_cadence (__main__.S2Format.test_O_cadence) ... ok
test_counter_and_published_ids_binding (__main__.S2Format) ... 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) ... 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) ... ok test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok
test_event_no_drop_duplicate_remap (__main__.S2Format) ... ok test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok
test_event_original_field_types_mandatory (__main__.S2Format) ... ok test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok
test_guard_actual_not_assumed (__main__.S2Format) ... ok test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok
test_harness_digest_allows_only_dictionary_order (__main__.S2Format) ... 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) ... 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) ... ok test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok
test_lifecycle_guard_errors_and_boundaries (__main__.S2Format) ... 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) ... 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) ... 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) ... ok test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok
test_raw_observation_binding (__main__.S2Format) ... ok test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok
test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format) ... 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) ... 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) ... ok test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok
test_stdout_exactly_one_line (__main__.S2Format) ... ok test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok
test_autoload_class_and_script_import (__main__.S2Static) ... ok test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok
test_comment_does_not_supply_proof (__main__.S2Static) ... ok test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok
test_current_probe (__main__.S2Static) ... ok test_current_probe (__main__.S2Static.test_current_probe) ... ok
test_full_state_and_godot_roundtrip_required (__main__.S2Static) ... 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) ... 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) ... 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) ... ok test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok
test_textual_stringname_ordering (__main__.S2Static) ... ok test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok
test_unlisted_live_method_and_late_preparation (__main__.S2Static) ... ok test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok
test_variant_inference (__main__.S2Static) ... ok test_variant_inference (__main__.S2Static.test_variant_inference) ... ok
test_X_recent_limit_and_counter (__main__.S3a) ... ok test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok
test_duplicate_JSON_key_rejected (__main__.S3a) ... ok test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok
test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a) ... 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) ... 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) ... 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) ... 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) ... ok test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... ok
test_event_count_defect_not_silently_accepted (__main__.S3a) ... ok test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... ok
test_exact_phase_keys (__main__.S3a) ... ok test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok
test_interval_gap_rejected (__main__.S3a) ... ok test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok
test_invalid_input_accepted_is_not_PASS (__main__.S3a) ... 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) ... ok test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... ok
test_operation_moved_outside_scenario_rejected (__main__.S3a) ... ok test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok
test_package_fixed_mutations_and_hashes (__main__.S3a) ... ok test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok
test_probe_connection_required (__main__.S3a) ... ok test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok
test_rejection_mutation_retained_as_defect (__main__.S3a) ... ok test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... ok
test_save_refusal_makes_load_NA (__main__.S3a) ... ok test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... ok
test_scenario_mutation_static_boundary (__main__.S3a) ... ok test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok
test_signals_all_minutes_and_order (__main__.S3a) ... 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) ... ok test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok
test_NA_not_PASS (__main__.S3b) ... ok test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok
test_aliasing_cannot_be_PASS (__main__.S3b) ... ok test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok
test_aliasing_missing_record (__main__.S3b) ... ok test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok
test_all_paths_and_ownership_R_O (__main__.S3b) ... 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) ... ok test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok
test_digest_materialization_required (__main__.S3b) ... ok test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok
test_empty_registry_not_NA (__main__.S3b) ... ok test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok
test_event_count_mismatch (__main__.S3b) ... ok test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok
test_facts_normalization_is_reversible (__main__.S3b) ... ok test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok
test_first_placeable_choice (__main__.S3b) ... ok test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok
test_interval_gap (__main__.S3b) ... ok test_interval_gap (__main__.S3b.test_interval_gap) ... ok
test_joined_s3b_self_comparison (__main__.S3b) ... ok test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok
test_load_X_not_digest_only (__main__.S3b) ... ok test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok
test_load_extra_event (__main__.S3b) ... ok test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok
test_missing_path_and_combination (__main__.S3b) ... ok test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok
test_missing_preview_observation (__main__.S3b) ... ok test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok
test_missing_trial (__main__.S3b) ... ok test_missing_trial (__main__.S3b.test_missing_trial) ... ok
test_nested_mutation_required (__main__.S3b) ... ok test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok
test_no_staleness_assertion_for_non_tick (__main__.S3b) ... 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) ... ok test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok
test_path_order (__main__.S3b) ... ok test_path_order (__main__.S3b.test_path_order) ... ok
test_restore_extra_event (__main__.S3b) ... ok test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok
test_signal_facts_cannot_be_dropped (__main__.S3b) ... ok test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok
test_sort_rule_rejected (__main__.S3b) ... ok test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok
test_static_handler_and_outside_mutations (__main__.S3b) ... ok test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok
test_unpreviewed_choice (__main__.S3b) ... ok test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok
test_wrong_registry_in_either_role (__main__.S3b) ... ok test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok
test_checkout_temp_destination_rejected (__main__.S4Bundle) ... ok test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok
test_existing_output_never_overwritten (__main__.S4Bundle) ... ok test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok
test_import_closure_rejections (__main__.S4Bundle) ... ok test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ok
test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle) ... 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) ... 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) ... 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) ... 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) ... 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) ... 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) ... 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) ... 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) ... 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) ... ok test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic.test_gate_none_success_not_boolean) ... ok
test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic) ... 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) ... 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) ... ok test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok
test_check_rejects_false_all_errors (__main__.S4ProbeAdmission) ... 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) ... 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) ... 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) ... ok test_nine_original_destinations_required (__main__.S4RemainingBindings.test_nine_original_destinations_required) ... ok
test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings) ... 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) ... 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) ... 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) ... 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) ... ok test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok
test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings) ... 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) ... ok test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok
test_fixed_scenario_paths (__main__.SourcesAndMetrics) ... ok test_blob_transport_is_verified_and_bound (__main__.S5.test_blob_transport_is_verified_and_bound) ... ok
test_metric_ranges (__main__.SourcesAndMetrics) ... 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_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics) ... ok test_compare_and_digest_equal_s1_reference (__main__.S5.test_compare_and_digest_equal_s1_reference) ... ok
test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics) ... ok test_event_stream_replacement_is_existing_defect_not_pass (__main__.S5.test_event_stream_replacement_is_existing_defect_not_pass) ... ok
test_array_order_and_all_differences (__main__.TypedTrees) ... ok test_every_checkpoint_full_tree_verified_like_inline (__main__.S5.test_every_checkpoint_full_tree_verified_like_inline)
test_components_and_sha (__main__.TypedTrees) ... ok S5 drops the diff chain: each checkpoint carries a complete tree, verified alone. ... ok
test_deep_state_not_truncated (__main__.TypedTrees) ... ok test_fast_normalization_equals_original (__main__.S5.test_fast_normalization_equals_original) ... ok
test_duplicate_textual_keys_rejected (__main__.TypedTrees) ... 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_exact_and_types (__main__.TypedTrees) ... ok test_lifecycle_binds_replacements_guard_text_and_blobs (__main__.S5.test_lifecycle_binds_replacements_guard_text_and_blobs) ... ok
test_nested_order_all_paths (__main__.TypedTrees) ... ok test_manifest_numbers_keep_their_type (__main__.S5.test_manifest_numbers_keep_their_type) ... ok
test_nonstring_keys_keep_types_and_values (__main__.TypedTrees) ... ok test_memo_scope_same_results_and_restores_gc (__main__.S5.test_memo_scope_same_results_and_restores_gc) ... ok
test_unsupported_and_malformed (__main__.TypedTrees) ... 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 OK
exit_code=0
wall_seconds=293.5742515
@@ -0,0 +1 @@
f52106391229dbf46d82e7dd9cdbc290b65f2ae9ed0e2a49d2cd4933c8af4ade 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
@@ -0,0 +1 @@
67b91e02e30046cefecf260aa2eb284e0bab5ae2d9728f674eea5bd06e38db0c 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
@@ -0,0 +1 @@
e2833369d98a4cf6600fcd98dcf271c2172e8b4a5fbb366eae4fcebc5ea8921d 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
@@ -0,0 +1 @@
f8b652c4f0cdc52c061cd58f1a1334e755c22d5dc206225a5cf2d4863a780c85 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)
@@ -0,0 +1 @@
7372a382cc2f3c090013874d8f28f17cad4cb1b965ad86c86de7a21fc2d851a8 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
@@ -6,6 +6,7 @@ copies using S1's sole F1-F3 implementation; it does not manufacture Godot proof
import base64 import base64
import ast import ast
import copy import copy
import gc
import hashlib import hashlib
import json import json
from pathlib import Path from pathlib import Path
@@ -18,7 +19,7 @@ import compare_versions as eq
def check_python_imports(text): def check_python_imports(text):
"""Closed EQ/stdlib import screen; dynamic file loading is separately pinned by run_eq.""" """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', 'os', 'pathlib', 're', 'shutil', 'struct', 'subprocess', 'sys', 'tempfile',
'unittest', 'uuid', 'check_eq', 'compare_versions', 'run_eq', 'verify_eq'} 'unittest', 'uuid', 'check_eq', 'compare_versions', 'run_eq', 'verify_eq'}
tree = ast.parse(text) tree = ast.parse(text)
@@ -54,6 +55,7 @@ def integer(value):
def fields(tree): def fields(tree):
tree = eq.materialize(tree)
eq.require(tree[0] == 27, 'dictionary required') eq.require(tree[0] == 27, 'dictionary required')
result = {} result = {}
for key, value in tree[1]: for key, value in tree[1]:
@@ -62,7 +64,21 @@ def fields(tree):
return result return result
_COUNTS = {}
def counts(tree): 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']) runtime = fields(fields(fields(tree)['systems'])['runtime'])
counters = fields(fields(runtime['id_generator'])['counters']) counters = fields(fields(runtime['id_generator'])['counters'])
# Product get_counter has an explicit zero default for an absent prefix. # 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, Key nodes remain exact. Only Dictionary enumeration order is canonicalized,
matching S1 fallback; floats, metadata and Array order remain untouched. matching S1 fallback; floats, metadata and Array order remain untouched.
""" """
def canonical(node): return eq.canonical_digest(tree)
result = copy.deepcopy(node)
if node[0] == 27:
names = [eq._key_text(k) for k, _ in node[1]] def edit_at(tree, names, change):
eq.require(len(set(names)) == len(names), 'digest textual key collision') """Copy of tree with the node at the textual path replaced by change(node).
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: Only the dictionaries on the path are copied; all other nodes are shared, so the
result[1] = [canonical(v) for v in node[1]] result has the same JSON as deepcopy-then-mutate. Inputs are never modified."""
return result tree = eq.materialize(tree)
eq.validate_tree(tree) if not names:
return eq.tree_sha(canonical(tree)) 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): def join_report(raw):
"""Return (joined report, reversible normalization evidence), never edit 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) joined = copy.deepcopy(raw)
proofs = [] proofs = []
trees = eq.reconstruct_checkpoints(raw['checkpoints']) trees = eq.reconstruct_checkpoints(raw['checkpoints'])
for cp, (s, latest) in zip(joined['checkpoints'], trees): for cp, (s, latest) in zip(joined['checkpoints'], trees):
row = {} 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']) normalized, side = eq.normalize_tree(tree, raw['profile_root'])
restored = eq.restore_tree(normalized, side, 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') 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)} 'restored_tree_sha': eq.tree_sha(restored)}
row = {'S': row['S'], 'latest': row['latest']}
expected = row['latest']['normalized_digest'] expected = row['latest']['normalized_digest']
eq.require(cp['harness_normalized_digest'] in (None, expected), 'forged normalized digest') eq.require(cp['harness_normalized_digest'] in (None, expected), 'forged normalized digest')
cp['harness_normalized_digest'] = expected cp['harness_normalized_digest'] = expected
proofs.append(row) proofs.append(row)
for i, scenario in enumerate(raw.get('scenarios', [])): 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'] joined['scenarios'][i]['checkpoints'] = derived['checkpoints']
proofs.append({'scenario': scenario['id'], 'checkpoints': scenario_proofs}) proofs.append({'scenario': scenario['id'], 'checkpoints': scenario_proofs})
if scenario['id'].startswith(('E4/', 'E5/')): if scenario['id'].startswith(('E4/', 'E5/')):
@@ -124,6 +198,10 @@ def join_report(raw):
def check_measurement(raw, measurement): 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(type(measurement) is dict, 'measurement connection inventory missing')
eq.require(set(measurement) == {'probe_object', 'connections', 'start_minute'}, 'measurement inventory fields') eq.require(set(measurement) == {'probe_object', 'connections', 'start_minute'}, 'measurement inventory fields')
eq.require(measurement['start_minute'] == raw['checkpoints'][0]['minute'], 'inventory B1 minute') 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(set(row) == {'object', 'method', 'flags'}, 'connection fields')
eq.require(sum(r['object'] == probe and r['method'] == method for r in rows) == 1, eq.require(sum(r['object'] == probe and r['method'] == method for r in rows) == 1,
'missing/duplicate probe connection: ' + signal) 'missing/duplicate probe connection: ' + signal)
expected = [{'signal': signal, 'minute': cp['minute']} for cp in raw['checkpoints'][1:] # S5: one tick-end publish per measured minute, plus one publish after each run of
for signal in ('snapshot_published', 'state_updated')] # commands completed by a paused submit (R minute 648: 50 commands, 51 pairs). The
eq.require(eq.exact(raw['signal_order'], expected), 'exact measured signal pairs/checkpoint minutes required') # 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): 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(type(c['_mvp_manifest_cache_generation']) is int, 'cache generation')
eq.require(c['catalog_is_current'] is True, 'unfilled checkpoint cache') eq.require(c['catalog_is_current'] is True, 'unfilled checkpoint cache')
ids = [c['catalog_instance_id'], c['current_catalog_instance_id']] 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.require(ids[0] == ids[1], 'cache identity contradiction')
eq.validate_tree([29, c['_mvp_manifest_cache_bytes']]) eq.validate_tree([29, c['_mvp_manifest_cache_bytes']])
trees = eq.reconstruct_checkpoints(cps) 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(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') eq.require(end['counter'] - begin['counter'] == len(event_ids), 'event counter delta')
for row in raw['signal_order']: 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') eq.require(type(row['minute']) is int and start_minute <= row['minute'] <= start_minute+30, 'signal minute')
check_measurement(raw, measurement) check_measurement(raw, measurement)
out.update(ok=True, normalization=proofs, comparison=compared) 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 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): def check_observation(cp, evidence):
"""Bind retained full bytes to report SHA, not merely asserted equality.""" """Bind retained full bytes to report SHA, not merely asserted equality."""
eq.require(set(evidence) == {'before', 'after'}, 'observation evidence fields') 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(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') 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'): for layer in ('S', 'K', 'C'):
eq.validate_tree([29, row[layer]]) actual = raw_layer_sha(row[layer])
actual = sha(base64.b64decode(row[layer], validate=True))
eq.require(actual == row['sha'][layer] == cp['observation'][side][layer], 'raw layer SHA mismatch') 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') eq.require(all(evidence['before'][k] == evidence['after'][k] for k in ('S', 'K', 'C')), 'raw observation bytes differ')
return True return True
@@ -245,15 +340,22 @@ def strict_json(text):
def check_lifecycle(raw, lifecycle, guard): def check_lifecycle(raw, lifecycle, guard):
"""S2 startup proof; S4 must supply the separately SHA-bound guard receipt.""" """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['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'}[lifecycle['stage']], 'probe did not finish 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'): if lifecycle['stage'] in ('S3a', 'S3b', 'S5'):
check_measurement(raw, lifecycle['measurement']) check_measurement(raw, lifecycle['measurement'])
check_e3(dict(raw, scenarios=raw['scenarios'][:18]), lifecycle['e3'], lifecycle['e3_b1']) 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']) 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'] 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']) root = eq._profile(raw['profile_root'])
eq.require(root == binding['live_root'] == guard['actual_user_data_dir'], 'live/guard profile contradiction') eq.require(root == binding['live_root'] == guard['actual_user_data_dir'], 'live/guard profile contradiction')
eq.require(hex_sha(binding['sha256']), 'guard SHA missing') eq.require(hex_sha(binding['sha256']), 'guard SHA missing')
@@ -264,8 +366,7 @@ def check_lifecycle(raw, lifecycle, guard):
row = lifecycle[label] row = lifecycle[label]
eq.require(row['kernel']['paused'] is True and row['kernel']['absolute_minute'] == minute, 'lifecycle minute: '+label) eq.require(row['kernel']['paused'] is True and row['kernel']['absolute_minute'] == minute, 'lifecycle minute: '+label)
for layer in ('S', 'K', 'C'): for layer in ('S', 'K', 'C'):
eq.validate_tree([29, row[layer]]) eq.require(raw_layer_sha(row[layer]) == row['sha'][layer], 'lifecycle raw SHA')
eq.require(sha(base64.b64decode(row[layer], validate=True)) == row['sha'][layer], 'lifecycle raw SHA')
for label in ('before_main', 'after_main', 'frame1', 'frame2'): 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') eq.require(type(lifecycle[label]) is dict and lifecycle[label]['kernel']['paused'] is True, 'startup frame evidence')
ops = lifecycle['ops'] 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') 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. # A digest alone, stub proof, or truncated checkpoint producer is not admitted.
required = { required = {
'transport': ('var_to_bytes(value)', 'var_to_bytes(decode(tree))', 'raw == rebuilt', 'encode(value)', 'hashed(json_text(tree).to_utf8_buffer())'), '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)'),
'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('), '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'), '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()'), '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"'), '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, [])) body = '\n'.join(functions.get(name, []))
for snippet in snippets: for snippet in snippets:
if snippet not in body: errors.append('missing E3 boundary/call: '+name+': '+snippet) 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: if code.count('run_e3()') != 2 or code.count('e3_prepare_package(') != 2:
errors.append('unexpected scenario dispatch') errors.append('unexpected scenario dispatch')
scenario_body = '\n'.join(functions.get('run_e3', [])) scenario_body = '\n'.join(functions.get('run_e3', []))
@@ -396,42 +505,50 @@ def node_at(tree, *names):
def e3_expected_input(b1, kind): def e3_expected_input(b1, kind):
value = copy.deepcopy(b1) value = eq.materialize(b1)
if kind == 'missing_key': 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'): elif kind in ('wrong_type', 'out_of_range'):
node_at(value, 'clock', 'absolute_minute' if kind == 'wrong_type' else 'speed')[:] = ( value = edit_at(value, ['clock', 'absolute_minute' if kind == 'wrong_type' else 'speed'],
[4, 'x'] if kind == 'wrong_type' else [2, 9]) lambda node: [4, 'x'] if kind == 'wrong_type' else [2, 9])
elif kind in ('duplicate_published_ids', 'duplicate_facility'): elif kind in ('duplicate_published_ids', 'duplicate_facility'):
nodes = (node_at(value, 'systems', 'runtime', 'domain_events', 'published_ids') if kind == 'duplicate_published_ids' names = (['systems', 'runtime', 'domain_events', 'published_ids'] if kind == 'duplicate_published_ids'
else node_at(value, 'systems', 'runtime', 'facilities', 'instances')) else ['systems', 'runtime', 'facilities', 'instances'])
if nodes[1]: def duplicate(nodes):
nodes[1].append(copy.deepcopy(nodes[1][0])) if nodes[1]:
else: return [nodes[0], nodes[1] + [copy.deepcopy(nodes[1][0])]] + nodes[2:]
eq.require(kind == 'duplicate_facility', 'no B1 published ID to duplicate') eq.require(kind == 'duplicate_facility', 'no B1 published ID to duplicate')
return nodes
value = edit_at(value, names, duplicate)
return value return value
def e3_x(before, event): def e3_x(before, event):
value = copy.deepcopy(before) value = edit_at(before, ['systems', 'runtime', 'id_generator', 'counters', 'domain_event'],
counter = node_at(value, 'systems', 'runtime', 'id_generator', 'counters', 'domain_event') lambda counter: [counter[0], counter[1] + 1] + counter[2:])
counter[1] += 1
domain = node_at(value, 'systems', 'runtime', 'domain_events') domain = node_at(value, 'systems', 'runtime', 'domain_events')
ids = fields(domain)['published_ids'] fields(domain)['published_ids']
ids[1].append(copy.deepcopy(fields(event)['event_id'])) event_id = copy.deepcopy(fields(event)['event_id'])
ids[1].sort(key=lambda n: n[1])
recent = fields(domain)['recent_events'] recent = fields(domain)['recent_events']
dropped = copy.deepcopy(recent[1][0]) if len(recent[1]) == 256 else None dropped = copy.deepcopy(recent[1][0]) if len(recent[1]) == 256 else None
recent[1].append(copy.deepcopy(event)) def published(ids):
if len(recent[1]) > 256: children = ids[1] + [event_id]
del recent[1][0] 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 return value, dropped
def e3_raw_observation(row): def e3_raw_observation(row):
cp = row['checkpoint'] cp = row['checkpoint']
check_observation(cp, row['raw']) 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') eq.require(cp['S']['raw_bytes_sha'] == row['raw']['before']['sha']['S'], 'E3 S transport/raw')
for layer in ('K', 'C'): for layer in ('K', 'C'):
proof = row[layer+'_tree'] proof = row[layer+'_tree']
@@ -449,7 +566,8 @@ def e3_package_check(package, kind):
out[name] = None out[name] = None
continue continue
eq.require(set(item) == {'bytes', 'sha256'}, 'package file fields') 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') eq.require(sha(content) == item['sha256'], 'package file SHA')
out[name] = content out[name] = content
return out return out
@@ -482,6 +600,10 @@ def e3_package_check(package, kind):
def check_e3(raw, evidence, b1_record): 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. """Separate malformed evidence from observed existing-defect candidates.
Returns all actual typed differences. Never promote NA or a defect to PASS. 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] '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']): 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.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 expected_count = 1 if api == 'load_from_path' and invoked else 0
if len(events) != expected_count: if len(events) != expected_count:
defects.append({'kind': 'event_count', 'expected': expected_count, 'actual': len(events)}) 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]: 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']}) defects.append({'kind': 'load_restore_committed', 'actual': data['recovery_report']})
expected_state, dropped = e3_x(before, events[0]) 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_expected, expected_side = eq.normalize_tree(expected_state, raw['profile_root'])
normalized_after, actual_side = eq.normalize_tree(after, raw['profile_root']) normalized_after, actual_side = eq.normalize_tree(after, raw['profile_root'])
delta = eq.compare_trees(normalized_expected, normalized_after) 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'] left, right = ev['begin']['raw']['before'], ev['end']['raw']['after']
if left[layer] != right[layer]: if left[layer] != right[layer]:
defects.append({'kind': layer+'_changes', 'before': left[layer], 'after': 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. # Dictionary APIs require byte identity, not normalization/order fallback.
if expected_count == 0 and ev['begin']['raw']['before']['S'] != ev['end']['raw']['after']['S']: 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']}) 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}, 'expected': {'ok': False if applied else None, 'code': expected_code, 'error': expected_error, 'event_count': expected_count},
'actual': copy.deepcopy(result), 'actual': copy.deepcopy(result),
'status': 'existing-defect-candidate' if defects else ('recorded' if applied else 'NA'), '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}) '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', 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)}, '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. # S3b additions. Raw evidence is authoritative; outcome labels are derived.
E4_PATHS = ('build_facility', 'tick', 'restore_snapshot', 'load_from_path', 'rejected_command') E4_PATHS = ('build_facility', 'tick', 'restore_snapshot', 'load_from_path', 'rejected_command')
E5_TARGETS = ('create_snapshot', 'latest_snapshot', 'snapshot_published', 'state_updated') 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) normal, side = eq.normalize_tree(tree, profile_root)
eq.require(eq.exact(eq.restore_tree(normal, side, profile_root), tree), 'fact normalization roundtrip') eq.require(eq.exact(eq.restore_tree(normal, side, profile_root), tree), 'fact normalization roundtrip')
proofs.append({'path': path, 'sidecar': side, 'raw': item}) proofs.append({'path': path, 'sidecar': side, 'raw': item})
return normal return describe(normal)
return tree return describe(tree)
if isinstance(item, dict): if isinstance(item, dict):
return {k: walk(v, path+[k]) for k, v in item.items()} return {k: walk(v, path+[k]) for k, v in item.items()}
if isinstance(item, list): if isinstance(item, list):
@@ -713,6 +957,10 @@ def check_command(detail):
def check_e45(raw, evidence, previous): 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 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')] for l in ('dict', 'array') for d in ('outside_in', 'inside_out')]
scenarios = raw['scenarios'][18:] scenarios = raw['scenarios'][18:]
@@ -730,8 +978,8 @@ def check_e45(raw, evidence, previous):
common = {'save_budget_before', 'save_budget_after', 'signals'} common = {'save_budget_before', 'save_budget_after', 'signals'}
specific = { specific = {
'build_facility': {'source', 'raw_ids', 'count', 'sorted_ids', 'sort_rule', 'bounds', 'cell_rule', 'trials', 'chosen', 'applicability', 'reason', 'observation_before', 'observation_after'}, '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'}, 'tick': set(), 'restore_snapshot': {'input', 'return', 'event_stream'},
'load_from_path': {'input', 'path', 'save_return', 'package_before', 'package_after', 'return', 'latest_save_operation_result'}, 'load_from_path': {'input', 'path', 'save_return', 'package_before', 'package_after', 'return', 'latest_save_operation_result', 'event_stream'},
'rejected_command': {'submit', 'command_result'}, 'rejected_command': {'submit', 'command_result'},
}[scenario['operation']] }[scenario['operation']]
if scenario['operation'] == 'build_facility' and detail.get('chosen') is not None: specific |= {'submit', 'command_result'} 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') 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(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') eq.require(eq.exact(result['facts'], e45_facts(detail)), 'E4/E5 facts binding/aliasing record missing')
for signal in ev['signals']: command, other = check_publishes(ev['signals'], event_numbers(events), counts(trees[0])[0]['counter'], counts(trees[-1])[0]['counter'],
eq.require(set(signal) == {'signal', 'minute'} and signal['signal'] in ('snapshot_published', 'state_updated') next_command_id(trees[0]), next_command_id(trees[-1]))
and obs[0]['checkpoint']['minute'] <= signal['minute'] <= obs[-1]['checkpoint']['minute'], 'scenario signal interval') eq.require(all(obs[0]['checkpoint']['minute'] <= m <= obs[-1]['checkpoint']['minute'] for m, _ in command + other), 'scenario signal interval')
eq.require(len(ev['signals']) % 2 == 0, 'scenario signal pair missing') op = scenario['operation']
for i in range(0, len(ev['signals']), 2): if scenario['id'].startswith('E4/'):
a, b = ev['signals'][i:i+2] expected_other = int(detail.get('chosen') is not None) if op == 'build_facility' else 1
eq.require(a['signal'] == 'snapshot_published' and b == {'signal': 'state_updated', 'minute': a['minute']}, 'scenario signal order') else:
expected_other = 2 if op == 'outside_in' else 3
eq.require(len(other) == expected_other, 'scenario tick/restore publish missing/extra')
defects = [] defects = []
differences = {} differences = {}
for name in ('S', 'latest'): for name in ('S', 'latest'):
a, b = [eq._transport(o['checkpoint'][name], None) for o in (obs[0], obs[-1])] a, b = [eq._transport(o['checkpoint'][name], None) for o in (obs[0], obs[-1])]
differences[name] = eq.compare_trees(a, b) differences[name] = eq.compare_trees(a, b)
for name in ('K', 'C'): 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'] op = scenario['operation']
begin_count, begin_ids = counts(trees[0]) begin_count, begin_ids = counts(trees[0])
end_count, end_ids = counts(trees[-1]) 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') 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 len(obs) == 3: eq.require(obs[1]['checkpoint']['minute'] == obs[0]['checkpoint']['minute'], 'submit checkpoint delayed')
if op in ('restore_snapshot', 'load_from_path'): if op in ('restore_snapshot', 'load_from_path'):
defects.extend(stream_defects(detail['event_stream'], events))
p = eq._transport(detail['input'], None) p = eq._transport(detail['input'], None)
eq.require(eq.exact(p, trees[0]), 'E4 input not current deep copy') eq.require(eq.exact(p, trees[0]), 'E4 input not current deep copy')
actual = eq._transport(detail['return'], None) 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'], package = {'save_return': detail['save_return'], 'original': detail['package_before'],
'modified': detail['package_before'], 'after_operation': detail['package_after']} 'modified': detail['package_before'], 'after_operation': detail['package_after']}
e3_package_check(package, 'good') 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']) left, left_side = eq.normalize_tree(expected, raw['profile_root'])
right, right_side = eq.normalize_tree(trees[-1], raw['profile_root']) right, right_side = eq.normalize_tree(trees[-1], raw['profile_root'])
delta = eq.compare_trees(left, right) delta = eq.compare_trees(left, right)
eq.require(delta['equal'], 'E4 restore/load X(P,event) mismatch') 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': if op == 'rejected_command':
command = check_command(detail) command = check_command(detail)
eq.require(command['code'] != [4, 'OK'], 'nonchanging command not rejected') 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) expected_held = eq._transport(baseline['S' if target == 'create_snapshot' else 'latest'], None)
eq.require(eq.exact(held, expected_held), 'E5 returned target binding') eq.require(eq.exact(held, expected_held), 'E5 returned target binding')
if direction == 'outside_in': if direction == 'outside_in':
expected = copy.deepcopy(held)
if location == 'dict': if location == 'dict':
n = node_at(expected, 'systems', 'runtime', 'id_generator', 'counters') def marked(n):
eq.require('__p28_eq_probe' not in fields(n), 'E5 marker already present') eq.require('__p28_eq_probe' not in fields(n), 'E5 marker already present')
n[1].append([[4, '__p28_eq_probe'], [2, 1]]) return [n[0], n[1] + [[[4, '__p28_eq_probe'], [2, 1]]]] + n[2:]
expected = edit_at(held, ['systems', 'runtime', 'id_generator', 'counters'], marked)
else: 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') eq.require(eq.exact(expected, after_held), 'E5 fixed nested mutation absent/extra')
for layer in ('S', 'latest'): for layer in ('S', 'latest'):
delta = eq.compare_trees(eq._transport(obs[1]['checkpoint'][layer], None), eq._transport(obs[2]['checkpoint'][layer], None)) 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 differences['held'] = delta
if not delta['equal']: defects.append({'kind': 'internal_mutation_of_returned_value', 'comparison': delta}) if not delta['equal']: defects.append({'kind': 'internal_mutation_of_returned_value', 'comparison': delta})
for layer in ('K', 'C'): 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') 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'), 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'], 'defects': defects, 'differences': differences, 'signals': ev['signals'],
@@ -4,9 +4,12 @@ Paths are lists of ['key', key_node] / ['index', integer] segments.
Transport validation is distinct from product acceptance (see IMPLEMENTATION.md). Transport validation is distinct from product acceptance (see IMPLEMENTATION.md).
""" """
import base64 import base64
from collections import OrderedDict
import copy import copy
import gzip
import hashlib import hashlib
import json import json
from pathlib import Path
import re 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')) 'scripts/world/path_service.gd', 'scripts/core/command_queue.gd'))
SOURCE_SHA = '651f703621c8ed06a724709f75d8bc05b1eef2988c3b3d5d3466096a3f759bf0' SOURCE_SHA = '651f703621c8ed06a724709f75d8bc05b1eef2988c3b3d5d3466096a3f759bf0'
TOKEN = '{PRIVATE_USER_ROOT}/' 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/<sha>.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): 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') 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): 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) 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): 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] pending = [node]
pop, extend = pending.pop, pending.extend
while pending: while pending:
n = pending.pop() n = pop()
require(type(n) is list and n and type(n[0]) is int, 'invalid typed node') if type(n) is not list or not n or type(n[0]) is not int:
raise EvidenceError('invalid typed node')
t = n[0] t = n[0]
require(t not in (23, 24, 25, 26) and 0 <= t <= 38, 'unsupported Variant type') if t in _BAD_TYPES or not 0 <= t <= 38:
require(len(n) == (1 if t == 0 else 3 if t in (27, 28) else 2), 'node arity') 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: if t == 0:
continue continue
p = n[1] p = n[1]
if t in (1, 2): if t == 2:
require(type(p) is int and (p in (0, 1) if t == 1 else -(2**63) <= p < 2**63), 'integer payload') 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: elif t == 3:
require(type(p) is str and re.fullmatch('[0-9a-f]{16}', p), 'float must be f64 LE hex') if type(p) is not str or not _HEX16(p):
elif t in (4, 21, 22): raise EvidenceError('float must be f64 LE hex')
require(type(p) is str, 'string payload') elif t == 1:
if type(p) is not int or p not in (0, 1):
raise EvidenceError('integer payload')
elif t == 29: elif t == 29:
require(type(p) is str, 'byte array payload') if type(p) is not str:
raise EvidenceError('byte array payload')
try: try:
require(base64.b64encode(base64.b64decode(p, validate=True)).decode() == p, 'noncanonical base64') require(base64.b64encode(base64.b64decode(p, validate=True)).decode() == p, 'noncanonical base64')
except (ValueError, TypeError) as exc: except (ValueError, TypeError) as exc:
raise EvidenceError('invalid base64') from 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: else:
require(type(p) is list, 'component nodes') if type(p) is not list:
sizes = {5: 2, 6: 2, 7: 4, 8: 4, 9: 3, 10: 3, 11: 6, raise EvidenceError('component nodes')
12: 4, 13: 4, 14: 4, 15: 4, 16: 6, 17: 9, 18: 12, 19: 16, 20: 4} if t in _SIZES:
if t in sizes: if len(p) != _SIZES[t]:
require(len(p) == sizes[t], 'component count') raise EvidenceError('component count')
integer = t in (6, 8, 10, 13) expected = 2 if t in _INTEGER_COMPONENTS else 3
require(all(x and x[0] == (2 if integer else 3) for x in p), 'component type') if not all(x and x[0] == expected for x in p):
elif t in (30, 31, 32, 33, 34): raise EvidenceError('component type')
expected = {30: 2, 31: 2, 32: 3, 33: 3, 34: 4}[t] elif t in _PACKED:
require(all(x and x[0] == expected for x in p), 'packed element type') expected = _PACKED[t]
if not all(x and x[0] == expected for x in p):
raise EvidenceError('packed element type')
else: else:
require(all(x and x[0] == {35: 5, 36: 9, 37: 20, 38: 12}[t] for x in p), 'packed vector type') expected = _PACKED_VECTORS[t]
pending.extend(p) 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 return node
@@ -106,12 +321,14 @@ def _typed_info(info, dictionary):
def tree_sha(tree): def tree_sha(tree):
if known_sha(tree) is not None:
return known_sha(tree)
validate_tree(tree) validate_tree(tree)
return hashlib.sha256(json_bytes(tree)).hexdigest() return hashlib.sha256(json_bytes(tree)).hexdigest()
def _key_segment(key): def _key_segment(key):
return ['key', copy.deepcopy(key)] return ['key', _copy_tree(key)]
def _field(node, name): def _field(node, name):
@@ -182,9 +399,37 @@ def apply_diff(tree, operations):
return result 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): def reconstruct_checkpoints(checkpoints):
"""Each checkpoint has S and latest transport records; verify before use.""" """Each checkpoint has S and latest transport records; verify before use."""
require(type(checkpoints) is list and checkpoints, 'missing checkpoints') 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 result, previous = [], None
for cp in checkpoints: for cp in checkpoints:
s = _transport(cp['S'], previous) s = _transport(cp['S'], previous)
@@ -207,7 +452,29 @@ def _proof(record, tree):
require(record.get('tree_sha') == tree_sha(tree), 'tree SHA mismatch') 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): 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(('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') require(set(record) == {'tree' if 'tree' in record else 'diff', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'}, 'transport fields')
if 'tree' in record: if 'tree' in record:
@@ -221,34 +488,39 @@ def _transport(record, baseline):
def compare_trees(a, b): def compare_trees(a, b):
validate_tree(a); validate_tree(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 = [], [] differences, orders = [], []
# Recorded nodes are shared with the compared trees, which are never mutated.
def diff(path, x, y): 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): def walk(x, y, path):
if exact(x, y): # Both sides are validated typed trees, so == is exact JSON equality.
if x == y:
return return
if x[0] != y[0] or len(x) != len(y): if x[0] != y[0] or len(x) != len(y):
diff(path, x, y); return diff(path, x, y); return
t = x[0] 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]) diff(path + [['metadata', 0]], x[2], y[2])
if t == 27: if t == 27:
def keyed(n): def keyed(n):
d = {} d = {}
for k, v in n[1]: 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') require(text not in d, 'duplicate textual dictionary key')
d[text] = (k, v) d[text] = (k, v)
return d return d
dx, dy = keyed(x), keyed(y) 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]]: if set(dx) == set(dy) and list(dx) != list(dy):
orders.append(copy.deepcopy(path)) orders.append(_copy_tree(path))
for key in sorted(set(dx) | set(dy)): for key in sorted(set(dx) | set(dy)):
if key not in dx or key not in dy: if key not in dx or key not in dy:
diff(path + [['key', [4, key]]], dx.get(key), dy.get(key)); continue diff(path + [['key', [4, key]]], dx.get(key), dy.get(key)); continue
kx, vx = dx[key]; ky, vy = dy[key] kx, vx = dx[key]; ky, vy = dy[key]
p = path + [_key_segment(kx)] p = path + [_key_segment(kx)]
if not exact(kx, ky): if kx != ky:
diff(p + [['key_type', 0]], kx, ky) diff(p + [['key_type', 0]], kx, ky)
walk(vx, vy, p) walk(vx, vy, p)
elif t == 28 or t in range(5, 21) or t in range(30, 39): 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: else:
diff(path, x, y) diff(path, x, y)
walk(a, b, []) 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): def _key_text(key):
@@ -295,7 +600,7 @@ def _marker(key, node):
def _normalize(trees, profile_root, events): def _normalize(trees, profile_root, events):
root = _profile(profile_root) root = _profile(profile_root)
result = copy.deepcopy(trees) result = _copy_tree(trees)
side = {'entries': [], 'violations': [], 'profile_root': {**profile_root, 'matches_guard': True}} side = {'entries': [], 'violations': [], 'profile_root': {**profile_root, 'matches_guard': True}}
def visit(n, path, event_index, permitted_payload=False, event_root=False): def visit(n, path, event_index, permitted_payload=False, event_root=False):
if n[0] == 27: if n[0] == 27:
@@ -376,17 +681,119 @@ def normalize_events(events, profile_root):
return _normalize(events, profile_root, True) 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): 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 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): def _restore(normalized, sidecar, profile_root, events):
root = _profile(profile_root) root = _profile(profile_root)
require(type(sidecar) is dict and set(sidecar) == {'entries', 'violations', 'profile_root'}, 'sidecar fields') 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(sidecar['profile_root'] == {**profile_root, 'matches_guard': True}, 'sidecar profile')
require(type(sidecar['entries']) is list and type(sidecar['violations']) is list, 'sidecar lists') require(type(sidecar['entries']) is list and type(sidecar['violations']) is list, 'sidecar lists')
result = copy.deepcopy(normalized) result = _copy_tree(normalized)
seen = set() seen = set()
for entry in sidecar['entries']: for entry in sidecar['entries']:
require(set(entry) == {'event_index', 'path', 'key', 'original_node'} | ({'root'} if entry.get('key') == 'message' else set()), 'entry fields') 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): 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) 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') require(tree_sha(restore_tree(ny, sy, profiles[1])) == tree_sha(y), 'b restore SHA')
sidecars(path, sx, sy) sidecars(path, sx, sy)
comparison(path, nx, ny) comparison(path, nx, ny)
return sx, sy return sx, sy, nx, ny
def comparison(path, x, y): def comparison(path, x, y):
c = compare_trees(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'): for field in ('catalog_instance_id', 'current_catalog_instance_id'):
excluded(path + ['C', field], 'process-local instance ID', x['C'][field], y['C'][field]) 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): def scenario_facts(path, x, y):
# Joined S3b facts carry lossless typed trees as well as JSON metadata. # Joined S3b facts carry lossless typed trees as well as JSON metadata.
# Apply the same dictionary-order-only fallback to returned values. # 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: 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) validate_tree(x); validate_tree(y)
comparison(path, x, y) comparison(path, x, y)
@@ -563,7 +994,7 @@ def compare_reports(a, b):
if main: if main:
require(x['minute'] == xs[0]['minute']+i and y['minute'] == ys[0]['minute']+i, 'checkpoint cadence') 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) 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: if ownership and i == 2:
record(p + ['digest_self_consistent'], x['latest_digest'] == x['computed_latest_digest'], y['latest_digest'] == y['computed_latest_digest']) 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']: 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']) record(p + ['computed_latest_digest'], x['computed_latest_digest'], y['computed_latest_digest'])
if sx['entries'] or sy['entries']: 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') 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']) 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'}) out['digest_modes'].append({'path': p, 'mode': 'harness normalized digest'})
else: else:
@@ -1,5 +1,8 @@
extends SceneTree 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/<sha>.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 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"] 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 var clock: Variant
@@ -7,8 +10,12 @@ var runtime: Variant
var main: Variant var main: Variant
var role: String var role: String
var report: Dictionary = {} var report: Dictionary = {}
var lifecycle: Dictionary = {"stage": "S3b", "errors": [], "preparation": [], "files": []} var lifecycle: Dictionary = {"stage": "S5", "errors": [], "preparation": [], "files": []}
var previous_tree: Array = [] 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 finished: bool = false
var bound: bool = false var bound: bool = false
var recording: bool = false var recording: bool = false
@@ -44,11 +51,18 @@ func f64(value: float) -> String:
bytes.encode_double(0, value) bytes.encode_double(0, value)
return bytes.hex_encode() 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: func typed_info(value: Variant) -> Variant:
if typeof(value) == TYPE_ARRAY and value.is_typed(): 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(): 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 return null
func encode(value: Variant) -> Array: func encode(value: Variant) -> Array:
@@ -160,61 +174,44 @@ func decode(node: Array) -> Variant:
func decoded_basis(v: Array) -> Basis: 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])) 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: func transport(value: Variant) -> Dictionary:
var tree: Array = encode(value)
var raw: PackedByteArray = var_to_bytes(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 rebuilt: PackedByteArray = var_to_bytes(decode(tree))
var ok: bool = raw == rebuilt var ok: bool = raw == rebuilt
demand(ok, "Godot typed tree byte roundtrip failed") 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} var text: PackedByteArray = json_text(tree).to_utf8_buffer()
if text.size() <= INLINE_LIMIT:
func tree_diff(before: Array, after: Array, path: Array = []) -> Array: return {"tree": tree, "tree_sha": hashed(text), "raw_bytes_sha": raw_sha, "roundtrip_ok": ok}
if json_text(before) == json_text(after): return [] var record: Dictionary = {"tree": store_blob(text), "tree_sha": hashed(text), "raw_bytes_sha": raw_sha, "roundtrip_ok": ok}
# Exact subtree replacement also preserves changes of insertion order and metadata. transport_cache[raw_sha] = record
if before[0] != after[0] or before[0] not in [TYPE_DICTIONARY, TYPE_ARRAY] or json_text(before[2]) != json_text(after[2]): return record.duplicate(true)
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}
func kernel_state() -> Dictionary: func kernel_state() -> Dictionary:
var result: 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)} return {"S": hashed(value.s_bytes), "K": hashed(value.k_bytes), "C": hashed(value.c_bytes)}
func raw_layers(value: Dictionary) -> Dictionary: 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: func checkpoint() -> void:
var before: Dictionary = layers() 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_value: Dictionary = runtime.get("latest_snapshot")
var latest: Dictionary = transport(latest_value)
var latest_bytes: PackedByteArray = var_to_bytes(latest_value) var latest_bytes: PackedByteArray = var_to_bytes(latest_value)
var digest: String = runtime.get("latest_digest") var digest: String = runtime.get("latest_digest")
var computed: String = SimulationDigest.compute(latest_value) var computed: String = SimulationDigest.compute(latest_value)
var current_tree: Array = s.tree # Every checkpoint keeps a complete tree (blob reference); no diff chain.
if not previous_tree.is_empty(): s = diff_record(s, previous_tree) var latest: Dictionary = {}
if latest_bytes == before.s_bytes: 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} latest = {"same_as_S": true, "tree_sha": s.tree_sha, "raw_bytes_sha": s.raw_bytes_sha, "roundtrip_ok": s.roundtrip_ok}
else: latest = diff_record(latest, current_tree) else: latest = transport(latest_value)
var after: Dictionary = layers() 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(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") demand(digest == computed, "latest digest self-consistency")
@@ -291,12 +288,13 @@ func checkpoint() -> void:
write_evidence("checkpoint-%02d.json" % index, cp) write_evidence("checkpoint-%02d.json" % index, cp)
write_evidence("observation-%02d.json" % index, {"before": raw_layers(before), "after": raw_layers(after)}) write_evidence("observation-%02d.json" % index, {"before": raw_layers(before), "after": raw_layers(after)})
report.checkpoints.append(cp) report.checkpoints.append(cp)
previous_tree = current_tree last_snapshot_value = snapshot_value
func event_counts() -> Dictionary: func event_counts() -> Dictionary:
return {"counter": runtime.id_generator.get_counter(&"domain_event"), "published_ids_count": runtime.domain_events.get("_published_ids").size()} 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: func observe_event(event: Variant) -> void:
known_event_ids[str(event.event_id)] = true
if scenario_active: if scenario_active:
scenario_events.append(transport(event.to_dictionary())) scenario_events.append(transport(event.to_dictionary()))
return 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)}, "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)}}) "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: func observe_snapshot(_snapshot: Dictionary, _digest: String) -> void:
snapshot_reference = _snapshot snapshot_reference = _snapshot
record_signal("snapshot_published") record_signal("snapshot_published")
@@ -315,8 +335,11 @@ func observe_state(_snapshot: Dictionary) -> void:
state_reference = _snapshot state_reference = _snapshot
record_signal("state_updated") 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: 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) if scenario_active: scenario_signals.append(row)
elif recording: report.signal_order.append(row) elif recording: report.signal_order.append(row)
else: warm_signals.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 path: String = OS.get_environment("THE_TREE_PACK_PROFILE_REPORT")
var file: FileAccess = FileAccess.open(path, FileAccess.READ) var file: FileAccess = FileAccess.open(path, FileAccess.READ)
if not demand(file != null, "guard receipt missing"): return false 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() file.close()
if not demand(receipt is Dictionary and receipt.get("verified") == true, "guard receipt unverified"): return false 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 actual: Variant = receipt.get("actual_user_data_dir")
var live_root: String = ProjectSettings.globalize_path("user://").simplify_path() 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 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} 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 bound = true
return true return true
func write_evidence(name: String, value: Variant) -> void: func write_evidence(name: String, value: Variant) -> void:
if not demand(bound, "evidence write before guard binding"): return if not demand(bound, "evidence write before guard binding"): return
var directory: String = "user://evidence" 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 if not demand(mkdir == OK, "evidence mkdir failed"): return
var path: String = directory.path_join(name) var path: String = directory.path_join(name)
if not demand(not FileAccess.file_exists(path), "refuse evidence overwrite: " + name): return 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.snapshot_published.connect(observe_snapshot)
runtime.state_updated.connect(observe_state) runtime.state_updated.connect(observe_state)
demand(runtime.domain_events.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard subscription failed") demand(runtime.domain_events.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard subscription failed")
subscribed_stream = runtime.domain_events
lifecycle["event_stream_replacements"] = []
connected = true connected = true
lifecycle["observer_before"] = initial_inventory lifecycle["observer_before"] = initial_inventory
lifecycle["observer_connected"] = inventory() lifecycle["observer_connected"] = inventory()
@@ -448,7 +476,9 @@ func begin() -> void:
report.event_start = event_counts() report.event_start = event_counts()
recording = true recording = true
checkpoint() 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. # Stage 4: B1 and 30 exact checkpoints, uncapped events through the last frame.
for unused: int in range(30): for unused: int in range(30):
if not lifecycle.errors.is_empty(): break if not lifecycle.errors.is_empty(): break
@@ -458,9 +488,10 @@ func begin() -> void:
recording = false recording = false
# Stage 5: E3 dedicated intervals, after measured event/signal closure. # Stage 5: E3 dedicated intervals, after measured event/signal closure.
lifecycle["outside"] = raw_layers(layers()) lifecycle["outside"] = raw_layers(layers())
run_e3() # Any harness error already fails the run; stop before further expensive scenarios.
run_e4() if lifecycle.errors.is_empty(): run_e3()
run_e5() if lifecycle.errors.is_empty(): run_e4()
if lifecycle.errors.is_empty(): run_e5()
finish() finish()
func finish() -> void: func finish() -> void:
@@ -474,10 +505,11 @@ func finish() -> void:
lifecycle["observer_disconnected"] = inventory() lifecycle["observer_disconnected"] = inventory()
demand(json_text(initial_inventory) == json_text(lifecycle.observer_disconnected), "observer inventory not restored") demand(json_text(initial_inventory) == json_text(lifecycle.observer_disconnected), "observer inventory not restored")
lifecycle["warm_signals"] = warm_signals 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: if bound:
write_evidence("events.json", report.events) write_evidence("events.json", report.events)
write_evidence("report.json", report) write_evidence("report.json", report)
lifecycle["blobs"] = blob_index.duplicate(true)
write_evidence("lifecycle.json", lifecycle.duplicate(true)) write_evidence("lifecycle.json", lifecycle.duplicate(true))
print("P28_02_EQ_REPORT=" + json_text(report)) 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) 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 if not demand(file != null, "package evidence open"): continue
var bytes: PackedByteArray = file.get_buffer(file.get_length()) var bytes: PackedByteArray = file.get_buffer(file.get_length())
file.close() file.close()
files[name] = {"bytes": Marshalls.raw_to_base64(bytes), "sha256": hashed(bytes)} files[name] = {"bytes": store_blob(bytes), "sha256": hashed(bytes)}
return files return files
func e3_prepare_package(path: String, input: Dictionary, kind: String) -> Dictionary: 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 manifest_path: String = path.path_join("manifest.json")
var file: FileAccess = FileAccess.open(manifest_path, FileAccess.READ) var file: FileAccess = FileAccess.open(manifest_path, FileAccess.READ)
if not demand(file != null, "manifest read"): return info 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() file.close()
match kind: # Edit only the one key line. Re-serializing a parsed manifest would turn every
"missing_key": manifest.erase("checksum_world") # JSON integer into a float (Godot parses numbers as float), changing other fields.
"wrong_type": manifest["format_version"] = "x" var key: String = "checksum_world" if kind == "missing_key" else "format_version"
"out_of_range": manifest["format_version"] = 2147483647 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) file = FileAccess.open(manifest_path, FileAccess.WRITE)
if not demand(file != null, "manifest write"): return info if not demand(file != null, "manifest write"): return info
file.store_string(json_text(manifest)) file.store_string("\n".join(lines))
file.close() file.close()
elif kind == "tampered_world": elif kind == "tampered_world":
var world_path: String = path.path_join("world.bin") var world_path: String = path.path_join("world.bin")
@@ -615,6 +659,7 @@ func run_e3() -> void:
"restore_snapshot": actual = runtime.restore_snapshot(input) "restore_snapshot": actual = runtime.restore_snapshot(input)
"validate": actual = SimulationSnapshot.validate(input) "validate": actual = SimulationSnapshot.validate(input)
"load_from_path": actual = runtime.load_from_path(path) "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) if package != null: package["after_operation"] = e3_package(path)
var after: Dictionary = e3_observation() var after: Dictionary = e3_observation()
scenario_active = false scenario_active = false
@@ -650,13 +695,17 @@ func run_e3() -> void:
var evidence: Dictionary = {"id": id, "begin": before, "invocation_before": invocation_before, "end": after, var evidence: Dictionary = {"id": id, "begin": before, "invocation_before": invocation_before, "end": after,
"input": transport(input), "return": transport(actual), "package": package, "events": scenario_events, "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")), "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) report.scenarios.append(scenario)
lifecycle.e3.append(evidence) lifecycle.e3.append(evidence)
write_evidence("e3-%02d.json" % (report.scenarios.size() - 1), evidence) write_evidence("e3-%02d.json" % (report.scenarios.size() - 1), evidence)
# S3b shares S3a's complete observation and transport; no state repair. # S3b shares S3a's complete observation and transport; no state repair.
func e45_emit(id: String, operation: String, observations: Array, detail: Dictionary) -> void: 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) detail["signals"] = scenario_signals.duplicate(true)
var cps: Array = [] var cps: Array = []
for observation: Dictionary in observations: cps.append(observation.checkpoint) 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) 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) 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"): 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 facts["return_fact"] = {"ok": actual.get(&"ok"), "code": actual.get(&"code")} if actual is Dictionary else actual
if facts.has("trials"): if facts.has("trials"):
for trial: Dictionary in facts.trials: for trial: Dictionary in facts.trials:
@@ -738,7 +787,10 @@ func run_e4() -> void:
elif operation == "restore_snapshot": elif operation == "restore_snapshot":
var input: Dictionary = runtime.create_snapshot().duplicate(true) var input: Dictionary = runtime.create_snapshot().duplicate(true)
detail["input"] = transport(input) 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": elif operation == "load_from_path":
var input: Dictionary = runtime.create_snapshot().duplicate(true) var input: Dictionary = runtime.create_snapshot().duplicate(true)
var path: String = "user://p28_eq/" + role.to_lower() + "/good" var path: String = "user://p28_eq/" + role.to_lower() + "/good"
@@ -748,7 +800,10 @@ func run_e4() -> void:
detail["save_return"] = transport(saved) detail["save_return"] = transport(saved)
detail["package_before"] = e3_package(path) detail["package_before"] = e3_package(path)
demand(saved.get(&"ok") == true, "E4 good package save failed") 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["latest_save_operation_result"] = transport(runtime.get("latest_save_operation_result"))
detail["package_after"] = e3_package(path) detail["package_after"] = e3_package(path)
elif operation == "rejected_command": elif operation == "rejected_command":
@@ -140,9 +140,10 @@ def plan(collector, standard, engine, fixture, out):
def copy_evidence(runtime, destination): def copy_evidence(runtime, destination):
source = runtime / 'evidence' source = runtime / 'evidence'
before = verify.inventory(source, seals=True) 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() destination.mkdir()
for name in sorted(before): for name in sorted(before):
(destination / name).parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source / name, destination / name) shutil.copyfile(source / name, destination / name)
require(verify.inventory(source, seals=True) == before == verify.inventory(destination, seals=True), 'evidence changed during copy') require(verify.inventory(source, seals=True) == before == verify.inventory(destination, seals=True), 'evidence changed during copy')
return before return before
@@ -228,7 +229,8 @@ def run(admission, out, collector, standard, outer, diag):
Path(admission['engine']), collector, standard) Path(admission['engine']), collector, standard)
require((ROOT / 'project.godot').read_bytes() == public and (stage / 'project.godot').read_bytes() == public, 'project restoration') require((ROOT / 'project.godot').read_bytes() == public and (stage / 'project.godot').read_bytes() == public, 'project restoration')
if error is None: 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) write(out / 'results.json', result)
for phase, values in result['phases'].items(): for phase, values in result['phases'].items():
for name, value in values.items(): for name, value in values.items():
@@ -432,6 +432,7 @@ def s2_report():
state = snapshot() state = snapshot()
runtime = eq._field(eq._field(state, 'systems'), 'runtime') 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, '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 = eq._field(runtime, 'domain_events')
events[1].append([[4, 'published_ids'], arr(*([4, 'domain_event.%012d' % i] for i in range(1, 4)))]) 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)] 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['K']['_phase_handlers'] = {str(i): [] for i in range(11)}
cp['C'].update(catalog_instance_id='123', current_catalog_instance_id='123') 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) 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 return value
@@ -672,7 +673,7 @@ class S2Static(unittest.TestCase):
self.reject(self.probe.replace('return str(a) < str(b)', 'return a < b')) self.reject(self.probe.replace('return str(a) < str(b)', 'return a < b'))
def test_full_state_and_godot_roundtrip_required(self): 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')) with self.subTest(required=required): self.reject(self.probe.replace(required, 'null'))
def test_no_event_cap_or_duplicate_report(self): 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, 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)), 'boundaries': ['begin', 'operation', 'end'], 'input': transport(checker.e3_expected_input(b1, kind)),
'return': transport(actual), 'package': package, 'events': event_records, 'signals': [], '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) return value, evidence, transport(b1)
@@ -894,6 +896,10 @@ class S3a(unittest.TestCase):
value, evidence, b1 = e3_fixture() value, evidence, b1 = e3_fixture()
value['scenarios'][0]['result']['ok'] = True value['scenarios'][0]['result']['ok'] = True
evidence[0]['return'] = transport([1, 1]) 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) checked = self.check(value, evidence, b1)
self.assertTrue(checked['ok'], checked['errors']) self.assertTrue(checked['ok'], checked['errors'])
self.assertEqual(checked['e3'][0]['defects'][0]['kind'], 'invalid_input_accepted') self.assertEqual(checked['e3'][0]['defects'][0]['kind'], 'invalid_input_accepted')
@@ -993,13 +999,19 @@ def e45_fixture(role='R'):
current = copy.deepcopy(previous) current = copy.deepcopy(previous)
evidence = [] evidence = []
def emit(ident, op, obs, detail, events=()): 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'), result = {'event_count': len(events), 'applicability': detail.get('applicability', 'applied'),
'reason': detail.get('reason'), 'facts': checker.e45_facts(detail)} 'reason': detail.get('reason'), 'facts': checker.e45_facts(detail)}
raw['scenarios'].append({'id': ident, 'operation': op, 'result': result, raw['scenarios'].append({'id': ident, 'operation': op, 'result': result,
'checkpoints': [o['checkpoint'] for o in obs], 'events': list(events)}) 'checkpoints': [o['checkpoint'] for o in obs], 'events': list(events)})
evidence.append({'id': ident, 'boundaries': ['begin', 'operation', 'end'], 'observations': obs, 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: for op in checker.E4_PATHS:
begin = copy.deepcopy(current); minute = begin['checkpoint']['minute'] begin = copy.deepcopy(current); minute = begin['checkpoint']['minute']
detail = {}; obs = [begin]; events = [] detail = {}; obs = [begin]; events = []
@@ -1017,14 +1029,14 @@ def e45_fixture(role='R'):
elif op == 'tick': elif op == 'tick':
minute += 1 minute += 1
elif op == 'restore_snapshot': 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': elif op == 'load_from_path':
ev = event(raw['profile_root']['root']) ev = event(raw['profile_root']['root'])
# Match the next identity required by X(P,event). # Match the next identity required by X(P,event).
counter = checker.counts(tree)[0]['counter'] 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'])), 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') 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'))) 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) tree, _ = checker.e3_x(tree, ev)
@@ -1294,7 +1306,7 @@ def s4_phase_fixture(directory, phase='R-A', pid=101):
'project_settings_persisted': False} 'project_settings_persisted': False}
guard_path = directory/(phase+'-guard.json') guard_path = directory/(phase+'-guard.json')
verifier.write(guard_path, guard) 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)}, 'guard': {'receipt': guard, 'live_root': root, 'sha256': verifier.sha(guard_path), 'path': str(guard_path)},
'observer_before': {}, 'observer_connected': {'probe': True}, 'observer_disconnected': {}, 'observer_before': {}, 'observer_connected': {'probe': True}, 'observer_disconnected': {},
'ops': {'code': 'INVALID_START_TIME'} if role == 'R' else {'ok': True}, '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') 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__': if __name__ == '__main__':
unittest.main(verbosity=2) unittest.main(verbosity=2)
@@ -5,6 +5,8 @@ import hashlib
import json import json
import os import os
from pathlib import Path from pathlib import Path
import re
import subprocess
import sys import sys
sys.dont_write_bytecode = True 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 NAMES = {f'{prefix}-{i:02d}.json' for prefix, count in
[('checkpoint', 31), ('observation', 31), ('e3', 18), ('e45', 21)] for i in range(count)} | { [('checkpoint', 31), ('observation', 31), ('e3', 18), ('e45', 21)] for i in range(count)} | {
'events.json', 'report.json', 'lifecycle.json', 'audio-close.json'} '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'} LOCAL_NAMES = {'run_eq.py', 'verify_eq.py', 'check_eq.py', 'compare_versions.py', 'eq_probe.gd', 'test_eq.py'}
require = eq.require require = eq.require
@@ -83,22 +95,35 @@ def check_audio(audio, pid):
def phase_evidence(directory, row): def phase_evidence(directory, row):
"""Recompute all S1-S3b checks from copied native bytes, with no cached verdicts.""" """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) directory = Path(directory)
phase, role = row['phase'], row['role'] phase, role = row['phase'], row['role']
require(ROLES.get(phase) == role, 'phase/role binding') require(ROLES.get(phase) == role, 'phase/role binding')
folder = directory / phase folder = directory / phase
evidence = folder / 'evidence' evidence = folder / 'evidence'
files = inventory(evidence, seals=True) 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()) raw = check.read_report_stdout((folder / 'stdout.log').read_text())
require(raw == read(evidence / 'report.json') and raw['role'] == role, 'raw report/role copy') 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') require(len(raw['scenarios']) == 39, '39 scenarios required')
life = read(evidence / 'lifecycle.json') 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') 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']} declared = {r['path']: {'bytes': r['bytes'], 'sha256': r['sha256']} for r in life['files']}
require(len(declared) == len(life['files']), 'duplicate lifecycle inventory') 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') require(declared == expected, 'lifecycle inventory differs')
guard_path = directory / (phase + '-guard.json') guard_path = directory / (phase + '-guard.json')
guard = read(guard_path) guard = read(guard_path)
@@ -120,7 +145,47 @@ def phase_evidence(directory, row):
return wire({'raw': raw, 'joined': joined, 'proofs': proofs, 'checker': checked}) 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.""" """Validate owned receipts and recompute every comparison from raw evidence."""
directory = Path(directory) directory = Path(directory)
require(execution['passed'] is True and execution['error'] is None and execution['cleanup_errors'] == [], 'execution failed') 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') require(execution['pins_after'] == admission['pins'], 'pin preservation')
rows = execution['phases'] rows = execution['phases']
require([r['phase'] for r in rows] == PHASES, 'seven phases in order') require([r['phase'] for r in rows] == PHASES, 'seven phases in order')
pids, profiles, joined, results = [], [], {}, {} pids, profiles, evidence_rows = [], [], []
previous = source previous = source
for i, row in enumerate(rows): for i, row in enumerate(rows):
phase = row['phase'] 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 require(row['role'] == ROLES[phase] and row['runtime_profile'] == command['runtime_profile'] and
row['runtime_name'] == command['runtime_name'], 'runtime/role binding') row['runtime_name'] == command['runtime_name'], 'runtime/role binding')
collector.guard_gate(row['guard'], Path(row['runtime_profile']), row['runtime_name']) collector.guard_gate(row['guard'], Path(row['runtime_profile']), row['runtime_name'])
results[phase] = phase_evidence(directory, row) evidence_rows.append(row)
joined[phase] = results[phase]['joined']
else: else:
require(row['role'] is None, 'parse/import role') require(row['role'] is None, 'parse/import role')
require('P28_02_EQ_REPORT=' not in (directory / phase / 'stdout.log').read_text(), 'parse/import report') 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(len(set(pids)) == 7 and len(set(profiles)) == 14, 'PID/profile reuse')
require({r['pid'] for r in execution['cleanup']} == set(pids) and all( require({r['pid'] for r in execution['cleanup']} == set(pids) and all(
r['engine_ended'] is True and r['outer_cleanup_errors'] == [] and r['engine_ended'] is True and r['outer_cleanup_errors'] == [] and
@@ -228,7 +294,7 @@ def verify(project_root, directory):
admission = execution['admission'] admission = execution['admission']
require(actual_pins(project_root, Path(admission['readonly_path']), Path(admission['fixture']), 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') 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') require(result == read(directory / 'results.json'), 'recomputed results differ')
for phase, values in result['phases'].items(): for phase, values in result['phases'].items():
for name, value in values.items(): for name, value in values.items():
@@ -237,12 +303,15 @@ def verify(project_root, directory):
def main(): def main():
if sys.argv[1:] == ['--phase-worker']:
return phase_worker()
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--project-root', type=Path, required=True) parser.add_argument('--project-root', type=Path, required=True)
args = parser.parse_args() args = parser.parse_args()
try: try:
require(os.environ.get('BUILD_OUTPUT_DIR'), 'BUILD_OUTPUT_DIR required') 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)) print(json.dumps({'status': 'verified', 'result': result}, ensure_ascii=False, allow_nan=False))
return 0 return 0
except (ValueError, OSError, KeyError, TypeError, IndexError) as error: except (ValueError, OSError, KeyError, TypeError, IndexError) as error: