P28-02-EQ: 意味を保つ軽量化:同等性の先行固定(harness)
This commit is contained in:
@@ -577,3 +577,133 @@ failures respectively; all earlier evidence remains unchanged.
|
||||
|
||||
This table records the required implementation targets, not completed validation.
|
||||
No native or formal validation is authorized for this worker.
|
||||
|
||||
|
||||
## S5 implementation (repair 10; EQ_S5_SCALE_PLAN r01/r02)
|
||||
|
||||
Implemented by Claude Opus 5.5 in the Desktop control session after the user moved
|
||||
implementation from Codex to Opus. The exact-S native of 6f75c9a failed in R-A and stays a
|
||||
failure; this stage fixes the harness defects that only the real engine exposed and makes
|
||||
the full R scope (E1-E5, 39 scenarios) fit the unchanged limits. All earlier text below and
|
||||
above this section is retained history.
|
||||
|
||||
### Defects found only by the real engine (scratchpad diagnostics, not formal evidence)
|
||||
|
||||
- write_evidence created `user://evidence` with the user:// path. Under the guard's runtime
|
||||
user-dir switch this fails; the directory is now created from the globalized path, as the
|
||||
RESTART helpers do.
|
||||
- typed_info encoded get_typed_script(), a null Object for unscripted typed containers, as an
|
||||
unsupported Variant 24. It is now transported as NIL ([0], which the Python side already
|
||||
required); a scripted element type still fails.
|
||||
- Package manifest mutation re-serialized a JSON-parsed manifest. Godot parses every JSON
|
||||
number as float, so untouched integers became 390.0, 1.0 and so on. Only the one key line is
|
||||
now edited as text; the Python rule already rejects any other change (new negative test).
|
||||
- The guard receipt was recorded after JSON parsing, with the same float problem. The exact
|
||||
receipt text is now kept beside it; Python parses that text strictly, compares it with the
|
||||
independently saved guard file and binds its SHA.
|
||||
- Godot instance IDs are signed 64-bit and RefCounted IDs are negative. The cache identity
|
||||
check accepts the sign. Raw values stay in raw; only the SPEC 3.2 exclusion (process-local
|
||||
instance ID) is applied across PIDs, with both values and the reason recorded.
|
||||
- A large E4 return is a blob; its ok/code facts are taken from the live return value.
|
||||
- After the first harness error, later scenarios are no longer run (the run already failed).
|
||||
|
||||
### Product behavior recorded as an existing defect, separate from harness capture
|
||||
|
||||
autoload/simulation_runtime.gd:788 and :1346: a successful restore replaces domain_events
|
||||
with a new DomainEventStream and does not carry subscribers. The probe's wildcard
|
||||
subscription stayed on the old stream after E4 restore, so SAVE_LOADED and all later events
|
||||
were not delivered. follow_event_stream() runs after every E3 call and after E4 restore and
|
||||
load. When the stream changed it records the new stream's subscriber IDs (no instance IDs),
|
||||
recovers the unobserved events from recent_events() in publication order, and subscribes to
|
||||
the live stream again. check_e3/check_e45 record event_stream_replaced_subscribers_not_carried
|
||||
as an existing defect (never PASS). Completeness is still proven only by the counter delta,
|
||||
the published_ids delta and the actual event IDs; an event outside the 256-entry
|
||||
recent_events window would remain missing and fail. lifecycle.event_stream_replacements must
|
||||
equal the per-scenario facts. No event is renumbered, no product file or hook is changed.
|
||||
|
||||
### Evidence representation (complete and reversible)
|
||||
|
||||
- Trees larger than 64 KiB, raw S/K/C bytes and package files are written once as
|
||||
evidence/blobs/<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の証拠として提出する。
|
||||
正式受入は保留、原本加算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_invalid_operations (__main__.Diffs) ... ok
|
||||
test_reconstruction_and_proofs (__main__.Diffs) ... ok
|
||||
test_attempt_to_normalize_violation_rejected (__main__.Normalization) ... ok
|
||||
test_environment_values_differ_but_normal_form_equal (__main__.Normalization) ... ok
|
||||
test_every_save_event_reversible (__main__.Normalization) ... ok
|
||||
test_f3_keeps_relative_paths_and_words (__main__.Normalization) ... ok
|
||||
test_guard_mismatch (__main__.Normalization) ... ok
|
||||
test_nested_lookalike_is_not_eligible (__main__.Normalization) ... ok
|
||||
test_original_transport_sha_detects_forged_timing (__main__.Normalization) ... ok
|
||||
test_partial_timing_payload_rejected (__main__.Normalization) ... ok
|
||||
test_prefix_boundary_and_reserved_token (__main__.Normalization) ... ok
|
||||
test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization) ... ok
|
||||
test_snapshot_and_latest_reversible (__main__.Normalization) ... ok
|
||||
test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization) ... ok
|
||||
test_wrong_event_and_source_are_preserved (__main__.Normalization) ... ok
|
||||
test_wrong_timing_type_and_negative (__main__.Normalization) ... ok
|
||||
test_all_events_beyond_recent_window (__main__.Reports) ... ok
|
||||
test_all_semantic_layers_compared (__main__.Reports) ... ok
|
||||
test_digest_alone_is_not_enough (__main__.Reports) ... ok
|
||||
test_event_original_types_and_order (__main__.Reports) ... ok
|
||||
test_event_start_and_ids (__main__.Reports) ... ok
|
||||
test_exclusions_keep_actual_values (__main__.Reports) ... ok
|
||||
test_handler_sets_and_counts (__main__.Reports) ... ok
|
||||
test_observation_full_layers (__main__.Reports) ... ok
|
||||
test_product_digest_self_consistency (__main__.Reports) ... ok
|
||||
test_same_report (__main__.Reports) ... ok
|
||||
test_scenario_existing_failures_preserved (__main__.Reports) ... ok
|
||||
test_timing_digest_rule (__main__.Reports) ... ok
|
||||
test_unknown_or_missing_fields_rejected (__main__.Reports) ... ok
|
||||
test_violation_presence_and_difference (__main__.Reports) ... ok
|
||||
test_O_cadence (__main__.S2Format) ... ok
|
||||
test_counter_and_published_ids_binding (__main__.S2Format) ... ok
|
||||
test_diff_and_same_as_S_transport (__main__.S2Format) ... ok
|
||||
test_digest_and_scenario_contradiction (__main__.S2Format) ... ok
|
||||
test_event_no_drop_duplicate_remap (__main__.S2Format) ... ok
|
||||
test_event_original_field_types_mandatory (__main__.S2Format) ... ok
|
||||
test_guard_actual_not_assumed (__main__.S2Format) ... ok
|
||||
test_harness_digest_allows_only_dictionary_order (__main__.S2Format) ... ok
|
||||
test_join_is_copy_and_retains_normalization_proof (__main__.S2Format) ... ok
|
||||
test_kernel_cache_contradictions (__main__.S2Format) ... ok
|
||||
test_lifecycle_guard_errors_and_boundaries (__main__.S2Format) ... ok
|
||||
test_missing_full_state_not_digest_only (__main__.S2Format) ... ok
|
||||
test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format) ... ok
|
||||
test_product_event_shape_and_typed_array (__main__.S2Format) ... ok
|
||||
test_raw_observation_binding (__main__.S2Format) ... ok
|
||||
test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format) ... ok
|
||||
test_s2_valid_and_not_formal_acceptance (__main__.S2Format) ... ok
|
||||
test_state_witness_and_observation_mutation (__main__.S2Format) ... ok
|
||||
test_stdout_exactly_one_line (__main__.S2Format) ... ok
|
||||
test_autoload_class_and_script_import (__main__.S2Static) ... ok
|
||||
test_comment_does_not_supply_proof (__main__.S2Static) ... ok
|
||||
test_current_probe (__main__.S2Static) ... ok
|
||||
test_full_state_and_godot_roundtrip_required (__main__.S2Static) ... ok
|
||||
test_no_changes_outside_preparation_or_step (__main__.S2Static) ... ok
|
||||
test_no_event_cap_or_duplicate_report (__main__.S2Static) ... ok
|
||||
test_pause_and_step_arguments (__main__.S2Static) ... ok
|
||||
test_textual_stringname_ordering (__main__.S2Static) ... ok
|
||||
test_unlisted_live_method_and_late_preparation (__main__.S2Static) ... ok
|
||||
test_variant_inference (__main__.S2Static) ... ok
|
||||
test_X_recent_limit_and_counter (__main__.S3a) ... ok
|
||||
test_duplicate_JSON_key_rejected (__main__.S3a) ... ok
|
||||
test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a) ... ok
|
||||
test_e3_coverage_input_and_event_count_binding (__main__.S3a) ... ok
|
||||
test_e3_invented_codes_and_missing_load_code (__main__.S3a) ... ok
|
||||
test_e3_valid_R_and_O_records (__main__.S3a) ... ok
|
||||
test_envelope_duplicates_not_defect (__main__.S3a) ... ok
|
||||
test_event_count_defect_not_silently_accepted (__main__.S3a) ... ok
|
||||
test_exact_phase_keys (__main__.S3a) ... ok
|
||||
test_interval_gap_rejected (__main__.S3a) ... ok
|
||||
test_invalid_input_accepted_is_not_PASS (__main__.S3a) ... ok
|
||||
test_load_payload_and_pause_outcome (__main__.S3a) ... ok
|
||||
test_operation_moved_outside_scenario_rejected (__main__.S3a) ... ok
|
||||
test_package_fixed_mutations_and_hashes (__main__.S3a) ... ok
|
||||
test_probe_connection_required (__main__.S3a) ... ok
|
||||
test_rejection_mutation_retained_as_defect (__main__.S3a) ... ok
|
||||
test_save_refusal_makes_load_NA (__main__.S3a) ... ok
|
||||
test_scenario_mutation_static_boundary (__main__.S3a) ... ok
|
||||
test_signals_all_minutes_and_order (__main__.S3a) ... ok
|
||||
test_E5_cached_digest_defect_is_comparable (__main__.S3b) ... ok
|
||||
test_NA_not_PASS (__main__.S3b) ... ok
|
||||
test_aliasing_cannot_be_PASS (__main__.S3b) ... ok
|
||||
test_aliasing_missing_record (__main__.S3b) ... ok
|
||||
test_all_paths_and_ownership_R_O (__main__.S3b) ... ok
|
||||
test_digest_defect_other_checkpoint_still_rejected (__main__.S3b) ... ok
|
||||
test_digest_materialization_required (__main__.S3b) ... ok
|
||||
test_empty_registry_not_NA (__main__.S3b) ... ok
|
||||
test_event_count_mismatch (__main__.S3b) ... ok
|
||||
test_facts_normalization_is_reversible (__main__.S3b) ... ok
|
||||
test_first_placeable_choice (__main__.S3b) ... ok
|
||||
test_interval_gap (__main__.S3b) ... ok
|
||||
test_joined_s3b_self_comparison (__main__.S3b) ... ok
|
||||
test_load_X_not_digest_only (__main__.S3b) ... ok
|
||||
test_load_extra_event (__main__.S3b) ... ok
|
||||
test_missing_path_and_combination (__main__.S3b) ... ok
|
||||
test_missing_preview_observation (__main__.S3b) ... ok
|
||||
test_missing_trial (__main__.S3b) ... ok
|
||||
test_nested_mutation_required (__main__.S3b) ... ok
|
||||
test_no_staleness_assertion_for_non_tick (__main__.S3b) ... ok
|
||||
test_ownership_fact_dictionary_order_fallback (__main__.S3b) ... ok
|
||||
test_path_order (__main__.S3b) ... ok
|
||||
test_restore_extra_event (__main__.S3b) ... ok
|
||||
test_signal_facts_cannot_be_dropped (__main__.S3b) ... ok
|
||||
test_sort_rule_rejected (__main__.S3b) ... ok
|
||||
test_static_handler_and_outside_mutations (__main__.S3b) ... ok
|
||||
test_unpreviewed_choice (__main__.S3b) ... ok
|
||||
test_wrong_registry_in_either_role (__main__.S3b) ... ok
|
||||
test_checkout_temp_destination_rejected (__main__.S4Bundle) ... ok
|
||||
test_existing_output_never_overwritten (__main__.S4Bundle) ... ok
|
||||
test_import_closure_rejections (__main__.S4Bundle) ... ok
|
||||
test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle) ... ok
|
||||
test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle) ... ok
|
||||
test_missing_profile_not_export_success (__main__.S4Bundle) ... ok
|
||||
test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle) ... ok
|
||||
test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle) ... ok
|
||||
test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle) ... ok
|
||||
test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle) ... ok
|
||||
test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic) ... ok
|
||||
test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic) ... ok
|
||||
test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic) ... ok
|
||||
test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic) ... ok
|
||||
test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic) ... ok
|
||||
test_check_accepts_true (__main__.S4ProbeAdmission) ... ok
|
||||
test_check_rejects_false_all_errors (__main__.S4ProbeAdmission) ... ok
|
||||
test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission) ... ok
|
||||
test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission) ... ok
|
||||
test_nine_original_destinations_required (__main__.S4RemainingBindings) ... ok
|
||||
test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings) ... ok
|
||||
test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings) ... ok
|
||||
test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings) ... ok
|
||||
test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings) ... ok
|
||||
test_cached_verdict_not_trusted (__main__.S4StoredResultBindings) ... ok
|
||||
test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings) ... ok
|
||||
test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings) ... ok
|
||||
test_fixed_scenario_paths (__main__.SourcesAndMetrics) ... ok
|
||||
test_metric_ranges (__main__.SourcesAndMetrics) ... ok
|
||||
test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics) ... ok
|
||||
test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics) ... ok
|
||||
test_array_order_and_all_differences (__main__.TypedTrees) ... ok
|
||||
test_components_and_sha (__main__.TypedTrees) ... ok
|
||||
test_deep_state_not_truncated (__main__.TypedTrees) ... ok
|
||||
test_duplicate_textual_keys_rejected (__main__.TypedTrees) ... ok
|
||||
test_exact_and_types (__main__.TypedTrees) ... ok
|
||||
test_nested_order_all_paths (__main__.TypedTrees) ... ok
|
||||
test_nonstring_keys_keep_types_and_values (__main__.TypedTrees) ... ok
|
||||
test_unsupported_and_malformed (__main__.TypedTrees) ... ok
|
||||
test_all_operations (__main__.Diffs.test_all_operations) ... ok
|
||||
test_invalid_operations (__main__.Diffs.test_invalid_operations) ... ok
|
||||
test_reconstruction_and_proofs (__main__.Diffs.test_reconstruction_and_proofs) ... ok
|
||||
test_attempt_to_normalize_violation_rejected (__main__.Normalization.test_attempt_to_normalize_violation_rejected) ... ok
|
||||
test_environment_values_differ_but_normal_form_equal (__main__.Normalization.test_environment_values_differ_but_normal_form_equal) ... ok
|
||||
test_every_save_event_reversible (__main__.Normalization.test_every_save_event_reversible) ... ok
|
||||
test_f3_keeps_relative_paths_and_words (__main__.Normalization.test_f3_keeps_relative_paths_and_words) ... ok
|
||||
test_guard_mismatch (__main__.Normalization.test_guard_mismatch) ... ok
|
||||
test_nested_lookalike_is_not_eligible (__main__.Normalization.test_nested_lookalike_is_not_eligible) ... ok
|
||||
test_original_transport_sha_detects_forged_timing (__main__.Normalization.test_original_transport_sha_detects_forged_timing) ... ok
|
||||
test_partial_timing_payload_rejected (__main__.Normalization.test_partial_timing_payload_rejected) ... ok
|
||||
test_prefix_boundary_and_reserved_token (__main__.Normalization.test_prefix_boundary_and_reserved_token) ... ok
|
||||
test_sidecar_missing_duplicate_and_forged_paths (__main__.Normalization.test_sidecar_missing_duplicate_and_forged_paths) ... ok
|
||||
test_snapshot_and_latest_reversible (__main__.Normalization.test_snapshot_and_latest_reversible) ... ok
|
||||
test_snapshot_event_outside_recent_is_not_eligible (__main__.Normalization.test_snapshot_event_outside_recent_is_not_eligible) ... ok
|
||||
test_wrong_event_and_source_are_preserved (__main__.Normalization.test_wrong_event_and_source_are_preserved) ... ok
|
||||
test_wrong_timing_type_and_negative (__main__.Normalization.test_wrong_timing_type_and_negative) ... ok
|
||||
test_all_events_beyond_recent_window (__main__.Reports.test_all_events_beyond_recent_window) ... ok
|
||||
test_all_semantic_layers_compared (__main__.Reports.test_all_semantic_layers_compared) ... ok
|
||||
test_digest_alone_is_not_enough (__main__.Reports.test_digest_alone_is_not_enough) ... ok
|
||||
test_event_original_types_and_order (__main__.Reports.test_event_original_types_and_order) ... ok
|
||||
test_event_start_and_ids (__main__.Reports.test_event_start_and_ids) ... ok
|
||||
test_exclusions_keep_actual_values (__main__.Reports.test_exclusions_keep_actual_values) ... ok
|
||||
test_handler_sets_and_counts (__main__.Reports.test_handler_sets_and_counts) ... ok
|
||||
test_observation_full_layers (__main__.Reports.test_observation_full_layers) ... ok
|
||||
test_product_digest_self_consistency (__main__.Reports.test_product_digest_self_consistency) ... ok
|
||||
test_same_report (__main__.Reports.test_same_report) ... ok
|
||||
test_scenario_existing_failures_preserved (__main__.Reports.test_scenario_existing_failures_preserved) ... ok
|
||||
test_timing_digest_rule (__main__.Reports.test_timing_digest_rule) ... ok
|
||||
test_unknown_or_missing_fields_rejected (__main__.Reports.test_unknown_or_missing_fields_rejected) ... ok
|
||||
test_violation_presence_and_difference (__main__.Reports.test_violation_presence_and_difference) ... ok
|
||||
test_O_cadence (__main__.S2Format.test_O_cadence) ... ok
|
||||
test_counter_and_published_ids_binding (__main__.S2Format.test_counter_and_published_ids_binding) ... ok
|
||||
test_diff_and_same_as_S_transport (__main__.S2Format.test_diff_and_same_as_S_transport) ... ok
|
||||
test_digest_and_scenario_contradiction (__main__.S2Format.test_digest_and_scenario_contradiction) ... ok
|
||||
test_event_no_drop_duplicate_remap (__main__.S2Format.test_event_no_drop_duplicate_remap) ... ok
|
||||
test_event_original_field_types_mandatory (__main__.S2Format.test_event_original_field_types_mandatory) ... ok
|
||||
test_guard_actual_not_assumed (__main__.S2Format.test_guard_actual_not_assumed) ... ok
|
||||
test_harness_digest_allows_only_dictionary_order (__main__.S2Format.test_harness_digest_allows_only_dictionary_order) ... ok
|
||||
test_join_is_copy_and_retains_normalization_proof (__main__.S2Format.test_join_is_copy_and_retains_normalization_proof) ... ok
|
||||
test_kernel_cache_contradictions (__main__.S2Format.test_kernel_cache_contradictions) ... ok
|
||||
test_lifecycle_guard_errors_and_boundaries (__main__.S2Format.test_lifecycle_guard_errors_and_boundaries) ... ok
|
||||
test_missing_full_state_not_digest_only (__main__.S2Format.test_missing_full_state_not_digest_only) ... ok
|
||||
test_normalized_digest_and_raw_tree_are_both_bound (__main__.S2Format.test_normalized_digest_and_raw_tree_are_both_bound) ... ok
|
||||
test_product_event_shape_and_typed_array (__main__.S2Format.test_product_event_shape_and_typed_array) ... ok
|
||||
test_raw_observation_binding (__main__.S2Format.test_raw_observation_binding) ... ok
|
||||
test_roundtrip_tree_sha_and_raw_sha (__main__.S2Format.test_roundtrip_tree_sha_and_raw_sha) ... ok
|
||||
test_s2_valid_and_not_formal_acceptance (__main__.S2Format.test_s2_valid_and_not_formal_acceptance) ... ok
|
||||
test_state_witness_and_observation_mutation (__main__.S2Format.test_state_witness_and_observation_mutation) ... ok
|
||||
test_stdout_exactly_one_line (__main__.S2Format.test_stdout_exactly_one_line) ... ok
|
||||
test_autoload_class_and_script_import (__main__.S2Static.test_autoload_class_and_script_import) ... ok
|
||||
test_comment_does_not_supply_proof (__main__.S2Static.test_comment_does_not_supply_proof) ... ok
|
||||
test_current_probe (__main__.S2Static.test_current_probe) ... ok
|
||||
test_full_state_and_godot_roundtrip_required (__main__.S2Static.test_full_state_and_godot_roundtrip_required) ... ok
|
||||
test_no_changes_outside_preparation_or_step (__main__.S2Static.test_no_changes_outside_preparation_or_step) ... ok
|
||||
test_no_event_cap_or_duplicate_report (__main__.S2Static.test_no_event_cap_or_duplicate_report) ... ok
|
||||
test_pause_and_step_arguments (__main__.S2Static.test_pause_and_step_arguments) ... ok
|
||||
test_textual_stringname_ordering (__main__.S2Static.test_textual_stringname_ordering) ... ok
|
||||
test_unlisted_live_method_and_late_preparation (__main__.S2Static.test_unlisted_live_method_and_late_preparation) ... ok
|
||||
test_variant_inference (__main__.S2Static.test_variant_inference) ... ok
|
||||
test_X_recent_limit_and_counter (__main__.S3a.test_X_recent_limit_and_counter) ... ok
|
||||
test_duplicate_JSON_key_rejected (__main__.S3a.test_duplicate_JSON_key_rejected) ... ok
|
||||
test_e3_NA_cannot_be_PASS_or_applied (__main__.S3a.test_e3_NA_cannot_be_PASS_or_applied) ... ok
|
||||
test_e3_coverage_input_and_event_count_binding (__main__.S3a.test_e3_coverage_input_and_event_count_binding) ... ok
|
||||
test_e3_invented_codes_and_missing_load_code (__main__.S3a.test_e3_invented_codes_and_missing_load_code) ... ok
|
||||
test_e3_valid_R_and_O_records (__main__.S3a.test_e3_valid_R_and_O_records) ... ok
|
||||
test_envelope_duplicates_not_defect (__main__.S3a.test_envelope_duplicates_not_defect) ... ok
|
||||
test_event_count_defect_not_silently_accepted (__main__.S3a.test_event_count_defect_not_silently_accepted) ... ok
|
||||
test_exact_phase_keys (__main__.S3a.test_exact_phase_keys) ... ok
|
||||
test_interval_gap_rejected (__main__.S3a.test_interval_gap_rejected) ... ok
|
||||
test_invalid_input_accepted_is_not_PASS (__main__.S3a.test_invalid_input_accepted_is_not_PASS) ... ok
|
||||
test_load_payload_and_pause_outcome (__main__.S3a.test_load_payload_and_pause_outcome) ... ok
|
||||
test_operation_moved_outside_scenario_rejected (__main__.S3a.test_operation_moved_outside_scenario_rejected) ... ok
|
||||
test_package_fixed_mutations_and_hashes (__main__.S3a.test_package_fixed_mutations_and_hashes) ... ok
|
||||
test_probe_connection_required (__main__.S3a.test_probe_connection_required) ... ok
|
||||
test_rejection_mutation_retained_as_defect (__main__.S3a.test_rejection_mutation_retained_as_defect) ... ok
|
||||
test_save_refusal_makes_load_NA (__main__.S3a.test_save_refusal_makes_load_NA) ... ok
|
||||
test_scenario_mutation_static_boundary (__main__.S3a.test_scenario_mutation_static_boundary) ... ok
|
||||
test_signals_all_minutes_and_order (__main__.S3a.test_signals_all_minutes_and_order) ... ok
|
||||
test_E5_cached_digest_defect_is_comparable (__main__.S3b.test_E5_cached_digest_defect_is_comparable) ... ok
|
||||
test_NA_not_PASS (__main__.S3b.test_NA_not_PASS) ... ok
|
||||
test_aliasing_cannot_be_PASS (__main__.S3b.test_aliasing_cannot_be_PASS) ... ok
|
||||
test_aliasing_missing_record (__main__.S3b.test_aliasing_missing_record) ... ok
|
||||
test_all_paths_and_ownership_R_O (__main__.S3b.test_all_paths_and_ownership_R_O) ... ok
|
||||
test_digest_defect_other_checkpoint_still_rejected (__main__.S3b.test_digest_defect_other_checkpoint_still_rejected) ... ok
|
||||
test_digest_materialization_required (__main__.S3b.test_digest_materialization_required) ... ok
|
||||
test_empty_registry_not_NA (__main__.S3b.test_empty_registry_not_NA) ... ok
|
||||
test_event_count_mismatch (__main__.S3b.test_event_count_mismatch) ... ok
|
||||
test_facts_normalization_is_reversible (__main__.S3b.test_facts_normalization_is_reversible) ... ok
|
||||
test_first_placeable_choice (__main__.S3b.test_first_placeable_choice) ... ok
|
||||
test_interval_gap (__main__.S3b.test_interval_gap) ... ok
|
||||
test_joined_s3b_self_comparison (__main__.S3b.test_joined_s3b_self_comparison) ... ok
|
||||
test_load_X_not_digest_only (__main__.S3b.test_load_X_not_digest_only) ... ok
|
||||
test_load_extra_event (__main__.S3b.test_load_extra_event) ... ok
|
||||
test_missing_path_and_combination (__main__.S3b.test_missing_path_and_combination) ... ok
|
||||
test_missing_preview_observation (__main__.S3b.test_missing_preview_observation) ... ok
|
||||
test_missing_trial (__main__.S3b.test_missing_trial) ... ok
|
||||
test_nested_mutation_required (__main__.S3b.test_nested_mutation_required) ... ok
|
||||
test_no_staleness_assertion_for_non_tick (__main__.S3b.test_no_staleness_assertion_for_non_tick) ... ok
|
||||
test_ownership_fact_dictionary_order_fallback (__main__.S3b.test_ownership_fact_dictionary_order_fallback) ... ok
|
||||
test_path_order (__main__.S3b.test_path_order) ... ok
|
||||
test_restore_extra_event (__main__.S3b.test_restore_extra_event) ... ok
|
||||
test_signal_facts_cannot_be_dropped (__main__.S3b.test_signal_facts_cannot_be_dropped) ... ok
|
||||
test_sort_rule_rejected (__main__.S3b.test_sort_rule_rejected) ... ok
|
||||
test_static_handler_and_outside_mutations (__main__.S3b.test_static_handler_and_outside_mutations) ... ok
|
||||
test_unpreviewed_choice (__main__.S3b.test_unpreviewed_choice) ... ok
|
||||
test_wrong_registry_in_either_role (__main__.S3b.test_wrong_registry_in_either_role) ... ok
|
||||
test_checkout_temp_destination_rejected (__main__.S4Bundle.test_checkout_temp_destination_rejected) ... ok
|
||||
test_existing_output_never_overwritten (__main__.S4Bundle.test_existing_output_never_overwritten) ... ok
|
||||
test_import_closure_rejections (__main__.S4Bundle.test_import_closure_rejections) ... ok
|
||||
test_missing_bundle_manifest_seal_and_changed_bytes (__main__.S4Bundle.test_missing_bundle_manifest_seal_and_changed_bytes) ... ok
|
||||
test_missing_private_evidence_and_extra_rejected (__main__.S4Bundle.test_missing_private_evidence_and_extra_rejected) ... ok
|
||||
test_missing_profile_not_export_success (__main__.S4Bundle.test_missing_profile_not_export_success) ... ok
|
||||
test_phase_role_pid_audio_and_guard_rejected (__main__.S4Bundle.test_phase_role_pid_audio_and_guard_rejected) ... ok
|
||||
test_rebound_lifecycle_and_scenario_contradictions (__main__.S4Bundle.test_rebound_lifecycle_and_scenario_contradictions) ... ok
|
||||
test_seal_normal_and_changes_not_native_proof (__main__.S4Bundle.test_seal_normal_and_changes_not_native_proof) ... ok
|
||||
test_synthetic_phase_all_real_checkers_not_native_proof (__main__.S4Bundle.test_synthetic_phase_all_real_checkers_not_native_proof) ... ok
|
||||
test_copied_manifest_root_and_destination_count_rejected (__main__.S4NativeBundleLogic.test_copied_manifest_root_and_destination_count_rejected) ... ok
|
||||
test_failure_keeps_raw_and_no_bundle_without_starting_engine (__main__.S4NativeBundleLogic.test_failure_keeps_raw_and_no_bundle_without_starting_engine) ... ok
|
||||
test_gate_none_success_not_boolean (__main__.S4NativeBundleLogic.test_gate_none_success_not_boolean) ... ok
|
||||
test_receipt_and_source_rejection_matrix (__main__.S4NativeBundleLogic.test_receipt_and_source_rejection_matrix) ... ok
|
||||
test_synthetic_full_bundle_positive_not_native_evidence (__main__.S4NativeBundleLogic.test_synthetic_full_bundle_positive_not_native_evidence) ... ok
|
||||
test_check_accepts_true (__main__.S4ProbeAdmission.test_check_accepts_true) ... ok
|
||||
test_check_rejects_false_all_errors (__main__.S4ProbeAdmission.test_check_rejects_false_all_errors) ... ok
|
||||
test_run_admission_accepts_true_mock_launch_only (__main__.S4ProbeAdmission.test_run_admission_accepts_true_mock_launch_only) ... ok
|
||||
test_run_admission_rejects_false_without_engine (__main__.S4ProbeAdmission.test_run_admission_rejects_false_without_engine) ... ok
|
||||
test_nine_original_destinations_required (__main__.S4RemainingBindings.test_nine_original_destinations_required) ... ok
|
||||
test_nonzero_determinism_rejected_after_real_join (__main__.S4RemainingBindings.test_nonzero_determinism_rejected_after_real_join) ... ok
|
||||
test_owned_lifecycle_exit_diagnostic_and_profile_rejections (__main__.S4RemainingBindings.test_owned_lifecycle_exit_diagnostic_and_profile_rejections) ... ok
|
||||
test_verifier_accepts_true_recomputes_raw (__main__.S4RemainingBindings.test_verifier_accepts_true_recomputes_raw) ... ok
|
||||
test_verifier_rejects_false_with_every_error (__main__.S4RemainingBindings.test_verifier_rejects_false_with_every_error) ... ok
|
||||
test_cached_verdict_not_trusted (__main__.S4StoredResultBindings.test_cached_verdict_not_trusted) ... ok
|
||||
test_missing_sidecar_rejected_after_seal (__main__.S4StoredResultBindings.test_missing_sidecar_rejected_after_seal) ... ok
|
||||
test_raw_joined_substitution_rejected_after_seal (__main__.S4StoredResultBindings.test_raw_joined_substitution_rejected_after_seal) ... ok
|
||||
test_blob_transport_is_verified_and_bound (__main__.S5.test_blob_transport_is_verified_and_bound) ... ok
|
||||
test_blob_trees_get_the_same_full_validation_as_inline (__main__.S5.test_blob_trees_get_the_same_full_validation_as_inline) ... ok
|
||||
test_compare_and_digest_equal_s1_reference (__main__.S5.test_compare_and_digest_equal_s1_reference) ... ok
|
||||
test_event_stream_replacement_is_existing_defect_not_pass (__main__.S5.test_event_stream_replacement_is_existing_defect_not_pass) ... ok
|
||||
test_every_checkpoint_full_tree_verified_like_inline (__main__.S5.test_every_checkpoint_full_tree_verified_like_inline)
|
||||
S5 drops the diff chain: each checkpoint carries a complete tree, verified alone. ... ok
|
||||
test_fast_normalization_equals_original (__main__.S5.test_fast_normalization_equals_original) ... ok
|
||||
test_labelled_harness_digest_falls_back_to_dictionary_order_only (__main__.S5.test_labelled_harness_digest_falls_back_to_dictionary_order_only) ... ok
|
||||
test_lifecycle_binds_replacements_guard_text_and_blobs (__main__.S5.test_lifecycle_binds_replacements_guard_text_and_blobs) ... ok
|
||||
test_manifest_numbers_keep_their_type (__main__.S5.test_manifest_numbers_keep_their_type) ... ok
|
||||
test_memo_scope_same_results_and_restores_gc (__main__.S5.test_memo_scope_same_results_and_restores_gc) ... ok
|
||||
test_path_copy_builders_equal_deepcopy_reference (__main__.S5.test_path_copy_builders_equal_deepcopy_reference) ... ok
|
||||
test_probe_static_full_tree_blob_and_stream_requirements (__main__.S5.test_probe_static_full_tree_blob_and_stream_requirements) ... ok
|
||||
test_publish_pairs_bound_to_source_causes (__main__.S5.test_publish_pairs_bound_to_source_causes) ... ok
|
||||
test_signed_instance_ids_raw_kept_and_excluded (__main__.S5.test_signed_instance_ids_raw_kept_and_excluded) ... ok
|
||||
test_validate_tree_equals_s1_reference_on_mutations (__main__.S5.test_validate_tree_equals_s1_reference_on_mutations) ... ok
|
||||
test_fixed_scenario_paths (__main__.SourcesAndMetrics.test_fixed_scenario_paths) ... ok
|
||||
test_metric_ranges (__main__.SourcesAndMetrics.test_metric_ranges) ... ok
|
||||
test_source_closure_synthetic_registered_map (__main__.SourcesAndMetrics.test_source_closure_synthetic_registered_map) ... ok
|
||||
test_source_pin_is_not_caller_controlled (__main__.SourcesAndMetrics.test_source_pin_is_not_caller_controlled) ... ok
|
||||
test_array_order_and_all_differences (__main__.TypedTrees.test_array_order_and_all_differences) ... ok
|
||||
test_components_and_sha (__main__.TypedTrees.test_components_and_sha) ... ok
|
||||
test_deep_state_not_truncated (__main__.TypedTrees.test_deep_state_not_truncated) ... ok
|
||||
test_duplicate_textual_keys_rejected (__main__.TypedTrees.test_duplicate_textual_keys_rejected) ... ok
|
||||
test_exact_and_types (__main__.TypedTrees.test_exact_and_types) ... ok
|
||||
test_nested_order_all_paths (__main__.TypedTrees.test_nested_order_all_paths) ... ok
|
||||
test_nonstring_keys_keep_types_and_values (__main__.TypedTrees.test_nonstring_keys_keep_types_and_values) ... ok
|
||||
test_unsupported_and_malformed (__main__.TypedTrees.test_unsupported_and_malformed) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 146 tests in 293.277s
|
||||
Ran 161 tests in 121.627s
|
||||
|
||||
OK
|
||||
|
||||
exit_code=0
|
||||
wall_seconds=293.5742515
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
f52106391229dbf46d82e7dd9cdbc290b65f2ae9ed0e2a49d2cd4933c8af4ade UNIT_OUTPUT_S5_ATTEMPT_01.txt
|
||||
+316
@@ -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
|
||||
+1
@@ -0,0 +1 @@
|
||||
67b91e02e30046cefecf260aa2eb284e0bab5ae2d9728f674eea5bd06e38db0c UNIT_OUTPUT_S5_ATTEMPT_02.txt
|
||||
+163
@@ -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
|
||||
+1
@@ -0,0 +1 @@
|
||||
e2833369d98a4cf6600fcd98dcf271c2172e8b4a5fbb366eae4fcebc5ea8921d UNIT_OUTPUT_S5_ATTEMPT_03.txt
|
||||
+167
@@ -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
|
||||
+1
@@ -0,0 +1 @@
|
||||
f8b652c4f0cdc52c061cd58f1a1334e755c22d5dc206225a5cf2d4863a780c85 UNIT_OUTPUT_S5_ATTEMPT_04.txt
|
||||
+202
@@ -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)
|
||||
+1
@@ -0,0 +1 @@
|
||||
7372a382cc2f3c090013874d8f28f17cad4cb1b965ad86c86de7a21fc2d851a8 UNIT_OUTPUT_S5_ATTEMPT_05.txt
|
||||
+167
@@ -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 ast
|
||||
import copy
|
||||
import gc
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
@@ -18,7 +19,7 @@ import compare_versions as eq
|
||||
|
||||
def check_python_imports(text):
|
||||
"""Closed EQ/stdlib import screen; dynamic file loading is separately pinned by run_eq."""
|
||||
allowed = {'argparse', 'ast', 'base64', 'copy', 'hashlib', 'importlib', 'json', 'math',
|
||||
allowed = {'argparse', 'ast', 'base64', 'collections', 'copy', 'gc', 'gzip', 'hashlib', 'importlib', 'json', 'math',
|
||||
'os', 'pathlib', 're', 'shutil', 'struct', 'subprocess', 'sys', 'tempfile',
|
||||
'unittest', 'uuid', 'check_eq', 'compare_versions', 'run_eq', 'verify_eq'}
|
||||
tree = ast.parse(text)
|
||||
@@ -54,6 +55,7 @@ def integer(value):
|
||||
|
||||
|
||||
def fields(tree):
|
||||
tree = eq.materialize(tree)
|
||||
eq.require(tree[0] == 27, 'dictionary required')
|
||||
result = {}
|
||||
for key, value in tree[1]:
|
||||
@@ -62,7 +64,21 @@ def fields(tree):
|
||||
return result
|
||||
|
||||
|
||||
_COUNTS = {}
|
||||
|
||||
|
||||
def counts(tree):
|
||||
"""Domain-event counter and published IDs; memoized by tree SHA (content-addressed)."""
|
||||
known = eq.known_sha(tree)
|
||||
if known is not None and known in _COUNTS:
|
||||
return _COUNTS[known]
|
||||
result = _counts(tree)
|
||||
if known is not None:
|
||||
_COUNTS[known] = result
|
||||
return result
|
||||
|
||||
|
||||
def _counts(tree):
|
||||
runtime = fields(fields(fields(tree)['systems'])['runtime'])
|
||||
counters = fields(fields(runtime['id_generator'])['counters'])
|
||||
# Product get_counter has an explicit zero default for an absent prefix.
|
||||
@@ -83,38 +99,96 @@ def normalized_digest(tree):
|
||||
Key nodes remain exact. Only Dictionary enumeration order is canonicalized,
|
||||
matching S1 fallback; floats, metadata and Array order remain untouched.
|
||||
"""
|
||||
def canonical(node):
|
||||
result = copy.deepcopy(node)
|
||||
if node[0] == 27:
|
||||
names = [eq._key_text(k) for k, _ in node[1]]
|
||||
eq.require(len(set(names)) == len(names), 'digest textual key collision')
|
||||
result[1] = [[copy.deepcopy(k), canonical(v)] for k, v in sorted(node[1], key=lambda p: eq._key_text(p[0]))]
|
||||
elif node[0] == 28 or 5 <= node[0] <= 20 or 30 <= node[0] <= 38:
|
||||
result[1] = [canonical(v) for v in node[1]]
|
||||
return result
|
||||
eq.validate_tree(tree)
|
||||
return eq.tree_sha(canonical(tree))
|
||||
return eq.canonical_digest(tree)
|
||||
|
||||
|
||||
def edit_at(tree, names, change):
|
||||
"""Copy of tree with the node at the textual path replaced by change(node).
|
||||
|
||||
Only the dictionaries on the path are copied; all other nodes are shared, so the
|
||||
result has the same JSON as deepcopy-then-mutate. Inputs are never modified."""
|
||||
tree = eq.materialize(tree)
|
||||
if not names:
|
||||
return change(tree)
|
||||
data = fields(tree)
|
||||
eq.require(names[0] in data, 'missing path: ' + names[0])
|
||||
pairs = [[k, edit_at(v, names[1:], change)] if k[0] in (4, 21) and k[1] == names[0] else [k, v] for k, v in tree[1]]
|
||||
return [27, pairs, tree[2]]
|
||||
|
||||
|
||||
def describe(tree):
|
||||
"""Large trees in derived results are recorded by SHA; raw blobs keep the content."""
|
||||
known = eq.known_sha(tree)
|
||||
if known is None:
|
||||
return tree
|
||||
normalized = isinstance(tree, eq.TreeRef) and tree.base is not None
|
||||
source = tree.base.sha if normalized else known
|
||||
return {'typed_tree_sha': known, 'source_tree_sha': source, 'normalized': normalized}
|
||||
|
||||
|
||||
class memo_scope:
|
||||
"""Within one verification call, identical check inputs are checked once (S5).
|
||||
|
||||
The keys are SHA-256 of the exact JSON inputs; blobs are content-addressed, so equal
|
||||
inputs give equal results. Only successful results are reused; outside a scope every
|
||||
call recomputes."""
|
||||
def __enter__(self):
|
||||
global _MEMO
|
||||
self.previous, _MEMO = _MEMO, ({} if _MEMO is None else _MEMO)
|
||||
# Typed trees are acyclic lists; cyclic GC passes over millions of their nodes
|
||||
# dominated the R time (E45 123 s -> 18 s). Results do not depend on GC.
|
||||
self.collecting = gc.isenabled()
|
||||
gc.disable()
|
||||
return self
|
||||
|
||||
def __exit__(self, *_):
|
||||
global _MEMO
|
||||
_MEMO = self.previous
|
||||
if self.collecting:
|
||||
gc.enable()
|
||||
return False
|
||||
|
||||
|
||||
_MEMO = None
|
||||
|
||||
|
||||
def _memoized(name, function, *args):
|
||||
if _MEMO is None:
|
||||
return function(*args)
|
||||
key = (name, sha(eq.json_bytes(list(args))))
|
||||
if key not in _MEMO:
|
||||
_MEMO[key] = function(*args)
|
||||
return _MEMO[key]
|
||||
|
||||
|
||||
def join_report(raw):
|
||||
"""Return (joined report, reversible normalization evidence), never edit raw."""
|
||||
return _memoized('join_report', _join_report, raw)
|
||||
|
||||
|
||||
def _join_report(raw):
|
||||
joined = copy.deepcopy(raw)
|
||||
proofs = []
|
||||
trees = eq.reconstruct_checkpoints(raw['checkpoints'])
|
||||
for cp, (s, latest) in zip(joined['checkpoints'], trees):
|
||||
row = {}
|
||||
for name, tree in (('S', s), ('latest', latest)):
|
||||
for name, tree in (('latest', latest), ('S', s)):
|
||||
normalized, side = eq.normalize_tree(tree, raw['profile_root'])
|
||||
restored = eq.restore_tree(normalized, side, raw['profile_root'])
|
||||
eq.require(eq.tree_sha(restored) == eq.tree_sha(tree), 'normalization restore SHA')
|
||||
row[name] = {'normalized_tree_sha': eq.tree_sha(normalized), 'normalized_digest': normalized_digest(normalized), 'sidecar': side,
|
||||
# The harness digest is the latest's (the only one compared); S records it only
|
||||
# when S is the same normalized tree, otherwise null.
|
||||
digest = (eq.harness_digest(normalized) if name == 'latest' else
|
||||
row['latest']['normalized_digest'] if eq.tree_sha(normalized) == row['latest']['normalized_tree_sha'] else None)
|
||||
row[name] = {'normalized_tree_sha': eq.tree_sha(normalized), 'normalized_digest': digest, 'sidecar': side,
|
||||
'restored_tree_sha': eq.tree_sha(restored)}
|
||||
row = {'S': row['S'], 'latest': row['latest']}
|
||||
expected = row['latest']['normalized_digest']
|
||||
eq.require(cp['harness_normalized_digest'] in (None, expected), 'forged normalized digest')
|
||||
cp['harness_normalized_digest'] = expected
|
||||
proofs.append(row)
|
||||
for i, scenario in enumerate(raw.get('scenarios', [])):
|
||||
derived, scenario_proofs = join_report({'profile_root': raw['profile_root'], 'checkpoints': scenario['checkpoints']})
|
||||
derived, scenario_proofs = _join_report({'profile_root': raw['profile_root'], 'checkpoints': scenario['checkpoints']})
|
||||
joined['scenarios'][i]['checkpoints'] = derived['checkpoints']
|
||||
proofs.append({'scenario': scenario['id'], 'checkpoints': scenario_proofs})
|
||||
if scenario['id'].startswith(('E4/', 'E5/')):
|
||||
@@ -124,6 +198,10 @@ def join_report(raw):
|
||||
|
||||
|
||||
def check_measurement(raw, measurement):
|
||||
return _memoized('check_measurement', _check_measurement, raw, measurement)
|
||||
|
||||
|
||||
def _check_measurement(raw, measurement):
|
||||
eq.require(type(measurement) is dict, 'measurement connection inventory missing')
|
||||
eq.require(set(measurement) == {'probe_object', 'connections', 'start_minute'}, 'measurement inventory fields')
|
||||
eq.require(measurement['start_minute'] == raw['checkpoints'][0]['minute'], 'inventory B1 minute')
|
||||
@@ -136,9 +214,16 @@ def check_measurement(raw, measurement):
|
||||
eq.require(set(row) == {'object', 'method', 'flags'}, 'connection fields')
|
||||
eq.require(sum(r['object'] == probe and r['method'] == method for r in rows) == 1,
|
||||
'missing/duplicate probe connection: ' + signal)
|
||||
expected = [{'signal': signal, 'minute': cp['minute']} for cp in raw['checkpoints'][1:]
|
||||
for signal in ('snapshot_published', 'state_updated')]
|
||||
eq.require(eq.exact(raw['signal_order'], expected), 'exact measured signal pairs/checkpoint minutes required')
|
||||
# S5: one tick-end publish per measured minute, plus one publish after each run of
|
||||
# commands completed by a paused submit (R minute 648: 50 commands, 51 pairs). The
|
||||
# S3a rule of exactly one pair per minute was a harness assumption, not product behavior.
|
||||
events = event_numbers([eq._transport(row['transport'], None) for row in raw['events']])
|
||||
trees = eq.reconstruct_checkpoints(raw['checkpoints'])
|
||||
command, other = check_publishes(raw['signal_order'], events, raw['event_start']['counter'], raw['event_end']['counter'],
|
||||
next_command_id(trees[0][0]), next_command_id(trees[-1][0]))
|
||||
minutes = [cp['minute'] for cp in raw['checkpoints'][1:]]
|
||||
eq.require(sorted(m for m, _ in other) == minutes and all(m in minutes for m, _ in command),
|
||||
'exact measured signal pairs/checkpoint minutes required')
|
||||
|
||||
|
||||
def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_evidence=None):
|
||||
@@ -173,7 +258,8 @@ def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_ev
|
||||
eq.require(type(c['_mvp_manifest_cache_generation']) is int, 'cache generation')
|
||||
eq.require(c['catalog_is_current'] is True, 'unfilled checkpoint cache')
|
||||
ids = [c['catalog_instance_id'], c['current_catalog_instance_id']]
|
||||
eq.require(all(type(v) is str and re.fullmatch('[1-9][0-9]*', v) for v in ids), 'cache instance IDs')
|
||||
# Godot instance IDs are signed 64-bit; RefCounted IDs are negative. Raw values are kept.
|
||||
eq.require(all(type(v) is str and re.fullmatch('-?[1-9][0-9]*', v) for v in ids), 'cache instance IDs')
|
||||
eq.require(ids[0] == ids[1], 'cache identity contradiction')
|
||||
eq.validate_tree([29, c['_mvp_manifest_cache_bytes']])
|
||||
trees = eq.reconstruct_checkpoints(cps)
|
||||
@@ -203,7 +289,7 @@ def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_ev
|
||||
eq.require(set(end_ids) == set(begin_ids) | set(event_ids), 'missing/extra E2 events')
|
||||
eq.require(end['counter'] - begin['counter'] == len(event_ids), 'event counter delta')
|
||||
for row in raw['signal_order']:
|
||||
eq.require(set(row) == {'signal', 'minute'} and row['signal'] in ('snapshot_published', 'state_updated'), 'signal record')
|
||||
eq.require(set(row) == {'signal', 'minute', 'event_counter', 'command_counter'} and row['signal'] in ('snapshot_published', 'state_updated'), 'signal record')
|
||||
eq.require(type(row['minute']) is int and start_minute <= row['minute'] <= start_minute+30, 'signal minute')
|
||||
check_measurement(raw, measurement)
|
||||
out.update(ok=True, normalization=proofs, comparison=compared)
|
||||
@@ -212,6 +298,16 @@ def check_report(raw, measurement=None, e3_evidence=None, b1_record=None, e45_ev
|
||||
return out
|
||||
|
||||
|
||||
def raw_layer_sha(value):
|
||||
"""S/K/C bytes: an S5 blob reference (verified on load) or legacy base64 text."""
|
||||
if type(value) is dict:
|
||||
eq.require(set(value) == {'blob'}, 'raw layer blob fields')
|
||||
eq.blob_bytes(value['blob'])
|
||||
return value['blob']
|
||||
eq.validate_tree([29, value])
|
||||
return sha(base64.b64decode(value, validate=True))
|
||||
|
||||
|
||||
def check_observation(cp, evidence):
|
||||
"""Bind retained full bytes to report SHA, not merely asserted equality."""
|
||||
eq.require(set(evidence) == {'before', 'after'}, 'observation evidence fields')
|
||||
@@ -220,8 +316,7 @@ def check_observation(cp, evidence):
|
||||
eq.require(set(row) == {'sha', 'S', 'K', 'C', 'kernel', 'cache'}, 'raw layer fields')
|
||||
eq.require(eq.exact(row['kernel'], cp['K']) and eq.exact(row['cache'], cp['C']), 'raw layer value contradiction')
|
||||
for layer in ('S', 'K', 'C'):
|
||||
eq.validate_tree([29, row[layer]])
|
||||
actual = sha(base64.b64decode(row[layer], validate=True))
|
||||
actual = raw_layer_sha(row[layer])
|
||||
eq.require(actual == row['sha'][layer] == cp['observation'][side][layer], 'raw layer SHA mismatch')
|
||||
eq.require(all(evidence['before'][k] == evidence['after'][k] for k in ('S', 'K', 'C')), 'raw observation bytes differ')
|
||||
return True
|
||||
@@ -245,15 +340,22 @@ def strict_json(text):
|
||||
|
||||
def check_lifecycle(raw, lifecycle, guard):
|
||||
"""S2 startup proof; S4 must supply the separately SHA-bound guard receipt."""
|
||||
eq.require(lifecycle['stage'] in ('S2', 'S3a', 'S3b') and lifecycle['errors'] == [], 'probe errors/stage')
|
||||
eq.require(lifecycle['stop_reason'] == {'S2': 'S2_complete_S3_pending', 'S3a': 'S3a_complete_S3b_pending', 'S3b': 'S3b_complete_S4_pending'}[lifecycle['stage']], 'probe did not finish stage')
|
||||
if lifecycle['stage'] in ('S3a', 'S3b'):
|
||||
eq.require(lifecycle['stage'] in ('S2', 'S3a', 'S3b', 'S5') and lifecycle['errors'] == [], 'probe errors/stage')
|
||||
eq.require(lifecycle['stop_reason'] == {'S2': 'S2_complete_S3_pending', 'S3a': 'S3a_complete_S3b_pending', 'S3b': 'S3b_complete_S4_pending', 'S5': 'S5_complete'}[lifecycle['stage']], 'probe did not finish stage')
|
||||
if lifecycle['stage'] in ('S3a', 'S3b', 'S5'):
|
||||
check_measurement(raw, lifecycle['measurement'])
|
||||
check_e3(dict(raw, scenarios=raw['scenarios'][:18]), lifecycle['e3'], lifecycle['e3_b1'])
|
||||
if lifecycle['stage'] == 'S3b':
|
||||
if lifecycle['stage'] in ('S3b', 'S5'):
|
||||
check_e45(raw, lifecycle['e45'], lifecycle['e3'][-1]['end'])
|
||||
if lifecycle['stage'] == 'S5':
|
||||
eq.require(eq.exact(lifecycle['event_stream_replacements'], stream_facts(lifecycle['e3'], lifecycle['e45'])),
|
||||
'event stream replacement record differs')
|
||||
binding = lifecycle['guard']
|
||||
eq.require(guard['verified'] is True and eq.exact(binding['receipt'], guard), 'independent guard differs')
|
||||
# The probe's parsed receipt has float numbers (Godot JSON); bind its exact text instead.
|
||||
receipt = strict_json(binding['receipt_text']) if 'receipt_text' in binding else binding['receipt']
|
||||
eq.require(guard['verified'] is True and eq.exact(receipt, guard), 'independent guard differs')
|
||||
if 'receipt_text' in binding:
|
||||
eq.require(sha(binding['receipt_text'].encode('utf-8')) == binding['sha256'], 'guard receipt text/SHA differs')
|
||||
root = eq._profile(raw['profile_root'])
|
||||
eq.require(root == binding['live_root'] == guard['actual_user_data_dir'], 'live/guard profile contradiction')
|
||||
eq.require(hex_sha(binding['sha256']), 'guard SHA missing')
|
||||
@@ -264,8 +366,7 @@ def check_lifecycle(raw, lifecycle, guard):
|
||||
row = lifecycle[label]
|
||||
eq.require(row['kernel']['paused'] is True and row['kernel']['absolute_minute'] == minute, 'lifecycle minute: '+label)
|
||||
for layer in ('S', 'K', 'C'):
|
||||
eq.validate_tree([29, row[layer]])
|
||||
eq.require(sha(base64.b64decode(row[layer], validate=True)) == row['sha'][layer], 'lifecycle raw SHA')
|
||||
eq.require(raw_layer_sha(row[layer]) == row['sha'][layer], 'lifecycle raw SHA')
|
||||
for label in ('before_main', 'after_main', 'frame1', 'frame2'):
|
||||
eq.require(type(lifecycle[label]) is dict and lifecycle[label]['kernel']['paused'] is True, 'startup frame evidence')
|
||||
ops = lifecycle['ops']
|
||||
@@ -328,10 +429,14 @@ def check_probe(text):
|
||||
if '.advance_minutes(' in clean and not re.search(r'\.advance_minutes\(1\)', clean): errors.append('only one-minute stepping allowed')
|
||||
# A digest alone, stub proof, or truncated checkpoint producer is not admitted.
|
||||
required = {
|
||||
'transport': ('var_to_bytes(value)', 'var_to_bytes(decode(tree))', 'raw == rebuilt', 'encode(value)', 'hashed(json_text(tree).to_utf8_buffer())'),
|
||||
'checkpoint': ('layers()', 'transport(runtime.create_snapshot())', 'transport(latest_value)', 'before.s_bytes == after.s_bytes', 'before.k_bytes == after.k_bytes', 'before.c_bytes == after.c_bytes', 'diff_record(', 'write_evidence('),
|
||||
'transport': ('var_to_bytes(value)', 'var_to_bytes(decode(tree))', 'raw == rebuilt', 'encode(value)', 'json_text(tree).to_utf8_buffer()', '"tree_sha": hashed(text)', 'store_blob(text)'),
|
||||
'store_blob': ('hashed(bytes)', 'COMPRESSION_GZIP', 'decompress_dynamic(', 'refuse blob overwrite', 'blob_index[digest]'),
|
||||
'checkpoint': ('layers()', 'runtime.create_snapshot()', 'transport(snapshot_value)', 'transport(latest_value)', 'before.s_bytes == after.s_bytes', 'before.k_bytes == after.k_bytes', 'before.c_bytes == after.c_bytes', 'write_evidence('),
|
||||
'bind_profile': ('ProjectSettings.globalize_path("user://").simplify_path()', 'receipt.get("actual_user_data_dir")', 'actual == live_root'),
|
||||
'observe_event': ('event.to_dictionary()', 'transport(value)', 'typeof(event.event_id)', 'typed_info(event.payload)', 'report.events.append('),
|
||||
'observe_event': ('event.to_dictionary()', 'transport(value)', 'typeof(event.event_id)', 'typed_info(event.payload)', 'report.events.append(',
|
||||
'known_event_ids[str(event.event_id)] = true'),
|
||||
'follow_event_stream': ('runtime.domain_events', 'current.recent_events()', 'known_event_ids.has(', 'observe_event(event)',
|
||||
'current.subscribe(&"*", &"p28_eq_observer", observe_event)', 'event_stream_replacements.append('),
|
||||
'begin': ('runtime.domain_events.subscribe(&"*"', 'report.event_start = event_counts()', 'await step()', 'checkpoint()'),
|
||||
'finish': ('root.close_requested.emit()', 'runtime.domain_events.unsubscribe(', 'write_evidence("events.json"', 'write_evidence("lifecycle.json"'),
|
||||
}
|
||||
@@ -354,6 +459,10 @@ def check_probe(text):
|
||||
body = '\n'.join(functions.get(name, []))
|
||||
for snippet in snippets:
|
||||
if snippet not in body: errors.append('missing E3 boundary/call: '+name+': '+snippet)
|
||||
# S5: a successful restore replaces the product event stream; every restore/load is followed.
|
||||
if ('\n'.join(functions.get('run_e3', [])).count('follow_event_stream()') != 1 or
|
||||
'\n'.join(functions.get('run_e4', [])).count('follow_event_stream()') != 2):
|
||||
errors.append('event stream replacement not followed after restore/load')
|
||||
if code.count('run_e3()') != 2 or code.count('e3_prepare_package(') != 2:
|
||||
errors.append('unexpected scenario dispatch')
|
||||
scenario_body = '\n'.join(functions.get('run_e3', []))
|
||||
@@ -396,42 +505,50 @@ def node_at(tree, *names):
|
||||
|
||||
|
||||
def e3_expected_input(b1, kind):
|
||||
value = copy.deepcopy(b1)
|
||||
value = eq.materialize(b1)
|
||||
if kind == 'missing_key':
|
||||
value[1] = [pair for pair in value[1] if pair[0][1] != 'clock']
|
||||
value = [value[0], [pair for pair in value[1] if pair[0][1] != 'clock'], value[2]]
|
||||
elif kind in ('wrong_type', 'out_of_range'):
|
||||
node_at(value, 'clock', 'absolute_minute' if kind == 'wrong_type' else 'speed')[:] = (
|
||||
[4, 'x'] if kind == 'wrong_type' else [2, 9])
|
||||
value = edit_at(value, ['clock', 'absolute_minute' if kind == 'wrong_type' else 'speed'],
|
||||
lambda node: [4, 'x'] if kind == 'wrong_type' else [2, 9])
|
||||
elif kind in ('duplicate_published_ids', 'duplicate_facility'):
|
||||
nodes = (node_at(value, 'systems', 'runtime', 'domain_events', 'published_ids') if kind == 'duplicate_published_ids'
|
||||
else node_at(value, 'systems', 'runtime', 'facilities', 'instances'))
|
||||
if nodes[1]:
|
||||
nodes[1].append(copy.deepcopy(nodes[1][0]))
|
||||
else:
|
||||
names = (['systems', 'runtime', 'domain_events', 'published_ids'] if kind == 'duplicate_published_ids'
|
||||
else ['systems', 'runtime', 'facilities', 'instances'])
|
||||
def duplicate(nodes):
|
||||
if nodes[1]:
|
||||
return [nodes[0], nodes[1] + [copy.deepcopy(nodes[1][0])]] + nodes[2:]
|
||||
eq.require(kind == 'duplicate_facility', 'no B1 published ID to duplicate')
|
||||
return nodes
|
||||
value = edit_at(value, names, duplicate)
|
||||
return value
|
||||
|
||||
|
||||
def e3_x(before, event):
|
||||
value = copy.deepcopy(before)
|
||||
counter = node_at(value, 'systems', 'runtime', 'id_generator', 'counters', 'domain_event')
|
||||
counter[1] += 1
|
||||
value = edit_at(before, ['systems', 'runtime', 'id_generator', 'counters', 'domain_event'],
|
||||
lambda counter: [counter[0], counter[1] + 1] + counter[2:])
|
||||
domain = node_at(value, 'systems', 'runtime', 'domain_events')
|
||||
ids = fields(domain)['published_ids']
|
||||
ids[1].append(copy.deepcopy(fields(event)['event_id']))
|
||||
ids[1].sort(key=lambda n: n[1])
|
||||
fields(domain)['published_ids']
|
||||
event_id = copy.deepcopy(fields(event)['event_id'])
|
||||
recent = fields(domain)['recent_events']
|
||||
dropped = copy.deepcopy(recent[1][0]) if len(recent[1]) == 256 else None
|
||||
recent[1].append(copy.deepcopy(event))
|
||||
if len(recent[1]) > 256:
|
||||
del recent[1][0]
|
||||
def published(ids):
|
||||
children = ids[1] + [event_id]
|
||||
children.sort(key=lambda n: n[1])
|
||||
return [ids[0], children] + ids[2:]
|
||||
def appended(events):
|
||||
children = events[1] + [copy.deepcopy(event)]
|
||||
if len(children) > 256:
|
||||
del children[0]
|
||||
return [events[0], children] + events[2:]
|
||||
value = edit_at(value, ['systems', 'runtime', 'domain_events', 'published_ids'], published)
|
||||
value = edit_at(value, ['systems', 'runtime', 'domain_events', 'recent_events'], appended)
|
||||
return value, dropped
|
||||
|
||||
|
||||
def e3_raw_observation(row):
|
||||
cp = row['checkpoint']
|
||||
check_observation(cp, row['raw'])
|
||||
s = eq._transport(cp['S'], None)
|
||||
s = eq._transport_ref(cp['S'])
|
||||
eq.require(cp['S']['raw_bytes_sha'] == row['raw']['before']['sha']['S'], 'E3 S transport/raw')
|
||||
for layer in ('K', 'C'):
|
||||
proof = row[layer+'_tree']
|
||||
@@ -449,7 +566,8 @@ def e3_package_check(package, kind):
|
||||
out[name] = None
|
||||
continue
|
||||
eq.require(set(item) == {'bytes', 'sha256'}, 'package file fields')
|
||||
content = base64.b64decode(item['bytes'], validate=True)
|
||||
content = (eq.blob_bytes(item['bytes']['blob']) if type(item['bytes']) is dict and set(item['bytes']) == {'blob'}
|
||||
else base64.b64decode(item['bytes'], validate=True))
|
||||
eq.require(sha(content) == item['sha256'], 'package file SHA')
|
||||
out[name] = content
|
||||
return out
|
||||
@@ -482,6 +600,10 @@ def e3_package_check(package, kind):
|
||||
|
||||
|
||||
def check_e3(raw, evidence, b1_record):
|
||||
return _memoized('check_e3', _check_e3, raw, evidence, b1_record)
|
||||
|
||||
|
||||
def _check_e3(raw, evidence, b1_record):
|
||||
"""Separate malformed evidence from observed existing-defect candidates.
|
||||
|
||||
Returns all actual typed differences. Never promote NA or a defect to PASS.
|
||||
@@ -568,6 +690,11 @@ def check_e3(raw, evidence, b1_record):
|
||||
'duplicate_published_ids': 'RESTORE_FAILED', 'duplicate_facility': 'RESTORE_FAILED', 'tampered_world': 'CHECKSUM_MISMATCH'}[kind]
|
||||
if result['code'] != expected_code and not (kind == 'out_of_range' and result['migration_attempted']):
|
||||
defects.append({'kind': 'unexpected_code', 'expected': expected_code, 'actual': result['code']})
|
||||
defects.extend(stream_defects(ev['event_stream'], events))
|
||||
# Only a successful restore publishes inside E3 (an accepted invalid input is a defect).
|
||||
_, other = check_publishes(ev['signals'], event_numbers(events), counts(before)[0]['counter'], counts(after)[0]['counter'],
|
||||
next_command_id(before), next_command_id(after))
|
||||
eq.require(len(other) == int(api in ('restore_snapshot', 'load_from_path') and result['ok'] is True), 'E3 publish missing/extra')
|
||||
expected_count = 1 if api == 'load_from_path' and invoked else 0
|
||||
if len(events) != expected_count:
|
||||
defects.append({'kind': 'event_count', 'expected': expected_count, 'actual': len(events)})
|
||||
@@ -592,6 +719,8 @@ def check_e3(raw, evidence, b1_record):
|
||||
if 'recovery_report' in data and fields(data['recovery_report']).get('restore_committed') != [1, 0]:
|
||||
defects.append({'kind': 'load_restore_committed', 'actual': data['recovery_report']})
|
||||
expected_state, dropped = e3_x(before, events[0])
|
||||
if expected_state is not before and eq.known_sha(before) is not None:
|
||||
expected_state = eq.adopt(expected_state)
|
||||
normalized_expected, expected_side = eq.normalize_tree(expected_state, raw['profile_root'])
|
||||
normalized_after, actual_side = eq.normalize_tree(after, raw['profile_root'])
|
||||
delta = eq.compare_trees(normalized_expected, normalized_after)
|
||||
@@ -600,7 +729,7 @@ def check_e3(raw, evidence, b1_record):
|
||||
left, right = ev['begin']['raw']['before'], ev['end']['raw']['after']
|
||||
if left[layer] != right[layer]:
|
||||
defects.append({'kind': layer+'_changes', 'before': left[layer], 'after': right[layer],
|
||||
'comparison': eq.compare_trees(ev['begin'][layer+'_tree']['tree'], ev['end'][layer+'_tree']['tree'])})
|
||||
'comparison': eq.compare_trees(eq._transport(ev['begin'][layer+'_tree'], None), eq._transport(ev['end'][layer+'_tree'], None))})
|
||||
# Dictionary APIs require byte identity, not normalization/order fallback.
|
||||
if expected_count == 0 and ev['begin']['raw']['before']['S'] != ev['end']['raw']['after']['S']:
|
||||
defects.append({'kind': 'S_bytes_changed', 'before': ev['begin']['raw']['before']['S'], 'after': ev['end']['raw']['after']['S']})
|
||||
@@ -608,7 +737,7 @@ def check_e3(raw, evidence, b1_record):
|
||||
'expected': {'ok': False if applied else None, 'code': expected_code, 'error': expected_error, 'event_count': expected_count},
|
||||
'actual': copy.deepcopy(result),
|
||||
'status': 'existing-defect-candidate' if defects else ('recorded' if applied else 'NA'),
|
||||
'defects': defects, 'dropped_recent_event': dropped, 'expected_state': expected_state,
|
||||
'defects': defects, 'dropped_recent_event': dropped, 'expected_state': describe(expected_state),
|
||||
'state_comparison': delta, 'expected_sidecar': expected_side, 'actual_sidecar': actual_side})
|
||||
results[-1]['exclusions'] = [{'reason': 'SaveService static diagnostic budget outside S/K/C; wall time',
|
||||
'before': eq._transport(ev['save_budget_before'], None), 'after': eq._transport(ev['save_budget_after'], None)},
|
||||
@@ -617,6 +746,121 @@ def check_e3(raw, evidence, b1_record):
|
||||
|
||||
|
||||
|
||||
COMMAND_EVENTS = ('COMMAND_COMPLETED', 'COMMAND_FAILED')
|
||||
|
||||
|
||||
def event_numbers(trees):
|
||||
"""domain_event.%012d IDs -> (event_type, absolute_minute, command ID or None)."""
|
||||
result = {}
|
||||
for tree in trees:
|
||||
data = fields(tree)
|
||||
match = re.fullmatch('domain_event[.]([0-9]{12})', data['event_id'][1])
|
||||
eq.require(match is not None and int(match[1]) not in result, 'event ID/counter form')
|
||||
command = None
|
||||
if data['event_type'][1] in COMMAND_EVENTS:
|
||||
command = fields(data['payload'])['command_id']
|
||||
eq.require(command[0] == 2 and integer(command[1]), 'command event ID')
|
||||
command = command[1]
|
||||
result[int(match[1])] = (data['event_type'][1], data['absolute_minute'][1], command)
|
||||
return result
|
||||
|
||||
|
||||
_COMMANDS = {}
|
||||
|
||||
|
||||
def next_command_id(tree):
|
||||
known = eq.known_sha(tree)
|
||||
if known is not None and known in _COMMANDS:
|
||||
return _COMMANDS[known]
|
||||
value = node_at(tree, 'systems', 'runtime', 'next_command_id')
|
||||
eq.require(value[0] == 2 and integer(value[1]), 'next_command_id')
|
||||
if known is not None:
|
||||
_COMMANDS[known] = value[1]
|
||||
return value[1]
|
||||
|
||||
|
||||
def _tree_facts(tree):
|
||||
"""Parse hook: small per-tree facts are taken while the tree is in memory."""
|
||||
for function in (counts, next_command_id):
|
||||
try:
|
||||
function(tree)
|
||||
except (eq.EvidenceError, KeyError, TypeError, IndexError):
|
||||
pass
|
||||
|
||||
|
||||
eq.PARSE_HOOKS.append(_tree_facts)
|
||||
|
||||
|
||||
def check_publishes(signals, events, start, end, commands_start, commands_end):
|
||||
"""Bind every recorded publish to its source cause; return (command pairs, other pairs).
|
||||
|
||||
_publish_snapshot emits snapshot_published then state_updated with no event between
|
||||
(simulation_runtime.gd:12616-12621). A submit while paused processes the due command at
|
||||
once and publishes after the command events of that call (:510-512, :5768-5793). Every
|
||||
other publish is a tick end (:1748) or a successful restore (:1473). The events between
|
||||
two publishes belong to the later one; a publish whose last preceding event is a command
|
||||
event of its minute is that call's command publish and covers the trailing command
|
||||
events. Every command event must be covered. Each accepted submit advances
|
||||
_next_command_id once (:508) and, being due at once while paused (:493), publishes once:
|
||||
a command publish follows exactly one submit, whose command is in its batch; any other
|
||||
publish follows none, and no submit may remain unpublished. The remaining publishes are
|
||||
counted by the caller against the ticks and restores actually performed, so missing,
|
||||
extra or reordered pairs fail.
|
||||
"""
|
||||
eq.require(type(signals) is list and len(signals) % 2 == 0, 'signal pair missing')
|
||||
pairs = []
|
||||
for i in range(0, len(signals), 2):
|
||||
first, second = signals[i], signals[i+1]
|
||||
eq.require(type(first) is dict and set(first) == {'signal', 'minute', 'event_counter', 'command_counter'}, 'signal record')
|
||||
eq.require(first['signal'] == 'snapshot_published' and all(type(first[k]) is int for k in ('minute', 'event_counter', 'command_counter'))
|
||||
and eq.exact(second, dict(first, signal='state_updated')), 'signal pair order')
|
||||
pairs.append((first['minute'], first['event_counter'], first['command_counter']))
|
||||
eq.require(pairs == sorted(pairs) and all([p[i] for p in pairs] == sorted(p[i] for p in pairs) for i in (1, 2)), 'signal order')
|
||||
eq.require(all(start <= p[1] <= end and commands_start <= p[2] <= commands_end for p in pairs), 'signal outside event interval')
|
||||
command, other, covered, previous, submitted = [], [], set(), start, commands_start
|
||||
for minute, counter, commands in pairs:
|
||||
last = events.get(counter)
|
||||
if counter > previous and last is not None and last[0] in COMMAND_EVENTS and last[1] == minute:
|
||||
batch = []
|
||||
number = counter
|
||||
while number > previous and number in events and events[number][0] in COMMAND_EVENTS:
|
||||
covered.add(number)
|
||||
batch.append(events[number][2])
|
||||
number -= 1
|
||||
eq.require(commands == submitted + 1 and commands - 1 in batch, 'command publish missing')
|
||||
command.append((minute, counter))
|
||||
else:
|
||||
eq.require(commands == submitted, 'command publish missing')
|
||||
other.append((minute, counter))
|
||||
previous, submitted = max(previous, counter), commands
|
||||
eq.require(submitted == commands_end, 'command publish missing')
|
||||
eq.require(covered == {n for n, row in events.items() if row[0] in COMMAND_EVENTS}, 'command publish missing')
|
||||
return command, other
|
||||
|
||||
|
||||
def stream_defects(fact, events):
|
||||
"""S5: a successful restore replaces the product's DomainEventStream without carrying its
|
||||
subscribers. The probe recovers the undelivered events from the live stream and
|
||||
subscribes again; the dropped subscription is an existing product fact, never a PASS."""
|
||||
eq.require(type(fact) is dict and type(fact.get('replaced')) is bool, 'event stream fact missing')
|
||||
if not fact['replaced']:
|
||||
eq.require(fact == {'replaced': False}, 'event stream fact fields')
|
||||
return []
|
||||
eq.require(set(fact) == {'replaced', 'new_stream_subscribers_before', 'recovered_event_ids'}, 'event stream fact fields')
|
||||
ids = [fields(event)['event_id'][1] for event in events]
|
||||
recovered = fact['recovered_event_ids']
|
||||
eq.require(type(recovered) is list and len(set(recovered)) == len(recovered) and set(recovered) <= set(ids), 'recovered events not recorded')
|
||||
eq.require('*/p28_eq_observer' not in fact['new_stream_subscribers_before'], 'subscription unexpectedly carried')
|
||||
return [{'kind': 'event_stream_replaced_subscribers_not_carried', 'new_stream_subscribers_before': fact['new_stream_subscribers_before'],
|
||||
'undelivered_recovered_event_ids': recovered}]
|
||||
|
||||
|
||||
def stream_facts(e3_evidence, e45_evidence):
|
||||
"""Every replacement, in scenario order, as the probe recorded it in lifecycle."""
|
||||
facts = [ev['event_stream'] for ev in e3_evidence] + [ev['detail']['event_stream'] for ev in e45_evidence if 'event_stream' in ev['detail']]
|
||||
return [f for f in facts if f.get('replaced') is True]
|
||||
|
||||
|
||||
# S3b additions. Raw evidence is authoritative; outcome labels are derived.
|
||||
E4_PATHS = ('build_facility', 'tick', 'restore_snapshot', 'load_from_path', 'rejected_command')
|
||||
E5_TARGETS = ('create_snapshot', 'latest_snapshot', 'snapshot_published', 'state_updated')
|
||||
@@ -634,8 +878,8 @@ def join_facts(value, profile_root):
|
||||
normal, side = eq.normalize_tree(tree, profile_root)
|
||||
eq.require(eq.exact(eq.restore_tree(normal, side, profile_root), tree), 'fact normalization roundtrip')
|
||||
proofs.append({'path': path, 'sidecar': side, 'raw': item})
|
||||
return normal
|
||||
return tree
|
||||
return describe(normal)
|
||||
return describe(tree)
|
||||
if isinstance(item, dict):
|
||||
return {k: walk(v, path+[k]) for k, v in item.items()}
|
||||
if isinstance(item, list):
|
||||
@@ -713,6 +957,10 @@ def check_command(detail):
|
||||
|
||||
|
||||
def check_e45(raw, evidence, previous):
|
||||
return _memoized('check_e45', _check_e45, raw, evidence, previous)
|
||||
|
||||
|
||||
def _check_e45(raw, evidence, previous):
|
||||
ids = ['E4/'+p for p in E4_PATHS] + ['E5/'+t+'/'+l+'/'+d for t in E5_TARGETS
|
||||
for l in ('dict', 'array') for d in ('outside_in', 'inside_out')]
|
||||
scenarios = raw['scenarios'][18:]
|
||||
@@ -730,8 +978,8 @@ def check_e45(raw, evidence, previous):
|
||||
common = {'save_budget_before', 'save_budget_after', 'signals'}
|
||||
specific = {
|
||||
'build_facility': {'source', 'raw_ids', 'count', 'sorted_ids', 'sort_rule', 'bounds', 'cell_rule', 'trials', 'chosen', 'applicability', 'reason', 'observation_before', 'observation_after'},
|
||||
'tick': set(), 'restore_snapshot': {'input', 'return'},
|
||||
'load_from_path': {'input', 'path', 'save_return', 'package_before', 'package_after', 'return', 'latest_save_operation_result'},
|
||||
'tick': set(), 'restore_snapshot': {'input', 'return', 'event_stream'},
|
||||
'load_from_path': {'input', 'path', 'save_return', 'package_before', 'package_after', 'return', 'latest_save_operation_result', 'event_stream'},
|
||||
'rejected_command': {'submit', 'command_result'},
|
||||
}[scenario['operation']]
|
||||
if scenario['operation'] == 'build_facility' and detail.get('chosen') is not None: specific |= {'submit', 'command_result'}
|
||||
@@ -748,20 +996,22 @@ def check_e45(raw, evidence, previous):
|
||||
and eq.exact(events, scenario['events']), 'E4/E5 event count binding')
|
||||
eq.require(result['applicability'] == detail.get('applicability', 'applied') and result['reason'] == detail.get('reason'), 'E4/E5 applicability binding')
|
||||
eq.require(eq.exact(result['facts'], e45_facts(detail)), 'E4/E5 facts binding/aliasing record missing')
|
||||
for signal in ev['signals']:
|
||||
eq.require(set(signal) == {'signal', 'minute'} and signal['signal'] in ('snapshot_published', 'state_updated')
|
||||
and obs[0]['checkpoint']['minute'] <= signal['minute'] <= obs[-1]['checkpoint']['minute'], 'scenario signal interval')
|
||||
eq.require(len(ev['signals']) % 2 == 0, 'scenario signal pair missing')
|
||||
for i in range(0, len(ev['signals']), 2):
|
||||
a, b = ev['signals'][i:i+2]
|
||||
eq.require(a['signal'] == 'snapshot_published' and b == {'signal': 'state_updated', 'minute': a['minute']}, 'scenario signal order')
|
||||
command, other = check_publishes(ev['signals'], event_numbers(events), counts(trees[0])[0]['counter'], counts(trees[-1])[0]['counter'],
|
||||
next_command_id(trees[0]), next_command_id(trees[-1]))
|
||||
eq.require(all(obs[0]['checkpoint']['minute'] <= m <= obs[-1]['checkpoint']['minute'] for m, _ in command + other), 'scenario signal interval')
|
||||
op = scenario['operation']
|
||||
if scenario['id'].startswith('E4/'):
|
||||
expected_other = int(detail.get('chosen') is not None) if op == 'build_facility' else 1
|
||||
else:
|
||||
expected_other = 2 if op == 'outside_in' else 3
|
||||
eq.require(len(other) == expected_other, 'scenario tick/restore publish missing/extra')
|
||||
defects = []
|
||||
differences = {}
|
||||
for name in ('S', 'latest'):
|
||||
a, b = [eq._transport(o['checkpoint'][name], None) for o in (obs[0], obs[-1])]
|
||||
differences[name] = eq.compare_trees(a, b)
|
||||
for name in ('K', 'C'):
|
||||
differences[name] = eq.compare_trees(obs[0][name+'_tree']['tree'], obs[-1][name+'_tree']['tree'])
|
||||
differences[name] = eq.compare_trees(eq._transport(obs[0][name+'_tree'], None), eq._transport(obs[-1][name+'_tree'], None))
|
||||
op = scenario['operation']
|
||||
begin_count, begin_ids = counts(trees[0])
|
||||
end_count, end_ids = counts(trees[-1])
|
||||
@@ -782,6 +1032,7 @@ def check_e45(raw, evidence, previous):
|
||||
eq.require(obs[-1]['checkpoint']['minute'] == obs[0]['checkpoint']['minute'] + int(tick), 'E4 tick count')
|
||||
if len(obs) == 3: eq.require(obs[1]['checkpoint']['minute'] == obs[0]['checkpoint']['minute'], 'submit checkpoint delayed')
|
||||
if op in ('restore_snapshot', 'load_from_path'):
|
||||
defects.extend(stream_defects(detail['event_stream'], events))
|
||||
p = eq._transport(detail['input'], None)
|
||||
eq.require(eq.exact(p, trees[0]), 'E4 input not current deep copy')
|
||||
actual = eq._transport(detail['return'], None)
|
||||
@@ -799,11 +1050,13 @@ def check_e45(raw, evidence, previous):
|
||||
package = {'save_return': detail['save_return'], 'original': detail['package_before'],
|
||||
'modified': detail['package_before'], 'after_operation': detail['package_after']}
|
||||
e3_package_check(package, 'good')
|
||||
if expected is not p and eq.known_sha(p) is not None:
|
||||
expected = eq.adopt(expected)
|
||||
left, left_side = eq.normalize_tree(expected, raw['profile_root'])
|
||||
right, right_side = eq.normalize_tree(trees[-1], raw['profile_root'])
|
||||
delta = eq.compare_trees(left, right)
|
||||
eq.require(delta['equal'], 'E4 restore/load X(P,event) mismatch')
|
||||
differences['expected_state'] = {'comparison': delta, 'expected': expected, 'sidecars': [left_side, right_side]}
|
||||
differences['expected_state'] = {'comparison': delta, 'expected': describe(expected), 'sidecars': [left_side, right_side]}
|
||||
if op == 'rejected_command':
|
||||
command = check_command(detail)
|
||||
eq.require(command['code'] != [4, 'OK'], 'nonchanging command not rejected')
|
||||
@@ -823,13 +1076,14 @@ def check_e45(raw, evidence, previous):
|
||||
expected_held = eq._transport(baseline['S' if target == 'create_snapshot' else 'latest'], None)
|
||||
eq.require(eq.exact(held, expected_held), 'E5 returned target binding')
|
||||
if direction == 'outside_in':
|
||||
expected = copy.deepcopy(held)
|
||||
if location == 'dict':
|
||||
n = node_at(expected, 'systems', 'runtime', 'id_generator', 'counters')
|
||||
eq.require('__p28_eq_probe' not in fields(n), 'E5 marker already present')
|
||||
n[1].append([[4, '__p28_eq_probe'], [2, 1]])
|
||||
def marked(n):
|
||||
eq.require('__p28_eq_probe' not in fields(n), 'E5 marker already present')
|
||||
return [n[0], n[1] + [[[4, '__p28_eq_probe'], [2, 1]]]] + n[2:]
|
||||
expected = edit_at(held, ['systems', 'runtime', 'id_generator', 'counters'], marked)
|
||||
else:
|
||||
node_at(expected, 'systems', 'runtime', 'domain_events', 'published_ids')[1].append([4, '__p28_eq_probe'])
|
||||
expected = edit_at(held, ['systems', 'runtime', 'domain_events', 'published_ids'],
|
||||
lambda n: [n[0], n[1] + [[4, '__p28_eq_probe']]] + n[2:])
|
||||
eq.require(eq.exact(expected, after_held), 'E5 fixed nested mutation absent/extra')
|
||||
for layer in ('S', 'latest'):
|
||||
delta = eq.compare_trees(eq._transport(obs[1]['checkpoint'][layer], None), eq._transport(obs[2]['checkpoint'][layer], None))
|
||||
@@ -846,7 +1100,7 @@ def check_e45(raw, evidence, previous):
|
||||
differences['held'] = delta
|
||||
if not delta['equal']: defects.append({'kind': 'internal_mutation_of_returned_value', 'comparison': delta})
|
||||
for layer in ('K', 'C'):
|
||||
differences['ownership_'+layer] = eq.compare_trees(obs[1][layer+'_tree']['tree'], obs[2][layer+'_tree']['tree'])
|
||||
differences['ownership_'+layer] = eq.compare_trees(eq._transport(obs[1][layer+'_tree'], None), eq._transport(obs[2][layer+'_tree'], None))
|
||||
eq.require(len(events) == counts(trees[-1])[0]['counter']-counts(trees[0])[0]['counter'], 'E5 uncapped event counter delta')
|
||||
outcomes.append({'id': scenario['id'], 'status': 'existing-defect' if defects else ('NA' if result['applicability'] == 'NA' else 'recorded'),
|
||||
'defects': defects, 'differences': differences, 'signals': ev['signals'],
|
||||
|
||||
+488
-52
@@ -4,9 +4,12 @@ Paths are lists of ['key', key_node] / ['index', integer] segments.
|
||||
Transport validation is distinct from product acceptance (see IMPLEMENTATION.md).
|
||||
"""
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
import gzip
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
@@ -22,6 +25,142 @@ OPT_PATHS = frozenset(('autoload/simulation_runtime.gd', 'scripts/core/simulatio
|
||||
'scripts/world/path_service.gd', 'scripts/core/command_queue.gd'))
|
||||
SOURCE_SHA = '651f703621c8ed06a724709f75d8bc05b1eef2988c3b3d5d3466096a3f759bf0'
|
||||
TOKEN = '{PRIVATE_USER_ROOT}/'
|
||||
PERMITTED_EVENTS = ('systems', 'runtime', 'domain_events', 'recent_events')
|
||||
|
||||
|
||||
# S5 content-addressed trees (EQ_S5_SCALE_PLAN). The probe writes each large tree's exact
|
||||
# JSON text, and raw S/K/C bytes, once as evidence/blobs/<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):
|
||||
@@ -33,62 +172,138 @@ def json_bytes(value):
|
||||
return json.dumps(value, ensure_ascii=False, separators=(',', ':'), allow_nan=False).encode('utf-8')
|
||||
|
||||
|
||||
def _copy_tree(value):
|
||||
"""Exact copy of nested lists (typed trees hold only lists, ints, strings and None)."""
|
||||
return [_copy_tree(v) if isinstance(v, list) else v for v in value] if isinstance(value, list) else copy.deepcopy(value)
|
||||
|
||||
|
||||
def exact(a, b):
|
||||
if a is b:
|
||||
return True
|
||||
if known_sha(a) is not None or known_sha(b) is not None:
|
||||
for value in (a, b):
|
||||
if known_sha(value) is None:
|
||||
try:
|
||||
validate_tree(value)
|
||||
except EvidenceError:
|
||||
return False
|
||||
return same_tree(a, b)
|
||||
return json_bytes(a) == json_bytes(b)
|
||||
|
||||
|
||||
_BAD_TYPES = frozenset((23, 24, 25, 26))
|
||||
_SIZES = {5: 2, 6: 2, 7: 4, 8: 4, 9: 3, 10: 3, 11: 6, 12: 4, 13: 4, 14: 4, 15: 4, 16: 6, 17: 9, 18: 12, 19: 16, 20: 4}
|
||||
_INTEGER_COMPONENTS = frozenset((6, 8, 10, 13))
|
||||
_PACKED = {30: 2, 31: 2, 32: 3, 33: 3, 34: 4}
|
||||
_PACKED_VECTORS = {35: 5, 36: 9, 37: 20, 38: 12}
|
||||
_HEX16 = re.compile('[0-9a-f]{16}').fullmatch
|
||||
_INT_MIN, _INT_MAX = -(2**63), 2**63
|
||||
|
||||
|
||||
def _leaf(x):
|
||||
"""The node checks for an INT/FLOAT/STRING component, exactly as when it is popped."""
|
||||
if type(x) is not list or not x or type(x[0]) is not int:
|
||||
raise EvidenceError('invalid typed node')
|
||||
t = x[0]
|
||||
if t in _BAD_TYPES or not 0 <= t <= 38:
|
||||
raise EvidenceError('unsupported Variant type')
|
||||
if len(x) != (1 if t == 0 else 3 if t in (27, 28) else 2):
|
||||
raise EvidenceError('node arity')
|
||||
p = x[1]
|
||||
if t == 2:
|
||||
if type(p) is not int or not _INT_MIN <= p < _INT_MAX:
|
||||
raise EvidenceError('integer payload')
|
||||
elif t == 3:
|
||||
if type(p) is not str or not _HEX16(p):
|
||||
raise EvidenceError('float must be f64 LE hex')
|
||||
elif type(p) is not str:
|
||||
raise EvidenceError('string payload')
|
||||
|
||||
|
||||
def validate_tree(node):
|
||||
"""Validate all nodes; no application depth cutoff or value coercion."""
|
||||
"""Validate all nodes; no application depth cutoff or value coercion.
|
||||
|
||||
A blob tree is used only after this full validation (its SHA is then recorded).
|
||||
S5: the S1 checks, messages and order are kept; components are checked in place in the
|
||||
same (last-in, first-out) order in which the S1 stack popped them."""
|
||||
if known_sha(node) is not None:
|
||||
return node
|
||||
pending = [node]
|
||||
pop, extend = pending.pop, pending.extend
|
||||
while pending:
|
||||
n = pending.pop()
|
||||
require(type(n) is list and n and type(n[0]) is int, 'invalid typed node')
|
||||
n = pop()
|
||||
if type(n) is not list or not n or type(n[0]) is not int:
|
||||
raise EvidenceError('invalid typed node')
|
||||
t = n[0]
|
||||
require(t not in (23, 24, 25, 26) and 0 <= t <= 38, 'unsupported Variant type')
|
||||
require(len(n) == (1 if t == 0 else 3 if t in (27, 28) else 2), 'node arity')
|
||||
if t in _BAD_TYPES or not 0 <= t <= 38:
|
||||
raise EvidenceError('unsupported Variant type')
|
||||
if len(n) != (1 if t == 0 else 3 if t == 27 or t == 28 else 2):
|
||||
raise EvidenceError('node arity')
|
||||
if t == 0:
|
||||
continue
|
||||
p = n[1]
|
||||
if t in (1, 2):
|
||||
require(type(p) is int and (p in (0, 1) if t == 1 else -(2**63) <= p < 2**63), 'integer payload')
|
||||
if t == 2:
|
||||
if type(p) is not int or not _INT_MIN <= p < _INT_MAX:
|
||||
raise EvidenceError('integer payload')
|
||||
elif t == 27:
|
||||
if type(p) is not list:
|
||||
raise EvidenceError('dictionary entries')
|
||||
seen = set()
|
||||
for pair in p:
|
||||
if type(pair) is not list or len(pair) != 2:
|
||||
raise EvidenceError('dictionary pair')
|
||||
k = pair[0]
|
||||
# Simple key nodes compare as (type, payload); validated ints are never bool.
|
||||
key = (k[0], k[1]) if type(k) is list and len(k) == 2 and type(k[1]) in (str, int) else json_bytes(k)
|
||||
if key in seen:
|
||||
raise EvidenceError('duplicate dictionary key')
|
||||
seen.add(key)
|
||||
extend(pair)
|
||||
_typed_info(n[2], dictionary=True)
|
||||
elif t == 4 or t == 21 or t == 22:
|
||||
if type(p) is not str:
|
||||
raise EvidenceError('string payload')
|
||||
elif t == 28:
|
||||
if type(p) is not list:
|
||||
raise EvidenceError('array elements')
|
||||
_typed_info(n[2], dictionary=False)
|
||||
extend(p)
|
||||
elif t == 3:
|
||||
require(type(p) is str and re.fullmatch('[0-9a-f]{16}', p), 'float must be f64 LE hex')
|
||||
elif t in (4, 21, 22):
|
||||
require(type(p) is str, 'string payload')
|
||||
if type(p) is not str or not _HEX16(p):
|
||||
raise EvidenceError('float must be f64 LE hex')
|
||||
elif t == 1:
|
||||
if type(p) is not int or p not in (0, 1):
|
||||
raise EvidenceError('integer payload')
|
||||
elif t == 29:
|
||||
require(type(p) is str, 'byte array payload')
|
||||
if type(p) is not str:
|
||||
raise EvidenceError('byte array payload')
|
||||
try:
|
||||
require(base64.b64encode(base64.b64decode(p, validate=True)).decode() == p, 'noncanonical base64')
|
||||
except (ValueError, TypeError) as exc:
|
||||
raise EvidenceError('invalid base64') from exc
|
||||
elif t == 27:
|
||||
require(type(p) is list, 'dictionary entries')
|
||||
seen = set()
|
||||
for pair in p:
|
||||
require(type(pair) is list and len(pair) == 2, 'dictionary pair')
|
||||
key = json_bytes(pair[0])
|
||||
require(key not in seen, 'duplicate dictionary key')
|
||||
seen.add(key)
|
||||
pending.extend(pair)
|
||||
_typed_info(n[2], dictionary=True)
|
||||
elif t == 28:
|
||||
require(type(p) is list, 'array elements')
|
||||
_typed_info(n[2], dictionary=False)
|
||||
pending.extend(p)
|
||||
else:
|
||||
require(type(p) is list, 'component nodes')
|
||||
sizes = {5: 2, 6: 2, 7: 4, 8: 4, 9: 3, 10: 3, 11: 6,
|
||||
12: 4, 13: 4, 14: 4, 15: 4, 16: 6, 17: 9, 18: 12, 19: 16, 20: 4}
|
||||
if t in sizes:
|
||||
require(len(p) == sizes[t], 'component count')
|
||||
integer = t in (6, 8, 10, 13)
|
||||
require(all(x and x[0] == (2 if integer else 3) for x in p), 'component type')
|
||||
elif t in (30, 31, 32, 33, 34):
|
||||
expected = {30: 2, 31: 2, 32: 3, 33: 3, 34: 4}[t]
|
||||
require(all(x and x[0] == expected for x in p), 'packed element type')
|
||||
if type(p) is not list:
|
||||
raise EvidenceError('component nodes')
|
||||
if t in _SIZES:
|
||||
if len(p) != _SIZES[t]:
|
||||
raise EvidenceError('component count')
|
||||
expected = 2 if t in _INTEGER_COMPONENTS else 3
|
||||
if not all(x and x[0] == expected for x in p):
|
||||
raise EvidenceError('component type')
|
||||
elif t in _PACKED:
|
||||
expected = _PACKED[t]
|
||||
if not all(x and x[0] == expected for x in p):
|
||||
raise EvidenceError('packed element type')
|
||||
else:
|
||||
require(all(x and x[0] == {35: 5, 36: 9, 37: 20, 38: 12}[t] for x in p), 'packed vector type')
|
||||
pending.extend(p)
|
||||
expected = _PACKED_VECTORS[t]
|
||||
if not all(x and x[0] == expected for x in p):
|
||||
raise EvidenceError('packed vector type')
|
||||
extend(p)
|
||||
continue
|
||||
# Leaf components (types 2/3/4 by the check above): the S1 stack popped them
|
||||
# last-first, before anything else, so they are checked here in that order.
|
||||
for x in reversed(p):
|
||||
_leaf(x)
|
||||
return node
|
||||
|
||||
|
||||
@@ -106,12 +321,14 @@ def _typed_info(info, dictionary):
|
||||
|
||||
|
||||
def tree_sha(tree):
|
||||
if known_sha(tree) is not None:
|
||||
return known_sha(tree)
|
||||
validate_tree(tree)
|
||||
return hashlib.sha256(json_bytes(tree)).hexdigest()
|
||||
|
||||
|
||||
def _key_segment(key):
|
||||
return ['key', copy.deepcopy(key)]
|
||||
return ['key', _copy_tree(key)]
|
||||
|
||||
|
||||
def _field(node, name):
|
||||
@@ -182,9 +399,37 @@ def apply_diff(tree, operations):
|
||||
return result
|
||||
|
||||
|
||||
class _Checkpoints:
|
||||
"""S5 checkpoints are complete blob/inline records: verify and load one at a time."""
|
||||
def __init__(self, checkpoints):
|
||||
self.checkpoints = checkpoints
|
||||
for i in range(len(checkpoints)):
|
||||
self[i]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.checkpoints)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cp = self.checkpoints[index]
|
||||
s = _transport_ref(cp['S'])
|
||||
latest = cp['latest']
|
||||
if latest.get('same_as_S') is True:
|
||||
require(set(latest) == {'same_as_S', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'}, 'same_as_S fields')
|
||||
_proof(latest, s)
|
||||
require(latest['raw_bytes_sha'] == cp['S']['raw_bytes_sha'], 'same_as_S byte mismatch')
|
||||
return s, (s if known_sha(s) is not None else copy.deepcopy(s))
|
||||
return s, _transport_ref(latest)
|
||||
|
||||
def __iter__(self):
|
||||
for i in range(len(self)):
|
||||
yield self[i]
|
||||
|
||||
|
||||
def reconstruct_checkpoints(checkpoints):
|
||||
"""Each checkpoint has S and latest transport records; verify before use."""
|
||||
require(type(checkpoints) is list and checkpoints, 'missing checkpoints')
|
||||
if not any('diff' in cp[name] for cp in checkpoints for name in ('S', 'latest') if type(cp.get(name)) is dict):
|
||||
return _Checkpoints(checkpoints)
|
||||
result, previous = [], None
|
||||
for cp in checkpoints:
|
||||
s = _transport(cp['S'], previous)
|
||||
@@ -207,7 +452,29 @@ def _proof(record, tree):
|
||||
require(record.get('tree_sha') == tree_sha(tree), 'tree SHA mismatch')
|
||||
|
||||
|
||||
def _blob_record(record):
|
||||
require(set(record) == {'tree', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'}, 'transport fields')
|
||||
require(type(record['tree']) is dict and set(record['tree']) == {'blob'}, 'blob reference fields')
|
||||
require(record['tree']['blob'] == record['tree_sha'], 'blob/tree SHA binding')
|
||||
require(record.get('roundtrip_ok') is True, 'Godot roundtrip missing/failed')
|
||||
require(type(record.get('raw_bytes_sha')) is str and re.fullmatch('[0-9a-f]{64}', record['raw_bytes_sha']), 'raw bytes SHA')
|
||||
blob_bytes(record['tree_sha'])
|
||||
return record['tree_sha']
|
||||
|
||||
|
||||
def _transport_ref(record):
|
||||
"""Like _transport, but a validated blob tree is kept as a TreeRef (parsed on demand)."""
|
||||
if type(record) is dict and type(record.get('tree')) is dict:
|
||||
sha = _blob_record(record)
|
||||
if sha not in _VALID_BLOBS:
|
||||
blob_tree(sha)
|
||||
return TreeRef(sha)
|
||||
return _transport(record, None)
|
||||
|
||||
|
||||
def _transport(record, baseline):
|
||||
if type(record) is dict and type(record.get('tree')) is dict:
|
||||
return blob_tree(_blob_record(record))
|
||||
require(('tree' in record) != ('diff' in record), 'tree/diff exclusive')
|
||||
require(set(record) == {'tree' if 'tree' in record else 'diff', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'}, 'transport fields')
|
||||
if 'tree' in record:
|
||||
@@ -221,34 +488,39 @@ def _transport(record, baseline):
|
||||
|
||||
def compare_trees(a, b):
|
||||
validate_tree(a); validate_tree(b)
|
||||
if same_tree(a, b):
|
||||
return {'equal': True, 'exact': True, 'differences': [], 'dict_order_paths': []}
|
||||
a, b = materialize(a), materialize(b)
|
||||
differences, orders = [], []
|
||||
# Recorded nodes are shared with the compared trees, which are never mutated.
|
||||
def diff(path, x, y):
|
||||
differences.append({'path': copy.deepcopy(path), 'a': copy.deepcopy(x), 'b': copy.deepcopy(y)})
|
||||
differences.append({'path': path, 'a': x, 'b': y})
|
||||
def walk(x, y, path):
|
||||
if exact(x, y):
|
||||
# Both sides are validated typed trees, so == is exact JSON equality.
|
||||
if x == y:
|
||||
return
|
||||
if x[0] != y[0] or len(x) != len(y):
|
||||
diff(path, x, y); return
|
||||
t = x[0]
|
||||
if t in (27, 28) and not exact(x[2], y[2]):
|
||||
if t in (27, 28) and x[2] != y[2]:
|
||||
diff(path + [['metadata', 0]], x[2], y[2])
|
||||
if t == 27:
|
||||
def keyed(n):
|
||||
d = {}
|
||||
for k, v in n[1]:
|
||||
text = _key_text(k)
|
||||
text = k[1] if k[0] in (4, 21, 22) else _key_text(k)
|
||||
require(text not in d, 'duplicate textual dictionary key')
|
||||
d[text] = (k, v)
|
||||
return d
|
||||
dx, dy = keyed(x), keyed(y)
|
||||
if set(dx) == set(dy) and [_key_text(k) for k, _ in x[1]] != [_key_text(k) for k, _ in y[1]]:
|
||||
orders.append(copy.deepcopy(path))
|
||||
if set(dx) == set(dy) and list(dx) != list(dy):
|
||||
orders.append(_copy_tree(path))
|
||||
for key in sorted(set(dx) | set(dy)):
|
||||
if key not in dx or key not in dy:
|
||||
diff(path + [['key', [4, key]]], dx.get(key), dy.get(key)); continue
|
||||
kx, vx = dx[key]; ky, vy = dy[key]
|
||||
p = path + [_key_segment(kx)]
|
||||
if not exact(kx, ky):
|
||||
if kx != ky:
|
||||
diff(p + [['key_type', 0]], kx, ky)
|
||||
walk(vx, vy, p)
|
||||
elif t == 28 or t in range(5, 21) or t in range(30, 39):
|
||||
@@ -260,7 +532,40 @@ def compare_trees(a, b):
|
||||
else:
|
||||
diff(path, x, y)
|
||||
walk(a, b, [])
|
||||
return {'equal': not differences, 'exact': exact(a, b), 'differences': differences, 'dict_order_paths': orders}
|
||||
return {'equal': not differences, 'exact': a == b, 'differences': differences, 'dict_order_paths': orders}
|
||||
|
||||
|
||||
HARNESS_LABEL = 'normalized-sha256:'
|
||||
_CANONICAL = {}
|
||||
|
||||
|
||||
def harness_digest(normalized):
|
||||
"""Labelled harness digest: SHA-256 of the exact normalized JSON (dict order kept).
|
||||
compare_reports falls back to canonical_digest when two labelled digests differ."""
|
||||
return HARNESS_LABEL + tree_sha(normalized)
|
||||
|
||||
|
||||
def canonical_digest(tree):
|
||||
"""Harness-only typed canonical digest; only Dictionary enumeration order is
|
||||
canonicalized (key nodes, scalars, metadata and Array order stay exact). The JSON is the
|
||||
S1 form, built without per-node copies; components and packed arrays hold no Dictionary."""
|
||||
known = known_sha(tree)
|
||||
if known is not None and known in _CANONICAL:
|
||||
return _CANONICAL[known]
|
||||
validate_tree(tree)
|
||||
def canonical(node):
|
||||
t = node[0]
|
||||
if t == 27:
|
||||
keyed = sorted([(k[1] if k[0] in (4, 21, 22) else _key_text(k), k, v) for k, v in node[1]], key=lambda row: row[0])
|
||||
require(len({row[0] for row in keyed}) == len(keyed), 'digest textual key collision')
|
||||
return [27, [[k, canonical(v)] for _, k, v in keyed], node[2]]
|
||||
if t == 28:
|
||||
return [28, [canonical(v) for v in node[1]], node[2]]
|
||||
return node
|
||||
digest = hashlib.sha256(json_bytes(canonical(materialize(tree)))).hexdigest()
|
||||
if known is not None:
|
||||
_CANONICAL[known] = digest
|
||||
return digest
|
||||
|
||||
|
||||
def _key_text(key):
|
||||
@@ -295,7 +600,7 @@ def _marker(key, node):
|
||||
|
||||
def _normalize(trees, profile_root, events):
|
||||
root = _profile(profile_root)
|
||||
result = copy.deepcopy(trees)
|
||||
result = _copy_tree(trees)
|
||||
side = {'entries': [], 'violations': [], 'profile_root': {**profile_root, 'matches_guard': True}}
|
||||
def visit(n, path, event_index, permitted_payload=False, event_root=False):
|
||||
if n[0] == 27:
|
||||
@@ -376,17 +681,119 @@ def normalize_events(events, profile_root):
|
||||
return _normalize(events, profile_root, True)
|
||||
|
||||
|
||||
def _path_nodes(tree):
|
||||
"""Return [(key, dict node)] along systems.runtime.domain_events and the events node."""
|
||||
nodes, n = [], tree
|
||||
for name in PERMITTED_EVENTS:
|
||||
if n[0] != 27:
|
||||
return None, None
|
||||
found = [(k, v) for k, v in n[1] if k[0] in (4, 21) and k[1] == name]
|
||||
if len(found) != 1:
|
||||
return None, None
|
||||
nodes.append((found[0][0], n))
|
||||
n = found[0][1]
|
||||
return nodes, n
|
||||
|
||||
|
||||
def _skeleton(nodes, events):
|
||||
"""The four permitted dictionaries, each keeping only the permitted key."""
|
||||
child = events
|
||||
for key, node in reversed(nodes):
|
||||
child = [27, [[key, child]], node[2]]
|
||||
return child
|
||||
|
||||
|
||||
def _replace_events(tree, events):
|
||||
nodes, _ = _path_nodes(tree)
|
||||
require(nodes is not None, 'events path missing')
|
||||
child = events
|
||||
for key, node in reversed(nodes):
|
||||
child = [27, [[k, child] if k is key else [k, v] for k, v in node[1]], node[2]]
|
||||
return child
|
||||
|
||||
|
||||
def _fast_normalize(tree, text, profile_root):
|
||||
"""Exact result of normalize_tree when every F1-F3 candidate lies in recent_events.
|
||||
|
||||
The original closed policy runs unchanged on a skeleton holding only the permitted
|
||||
path; the remaining text is proven free of the profile root, the reserved token,
|
||||
_us/_ms names and save_service. Returns None to request the full original walk.
|
||||
"""
|
||||
root = _profile(profile_root)
|
||||
if not (root.isascii() and root.isprintable()) or any(c in root for c in '[]",\\{}') or TOKEN in text:
|
||||
return None
|
||||
nodes, events = _path_nodes(tree)
|
||||
tokens = (root, '_us"', '_ms"', 'save_service')
|
||||
side_profile = {**profile_root, 'matches_guard': True}
|
||||
if not any(t in text for t in tokens):
|
||||
if events is not None:
|
||||
require(events[0] == 28, 'recent_events array required')
|
||||
return 'identity', None, {'entries': [], 'violations': [], 'profile_root': side_profile}, None, None, None
|
||||
if events is None or text.count('"recent_events"]') != 1:
|
||||
return None
|
||||
marker = '"recent_events"],'
|
||||
start = text.find(marker)
|
||||
if start < 0:
|
||||
return None
|
||||
start += len(marker)
|
||||
try:
|
||||
value, end = json.JSONDecoder().raw_decode(text, start)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
if value != events or any(text.count(t, 0, start) or text.count(t, end) for t in tokens):
|
||||
return None
|
||||
normalized, side = _normalize([_skeleton(nodes, events)], profile_root, False)
|
||||
_, new_events = _path_nodes(normalized[0])
|
||||
spliced = (text[:start] + json_bytes(new_events).decode('utf-8') + text[end:]).encode('utf-8')
|
||||
# The skeleton keys/metadata and the original events let restore run without a parse.
|
||||
kept = [(key, [27, [], node[2]]) for key, node in nodes]
|
||||
return 'normalized', new_events, side, hashlib.sha256(spliced).hexdigest(), kept, events
|
||||
|
||||
|
||||
def normalize_tree(tree, profile_root):
|
||||
normalized, side = _normalize([tree], profile_root, False)
|
||||
sha = known_sha(tree)
|
||||
if sha is not None and not (isinstance(tree, TreeRef) and tree.base is not None):
|
||||
key = (sha, json_bytes(profile_root))
|
||||
if key not in _NORMALIZED:
|
||||
fast = _fast_normalize(materialize(tree), blob_bytes(sha).decode('utf-8'), profile_root)
|
||||
if fast is None:
|
||||
# Candidates outside recent_events: the original full walk, not memoized.
|
||||
normalized, side = _normalize([materialize(tree)], profile_root, False)
|
||||
return normalized[0], side
|
||||
_NORMALIZED[key] = fast
|
||||
kind, events, side, normalized_sha = _NORMALIZED[key][:4]
|
||||
base = tree if isinstance(tree, TreeRef) else TreeRef(sha)
|
||||
if kind == 'identity':
|
||||
return base, copy.deepcopy(side)
|
||||
return TreeRef(normalized_sha, base, events), copy.deepcopy(side)
|
||||
normalized, side = _normalize([materialize(tree)], profile_root, False)
|
||||
return normalized[0], side
|
||||
|
||||
|
||||
def adopt(tree):
|
||||
"""Give a large constructed tree a SHA-backed text so the exact fast paths apply."""
|
||||
validate_tree(tree)
|
||||
text = json_bytes(tree)
|
||||
sha = hashlib.sha256(text).hexdigest()
|
||||
if sha not in _BLOB_BYTES:
|
||||
_BLOB_BYTES[sha] = text
|
||||
while len(_BLOB_BYTES) > BYTES_CACHE:
|
||||
_BLOB_BYTES.popitem(last=False)
|
||||
result = BlobList(tree)
|
||||
result.sha = sha
|
||||
_VALID_BLOBS.add(sha)
|
||||
_BLOB_TREES[sha] = result
|
||||
while len(_BLOB_TREES) > TREE_CACHE:
|
||||
_BLOB_TREES.popitem(last=False)
|
||||
return result
|
||||
|
||||
|
||||
def _restore(normalized, sidecar, profile_root, events):
|
||||
root = _profile(profile_root)
|
||||
require(type(sidecar) is dict and set(sidecar) == {'entries', 'violations', 'profile_root'}, 'sidecar fields')
|
||||
require(sidecar['profile_root'] == {**profile_root, 'matches_guard': True}, 'sidecar profile')
|
||||
require(type(sidecar['entries']) is list and type(sidecar['violations']) is list, 'sidecar lists')
|
||||
result = copy.deepcopy(normalized)
|
||||
result = _copy_tree(normalized)
|
||||
seen = set()
|
||||
for entry in sidecar['entries']:
|
||||
require(set(entry) == {'event_index', 'path', 'key', 'original_node'} | ({'root'} if entry.get('key') == 'message' else set()), 'entry fields')
|
||||
@@ -418,6 +825,20 @@ def restore_events(normalized, sidecar, profile_root):
|
||||
|
||||
|
||||
def restore_tree(normalized, sidecar, profile_root):
|
||||
if isinstance(normalized, TreeRef):
|
||||
base = normalized.base if normalized.base is not None else normalized
|
||||
expected, expected_side = normalize_tree(base, profile_root)
|
||||
require(isinstance(expected, TreeRef) and expected.sha == normalized.sha, 'sidecar does not match normalized node')
|
||||
if normalized.base is None:
|
||||
require(exact(sidecar, expected_side) and not sidecar['entries'] and not sidecar['violations'],
|
||||
'non-reversible/unauthorized sidecar')
|
||||
return normalized
|
||||
# The original reversible restore runs on the skeleton; the rest is shared.
|
||||
nodes, events = _NORMALIZED[(known_sha(base), json_bytes(profile_root))][4:]
|
||||
restored = _restore(_skeleton(nodes, normalized.replacement), sidecar, profile_root, False)
|
||||
_, restored_events = _path_nodes(restored)
|
||||
require(restored_events == events, 'non-reversible/unauthorized sidecar')
|
||||
return base
|
||||
return _restore(normalized, sidecar, profile_root, False)
|
||||
|
||||
|
||||
@@ -471,7 +892,7 @@ def compare_reports(a, b):
|
||||
require(tree_sha(restore_tree(ny, sy, profiles[1])) == tree_sha(y), 'b restore SHA')
|
||||
sidecars(path, sx, sy)
|
||||
comparison(path, nx, ny)
|
||||
return sx, sy
|
||||
return sx, sy, nx, ny
|
||||
|
||||
def comparison(path, x, y):
|
||||
c = compare_trees(x, y)
|
||||
@@ -523,9 +944,19 @@ def compare_reports(a, b):
|
||||
for field in ('catalog_instance_id', 'current_catalog_instance_id'):
|
||||
excluded(path + ['C', field], 'process-local instance ID', x['C'][field], y['C'][field])
|
||||
|
||||
def described(x, side):
|
||||
"""S5 joined facts record a large tree by SHA; rebuild it from its verified blob."""
|
||||
tree = TreeRef(x['source_tree_sha'])
|
||||
return materialize(normalize_tree(tree, profiles[side])[0] if x['normalized'] else tree)
|
||||
|
||||
def scenario_facts(path, x, y):
|
||||
# Joined S3b facts carry lossless typed trees as well as JSON metadata.
|
||||
# Apply the same dictionary-order-only fallback to returned values.
|
||||
descriptor = {'typed_tree_sha', 'source_tree_sha', 'normalized'}
|
||||
if isinstance(x, dict) and isinstance(y, dict) and set(x) == set(y) == descriptor:
|
||||
if x['typed_tree_sha'] != y['typed_tree_sha']:
|
||||
comparison(path, described(x, 0), described(y, 1))
|
||||
return
|
||||
if isinstance(x, list) and isinstance(y, list) and x and y and type(x[0]) is int and type(y[0]) is int:
|
||||
validate_tree(x); validate_tree(y)
|
||||
comparison(path, x, y)
|
||||
@@ -563,7 +994,7 @@ def compare_reports(a, b):
|
||||
if main:
|
||||
require(x['minute'] == xs[0]['minute']+i and y['minute'] == ys[0]['minute']+i, 'checkpoint cadence')
|
||||
trees(p + ['S'], tx[i][0], ty[i][0], profiles)
|
||||
sx, sy = trees(p + ['latest'], tx[i][1], ty[i][1], profiles)
|
||||
sx, sy, nlx, nly = trees(p + ['latest'], tx[i][1], ty[i][1], profiles)
|
||||
if ownership and i == 2:
|
||||
record(p + ['digest_self_consistent'], x['latest_digest'] == x['computed_latest_digest'], y['latest_digest'] == y['computed_latest_digest'])
|
||||
if sx['entries'] or sy['entries']:
|
||||
@@ -572,7 +1003,12 @@ def compare_reports(a, b):
|
||||
record(p + ['computed_latest_digest'], x['computed_latest_digest'], y['computed_latest_digest'])
|
||||
if sx['entries'] or sy['entries']:
|
||||
require(type(x['harness_normalized_digest']) is str and type(y['harness_normalized_digest']) is str, 'missing harness normalized digest')
|
||||
record(p + ['harness_normalized_digest'], x['harness_normalized_digest'], y['harness_normalized_digest'])
|
||||
hx, hy = x['harness_normalized_digest'], y['harness_normalized_digest']
|
||||
if hx != hy and all(h.startswith(HARNESS_LABEL) for h in (hx, hy)) and canonical_digest(nlx) == canonical_digest(nly):
|
||||
# Exact normalized JSON differs only by Dictionary order (paths recorded above).
|
||||
out['digest_modes'].append({'path': p, 'mode': 'harness canonical digest (dictionary order only)'})
|
||||
else:
|
||||
record(p + ['harness_normalized_digest'], hx, hy)
|
||||
excluded(p + ['latest_digest'], 'latest snapshot contains F1-F3 environment values', x['latest_digest'], y['latest_digest'])
|
||||
out['digest_modes'].append({'path': p, 'mode': 'harness normalized digest'})
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
extends SceneTree
|
||||
## S3b external guarded probe. Native admission remains S4/Root.
|
||||
## S5 external guarded probe. Native admission remains Root.
|
||||
## Large trees, raw S/K/C bytes and package files are content-addressed gzip blobs
|
||||
## (evidence/blobs/<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 K_FIELDS: Array = ["absolute_minute", "speed", "paused", "snapshot_version", "_skip_next_process", "_pause_request_serial", "_abort_current_tick_requested", "_last_tick_advanced"]
|
||||
var clock: Variant
|
||||
@@ -7,8 +10,12 @@ var runtime: Variant
|
||||
var main: Variant
|
||||
var role: String
|
||||
var report: Dictionary = {}
|
||||
var lifecycle: Dictionary = {"stage": "S3b", "errors": [], "preparation": [], "files": []}
|
||||
var previous_tree: Array = []
|
||||
var lifecycle: Dictionary = {"stage": "S5", "errors": [], "preparation": [], "files": []}
|
||||
var blob_index: Dictionary = {}
|
||||
var transport_cache: Dictionary = {}
|
||||
var last_snapshot_value: Dictionary = {}
|
||||
var subscribed_stream: Variant = null
|
||||
var known_event_ids: Dictionary = {}
|
||||
var finished: bool = false
|
||||
var bound: bool = false
|
||||
var recording: bool = false
|
||||
@@ -44,11 +51,18 @@ func f64(value: float) -> String:
|
||||
bytes.encode_double(0, value)
|
||||
return bytes.hex_encode()
|
||||
|
||||
# get_typed_script() returns a null Object for unscripted typed containers; it is
|
||||
# transported as NIL. A scripted element type is not supported and fails loudly.
|
||||
func typed_script(script: Variant) -> Array:
|
||||
if script == null: return [0]
|
||||
demand(false, "unsupported typed script metadata")
|
||||
return []
|
||||
|
||||
func typed_info(value: Variant) -> Variant:
|
||||
if typeof(value) == TYPE_ARRAY and value.is_typed():
|
||||
return [encode(value.get_typed_builtin()), encode(value.get_typed_class_name()), encode(value.get_typed_script())]
|
||||
return [encode(value.get_typed_builtin()), encode(value.get_typed_class_name()), typed_script(value.get_typed_script())]
|
||||
if typeof(value) == TYPE_DICTIONARY and value.is_typed():
|
||||
return [[encode(value.get_typed_key_builtin()), encode(value.get_typed_key_class_name()), encode(value.get_typed_key_script())], [encode(value.get_typed_value_builtin()), encode(value.get_typed_value_class_name()), encode(value.get_typed_value_script())]]
|
||||
return [[encode(value.get_typed_key_builtin()), encode(value.get_typed_key_class_name()), typed_script(value.get_typed_key_script())], [encode(value.get_typed_value_builtin()), encode(value.get_typed_value_class_name()), typed_script(value.get_typed_value_script())]]
|
||||
return null
|
||||
|
||||
func encode(value: Variant) -> Array:
|
||||
@@ -160,61 +174,44 @@ func decode(node: Array) -> Variant:
|
||||
func decoded_basis(v: Array) -> Basis:
|
||||
return Basis(Vector3(v[0], v[1], v[2]), Vector3(v[3], v[4], v[5]), Vector3(v[6], v[7], v[8]))
|
||||
|
||||
# Write each distinct content once; the name is the SHA-256 of the uncompressed bytes.
|
||||
func store_blob(bytes: PackedByteArray) -> Dictionary:
|
||||
var digest: String = hashed(bytes)
|
||||
if blob_index.has(digest): return {"blob": digest}
|
||||
if not demand(bound, "blob write before guard binding"): return {"blob": digest}
|
||||
var directory: String = "user://evidence/blobs"
|
||||
var mkdir: int = DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(directory))
|
||||
if not demand(mkdir == OK, "blob mkdir failed"): return {"blob": digest}
|
||||
var path: String = directory.path_join(digest + ".gz")
|
||||
if not demand(not FileAccess.file_exists(path), "refuse blob overwrite: " + digest): return {"blob": digest}
|
||||
var packed: PackedByteArray = bytes.compress(FileAccess.COMPRESSION_GZIP)
|
||||
if not demand(packed.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP) == bytes, "blob gzip roundtrip failed: " + digest): return {"blob": digest}
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
if not demand(file != null, "blob open failed: " + digest): return {"blob": digest}
|
||||
file.store_buffer(packed)
|
||||
file.flush()
|
||||
var error: int = file.get_error()
|
||||
file.close()
|
||||
if not demand(error == OK, "blob write failed: " + digest): return {"blob": digest}
|
||||
blob_index[digest] = {"bytes": bytes.size(), "gz_bytes": packed.size(), "gz_sha256": hashed(packed)}
|
||||
return {"blob": digest}
|
||||
|
||||
# var_to_bytes distinguishes typed metadata, String/StringName, int/float and key order,
|
||||
# so equal bytes mean an equal value and an equal tree; the proof is reused for them.
|
||||
func transport(value: Variant) -> Dictionary:
|
||||
var tree: Array = encode(value)
|
||||
var raw: PackedByteArray = var_to_bytes(value)
|
||||
var raw_sha: String = hashed(raw)
|
||||
if transport_cache.has(raw_sha): return transport_cache[raw_sha].duplicate(true)
|
||||
var tree: Array = encode(value)
|
||||
var rebuilt: PackedByteArray = var_to_bytes(decode(tree))
|
||||
var ok: bool = raw == rebuilt
|
||||
demand(ok, "Godot typed tree byte roundtrip failed")
|
||||
return {"tree": tree, "tree_sha": hashed(json_text(tree).to_utf8_buffer()), "raw_bytes_sha": hashed(raw), "roundtrip_ok": ok}
|
||||
|
||||
func tree_diff(before: Array, after: Array, path: Array = []) -> Array:
|
||||
if json_text(before) == json_text(after): return []
|
||||
# Exact subtree replacement also preserves changes of insertion order and metadata.
|
||||
if before[0] != after[0] or before[0] not in [TYPE_DICTIONARY, TYPE_ARRAY] or json_text(before[2]) != json_text(after[2]):
|
||||
return [{"op": "set", "path": path, "node": after}]
|
||||
var changes: Array = []
|
||||
if before[0] == TYPE_DICTIONARY:
|
||||
var old_keys: Array = []
|
||||
var new_keys: Array = []
|
||||
for pair: Array in before[1]: old_keys.append(pair[0])
|
||||
for pair: Array in after[1]: new_keys.append(pair[0])
|
||||
if json_text(old_keys) != json_text(new_keys): return [{"op": "set", "path": path, "node": after}]
|
||||
for i: int in range(before[1].size()):
|
||||
changes.append_array(tree_diff(before[1][i][1], after[1][i][1], path + [["key", after[1][i][0]]]))
|
||||
else:
|
||||
if before[1].size() != after[1].size():
|
||||
return [{"op": "array_splice", "path": path, "start": 0, "delete_count": before[1].size(), "nodes": after[1]}]
|
||||
for i: int in range(before[1].size()): changes.append_array(tree_diff(before[1][i], after[1][i], path + [["index", i]]))
|
||||
return changes
|
||||
|
||||
func tree_at(tree: Array, path: Array) -> Array:
|
||||
var node: Array = tree
|
||||
for segment: Array in path:
|
||||
if segment[0] == "index": node = node[1][segment[1]]
|
||||
else:
|
||||
var found: bool = false
|
||||
for pair: Array in node[1]:
|
||||
if json_text(pair[0]) == json_text(segment[1]):
|
||||
node = pair[1]
|
||||
found = true
|
||||
break
|
||||
demand(found, "diff path missing")
|
||||
return node
|
||||
|
||||
func diff_record(record: Dictionary, baseline: Array) -> Dictionary:
|
||||
var operations: Array = tree_diff(baseline, record.tree)
|
||||
var replay: Array = baseline.duplicate(true)
|
||||
for op: Dictionary in operations:
|
||||
var target: Array = tree_at(replay, op.path)
|
||||
if op.op == "set":
|
||||
target.clear()
|
||||
target.append_array(op.node.duplicate(true))
|
||||
elif op.op == "array_splice":
|
||||
for unused: int in range(op.delete_count): target[1].remove_at(op.start)
|
||||
for i: int in range(op.nodes.size()): target[1].insert(op.start + i, op.nodes[i].duplicate(true))
|
||||
demand(json_text(replay) == json_text(record.tree), "diff roundtrip mismatch")
|
||||
return {"diff": operations, "tree_sha": record.tree_sha, "raw_bytes_sha": record.raw_bytes_sha, "roundtrip_ok": record.roundtrip_ok}
|
||||
var text: PackedByteArray = json_text(tree).to_utf8_buffer()
|
||||
if text.size() <= INLINE_LIMIT:
|
||||
return {"tree": tree, "tree_sha": hashed(text), "raw_bytes_sha": raw_sha, "roundtrip_ok": ok}
|
||||
var record: Dictionary = {"tree": store_blob(text), "tree_sha": hashed(text), "raw_bytes_sha": raw_sha, "roundtrip_ok": ok}
|
||||
transport_cache[raw_sha] = record
|
||||
return record.duplicate(true)
|
||||
|
||||
func kernel_state() -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
@@ -266,21 +263,21 @@ func witness(value: Dictionary) -> Dictionary:
|
||||
return {"S": hashed(value.s_bytes), "K": hashed(value.k_bytes), "C": hashed(value.c_bytes)}
|
||||
|
||||
func raw_layers(value: Dictionary) -> Dictionary:
|
||||
return {"sha": witness(value), "S": Marshalls.raw_to_base64(value.s_bytes), "K": Marshalls.raw_to_base64(value.k_bytes), "C": Marshalls.raw_to_base64(value.c_bytes), "kernel": value.K, "cache": value.C}
|
||||
return {"sha": witness(value), "S": store_blob(value.s_bytes), "K": store_blob(value.k_bytes), "C": store_blob(value.c_bytes), "kernel": value.K, "cache": value.C}
|
||||
|
||||
func checkpoint() -> void:
|
||||
var before: Dictionary = layers()
|
||||
var s: Dictionary = transport(runtime.create_snapshot())
|
||||
var snapshot_value: Dictionary = runtime.create_snapshot()
|
||||
var s: Dictionary = transport(snapshot_value)
|
||||
var latest_value: Dictionary = runtime.get("latest_snapshot")
|
||||
var latest: Dictionary = transport(latest_value)
|
||||
var latest_bytes: PackedByteArray = var_to_bytes(latest_value)
|
||||
var digest: String = runtime.get("latest_digest")
|
||||
var computed: String = SimulationDigest.compute(latest_value)
|
||||
var current_tree: Array = s.tree
|
||||
if not previous_tree.is_empty(): s = diff_record(s, previous_tree)
|
||||
# Every checkpoint keeps a complete tree (blob reference); no diff chain.
|
||||
var latest: Dictionary = {}
|
||||
if latest_bytes == before.s_bytes:
|
||||
latest = {"same_as_S": true, "tree_sha": latest.tree_sha, "raw_bytes_sha": latest.raw_bytes_sha, "roundtrip_ok": latest.roundtrip_ok}
|
||||
else: latest = diff_record(latest, current_tree)
|
||||
latest = {"same_as_S": true, "tree_sha": s.tree_sha, "raw_bytes_sha": s.raw_bytes_sha, "roundtrip_ok": s.roundtrip_ok}
|
||||
else: latest = transport(latest_value)
|
||||
var after: Dictionary = layers()
|
||||
demand(before.s_bytes == after.s_bytes and before.k_bytes == after.k_bytes and before.c_bytes == after.c_bytes, "observation changed full S/K/C")
|
||||
demand(digest == computed, "latest digest self-consistency")
|
||||
@@ -291,12 +288,13 @@ func checkpoint() -> void:
|
||||
write_evidence("checkpoint-%02d.json" % index, cp)
|
||||
write_evidence("observation-%02d.json" % index, {"before": raw_layers(before), "after": raw_layers(after)})
|
||||
report.checkpoints.append(cp)
|
||||
previous_tree = current_tree
|
||||
last_snapshot_value = snapshot_value
|
||||
|
||||
func event_counts() -> Dictionary:
|
||||
return {"counter": runtime.id_generator.get_counter(&"domain_event"), "published_ids_count": runtime.domain_events.get("_published_ids").size()}
|
||||
|
||||
func observe_event(event: Variant) -> void:
|
||||
known_event_ids[str(event.event_id)] = true
|
||||
if scenario_active:
|
||||
scenario_events.append(transport(event.to_dictionary()))
|
||||
return
|
||||
@@ -307,6 +305,28 @@ func observe_event(event: Variant) -> void:
|
||||
"entity_ids": {"type": typeof(event.entity_ids), "typed_info": typed_info(event.entity_ids)},
|
||||
"payload": {"type": typeof(event.payload), "typed_info": typed_info(event.payload)}, "causation_command_id": typeof(event.causation_command_id)}})
|
||||
|
||||
# A successful product restore replaces runtime.domain_events with a new stream that does
|
||||
# not carry subscribers. Record that fact, recover from the live stream's recent_events()
|
||||
# the events the observer could not receive, and subscribe to the live stream again.
|
||||
func follow_event_stream() -> Dictionary:
|
||||
var current: Variant = runtime.domain_events
|
||||
if current == subscribed_stream: return {"replaced": false}
|
||||
var subscribers: Array = []
|
||||
var all_subscribers: Dictionary = current.get("_subscribers")
|
||||
for event_type: Variant in all_subscribers:
|
||||
for id: Variant in all_subscribers[event_type]: subscribers.append(str(event_type) + "/" + str(id))
|
||||
subscribers.sort_custom(func(a: Variant, b: Variant) -> bool: return str(a) < str(b))
|
||||
var recovered: Array = []
|
||||
for event: Variant in current.recent_events():
|
||||
if known_event_ids.has(str(event.event_id)): continue
|
||||
recovered.append(str(event.event_id))
|
||||
observe_event(event)
|
||||
demand(current.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard resubscription failed")
|
||||
subscribed_stream = current
|
||||
var fact: Dictionary = {"replaced": true, "new_stream_subscribers_before": subscribers, "recovered_event_ids": recovered}
|
||||
lifecycle.event_stream_replacements.append(fact.duplicate(true))
|
||||
return fact
|
||||
|
||||
func observe_snapshot(_snapshot: Dictionary, _digest: String) -> void:
|
||||
snapshot_reference = _snapshot
|
||||
record_signal("snapshot_published")
|
||||
@@ -315,8 +335,11 @@ func observe_state(_snapshot: Dictionary) -> void:
|
||||
state_reference = _snapshot
|
||||
record_signal("state_updated")
|
||||
|
||||
# The domain_event counter at emission places each signal in the event order, so every
|
||||
# publish can be bound to its cause (tick end, successful restore, completed command).
|
||||
func record_signal(name: String) -> void:
|
||||
var row: Dictionary = {"signal": name, "minute": clock.absolute_minute}
|
||||
var row: Dictionary = {"signal": name, "minute": clock.absolute_minute, "event_counter": runtime.id_generator.get_counter(&"domain_event"),
|
||||
"command_counter": runtime.get("_next_command_id")}
|
||||
if scenario_active: scenario_signals.append(row)
|
||||
elif recording: report.signal_order.append(row)
|
||||
else: warm_signals.append(row)
|
||||
@@ -352,21 +375,24 @@ func bind_profile() -> bool:
|
||||
var path: String = OS.get_environment("THE_TREE_PACK_PROFILE_REPORT")
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
if not demand(file != null, "guard receipt missing"): return false
|
||||
var receipt: Variant = JSON.parse_string(file.get_as_text())
|
||||
# Godot parses JSON numbers as float; the exact receipt text is retained for binding.
|
||||
var receipt_text: String = file.get_as_text()
|
||||
var receipt: Variant = JSON.parse_string(receipt_text)
|
||||
file.close()
|
||||
if not demand(receipt is Dictionary and receipt.get("verified") == true, "guard receipt unverified"): return false
|
||||
var actual: Variant = receipt.get("actual_user_data_dir")
|
||||
var live_root: String = ProjectSettings.globalize_path("user://").simplify_path()
|
||||
if not demand(actual is String and actual == live_root and OS.get_user_data_dir() == actual, "live profile differs from guard actual_user_data_dir"): return false
|
||||
report["profile_root"] = {"root": live_root, "actual_user_data_dir": actual}
|
||||
lifecycle["guard"] = {"path": path, "sha256": FileAccess.get_sha256(path), "receipt": receipt, "live_root": live_root}
|
||||
lifecycle["guard"] = {"path": path, "sha256": FileAccess.get_sha256(path), "receipt": receipt, "receipt_text": receipt_text, "live_root": live_root}
|
||||
bound = true
|
||||
return true
|
||||
|
||||
func write_evidence(name: String, value: Variant) -> void:
|
||||
if not demand(bound, "evidence write before guard binding"): return
|
||||
var directory: String = "user://evidence"
|
||||
var mkdir: int = DirAccess.make_dir_recursive_absolute(directory)
|
||||
# The guard switches the user dir at runtime; globalize first, as the RESTART helpers do.
|
||||
var mkdir: int = DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(directory))
|
||||
if not demand(mkdir == OK, "evidence mkdir failed"): return
|
||||
var path: String = directory.path_join(name)
|
||||
if not demand(not FileAccess.file_exists(path), "refuse evidence overwrite: " + name): return
|
||||
@@ -434,6 +460,8 @@ func begin() -> void:
|
||||
runtime.snapshot_published.connect(observe_snapshot)
|
||||
runtime.state_updated.connect(observe_state)
|
||||
demand(runtime.domain_events.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard subscription failed")
|
||||
subscribed_stream = runtime.domain_events
|
||||
lifecycle["event_stream_replacements"] = []
|
||||
connected = true
|
||||
lifecycle["observer_before"] = initial_inventory
|
||||
lifecycle["observer_connected"] = inventory()
|
||||
@@ -448,7 +476,9 @@ func begin() -> void:
|
||||
report.event_start = event_counts()
|
||||
recording = true
|
||||
checkpoint()
|
||||
b1_snapshot = decode(report.checkpoints[0].S.tree).duplicate(true)
|
||||
# The B1 value itself; its transport proves decode(tree) has the same bytes.
|
||||
b1_snapshot = last_snapshot_value.duplicate(true)
|
||||
for event_id: Variant in b1_snapshot[&"systems"][&"runtime"][&"domain_events"]["published_ids"]: known_event_ids[str(event_id)] = true
|
||||
# Stage 4: B1 and 30 exact checkpoints, uncapped events through the last frame.
|
||||
for unused: int in range(30):
|
||||
if not lifecycle.errors.is_empty(): break
|
||||
@@ -458,9 +488,10 @@ func begin() -> void:
|
||||
recording = false
|
||||
# Stage 5: E3 dedicated intervals, after measured event/signal closure.
|
||||
lifecycle["outside"] = raw_layers(layers())
|
||||
run_e3()
|
||||
run_e4()
|
||||
run_e5()
|
||||
# Any harness error already fails the run; stop before further expensive scenarios.
|
||||
if lifecycle.errors.is_empty(): run_e3()
|
||||
if lifecycle.errors.is_empty(): run_e4()
|
||||
if lifecycle.errors.is_empty(): run_e5()
|
||||
finish()
|
||||
|
||||
func finish() -> void:
|
||||
@@ -474,10 +505,11 @@ func finish() -> void:
|
||||
lifecycle["observer_disconnected"] = inventory()
|
||||
demand(json_text(initial_inventory) == json_text(lifecycle.observer_disconnected), "observer inventory not restored")
|
||||
lifecycle["warm_signals"] = warm_signals
|
||||
lifecycle["stop_reason"] = "S3b_complete_S4_pending" if lifecycle.errors.is_empty() else "failed"
|
||||
lifecycle["stop_reason"] = "S5_complete" if lifecycle.errors.is_empty() else "failed"
|
||||
if bound:
|
||||
write_evidence("events.json", report.events)
|
||||
write_evidence("report.json", report)
|
||||
lifecycle["blobs"] = blob_index.duplicate(true)
|
||||
write_evidence("lifecycle.json", lifecycle.duplicate(true))
|
||||
print("P28_02_EQ_REPORT=" + json_text(report))
|
||||
if is_instance_valid(main): main.ui_audio_window_close_drained.connect(audio_close, CONNECT_ONE_SHOT)
|
||||
@@ -525,7 +557,7 @@ func e3_package(path: String) -> Dictionary:
|
||||
if not demand(file != null, "package evidence open"): continue
|
||||
var bytes: PackedByteArray = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
files[name] = {"bytes": Marshalls.raw_to_base64(bytes), "sha256": hashed(bytes)}
|
||||
files[name] = {"bytes": store_blob(bytes), "sha256": hashed(bytes)}
|
||||
return files
|
||||
|
||||
func e3_prepare_package(path: String, input: Dictionary, kind: String) -> Dictionary:
|
||||
@@ -541,15 +573,27 @@ func e3_prepare_package(path: String, input: Dictionary, kind: String) -> Dictio
|
||||
var manifest_path: String = path.path_join("manifest.json")
|
||||
var file: FileAccess = FileAccess.open(manifest_path, FileAccess.READ)
|
||||
if not demand(file != null, "manifest read"): return info
|
||||
var manifest: Dictionary = JSON.parse_string(file.get_as_text())
|
||||
var text: String = file.get_as_text()
|
||||
file.close()
|
||||
match kind:
|
||||
"missing_key": manifest.erase("checksum_world")
|
||||
"wrong_type": manifest["format_version"] = "x"
|
||||
"out_of_range": manifest["format_version"] = 2147483647
|
||||
# Edit only the one key line. Re-serializing a parsed manifest would turn every
|
||||
# JSON integer into a float (Godot parses numbers as float), changing other fields.
|
||||
var key: String = "checksum_world" if kind == "missing_key" else "format_version"
|
||||
var prefix: String = "\t\"" + key + "\": "
|
||||
var lines: PackedStringArray = PackedStringArray()
|
||||
var found: int = 0
|
||||
for line: String in text.split("\n"):
|
||||
if line.begins_with(prefix):
|
||||
found += 1
|
||||
var comma: String = "," if line.ends_with(",") else ""
|
||||
if kind == "missing_key":
|
||||
demand(comma == ",", "removed manifest key must not be last")
|
||||
continue
|
||||
line = prefix + ("\"x\"" if kind == "wrong_type" else "2147483647") + comma
|
||||
lines.append(line)
|
||||
if not demand(found == 1, "manifest key line: " + key): return info
|
||||
file = FileAccess.open(manifest_path, FileAccess.WRITE)
|
||||
if not demand(file != null, "manifest write"): return info
|
||||
file.store_string(json_text(manifest))
|
||||
file.store_string("\n".join(lines))
|
||||
file.close()
|
||||
elif kind == "tampered_world":
|
||||
var world_path: String = path.path_join("world.bin")
|
||||
@@ -615,6 +659,7 @@ func run_e3() -> void:
|
||||
"restore_snapshot": actual = runtime.restore_snapshot(input)
|
||||
"validate": actual = SimulationSnapshot.validate(input)
|
||||
"load_from_path": actual = runtime.load_from_path(path)
|
||||
var stream_fact: Dictionary = follow_event_stream()
|
||||
if package != null: package["after_operation"] = e3_package(path)
|
||||
var after: Dictionary = e3_observation()
|
||||
scenario_active = false
|
||||
@@ -650,13 +695,17 @@ func run_e3() -> void:
|
||||
var evidence: Dictionary = {"id": id, "begin": before, "invocation_before": invocation_before, "end": after,
|
||||
"input": transport(input), "return": transport(actual), "package": package, "events": scenario_events,
|
||||
"signals": scenario_signals.duplicate(true), "latest_save_operation_result": transport(runtime.get("latest_save_operation_result")),
|
||||
"save_budget_before": transport(budget_before), "save_budget_after": transport(budget_after), "boundaries": ["begin", "operation", "end"]}
|
||||
"save_budget_before": transport(budget_before), "save_budget_after": transport(budget_after), "boundaries": ["begin", "operation", "end"],
|
||||
"event_stream": stream_fact}
|
||||
report.scenarios.append(scenario)
|
||||
lifecycle.e3.append(evidence)
|
||||
write_evidence("e3-%02d.json" % (report.scenarios.size() - 1), evidence)
|
||||
|
||||
# S3b shares S3a's complete observation and transport; no state repair.
|
||||
func e45_emit(id: String, operation: String, observations: Array, detail: Dictionary) -> void:
|
||||
# The returned value itself; a large return is stored as a blob and is not decoded here.
|
||||
var returned_value: Variant = detail.get("_return_value")
|
||||
detail.erase("_return_value")
|
||||
detail["signals"] = scenario_signals.duplicate(true)
|
||||
var cps: Array = []
|
||||
for observation: Dictionary in observations: cps.append(observation.checkpoint)
|
||||
@@ -666,7 +715,7 @@ func e45_emit(id: String, operation: String, observations: Array, detail: Dictio
|
||||
var facts: Dictionary = detail.duplicate(true)
|
||||
for key: String in ["package_before", "package_after", "save_return", "return", "save_budget_before", "save_budget_after", "latest_save_operation_result", "observation_before", "observation_after"]: facts.erase(key)
|
||||
if detail.has("return"):
|
||||
var actual: Variant = decode(detail["return"].tree)
|
||||
var actual: Variant = returned_value
|
||||
facts["return_fact"] = {"ok": actual.get(&"ok"), "code": actual.get(&"code")} if actual is Dictionary else actual
|
||||
if facts.has("trials"):
|
||||
for trial: Dictionary in facts.trials:
|
||||
@@ -738,7 +787,10 @@ func run_e4() -> void:
|
||||
elif operation == "restore_snapshot":
|
||||
var input: Dictionary = runtime.create_snapshot().duplicate(true)
|
||||
detail["input"] = transport(input)
|
||||
detail["return"] = transport(runtime.restore_snapshot(input))
|
||||
var restore_return: Variant = runtime.restore_snapshot(input)
|
||||
detail["event_stream"] = follow_event_stream()
|
||||
detail["return"] = transport(restore_return)
|
||||
detail["_return_value"] = restore_return
|
||||
elif operation == "load_from_path":
|
||||
var input: Dictionary = runtime.create_snapshot().duplicate(true)
|
||||
var path: String = "user://p28_eq/" + role.to_lower() + "/good"
|
||||
@@ -748,7 +800,10 @@ func run_e4() -> void:
|
||||
detail["save_return"] = transport(saved)
|
||||
detail["package_before"] = e3_package(path)
|
||||
demand(saved.get(&"ok") == true, "E4 good package save failed")
|
||||
detail["return"] = transport(runtime.load_from_path(path))
|
||||
var load_return: Variant = runtime.load_from_path(path)
|
||||
detail["event_stream"] = follow_event_stream()
|
||||
detail["return"] = transport(load_return)
|
||||
detail["_return_value"] = load_return
|
||||
detail["latest_save_operation_result"] = transport(runtime.get("latest_save_operation_result"))
|
||||
detail["package_after"] = e3_package(path)
|
||||
elif operation == "rejected_command":
|
||||
|
||||
@@ -140,9 +140,10 @@ def plan(collector, standard, engine, fixture, out):
|
||||
def copy_evidence(runtime, destination):
|
||||
source = runtime / 'evidence'
|
||||
before = verify.inventory(source, seals=True)
|
||||
require(set(before) == verify.NAMES, 'private evidence missing/extra')
|
||||
require(set(verify.split_evidence(before)[0]) == verify.NAMES, 'private evidence missing/extra')
|
||||
destination.mkdir()
|
||||
for name in sorted(before):
|
||||
(destination / name).parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source / name, destination / name)
|
||||
require(verify.inventory(source, seals=True) == before == verify.inventory(destination, seals=True), 'evidence changed during copy')
|
||||
return before
|
||||
@@ -228,7 +229,8 @@ def run(admission, out, collector, standard, outer, diag):
|
||||
Path(admission['engine']), collector, standard)
|
||||
require((ROOT / 'project.godot').read_bytes() == public and (stage / 'project.godot').read_bytes() == public, 'project restoration')
|
||||
if error is None:
|
||||
result = verify.recompute(out, execution, collector, standard, diag)
|
||||
with check.memo_scope():
|
||||
result = verify.recompute(out, execution, collector, standard, diag, parallel=True)
|
||||
write(out / 'results.json', result)
|
||||
for phase, values in result['phases'].items():
|
||||
for name, value in values.items():
|
||||
|
||||
@@ -432,6 +432,7 @@ def s2_report():
|
||||
state = snapshot()
|
||||
runtime = eq._field(eq._field(state, 'systems'), 'runtime')
|
||||
runtime[1].append([[4, 'id_generator'], d(counters=d(domain_event=[2, 3]))])
|
||||
runtime[1].append([[4, 'next_command_id'], [2, 1]])
|
||||
events = eq._field(runtime, 'domain_events')
|
||||
events[1].append([[4, 'published_ids'], arr(*([4, 'domain_event.%012d' % i] for i in range(1, 4)))])
|
||||
value['checkpoints'] = [checkpoint(state, 632+i) for i in range(31)]
|
||||
@@ -441,7 +442,7 @@ def s2_report():
|
||||
cp['K']['_phase_handlers'] = {str(i): [] for i in range(11)}
|
||||
cp['C'].update(catalog_instance_id='123', current_catalog_instance_id='123')
|
||||
cp.update(latest_digest='b'*64, computed_latest_digest='b'*64, harness_normalized_digest=None)
|
||||
value['signal_order'] = [{'signal': sig, 'minute': cp['minute']} for cp in value['checkpoints'][1:] for sig in ('snapshot_published', 'state_updated')]
|
||||
value['signal_order'] = [{'signal': sig, 'minute': cp['minute'], 'event_counter': 3, 'command_counter': 1} for cp in value['checkpoints'][1:] for sig in ('snapshot_published', 'state_updated')]
|
||||
return value
|
||||
|
||||
|
||||
@@ -672,7 +673,7 @@ class S2Static(unittest.TestCase):
|
||||
self.reject(self.probe.replace('return str(a) < str(b)', 'return a < b'))
|
||||
|
||||
def test_full_state_and_godot_roundtrip_required(self):
|
||||
for required in ('var_to_bytes(decode(tree))', 'transport(runtime.create_snapshot())', 'before.k_bytes == after.k_bytes', 'event.to_dictionary()', 'receipt.get("actual_user_data_dir")'):
|
||||
for required in ('var_to_bytes(decode(tree))', 'transport(snapshot_value)', 'before.k_bytes == after.k_bytes', 'event.to_dictionary()', 'receipt.get("actual_user_data_dir")'):
|
||||
with self.subTest(required=required): self.reject(self.probe.replace(required, 'null'))
|
||||
|
||||
def test_no_event_cap_or_duplicate_report(self):
|
||||
@@ -790,7 +791,8 @@ def e3_fixture(role='R'):
|
||||
evidence.append({'id': id_, 'begin': begin, 'invocation_before': copy.deepcopy(begin), 'end': end,
|
||||
'boundaries': ['begin', 'operation', 'end'], 'input': transport(checker.e3_expected_input(b1, kind)),
|
||||
'return': transport(actual), 'package': package, 'events': event_records, 'signals': [],
|
||||
'latest_save_operation_result': transport(latest), 'save_budget_before': transport(d()), 'save_budget_after': transport(d())})
|
||||
'latest_save_operation_result': transport(latest), 'save_budget_before': transport(d()), 'save_budget_after': transport(d()),
|
||||
'event_stream': {'replaced': False}})
|
||||
return value, evidence, transport(b1)
|
||||
|
||||
|
||||
@@ -894,6 +896,10 @@ class S3a(unittest.TestCase):
|
||||
value, evidence, b1 = e3_fixture()
|
||||
value['scenarios'][0]['result']['ok'] = True
|
||||
evidence[0]['return'] = transport([1, 1])
|
||||
# S5: an accepted restore publishes once (simulation_runtime.gd:1473), as the product does.
|
||||
state = evidence[0]['begin']['checkpoint']['S']['tree']
|
||||
evidence[0]['signals'] = [{'signal': sig, 'minute': evidence[0]['begin']['checkpoint']['minute'], 'event_counter': checker.counts(state)[0]['counter'],
|
||||
'command_counter': checker.next_command_id(state)} for sig in ('snapshot_published', 'state_updated')]
|
||||
checked = self.check(value, evidence, b1)
|
||||
self.assertTrue(checked['ok'], checked['errors'])
|
||||
self.assertEqual(checked['e3'][0]['defects'][0]['kind'], 'invalid_input_accepted')
|
||||
@@ -993,13 +999,19 @@ def e45_fixture(role='R'):
|
||||
current = copy.deepcopy(previous)
|
||||
evidence = []
|
||||
def emit(ident, op, obs, detail, events=()):
|
||||
detail['signals'] = []
|
||||
# Publishes as the product makes them: one per successful restore/load and per tick.
|
||||
counter = checker.counts(obs[0]['checkpoint']['S']['tree'])[0]['counter']
|
||||
commands = checker.next_command_id(obs[0]['checkpoint']['S']['tree'])
|
||||
minutes = [o['checkpoint']['minute'] for o in obs]
|
||||
published = ([minutes[0]] if op in ('restore_snapshot', 'load_from_path') else []) + [b for a, b in zip(minutes, minutes[1:]) if b == a+1]
|
||||
signals = [{'signal': sig, 'minute': m, 'event_counter': counter, 'command_counter': commands} for m in published for sig in ('snapshot_published', 'state_updated')]
|
||||
detail['signals'] = copy.deepcopy(signals)
|
||||
result = {'event_count': len(events), 'applicability': detail.get('applicability', 'applied'),
|
||||
'reason': detail.get('reason'), 'facts': checker.e45_facts(detail)}
|
||||
raw['scenarios'].append({'id': ident, 'operation': op, 'result': result,
|
||||
'checkpoints': [o['checkpoint'] for o in obs], 'events': list(events)})
|
||||
evidence.append({'id': ident, 'boundaries': ['begin', 'operation', 'end'], 'observations': obs,
|
||||
'detail': detail, 'events': [transport(e) for e in events], 'signals': []})
|
||||
'detail': detail, 'events': [transport(e) for e in events], 'signals': signals})
|
||||
for op in checker.E4_PATHS:
|
||||
begin = copy.deepcopy(current); minute = begin['checkpoint']['minute']
|
||||
detail = {}; obs = [begin]; events = []
|
||||
@@ -1017,14 +1029,14 @@ def e45_fixture(role='R'):
|
||||
elif op == 'tick':
|
||||
minute += 1
|
||||
elif op == 'restore_snapshot':
|
||||
detail = {'input': transport(tree), 'return': transport([1, 1])}
|
||||
detail = {'input': transport(tree), 'return': transport([1, 1]), 'event_stream': {'replaced': False}}
|
||||
elif op == 'load_from_path':
|
||||
ev = event(raw['profile_root']['root'])
|
||||
# Match the next identity required by X(P,event).
|
||||
counter = checker.counts(tree)[0]['counter']
|
||||
checker.node_at(ev, 'event_id')[1] = 'event_'+str(counter+1)
|
||||
checker.node_at(ev, 'event_id')[1] = 'domain_event.%012d' % (counter+1)
|
||||
detail = {'input': transport(tree), 'return': transport(d(ok=[1, 1], code=[4, 'OK'])),
|
||||
'path': 'user://p28_eq/'+role.lower()+'/good'}
|
||||
'path': 'user://p28_eq/'+role.lower()+'/good', 'event_stream': {'replaced': False}}
|
||||
package = e3_package_fixture('duplicate_published_ids')
|
||||
detail.update(save_return=package['save_return'], package_before=package['original'], package_after=package['original'], latest_save_operation_result=transport(checker.node_at(ev, 'payload')))
|
||||
tree, _ = checker.e3_x(tree, ev)
|
||||
@@ -1294,7 +1306,7 @@ def s4_phase_fixture(directory, phase='R-A', pid=101):
|
||||
'project_settings_persisted': False}
|
||||
guard_path = directory/(phase+'-guard.json')
|
||||
verifier.write(guard_path, guard)
|
||||
life = {'stage': 'S3b', 'errors': [], 'stop_reason': 'S3b_complete_S4_pending',
|
||||
life = {'stage': 'S5', 'errors': [], 'stop_reason': 'S5_complete', 'blobs': {}, 'event_stream_replacements': [],
|
||||
'guard': {'receipt': guard, 'live_root': root, 'sha256': verifier.sha(guard_path), 'path': str(guard_path)},
|
||||
'observer_before': {}, 'observer_connected': {'probe': True}, 'observer_disconnected': {},
|
||||
'ops': {'code': 'INVALID_START_TIME'} if role == 'R' else {'ok': True},
|
||||
@@ -1698,5 +1710,588 @@ class S4StoredResultBindings(unittest.TestCase):
|
||||
def test_raw_joined_substitution_rejected_after_seal(self): self.reject_stored_change('raw_joined')
|
||||
|
||||
|
||||
# S5 (EQ_S5_SCALE_PLAN r01/r02): content-addressed blobs, exact fast paths, event stream.
|
||||
import gzip
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
|
||||
def s5_root():
|
||||
base = os.environ.get('BUILD_OUTPUT_DIR') or tempfile.gettempdir()
|
||||
return Path(tempfile.mkdtemp(prefix='p28-eq-s5-', dir=base))
|
||||
|
||||
|
||||
def put_blob(root, data):
|
||||
digest = hashlib.sha256(data).hexdigest()
|
||||
(root/(digest+'.gz')).write_bytes(gzip.compress(data))
|
||||
return digest
|
||||
|
||||
|
||||
def as_blobs(value, root):
|
||||
"""Every inline transport record becomes a blob reference to the same tree."""
|
||||
if isinstance(value, dict):
|
||||
if set(value) == {'tree', 'tree_sha', 'raw_bytes_sha', 'roundtrip_ok'} and isinstance(value['tree'], list):
|
||||
digest = put_blob(root, eq.json_bytes(value['tree']))
|
||||
assert digest == value['tree_sha']
|
||||
return dict(value, tree={'blob': digest})
|
||||
return {k: as_blobs(v, root) for k, v in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [as_blobs(v, root) for v in value]
|
||||
return value
|
||||
|
||||
|
||||
def reference_exact(a, b):
|
||||
return eq.json_bytes(a) == eq.json_bytes(b)
|
||||
|
||||
|
||||
def reference_compare_trees(a, b):
|
||||
"""The S1-S4 algorithm (JSON comparison at every level), kept to prove equivalence."""
|
||||
eq.validate_tree(a); eq.validate_tree(b)
|
||||
differences, orders = [], []
|
||||
def diff(path, x, y):
|
||||
differences.append({'path': copy.deepcopy(path), 'a': copy.deepcopy(x), 'b': copy.deepcopy(y)})
|
||||
def walk(x, y, path):
|
||||
if reference_exact(x, y):
|
||||
return
|
||||
if x[0] != y[0] or len(x) != len(y):
|
||||
diff(path, x, y); return
|
||||
t = x[0]
|
||||
if t in (27, 28) and not reference_exact(x[2], y[2]):
|
||||
diff(path + [['metadata', 0]], x[2], y[2])
|
||||
if t == 27:
|
||||
def keyed(n):
|
||||
result = {}
|
||||
for k, v in n[1]:
|
||||
result[eq._key_text(k)] = (k, v)
|
||||
return result
|
||||
dx, dy = keyed(x), keyed(y)
|
||||
if set(dx) == set(dy) and [eq._key_text(k) for k, _ in x[1]] != [eq._key_text(k) for k, _ in y[1]]:
|
||||
orders.append(copy.deepcopy(path))
|
||||
for key in sorted(set(dx) | set(dy)):
|
||||
if key not in dx or key not in dy:
|
||||
diff(path + [['key', [4, key]]], dx.get(key), dy.get(key)); continue
|
||||
kx, vx = dx[key]; ky, vy = dy[key]
|
||||
p = path + [eq._key_segment(kx)]
|
||||
if not reference_exact(kx, ky):
|
||||
diff(p + [['key_type', 0]], kx, ky)
|
||||
walk(vx, vy, p)
|
||||
elif t == 28 or t in range(5, 21) or t in range(30, 39):
|
||||
for i in range(max(len(x[1]), len(y[1]))):
|
||||
if i >= len(x[1]) or i >= len(y[1]):
|
||||
diff(path + [['index', i]], x[1][i] if i < len(x[1]) else None, y[1][i] if i < len(y[1]) else None)
|
||||
else:
|
||||
walk(x[1][i], y[1][i], path + [['index', i]])
|
||||
else:
|
||||
diff(path, x, y)
|
||||
walk(a, b, [])
|
||||
return {'equal': not differences, 'exact': reference_exact(a, b), 'differences': differences, 'dict_order_paths': orders}
|
||||
|
||||
|
||||
def reference_digest(tree):
|
||||
def canonical(node):
|
||||
result = copy.deepcopy(node)
|
||||
if node[0] == 27:
|
||||
names = [eq._key_text(k) for k, _ in node[1]]
|
||||
eq.require(len(set(names)) == len(names), 'digest textual key collision')
|
||||
result[1] = [[copy.deepcopy(k), canonical(v)] for k, v in sorted(node[1], key=lambda p: eq._key_text(p[0]))]
|
||||
elif node[0] == 28 or 5 <= node[0] <= 20 or 30 <= node[0] <= 38:
|
||||
result[1] = [canonical(v) for v in node[1]]
|
||||
return result
|
||||
eq.validate_tree(tree)
|
||||
return eq.tree_sha(canonical(tree))
|
||||
|
||||
|
||||
def reference_expected_input(b1, kind):
|
||||
value = copy.deepcopy(b1)
|
||||
if kind == 'missing_key':
|
||||
value[1] = [pair for pair in value[1] if pair[0][1] != 'clock']
|
||||
elif kind in ('wrong_type', 'out_of_range'):
|
||||
checker.node_at(value, 'clock', 'absolute_minute' if kind == 'wrong_type' else 'speed')[:] = [4, 'x'] if kind == 'wrong_type' else [2, 9]
|
||||
elif kind in ('duplicate_published_ids', 'duplicate_facility'):
|
||||
nodes = (checker.node_at(value, 'systems', 'runtime', 'domain_events', 'published_ids') if kind == 'duplicate_published_ids'
|
||||
else checker.node_at(value, 'systems', 'runtime', 'facilities', 'instances'))
|
||||
if nodes[1]:
|
||||
nodes[1].append(copy.deepcopy(nodes[1][0]))
|
||||
return value
|
||||
|
||||
|
||||
def reference_x(before, event_tree):
|
||||
value = copy.deepcopy(before)
|
||||
counter = checker.node_at(value, 'systems', 'runtime', 'id_generator', 'counters', 'domain_event')
|
||||
counter[1] += 1
|
||||
domain = checker.node_at(value, 'systems', 'runtime', 'domain_events')
|
||||
ids = checker.fields(domain)['published_ids']
|
||||
ids[1].append(copy.deepcopy(checker.fields(event_tree)['event_id']))
|
||||
ids[1].sort(key=lambda n: n[1])
|
||||
recent = checker.fields(domain)['recent_events']
|
||||
dropped = copy.deepcopy(recent[1][0]) if len(recent[1]) == 256 else None
|
||||
recent[1].append(copy.deepcopy(event_tree))
|
||||
if len(recent[1]) > 256:
|
||||
del recent[1][0]
|
||||
return value, dropped
|
||||
|
||||
|
||||
def s5_state(events=(), minute=0, extra=None):
|
||||
state = d(clock=d(absolute_minute=[2, minute], speed=[2, 1]),
|
||||
systems=d(runtime=d(id_generator=d(counters=d(domain_event=[2, 3])),
|
||||
domain_events=d(published_ids=arr(*([4, 'domain_event.%012d' % i] for i in range(1, 4))),
|
||||
recent_events=arr(*events)),
|
||||
facilities=d(instances=arr(d(id=[4, 'f1']))),
|
||||
vectors=arr([9, [[3, '0000000000000000'], [3, '000000000000f03f'], [3, '0000000000000040']]],
|
||||
[30, [[2, 1], [2, 2]]]))),
|
||||
value=arr([2, minute]))
|
||||
if extra is not None:
|
||||
state[1].append([[4, 'extra'], extra])
|
||||
return state
|
||||
|
||||
|
||||
def reference_validate(node):
|
||||
"""The S1 validate_tree (JSON key comparison, stack order), kept to prove equivalence."""
|
||||
import base64, re
|
||||
pending = [node]
|
||||
while pending:
|
||||
n = pending.pop()
|
||||
eq.require(type(n) is list and n and type(n[0]) is int, 'invalid typed node')
|
||||
t = n[0]
|
||||
eq.require(t not in (23, 24, 25, 26) and 0 <= t <= 38, 'unsupported Variant type')
|
||||
eq.require(len(n) == (1 if t == 0 else 3 if t in (27, 28) else 2), 'node arity')
|
||||
if t == 0:
|
||||
continue
|
||||
p = n[1]
|
||||
if t in (1, 2):
|
||||
eq.require(type(p) is int and (p in (0, 1) if t == 1 else -(2**63) <= p < 2**63), 'integer payload')
|
||||
elif t == 3:
|
||||
eq.require(type(p) is str and re.fullmatch('[0-9a-f]{16}', p), 'float must be f64 LE hex')
|
||||
elif t in (4, 21, 22):
|
||||
eq.require(type(p) is str, 'string payload')
|
||||
elif t == 29:
|
||||
eq.require(type(p) is str, 'byte array payload')
|
||||
try:
|
||||
eq.require(base64.b64encode(base64.b64decode(p, validate=True)).decode() == p, 'noncanonical base64')
|
||||
except (ValueError, TypeError) as exc:
|
||||
raise eq.EvidenceError('invalid base64') from exc
|
||||
elif t == 27:
|
||||
eq.require(type(p) is list, 'dictionary entries')
|
||||
seen = set()
|
||||
for pair in p:
|
||||
eq.require(type(pair) is list and len(pair) == 2, 'dictionary pair')
|
||||
key = eq.json_bytes(pair[0])
|
||||
eq.require(key not in seen, 'duplicate dictionary key')
|
||||
seen.add(key)
|
||||
pending.extend(pair)
|
||||
eq._typed_info(n[2], dictionary=True)
|
||||
elif t == 28:
|
||||
eq.require(type(p) is list, 'array elements')
|
||||
eq._typed_info(n[2], dictionary=False)
|
||||
pending.extend(p)
|
||||
else:
|
||||
eq.require(type(p) is list, 'component nodes')
|
||||
sizes = {5: 2, 6: 2, 7: 4, 8: 4, 9: 3, 10: 3, 11: 6, 12: 4, 13: 4, 14: 4, 15: 4, 16: 6, 17: 9, 18: 12, 19: 16, 20: 4}
|
||||
if t in sizes:
|
||||
eq.require(len(p) == sizes[t], 'component count')
|
||||
integer = t in (6, 8, 10, 13)
|
||||
eq.require(all(x and x[0] == (2 if integer else 3) for x in p), 'component type')
|
||||
elif t in (30, 31, 32, 33, 34):
|
||||
expected = {30: 2, 31: 2, 32: 3, 33: 3, 34: 4}[t]
|
||||
eq.require(all(x and x[0] == expected for x in p), 'packed element type')
|
||||
else:
|
||||
eq.require(all(x and x[0] == {35: 5, 36: 9, 37: 20, 38: 12}[t] for x in p), 'packed vector type')
|
||||
pending.extend(p)
|
||||
return node
|
||||
|
||||
|
||||
class S5(unittest.TestCase):
|
||||
def setUp(self):
|
||||
eq.reset_blobs()
|
||||
self.root = s5_root()
|
||||
eq.add_blob_root(self.root)
|
||||
|
||||
def tearDown(self):
|
||||
eq.reset_blobs()
|
||||
shutil.rmtree(self.root)
|
||||
|
||||
def record(self, tree):
|
||||
return {'tree': {'blob': put_blob(self.root, eq.json_bytes(tree))}, 'tree_sha': eq.tree_sha(tree),
|
||||
'raw_bytes_sha': 'a'*64, 'roundtrip_ok': True}
|
||||
|
||||
def test_blob_transport_is_verified_and_bound(self):
|
||||
tree = s5_state([event()])
|
||||
record = self.record(tree)
|
||||
loaded = eq._transport(record, None)
|
||||
self.assertEqual(list(loaded), tree)
|
||||
self.assertEqual(eq.tree_sha(loaded), eq.tree_sha(tree))
|
||||
for name, mutate in [('tree_sha', lambda r: r.update(tree_sha='b'*64)), ('roundtrip', lambda r: r.update(roundtrip_ok=False)),
|
||||
('raw', lambda r: r.update(raw_bytes_sha='x')), ('extra', lambda r: r.update(diff=[])),
|
||||
('ref', lambda r: r['tree'].update(extra=1)), ('missing', lambda r: r['tree'].update(blob='c'*64))]:
|
||||
bad = copy.deepcopy(record); mutate(bad)
|
||||
with self.subTest(name=name), self.assertRaises((eq.EvidenceError, KeyError)):
|
||||
eq._transport(bad, None)
|
||||
digest = record['tree_sha']
|
||||
for name, content in [('changed', gzip.compress(eq.json_bytes(s5_state()))), ('not gzip', b'not gzip'),
|
||||
('float', gzip.compress(b'[3,1.5]')), ('object', gzip.compress(b'[27,{"a":1},null]'))]:
|
||||
eq.reset_blobs(); eq.add_blob_root(self.root)
|
||||
(self.root/(digest+'.gz')).write_bytes(content)
|
||||
with self.subTest(name=name), self.assertRaises(eq.EvidenceError):
|
||||
eq._transport(record, None)
|
||||
for content in (b'[3,1.5]', b'[27,{"a":1},null]'):
|
||||
other = put_blob(self.root, content)
|
||||
with self.subTest(content=content), self.assertRaises(eq.EvidenceError):
|
||||
eq.blob_tree(other)
|
||||
|
||||
def test_every_checkpoint_full_tree_verified_like_inline(self):
|
||||
"""S5 drops the diff chain: each checkpoint carries a complete tree, verified alone."""
|
||||
value = s2_report()
|
||||
for i, cp in enumerate(value['checkpoints']):
|
||||
state = cp['S']['tree']
|
||||
state[1] = [pair for pair in state[1] if pair[0][1] != 'value'] + [[[4, 'value'], arr([2, i])]]
|
||||
cp['S'] = transport(state); cp['latest'] = transport(state)
|
||||
inline_joined, inline_proofs = checker.join_report(value)
|
||||
self.assertTrue(checker.check_report(value, measurement(value))['ok'])
|
||||
blobbed = as_blobs(value, self.root)
|
||||
self.assertEqual(len(list(self.root.iterdir())), 31)
|
||||
joined, proofs = checker.join_report(blobbed)
|
||||
self.assertEqual(eq.json_bytes(proofs), eq.json_bytes(inline_proofs))
|
||||
self.assertEqual([cp['harness_normalized_digest'] for cp in joined['checkpoints']],
|
||||
[cp['harness_normalized_digest'] for cp in inline_joined['checkpoints']])
|
||||
self.assertTrue(checker.check_report(blobbed, measurement(blobbed))['ok'])
|
||||
self.assertTrue(eq.compare_reports(joined, inline_joined)['ok'])
|
||||
for index in range(31):
|
||||
digest = blobbed['checkpoints'][index]['S']['tree_sha']
|
||||
original = (self.root/(digest+'.gz')).read_bytes()
|
||||
other = copy.deepcopy(value['checkpoints'][index]['S']['tree']); other[1][-1][1][1][0][1] = 999
|
||||
(self.root/(digest+'.gz')).write_bytes(gzip.compress(eq.json_bytes(other)))
|
||||
eq.reset_blobs(); eq.add_blob_root(self.root)
|
||||
with self.subTest(index=index):
|
||||
self.assertFalse(checker.check_report(blobbed, measurement(blobbed))['ok'])
|
||||
(self.root/(digest+'.gz')).write_bytes(original)
|
||||
eq.reset_blobs(); eq.add_blob_root(self.root)
|
||||
self.assertTrue(checker.check_report(blobbed, measurement(blobbed))['ok'])
|
||||
|
||||
def test_fast_normalization_equals_original(self):
|
||||
root = '/private/a'
|
||||
cases = {
|
||||
'identity': s5_state(),
|
||||
'events': s5_state([event(root), event(root, kind='SAVE_LOAD_FAILED', duration=9)]),
|
||||
'outside timing name': s5_state([event(root)], extra=d(wait_us=[2, 5])),
|
||||
'outside profile string': s5_state([event(root)], extra=[4, root+'/x']),
|
||||
'profile in key': s5_state([event(root)], extra=d(**{root+'/k': [2, 1]})),
|
||||
'save_service elsewhere': s5_state([event(root)], extra=[4, 'save_service']),
|
||||
'non-save event': s5_state([event(root, source='other')]),
|
||||
}
|
||||
for name, tree in cases.items():
|
||||
eq.reset_blobs(); eq.add_blob_root(self.root)
|
||||
with self.subTest(name=name):
|
||||
expected, expected_side = eq._normalize([copy.deepcopy(tree)], profile(root), False)
|
||||
loaded = eq._transport(self.record(tree), None)
|
||||
normalized, side = eq.normalize_tree(loaded, profile(root))
|
||||
self.assertEqual(eq.json_bytes(eq.materialize(normalized)), eq.json_bytes(expected[0]))
|
||||
self.assertEqual(eq.json_bytes(side), eq.json_bytes(expected_side))
|
||||
self.assertEqual(eq.tree_sha(normalized), eq.tree_sha(expected[0]))
|
||||
restored = eq.restore_tree(normalized, side, profile(root))
|
||||
self.assertEqual(eq.tree_sha(restored), eq.tree_sha(tree))
|
||||
if side['entries']:
|
||||
forged = copy.deepcopy(side); forged['entries'].pop()
|
||||
with self.assertRaises(eq.EvidenceError): eq.restore_tree(normalized, forged, profile(root))
|
||||
token = s5_state([event(root)], extra=[4, eq.TOKEN+'x'])
|
||||
for value in (token, s5_state([event(root)])):
|
||||
if value is token:
|
||||
with self.assertRaises(eq.EvidenceError): eq._normalize([copy.deepcopy(value)], profile(root), False)
|
||||
with self.assertRaises(eq.EvidenceError): eq.normalize_tree(eq._transport(self.record(value), None), profile(root))
|
||||
broken = s5_state()
|
||||
checker.node_at(broken, 'systems', 'runtime', 'domain_events')[1][1][1] = d()
|
||||
with self.assertRaisesRegex(eq.EvidenceError, 'recent_events array required'):
|
||||
eq.normalize_tree(eq._transport(self.record(broken), None), profile(root))
|
||||
|
||||
def test_compare_and_digest_equal_s1_reference(self):
|
||||
a = s5_state([event()], extra=d(z=[2, 1], y=d(b=[4, 'v'], a=[21, 'v'])))
|
||||
variants = {'same': copy.deepcopy(a)}
|
||||
b = copy.deepcopy(a); b[1].reverse(); variants['dict order'] = b
|
||||
c = copy.deepcopy(a); checker.node_at(c, 'extra', 'y', 'a')[0] = 4; variants['value type'] = c
|
||||
k = copy.deepcopy(a); checker.node_at(k, 'extra')[1][1][0] = [21, 'y']; variants['key type'] = k
|
||||
m = copy.deepcopy(a); checker.node_at(m, 'systems', 'runtime', 'vectors')[2] = [[2, 10], [21, ''], [0]]; variants['metadata'] = m
|
||||
n = copy.deepcopy(a); checker.node_at(n, 'systems', 'runtime', 'vectors')[1].append([2, 3]); variants['array length'] = n
|
||||
v = copy.deepcopy(a); checker.node_at(v, 'systems', 'runtime', 'vectors')[1][0][1][2] = [3, '0000000000000840']; variants['vector'] = v
|
||||
for name, other in variants.items():
|
||||
with self.subTest(name=name):
|
||||
expected = reference_compare_trees(a, other)
|
||||
self.assertEqual(eq.json_bytes(eq.compare_trees(a, other)), eq.json_bytes(expected))
|
||||
left, right = eq._transport(self.record(a), None), eq._transport(self.record(other), None)
|
||||
self.assertEqual(eq.json_bytes(eq.compare_trees(left, right)), eq.json_bytes(expected))
|
||||
self.assertEqual(checker.normalized_digest(other), reference_digest(other))
|
||||
self.assertEqual(checker.normalized_digest(right), reference_digest(other))
|
||||
self.assertEqual(eq.exact(left, right), reference_exact(a, other))
|
||||
|
||||
def test_path_copy_builders_equal_deepcopy_reference(self):
|
||||
b1 = s5_state([event()])
|
||||
frozen = eq.json_bytes(b1)
|
||||
for kind in checker.E3_KINDS:
|
||||
with self.subTest(kind=kind):
|
||||
self.assertEqual(eq.json_bytes(checker.e3_expected_input(b1, kind)), eq.json_bytes(reference_expected_input(b1, kind)))
|
||||
loaded = eq._transport(self.record(b1), None)
|
||||
for kind in checker.E3_KINDS:
|
||||
self.assertEqual(eq.json_bytes(checker.e3_expected_input(loaded, kind)), eq.json_bytes(reference_expected_input(b1, kind)))
|
||||
new_event = event(); checker.node_at(new_event, 'event_id')[1] = 'domain_event.000000000004'
|
||||
for before in (b1, s5_state([event()]*256)):
|
||||
got, dropped = checker.e3_x(before, new_event)
|
||||
want, want_dropped = reference_x(before, new_event)
|
||||
self.assertEqual(eq.json_bytes([got, dropped]), eq.json_bytes([want, want_dropped]))
|
||||
self.assertEqual(eq.json_bytes(b1), frozen)
|
||||
self.assertEqual(eq.json_bytes(loaded), frozen)
|
||||
|
||||
def test_event_stream_replacement_is_existing_defect_not_pass(self):
|
||||
ev = event(); checker.node_at(ev, 'event_id')[1] = 'domain_event.000000000004'
|
||||
fact = {'replaced': True, 'new_stream_subscribers_before': [], 'recovered_event_ids': ['domain_event.000000000004']}
|
||||
defects = checker.stream_defects(fact, [ev])
|
||||
self.assertEqual([x['kind'] for x in defects], ['event_stream_replaced_subscribers_not_carried'])
|
||||
self.assertEqual(checker.stream_defects({'replaced': False}, []), [])
|
||||
for bad in [dict(fact, recovered_event_ids=['missing']), dict(fact, recovered_event_ids=['domain_event.000000000004']*2),
|
||||
dict(fact, new_stream_subscribers_before=['*/p28_eq_observer']), {'replaced': False, 'extra': 1},
|
||||
{'replaced': True}, None, dict(fact, extra=1)]:
|
||||
with self.subTest(bad=bad), self.assertRaises((eq.EvidenceError, KeyError, TypeError, AttributeError)):
|
||||
checker.stream_defects(bad, [ev])
|
||||
raw, evidence, previous = e45_fixture('O')
|
||||
index = [e['id'] for e in evidence].index('E4/restore_snapshot')
|
||||
evidence[index]['detail']['event_stream'] = {'replaced': True, 'new_stream_subscribers_before': [], 'recovered_event_ids': []}
|
||||
raw['scenarios'][18+index]['result']['facts'] = checker.e45_facts(evidence[index]['detail'])
|
||||
outcome = checker.check_e45(raw, evidence, previous)[index]
|
||||
self.assertEqual(outcome['status'], 'existing-defect')
|
||||
self.assertEqual(outcome['defects'][0]['kind'], 'event_stream_replaced_subscribers_not_carried')
|
||||
|
||||
def test_lifecycle_binds_replacements_guard_text_and_blobs(self):
|
||||
with synthetic_directory() as name:
|
||||
directory = Path(name)
|
||||
row = s4_phase_fixture(directory)
|
||||
evidence = directory/row['phase']/'evidence'
|
||||
raw = verifier.read(evidence/'report.json'); life = verifier.read(evidence/'lifecycle.json')
|
||||
guard = verifier.read(directory/(row['phase']+'-guard.json'))
|
||||
self.assertTrue(checker.check_lifecycle(raw, life, guard))
|
||||
bad = copy.deepcopy(life); bad['event_stream_replacements'] = [{'replaced': True}]
|
||||
with self.assertRaisesRegex(eq.EvidenceError, 'event stream replacement record differs'):
|
||||
checker.check_lifecycle(raw, bad, guard)
|
||||
text = (directory/(row['phase']+'-guard.json')).read_text()
|
||||
good = copy.deepcopy(life); good['guard']['receipt_text'] = text
|
||||
good['guard']['receipt'] = json.loads(text.replace('"conditions_checked": 5', '"conditions_checked": 5.0'))
|
||||
self.assertTrue(checker.check_lifecycle(raw, good, guard))
|
||||
for changed in (text.replace('"conditions_checked": 5', '"conditions_checked": 5.0'), text + ' '):
|
||||
bad = copy.deepcopy(good); bad['guard']['receipt_text'] = changed
|
||||
with self.subTest(changed=changed[-20:]), self.assertRaises(eq.EvidenceError):
|
||||
checker.check_lifecycle(raw, bad, guard)
|
||||
verifier.phase_evidence(directory, row)
|
||||
blobs = evidence/'blobs'; blobs.mkdir()
|
||||
extra = put_blob(blobs, b'extra')
|
||||
moved = dict(row, evidence_files=verifier.inventory(evidence, seals=True))
|
||||
with self.assertRaisesRegex(eq.EvidenceError, 'blob inventory differs'):
|
||||
verifier.phase_evidence(directory, moved)
|
||||
(blobs/(extra+'.gz')).unlink(); (blobs/'not-a-blob.gz').write_bytes(b'x')
|
||||
with self.assertRaisesRegex(eq.EvidenceError, 'unexpected evidence blob name'):
|
||||
verifier.phase_evidence(directory, dict(row, evidence_files=verifier.inventory(evidence, seals=True)))
|
||||
|
||||
def test_manifest_numbers_keep_their_type(self):
|
||||
package = e3_package_fixture('missing_key')
|
||||
self.assertTrue(checker.e3_package_check(package, 'missing_key'))
|
||||
import base64
|
||||
changed = b'{"format_version":1.0}'
|
||||
package['modified']['manifest.json'] = {'bytes': base64.b64encode(changed).decode(), 'sha256': checker.sha(changed)}
|
||||
with self.assertRaisesRegex(eq.EvidenceError, 'manifest mutation rule'):
|
||||
checker.e3_package_check(package, 'missing_key')
|
||||
blobbed = e3_package_fixture('missing_key')
|
||||
for side in ('original', 'modified', 'after_operation'):
|
||||
for name, item in blobbed[side].items():
|
||||
if item is not None:
|
||||
content = base64.b64decode(item['bytes'])
|
||||
item['bytes'] = {'blob': put_blob(self.root, content)}
|
||||
self.assertTrue(checker.e3_package_check(blobbed, 'missing_key'))
|
||||
|
||||
def test_signed_instance_ids_raw_kept_and_excluded(self):
|
||||
value = s2_report()
|
||||
for cp in value['checkpoints']:
|
||||
cp['C'].update(catalog_instance_id='-9223371962598816238', current_catalog_instance_id='-9223371962598816238')
|
||||
self.assertTrue(checker.check_report(value, measurement(value))['ok'])
|
||||
other = copy.deepcopy(value)
|
||||
for cp in other['checkpoints']:
|
||||
cp['C'].update(catalog_instance_id='-1', current_catalog_instance_id='-1')
|
||||
joined_a, _ = checker.join_report(value); joined_b, _ = checker.join_report(other)
|
||||
result = eq.compare_reports(joined_a, joined_b)
|
||||
self.assertTrue(result['ok'])
|
||||
self.assertIn({'path': ['checkpoints', 0, 'C', 'catalog_instance_id'], 'reason': 'process-local instance ID',
|
||||
'a': '-9223371962598816238', 'b': '-1'}, result['excluded'])
|
||||
for cp in value['checkpoints']:
|
||||
cp['C'].update(catalog_instance_id='-0x1')
|
||||
self.assertFalse(checker.check_report(value, measurement(value))['ok'])
|
||||
|
||||
def test_publish_pairs_bound_to_source_causes(self):
|
||||
def pair(minute, counter, commands):
|
||||
return [{'signal': 'snapshot_published', 'minute': minute, 'event_counter': counter, 'command_counter': commands},
|
||||
{'signal': 'state_updated', 'minute': minute, 'event_counter': counter, 'command_counter': commands}]
|
||||
# R minute 648 shape: paused submits each publish after their command events; tick end last.
|
||||
events = {4: ('GUEST_STATE_CHANGED', 634, None), 5: ('COMMAND_COMPLETED', 634, 1), 6: ('COMMAND_COMPLETED', 634, 2),
|
||||
7: ('GUEST_STATE_CHANGED', 634, None), 8: ('COMMAND_FAILED', 634, 3)}
|
||||
good = pair(633, 3, 1) + pair(634, 5, 2) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4)
|
||||
command, other = checker.check_publishes(good, events, 3, 8, 1, 4)
|
||||
self.assertEqual(command, [(634, 5), (634, 6), (634, 8)])
|
||||
self.assertEqual(other, [(633, 3), (634, 8)])
|
||||
bad = {
|
||||
'missing adjacent command publish': pair(633, 3, 1) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4),
|
||||
'missing first command publish': pair(633, 3, 1) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4)[:0] + pair(634, 8, 4),
|
||||
'unpublished submit': pair(633, 3, 1) + pair(634, 5, 2) + pair(634, 6, 3),
|
||||
'submit without publish before tick': pair(633, 3, 2) + pair(634, 5, 2) + pair(634, 6, 3) + pair(634, 8, 4) + pair(634, 8, 4),
|
||||
'odd signal': good[:-1],
|
||||
'swapped pair': good[:2] + [good[3], good[2]] + good[4:],
|
||||
'pair across minutes': pair(633, 3, 1) + [good[2], dict(good[3], minute=635)] + good[4:],
|
||||
'counter mismatch in pair': pair(633, 3, 1) + [good[2], dict(good[3], event_counter=6)] + good[4:],
|
||||
'order': pair(634, 5, 2) + pair(633, 3, 1) + good[4:],
|
||||
'outside interval': good + pair(634, 9, 4),
|
||||
'missing fields': [{'signal': s['signal'], 'minute': s['minute']} for s in good],
|
||||
}
|
||||
for name, value in bad.items():
|
||||
with self.subTest(name=name):
|
||||
try:
|
||||
command, other = checker.check_publishes(value, events, 3, 8, 1, 4)
|
||||
except (eq.EvidenceError, KeyError, TypeError):
|
||||
continue
|
||||
self.fail('accepted: %s -> %r %r' % (name, command, other))
|
||||
# A missing tick-end publish leaves the command publishes intact; the caller's count of
|
||||
# tick/restore publishes (one per measured minute) rejects it.
|
||||
command, other = checker.check_publishes(pair(633, 3, 1) + pair(634, 5, 2) + pair(634, 6, 3) + pair(634, 8, 4), events, 3, 8, 1, 4)
|
||||
self.assertEqual((len(command), other), (3, [(633, 3)]))
|
||||
# In the measurement the remaining publishes are exactly one tick end per minute.
|
||||
value = s2_report()
|
||||
self.assertTrue(checker.check_report(value, measurement(value))['ok'])
|
||||
for name, mutate in [('extra tick publish', lambda rows: rows.__setitem__(slice(2, 2), pair(634, 3, 1))),
|
||||
('missing tick publish', lambda rows: rows.__delitem__(slice(2, 4)))]:
|
||||
broken = s2_report(); mutate(broken['signal_order'])
|
||||
with self.subTest(name=name):
|
||||
self.assertFalse(checker.check_report(broken, measurement(broken))['ok'])
|
||||
|
||||
def test_labelled_harness_digest_falls_back_to_dictionary_order_only(self):
|
||||
def joined(tree):
|
||||
value = report(tree)
|
||||
for cp in value['checkpoints']:
|
||||
cp['harness_normalized_digest'] = None
|
||||
return checker.join_report(value)[0]
|
||||
state = snapshot([event()])
|
||||
reordered = copy.deepcopy(state); reordered[1].reverse()
|
||||
a, b = joined(state), joined(reordered)
|
||||
self.assertTrue(all(cp['harness_normalized_digest'].startswith(eq.HARNESS_LABEL) for cp in a['checkpoints']))
|
||||
self.assertNotEqual(a['checkpoints'][0]['harness_normalized_digest'], b['checkpoints'][0]['harness_normalized_digest'])
|
||||
result = eq.compare_reports(a, b)
|
||||
self.assertTrue(result['ok'], result['differences'][:2])
|
||||
self.assertIn('harness canonical digest (dictionary order only)', [m['mode'] for m in result['digest_modes']])
|
||||
self.assertTrue(result['dict_order_paths'])
|
||||
changed = copy.deepcopy(state); checker.node_at(changed, 'value')[1][0] = [2, 2]
|
||||
result = eq.compare_reports(a, joined(changed))
|
||||
self.assertFalse(result['ok'])
|
||||
self.assertIn(['checkpoints', 0, 'harness_normalized_digest'], [d['path'] for d in result['differences']])
|
||||
|
||||
def test_memo_scope_same_results_and_restores_gc(self):
|
||||
import gc
|
||||
value, evidence, b1 = e3_fixture()
|
||||
plain = checker.check_report(value, measurement(value), evidence, b1)
|
||||
self.assertTrue(gc.isenabled())
|
||||
with checker.memo_scope():
|
||||
self.assertFalse(gc.isenabled())
|
||||
first = checker.check_report(value, measurement(value), evidence, b1)
|
||||
second = checker.check_report(value, measurement(value), evidence, b1)
|
||||
self.assertTrue(gc.isenabled())
|
||||
self.assertEqual(eq.json_bytes(plain), eq.json_bytes(first))
|
||||
self.assertEqual(eq.json_bytes(first), eq.json_bytes(second))
|
||||
broken = copy.deepcopy(value); broken['scenarios'][0]['result']['code_reason'] = 'invented'
|
||||
with checker.memo_scope():
|
||||
checker.check_report(value, measurement(value), evidence, b1)
|
||||
self.assertFalse(checker.check_report(broken, measurement(broken), evidence, b1)['ok'])
|
||||
|
||||
def test_blob_trees_get_the_same_full_validation_as_inline(self):
|
||||
invalid = {
|
||||
'bool as int': [2, True], 'bool payload': [1, True], 'object tag': [24, 0], 'callable tag': [25, 0],
|
||||
'nil arity': [0, 1], 'int arity': [2], 'dict arity': [27, []], 'array arity': [28, [], None, None],
|
||||
'float text': [3, '1.0'], 'base64': [29, 'not base64!'], 'scripted metadata': [28, [], [[2, 2], [21, ''], [2, 1]]],
|
||||
'nested bool': [27, [[[4, 'k'], [2, False]]], None], 'duplicate key': [27, [[[4, 'k'], [0]], [[4, 'k'], [0]]], None],
|
||||
'vector component': [9, [[2, 1], [2, 2], [2, 3]]],
|
||||
}
|
||||
for name, tree in invalid.items():
|
||||
with self.subTest(name=name):
|
||||
with self.assertRaises(eq.EvidenceError): eq.validate_tree(copy.deepcopy(tree))
|
||||
digest = put_blob(self.root, json.dumps(tree, separators=(',', ':')).encode())
|
||||
record = {'tree': {'blob': digest}, 'tree_sha': digest, 'raw_bytes_sha': 'a'*64, 'roundtrip_ok': True}
|
||||
for attempt in range(2):
|
||||
with self.assertRaises(eq.EvidenceError): eq._transport(record, None)
|
||||
with self.assertRaises(eq.EvidenceError): eq._transport_ref(record)
|
||||
with self.assertRaises(eq.EvidenceError): eq.known_sha(eq.TreeRef(digest))
|
||||
self.assertNotIn(digest, eq._VALID_BLOBS)
|
||||
good, fake = [2, 1], [2, True]
|
||||
good_record = self.record(good)
|
||||
fake_digest = put_blob(self.root, b'[2,true]')
|
||||
fake_ref = eq.TreeRef(fake_digest)
|
||||
with self.assertRaises(eq.EvidenceError): eq.exact(fake_ref, eq._transport(good_record, None))
|
||||
with self.assertRaises(eq.EvidenceError): eq.same_tree(fake_ref, eq.TreeRef(fake_digest))
|
||||
with self.assertRaises(eq.EvidenceError): eq.validate_tree(fake_ref)
|
||||
self.assertTrue(eq.exact(eq._transport(good_record, None), eq._transport_ref(good_record)))
|
||||
|
||||
def test_validate_tree_equals_s1_reference_on_mutations(self):
|
||||
class Lcg:
|
||||
# Deterministic generator; the EQ import closure has no random module.
|
||||
def __init__(self, seed): self.state = seed
|
||||
def randrange(self, n):
|
||||
self.state = (self.state * 6364136223846793005 + 1442695040888963407) % 2**64
|
||||
return (self.state >> 33) % n
|
||||
def choice(self, values): return values[self.randrange(len(values))]
|
||||
base = d(a=arr([2, 1], [3, '000000000000f03f'], [4, 's'], [21, 'n']),
|
||||
b=[9, [[3, '0000000000000000'], [3, '000000000000f03f'], [3, '0000000000000040']]],
|
||||
c=[10, [[2, 1], [2, 2], [2, 3]]], e=[30, [[2, 1], [2, 2]]], f=[35, [[5, [[3, '0000000000000000'], [3, '0000000000000000']]]]],
|
||||
g=[28, [[2, 1]], [[2, 2], [21, ''], [0]]], h=[29, 'AA=='], i=[1, 1], j=[0], k=[22, 'np'],
|
||||
l=[27, [[[2, 1], [0]], [[21, 'x'], [0]]], [[[2, 2], [21, ''], [0]], [[2, 0], [21, ''], [0]]]])
|
||||
def outcome(function, value):
|
||||
try:
|
||||
function(value)
|
||||
return 'ok'
|
||||
except Exception as error:
|
||||
return type(error).__name__ + ':' + str(error)
|
||||
def mutate(node):
|
||||
choice = rng.randrange(9)
|
||||
if not node:
|
||||
node.append(rng.choice([0, 2, 'x', None])); return
|
||||
if choice == 0: node[0] = rng.choice([24, 25, 99, -1, 0, 1, 2, 3, 4, 9, 10, 27, 28, 30, 35, True, '2'])
|
||||
elif choice == 1: node.append(None)
|
||||
elif choice == 2: node[-1] = rng.choice([True, False, 1.5, 'zz', [], None, 2**64, [0]])
|
||||
elif choice == 3: del node[-1]
|
||||
elif choice == 4 and len(node) > 1: node[1] = rng.choice([True, 1.5, 'x', [], [[4, 'a']], [[2, 1], [2, 1]], 2**63, 'AA==', '000000000000f03f'])
|
||||
elif choice == 5: node.clear()
|
||||
elif choice == 6 and len(node) > 1 and isinstance(node[1], list) and node[1]: node[1].append(copy.deepcopy(node[1][0]))
|
||||
elif choice == 7 and len(node) > 2: node[2] = rng.choice([[[2, 2], [21, ''], [0]], [[2, 2], [21, ''], [2, 1]], [], 'x', None])
|
||||
else: node.insert(0, 3)
|
||||
rng = Lcg(20261022)
|
||||
seen = set()
|
||||
for case in range(4000):
|
||||
value = copy.deepcopy(base); pool = []
|
||||
def collect(n):
|
||||
if isinstance(n, list):
|
||||
pool.append(n)
|
||||
for c in n: collect(c)
|
||||
collect(value)
|
||||
for _ in range(rng.choice((1, 1, 2, 3))):
|
||||
mutate(rng.choice(pool))
|
||||
expected = outcome(reference_validate, copy.deepcopy(value))
|
||||
seen.add(expected.split(':')[-1])
|
||||
self.assertEqual(outcome(eq.validate_tree, value), expected, case)
|
||||
self.assertGreater(len(seen), 12)
|
||||
|
||||
def test_probe_static_full_tree_blob_and_stream_requirements(self):
|
||||
probe = (verifier.HERE/'eq_probe.gd').read_text()
|
||||
self.assertTrue(checker.check_probe(probe)['ok'])
|
||||
for old, new in [('var snapshot_value: Dictionary = runtime.create_snapshot()', 'var snapshot_value: Dictionary = last_snapshot_value'),
|
||||
('var s: Dictionary = transport(snapshot_value)', 'var s: Dictionary = {}'),
|
||||
('"tree": store_blob(text)', '"tree": {"blob": hashed(text)}'),
|
||||
('refuse blob overwrite', 'blob exists'),
|
||||
('packed.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP) == bytes', 'true'),
|
||||
('known_event_ids[str(event.event_id)] = true', 'pass'),
|
||||
('if known_event_ids.has(str(event.event_id)): continue', 'pass'),
|
||||
('demand(current.subscribe(&"*", &"p28_eq_observer", observe_event), "wildcard resubscription failed")', 'pass'),
|
||||
(' detail["event_stream"] = follow_event_stream()\n detail["return"] = transport(load_return)', ' detail["return"] = transport(load_return)'),
|
||||
(' var stream_fact: Dictionary = follow_event_stream()', ' var stream_fact: Dictionary = {"replaced": false}')]:
|
||||
with self.subTest(old=old[:60]):
|
||||
self.assertEqual(probe.count(old), 1)
|
||||
self.assertFalse(checker.check_probe(probe.replace(old, new))['ok'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
@@ -5,6 +5,8 @@ import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
@@ -18,6 +20,16 @@ ROLES = {'R-A': 'R', 'R-B': 'R', 'O-A': 'O', 'O-B': 'O'}
|
||||
NAMES = {f'{prefix}-{i:02d}.json' for prefix, count in
|
||||
[('checkpoint', 31), ('observation', 31), ('e3', 18), ('e45', 21)] for i in range(count)} | {
|
||||
'events.json', 'report.json', 'lifecycle.json', 'audio-close.json'}
|
||||
BLOB_NAME = re.compile('blobs/[0-9a-f]{64}[.]gz')
|
||||
|
||||
|
||||
def split_evidence(files):
|
||||
"""Fixed evidence names, and the S5 content-addressed blobs beside them."""
|
||||
blobs = {n: p for n, p in files.items() if n.startswith('blobs/')}
|
||||
require(all(BLOB_NAME.fullmatch(n) for n in blobs), 'unexpected evidence blob name')
|
||||
return {n: p for n, p in files.items() if n not in blobs}, blobs
|
||||
|
||||
|
||||
LOCAL_NAMES = {'run_eq.py', 'verify_eq.py', 'check_eq.py', 'compare_versions.py', 'eq_probe.gd', 'test_eq.py'}
|
||||
require = eq.require
|
||||
|
||||
@@ -83,22 +95,35 @@ def check_audio(audio, pid):
|
||||
|
||||
def phase_evidence(directory, row):
|
||||
"""Recompute all S1-S3b checks from copied native bytes, with no cached verdicts."""
|
||||
with check.memo_scope():
|
||||
return _phase_evidence(directory, row)
|
||||
|
||||
|
||||
def _phase_evidence(directory, row):
|
||||
directory = Path(directory)
|
||||
phase, role = row['phase'], row['role']
|
||||
require(ROLES.get(phase) == role, 'phase/role binding')
|
||||
folder = directory / phase
|
||||
evidence = folder / 'evidence'
|
||||
files = inventory(evidence, seals=True)
|
||||
require(set(files) == NAMES and files == row['evidence_files'], 'exact private evidence inventory')
|
||||
fixed, blobs = split_evidence(files)
|
||||
require(set(fixed) == NAMES and files == row['evidence_files'], 'exact private evidence inventory')
|
||||
eq.add_blob_root(evidence / 'blobs')
|
||||
raw = check.read_report_stdout((folder / 'stdout.log').read_text())
|
||||
require(raw == read(evidence / 'report.json') and raw['role'] == role, 'raw report/role copy')
|
||||
eq.ACTIVE_PROFILES[:] = [raw['profile_root']]
|
||||
require(len(raw['scenarios']) == 39, '39 scenarios required')
|
||||
life = read(evidence / 'lifecycle.json')
|
||||
require(life['stage'] == 'S3b', 'S3b lifecycle required')
|
||||
require(life['stage'] == 'S5', 'S5 lifecycle required')
|
||||
declared_blobs = life['blobs']
|
||||
require(type(declared_blobs) is dict and {'blobs/' + k + '.gz': {'bytes': b['gz_bytes'], 'sha256': b['gz_sha256']}
|
||||
for k, b in declared_blobs.items()} == blobs, 'blob inventory differs')
|
||||
for digest, item in sorted(declared_blobs.items()):
|
||||
require(len(eq.blob_bytes(digest)) == item['bytes'], 'blob size differs: ' + digest)
|
||||
require(len(life['e3']) == 18 and len(life['e45']) == 21, 'scenario evidence coverage')
|
||||
declared = {r['path']: {'bytes': r['bytes'], 'sha256': r['sha256']} for r in life['files']}
|
||||
require(len(declared) == len(life['files']), 'duplicate lifecycle inventory')
|
||||
expected = {'evidence/' + n: v for n, v in files.items() if n not in ('lifecycle.json', 'audio-close.json')}
|
||||
expected = {'evidence/' + n: v for n, v in fixed.items() if n not in ('lifecycle.json', 'audio-close.json')}
|
||||
require(declared == expected, 'lifecycle inventory differs')
|
||||
guard_path = directory / (phase + '-guard.json')
|
||||
guard = read(guard_path)
|
||||
@@ -120,7 +145,47 @@ def phase_evidence(directory, row):
|
||||
return wire({'raw': raw, 'joined': joined, 'proofs': proofs, 'checker': checked})
|
||||
|
||||
|
||||
def recompute(directory, execution, collector, standard, diag):
|
||||
def phase_worker():
|
||||
"""--phase-worker: phase_evidence for one phase (JSON on stdin), result and state on stdout."""
|
||||
payload = json.load(sys.stdin)
|
||||
with check.memo_scope():
|
||||
result = phase_evidence(Path(payload['directory']), payload['row'])
|
||||
state = {'valid_blobs': sorted(eq._VALID_BLOBS),
|
||||
'normalized': [[key[0], key[1].decode('utf-8'), value] for key, value in eq._NORMALIZED.items()]}
|
||||
json.dump({'result': result, 'state': state}, sys.stdout, ensure_ascii=False, allow_nan=False)
|
||||
return 0
|
||||
|
||||
|
||||
def phase_results(directory, rows, parallel):
|
||||
"""phase_evidence for every role phase. In parallel mode each phase runs in its own
|
||||
worker process of this same verification; the workers' validated blob SHAs and
|
||||
normalization facts are then used here for the same-role comparisons, so blobs are
|
||||
validated once. Results are identical to the sequential mode, which is the fallback."""
|
||||
if not parallel:
|
||||
return {row['phase']: phase_evidence(directory, row) for row in rows}
|
||||
try:
|
||||
workers = [(row, subprocess.Popen([sys.executable, '-B', str(HERE / 'verify_eq.py'), '--phase-worker'], cwd=str(HERE),
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)) for row in rows]
|
||||
except OSError:
|
||||
return phase_results(directory, rows, False)
|
||||
for row, worker in workers:
|
||||
worker.stdin.write(json.dumps({'directory': str(directory), 'row': row}, ensure_ascii=False).encode('utf-8'))
|
||||
worker.stdin.close()
|
||||
worker.stdin = None # every input is sent first, so all workers run at once
|
||||
results = {}
|
||||
for row, worker in workers:
|
||||
out, err = worker.communicate()
|
||||
require(worker.returncode == 0, 'phase worker failed: ' + row['phase'] + ': ' + err.decode('utf-8', 'replace')[-2000:])
|
||||
value = check.strict_json(out.decode('utf-8'))
|
||||
results[row['phase']] = value['result']
|
||||
eq.add_blob_root(Path(directory) / row['phase'] / 'evidence' / 'blobs')
|
||||
eq._VALID_BLOBS.update(value['state']['valid_blobs'])
|
||||
for sha_, profile_json, fact in value['state']['normalized']:
|
||||
eq._NORMALIZED.setdefault((sha_, profile_json.encode('utf-8')), tuple(fact))
|
||||
return {row['phase']: results[row['phase']] for row in rows}
|
||||
|
||||
|
||||
def recompute(directory, execution, collector, standard, diag, parallel=False):
|
||||
"""Validate owned receipts and recompute every comparison from raw evidence."""
|
||||
directory = Path(directory)
|
||||
require(execution['passed'] is True and execution['error'] is None and execution['cleanup_errors'] == [], 'execution failed')
|
||||
@@ -149,7 +214,7 @@ def recompute(directory, execution, collector, standard, diag):
|
||||
require(execution['pins_after'] == admission['pins'], 'pin preservation')
|
||||
rows = execution['phases']
|
||||
require([r['phase'] for r in rows] == PHASES, 'seven phases in order')
|
||||
pids, profiles, joined, results = [], [], {}, {}
|
||||
pids, profiles, evidence_rows = [], [], []
|
||||
previous = source
|
||||
for i, row in enumerate(rows):
|
||||
phase = row['phase']
|
||||
@@ -201,11 +266,12 @@ def recompute(directory, execution, collector, standard, diag):
|
||||
require(row['role'] == ROLES[phase] and row['runtime_profile'] == command['runtime_profile'] and
|
||||
row['runtime_name'] == command['runtime_name'], 'runtime/role binding')
|
||||
collector.guard_gate(row['guard'], Path(row['runtime_profile']), row['runtime_name'])
|
||||
results[phase] = phase_evidence(directory, row)
|
||||
joined[phase] = results[phase]['joined']
|
||||
evidence_rows.append(row)
|
||||
else:
|
||||
require(row['role'] is None, 'parse/import role')
|
||||
require('P28_02_EQ_REPORT=' not in (directory / phase / 'stdout.log').read_text(), 'parse/import report')
|
||||
results = phase_results(directory, evidence_rows, parallel)
|
||||
joined = {phase: value['joined'] for phase, value in results.items()}
|
||||
require(len(set(pids)) == 7 and len(set(profiles)) == 14, 'PID/profile reuse')
|
||||
require({r['pid'] for r in execution['cleanup']} == set(pids) and all(
|
||||
r['engine_ended'] is True and r['outer_cleanup_errors'] == [] and
|
||||
@@ -228,7 +294,7 @@ def verify(project_root, directory):
|
||||
admission = execution['admission']
|
||||
require(actual_pins(project_root, Path(admission['readonly_path']), Path(admission['fixture']),
|
||||
Path(admission['engine']), collector, standard) == admission['pins'], 'actual input/source/EQ pins differ')
|
||||
result = recompute(directory, execution, collector, standard, diag)
|
||||
result = recompute(directory, execution, collector, standard, diag, parallel=True)
|
||||
require(result == read(directory / 'results.json'), 'recomputed results differ')
|
||||
for phase, values in result['phases'].items():
|
||||
for name, value in values.items():
|
||||
@@ -237,12 +303,15 @@ def verify(project_root, directory):
|
||||
|
||||
|
||||
def main():
|
||||
if sys.argv[1:] == ['--phase-worker']:
|
||||
return phase_worker()
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--project-root', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
require(os.environ.get('BUILD_OUTPUT_DIR'), 'BUILD_OUTPUT_DIR required')
|
||||
result = verify(args.project_root, Path(os.environ['BUILD_OUTPUT_DIR']) / 'p28-02-eq-evidence')
|
||||
with check.memo_scope():
|
||||
result = verify(args.project_root, Path(os.environ['BUILD_OUTPUT_DIR']) / 'p28-02-eq-evidence')
|
||||
print(json.dumps({'status': 'verified', 'result': result}, ensure_ascii=False, allow_nan=False))
|
||||
return 0
|
||||
except (ValueError, OSError, KeyError, TypeError, IndexError) as error:
|
||||
|
||||
Reference in New Issue
Block a user