commit 5e91441795b5a838631af610a42d0ae367045bea
parent 08d7134408cd95c9d3fbda1aa60ae4f46682f5e4
Author: triesap <tyson@radroots.org>
Date: Tue, 21 Jul 2026 22:57:56 +0000
event-store: add atomic raw-source rebuild
- rebuild visibility and Food projections from immutable envelopes
- support governed cold repair with rollback-safe maintenance authority
- bind raw and normalized state digests to executable contracts
- verify cursor, WAL, schema, and caller-state isolation
Diffstat:
32 files changed, 17787 insertions(+), 1735 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -137,6 +137,40 @@ publish policy both pass for the same source revision.
`RadrootsEventStoreReconciliationResource` type and
`ReconciliationCapacityExceeded` error variant are replaced by the
versioned source-capacity resource and typed capacity/history errors.
+<!-- release-change: event-store-raw-source-rebuild-authority -->
+- Event-store schema v4 now exposes a versioned raw-source rebuild operation
+ that repairs governed derived NIP-09, current-visibility, and focused
+ FoodAvailability state from reverified immutable raw envelopes in one
+ `BEGIN IMMEDIATE` transaction. The typed report returns prior and new source
+ generations, the rebound capacity/high-water seal, a domain-separated
+ immutable-raw digest, and a generation-normalized active-product digest.
+ Rebuild drift exposes six stable typed authority categories while retaining
+ non-contractual diagnostic detail for operators.
+ The file-only repair entry point requires an existing exact managed-v4 WAL
+ database and returns a store only after repaired state commits. It creates a
+ deterministic single-connection pool and proves a fresh canonical-path
+ connection shares the validated SQLite writer-lock domain. Callers must
+ quiesce every alias, independent pool, direct SQL user, and filesystem path,
+ symlink, or file-replacement operation for the repair duration. The public
+ event-store error enum is now non-exhaustive so future typed recovery errors
+ do not repeatedly break downstream matches.
+ Rebuild preserves unrelated caller-owned tables with no schema dependency on
+ any rebuild-mutated parent, generic projection cursors, and unrelated SQLite
+ sequence row triples. Transition replay performs one shared
+ target-alias cleanup, places the governed target first, and validates that
+ exact row after replay; generic cursor inventory is instead
+ prospectively bounded at 4,096 identities and invalidates lazily after a
+ generation change.
+ Before entropy or mutation, rebuild also bounds caller-owned main tables and
+ cumulative foreign-key rows at 4,096 each and refuses every caller-owned
+ inbound foreign key to every directly or indirectly mutated parent,
+ regardless of its SQLite action, so derived replacement cannot cascade into
+ caller rows or triggers. The sealed parent inventory includes the Food FTS5
+ virtual table and all five shadows plus the governed `sqlite_sequence` row;
+ it remains separate from the narrower scoped-integrity inventory.
+ The executable raw-rebuild successor contract freezes the SourceMaintenance
+ predecessor and the `0001` through `0004` migration inventory; no schema
+ migration is added.
- Bare-envelope replica ingestion is quarantined behind the explicit,
non-default `legacy-ingest` feature. Default replica APIs expose emit and sync
surfaces only; a future product ingest boundary must consume a store-produced
diff --git a/Cargo.lock b/Cargo.lock
@@ -4593,6 +4593,7 @@ dependencies = [
name = "radroots_event_store"
version = "1.0.0-alpha.1"
dependencies = [
+ "futures",
"getrandom 0.2.17",
"hex",
"nostr",
diff --git a/build/nix/common.nix b/build/nix/common.nix
@@ -19,8 +19,13 @@ let
../../.cargo
../../Cargo.toml
../../Cargo.lock
+ ../../flake.lock
../../CHANGELOG.md
../../README
+ ../../flake.nix
+ ../../build/nix/apps.nix
+ ../../build/nix/common.nix
+ ../../build/nix/toolchains.nix
../../dto_bindgen.toml
../../rust-toolchain.toml
../../contracts
diff --git a/contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json b/contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json
@@ -0,0 +1,462 @@
+{
+ "schema_version": 1,
+ "contract_id": "radroots_event_store.raw_source_rebuild_v1",
+ "delegated_suite": {
+ "id": "radroots_event_store.raw_source_rebuild_v1.delegated_rust_test_suite.v1",
+ "lane": "nix run .#contract",
+ "package": "radroots_event_store",
+ "authorities": [
+ {
+ "authority": "raw_source_rebuild_incremental_reopen_and_repeat_parity_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "projection_cursor_capacity_accepts_exact_and_rejects_one_over_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_invalidates_generic_cursors_without_enumerating_or_mutating_them_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_normalizes_only_transition_sqlite_sequence_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_rejects_unrelated_minimum_transition_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_reuses_target_alias_at_minimum_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_repairs_active_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_repairs_empty_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_repairs_derived_drift_and_refuses_raw_drift_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_refuses_transition_history_gap_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_refuses_historical_generation_lineage_corruption_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_rollback_failure_preserves_primary_and_rollback_errors_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_wal_readers_observe_only_committed_generation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_rejects_caller_inbound_foreign_keys_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_caller_schema_inventory_limits_are_typed_and_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_scoped_integrity_preserves_caller_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_generation_exhaustion_precedes_entropy_and_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_entropy_failure_is_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_empty_source_without_transitions_is_deterministic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_cold_file_repair_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_preflights_reject_bounded_authority_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_rejects_delete_mode_exact_v4_without_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_rejects_canonical_path_lock_domain_mismatch_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_post_preflight_failures_preserve_wal_and_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_covers_regular_replaceable_addressable_and_deletion_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_matches_wide_event_and_address_requests_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_matches_all_protocol_decision_branches_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_is_order_and_repeat_invariant_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ }
+ ]
+ },
+ "cases": [
+ {
+ "id": "empty_source_repeat_digest_parity",
+ "execution": "direct_executor",
+ "authority": "raw_source_rebuild_v1_result_vector",
+ "authority_path": "crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs",
+ "expected_outcome": "two committed rebuilds rotate generations while preserving zero capacity, raw high-water, immutable-raw digest, and normalized product digest",
+ "expected_immutable_raw_digest": "73e66ea95452e902176d701311e6e82b3cd7895ae77f3d73fd1cbb67bcf9d321",
+ "expected_active_product_state_digest": "bf20fc2ba0e7c64bb0958829e118d87a86efe17e2848bb098d3d9ab2a78c2245"
+ },
+ {
+ "id": "signed_food_fixture_typed_digest_parity",
+ "execution": "direct_executor",
+ "authority": "raw_source_rebuild_v1_result_vector",
+ "authority_path": "crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs",
+ "expected_outcome": "one signed admitted FoodAvailability fixture freezes exact immutable-raw and generation-normalized product digests across text, i64, boolean, optional, and blob framing and preserves them across repeated rebuild",
+ "expected_immutable_raw_digest": "336a6a6cf1d84b0fcb185c4a7550cf5a8d8047c5a3f89df40e0d8f1ead543c68",
+ "expected_active_product_state_digest": "1ed8a22036091f0a492f3848027b313be4ef1202a9cdfa6c3ff35e12ab10f15c"
+ },
+ {
+ "id": "incremental_reopen_repeat_product_parity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_incremental_reopen_and_repeat_parity_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "incremental, file reopen, first rebuild, and repeated rebuild expose identical generation-normalized current visibility, Food image, and logical FTS product witnesses",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "projection_cursor_capacity_exact_and_one_over",
+ "execution": "delegated_rust_test",
+ "authority": "projection_cursor_capacity_accepts_exact_and_rejects_one_over_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "4,096 unique generic cursors are accepted, the next unique insert and a 4,097-row migration/reconciliation inventory probe fail typed, and an existing identity at capacity remains updateable",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "generic_cursor_lazy_generation_invalidation",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_invalidates_generic_cursors_without_enumerating_or_mutating_them_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "generic cursor rows remain byte-identical and become invalid only through active-generation mismatch",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "target_first_transition_sequence_normalization",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_normalizes_only_transition_sqlite_sequence_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "missing, low, high, duplicate, and case-aliased target rows normalize once to a canonical target-first row at the retained transition maximum while every unrelated sqlite_sequence row triple remains unchanged",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "unrelated_minimum_transition_sequence_rowid_refusal",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_rejects_unrelated_minimum_transition_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "an unrelated sqlite_sequence row at the minimum SQLite rowid exhausts target-first placement and rejects atomically",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "minimum_target_alias_sequence_reuse",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_reuses_target_alias_at_minimum_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "a case-aliased target already at the minimum SQLite rowid is reused, canonicalized, and advanced to the replay high-water",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "active_transition_high_water_metadata_repair",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_repairs_active_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "low and high active transition high-water metadata drift repairs to the exact retained transition maximum while preserving immutable-raw, normalized product, and logical product parity across repeat rebuild",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "empty_transition_high_water_metadata_repair",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_repairs_empty_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "nonzero active transition high-water metadata on an empty source repairs to zero while preserving immutable-raw, normalized product, and logical product parity across reopen validation and repeat rebuild",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "derived_repair_and_raw_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_repairs_derived_drift_and_refuses_raw_drift_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "managed-v4 derived corruption is repaired, while immutable raw, capacity, governed catalog, or migration-ledger drift is refused before mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "transition_history_gap_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_refuses_transition_history_gap_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "a retained transition-history gap is refused as addressable-transition authority drift before any rebuild-owned state mutates",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "historical_generation_lineage_corruption_refusal",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_refuses_historical_generation_lineage_corruption_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "historical generation baselines and generation-bound transition lineage are authenticated before mutation, and any mismatch is refused atomically",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_marker_open",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_marker_open restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_generation_rotation",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_generation_rotation restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_core_replay",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_core_replay restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_visibility_audit",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_visibility_audit restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_food_reset_replay",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_food_reset_replay restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_food_audit",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_food_audit restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_marker_close",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_marker_close restores the exact prior committed database and leaves no marker residue",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_failure_preserves_both_errors",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_rollback_failure_preserves_primary_and_rollback_errors_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "the typed rollback error preserves both the primary rebuild failure and the SQL rollback failure",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "wal_reader_commit_visibility",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_wal_readers_observe_only_committed_generation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "concurrent WAL readers observe only the prior committed generation until rebuild commit",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "caller_inbound_foreign_key_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_rejects_caller_inbound_foreign_keys_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "caller-owned inbound foreign keys to directly or indirectly mutated parents are rejected before entropy or mutation; representative managed-parent cases cover CASCADE, SET NULL, SET DEFAULT, RESTRICT, and NO ACTION, while explicit parent-inventory cases cover the Food FTS5 virtual table, all five shadows, and sqlite_sequence, preserving caller rows, schema, triggers, side effects, and rebuild authority",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "caller_schema_inventory_capacity_exact_and_one_over",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_caller_schema_inventory_limits_are_typed_and_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "bounded caller main-table and cumulative foreign-key-row inventories accept their exact limits and return typed atomic refusal one row over before entropy or mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "scoped_integrity_preserves_caller_state",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_scoped_integrity_preserves_caller_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "unrelated caller rows, indices, foreign-key violations, and AUTOINCREMENT counters with no dependency on rebuild-owned tables remain outside rebuild authority and byte-identical",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "generation_exhaustion_preflight",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_generation_exhaustion_precedes_entropy_and_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "the ninth retained generation fails before entropy, marker, sequence, raw, or derived mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "generation_entropy_failure_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_entropy_failure_is_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "source-generation entropy failure returns the typed error before marker, sequence, raw, or derived mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "empty_source_without_transitions",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_empty_source_without_transitions_is_deterministic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "an empty source and absent target transition sequence rebuild deterministically without creating unrelated sequence state",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_file_repair",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_cold_file_repair_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "maintenance-only file repair restores exact managed-v4 derived corruption through a fresh governed connection while refusing nonexistent, unmanaged, and non-v4 databases without weakening ordinary constructors",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_bounded_preflight_authority_drift_refusal",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_preflights_reject_bounded_authority_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "bounded catalog, migration-history, temporary-schema, and encoding preflights refuse authority drift without creating rebuild state or changing persistent database authority",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_non_wal_file_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_rejects_delete_mode_exact_v4_without_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "cold repair requires an existing WAL database and rejects an exact managed-v4 DELETE-mode file without changing journal mode or governed state",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_canonical_path_lock_domain_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_rejects_canonical_path_lock_domain_mismatch_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "cold repair proves the caller path shares the validated SQLite writer-lock domain and rejects a mismatched canonical path without mutating either database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_post_preflight_failure_wal_state_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_post_preflight_failures_preserve_wal_and_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "raw reconciliation and source-capacity failures after cold preflight preserve the exact prior rebuild-owned state and WAL journal mode",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "pure_raw_snapshot_visibility_oracle",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_covers_regular_replaceable_addressable_and_deletion_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "the independent pure oracle covers regular, replaceable, addressable, empty-identifier replaceable address targets, and kind-5 visibility without consulting derived SQL views",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "direct_indexed_visibility_oracle_wide_targets",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_matches_wide_event_and_address_requests_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "one wide deletion request is reduced once into direct indexed event and address evidence whose per-target decisions exactly match the frozen NIP-09 evaluator without projection rescans",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "direct_indexed_visibility_oracle_protocol_matrix",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_matches_all_protocol_decision_branches_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "the direct indexed reducer exactly matches all seven frozen NIP-09 outcomes, preserves stale and winning evidence, and applies authorized-evidence precedence over mismatches",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "direct_indexed_visibility_oracle_order_repeat_invariance",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_is_order_and_repeat_invariant_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "canonical exact-event and address evidence is invariant under reversed request order and repeated admitted requests, including cutoff ties",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ }
+ ]
+}
diff --git a/contracts/releases/1.0.0-alpha.1.toml b/contracts/releases/1.0.0-alpha.1.toml
@@ -420,6 +420,21 @@ semver_impacts = [
summary = "Advance the event store to schema version 4 with prospective retained-source capacity enforcement across independent file pools, UTF-8 preflight before schema or journal mutation, bounded reopen recounts, rollback-protected finite generation history, coherent NIP-09 and FoodAvailability rebuild seals, typed capacity and recovery failures, and an authenticated executable SourceMaintenance successor contract that replaces exactly radroots_event_store_food_availability_image_delete_guard, radroots_event_store_food_availability_projection_delete_guard, and radroots_event_store_source_rebuild_marker_insert_guard; rejects drifted v3 upgrades atomically; restores the exact predecessor trigger SQL on rollback; and reserves future derived-state repair for an exact managed-v4 catalog, ledger, migration history, immutable raw/source lineage, and capacity without requiring derived hook health as a precondition."
[[changes]]
+id = "event-store-raw-source-rebuild-authority"
+classification = "breaking"
+semver_impacts = [
+ "add_exported_type",
+ "add_exported_function",
+ "add_exported_constant",
+ "add_exported_field",
+ "add_enum_variant",
+ "add_conformance_vector",
+ "change_exported_enum_variant",
+ "change_exported_algorithm_behavior",
+]
+summary = "Add an authenticated managed-v4 raw-source rebuild and file-only cold-repair authority with stable typed drift categories, serialized generation rotation, independent immutable-raw visibility audit, typed generation-normalized product-state digests, bounded generic projection cursors, a bounded caller-schema dependency preflight over every directly or indirectly mutated parent including the full Food FTS5 table family and sqlite_sequence, target-first transition sequence normalization, separately scoped integrity checks, exact rollback failpoints, a canonical-path SQLite lock-domain probe, deterministic crate-owned connection policy, and an executable successor contract while freezing the SourceMaintenance predecessor and migration inventory."
+
+[[changes]]
id = "transport-event-outcomes-and-replica-quarantine"
classification = "breaking"
semver_impacts = [
diff --git a/crates/event_store/Cargo.toml b/crates/event_store/Cargo.toml
@@ -13,7 +13,7 @@ readme = "README"
[features]
default = ["sqlite", "runtime-tokio"]
-sqlite = ["dep:getrandom", "dep:sqlx", "sqlx/sqlite-bundled"]
+sqlite = ["dep:futures", "dep:getrandom", "dep:sqlx", "sqlx/sqlite-bundled"]
runtime-tokio = ["sqlx/runtime-tokio"]
[dependencies]
@@ -31,6 +31,7 @@ radroots_event_codec = { workspace = true, default-features = false, features =
] }
radroots_transport = { workspace = true, default-features = false }
hex = { workspace = true }
+futures = { workspace = true, optional = true }
getrandom = { workspace = true, optional = true, features = ["std"] }
serde = { workspace = true, features = ["std"] }
serde_json = { workspace = true, features = ["std"] }
diff --git a/crates/event_store/README b/crates/event_store/README
@@ -179,9 +179,19 @@ history access explicitly to SQLite's `main` database and reject governed
temporary-schema collisions on the connection before authority reads or
mutation. Reconciliation and migration integrity checks fail on foreign-key
violations whose child table is declared as event-store-owned or uses the
-reserved namespace. Violations in unrelated shared-schema child tables remain
-caller-owned and visible through SQLite's full `foreign_key_check`; they do not
-block event-store migration or source rebuild.
+reserved namespace. Violations in unrelated shared-schema child tables with no
+dependency on event-store-owned tables remain caller-owned and do not block
+event-store migration or source rebuild. Raw-source rebuild bounds its
+caller-owned main-table and cumulative foreign-key-row inventories at 4,096
+each, then rejects every caller-owned inbound foreign key to the exact
+rebuild-mutated parent set before generation entropy or mutation. This refusal
+applies regardless of whether the declared action is `CASCADE`, `SET NULL`,
+`SET DEFAULT`,
+`RESTRICT`, or `NO ACTION`, preventing caller rows and their triggers from
+being changed as a side effect of derived-state replacement. The dependency
+inventory covers direct mutations, Food FTS5's virtual table and all five
+shadow tables, and the transition row in `sqlite_sequence`; this inventory is
+separate from the narrower post-rebuild scoped-integrity table set.
Migration `0002_nip09` re-verifies the frozen raw JSON and immutable columns,
rebuilds derived registry-v7 admission and raw-head facts, then creates one
@@ -192,15 +202,62 @@ authority before commit. Generations, NIP-09 facts, canonical addressable state,
and transitions are immutable and generation-partitioned. The reconciliation
version, addressable-feed version, registry version, hook-manifest digest, raw
counts, raw high-water sequence, and transition floor are stored with each
-generation and validated on open. A supported current-schema full rebuild must
-also reset and replay the version-3 Food authority before that marker closes;
-this checkpoint does not yet expose such a maintenance operation. Repair of
-derived transition high-water or Food projection state is authorized only
-inside that future rebuild after the exact managed-v4 catalog, ledger, and
-migration history plus immutable raw/source lineage and capacity validate.
-Derived hook state is the repair target, not a repair precondition. A drifted
-managed-v3 database is rejected atomically before v4 changes begin; schema
-upgrade is not a repair path for corrupt v3 authority.
+generation and validated on open. `rebuild_from_raw_v1` is the supported
+current-schema maintenance operation. It validates an exact managed-v4 catalog,
+ledger, migration history, immutable raw/source lineage, and capacity without
+requiring derived hook health; derived NIP-09, current-visibility, transition,
+and FoodAvailability state is the repair target. The serialized transaction
+reverifies raw NIP-01 envelopes, appends a fresh generation, rebuilds and audits
+core visibility through an independent raw-snapshot oracle, explicitly resets
+and replays Food rows, images, cursor, and FTS, then closes the guarded marker
+and commits. `repair_file_from_raw_v1` provides the same operation for cold
+derived-state corruption without weakening ordinary constructors; the file
+path must already exist and no migration is attempted. Cold repair requires
+every store alias, independent pool, and direct SQL user of the database to
+remain quiesced. The canonical path, symlink targets, and file replacement or
+rename operations must also remain quiesced for the repair duration. The file
+must already use WAL journal mode; repair validates
+this with a read-only query and never changes journal mode on the validation
+connection. The API does not accept caller-provided pools because their
+connection callbacks and session state cannot be sealed. It instead
+canonicalizes the file identity and creates a fresh governed single-connection
+pool with deterministic future options. After validating that connection, it
+reserves the writer transaction. A second independent connection must resolve
+to the same canonical identity and fail a write probe against that reservation,
+proving the path shares the same SQLite lock domain before rebuild proceeds in
+the original validated transaction. Live in-memory stores use
+`rebuild_from_raw_v1`.
+A drifted managed-v3 database is rejected atomically before v4 changes begin;
+schema upgrade is not a repair path for corrupt v3 authority.
+`RawSourceRebuildStateDrift` exposes a
+`RadrootsEventStoreRawSourceRebuildDriftV1` category whose `code()` value is
+stable for programmatic handling: `managed_schema_authority`,
+`immutable_raw_authority`, `source_generation_lineage`,
+`addressable_transition_authority`, `derived_product_state_authority`, or
+`rebuild_postcondition`. Its `detail` text is non-contractual diagnostic
+context and must not be parsed by callers.
+
+The rebuild report contains prior and new source generations, the rebound
+capacity and raw high-water seal, a domain-separated digest of ordered immutable
+raw rows, and a generation-normalized digest of active product state. Repeated
+rebuilds preserve both logical digests while rotating generation identity.
+Every successful rebuild irreversibly appends one of the eight retained source
+generations and can return `SourceGenerationHistoryLimitReached`; generation
+rotation invalidates generic projection cursors lazily and rebinds the governed
+FoodAvailability cursor in the same transaction.
+Only the `radroots_event_store_addressable_head_transition` SQLite sequence is
+normalized. One case-insensitive target-alias cleanup is the sole shared
+sequence-table scan; the canonical target row is placed first so transition
+replay does not repeatedly scan unrelated AUTOINCREMENT rows, then that exact
+row is validated after replay. Every unrelated sequence row triple is
+preserved. Unrelated caller-owned tables, indices, foreign-key violations,
+generic projection cursors, and AUTOINCREMENT rows that have no schema
+dependency on a rebuild-mutated parent remain outside rebuild authority. Scoped
+post-rebuild integrity checks cover only event-store-owned tables and indices
+plus the owned Food FTS table. Caller-schema inventory excess returns a typed
+capacity refusal; a bounded inbound dependency returns its caller table,
+foreign-key id and sequence, columns, managed parent, and SQLite actions in a
+typed error.
Every pending rebuild-bound migration, including `0002_nip09` and
`0003_food_availability_projection`, preflights and then rechecks under the
@@ -265,6 +322,15 @@ returns a cursor bound to the active source generation.
mismatch, sequence regression, a sequence beyond the raw high-water mark, and
conflicting writers.
+Supported APIs and migrations enforce a 4,096-identity bound for caller-owned
+generic projection cursors. Prospective insertion of a new identity fails with
+`ProjectionCursorCapacityExceeded` before cursor mutation at that boundary;
+updating an existing identity remains valid. Migration-time reconciliation
+inventory validation uses bounded cap-plus-one probes rather than an unbounded
+cursor inventory scan. Ordinary current-v4 reopen and raw-source rebuild never
+enumerate or mutate this inventory. Existing cursors invalidate lazily when
+their source generation no longer matches the active generation.
+
A consumer that needs a different projection version or encounters an
unbound legacy cursor must call `prepare_projection_cursor_rebuild`, rebuild
derived state through the ticket's target raw high-water sequence, and pass
diff --git a/crates/event_store/contracts/raw_source_rebuild_v1.manifest.json b/crates/event_store/contracts/raw_source_rebuild_v1.manifest.json
@@ -0,0 +1,1301 @@
+{
+ "schema_version": 1,
+ "contract_id": "radroots_event_store.raw_source_rebuild_v1",
+ "authority_id": "raw_source_rebuild_v1",
+ "manifest_schema": {
+ "path": "crates/event_store/contracts/raw_source_rebuild_v1.manifest.schema.json",
+ "byte_length": 17896,
+ "sha256": "f9d210967e54b66f39c8bb965d97b2001a0ebc0927e7c2c14edb8e474bfda695",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ "predecessor": {
+ "contract_id": "radroots_event_store.source_maintenance_v1",
+ "manifest": {
+ "path": "crates/event_store/contracts/source_maintenance_v1.manifest.json",
+ "byte_length": 14216,
+ "sha256": "e8911e6e5710278969cbd15557a5b856b1575dfd11a655711403598370b41221",
+ "hash_algorithm": "sha256_bytes_v1"
+ }
+ },
+ "migration_inventory": [
+ {
+ "path": "crates/event_store/migrations/0001_event_store.down.sql",
+ "byte_length": 522,
+ "sha256": "fa84d587f657f601947eaeb9cd239c962a48f6fcdce723588476e8d22f3c1f53",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_store/migrations/0001_event_store.up.sql",
+ "byte_length": 10712,
+ "sha256": "4c03906a1cffd418a48d40907aa9a1ca51bb41766cff7250c4dfc7c2fd6eddde",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_store/migrations/0002_nip09.down.sql",
+ "byte_length": 4807,
+ "sha256": "c51a099d9501f1e692c13d2226296a68ed9e6bfa5e8e46b2f12c6574dbe59e31",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_store/migrations/0002_nip09.up.sql",
+ "byte_length": 81614,
+ "sha256": "0c1730ff36eaebd285f9c0c94b9b7346af60266afa55c24a18e30446d369581a",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_store/migrations/0003_food_availability_projection.down.sql",
+ "byte_length": 1755,
+ "sha256": "29d663320109d9dd0df6a00b6a53d8d988438d01f7a66960a9d4ba3482ffffb8",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_store/migrations/0003_food_availability_projection.up.sql",
+ "byte_length": 23683,
+ "sha256": "4e7edfb981b25f76055efc7802ec30b4034eeae9b9c0809ea4ea7c574678748a",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_store/migrations/0004_source_maintenance.down.sql",
+ "byte_length": 5172,
+ "sha256": "fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_store/migrations/0004_source_maintenance.up.sql",
+ "byte_length": 19841,
+ "sha256": "425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d",
+ "hash_algorithm": "sha256_bytes_v1"
+ }
+ ],
+ "runtime": {
+ "event_store_schema_version": 4,
+ "event_contract_registry_version": 7,
+ "transaction_mode": "begin_immediate_v1",
+ "projection_cursor_count_limit": 4096,
+ "projection_cursor_rejection_probe_limit": 4097,
+ "caller_main_table_count_limit": 4096,
+ "caller_foreign_key_row_count_limit": 4096,
+ "caller_inbound_foreign_key_policy": "reject_all_rebuild_mutated_parent_dependencies_before_entropy_v1",
+ "caller_inbound_foreign_key_parent_tables": [
+ "event_envelopes",
+ "event_envelope_tags",
+ "event_envelope_head",
+ "radroots_event_store_source_generation",
+ "radroots_event_store_source_rebuild_commit_barrier",
+ "radroots_event_store_source_rebuild_marker",
+ "radroots_event_store_source_state",
+ "radroots_event_store_write_lock",
+ "radroots_event_store_source_capacity_v1",
+ "radroots_event_store_event_coordinate",
+ "radroots_event_store_nip09_request",
+ "radroots_event_store_nip09_event_target",
+ "radroots_event_store_nip09_address_target",
+ "radroots_event_store_addressable_head_state",
+ "radroots_event_store_addressable_head_transition",
+ "radroots_event_store_addressable_feed_integrity_v1",
+ "radroots_event_store_food_availability_cursor",
+ "radroots_event_store_food_availability_projection",
+ "radroots_event_store_food_availability_image",
+ "radroots_event_store_food_availability_search_fts",
+ "radroots_event_store_food_availability_search_fts_config",
+ "radroots_event_store_food_availability_search_fts_content",
+ "radroots_event_store_food_availability_search_fts_data",
+ "radroots_event_store_food_availability_search_fts_docsize",
+ "radroots_event_store_food_availability_search_fts_idx",
+ "sqlite_sequence"
+ ],
+ "cold_repair_mode": "canonical_file_only_single_connection_lock_domain_probe_v1",
+ "immutable_raw_digest": {
+ "algorithm": "sha256_domain_nul_typed_fields_v1",
+ "domain_utf8": "radroots:event-store:immutable-raw-digest:v1",
+ "domain_terminator": "nul_byte",
+ "framing": {
+ "section": "S_then_N_then_u64be_length_then_utf8_name",
+ "row": "R",
+ "signed_i64": "I_then_i64be",
+ "boolean": "B_then_u8_0_or_1",
+ "optional": "O_then_presence_u8_then_nested_value_when_present",
+ "text": "T_then_u64be_length_then_utf8_bytes",
+ "blob": "X_then_u64be_length_then_bytes"
+ },
+ "output_bytes": 32,
+ "source_queries": [
+ {
+ "section": "event_envelopes",
+ "sql": "SELECT seq, event_id, pubkey, created_at, kind, tags_json, content, sig, raw_json, inserted_at_ms FROM event_envelopes ORDER BY seq",
+ "fields": [
+ {
+ "name": "seq",
+ "framing": "i64"
+ },
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "created_at",
+ "framing": "i64"
+ },
+ {
+ "name": "kind",
+ "framing": "i64"
+ },
+ {
+ "name": "tags_json",
+ "framing": "text"
+ },
+ {
+ "name": "content",
+ "framing": "text"
+ },
+ {
+ "name": "sig",
+ "framing": "text"
+ },
+ {
+ "name": "raw_json",
+ "framing": "text"
+ },
+ {
+ "name": "inserted_at_ms",
+ "framing": "i64"
+ }
+ ]
+ },
+ {
+ "section": "event_envelope_tags",
+ "sql": "SELECT event.seq, tag.event_id, tag.tag_index, tag.tag_name, tag.tag_value, tag.tag_json FROM event_envelope_tags AS tag JOIN event_envelopes AS event ON event.event_id = tag.event_id ORDER BY event.seq, tag.tag_index",
+ "fields": [
+ {
+ "name": "seq",
+ "framing": "i64"
+ },
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "tag_index",
+ "framing": "i64"
+ },
+ {
+ "name": "tag_name",
+ "framing": "text"
+ },
+ {
+ "name": "tag_value",
+ "framing": "optional_text"
+ },
+ {
+ "name": "tag_json",
+ "framing": "text"
+ }
+ ]
+ }
+ ]
+ },
+ "active_product_state_digest": {
+ "algorithm": "sha256_domain_nul_typed_fields_v1",
+ "domain_utf8": "radroots:event-store:active-product-state-digest:v1",
+ "domain_terminator": "nul_byte",
+ "framing": {
+ "section": "S_then_N_then_u64be_length_then_utf8_name",
+ "row": "R",
+ "signed_i64": "I_then_i64be",
+ "boolean": "B_then_u8_0_or_1",
+ "optional": "O_then_presence_u8_then_nested_value_when_present",
+ "text": "T_then_u64be_length_then_utf8_bytes",
+ "blob": "X_then_u64be_length_then_bytes"
+ },
+ "output_bytes": 32,
+ "components": [
+ "logical_current_classifications",
+ "raw_heads",
+ "active_addressable_head_state",
+ "active_nip09_facts",
+ "current_visibility",
+ "food_availability_rows",
+ "food_availability_images",
+ "logical_food_fts_rows",
+ "stable_food_cursor_metadata"
+ ],
+ "exclusions": [
+ "source_generation",
+ "absolute_transition_sequence",
+ "transition_history",
+ "rebuild_origin",
+ "rebuild_cause",
+ "operational_timestamps",
+ "generic_projection_cursors",
+ "caller_owned_state"
+ ],
+ "component_queries": [
+ {
+ "section": "envelope_classification",
+ "sql": "SELECT event_id, verification_status, contract_status, contract_id, event_class, projection_eligible FROM event_envelopes ORDER BY event_id",
+ "fields": [
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "verification_status",
+ "framing": "text"
+ },
+ {
+ "name": "contract_status",
+ "framing": "text"
+ },
+ {
+ "name": "contract_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "event_class",
+ "framing": "optional_text"
+ },
+ {
+ "name": "projection_eligible",
+ "framing": "boolean"
+ }
+ ]
+ },
+ {
+ "section": "tag_classification",
+ "sql": "SELECT event_id, tag_index, contract_semantic, contract_value_type, relay_indexed FROM event_envelope_tags ORDER BY event_id, tag_index",
+ "fields": [
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "tag_index",
+ "framing": "i64"
+ },
+ {
+ "name": "contract_semantic",
+ "framing": "optional_text"
+ },
+ {
+ "name": "contract_value_type",
+ "framing": "optional_text"
+ },
+ {
+ "name": "relay_indexed",
+ "framing": "boolean"
+ }
+ ]
+ },
+ {
+ "section": "raw_heads",
+ "sql": "SELECT coordinate_type, kind, pubkey, d_tag, event_id, created_at FROM event_envelope_head ORDER BY coordinate_type, kind, pubkey, d_tag",
+ "fields": [
+ {
+ "name": "coordinate_type",
+ "framing": "text"
+ },
+ {
+ "name": "kind",
+ "framing": "i64"
+ },
+ {
+ "name": "pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "d_tag",
+ "framing": "optional_text"
+ },
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "created_at",
+ "framing": "i64"
+ }
+ ]
+ },
+ {
+ "section": "event_coordinates",
+ "sql": "SELECT event_id, coordinate_type, kind, pubkey, created_at, admission_status, admission_code, contract_id, raw_d_tag, nip09_matchable, nip09_d_tag FROM radroots_event_store_event_coordinate WHERE source_generation = ? ORDER BY event_id",
+ "fields": [
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "coordinate_type",
+ "framing": "text"
+ },
+ {
+ "name": "kind",
+ "framing": "i64"
+ },
+ {
+ "name": "pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "created_at",
+ "framing": "i64"
+ },
+ {
+ "name": "admission_status",
+ "framing": "text"
+ },
+ {
+ "name": "admission_code",
+ "framing": "optional_text"
+ },
+ {
+ "name": "contract_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "raw_d_tag",
+ "framing": "text"
+ },
+ {
+ "name": "nip09_matchable",
+ "framing": "boolean"
+ },
+ {
+ "name": "nip09_d_tag",
+ "framing": "optional_text"
+ }
+ ]
+ },
+ {
+ "section": "nip09_requests",
+ "sql": "SELECT request_event_id, request_pubkey, request_created_at FROM radroots_event_store_nip09_request WHERE source_generation = ? ORDER BY request_event_id",
+ "fields": [
+ {
+ "name": "request_event_id",
+ "framing": "text"
+ },
+ {
+ "name": "request_pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "request_created_at",
+ "framing": "i64"
+ }
+ ]
+ },
+ {
+ "section": "nip09_event_targets",
+ "sql": "SELECT request_event_id, target_event_id, source_tag_index, source_tag_value FROM radroots_event_store_nip09_event_target WHERE source_generation = ? ORDER BY request_event_id, target_event_id, source_tag_index",
+ "fields": [
+ {
+ "name": "request_event_id",
+ "framing": "text"
+ },
+ {
+ "name": "target_event_id",
+ "framing": "text"
+ },
+ {
+ "name": "source_tag_index",
+ "framing": "i64"
+ },
+ {
+ "name": "source_tag_value",
+ "framing": "text"
+ }
+ ]
+ },
+ {
+ "section": "nip09_address_targets",
+ "sql": "SELECT request_event_id, target_kind, target_pubkey, target_d_tag, inclusive_cutoff, source_tag_index, source_tag_value, source_kind_text, source_pubkey_text, source_d_tag FROM radroots_event_store_nip09_address_target WHERE source_generation = ? ORDER BY request_event_id, target_kind, target_pubkey, target_d_tag, source_tag_index",
+ "fields": [
+ {
+ "name": "request_event_id",
+ "framing": "text"
+ },
+ {
+ "name": "target_kind",
+ "framing": "i64"
+ },
+ {
+ "name": "target_pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "target_d_tag",
+ "framing": "text"
+ },
+ {
+ "name": "inclusive_cutoff",
+ "framing": "i64"
+ },
+ {
+ "name": "source_tag_index",
+ "framing": "i64"
+ },
+ {
+ "name": "source_tag_value",
+ "framing": "text"
+ },
+ {
+ "name": "source_kind_text",
+ "framing": "text"
+ },
+ {
+ "name": "source_pubkey_text",
+ "framing": "text"
+ },
+ {
+ "name": "source_d_tag",
+ "framing": "text"
+ }
+ ]
+ },
+ {
+ "section": "addressable_heads",
+ "sql": "SELECT kind, pubkey, d_tag, raw_head_event_id, raw_head_created_at, admission_status, admission_code, contract_id, visibility, nip09_outcome, nip09_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff FROM radroots_event_store_addressable_head_state WHERE source_generation = ? ORDER BY kind, pubkey, d_tag",
+ "fields": [
+ {
+ "name": "kind",
+ "framing": "i64"
+ },
+ {
+ "name": "pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "d_tag",
+ "framing": "text"
+ },
+ {
+ "name": "raw_head_event_id",
+ "framing": "text"
+ },
+ {
+ "name": "raw_head_created_at",
+ "framing": "i64"
+ },
+ {
+ "name": "admission_status",
+ "framing": "text"
+ },
+ {
+ "name": "admission_code",
+ "framing": "optional_text"
+ },
+ {
+ "name": "contract_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "visibility",
+ "framing": "text"
+ },
+ {
+ "name": "nip09_outcome",
+ "framing": "optional_text"
+ },
+ {
+ "name": "nip09_reason",
+ "framing": "optional_text"
+ },
+ {
+ "name": "event_reference_request_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "address_reference_request_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "address_reference_cutoff",
+ "framing": "optional_i64"
+ }
+ ]
+ },
+ {
+ "section": "current_visibility",
+ "sql": "SELECT event_id, admission_status, contract_id, event_class, raw_d_tag, is_raw_head, raw_head_event_id, suppression_outcome, suppression_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff, current_visibility FROM radroots_event_store_current_visibility_v1 WHERE source_generation = ? ORDER BY event_id",
+ "fields": [
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "admission_status",
+ "framing": "text"
+ },
+ {
+ "name": "contract_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "event_class",
+ "framing": "text"
+ },
+ {
+ "name": "raw_d_tag",
+ "framing": "optional_text"
+ },
+ {
+ "name": "is_raw_head",
+ "framing": "boolean"
+ },
+ {
+ "name": "raw_head_event_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "suppression_outcome",
+ "framing": "optional_text"
+ },
+ {
+ "name": "suppression_reason",
+ "framing": "optional_text"
+ },
+ {
+ "name": "event_reference_request_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "address_reference_request_id",
+ "framing": "optional_text"
+ },
+ {
+ "name": "address_reference_cutoff",
+ "framing": "optional_i64"
+ },
+ {
+ "name": "current_visibility",
+ "framing": "text"
+ }
+ ]
+ },
+ {
+ "section": "food_projection",
+ "sql": "SELECT kind, pubkey, d_tag, event_id, created_at, contract_id, content, title, summary, published_at, location, price_amount, price_currency, price_unit, quantity_amount, quantity_unit, status, diagnostic_codes_json FROM radroots_event_store_food_availability_projection WHERE source_generation = ? ORDER BY pubkey, d_tag",
+ "fields": [
+ {
+ "name": "kind",
+ "framing": "i64"
+ },
+ {
+ "name": "pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "d_tag",
+ "framing": "text"
+ },
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "created_at",
+ "framing": "i64"
+ },
+ {
+ "name": "contract_id",
+ "framing": "text"
+ },
+ {
+ "name": "content",
+ "framing": "text"
+ },
+ {
+ "name": "title",
+ "framing": "text"
+ },
+ {
+ "name": "summary",
+ "framing": "text"
+ },
+ {
+ "name": "published_at",
+ "framing": "i64"
+ },
+ {
+ "name": "location",
+ "framing": "text"
+ },
+ {
+ "name": "price_amount",
+ "framing": "text"
+ },
+ {
+ "name": "price_currency",
+ "framing": "text"
+ },
+ {
+ "name": "price_unit",
+ "framing": "text"
+ },
+ {
+ "name": "quantity_amount",
+ "framing": "optional_text"
+ },
+ {
+ "name": "quantity_unit",
+ "framing": "optional_text"
+ },
+ {
+ "name": "status",
+ "framing": "text"
+ },
+ {
+ "name": "diagnostic_codes_json",
+ "framing": "text"
+ }
+ ]
+ },
+ {
+ "section": "food_images",
+ "sql": "SELECT pubkey, d_tag, image_index, raw_tag_json, url, width, height, blossom_sha256, qualifies, diagnostic_codes_json FROM radroots_event_store_food_availability_image WHERE source_generation = ? ORDER BY pubkey, d_tag, image_index",
+ "fields": [
+ {
+ "name": "pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "d_tag",
+ "framing": "text"
+ },
+ {
+ "name": "image_index",
+ "framing": "i64"
+ },
+ {
+ "name": "raw_tag_json",
+ "framing": "text"
+ },
+ {
+ "name": "url",
+ "framing": "optional_text"
+ },
+ {
+ "name": "width",
+ "framing": "optional_i64"
+ },
+ {
+ "name": "height",
+ "framing": "optional_i64"
+ },
+ {
+ "name": "blossom_sha256",
+ "framing": "optional_text"
+ },
+ {
+ "name": "qualifies",
+ "framing": "boolean"
+ },
+ {
+ "name": "diagnostic_codes_json",
+ "framing": "text"
+ }
+ ]
+ },
+ {
+ "section": "food_search",
+ "sql": "SELECT event_id, pubkey, d_tag, title, summary, content, location FROM radroots_event_store_food_availability_search_fts ORDER BY event_id",
+ "fields": [
+ {
+ "name": "event_id",
+ "framing": "text"
+ },
+ {
+ "name": "pubkey",
+ "framing": "text"
+ },
+ {
+ "name": "d_tag",
+ "framing": "text"
+ },
+ {
+ "name": "title",
+ "framing": "text"
+ },
+ {
+ "name": "summary",
+ "framing": "text"
+ },
+ {
+ "name": "content",
+ "framing": "text"
+ },
+ {
+ "name": "location",
+ "framing": "text"
+ }
+ ]
+ },
+ {
+ "section": "food_cursor",
+ "sql": "SELECT feed_version, projection_version, scope_fingerprint, hook_manifest_sha256, projected_row_count FROM radroots_event_store_food_availability_cursor WHERE singleton = 1",
+ "fields": [
+ {
+ "name": "feed_version",
+ "framing": "i64"
+ },
+ {
+ "name": "projection_version",
+ "framing": "i64"
+ },
+ {
+ "name": "scope_fingerprint",
+ "framing": "blob"
+ },
+ {
+ "name": "hook_manifest_sha256",
+ "framing": "text"
+ },
+ {
+ "name": "projected_row_count",
+ "framing": "i64"
+ }
+ ]
+ }
+ ]
+ },
+ "visibility_oracle": "pure_verified_raw_snapshot_direct_indexed_evidence_v1",
+ "scoped_integrity_mode": "event_store_owned_tables_and_indices_v1",
+ "scoped_integrity_tables": [
+ "event_envelopes",
+ "event_envelope_tags",
+ "event_envelope_head",
+ "radroots_event_store_source_generation",
+ "radroots_event_store_source_rebuild_commit_barrier",
+ "radroots_event_store_source_rebuild_marker",
+ "radroots_event_store_source_state",
+ "radroots_event_store_write_lock",
+ "radroots_event_store_source_capacity_v1",
+ "radroots_event_store_event_coordinate",
+ "radroots_event_store_nip09_request",
+ "radroots_event_store_nip09_event_target",
+ "radroots_event_store_nip09_address_target",
+ "radroots_event_store_addressable_head_state",
+ "radroots_event_store_addressable_head_transition",
+ "radroots_event_store_addressable_feed_integrity_v1",
+ "radroots_event_store_food_availability_cursor",
+ "radroots_event_store_food_availability_projection",
+ "radroots_event_store_food_availability_image"
+ ],
+ "sqlite_sequence_scope": "target_first_after_single_shared_sequence_scan_v1",
+ "stages": [
+ "after_marker_open",
+ "after_generation_rotation",
+ "after_core_replay",
+ "after_visibility_audit",
+ "after_food_reset_replay",
+ "after_food_audit",
+ "after_marker_close"
+ ],
+ "failpoints": [
+ "after_marker_open",
+ "after_generation_rotation",
+ "after_core_replay",
+ "after_visibility_audit",
+ "after_food_reset_replay",
+ "after_food_audit",
+ "after_marker_close"
+ ],
+ "preserved_authorities": [
+ "legacy_listing",
+ "trade",
+ "transport_observation",
+ "generic_projection_cursor",
+ "unrelated_caller_state_without_dependencies_on_rebuild_owned_tables"
+ ]
+ },
+ "entry_points": [
+ {
+ "role": "live_rebuild",
+ "rust_path": "radroots_event_store::RadrootsEventStore::rebuild_from_raw_v1"
+ },
+ {
+ "role": "cold_file_repair",
+ "rust_path": "radroots_event_store::RadrootsEventStore::repair_file_from_raw_v1"
+ },
+ {
+ "role": "projection_cursor_insert_preflight",
+ "rust_path": "radroots_event_store::nip09::reconciliation_v1::preflight_projection_cursor_insert_v1"
+ },
+ {
+ "role": "serialized_rebuild_runtime",
+ "rust_path": "radroots_event_store::nip09::reconciliation_v1::raw_source_rebuild::rebuild_from_raw_v1_on_pool"
+ },
+ {
+ "role": "independent_visibility_oracle",
+ "rust_path": "radroots_event_store::nip09::reconciliation_v1::visibility_oracle_v1::audit_current_visibility_from_raw_v1"
+ },
+ {
+ "role": "result_vector_executor",
+ "rust_path": "raw_source_rebuild_v1_result_vector"
+ }
+ ],
+ "source_files": [
+ {
+ "role": "workspace_manifest_authority",
+ "path": "Cargo.toml",
+ "byte_length": 10836,
+ "sha256": "285532dbb0894204843a832880f136ceac5ee312a3203ff951fb0551fac63ec4",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "workspace_lockfile_authority",
+ "path": "Cargo.lock",
+ "byte_length": 216965,
+ "sha256": "f26bf62f77e48914c89c15e689fdbc6799928e9603cab38f50c0c65a0c405edf",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nix_flake_app_export_authority",
+ "path": "flake.nix",
+ "byte_length": 1835,
+ "sha256": "0251b26040cf5338c12dc777a4deaadb8f63eb4e88bc05929dcec67db88ff2bf",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nix_input_lock_authority",
+ "path": "flake.lock",
+ "byte_length": 3031,
+ "sha256": "41b569739bfa0c488625326f4f0a874561601787951cdf7a3f171e60572fa20e",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nix_contract_app_routing_authority",
+ "path": "build/nix/apps.nix",
+ "byte_length": 2836,
+ "sha256": "41a185ac87379e24c1ede09c0f1aac820653dffc09f99cd803b145b44bed982c",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nix_contract_test_lane_authority",
+ "path": "build/nix/common.nix",
+ "byte_length": 11127,
+ "sha256": "b3340e1b4973e6a1e02899d164ca74842757f22b6b1a03f90461532fcd844df5",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nix_toolchain_routing_authority",
+ "path": "build/nix/toolchains.nix",
+ "byte_length": 178,
+ "sha256": "cd664be945e28bf6c25c7758182ff8d01e03248832dfc2c045c01b4f4aff960f",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "rust_toolchain_authority",
+ "path": "rust-toolchain.toml",
+ "byte_length": 132,
+ "sha256": "c33aa38292bab6513bf79ed2f69c1525b736dd738b15ca78af713b70b29265c9",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "xtask_manifest_authority",
+ "path": "tools/xtask/Cargo.toml",
+ "byte_length": 1097,
+ "sha256": "7e858f4f33913f986c565be2a31c41615ea0585c9e19572363ef5cae36cafdc9",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "event_store_dependency_feature_authority",
+ "path": "crates/event_store/Cargo.toml",
+ "byte_length": 1529,
+ "sha256": "4bddb3462a7543c9a7981ead5cf1027988fc381457432f4b04b0e9c43f6d51ca",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "event_store_error_surface",
+ "path": "crates/event_store/src/error.rs",
+ "byte_length": 24687,
+ "sha256": "404f3f91b1b4aed345faf23a2bfd8a59cdf475d5f411d416dd26418c71ea9a89",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "generated_descriptor_registration",
+ "path": "crates/event_store/src/generated.rs",
+ "byte_length": 188,
+ "sha256": "05328d38ebb6f827f6986b384fefb834948652dfd77fc29c9633d8a1a0d5947e",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "food_generated_descriptor_input",
+ "path": "crates/event_store/src/generated/food_availability_projection_manifest.rs",
+ "byte_length": 21437,
+ "sha256": "90908da53ab9572f45f5916ccc2652736b7ea26ba6dd202a4f69af1e651b564b",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nip09_generated_descriptor_input",
+ "path": "crates/event_store/src/generated/nip09_reconciliation_manifest.rs",
+ "byte_length": 586039,
+ "sha256": "406a760e9bed1e8fc89c8e7ae0976c7eff844de7427a3f473528c895439500b3",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "source_maintenance_generated_descriptor_input",
+ "path": "crates/event_store/src/generated/source_maintenance_manifest.rs",
+ "byte_length": 18723,
+ "sha256": "5f988f800425cf36d4327c828b30943c2f79c1fa577ce80730dc13383a1466b1",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "public_surface",
+ "path": "crates/event_store/src/lib.rs",
+ "byte_length": 4133,
+ "sha256": "7cc60495cd26d1f3d8147b1c6b39db83a170f934f74226a617263c0c13c25ada",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "migration_runtime_registry",
+ "path": "crates/event_store/src/migrations.rs",
+ "byte_length": 73585,
+ "sha256": "a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "model_registration",
+ "path": "crates/event_store/src/model.rs",
+ "byte_length": 33818,
+ "sha256": "66d0b7b8d9966084c76d85aa7f79e9ec0d68cde464ea8b0a327404e61eadd8ff",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "addressable_transition_feed_model",
+ "path": "crates/event_store/src/model/addressable_transition_feed_v1.rs",
+ "byte_length": 22314,
+ "sha256": "b1c6b0a68f34459f7e14bd63857596154c0aa3fd02dc6c1661d543bb681324a7",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "current_visibility_model",
+ "path": "crates/event_store/src/model/current_visibility_v1.rs",
+ "byte_length": 5691,
+ "sha256": "25ec92f45006e2f66f2e1c8b954a021334bb1595b849c4d8529f289d3f7aeb25",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "food_availability_projection_model",
+ "path": "crates/event_store/src/model/food_availability_projection_v1.rs",
+ "byte_length": 17493,
+ "sha256": "1e5ff9c05a81fda223ed1a27ff18a1b08bcdeaec9047a13fdd577390b3e0fdb9",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "ingest_reconciliation_model",
+ "path": "crates/event_store/src/model/ingest_reconciliation_v1.rs",
+ "byte_length": 1626,
+ "sha256": "47bf13b3fc0f8a913a660f7d655413de0f6b90568bc4acc510aa6bd741bab47b",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "rebuild_report_and_digest_models",
+ "path": "crates/event_store/src/model/raw_source_rebuild_v1.rs",
+ "byte_length": 2804,
+ "sha256": "a59459b5566f4450576fc5412e3c8ac0153954b653be376ccd925b19fc647345",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "reconciliation_model",
+ "path": "crates/event_store/src/model/reconciliation_v1.rs",
+ "byte_length": 11138,
+ "sha256": "8a26bc373035878ef9b41767ceea7b681896e17d88bedce118de1d622125e1d6",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nip09_module_registration",
+ "path": "crates/event_store/src/nip09.rs",
+ "byte_length": 34,
+ "sha256": "fbd8a3b36d7f36e7b0d301aee0847d42c3908659f066cafcae3e247d67a75845",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "reconciliation_runtime_registration",
+ "path": "crates/event_store/src/nip09/reconciliation_v1.rs",
+ "byte_length": 194622,
+ "sha256": "4c14df2bd3af7bfefb002917dc7549f6ada155be3a748acb9cd6d78199ee6f76",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "serialized_raw_source_rebuild",
+ "path": "crates/event_store/src/nip09/reconciliation_v1/raw_source_rebuild.rs",
+ "byte_length": 60973,
+ "sha256": "a8db92dfbfa420b545038502c2a04547bed41b76e283f09758faa248c597c781",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "nip09_result_vector_executor_input",
+ "path": "crates/event_store/src/nip09/reconciliation_v1/result_vector_executor.rs",
+ "byte_length": 18446,
+ "sha256": "ca2a2bf54062aa6ddf2e553fd624c7217a01ad56309487ce73fa58c47c06c208",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "independent_raw_visibility_oracle",
+ "path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "byte_length": 36998,
+ "sha256": "48b60aba869d804ad7b3b120d7479c7ff45dd3758502a90b4fbce312d9848a99",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "managed_v4_validation_and_scoped_integrity",
+ "path": "crates/event_store/src/schema.rs",
+ "byte_length": 153682,
+ "sha256": "df92fc509b44e40dae5a48d03ad9bf5cc556c4319a78215460ca26b899c610a2",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "source_capacity_rebuild_authority",
+ "path": "crates/event_store/src/source_maintenance_v1.rs",
+ "byte_length": 51756,
+ "sha256": "f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "public_rebuild_and_cold_repair_boundary",
+ "path": "crates/event_store/src/store.rs",
+ "byte_length": 402744,
+ "sha256": "56a84cc05208a335cbb6bad41b024c20ed77db5000fa69e684611e196ae461f4",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "addressable_transition_feed_storage",
+ "path": "crates/event_store/src/store/addressable_transition_feed_v1.rs",
+ "byte_length": 40253,
+ "sha256": "fe23424aa1e6b39f9aba2dfa4470652b26b4990f91204a2bdfe379c03da9b610",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "current_visibility_storage",
+ "path": "crates/event_store/src/store/current_visibility_v1.rs",
+ "byte_length": 15860,
+ "sha256": "8615086e674c30700305debcef11de5b3dbfe5aec735c0f58b4ac11caa518596",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "raw_source_rebuild_focused_tests",
+ "path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "byte_length": 103772,
+ "sha256": "383ca6f8aac6418d1d4460603d50746d224011c16367c2b86d8342818567ae2a",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "signed_food_digest_fixture",
+ "path": "crates/event_store/tests/fixtures/food_availability_projection.v1.json",
+ "byte_length": 103659,
+ "sha256": "fca2b71b47736ed04ed1e908823b65b3fc3cf0366cb162128369fe328295bb63",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "food_projection_reset_and_replay",
+ "path": "crates/event_store/src/store/food_availability_projection_v1.rs",
+ "byte_length": 50858,
+ "sha256": "adc8a3eb59f5bccb4c0d0ba4c5319dbf55cffb5e0c333db8235db63fd0df5f21",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "post_core_extension_capabilities",
+ "path": "crates/event_store/src/store/post_core_extension_capabilities.rs",
+ "byte_length": 1255,
+ "sha256": "cb434372156cb7ff31dac392d7095c2e5f44b128fae4e705516d6d000e2e2502",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "post_core_extension_dispatcher",
+ "path": "crates/event_store/src/store/post_core_extension_dispatcher.rs",
+ "byte_length": 576,
+ "sha256": "df62ee92e9f165502d5e533997a47f533129fd3cffab2d9b2012e2ed22405f48",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "post_core_extensions_v1",
+ "path": "crates/event_store/src/store/post_core_extensions_v1.rs",
+ "byte_length": 6935,
+ "sha256": "fb165704c64d982cf3be0a880c44985be6b375758451e94b2aaaf30881769f18",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "post_core_extensions_v2",
+ "path": "crates/event_store/src/store/post_core_extensions_v2.rs",
+ "byte_length": 294,
+ "sha256": "8dcbc503ed9ea6fb06ed9a2a83b0804d928f9590b5706de25a057ad72c0d38d2",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "post_core_storage_v1",
+ "path": "crates/event_store/src/store/post_core_storage_v1.rs",
+ "byte_length": 16871,
+ "sha256": "a6dca0884762cec3c32e460d17662ced9d0335f30e79b3d5fdb3461259d3ec19",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "post_core_storage_v2",
+ "path": "crates/event_store/src/store/post_core_storage_v2.rs",
+ "byte_length": 632,
+ "sha256": "4b672770f3c34bf887e4cc949c068cb0c87396cb4af8efb6e13d39aa4e0d973a",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "protocol_reconciliation_storage",
+ "path": "crates/event_store/src/store/protocol_reconciliation_v1.rs",
+ "byte_length": 30140,
+ "sha256": "210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "protocol_storage_boundary",
+ "path": "crates/event_store/src/store/protocol_storage_v1.rs",
+ "byte_length": 10975,
+ "sha256": "155c74d27eee5db1d6f0f844f9d319604eefbbf640f4b2371ac5d0e370816e50",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "event_store_package_readme",
+ "path": "crates/event_store/README",
+ "byte_length": 22209,
+ "sha256": "9e1cf2ec9ba58c2028d78eb33e6355fc2dff3507837b6e8640941dccd5608dc7",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "signed_nip09_reconciliation_fixture",
+ "path": "crates/event_store/tests/fixtures/nip09_reconciliation.v1.json",
+ "byte_length": 10405,
+ "sha256": "31cd9507734ff3308436881622a626b9782b75b548d9f5e159e4125621855b9c",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "transitive_food_predecessor_governance",
+ "path": "tools/xtask/src/contract/food_availability_projection.rs",
+ "byte_length": 194995,
+ "sha256": "02f8b70b3885267b09fd5241ec89bcf020d2975e1eb1c6c533656a036723395b",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "immutable_predecessor_governance",
+ "path": "tools/xtask/src/contract/source_maintenance.rs",
+ "byte_length": 123766,
+ "sha256": "f10962e0cc0fa44dc109d87b707f02be11fe6dad1113707eef820ff3c5ae97ee",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "transitive_nip09_predecessor_governance",
+ "path": "tools/xtask/src/contract/nip09_reconciliation.rs",
+ "byte_length": 850763,
+ "sha256": "852697eaaffcfe99391ffafd0c7c390c8eeb175377ac7e5cd5f490050b57ed58",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "raw_source_rebuild_governance",
+ "path": "tools/xtask/src/contract/raw_source_rebuild.rs",
+ "byte_length": 294540,
+ "sha256": "543c21131346381a833f9e063fd535efd0d0e192419aefa1f60e9b0f68475866",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "contract_command_authority",
+ "path": "tools/xtask/src/contract.rs",
+ "byte_length": 479703,
+ "sha256": "72fbd457b0cfdff1e30f07bf2452a0c71c23cef93bdaefffc114defa616d6342",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "xtask_dispatch_and_release_preflight",
+ "path": "tools/xtask/src/main.rs",
+ "byte_length": 15018,
+ "sha256": "9aab8db1186b776ba8dcac90cc46c29d96928b610850b084be0e3a25d3e4cb0f",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "release_breaking_change_authority",
+ "path": "contracts/releases/1.0.0-alpha.1.toml",
+ "byte_length": 19840,
+ "sha256": "946d90dacc9db522825898dbb5d9d424b020a22fe53b80ec15eb67c5f1d8cd2d",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "release_note_authority",
+ "path": "CHANGELOG.md",
+ "byte_length": 28939,
+ "sha256": "61b9d2a9050e4123bc5324aebf6e54393b9b1efdc2145a4a2cf6c009a4345b23",
+ "hash_algorithm": "sha256_bytes_v1"
+ }
+ ],
+ "public_api": {
+ "added_symbols": [
+ "RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1",
+ "RadrootsEventStoreCallerInboundForeignKeyV1",
+ "RadrootsEventStoreActiveProductStateDigestV1",
+ "RadrootsEventStoreImmutableRawDigestV1",
+ "RadrootsEventStoreRawSourceRebuildDriftV1",
+ "RadrootsEventStoreRawSourceRebuildReportV1"
+ ],
+ "methods": [
+ "RadrootsEventStore::rebuild_from_raw_v1",
+ "RadrootsEventStore::repair_file_from_raw_v1",
+ "RadrootsEventStoreRawSourceRebuildReportV1::prior_source_generation",
+ "RadrootsEventStoreRawSourceRebuildReportV1::new_source_generation",
+ "RadrootsEventStoreRawSourceRebuildReportV1::source_capacity",
+ "RadrootsEventStoreRawSourceRebuildReportV1::raw_high_water_seq",
+ "RadrootsEventStoreRawSourceRebuildReportV1::immutable_raw_digest",
+ "RadrootsEventStoreRawSourceRebuildReportV1::active_product_state_digest",
+ "RadrootsEventStoreImmutableRawDigestV1::as_bytes",
+ "RadrootsEventStoreActiveProductStateDigestV1::as_bytes",
+ "RadrootsEventStoreRawSourceRebuildDriftV1::code"
+ ],
+ "error_variants": [
+ "ProjectionCursorCapacityExceeded",
+ "RawSourceRepairDatabaseIdentityMismatch",
+ "RawSourceRepairCanonicalPathLockDomainMismatch",
+ "RawSourceRepairMainDatabaseCanonicalizationFailed",
+ "RawSourceRebuildCallerForeignKeyCapacityExceeded",
+ "RawSourceRebuildCallerInboundForeignKeyUnsupported",
+ "RawSourceRebuildCallerTableCapacityExceeded",
+ "RawSourceRebuildStateDrift",
+ "RawSourceRebuildTransactionRollbackFailed"
+ ],
+ "drift_kinds": [
+ {
+ "variant": "ManagedSchemaAuthority",
+ "code": "managed_schema_authority"
+ },
+ {
+ "variant": "ImmutableRawAuthority",
+ "code": "immutable_raw_authority"
+ },
+ {
+ "variant": "SourceGenerationLineage",
+ "code": "source_generation_lineage"
+ },
+ {
+ "variant": "AddressableTransitionAuthority",
+ "code": "addressable_transition_authority"
+ },
+ {
+ "variant": "DerivedProductStateAuthority",
+ "code": "derived_product_state_authority"
+ },
+ {
+ "variant": "RebuildPostcondition",
+ "code": "rebuild_postcondition"
+ }
+ ]
+ },
+ "result_vector": {
+ "canonical_path": "contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json",
+ "mirror_path": "crates/event_store/tests/fixtures/raw_source_rebuild.v1.json",
+ "byte_length": 26833,
+ "sha256": "c37a2bf3714f53ab04fae8c5c9dbe2ad4b3f5310efa51f46bd8b116660f1fe15",
+ "hash_algorithm": "sha256_bytes_v1",
+ "executor_id": "radroots_event_store.raw_source_rebuild_v1.result_vector_executor.v1",
+ "executor_path": "crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs",
+ "executor_test": "raw_source_rebuild_v1_result_vector",
+ "executor_byte_length": 25542,
+ "executor_sha256": "51647259efdd0d99689ef1db0defb139c8d1f60f2ead69b793ddb2733a28e832",
+ "executor_hash_algorithm": "sha256_bytes_v1"
+ }
+}
diff --git a/crates/event_store/contracts/raw_source_rebuild_v1.manifest.schema.json b/crates/event_store/contracts/raw_source_rebuild_v1.manifest.schema.json
@@ -0,0 +1,655 @@
+{
+ "$id": "https://radroots.org/contracts/event-store/raw-source-rebuild-v1-manifest.schema.json",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": false,
+ "properties": {
+ "authority_id": {
+ "const": "raw_source_rebuild_v1"
+ },
+ "contract_id": {
+ "const": "radroots_event_store.raw_source_rebuild_v1"
+ },
+ "entry_points": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "role": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "rust_path": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "role",
+ "rust_path"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "manifest_schema": {
+ "additionalProperties": false,
+ "properties": {
+ "byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "path": {
+ "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$",
+ "type": "string"
+ },
+ "sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ }
+ },
+ "required": [
+ "path",
+ "byte_length",
+ "sha256",
+ "hash_algorithm"
+ ],
+ "type": "object"
+ },
+ "migration_inventory": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "path": {
+ "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$",
+ "type": "string"
+ },
+ "sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ }
+ },
+ "required": [
+ "path",
+ "byte_length",
+ "sha256",
+ "hash_algorithm"
+ ],
+ "type": "object"
+ },
+ "maxItems": 8,
+ "minItems": 8,
+ "type": "array"
+ },
+ "predecessor": {
+ "additionalProperties": false,
+ "properties": {
+ "contract_id": {
+ "const": "radroots_event_store.source_maintenance_v1"
+ },
+ "manifest": {
+ "additionalProperties": false,
+ "properties": {
+ "byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "path": {
+ "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$",
+ "type": "string"
+ },
+ "sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ }
+ },
+ "required": [
+ "path",
+ "byte_length",
+ "sha256",
+ "hash_algorithm"
+ ],
+ "type": "object"
+ }
+ },
+ "required": [
+ "contract_id",
+ "manifest"
+ ],
+ "type": "object"
+ },
+ "public_api": {
+ "additionalProperties": false,
+ "properties": {
+ "added_symbols": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "drift_kinds": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "code": {
+ "pattern": "^[a-z][a-z0-9_]*$",
+ "type": "string"
+ },
+ "variant": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "variant",
+ "code"
+ ],
+ "type": "object"
+ },
+ "maxItems": 6,
+ "minItems": 6,
+ "type": "array"
+ },
+ "error_variants": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "methods": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ }
+ },
+ "required": [
+ "added_symbols",
+ "methods",
+ "error_variants",
+ "drift_kinds"
+ ],
+ "type": "object"
+ },
+ "result_vector": {
+ "additionalProperties": false,
+ "properties": {
+ "byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "canonical_path": {
+ "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$",
+ "type": "string"
+ },
+ "executor_byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "executor_hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "executor_id": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "executor_path": {
+ "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$",
+ "type": "string"
+ },
+ "executor_sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ },
+ "executor_test": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "mirror_path": {
+ "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$",
+ "type": "string"
+ },
+ "sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ }
+ },
+ "required": [
+ "canonical_path",
+ "mirror_path",
+ "byte_length",
+ "sha256",
+ "hash_algorithm",
+ "executor_id",
+ "executor_path",
+ "executor_test",
+ "executor_byte_length",
+ "executor_sha256",
+ "executor_hash_algorithm"
+ ],
+ "type": "object"
+ },
+ "runtime": {
+ "additionalProperties": false,
+ "properties": {
+ "active_product_state_digest": {
+ "additionalProperties": false,
+ "properties": {
+ "algorithm": {
+ "const": "sha256_domain_nul_typed_fields_v1"
+ },
+ "component_queries": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "fields": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "framing": {
+ "enum": [
+ "i64",
+ "boolean",
+ "optional_i64",
+ "text",
+ "optional_text",
+ "blob"
+ ]
+ },
+ "name": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "framing"
+ ],
+ "type": "object"
+ },
+ "minItems": 1,
+ "type": "array"
+ },
+ "section": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "sql": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "section",
+ "sql",
+ "fields"
+ ],
+ "type": "object"
+ },
+ "maxItems": 13,
+ "minItems": 13,
+ "type": "array"
+ },
+ "components": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "domain_terminator": {
+ "const": "nul_byte"
+ },
+ "domain_utf8": {
+ "const": "radroots:event-store:active-product-state-digest:v1"
+ },
+ "exclusions": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "framing": {
+ "additionalProperties": false,
+ "properties": {
+ "blob": {
+ "const": "X_then_u64be_length_then_bytes"
+ },
+ "boolean": {
+ "const": "B_then_u8_0_or_1"
+ },
+ "optional": {
+ "const": "O_then_presence_u8_then_nested_value_when_present"
+ },
+ "row": {
+ "const": "R"
+ },
+ "section": {
+ "const": "S_then_N_then_u64be_length_then_utf8_name"
+ },
+ "signed_i64": {
+ "const": "I_then_i64be"
+ },
+ "text": {
+ "const": "T_then_u64be_length_then_utf8_bytes"
+ }
+ },
+ "required": [
+ "section",
+ "row",
+ "signed_i64",
+ "boolean",
+ "optional",
+ "text",
+ "blob"
+ ],
+ "type": "object"
+ },
+ "output_bytes": {
+ "const": 32
+ }
+ },
+ "required": [
+ "algorithm",
+ "domain_utf8",
+ "domain_terminator",
+ "framing",
+ "output_bytes",
+ "components",
+ "exclusions",
+ "component_queries"
+ ],
+ "type": "object"
+ },
+ "caller_foreign_key_row_count_limit": {
+ "const": 4096
+ },
+ "caller_inbound_foreign_key_parent_tables": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "caller_inbound_foreign_key_policy": {
+ "const": "reject_all_rebuild_mutated_parent_dependencies_before_entropy_v1"
+ },
+ "caller_main_table_count_limit": {
+ "const": 4096
+ },
+ "cold_repair_mode": {
+ "const": "canonical_file_only_single_connection_lock_domain_probe_v1"
+ },
+ "event_contract_registry_version": {
+ "const": 7
+ },
+ "event_store_schema_version": {
+ "const": 4
+ },
+ "failpoints": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "immutable_raw_digest": {
+ "additionalProperties": false,
+ "properties": {
+ "algorithm": {
+ "const": "sha256_domain_nul_typed_fields_v1"
+ },
+ "domain_terminator": {
+ "const": "nul_byte"
+ },
+ "domain_utf8": {
+ "const": "radroots:event-store:immutable-raw-digest:v1"
+ },
+ "framing": {
+ "additionalProperties": false,
+ "properties": {
+ "blob": {
+ "const": "X_then_u64be_length_then_bytes"
+ },
+ "boolean": {
+ "const": "B_then_u8_0_or_1"
+ },
+ "optional": {
+ "const": "O_then_presence_u8_then_nested_value_when_present"
+ },
+ "row": {
+ "const": "R"
+ },
+ "section": {
+ "const": "S_then_N_then_u64be_length_then_utf8_name"
+ },
+ "signed_i64": {
+ "const": "I_then_i64be"
+ },
+ "text": {
+ "const": "T_then_u64be_length_then_utf8_bytes"
+ }
+ },
+ "required": [
+ "section",
+ "row",
+ "signed_i64",
+ "boolean",
+ "optional",
+ "text",
+ "blob"
+ ],
+ "type": "object"
+ },
+ "output_bytes": {
+ "const": 32
+ },
+ "source_queries": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "fields": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "framing": {
+ "enum": [
+ "i64",
+ "boolean",
+ "optional_i64",
+ "text",
+ "optional_text",
+ "blob"
+ ]
+ },
+ "name": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "framing"
+ ],
+ "type": "object"
+ },
+ "minItems": 1,
+ "type": "array"
+ },
+ "section": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "sql": {
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "section",
+ "sql",
+ "fields"
+ ],
+ "type": "object"
+ },
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array"
+ }
+ },
+ "required": [
+ "algorithm",
+ "domain_utf8",
+ "domain_terminator",
+ "framing",
+ "output_bytes",
+ "source_queries"
+ ],
+ "type": "object"
+ },
+ "preserved_authorities": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "projection_cursor_count_limit": {
+ "const": 4096
+ },
+ "projection_cursor_rejection_probe_limit": {
+ "const": 4097
+ },
+ "scoped_integrity_mode": {
+ "const": "event_store_owned_tables_and_indices_v1"
+ },
+ "scoped_integrity_tables": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "sqlite_sequence_scope": {
+ "const": "target_first_after_single_shared_sequence_scan_v1"
+ },
+ "stages": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": "array",
+ "uniqueItems": true
+ },
+ "transaction_mode": {
+ "const": "begin_immediate_v1"
+ },
+ "visibility_oracle": {
+ "const": "pure_verified_raw_snapshot_direct_indexed_evidence_v1"
+ }
+ },
+ "required": [
+ "event_store_schema_version",
+ "event_contract_registry_version",
+ "transaction_mode",
+ "projection_cursor_count_limit",
+ "projection_cursor_rejection_probe_limit",
+ "caller_main_table_count_limit",
+ "caller_foreign_key_row_count_limit",
+ "caller_inbound_foreign_key_policy",
+ "caller_inbound_foreign_key_parent_tables",
+ "cold_repair_mode",
+ "immutable_raw_digest",
+ "active_product_state_digest",
+ "visibility_oracle",
+ "scoped_integrity_mode",
+ "scoped_integrity_tables",
+ "sqlite_sequence_scope",
+ "stages",
+ "failpoints",
+ "preserved_authorities"
+ ],
+ "type": "object"
+ },
+ "schema_version": {
+ "const": 1
+ },
+ "source_files": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "path": {
+ "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$",
+ "type": "string"
+ },
+ "role": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ }
+ },
+ "required": [
+ "role",
+ "path",
+ "byte_length",
+ "sha256",
+ "hash_algorithm"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "schema_version",
+ "contract_id",
+ "authority_id",
+ "manifest_schema",
+ "predecessor",
+ "migration_inventory",
+ "runtime",
+ "entry_points",
+ "source_files",
+ "public_api",
+ "result_vector"
+ ],
+ "title": "Radroots event-store raw-source rebuild v1 manifest",
+ "type": "object"
+}
diff --git a/crates/event_store/contracts/raw_source_rebuild_v1.manifest.sha256 b/crates/event_store/contracts/raw_source_rebuild_v1.manifest.sha256
@@ -0,0 +1 @@
+b8737a9c5836517114e7df6c2194c46e3c200093e12c4e6297165d2b9dae56a1
diff --git a/crates/event_store/src/error.rs b/crates/event_store/src/error.rs
@@ -14,6 +14,8 @@ pub const RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1: u64 = 64 * 1024 *
pub const RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1: u64 = 32 * 1024 * 1024;
/// Maximum append-only source generations retained before fresh-store resync.
pub const RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1: u32 = 8;
+/// Maximum caller-owned generic projection cursor identities in one store.
+pub const RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1: u32 = 4_096;
/// Governed retained raw-source resource dimension.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -47,6 +49,86 @@ impl core::fmt::Display for RadrootsEventStoreSourceCapacityResourceV1 {
}
}
+/// Stable category for raw-source rebuild authority drift.
+///
+/// The category code is contractual. Error detail remains diagnostic context
+/// and must not be parsed by callers.
+#[non_exhaustive]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RadrootsEventStoreRawSourceRebuildDriftV1 {
+ /// The database is not the exact managed schema supported by repair.
+ ManagedSchemaAuthority,
+ /// Retained immutable raw events or tags are internally inconsistent.
+ ImmutableRawAuthority,
+ /// Retained source-generation history or baselines are inconsistent.
+ SourceGenerationLineage,
+ /// Addressable transition sequence authority is inconsistent.
+ AddressableTransitionAuthority,
+ /// Rebuilt visibility or projection state is inconsistent.
+ DerivedProductStateAuthority,
+ /// The rebuild could not establish its final atomic postconditions.
+ RebuildPostcondition,
+}
+
+impl RadrootsEventStoreRawSourceRebuildDriftV1 {
+ /// Stable machine-readable category code.
+ pub const fn code(self) -> &'static str {
+ match self {
+ Self::ManagedSchemaAuthority => "managed_schema_authority",
+ Self::ImmutableRawAuthority => "immutable_raw_authority",
+ Self::SourceGenerationLineage => "source_generation_lineage",
+ Self::AddressableTransitionAuthority => "addressable_transition_authority",
+ Self::DerivedProductStateAuthority => "derived_product_state_authority",
+ Self::RebuildPostcondition => "rebuild_postcondition",
+ }
+ }
+}
+
+impl core::fmt::Display for RadrootsEventStoreRawSourceRebuildDriftV1 {
+ fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ formatter.write_str(self.code())
+ }
+}
+
+/// Caller-owned foreign-key dependency that makes raw-source rebuild unsafe.
+#[non_exhaustive]
+#[derive(Debug, PartialEq, Eq)]
+pub struct RadrootsEventStoreCallerInboundForeignKeyV1 {
+ pub child_table: String,
+ pub foreign_key_id: i64,
+ pub foreign_key_sequence: i64,
+ pub child_column: String,
+ pub parent_table: String,
+ pub parent_column: Option<String>,
+ pub on_update: String,
+ pub on_delete: String,
+ pub match_clause: String,
+}
+
+impl core::fmt::Display for RadrootsEventStoreCallerInboundForeignKeyV1 {
+ fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(
+ formatter,
+ "{}:{} on `{}` (`{}` -> `{}`.",
+ self.foreign_key_id,
+ self.foreign_key_sequence,
+ self.child_table,
+ self.child_column,
+ self.parent_table,
+ )?;
+ match self.parent_column.as_deref() {
+ Some(parent_column) => write!(formatter, "`{parent_column}`")?,
+ None => formatter.write_str("<implicit primary key>")?,
+ }
+ write!(
+ formatter,
+ ", on update {}, on delete {}, match {})",
+ self.on_update, self.on_delete, self.match_clause,
+ )
+ }
+}
+
+#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum RadrootsEventStoreError {
#[error("sqlx error: {0}")]
@@ -120,6 +202,20 @@ pub enum RadrootsEventStoreError {
)]
UnsafeInMemoryPoolConnectionCount { actual: u32 },
#[error(
+ "raw-source repair SQLite main database identity mismatch: expected `{expected}`, found `{actual}`"
+ )]
+ RawSourceRepairDatabaseIdentityMismatch { expected: String, actual: String },
+ #[error(
+ "raw-source repair canonical path `{canonical_path}` does not share the validated SQLite main lock domain"
+ )]
+ RawSourceRepairCanonicalPathLockDomainMismatch { canonical_path: String },
+ #[error("raw-source repair could not canonicalize SQLite main database `{filename}`: {source}")]
+ RawSourceRepairMainDatabaseCanonicalizationFailed {
+ filename: String,
+ #[source]
+ source: std::io::Error,
+ },
+ #[error(
"event-store pool backing mismatch: file_backed={file_backed}, configured filename `{filename}`"
)]
SqlitePoolBackingMismatch { file_backed: bool, filename: String },
@@ -127,9 +223,7 @@ pub enum RadrootsEventStoreError {
SqliteMainDatabaseUnavailable,
#[error("event-store SQLite main database must use UTF-8 encoding; reported `{actual}`")]
SqliteMainDatabaseEncodingNotUtf8 { actual: String },
- #[error(
- "event-store SQLite file connection did not enter WAL journal mode; reported `{actual}`"
- )]
+ #[error("event-store SQLite file connection must use WAL journal mode; reported `{actual}`")]
SqliteFileJournalModeNotWal { actual: String },
#[error(
"temporary schema object `{name}` ({object_type}, table `{table_name}`) collides with event-store authority"
@@ -228,6 +322,17 @@ pub enum RadrootsEventStoreError {
primary: Box<RadrootsEventStoreError>,
rollback: sqlx::Error,
},
+ #[error("event-store raw-source rebuild authority is inconsistent ({kind}): {detail}")]
+ RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1,
+ detail: String,
+ },
+ #[error("raw-source rebuild failed: {primary}; transaction rollback also failed: {rollback}")]
+ RawSourceRebuildTransactionRollbackFailed {
+ #[source]
+ primary: Box<RadrootsEventStoreError>,
+ rollback: sqlx::Error,
+ },
#[error("event-store source generation entropy is unavailable")]
SourceGenerationEntropyUnavailable,
#[error(
@@ -291,6 +396,20 @@ pub enum RadrootsEventStoreError {
parent: String,
foreign_key_index: i64,
},
+ #[error(
+ "event-store raw-source rebuild caller main-table inventory exceeds bounded preflight capacity: observed at least {observed_at_least}, limit {limit}"
+ )]
+ RawSourceRebuildCallerTableCapacityExceeded { observed_at_least: u64, limit: u64 },
+ #[error(
+ "event-store raw-source rebuild caller foreign-key inventory exceeds bounded preflight capacity: observed at least {observed_at_least} rows, limit {limit}"
+ )]
+ RawSourceRebuildCallerForeignKeyCapacityExceeded { observed_at_least: u64, limit: u64 },
+ #[error(
+ "event-store raw-source rebuild does not support caller-owned foreign key {dependency}"
+ )]
+ RawSourceRebuildCallerInboundForeignKeyUnsupported {
+ dependency: Box<RadrootsEventStoreCallerInboundForeignKeyV1>,
+ },
#[error("invalid stored enum value `{value}` for {field}")]
InvalidStoredEnum { field: &'static str, value: String },
#[error("invalid stored boolean value `{value}` for {field}; expected 0 or 1")]
@@ -349,6 +468,10 @@ pub enum RadrootsEventStoreError {
high_water: i64,
},
#[error(
+ "event-store generic projection cursor capacity exceeded: current {current}, limit {limit}"
+ )]
+ ProjectionCursorCapacityExceeded { current: u32, limit: u32 },
+ #[error(
"projection `{projection_id}` version {projection_version} is already current for the active source generation"
)]
ProjectionRebuildNotRequired {
diff --git a/crates/event_store/src/generated.rs b/crates/event_store/src/generated.rs
@@ -1,3 +1,4 @@
pub(crate) mod food_availability_projection_manifest;
pub(crate) mod nip09_reconciliation_manifest;
+pub(crate) mod raw_source_rebuild_manifest;
pub(crate) mod source_maintenance_manifest;
diff --git a/crates/event_store/src/generated/raw_source_rebuild_manifest.rs b/crates/event_store/src/generated/raw_source_rebuild_manifest.rs
@@ -0,0 +1,18 @@
+// @generated by `cargo xtask contract raw-source-rebuild-manifest --write`; do not edit.
+#![allow(dead_code)]
+
+pub(crate) const RAW_SOURCE_REBUILD_MANIFEST_JSON: &str = "{\n \"schema_version\": 1,\n \"contract_id\": \"radroots_event_store.raw_source_rebuild_v1\",\n \"authority_id\": \"raw_source_rebuild_v1\",\n \"manifest_schema\": {\n \"path\": \"crates/event_store/contracts/raw_source_rebuild_v1.manifest.schema.json\",\n \"byte_length\": 17896,\n \"sha256\": \"f9d210967e54b66f39c8bb965d97b2001a0ebc0927e7c2c14edb8e474bfda695\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"predecessor\": {\n \"contract_id\": \"radroots_event_store.source_maintenance_v1\",\n \"manifest\": {\n \"path\": \"crates/event_store/contracts/source_maintenance_v1.manifest.json\",\n \"byte_length\": 14216,\n \"sha256\": \"e8911e6e5710278969cbd15557a5b856b1575dfd11a655711403598370b41221\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n },\n \"migration_inventory\": [\n {\n \"path\": \"crates/event_store/migrations/0001_event_store.down.sql\",\n \"byte_length\": 522,\n \"sha256\": \"fa84d587f657f601947eaeb9cd239c962a48f6fcdce723588476e8d22f3c1f53\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"path\": \"crates/event_store/migrations/0001_event_store.up.sql\",\n \"byte_length\": 10712,\n \"sha256\": \"4c03906a1cffd418a48d40907aa9a1ca51bb41766cff7250c4dfc7c2fd6eddde\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"path\": \"crates/event_store/migrations/0002_nip09.down.sql\",\n \"byte_length\": 4807,\n \"sha256\": \"c51a099d9501f1e692c13d2226296a68ed9e6bfa5e8e46b2f12c6574dbe59e31\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"path\": \"crates/event_store/migrations/0002_nip09.up.sql\",\n \"byte_length\": 81614,\n \"sha256\": \"0c1730ff36eaebd285f9c0c94b9b7346af60266afa55c24a18e30446d369581a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"path\": \"crates/event_store/migrations/0003_food_availability_projection.down.sql\",\n \"byte_length\": 1755,\n \"sha256\": \"29d663320109d9dd0df6a00b6a53d8d988438d01f7a66960a9d4ba3482ffffb8\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"path\": \"crates/event_store/migrations/0003_food_availability_projection.up.sql\",\n \"byte_length\": 23683,\n \"sha256\": \"4e7edfb981b25f76055efc7802ec30b4034eeae9b9c0809ea4ea7c574678748a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.down.sql\",\n \"byte_length\": 5172,\n \"sha256\": \"fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.up.sql\",\n \"byte_length\": 19841,\n \"sha256\": \"425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"runtime\": {\n \"event_store_schema_version\": 4,\n \"event_contract_registry_version\": 7,\n \"transaction_mode\": \"begin_immediate_v1\",\n \"projection_cursor_count_limit\": 4096,\n \"projection_cursor_rejection_probe_limit\": 4097,\n \"caller_main_table_count_limit\": 4096,\n \"caller_foreign_key_row_count_limit\": 4096,\n \"caller_inbound_foreign_key_policy\": \"reject_all_rebuild_mutated_parent_dependencies_before_entropy_v1\",\n \"caller_inbound_foreign_key_parent_tables\": [\n \"event_envelopes\",\n \"event_envelope_tags\",\n \"event_envelope_head\",\n \"radroots_event_store_source_generation\",\n \"radroots_event_store_source_rebuild_commit_barrier\",\n \"radroots_event_store_source_rebuild_marker\",\n \"radroots_event_store_source_state\",\n \"radroots_event_store_write_lock\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_event_coordinate\",\n \"radroots_event_store_nip09_request\",\n \"radroots_event_store_nip09_event_target\",\n \"radroots_event_store_nip09_address_target\",\n \"radroots_event_store_addressable_head_state\",\n \"radroots_event_store_addressable_head_transition\",\n \"radroots_event_store_addressable_feed_integrity_v1\",\n \"radroots_event_store_food_availability_cursor\",\n \"radroots_event_store_food_availability_projection\",\n \"radroots_event_store_food_availability_image\",\n \"radroots_event_store_food_availability_search_fts\",\n \"radroots_event_store_food_availability_search_fts_config\",\n \"radroots_event_store_food_availability_search_fts_content\",\n \"radroots_event_store_food_availability_search_fts_data\",\n \"radroots_event_store_food_availability_search_fts_docsize\",\n \"radroots_event_store_food_availability_search_fts_idx\",\n \"sqlite_sequence\"\n ],\n \"cold_repair_mode\": \"canonical_file_only_single_connection_lock_domain_probe_v1\",\n \"immutable_raw_digest\": {\n \"algorithm\": \"sha256_domain_nul_typed_fields_v1\",\n \"domain_utf8\": \"radroots:event-store:immutable-raw-digest:v1\",\n \"domain_terminator\": \"nul_byte\",\n \"framing\": {\n \"section\": \"S_then_N_then_u64be_length_then_utf8_name\",\n \"row\": \"R\",\n \"signed_i64\": \"I_then_i64be\",\n \"boolean\": \"B_then_u8_0_or_1\",\n \"optional\": \"O_then_presence_u8_then_nested_value_when_present\",\n \"text\": \"T_then_u64be_length_then_utf8_bytes\",\n \"blob\": \"X_then_u64be_length_then_bytes\"\n },\n \"output_bytes\": 32,\n \"source_queries\": [\n {\n \"section\": \"event_envelopes\",\n \"sql\": \"SELECT seq, event_id, pubkey, created_at, kind, tags_json, content, sig, raw_json, inserted_at_ms FROM event_envelopes ORDER BY seq\",\n \"fields\": [\n {\n \"name\": \"seq\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"created_at\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"kind\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"tags_json\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"content\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"sig\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"raw_json\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"inserted_at_ms\",\n \"framing\": \"i64\"\n }\n ]\n },\n {\n \"section\": \"event_envelope_tags\",\n \"sql\": \"SELECT event.seq, tag.event_id, tag.tag_index, tag.tag_name, tag.tag_value, tag.tag_json FROM event_envelope_tags AS tag JOIN event_envelopes AS event ON event.event_id = tag.event_id ORDER BY event.seq, tag.tag_index\",\n \"fields\": [\n {\n \"name\": \"seq\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"tag_index\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"tag_name\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"tag_value\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"tag_json\",\n \"framing\": \"text\"\n }\n ]\n }\n ]\n },\n \"active_product_state_digest\": {\n \"algorithm\": \"sha256_domain_nul_typed_fields_v1\",\n \"domain_utf8\": \"radroots:event-store:active-product-state-digest:v1\",\n \"domain_terminator\": \"nul_byte\",\n \"framing\": {\n \"section\": \"S_then_N_then_u64be_length_then_utf8_name\",\n \"row\": \"R\",\n \"signed_i64\": \"I_then_i64be\",\n \"boolean\": \"B_then_u8_0_or_1\",\n \"optional\": \"O_then_presence_u8_then_nested_value_when_present\",\n \"text\": \"T_then_u64be_length_then_utf8_bytes\",\n \"blob\": \"X_then_u64be_length_then_bytes\"\n },\n \"output_bytes\": 32,\n \"components\": [\n \"logical_current_classifications\",\n \"raw_heads\",\n \"active_addressable_head_state\",\n \"active_nip09_facts\",\n \"current_visibility\",\n \"food_availability_rows\",\n \"food_availability_images\",\n \"logical_food_fts_rows\",\n \"stable_food_cursor_metadata\"\n ],\n \"exclusions\": [\n \"source_generation\",\n \"absolute_transition_sequence\",\n \"transition_history\",\n \"rebuild_origin\",\n \"rebuild_cause\",\n \"operational_timestamps\",\n \"generic_projection_cursors\",\n \"caller_owned_state\"\n ],\n \"component_queries\": [\n {\n \"section\": \"envelope_classification\",\n \"sql\": \"SELECT event_id, verification_status, contract_status, contract_id, event_class, projection_eligible FROM event_envelopes ORDER BY event_id\",\n \"fields\": [\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"verification_status\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"contract_status\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"contract_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"event_class\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"projection_eligible\",\n \"framing\": \"boolean\"\n }\n ]\n },\n {\n \"section\": \"tag_classification\",\n \"sql\": \"SELECT event_id, tag_index, contract_semantic, contract_value_type, relay_indexed FROM event_envelope_tags ORDER BY event_id, tag_index\",\n \"fields\": [\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"tag_index\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"contract_semantic\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"contract_value_type\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"relay_indexed\",\n \"framing\": \"boolean\"\n }\n ]\n },\n {\n \"section\": \"raw_heads\",\n \"sql\": \"SELECT coordinate_type, kind, pubkey, d_tag, event_id, created_at FROM event_envelope_head ORDER BY coordinate_type, kind, pubkey, d_tag\",\n \"fields\": [\n {\n \"name\": \"coordinate_type\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"kind\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"d_tag\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"created_at\",\n \"framing\": \"i64\"\n }\n ]\n },\n {\n \"section\": \"event_coordinates\",\n \"sql\": \"SELECT event_id, coordinate_type, kind, pubkey, created_at, admission_status, admission_code, contract_id, raw_d_tag, nip09_matchable, nip09_d_tag FROM radroots_event_store_event_coordinate WHERE source_generation = ? ORDER BY event_id\",\n \"fields\": [\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"coordinate_type\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"kind\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"created_at\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"admission_status\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"admission_code\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"contract_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"raw_d_tag\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"nip09_matchable\",\n \"framing\": \"boolean\"\n },\n {\n \"name\": \"nip09_d_tag\",\n \"framing\": \"optional_text\"\n }\n ]\n },\n {\n \"section\": \"nip09_requests\",\n \"sql\": \"SELECT request_event_id, request_pubkey, request_created_at FROM radroots_event_store_nip09_request WHERE source_generation = ? ORDER BY request_event_id\",\n \"fields\": [\n {\n \"name\": \"request_event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"request_pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"request_created_at\",\n \"framing\": \"i64\"\n }\n ]\n },\n {\n \"section\": \"nip09_event_targets\",\n \"sql\": \"SELECT request_event_id, target_event_id, source_tag_index, source_tag_value FROM radroots_event_store_nip09_event_target WHERE source_generation = ? ORDER BY request_event_id, target_event_id, source_tag_index\",\n \"fields\": [\n {\n \"name\": \"request_event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"target_event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"source_tag_index\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"source_tag_value\",\n \"framing\": \"text\"\n }\n ]\n },\n {\n \"section\": \"nip09_address_targets\",\n \"sql\": \"SELECT request_event_id, target_kind, target_pubkey, target_d_tag, inclusive_cutoff, source_tag_index, source_tag_value, source_kind_text, source_pubkey_text, source_d_tag FROM radroots_event_store_nip09_address_target WHERE source_generation = ? ORDER BY request_event_id, target_kind, target_pubkey, target_d_tag, source_tag_index\",\n \"fields\": [\n {\n \"name\": \"request_event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"target_kind\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"target_pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"target_d_tag\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"inclusive_cutoff\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"source_tag_index\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"source_tag_value\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"source_kind_text\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"source_pubkey_text\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"source_d_tag\",\n \"framing\": \"text\"\n }\n ]\n },\n {\n \"section\": \"addressable_heads\",\n \"sql\": \"SELECT kind, pubkey, d_tag, raw_head_event_id, raw_head_created_at, admission_status, admission_code, contract_id, visibility, nip09_outcome, nip09_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff FROM radroots_event_store_addressable_head_state WHERE source_generation = ? ORDER BY kind, pubkey, d_tag\",\n \"fields\": [\n {\n \"name\": \"kind\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"d_tag\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"raw_head_event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"raw_head_created_at\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"admission_status\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"admission_code\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"contract_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"visibility\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"nip09_outcome\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"nip09_reason\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"event_reference_request_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"address_reference_request_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"address_reference_cutoff\",\n \"framing\": \"optional_i64\"\n }\n ]\n },\n {\n \"section\": \"current_visibility\",\n \"sql\": \"SELECT event_id, admission_status, contract_id, event_class, raw_d_tag, is_raw_head, raw_head_event_id, suppression_outcome, suppression_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff, current_visibility FROM radroots_event_store_current_visibility_v1 WHERE source_generation = ? ORDER BY event_id\",\n \"fields\": [\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"admission_status\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"contract_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"event_class\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"raw_d_tag\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"is_raw_head\",\n \"framing\": \"boolean\"\n },\n {\n \"name\": \"raw_head_event_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"suppression_outcome\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"suppression_reason\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"event_reference_request_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"address_reference_request_id\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"address_reference_cutoff\",\n \"framing\": \"optional_i64\"\n },\n {\n \"name\": \"current_visibility\",\n \"framing\": \"text\"\n }\n ]\n },\n {\n \"section\": \"food_projection\",\n \"sql\": \"SELECT kind, pubkey, d_tag, event_id, created_at, contract_id, content, title, summary, published_at, location, price_amount, price_currency, price_unit, quantity_amount, quantity_unit, status, diagnostic_codes_json FROM radroots_event_store_food_availability_projection WHERE source_generation = ? ORDER BY pubkey, d_tag\",\n \"fields\": [\n {\n \"name\": \"kind\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"d_tag\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"created_at\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"contract_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"content\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"title\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"summary\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"published_at\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"location\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"price_amount\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"price_currency\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"price_unit\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"quantity_amount\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"quantity_unit\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"status\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"diagnostic_codes_json\",\n \"framing\": \"text\"\n }\n ]\n },\n {\n \"section\": \"food_images\",\n \"sql\": \"SELECT pubkey, d_tag, image_index, raw_tag_json, url, width, height, blossom_sha256, qualifies, diagnostic_codes_json FROM radroots_event_store_food_availability_image WHERE source_generation = ? ORDER BY pubkey, d_tag, image_index\",\n \"fields\": [\n {\n \"name\": \"pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"d_tag\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"image_index\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"raw_tag_json\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"url\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"width\",\n \"framing\": \"optional_i64\"\n },\n {\n \"name\": \"height\",\n \"framing\": \"optional_i64\"\n },\n {\n \"name\": \"blossom_sha256\",\n \"framing\": \"optional_text\"\n },\n {\n \"name\": \"qualifies\",\n \"framing\": \"boolean\"\n },\n {\n \"name\": \"diagnostic_codes_json\",\n \"framing\": \"text\"\n }\n ]\n },\n {\n \"section\": \"food_search\",\n \"sql\": \"SELECT event_id, pubkey, d_tag, title, summary, content, location FROM radroots_event_store_food_availability_search_fts ORDER BY event_id\",\n \"fields\": [\n {\n \"name\": \"event_id\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"pubkey\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"d_tag\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"title\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"summary\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"content\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"location\",\n \"framing\": \"text\"\n }\n ]\n },\n {\n \"section\": \"food_cursor\",\n \"sql\": \"SELECT feed_version, projection_version, scope_fingerprint, hook_manifest_sha256, projected_row_count FROM radroots_event_store_food_availability_cursor WHERE singleton = 1\",\n \"fields\": [\n {\n \"name\": \"feed_version\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"projection_version\",\n \"framing\": \"i64\"\n },\n {\n \"name\": \"scope_fingerprint\",\n \"framing\": \"blob\"\n },\n {\n \"name\": \"hook_manifest_sha256\",\n \"framing\": \"text\"\n },\n {\n \"name\": \"projected_row_count\",\n \"framing\": \"i64\"\n }\n ]\n }\n ]\n },\n \"visibility_oracle\": \"pure_verified_raw_snapshot_direct_indexed_evidence_v1\",\n \"scoped_integrity_mode\": \"event_store_owned_tables_and_indices_v1\",\n \"scoped_integrity_tables\": [\n \"event_envelopes\",\n \"event_envelope_tags\",\n \"event_envelope_head\",\n \"radroots_event_store_source_generation\",\n \"radroots_event_store_source_rebuild_commit_barrier\",\n \"radroots_event_store_source_rebuild_marker\",\n \"radroots_event_store_source_state\",\n \"radroots_event_store_write_lock\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_event_coordinate\",\n \"radroots_event_store_nip09_request\",\n \"radroots_event_store_nip09_event_target\",\n \"radroots_event_store_nip09_address_target\",\n \"radroots_event_store_addressable_head_state\",\n \"radroots_event_store_addressable_head_transition\",\n \"radroots_event_store_addressable_feed_integrity_v1\",\n \"radroots_event_store_food_availability_cursor\",\n \"radroots_event_store_food_availability_projection\",\n \"radroots_event_store_food_availability_image\"\n ],\n \"sqlite_sequence_scope\": \"target_first_after_single_shared_sequence_scan_v1\",\n \"stages\": [\n \"after_marker_open\",\n \"after_generation_rotation\",\n \"after_core_replay\",\n \"after_visibility_audit\",\n \"after_food_reset_replay\",\n \"after_food_audit\",\n \"after_marker_close\"\n ],\n \"failpoints\": [\n \"after_marker_open\",\n \"after_generation_rotation\",\n \"after_core_replay\",\n \"after_visibility_audit\",\n \"after_food_reset_replay\",\n \"after_food_audit\",\n \"after_marker_close\"\n ],\n \"preserved_authorities\": [\n \"legacy_listing\",\n \"trade\",\n \"transport_observation\",\n \"generic_projection_cursor\",\n \"unrelated_caller_state_without_dependencies_on_rebuild_owned_tables\"\n ]\n },\n \"entry_points\": [\n {\n \"role\": \"live_rebuild\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::rebuild_from_raw_v1\"\n },\n {\n \"role\": \"cold_file_repair\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::repair_file_from_raw_v1\"\n },\n {\n \"role\": \"projection_cursor_insert_preflight\",\n \"rust_path\": \"radroots_event_store::nip09::reconciliation_v1::preflight_projection_cursor_insert_v1\"\n },\n {\n \"role\": \"serialized_rebuild_runtime\",\n \"rust_path\": \"radroots_event_store::nip09::reconciliation_v1::raw_source_rebuild::rebuild_from_raw_v1_on_pool\"\n },\n {\n \"role\": \"independent_visibility_oracle\",\n \"rust_path\": \"radroots_event_store::nip09::reconciliation_v1::visibility_oracle_v1::audit_current_visibility_from_raw_v1\"\n },\n {\n \"role\": \"result_vector_executor\",\n \"rust_path\": \"raw_source_rebuild_v1_result_vector\"\n }\n ],\n \"source_files\": [\n {\n \"role\": \"workspace_manifest_authority\",\n \"path\": \"Cargo.toml\",\n \"byte_length\": 10836,\n \"sha256\": \"285532dbb0894204843a832880f136ceac5ee312a3203ff951fb0551fac63ec4\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"workspace_lockfile_authority\",\n \"path\": \"Cargo.lock\",\n \"byte_length\": 216965,\n \"sha256\": \"f26bf62f77e48914c89c15e689fdbc6799928e9603cab38f50c0c65a0c405edf\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nix_flake_app_export_authority\",\n \"path\": \"flake.nix\",\n \"byte_length\": 1835,\n \"sha256\": \"0251b26040cf5338c12dc777a4deaadb8f63eb4e88bc05929dcec67db88ff2bf\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nix_input_lock_authority\",\n \"path\": \"flake.lock\",\n \"byte_length\": 3031,\n \"sha256\": \"41b569739bfa0c488625326f4f0a874561601787951cdf7a3f171e60572fa20e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nix_contract_app_routing_authority\",\n \"path\": \"build/nix/apps.nix\",\n \"byte_length\": 2836,\n \"sha256\": \"41a185ac87379e24c1ede09c0f1aac820653dffc09f99cd803b145b44bed982c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nix_contract_test_lane_authority\",\n \"path\": \"build/nix/common.nix\",\n \"byte_length\": 11127,\n \"sha256\": \"b3340e1b4973e6a1e02899d164ca74842757f22b6b1a03f90461532fcd844df5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nix_toolchain_routing_authority\",\n \"path\": \"build/nix/toolchains.nix\",\n \"byte_length\": 178,\n \"sha256\": \"cd664be945e28bf6c25c7758182ff8d01e03248832dfc2c045c01b4f4aff960f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"rust_toolchain_authority\",\n \"path\": \"rust-toolchain.toml\",\n \"byte_length\": 132,\n \"sha256\": \"c33aa38292bab6513bf79ed2f69c1525b736dd738b15ca78af713b70b29265c9\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_manifest_authority\",\n \"path\": \"tools/xtask/Cargo.toml\",\n \"byte_length\": 1097,\n \"sha256\": \"7e858f4f33913f986c565be2a31c41615ea0585c9e19572363ef5cae36cafdc9\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_dependency_feature_authority\",\n \"path\": \"crates/event_store/Cargo.toml\",\n \"byte_length\": 1529,\n \"sha256\": \"4bddb3462a7543c9a7981ead5cf1027988fc381457432f4b04b0e9c43f6d51ca\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_error_surface\",\n \"path\": \"crates/event_store/src/error.rs\",\n \"byte_length\": 24687,\n \"sha256\": \"404f3f91b1b4aed345faf23a2bfd8a59cdf475d5f411d416dd26418c71ea9a89\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"generated_descriptor_registration\",\n \"path\": \"crates/event_store/src/generated.rs\",\n \"byte_length\": 188,\n \"sha256\": \"05328d38ebb6f827f6986b384fefb834948652dfd77fc29c9633d8a1a0d5947e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"food_generated_descriptor_input\",\n \"path\": \"crates/event_store/src/generated/food_availability_projection_manifest.rs\",\n \"byte_length\": 21437,\n \"sha256\": \"90908da53ab9572f45f5916ccc2652736b7ea26ba6dd202a4f69af1e651b564b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nip09_generated_descriptor_input\",\n \"path\": \"crates/event_store/src/generated/nip09_reconciliation_manifest.rs\",\n \"byte_length\": 586039,\n \"sha256\": \"406a760e9bed1e8fc89c8e7ae0976c7eff844de7427a3f473528c895439500b3\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_generated_descriptor_input\",\n \"path\": \"crates/event_store/src/generated/source_maintenance_manifest.rs\",\n \"byte_length\": 18723,\n \"sha256\": \"5f988f800425cf36d4327c828b30943c2f79c1fa577ce80730dc13383a1466b1\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_surface\",\n \"path\": \"crates/event_store/src/lib.rs\",\n \"byte_length\": 4133,\n \"sha256\": \"7cc60495cd26d1f3d8147b1c6b39db83a170f934f74226a617263c0c13c25ada\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"migration_runtime_registry\",\n \"path\": \"crates/event_store/src/migrations.rs\",\n \"byte_length\": 73585,\n \"sha256\": \"a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"model_registration\",\n \"path\": \"crates/event_store/src/model.rs\",\n \"byte_length\": 33818,\n \"sha256\": \"66d0b7b8d9966084c76d85aa7f79e9ec0d68cde464ea8b0a327404e61eadd8ff\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"addressable_transition_feed_model\",\n \"path\": \"crates/event_store/src/model/addressable_transition_feed_v1.rs\",\n \"byte_length\": 22314,\n \"sha256\": \"b1c6b0a68f34459f7e14bd63857596154c0aa3fd02dc6c1661d543bb681324a7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"current_visibility_model\",\n \"path\": \"crates/event_store/src/model/current_visibility_v1.rs\",\n \"byte_length\": 5691,\n \"sha256\": \"25ec92f45006e2f66f2e1c8b954a021334bb1595b849c4d8529f289d3f7aeb25\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"food_availability_projection_model\",\n \"path\": \"crates/event_store/src/model/food_availability_projection_v1.rs\",\n \"byte_length\": 17493,\n \"sha256\": \"1e5ff9c05a81fda223ed1a27ff18a1b08bcdeaec9047a13fdd577390b3e0fdb9\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"ingest_reconciliation_model\",\n \"path\": \"crates/event_store/src/model/ingest_reconciliation_v1.rs\",\n \"byte_length\": 1626,\n \"sha256\": \"47bf13b3fc0f8a913a660f7d655413de0f6b90568bc4acc510aa6bd741bab47b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"rebuild_report_and_digest_models\",\n \"path\": \"crates/event_store/src/model/raw_source_rebuild_v1.rs\",\n \"byte_length\": 2804,\n \"sha256\": \"a59459b5566f4450576fc5412e3c8ac0153954b653be376ccd925b19fc647345\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"reconciliation_model\",\n \"path\": \"crates/event_store/src/model/reconciliation_v1.rs\",\n \"byte_length\": 11138,\n \"sha256\": \"8a26bc373035878ef9b41767ceea7b681896e17d88bedce118de1d622125e1d6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nip09_module_registration\",\n \"path\": \"crates/event_store/src/nip09.rs\",\n \"byte_length\": 34,\n \"sha256\": \"fbd8a3b36d7f36e7b0d301aee0847d42c3908659f066cafcae3e247d67a75845\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"reconciliation_runtime_registration\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1.rs\",\n \"byte_length\": 194622,\n \"sha256\": \"4c14df2bd3af7bfefb002917dc7549f6ada155be3a748acb9cd6d78199ee6f76\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"serialized_raw_source_rebuild\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1/raw_source_rebuild.rs\",\n \"byte_length\": 60973,\n \"sha256\": \"a8db92dfbfa420b545038502c2a04547bed41b76e283f09758faa248c597c781\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"nip09_result_vector_executor_input\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1/result_vector_executor.rs\",\n \"byte_length\": 18446,\n \"sha256\": \"ca2a2bf54062aa6ddf2e553fd624c7217a01ad56309487ce73fa58c47c06c208\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"independent_raw_visibility_oracle\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs\",\n \"byte_length\": 36998,\n \"sha256\": \"48b60aba869d804ad7b3b120d7479c7ff45dd3758502a90b4fbce312d9848a99\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"managed_v4_validation_and_scoped_integrity\",\n \"path\": \"crates/event_store/src/schema.rs\",\n \"byte_length\": 153682,\n \"sha256\": \"df92fc509b44e40dae5a48d03ad9bf5cc556c4319a78215460ca26b899c610a2\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_capacity_rebuild_authority\",\n \"path\": \"crates/event_store/src/source_maintenance_v1.rs\",\n \"byte_length\": 51756,\n \"sha256\": \"f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_rebuild_and_cold_repair_boundary\",\n \"path\": \"crates/event_store/src/store.rs\",\n \"byte_length\": 402744,\n \"sha256\": \"56a84cc05208a335cbb6bad41b024c20ed77db5000fa69e684611e196ae461f4\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"addressable_transition_feed_storage\",\n \"path\": \"crates/event_store/src/store/addressable_transition_feed_v1.rs\",\n \"byte_length\": 40253,\n \"sha256\": \"fe23424aa1e6b39f9aba2dfa4470652b26b4990f91204a2bdfe379c03da9b610\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"current_visibility_storage\",\n \"path\": \"crates/event_store/src/store/current_visibility_v1.rs\",\n \"byte_length\": 15860,\n \"sha256\": \"8615086e674c30700305debcef11de5b3dbfe5aec735c0f58b4ac11caa518596\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_source_rebuild_focused_tests\",\n \"path\": \"crates/event_store/src/store/raw_source_rebuild_v1_tests.rs\",\n \"byte_length\": 103772,\n \"sha256\": \"383ca6f8aac6418d1d4460603d50746d224011c16367c2b86d8342818567ae2a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"signed_food_digest_fixture\",\n \"path\": \"crates/event_store/tests/fixtures/food_availability_projection.v1.json\",\n \"byte_length\": 103659,\n \"sha256\": \"fca2b71b47736ed04ed1e908823b65b3fc3cf0366cb162128369fe328295bb63\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"food_projection_reset_and_replay\",\n \"path\": \"crates/event_store/src/store/food_availability_projection_v1.rs\",\n \"byte_length\": 50858,\n \"sha256\": \"adc8a3eb59f5bccb4c0d0ba4c5319dbf55cffb5e0c333db8235db63fd0df5f21\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"post_core_extension_capabilities\",\n \"path\": \"crates/event_store/src/store/post_core_extension_capabilities.rs\",\n \"byte_length\": 1255,\n \"sha256\": \"cb434372156cb7ff31dac392d7095c2e5f44b128fae4e705516d6d000e2e2502\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"post_core_extension_dispatcher\",\n \"path\": \"crates/event_store/src/store/post_core_extension_dispatcher.rs\",\n \"byte_length\": 576,\n \"sha256\": \"df62ee92e9f165502d5e533997a47f533129fd3cffab2d9b2012e2ed22405f48\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"post_core_extensions_v1\",\n \"path\": \"crates/event_store/src/store/post_core_extensions_v1.rs\",\n \"byte_length\": 6935,\n \"sha256\": \"fb165704c64d982cf3be0a880c44985be6b375758451e94b2aaaf30881769f18\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"post_core_extensions_v2\",\n \"path\": \"crates/event_store/src/store/post_core_extensions_v2.rs\",\n \"byte_length\": 294,\n \"sha256\": \"8dcbc503ed9ea6fb06ed9a2a83b0804d928f9590b5706de25a057ad72c0d38d2\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"post_core_storage_v1\",\n \"path\": \"crates/event_store/src/store/post_core_storage_v1.rs\",\n \"byte_length\": 16871,\n \"sha256\": \"a6dca0884762cec3c32e460d17662ced9d0335f30e79b3d5fdb3461259d3ec19\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"post_core_storage_v2\",\n \"path\": \"crates/event_store/src/store/post_core_storage_v2.rs\",\n \"byte_length\": 632,\n \"sha256\": \"4b672770f3c34bf887e4cc949c068cb0c87396cb4af8efb6e13d39aa4e0d973a\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"protocol_reconciliation_storage\",\n \"path\": \"crates/event_store/src/store/protocol_reconciliation_v1.rs\",\n \"byte_length\": 30140,\n \"sha256\": \"210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"protocol_storage_boundary\",\n \"path\": \"crates/event_store/src/store/protocol_storage_v1.rs\",\n \"byte_length\": 10975,\n \"sha256\": \"155c74d27eee5db1d6f0f844f9d319604eefbbf640f4b2371ac5d0e370816e50\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_package_readme\",\n \"path\": \"crates/event_store/README\",\n \"byte_length\": 22209,\n \"sha256\": \"9e1cf2ec9ba58c2028d78eb33e6355fc2dff3507837b6e8640941dccd5608dc7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"signed_nip09_reconciliation_fixture\",\n \"path\": \"crates/event_store/tests/fixtures/nip09_reconciliation.v1.json\",\n \"byte_length\": 10405,\n \"sha256\": \"31cd9507734ff3308436881622a626b9782b75b548d9f5e159e4125621855b9c\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_food_predecessor_governance\",\n \"path\": \"tools/xtask/src/contract/food_availability_projection.rs\",\n \"byte_length\": 194995,\n \"sha256\": \"02f8b70b3885267b09fd5241ec89bcf020d2975e1eb1c6c533656a036723395b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"immutable_predecessor_governance\",\n \"path\": \"tools/xtask/src/contract/source_maintenance.rs\",\n \"byte_length\": 123766,\n \"sha256\": \"f10962e0cc0fa44dc109d87b707f02be11fe6dad1113707eef820ff3c5ae97ee\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_nip09_predecessor_governance\",\n \"path\": \"tools/xtask/src/contract/nip09_reconciliation.rs\",\n \"byte_length\": 850763,\n \"sha256\": \"852697eaaffcfe99391ffafd0c7c390c8eeb175377ac7e5cd5f490050b57ed58\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_source_rebuild_governance\",\n \"path\": \"tools/xtask/src/contract/raw_source_rebuild.rs\",\n \"byte_length\": 294540,\n \"sha256\": \"543c21131346381a833f9e063fd535efd0d0e192419aefa1f60e9b0f68475866\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 479703,\n \"sha256\": \"72fbd457b0cfdff1e30f07bf2452a0c71c23cef93bdaefffc114defa616d6342\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_dispatch_and_release_preflight\",\n \"path\": \"tools/xtask/src/main.rs\",\n \"byte_length\": 15018,\n \"sha256\": \"9aab8db1186b776ba8dcac90cc46c29d96928b610850b084be0e3a25d3e4cb0f\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"release_breaking_change_authority\",\n \"path\": \"contracts/releases/1.0.0-alpha.1.toml\",\n \"byte_length\": 19840,\n \"sha256\": \"946d90dacc9db522825898dbb5d9d424b020a22fe53b80ec15eb67c5f1d8cd2d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"release_note_authority\",\n \"path\": \"CHANGELOG.md\",\n \"byte_length\": 28939,\n \"sha256\": \"61b9d2a9050e4123bc5324aebf6e54393b9b1efdc2145a4a2cf6c009a4345b23\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"public_api\": {\n \"added_symbols\": [\n \"RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1\",\n \"RadrootsEventStoreCallerInboundForeignKeyV1\",\n \"RadrootsEventStoreActiveProductStateDigestV1\",\n \"RadrootsEventStoreImmutableRawDigestV1\",\n \"RadrootsEventStoreRawSourceRebuildDriftV1\",\n \"RadrootsEventStoreRawSourceRebuildReportV1\"\n ],\n \"methods\": [\n \"RadrootsEventStore::rebuild_from_raw_v1\",\n \"RadrootsEventStore::repair_file_from_raw_v1\",\n \"RadrootsEventStoreRawSourceRebuildReportV1::prior_source_generation\",\n \"RadrootsEventStoreRawSourceRebuildReportV1::new_source_generation\",\n \"RadrootsEventStoreRawSourceRebuildReportV1::source_capacity\",\n \"RadrootsEventStoreRawSourceRebuildReportV1::raw_high_water_seq\",\n \"RadrootsEventStoreRawSourceRebuildReportV1::immutable_raw_digest\",\n \"RadrootsEventStoreRawSourceRebuildReportV1::active_product_state_digest\",\n \"RadrootsEventStoreImmutableRawDigestV1::as_bytes\",\n \"RadrootsEventStoreActiveProductStateDigestV1::as_bytes\",\n \"RadrootsEventStoreRawSourceRebuildDriftV1::code\"\n ],\n \"error_variants\": [\n \"ProjectionCursorCapacityExceeded\",\n \"RawSourceRepairDatabaseIdentityMismatch\",\n \"RawSourceRepairCanonicalPathLockDomainMismatch\",\n \"RawSourceRepairMainDatabaseCanonicalizationFailed\",\n \"RawSourceRebuildCallerForeignKeyCapacityExceeded\",\n \"RawSourceRebuildCallerInboundForeignKeyUnsupported\",\n \"RawSourceRebuildCallerTableCapacityExceeded\",\n \"RawSourceRebuildStateDrift\",\n \"RawSourceRebuildTransactionRollbackFailed\"\n ],\n \"drift_kinds\": [\n {\n \"variant\": \"ManagedSchemaAuthority\",\n \"code\": \"managed_schema_authority\"\n },\n {\n \"variant\": \"ImmutableRawAuthority\",\n \"code\": \"immutable_raw_authority\"\n },\n {\n \"variant\": \"SourceGenerationLineage\",\n \"code\": \"source_generation_lineage\"\n },\n {\n \"variant\": \"AddressableTransitionAuthority\",\n \"code\": \"addressable_transition_authority\"\n },\n {\n \"variant\": \"DerivedProductStateAuthority\",\n \"code\": \"derived_product_state_authority\"\n },\n {\n \"variant\": \"RebuildPostcondition\",\n \"code\": \"rebuild_postcondition\"\n }\n ]\n },\n \"result_vector\": {\n \"canonical_path\": \"contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json\",\n \"mirror_path\": \"crates/event_store/tests/fixtures/raw_source_rebuild.v1.json\",\n \"byte_length\": 26833,\n \"sha256\": \"c37a2bf3714f53ab04fae8c5c9dbe2ad4b3f5310efa51f46bd8b116660f1fe15\",\n \"hash_algorithm\": \"sha256_bytes_v1\",\n \"executor_id\": \"radroots_event_store.raw_source_rebuild_v1.result_vector_executor.v1\",\n \"executor_path\": \"crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs\",\n \"executor_test\": \"raw_source_rebuild_v1_result_vector\",\n \"executor_byte_length\": 25542,\n \"executor_sha256\": \"51647259efdd0d99689ef1db0defb139c8d1f60f2ead69b793ddb2733a28e832\",\n \"executor_hash_algorithm\": \"sha256_bytes_v1\"\n }\n}\n";
+pub(crate) const RAW_SOURCE_REBUILD_MANIFEST_BYTE_LENGTH: usize = 45449;
+pub(crate) const RAW_SOURCE_REBUILD_MANIFEST_SHA256: &str =
+ "b8737a9c5836517114e7df6c2194c46e3c200093e12c4e6297165d2b9dae56a1";
+pub(crate) const RAW_SOURCE_REBUILD_CONTRACT_ID: &str =
+ "radroots_event_store.raw_source_rebuild_v1";
+pub(crate) const RAW_SOURCE_REBUILD_AUTHORITY_ID: &str = "raw_source_rebuild_v1";
+pub(crate) const RAW_SOURCE_REBUILD_PREDECESSOR_MANIFEST_SHA256: &str =
+ "e8911e6e5710278969cbd15557a5b856b1575dfd11a655711403598370b41221";
+pub(crate) const RAW_SOURCE_REBUILD_EVENT_STORE_SCHEMA_VERSION: u32 = 4;
+pub(crate) const RAW_SOURCE_REBUILD_EVENT_CONTRACT_REGISTRY_VERSION: u32 = 7;
+pub(crate) const RAW_SOURCE_REBUILD_RESULT_VECTOR_SHA256: &str =
+ "c37a2bf3714f53ab04fae8c5c9dbe2ad4b3f5310efa51f46bd8b116660f1fe15";
+pub(crate) const RAW_SOURCE_REBUILD_RESULT_VECTOR_EXECUTOR_SHA256: &str =
+ "51647259efdd0d99689ef1db0defb139c8d1f60f2ead69b793ddb2733a28e832";
diff --git a/crates/event_store/src/lib.rs b/crates/event_store/src/lib.rs
@@ -20,11 +20,13 @@ mod store;
#[cfg(feature = "sqlite")]
pub use error::{
+ RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1,
RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1,
RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1,
RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1, RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1,
- RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1, RadrootsEventStoreError,
- RadrootsEventStoreSourceCapacityResourceV1,
+ RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1,
+ RadrootsEventStoreCallerInboundForeignKeyV1, RadrootsEventStoreError,
+ RadrootsEventStoreRawSourceRebuildDriftV1, RadrootsEventStoreSourceCapacityResourceV1,
};
#[cfg(feature = "sqlite")]
pub use migrations::{
@@ -51,11 +53,13 @@ pub use model::{
RadrootsAddressableTransitionV1, RadrootsAddressableTransitionVisibilityV1,
RadrootsCurrentEventVisibilityV1, RadrootsCurrentVisibilityDecisionV1,
RadrootsEventAdmissionStatus, RadrootsEventIngest, RadrootsEventIngestReceipt,
- RadrootsEventPersistence, RadrootsEventStoreSourceGeneration, RadrootsEventStoreStatusSummary,
- RadrootsEventVisibility, RadrootsFoodAvailabilitySearchQueryV1,
- RadrootsFoodAvailabilityStatusFilterV1, RadrootsNip09SuppressionEvidenceV1,
- RadrootsNip09SuppressionOutcome, RadrootsNip09SuppressionReason, RadrootsProjectionCursor,
- RadrootsProjectionRebuildPrior, RadrootsProjectionRebuildTicket, RadrootsRawHeadDecision,
+ RadrootsEventPersistence, RadrootsEventStoreActiveProductStateDigestV1,
+ RadrootsEventStoreImmutableRawDigestV1, RadrootsEventStoreRawSourceRebuildReportV1,
+ RadrootsEventStoreSourceGeneration, RadrootsEventStoreStatusSummary, RadrootsEventVisibility,
+ RadrootsFoodAvailabilitySearchQueryV1, RadrootsFoodAvailabilityStatusFilterV1,
+ RadrootsNip09SuppressionEvidenceV1, RadrootsNip09SuppressionOutcome,
+ RadrootsNip09SuppressionReason, RadrootsProjectionCursor, RadrootsProjectionRebuildPrior,
+ RadrootsProjectionRebuildTicket, RadrootsRawHeadDecision,
RadrootsStoreProducedCanonicalEventV1, RadrootsStoredEventTag,
RadrootsStoredFoodAvailabilityImageV1, RadrootsStoredFoodAvailabilityV1,
RadrootsStoredRawEvent, RadrootsStoredRawEventHead, RadrootsStoredSellerReservation,
diff --git a/crates/event_store/src/model.rs b/crates/event_store/src/model.rs
@@ -2,6 +2,7 @@ mod addressable_transition_feed_v1;
mod current_visibility_v1;
mod food_availability_projection_v1;
mod ingest_reconciliation_v1;
+mod raw_source_rebuild_v1;
pub(crate) mod reconciliation_v1;
pub use addressable_transition_feed_v1::{
@@ -32,6 +33,10 @@ pub use food_availability_projection_v1::{
RadrootsFoodAvailabilityStatusFilterV1, RadrootsStoredFoodAvailabilityImageV1,
RadrootsStoredFoodAvailabilityV1,
};
+pub use raw_source_rebuild_v1::{
+ RadrootsEventStoreActiveProductStateDigestV1, RadrootsEventStoreImmutableRawDigestV1,
+ RadrootsEventStoreRawSourceRebuildReportV1,
+};
use crate::RadrootsEventStoreError;
use radroots_event::RadrootsEventKind;
diff --git a/crates/event_store/src/model/raw_source_rebuild_v1.rs b/crates/event_store/src/model/raw_source_rebuild_v1.rs
@@ -0,0 +1,76 @@
+use super::RadrootsEventStoreSourceGeneration;
+use crate::RadrootsEventStoreSourceCapacityV1;
+
+/// SHA-256 digest of the ordered immutable raw-event and raw-tag authority.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct RadrootsEventStoreImmutableRawDigestV1(pub(crate) [u8; 32]);
+
+impl RadrootsEventStoreImmutableRawDigestV1 {
+ pub(crate) const fn from_bytes(bytes: [u8; 32]) -> Self {
+ Self(bytes)
+ }
+
+ /// Returns the fixed-width digest bytes.
+ pub const fn as_bytes(&self) -> &[u8; 32] {
+ &self.0
+ }
+}
+
+/// SHA-256 digest of generation-normalized active product state.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct RadrootsEventStoreActiveProductStateDigestV1(pub(crate) [u8; 32]);
+
+impl RadrootsEventStoreActiveProductStateDigestV1 {
+ pub(crate) const fn from_bytes(bytes: [u8; 32]) -> Self {
+ Self(bytes)
+ }
+
+ /// Returns the fixed-width digest bytes.
+ pub const fn as_bytes(&self) -> &[u8; 32] {
+ &self.0
+ }
+}
+
+/// Committed result of rebuilding all active product state from immutable raw rows.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct RadrootsEventStoreRawSourceRebuildReportV1 {
+ pub(crate) prior_source_generation: RadrootsEventStoreSourceGeneration,
+ pub(crate) new_source_generation: RadrootsEventStoreSourceGeneration,
+ pub(crate) source_capacity: RadrootsEventStoreSourceCapacityV1,
+ pub(crate) immutable_raw_digest: RadrootsEventStoreImmutableRawDigestV1,
+ pub(crate) active_product_state_digest: RadrootsEventStoreActiveProductStateDigestV1,
+}
+
+impl RadrootsEventStoreRawSourceRebuildReportV1 {
+ /// Returns the active generation replaced by this rebuild.
+ pub const fn prior_source_generation(&self) -> RadrootsEventStoreSourceGeneration {
+ self.prior_source_generation
+ }
+
+ /// Returns the generation committed by this rebuild.
+ pub const fn new_source_generation(&self) -> RadrootsEventStoreSourceGeneration {
+ self.new_source_generation
+ }
+
+ /// Returns the raw-source capacity seal committed for the new generation.
+ pub const fn source_capacity(&self) -> RadrootsEventStoreSourceCapacityV1 {
+ self.source_capacity
+ }
+
+ /// Returns the greatest retained raw event sequence.
+ pub const fn raw_high_water_seq(&self) -> i64 {
+ self.source_capacity.raw_high_water_seq()
+ }
+
+ /// Returns the digest of ordered immutable raw authority.
+ pub const fn immutable_raw_digest(&self) -> RadrootsEventStoreImmutableRawDigestV1 {
+ self.immutable_raw_digest
+ }
+
+ /// Returns the generation-normalized active product-state digest.
+ pub const fn active_product_state_digest(
+ &self,
+ ) -> RadrootsEventStoreActiveProductStateDigestV1 {
+ self.active_product_state_digest
+ }
+}
diff --git a/crates/event_store/src/nip09/reconciliation_v1.rs b/crates/event_store/src/nip09/reconciliation_v1.rs
@@ -12,6 +12,7 @@ use crate::model::reconciliation_v1::{
RadrootsRawHeadDecision, StoredEventClass, tag_semantic_name, tag_value_type_name,
};
use crate::{
+ RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1,
RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1,
RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1,
RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1, RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1,
@@ -31,8 +32,10 @@ use radroots_event_codec::admission::registry_v7::{
use radroots_event_codec::deletion::reconciliation_v1::admission::{
RadrootsAdmittedNip09DeletionRequestEventV1, admit_verified_nip09_deletion_request_event_v1,
};
+#[cfg(test)]
+use radroots_event_codec::deletion::reconciliation_v1::evaluator::evaluate_nip09_suppression_from_borrowed_requests_v1;
use radroots_event_codec::deletion::reconciliation_v1::evaluator::{
- RadrootsNip09SuppressionOutcome, evaluate_nip09_suppression_from_borrowed_requests_v1,
+ RadrootsNip09SuppressionOutcome, RadrootsNip09SuppressionReason,
};
use radroots_event_codec::verification::v1::RadrootsSignatureVerifiedEvent;
#[cfg(test)]
@@ -40,8 +43,20 @@ use sqlx::SqlitePool;
use sqlx::{Row, SqliteConnection};
use std::collections::{BTreeMap, BTreeSet};
+mod raw_source_rebuild;
#[cfg(test)]
mod result_vector_executor;
+mod visibility_oracle_v1;
+
+#[cfg(test)]
+pub(crate) use raw_source_rebuild::{
+ RawSourceRebuildFailpointV1, preserve_raw_source_rebuild_primary_failure_for_test,
+ rebuild_from_raw_v1_in_transaction_for_test, rebuild_from_raw_v1_on_pool_for_test,
+ rebuild_from_raw_v1_on_pool_with_caller_schema_limits_for_test,
+};
+pub(crate) use raw_source_rebuild::{
+ rebuild_from_raw_v1_in_existing_transaction, rebuild_from_raw_v1_on_pool,
+};
const RECONCILIATION_SNAPSHOT_BATCH_SIZE: i64 = 512;
const RECONCILIATION_SNAPSHOT_BATCH_LEN: usize = 512;
@@ -318,17 +333,25 @@ struct SourceRebuildPlan {
prior: Option<SourceState>,
}
-struct RequestIndex<'a> {
- requests: &'a [RadrootsAdmittedNip09DeletionRequestEventV1],
- event_targets: BTreeMap<String, Vec<usize>>,
- address_targets: BTreeMap<(i64, String, String), Vec<usize>>,
+struct SourceRebuildMarkerTokenV1 {
+ generation: RadrootsEventStoreSourceGeneration,
}
-struct MergedRequestIndices<'a> {
- event_indices: &'a [usize],
- address_indices: &'a [usize],
- event_position: usize,
- address_position: usize,
+struct RequestIndex {
+ event_targets: BTreeMap<String, BTreeMap<String, String>>,
+ address_targets: BTreeMap<(i64, String, String), AddressRequestEvidence>,
+}
+
+#[derive(Clone)]
+struct IndexedRequestEvidence {
+ request_id: String,
+ created_at: u64,
+}
+
+#[derive(Default)]
+struct AddressRequestEvidence {
+ authorized: Option<IndexedRequestEvidence>,
+ unauthorized: bool,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -389,6 +412,7 @@ struct EventCoordinateFact {
nip09_d_tag: Option<String>,
}
+#[derive(Debug, PartialEq, Eq)]
struct StoredSuppressionDecision {
outcome: RadrootsNip09SuppressionOutcome,
reason: &'static str,
@@ -397,95 +421,125 @@ struct StoredSuppressionDecision {
address_reference_cutoff: Option<i64>,
}
-impl<'a> RequestIndex<'a> {
- fn new(requests: &'a [RadrootsAdmittedNip09DeletionRequestEventV1]) -> Self {
- let mut event_targets = BTreeMap::<String, Vec<usize>>::new();
- let mut address_targets = BTreeMap::<(i64, String, String), Vec<usize>>::new();
- for (index, request) in requests.iter().enumerate() {
- for target in request.projection().event_targets() {
- event_targets
- .entry(target.event_id().as_str().to_owned())
- .or_default()
- .push(index);
- }
- for target in request.projection().address_targets() {
- address_targets
- .entry((
- i64::from(target.coordinate().kind()),
- target.coordinate().pubkey().as_str().to_owned(),
- target.coordinate().identifier().to_owned(),
- ))
- .or_default()
- .push(index);
- }
+impl RequestIndex {
+ fn new(requests: &[RadrootsAdmittedNip09DeletionRequestEventV1]) -> Self {
+ let mut index = Self {
+ event_targets: BTreeMap::new(),
+ address_targets: BTreeMap::new(),
+ };
+ for request in requests {
+ index.insert(request);
}
- Self {
- requests,
- event_targets,
- address_targets,
+ index
+ }
+
+ fn insert(&mut self, request: &RadrootsAdmittedNip09DeletionRequestEventV1) {
+ let request_event = request.event();
+ let request_author = request_event.author_str();
+ let request_id = request_event.id_str();
+ for target in request.projection().event_targets() {
+ self.event_targets
+ .entry(target.event_id().as_str().to_owned())
+ .or_default()
+ .entry(request_author.to_owned())
+ .and_modify(|current| {
+ if request_id < current.as_str() {
+ *current = request_id.to_owned();
+ }
+ })
+ .or_insert_with(|| request_id.to_owned());
+ }
+ for target in request.projection().address_targets() {
+ let coordinate = (
+ i64::from(target.coordinate().kind()),
+ target.coordinate().pubkey().as_str().to_owned(),
+ target.coordinate().identifier().to_owned(),
+ );
+ let evidence = self.address_targets.entry(coordinate.clone()).or_default();
+ if request_author == coordinate.1 {
+ let replace = evidence.authorized.as_ref().is_none_or(|current| {
+ request_event.created_at_u64() > current.created_at
+ || (request_event.created_at_u64() == current.created_at
+ && request_id < current.request_id.as_str())
+ });
+ if replace {
+ evidence.authorized = Some(IndexedRequestEvidence {
+ request_id: request_id.to_owned(),
+ created_at: request_event.created_at_u64(),
+ });
+ }
+ } else {
+ evidence.unauthorized = true;
+ }
}
}
- fn matching<'index>(
- &'index self,
+ fn decision(
+ &self,
event: &RadrootsEventEnvelope,
- ) -> impl Iterator<Item = &'index RadrootsAdmittedNip09DeletionRequestEventV1> + 'index {
- let event_indices = self
+ ) -> Result<StoredSuppressionDecision, RadrootsEventStoreError> {
+ if event.kind_u32() == 5 {
+ return Ok(StoredSuppressionDecision {
+ outcome: RadrootsNip09SuppressionOutcome::Visible,
+ reason: RadrootsNip09SuppressionReason::DeletionRequestImmune.code(),
+ event_reference_request_id: None,
+ address_reference_request_id: None,
+ address_reference_cutoff: None,
+ });
+ }
+
+ let (event_reference_request_id, unauthorized_event_reference) = self
.event_targets
.get(event.id_str())
- .map(Vec::as_slice)
- .unwrap_or_default();
- let address_indices = nip01_coordinate_key(event)
+ .map_or((None, false), |by_author| {
+ let authorized = by_author.get(event.author_str()).cloned();
+ let unauthorized = by_author.len() > usize::from(authorized.is_some());
+ (authorized, unauthorized)
+ });
+ let address_evidence = nip01_coordinate_key(event)
.as_ref()
- .and_then(|coordinate| self.address_targets.get(coordinate))
- .map(Vec::as_slice)
- .unwrap_or_default();
- MergedRequestIndices::new(event_indices, address_indices).map(|index| &self.requests[index])
- }
-}
-
-impl<'a> MergedRequestIndices<'a> {
- const fn new(event_indices: &'a [usize], address_indices: &'a [usize]) -> Self {
- Self {
- event_indices,
- address_indices,
- event_position: 0,
- address_position: 0,
- }
- }
-}
-
-impl Iterator for MergedRequestIndices<'_> {
- type Item = usize;
-
- fn next(&mut self) -> Option<Self::Item> {
- match (
- self.event_indices.get(self.event_position).copied(),
- self.address_indices.get(self.address_position).copied(),
- ) {
- (Some(event_index), Some(address_index)) if event_index < address_index => {
- self.event_position += 1;
- Some(event_index)
- }
- (Some(event_index), Some(address_index)) if address_index < event_index => {
- self.address_position += 1;
- Some(address_index)
- }
- (Some(index), Some(_)) => {
- self.event_position += 1;
- self.address_position += 1;
- Some(index)
- }
- (Some(index), None) => {
- self.event_position += 1;
- Some(index)
- }
- (None, Some(index)) => {
- self.address_position += 1;
- Some(index)
- }
- (None, None) => None,
- }
+ .and_then(|coordinate| self.address_targets.get(coordinate));
+ let address_reference = address_evidence.and_then(|evidence| evidence.authorized.as_ref());
+ let has_unauthorized_reference = unauthorized_event_reference
+ || address_evidence.is_some_and(|evidence| evidence.unauthorized);
+ let address_applies =
+ address_reference.is_some_and(|evidence| event.created_at_u64() <= evidence.created_at);
+ let (outcome, reason) = match (event_reference_request_id.is_some(), address_applies) {
+ (true, true) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdAndAddressReference,
+ ),
+ (true, false) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdReference,
+ ),
+ (false, true) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff,
+ ),
+ (false, false) if address_reference.is_some() => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget,
+ ),
+ (false, false) if has_unauthorized_reference => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::RequestAuthorMismatch,
+ ),
+ (false, false) => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::NoAuthorizedReference,
+ ),
+ };
+ Ok(StoredSuppressionDecision {
+ outcome,
+ reason: reason.code(),
+ event_reference_request_id,
+ address_reference_request_id: address_reference
+ .map(|evidence| evidence.request_id.clone()),
+ address_reference_cutoff: address_reference
+ .map(|evidence| i64_from_u64("address_reference_cutoff", evidence.created_at))
+ .transpose()?,
+ })
}
}
@@ -595,7 +649,7 @@ pub(crate) async fn apply_reconciliation_hook(
prior,
};
- open_source_rebuild_marker(connection, &plan).await?;
+ let marker = open_source_rebuild_marker(connection, &plan).await?;
append_source_generation(connection, &plan).await?;
rotate_source_state(connection, &plan).await?;
reconcile_raw_events(connection, &events).await?;
@@ -631,7 +685,7 @@ pub(crate) async fn apply_reconciliation_hook(
)
.await?;
}
- close_source_rebuild_marker(connection, plan.generation).await?;
+ close_source_rebuild_marker(connection, marker).await?;
validate_sqlite_integrity_after_rebuild(connection).await?;
validate_active_hook_state_fast(connection).await
}
@@ -639,7 +693,7 @@ pub(crate) async fn apply_reconciliation_hook(
async fn open_source_rebuild_marker(
connection: &mut SqliteConnection,
plan: &SourceRebuildPlan,
-) -> Result<(), RadrootsEventStoreError> {
+) -> Result<SourceRebuildMarkerTokenV1, RadrootsEventStoreError> {
let prior_generation = plan
.prior
.as_ref()
@@ -667,7 +721,10 @@ async fn open_source_rebuild_marker(
.bind(plan.prior.as_ref().map(|state| state.last_transition_seq))
.execute(&mut *connection)
.await?;
- require_expected_insert(inserted.rows_affected(), "source rebuild marker")
+ require_expected_insert(inserted.rows_affected(), "source rebuild marker")?;
+ Ok(SourceRebuildMarkerTokenV1 {
+ generation: plan.generation,
+ })
}
async fn append_source_generation(
@@ -732,12 +789,12 @@ async fn rotate_source_state(
async fn close_source_rebuild_marker(
connection: &mut SqliteConnection,
- generation: RadrootsEventStoreSourceGeneration,
+ marker: SourceRebuildMarkerTokenV1,
) -> Result<(), RadrootsEventStoreError> {
let deleted = sqlx::query(
"DELETE FROM radroots_event_store_source_rebuild_marker WHERE singleton = 1 AND target_generation = ?",
)
- .bind(generation.as_bytes().as_slice())
+ .bind(marker.generation.as_bytes().as_slice())
.execute(&mut *connection)
.await?;
if deleted.rows_affected() != 1 {
@@ -808,6 +865,29 @@ async fn validate_rebuild_hook_state_with_events(
validate_hook_state_with_events(connection, &state, events).await
}
+async fn validate_raw_source_rebuild_core_with_events_v1(
+ connection: &mut SqliteConnection,
+ generation: RadrootsEventStoreSourceGeneration,
+ events: &[ReconciledEvent],
+) -> Result<(), RadrootsEventStoreError> {
+ validate_active_rebuild_marker(connection, generation).await?;
+ let state = read_source_state(connection).await?;
+ if state.generation != generation {
+ return hook_drift(
+ "open rebuild marker target does not match active source generation".to_owned(),
+ );
+ }
+ validate_source_raw_authority_with_state(connection, &state).await?;
+ validate_transition_interval_full(connection, &state).await?;
+ validate_derived_event_storage(connection, events).await?;
+ validate_raw_heads(connection, events).await?;
+ validate_event_coordinate_facts(connection, state.generation, events).await?;
+ let requests = validate_nip09_fact_graph(connection, state.generation, events).await?;
+ validate_addressable_state(connection, state.generation, events, &requests).await?;
+ validate_transition_history(connection, &state, events).await?;
+ validate_latest_transitions_match_state(connection, state.generation).await
+}
+
async fn validate_hook_state_with_events(
connection: &mut SqliteConnection,
state: &SourceState,
@@ -828,8 +908,8 @@ pub(crate) async fn validate_active_hook_state_fast(
connection: &mut SqliteConnection,
) -> Result<(), RadrootsEventStoreError> {
// Supported writes are guarded transactionally. Reopen validates only
- // constant-cost authority bounds; full history/state and cursor inventory
- // comparisons remain part of migration and rebuild audits.
+ // constant-cost authority bounds; full history/state checks remain part of
+ // migration and rebuild audits, while cursor inventory is migration-only.
validate_rebuild_marker_absent(connection).await?;
validate_structural_source_state_fast(connection)
.await
@@ -976,12 +1056,25 @@ async fn validate_active_rebuild_marker(
async fn validate_projection_cursor_authority(
connection: &mut SqliteConnection,
) -> Result<(), RadrootsEventStoreError> {
+ let probe_limit = i64::from(RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1) + 1;
+ let cursor_probe = sqlx::query("SELECT 1 FROM projection_cursor LIMIT ?")
+ .bind(probe_limit)
+ .fetch_all(&mut *connection)
+ .await?;
+ validate_projection_cursor_cardinality_v1(cursor_probe.len())?;
+ let identity_probe =
+ sqlx::query("SELECT 1 FROM radroots_event_store_projection_cursor_source LIMIT ?")
+ .bind(probe_limit)
+ .fetch_all(&mut *connection)
+ .await?;
+ validate_projection_cursor_cardinality_v1(identity_probe.len())?;
+
let raw_high_water: i64 =
sqlx::query_scalar("SELECT COALESCE(MAX(seq), 0) FROM event_envelopes")
.fetch_one(&mut *connection)
.await?;
- let invalid_count: i64 = sqlx::query_scalar(
- "SELECT COUNT(*)
+ let invalid: Option<i64> = sqlx::query_scalar(
+ "SELECT 1
FROM projection_cursor AS cursor
LEFT JOIN radroots_event_store_projection_cursor_source AS source
ON source.projection_id = cursor.projection_id
@@ -1004,29 +1097,61 @@ async fn validate_projection_cursor_authority(
typeof(source.source_generation) != 'blob'
OR length(source.source_generation) != 32
)
- )",
+ )
+ LIMIT 1",
)
.bind(raw_high_water)
- .fetch_one(&mut *connection)
+ .fetch_optional(&mut *connection)
.await?;
- if invalid_count != 0 {
- return hook_drift(format!(
- "{invalid_count} projection cursor identities are invalid or ahead of raw source authority"
- ));
+ if invalid.is_some() {
+ return hook_drift(
+ "a projection cursor identity is invalid or ahead of raw source authority".to_owned(),
+ );
}
- let orphan_identity_count: i64 = sqlx::query_scalar(
- "SELECT COUNT(*)
+ let orphan_identity: Option<i64> = sqlx::query_scalar(
+ "SELECT 1
FROM radroots_event_store_projection_cursor_source AS source
LEFT JOIN projection_cursor AS cursor
ON cursor.projection_id = source.projection_id
- WHERE cursor.projection_id IS NULL",
+ WHERE cursor.projection_id IS NULL
+ LIMIT 1",
)
- .fetch_one(&mut *connection)
+ .fetch_optional(&mut *connection)
.await?;
- if orphan_identity_count != 0 {
- return hook_drift(format!(
- "{orphan_identity_count} projection cursor source identities have no cursor"
- ));
+ if orphan_identity.is_some() {
+ return hook_drift("a projection cursor source identity has no cursor".to_owned());
+ }
+ Ok(())
+}
+
+pub(crate) async fn preflight_projection_cursor_insert_v1(
+ connection: &mut SqliteConnection,
+) -> Result<(), RadrootsEventStoreError> {
+ let rows = sqlx::query("SELECT 1 FROM projection_cursor LIMIT ?")
+ .bind(i64::from(
+ RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1,
+ ))
+ .fetch_all(&mut *connection)
+ .await?;
+ if rows.len()
+ >= usize::try_from(RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1)
+ .unwrap_or(usize::MAX)
+ {
+ return Err(RadrootsEventStoreError::ProjectionCursorCapacityExceeded {
+ current: RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1,
+ limit: RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1,
+ });
+ }
+ Ok(())
+}
+
+fn validate_projection_cursor_cardinality_v1(
+ observed: usize,
+) -> Result<(), RadrootsEventStoreError> {
+ let limit = RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1;
+ let current = u32::try_from(observed).unwrap_or(u32::MAX);
+ if current > limit {
+ return Err(RadrootsEventStoreError::ProjectionCursorCapacityExceeded { current, limit });
}
Ok(())
}
@@ -2609,7 +2734,7 @@ fn desired_addressable_states(
d_tag.as_str(),
winner.event_seq,
event,
- request_index.matching(event.verified_event.event()),
+ &request_index,
)?;
desired.insert((i64::from(kind), pubkey.to_string(), d_tag), state);
}
@@ -2803,7 +2928,8 @@ fn expected_transition_history(
.iter()
.map(|event| (event.verified_event.event().id_str(), event))
.collect::<BTreeMap<_, _>>();
- let mut requests = admitted_nip09_requests(&baseline_events)?;
+ let requests = admitted_nip09_requests(&baseline_events)?;
+ let mut request_index = RequestIndex::new(&requests);
let mut winners = select_raw_head_winners(&baseline_events);
let mut states = desired_addressable_states(&baseline_events, &requests)?;
let mut transitions = Vec::new();
@@ -2828,34 +2954,12 @@ fn expected_transition_history(
&& event.verified_event.event().kind_u32() == 5
{
let request = admitted_nip09_request(event)?;
- for (coordinate, winner) in &winners {
- let RadrootsEventHeadCoordinate::Addressable {
- kind,
- pubkey,
- d_tag,
- } = coordinate
- else {
- continue;
- };
- let target = event_by_id
- .get(winner.candidate.event_id.as_str())
- .ok_or_else(|| RadrootsEventStoreError::MigrationHookStateDrift {
- hook_id: NIP09_HOOK_ID,
- reason: format!(
- "raw head `{}` has no reconciled event",
- winner.candidate.event_id
- ),
- })?;
- if request_references_event(&request, target.verified_event.event()) {
- affected_coordinates.insert((
- i64::from(*kind),
- pubkey.to_string(),
- d_tag.clone(),
- ));
- }
- }
- requests.push(request);
- requests.sort_by(|left, right| left.event().id().cmp(right.event().id()));
+ affected_coordinates.extend(request_affected_addressable_coordinates(
+ &request,
+ &winners,
+ &event_by_id,
+ ));
+ request_index.insert(&request);
} else if matches!(raw_head_decision, RadrootsRawHeadDecision::Applied)
&& let RadrootsEventHeadCandidateResult::Candidate(candidate) =
event_head_candidate_for_nip01_event_v1(event.verified_event.event())
@@ -2868,7 +2972,6 @@ fn expected_transition_history(
affected_coordinates.insert((i64::from(kind), pubkey.to_string(), d_tag));
}
- let request_index = RequestIndex::new(&requests);
for (kind, pubkey, d_tag) in affected_coordinates {
let key = (kind, pubkey.clone(), d_tag.clone());
let coordinate = RadrootsEventHeadCoordinate::Addressable {
@@ -2896,14 +2999,13 @@ fn expected_transition_history(
winner.candidate.event_id
),
})?;
- let matching = request_index.matching(target.verified_event.event());
let desired = addressable_state_for_event(
kind,
&pubkey,
&d_tag,
winner.event_seq,
target,
- matching,
+ &request_index,
)?;
let prior = states.get(&key);
if prior == Some(&desired) {
@@ -2962,22 +3064,56 @@ fn admitted_nip09_request(
})
}
-fn request_references_event(
+fn request_affected_addressable_coordinates<'a>(
request: &RadrootsAdmittedNip09DeletionRequestEventV1,
- event: &RadrootsEventEnvelope,
-) -> bool {
- request
- .projection()
- .event_targets()
- .iter()
- .any(|target| target.event_id() == event.id())
- || nip01_coordinate_key(event).is_some_and(|(kind, pubkey, d_tag)| {
- request.projection().address_targets().iter().any(|target| {
- i64::from(target.coordinate().kind()) == kind
- && target.coordinate().pubkey().as_str() == pubkey
- && target.coordinate().identifier() == d_tag
- })
- })
+ winners: &BTreeMap<RadrootsEventHeadCoordinate, RawHeadWinner>,
+ event_by_id: &BTreeMap<&'a str, &'a ReconciledEvent>,
+) -> BTreeSet<(i64, String, String)> {
+ let mut affected = BTreeSet::new();
+ for target in request.projection().event_targets() {
+ let Some(event) = event_by_id.get(target.event_id().as_str()) else {
+ continue;
+ };
+ let RadrootsEventHeadCandidateResult::Candidate(candidate) =
+ event_head_candidate_for_nip01_event_v1(event.verified_event.event())
+ else {
+ continue;
+ };
+ let coordinate = &candidate.coordinate;
+ let RadrootsEventHeadCoordinate::Addressable {
+ kind,
+ pubkey,
+ d_tag,
+ } = coordinate
+ else {
+ continue;
+ };
+ if winners
+ .get(coordinate)
+ .is_some_and(|winner| winner.candidate.event_id == candidate.event_id)
+ {
+ affected.insert((i64::from(*kind), pubkey.to_string(), d_tag.clone()));
+ }
+ }
+ for target in request.projection().address_targets() {
+ let kind = target.coordinate().kind();
+ if !(30_000..=39_999).contains(&kind) {
+ continue;
+ }
+ let coordinate = RadrootsEventHeadCoordinate::Addressable {
+ kind,
+ pubkey: target.coordinate().pubkey().clone(),
+ d_tag: target.coordinate().identifier().to_owned(),
+ };
+ if winners.contains_key(&coordinate) {
+ affected.insert((
+ i64::from(kind),
+ target.coordinate().pubkey().as_str().to_owned(),
+ target.coordinate().identifier().to_owned(),
+ ));
+ }
+ }
+ affected
}
fn addressable_transition_fact(
@@ -3018,34 +3154,26 @@ fn addressable_transition_fact(
}
}
-fn addressable_state_for_event<'a>(
+fn addressable_state_for_event(
kind: i64,
pubkey: &str,
d_tag: &str,
event_seq: i64,
event: &ReconciledEvent,
- requests: impl IntoIterator<Item = &'a RadrootsAdmittedNip09DeletionRequestEventV1>,
+ request_index: &RequestIndex,
) -> Result<AddressableHeadState, RadrootsEventStoreError> {
let mut state = addressable_state_base(kind, pubkey, d_tag, event_seq, event)?;
if event.admission.status != RadrootsEventAdmissionStatus::Admitted {
return Ok(state);
}
- let decision =
- evaluate_nip09_suppression_from_borrowed_requests_v1(&event.verified_event, requests);
- state.nip09_outcome = Some(decision.outcome().code().to_owned());
- state.nip09_reason = Some(decision.reason().code().to_owned());
- state.event_reference_request_id = decision
- .event_reference()
- .map(|evidence| evidence.request_id().as_str().to_owned());
- if let Some(evidence) = decision.address_reference() {
- state.address_reference_request_id = Some(evidence.request_id().as_str().to_owned());
- state.address_reference_cutoff = Some(i64_from_u64(
- "address_reference_cutoff",
- evidence.inclusive_cutoff(),
- )?);
- }
- state.visibility = match decision.outcome() {
+ let decision = request_index.decision(event.verified_event.event())?;
+ state.nip09_outcome = Some(decision.outcome.code().to_owned());
+ state.nip09_reason = Some(decision.reason.to_owned());
+ state.event_reference_request_id = decision.event_reference_request_id;
+ state.address_reference_request_id = decision.address_reference_request_id;
+ state.address_reference_cutoff = decision.address_reference_cutoff;
+ state.visibility = match decision.outcome {
RadrootsNip09SuppressionOutcome::Visible => "visible",
RadrootsNip09SuppressionOutcome::Suppressed => "suppressed",
}
@@ -4003,9 +4131,9 @@ INSERT INTO radroots_event_store_owned_child_probe(id, parent_id) VALUES (1, 999
}
#[test]
- fn request_index_borrows_one_maximum_shape_request_across_all_target_heads() {
+ fn request_index_reduces_maximum_shape_requests_once_and_decides_by_lookup() {
let author = fixture_author();
- let request_tags = (0..RADROOTS_NIP09_DELETION_TAG_MAX_COUNT)
+ let address_tags = (0..RADROOTS_NIP09_DELETION_TAG_MAX_COUNT)
.map(|index| {
vec![
"a".to_owned(),
@@ -4013,39 +4141,59 @@ INSERT INTO radroots_event_store_owned_child_probe(id, parent_id) VALUES (1, 999
]
})
.collect::<Vec<_>>();
- let request = admitted_request(REQUEST_CREATED_AT, request_tags, "maximum fanout");
+ let address_request =
+ admitted_request(REQUEST_CREATED_AT, address_tags, "maximum address fanout");
+ let event_tags = (0..RADROOTS_NIP09_DELETION_TAG_MAX_COUNT)
+ .map(|index| vec!["e".to_owned(), format!("{:064x}", index + 1)])
+ .collect::<Vec<_>>();
+ let event_request =
+ admitted_request(REQUEST_CREATED_AT + 1, event_tags, "maximum event fanout");
+ assert_eq!(
+ address_request.event().tag_slices().len(),
+ RADROOTS_NIP09_DELETION_TAG_MAX_COUNT
+ );
assert_eq!(
- request.event().tag_slices().len(),
+ address_request.projection().address_targets().len(),
RADROOTS_NIP09_DELETION_TAG_MAX_COUNT
);
assert_eq!(
- request.projection().address_targets().len(),
+ event_request.projection().event_targets().len(),
RADROOTS_NIP09_DELETION_TAG_MAX_COUNT
);
- let request_id = request.event().id_str().to_owned();
- let requests = vec![request];
+ let request_id = address_request.event().id_str().to_owned();
+ let requests = vec![address_request, event_request];
let request_index = RequestIndex::new(&requests);
- assert!(request_index.event_targets.is_empty());
assert_eq!(
- request_index.address_targets.len(),
+ request_index.event_targets.len(),
RADROOTS_NIP09_DELETION_TAG_MAX_COUNT
);
assert_eq!(
+ request_index.address_targets.len(),
+ RADROOTS_NIP09_DELETION_TAG_MAX_COUNT
+ );
+ assert!(
request_index
.address_targets
.values()
- .map(Vec::len)
- .sum::<usize>(),
- RADROOTS_NIP09_DELETION_TAG_MAX_COUNT
+ .all(|evidence| { evidence.authorized.is_some() && !evidence.unauthorized })
);
for index in 0..RADROOTS_NIP09_DELETION_TAG_MAX_COUNT {
let target = unsigned_addressable_target(author.as_str(), index);
- let mut matching = request_index.matching(&target);
- let matched = matching.next().expect("indexed request");
- assert!(core::ptr::eq(matched, &requests[0]));
- assert!(matching.next().is_none());
+ let decision = request_index.decision(&target).expect("indexed decision");
+ assert_eq!(
+ decision.outcome,
+ RadrootsNip09SuppressionOutcome::Suppressed
+ );
+ assert_eq!(
+ decision.reason,
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff.code()
+ );
+ assert_eq!(
+ decision.address_reference_request_id.as_deref(),
+ Some(request_id.as_str())
+ );
}
let identifier = fanout_identifier(0);
@@ -4072,7 +4220,7 @@ INSERT INTO radroots_event_store_owned_child_probe(id, parent_id) VALUES (1, 999
identifier.as_str(),
event.seq,
&event,
- request_index.matching(event.verified_event.event()),
+ &request_index,
)
.expect("suppressed addressable state");
@@ -4092,22 +4240,28 @@ INSERT INTO radroots_event_store_owned_child_probe(id, parent_id) VALUES (1, 999
}
#[test]
- fn request_index_merges_event_and_address_indices_once_in_canonical_order() {
+ fn request_index_reduces_event_and_address_evidence_canonically() {
let author = fixture_author();
- let target = unsigned_addressable_target(author.as_str(), 0);
+ let target = verify_nip01_event_v1(signed_event(
+ TARGET_CREATED_AT,
+ KIND_LIST_SET_RELAY,
+ vec![vec!["d".to_owned(), fanout_identifier(0)]],
+ "{}",
+ ))
+ .expect("verified target");
let target_coordinate = coordinate(author.as_str(), fanout_identifier(0).as_str());
let mut requests = vec![
admitted_request(
REQUEST_CREATED_AT,
vec![
- vec!["e".to_owned(), target.id_str().to_owned()],
+ vec!["e".to_owned(), target.event().id_str().to_owned()],
vec!["a".to_owned(), target_coordinate.clone()],
],
"both",
),
admitted_request(
REQUEST_CREATED_AT + 1,
- vec![vec!["e".to_owned(), target.id_str().to_owned()]],
+ vec![vec!["e".to_owned(), target.event().id_str().to_owned()]],
"event",
),
admitted_request(
@@ -4118,29 +4272,83 @@ INSERT INTO radroots_event_store_owned_child_probe(id, parent_id) VALUES (1, 999
];
requests.sort_by(|left, right| left.event().id().cmp(right.event().id()));
let request_index = RequestIndex::new(&requests);
- let coordinate_key = nip01_coordinate_key(&target).expect("target coordinate");
+ let coordinate_key = nip01_coordinate_key(target.event()).expect("target coordinate");
assert_eq!(
request_index
.event_targets
- .get(target.id_str())
+ .get(target.event().id_str())
.expect("event indices")
.len(),
- 2
+ 1
+ );
+ let address_evidence = request_index
+ .address_targets
+ .get(&coordinate_key)
+ .expect("address evidence");
+ assert!(address_evidence.authorized.is_some());
+ assert!(!address_evidence.unauthorized);
+ let expected_event_request_id = requests
+ .iter()
+ .filter(|request| {
+ request
+ .projection()
+ .event_targets()
+ .iter()
+ .any(|event_target| event_target.event_id().as_str() == target.event().id_str())
+ })
+ .map(|request| request.event().id_str())
+ .min()
+ .expect("event evidence");
+ let expected_address_request_id = requests
+ .iter()
+ .filter(|request| {
+ request
+ .projection()
+ .address_targets()
+ .iter()
+ .any(|address_target| {
+ i64::from(address_target.coordinate().kind()) == coordinate_key.0
+ && address_target.coordinate().pubkey().as_str()
+ == coordinate_key.1.as_str()
+ && address_target.coordinate().identifier() == coordinate_key.2.as_str()
+ })
+ })
+ .max_by(|left, right| {
+ left.event()
+ .created_at_u64()
+ .cmp(&right.event().created_at_u64())
+ .then_with(|| right.event().id().cmp(left.event().id()))
+ })
+ .map(|request| request.event().id_str())
+ .expect("address evidence");
+ let decision = request_index
+ .decision(target.event())
+ .expect("indexed decision");
+ assert_eq!(
+ decision.reason,
+ RadrootsNip09SuppressionReason::EventIdAndAddressReference.code()
);
assert_eq!(
- request_index
- .address_targets
- .get(&coordinate_key)
- .expect("address indices")
- .len(),
- 2
+ decision.event_reference_request_id.as_deref(),
+ Some(expected_event_request_id)
);
- let matching = request_index.matching(&target).collect::<Vec<_>>();
- assert_eq!(matching.len(), 3);
- for (expected, actual) in requests.iter().zip(matching) {
- assert!(core::ptr::eq(expected, actual));
- }
+ assert_eq!(
+ decision.address_reference_request_id.as_deref(),
+ Some(expected_address_request_id)
+ );
+
+ let mut reversed = requests.clone();
+ reversed.reverse();
+ reversed.push(requests[0].clone());
+ assert_eq!(
+ RequestIndex::new(&reversed)
+ .decision(target.event())
+ .expect("reversed repeated decision"),
+ decision
+ );
+ assert_request_index_matches_protocol(&target, &requests, &request_index);
+ assert_request_index_matches_protocol(&target, &reversed, &RequestIndex::new(&reversed));
}
type RawEventRows = Vec<(
@@ -4508,6 +4716,35 @@ INSERT INTO caller_child(id, parent_id) VALUES (1, 999);",
admit_verified_nip09_deletion_request_event_v1(verified).expect("admitted request")
}
+ fn assert_request_index_matches_protocol(
+ target: &RadrootsSignatureVerifiedEvent,
+ requests: &[RadrootsAdmittedNip09DeletionRequestEventV1],
+ index: &RequestIndex,
+ ) {
+ let expected = evaluate_nip09_suppression_from_borrowed_requests_v1(target, requests);
+ let actual = index.decision(target.event()).expect("indexed decision");
+ assert_eq!(actual.outcome, expected.outcome());
+ assert_eq!(actual.reason, expected.reason().code());
+ assert_eq!(
+ actual.event_reference_request_id.as_deref(),
+ expected
+ .event_reference()
+ .map(|evidence| evidence.request_id().as_str())
+ );
+ assert_eq!(
+ actual.address_reference_request_id.as_deref(),
+ expected
+ .address_reference()
+ .map(|evidence| evidence.request_id().as_str())
+ );
+ assert_eq!(
+ actual.address_reference_cutoff,
+ expected
+ .address_reference()
+ .map(|evidence| i64::try_from(evidence.inclusive_cutoff()).expect("cutoff range"))
+ );
+ }
+
fn unsigned_addressable_target(author: &str, index: usize) -> RadrootsEventEnvelope {
let tags = vec![vec!["d".to_owned(), fanout_identifier(index)]];
let id = compute_canonical_nip01_event_id(
diff --git a/crates/event_store/src/nip09/reconciliation_v1/raw_source_rebuild.rs b/crates/event_store/src/nip09/reconciliation_v1/raw_source_rebuild.rs
@@ -0,0 +1,1418 @@
+use super::visibility_oracle_v1::{VisibilityOracleFactV1, audit_current_visibility_from_raw_v1};
+use super::{
+ OsSourceGenerationProvider, ReconciledEvent, ReconciliationCapacityLimits,
+ SourceGenerationProvider, SourceRebuildPlan, SourceState, TransitionOrigin,
+ append_source_generation, close_source_rebuild_marker, generation_from_blob,
+ load_reconciliation_snapshot, open_source_rebuild_marker, persist_event_coordinate_facts,
+ persist_nip09_facts, read_source_state, rebuild_raw_heads, reconcile_raw_events,
+ reconciliation_profile, rotate_source_state, synchronize_addressable_heads,
+ update_source_authority, validate_active_hook_state_fast, validate_baseline_authority,
+ validate_raw_source_rebuild_core_with_events_v1, validate_rebuild_marker_absent,
+ validate_source_raw_authority_with_state,
+};
+use crate::migrations::{
+ EVENT_STORE_LEDGER_NAME, EVENT_STORE_MIGRATIONS, EVENT_STORE_RESERVED_PREFIX,
+};
+use crate::model::{
+ RadrootsEventStoreActiveProductStateDigestV1, RadrootsEventStoreImmutableRawDigestV1,
+ RadrootsEventStoreRawSourceRebuildReportV1,
+};
+use crate::schema::validate_exact_managed_v4_for_raw_source_rebuild_v1;
+use crate::source_maintenance_v1::{
+ preflight_source_generation_append_v1, validate_source_capacity_authority_fast_v1,
+ validate_source_capacity_authority_full_v1,
+};
+use crate::store::food_availability_projection_v1::{
+ reset_and_replay_food_availability_from_raw_v1,
+ validate_food_availability_projection_hook_state_fast_v1,
+ validate_food_availability_projection_hook_v1,
+};
+use futures::TryStreamExt;
+use sha2::{Digest, Sha256};
+use sqlx::{Row, Sqlite, SqliteConnection, SqlitePool, Transaction};
+use std::collections::BTreeSet;
+
+use crate::{
+ RadrootsEventStoreCallerInboundForeignKeyV1, RadrootsEventStoreError,
+ RadrootsEventStoreRawSourceRebuildDriftV1, RadrootsEventStoreSourceGeneration,
+};
+
+const IMMUTABLE_RAW_DIGEST_DOMAIN_V1: &[u8] = b"radroots:event-store:immutable-raw-digest:v1\0";
+const ACTIVE_PRODUCT_STATE_DIGEST_DOMAIN_V1: &[u8] =
+ b"radroots:event-store:active-product-state-digest:v1\0";
+const TRANSITION_SEQUENCE_NAME: &str = "radroots_event_store_addressable_head_transition";
+const RAW_SOURCE_REBUILD_CALLER_MAIN_TABLE_COUNT_LIMIT_V1: u32 = 4_096;
+const RAW_SOURCE_REBUILD_CALLER_FOREIGN_KEY_ROW_COUNT_LIMIT_V1: u32 = 4_096;
+const REBUILD_OWNED_TABLES_V1: &[&str] = &[
+ "event_envelopes",
+ "event_envelope_tags",
+ "event_envelope_head",
+ "radroots_event_store_source_generation",
+ "radroots_event_store_source_rebuild_commit_barrier",
+ "radroots_event_store_source_rebuild_marker",
+ "radroots_event_store_source_state",
+ "radroots_event_store_write_lock",
+ "radroots_event_store_source_capacity_v1",
+ "radroots_event_store_event_coordinate",
+ "radroots_event_store_nip09_request",
+ "radroots_event_store_nip09_event_target",
+ "radroots_event_store_nip09_address_target",
+ "radroots_event_store_addressable_head_state",
+ "radroots_event_store_addressable_head_transition",
+ "radroots_event_store_addressable_feed_integrity_v1",
+ "radroots_event_store_food_availability_cursor",
+ "radroots_event_store_food_availability_projection",
+ "radroots_event_store_food_availability_image",
+];
+const RAW_SOURCE_REBUILD_MUTATED_PARENT_TABLES_V1: &[&str] = &[
+ "event_envelopes",
+ "event_envelope_tags",
+ "event_envelope_head",
+ "radroots_event_store_source_generation",
+ "radroots_event_store_source_rebuild_commit_barrier",
+ "radroots_event_store_source_rebuild_marker",
+ "radroots_event_store_source_state",
+ "radroots_event_store_write_lock",
+ "radroots_event_store_source_capacity_v1",
+ "radroots_event_store_event_coordinate",
+ "radroots_event_store_nip09_request",
+ "radroots_event_store_nip09_event_target",
+ "radroots_event_store_nip09_address_target",
+ "radroots_event_store_addressable_head_state",
+ "radroots_event_store_addressable_head_transition",
+ "radroots_event_store_addressable_feed_integrity_v1",
+ "radroots_event_store_food_availability_cursor",
+ "radroots_event_store_food_availability_projection",
+ "radroots_event_store_food_availability_image",
+ "radroots_event_store_food_availability_search_fts",
+ "radroots_event_store_food_availability_search_fts_config",
+ "radroots_event_store_food_availability_search_fts_content",
+ "radroots_event_store_food_availability_search_fts_data",
+ "radroots_event_store_food_availability_search_fts_docsize",
+ "radroots_event_store_food_availability_search_fts_idx",
+ "sqlite_sequence",
+];
+
+#[derive(Clone, Copy)]
+struct RawSourceRebuildCallerSchemaLimitsV1 {
+ main_tables: u32,
+ foreign_key_rows: u32,
+}
+
+impl RawSourceRebuildCallerSchemaLimitsV1 {
+ const fn production() -> Self {
+ Self {
+ main_tables: RAW_SOURCE_REBUILD_CALLER_MAIN_TABLE_COUNT_LIMIT_V1,
+ foreign_key_rows: RAW_SOURCE_REBUILD_CALLER_FOREIGN_KEY_ROW_COUNT_LIMIT_V1,
+ }
+ }
+}
+
+#[cfg(test)]
+#[allow(clippy::enum_variant_names)] // Variants mirror the governed after_* failpoint IDs.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) enum RawSourceRebuildFailpointV1 {
+ AfterMarkerOpen,
+ AfterGenerationRotation,
+ AfterCoreReplay,
+ AfterVisibilityAudit,
+ AfterFoodResetAndReplay,
+ AfterFoodAudit,
+ AfterMarkerClose,
+}
+
+#[cfg(not(test))]
+type RawSourceRebuildFailpointV1 = ();
+
+#[cfg(test)]
+impl RawSourceRebuildFailpointV1 {
+ const fn as_str(self) -> &'static str {
+ match self {
+ Self::AfterMarkerOpen => "after_marker_open",
+ Self::AfterGenerationRotation => "after_generation_rotation",
+ Self::AfterCoreReplay => "after_core_replay",
+ Self::AfterVisibilityAudit => "after_visibility_audit",
+ Self::AfterFoodResetAndReplay => "after_food_reset_replay",
+ Self::AfterFoodAudit => "after_food_audit",
+ Self::AfterMarkerClose => "after_marker_close",
+ }
+ }
+}
+
+pub(crate) async fn rebuild_from_raw_v1_on_pool(
+ pool: &SqlitePool,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ rebuild_from_raw_v1_on_pool_inner(
+ pool,
+ &OsSourceGenerationProvider,
+ None,
+ RawSourceRebuildCallerSchemaLimitsV1::production(),
+ )
+ .await
+}
+
+#[cfg(test)]
+pub(crate) async fn rebuild_from_raw_v1_on_pool_for_test(
+ pool: &SqlitePool,
+ generation_provider: &dyn SourceGenerationProvider,
+ failpoint: Option<RawSourceRebuildFailpointV1>,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ rebuild_from_raw_v1_on_pool_inner(
+ pool,
+ generation_provider,
+ failpoint,
+ RawSourceRebuildCallerSchemaLimitsV1::production(),
+ )
+ .await
+}
+
+#[cfg(test)]
+pub(crate) async fn rebuild_from_raw_v1_on_pool_with_caller_schema_limits_for_test(
+ pool: &SqlitePool,
+ generation_provider: &dyn SourceGenerationProvider,
+ main_table_limit: u32,
+ foreign_key_row_limit: u32,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ rebuild_from_raw_v1_on_pool_inner(
+ pool,
+ generation_provider,
+ None,
+ RawSourceRebuildCallerSchemaLimitsV1 {
+ main_tables: main_table_limit,
+ foreign_key_rows: foreign_key_row_limit,
+ },
+ )
+ .await
+}
+
+#[cfg(test)]
+pub(crate) async fn rebuild_from_raw_v1_in_transaction_for_test(
+ connection: &mut SqliteConnection,
+ generation_provider: &dyn SourceGenerationProvider,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ rebuild_from_raw_v1_in_transaction_inner(
+ connection,
+ generation_provider,
+ None,
+ RawSourceRebuildCallerSchemaLimitsV1::production(),
+ )
+ .await
+}
+
+async fn rebuild_from_raw_v1_on_pool_inner(
+ pool: &SqlitePool,
+ generation_provider: &dyn SourceGenerationProvider,
+ failpoint: Option<RawSourceRebuildFailpointV1>,
+ caller_schema_limits: RawSourceRebuildCallerSchemaLimitsV1,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ let mut transaction = pool.begin_with("BEGIN IMMEDIATE").await?;
+ let result = rebuild_from_raw_v1_in_transaction_inner(
+ &mut transaction,
+ generation_provider,
+ failpoint,
+ caller_schema_limits,
+ )
+ .await;
+ finish_raw_source_rebuild_transaction(transaction, result).await
+}
+
+pub(crate) async fn rebuild_from_raw_v1_in_existing_transaction(
+ mut transaction: Transaction<'_, Sqlite>,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ let result = rebuild_from_raw_v1_in_transaction_inner(
+ &mut transaction,
+ &OsSourceGenerationProvider,
+ None,
+ RawSourceRebuildCallerSchemaLimitsV1::production(),
+ )
+ .await;
+ finish_raw_source_rebuild_transaction(transaction, result).await
+}
+
+async fn rebuild_from_raw_v1_in_transaction_inner(
+ connection: &mut SqliteConnection,
+ generation_provider: &dyn SourceGenerationProvider,
+ _failpoint: Option<RawSourceRebuildFailpointV1>,
+ caller_schema_limits: RawSourceRebuildCallerSchemaLimitsV1,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ validate_exact_managed_v4_for_raw_source_rebuild_v1(connection).await?;
+ preflight_caller_owned_schema_dependencies_v1(connection, caller_schema_limits).await?;
+ validate_rebuild_marker_absent(connection).await?;
+ validate_source_capacity_authority_full_v1(connection).await?;
+ preflight_source_generation_append_v1(connection).await?;
+
+ let snapshot =
+ load_reconciliation_snapshot(connection, ReconciliationCapacityLimits::production())
+ .await?;
+ let transition_floor_seq = transition_high_water_v1(connection).await?;
+ let prior =
+ validate_source_lineage_for_rebuild_v1(connection, &snapshot.events, transition_floor_seq)
+ .await?;
+ let immutable_raw_digest = immutable_raw_digest_v1(connection).await?;
+ if transition_floor_seq == i64::MAX {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ "addressable transition sequence space is exhausted at SQLite INTEGER maximum",
+ );
+ }
+ let mut generation_bytes = [0_u8; 32];
+ generation_provider.fill_generation(&mut generation_bytes)?;
+ let generation = RadrootsEventStoreSourceGeneration::from_bytes(generation_bytes);
+ let generation_exists: i64 = sqlx::query_scalar(
+ "SELECT EXISTS (SELECT 1 FROM radroots_event_store_source_generation WHERE source_generation = ?)",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch_one(&mut *connection)
+ .await?;
+ if generation_exists != 0 {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "fresh source generation collided with retained lineage",
+ );
+ }
+
+ let raw_event_count = i64::try_from(snapshot.capacity.raw_events).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::ImmutableRawAuthority,
+ "raw event count exceeds SQLite integer range",
+ )
+ })?;
+ let raw_tag_count = i64::try_from(snapshot.capacity.raw_tags).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::ImmutableRawAuthority,
+ "raw tag count exceeds SQLite integer range",
+ )
+ })?;
+ let raw_high_water_seq = snapshot.events.last().map(|event| event.seq).unwrap_or(0);
+ let generation_ordinal: i64 = sqlx::query_scalar(
+ "SELECT COALESCE(MAX(generation_ordinal), 0) + 1 FROM radroots_event_store_source_generation",
+ )
+ .fetch_one(&mut *connection)
+ .await?;
+ let plan = SourceRebuildPlan {
+ generation,
+ generation_ordinal,
+ transition_floor_seq,
+ raw_event_count,
+ raw_tag_count,
+ raw_high_water_seq,
+ prior: Some(prior.clone()),
+ };
+
+ let marker = open_source_rebuild_marker(connection, &plan).await?;
+ #[cfg(test)]
+ inject_raw_source_rebuild_failpoint_v1(
+ _failpoint,
+ RawSourceRebuildFailpointV1::AfterMarkerOpen,
+ )?;
+ append_source_generation(connection, &plan).await?;
+ rotate_source_state(connection, &plan).await?;
+ #[cfg(test)]
+ inject_raw_source_rebuild_failpoint_v1(
+ _failpoint,
+ RawSourceRebuildFailpointV1::AfterGenerationRotation,
+ )?;
+ let transition_sequence_rowid =
+ prepare_transition_sqlite_sequence_v1(connection, transition_floor_seq).await?;
+
+ reconcile_raw_events(connection, &snapshot.events).await?;
+ persist_event_coordinate_facts(connection, generation, &snapshot.events).await?;
+ rebuild_raw_heads(connection, &snapshot.events).await?;
+ let requests = persist_nip09_facts(connection, generation, &snapshot.events).await?;
+ synchronize_addressable_heads(
+ connection,
+ generation,
+ &snapshot.events,
+ &requests,
+ TransitionOrigin::Baseline,
+ None,
+ "baseline_rebuild",
+ )
+ .await?;
+ update_source_authority(
+ connection,
+ raw_event_count,
+ raw_tag_count,
+ raw_high_water_seq,
+ )
+ .await?;
+ let replay_transition_high_water = transition_high_water_v1(connection).await?;
+ validate_transition_sqlite_sequence_v1(
+ connection,
+ transition_sequence_rowid,
+ replay_transition_high_water,
+ )
+ .await?;
+ validate_raw_source_rebuild_core_with_events_v1(connection, generation, &snapshot.events)
+ .await?;
+ #[cfg(test)]
+ inject_raw_source_rebuild_failpoint_v1(
+ _failpoint,
+ RawSourceRebuildFailpointV1::AfterCoreReplay,
+ )?;
+
+ let derived_visibility = load_derived_visibility_rows_v1(connection, generation).await?;
+ audit_current_visibility_from_raw_v1(&snapshot.events, derived_visibility).await?;
+ #[cfg(test)]
+ inject_raw_source_rebuild_failpoint_v1(
+ _failpoint,
+ RawSourceRebuildFailpointV1::AfterVisibilityAudit,
+ )?;
+
+ crate::source_maintenance_v1::bind_source_capacity_to_generation_v1(connection, generation)
+ .await?;
+ reset_and_replay_food_availability_from_raw_v1(connection, generation).await?;
+ #[cfg(test)]
+ inject_raw_source_rebuild_failpoint_v1(
+ _failpoint,
+ RawSourceRebuildFailpointV1::AfterFoodResetAndReplay,
+ )?;
+ validate_food_availability_projection_hook_v1(connection).await?;
+ #[cfg(test)]
+ inject_raw_source_rebuild_failpoint_v1(
+ _failpoint,
+ RawSourceRebuildFailpointV1::AfterFoodAudit,
+ )?;
+
+ let final_raw_digest = immutable_raw_digest_v1(connection).await?;
+ if final_raw_digest != immutable_raw_digest {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::ImmutableRawAuthority,
+ "immutable raw digest changed during source rebuild",
+ );
+ }
+ close_source_rebuild_marker(connection, marker).await?;
+ #[cfg(test)]
+ inject_raw_source_rebuild_failpoint_v1(
+ _failpoint,
+ RawSourceRebuildFailpointV1::AfterMarkerClose,
+ )?;
+
+ validate_active_hook_state_fast(connection).await?;
+ let source_capacity = validate_source_capacity_authority_fast_v1(connection).await?;
+ validate_food_availability_projection_hook_state_fast_v1(connection).await?;
+ validate_scoped_integrity_v1(connection).await?;
+ let active_product_state_digest =
+ active_product_state_digest_v1(connection, generation).await?;
+ if source_capacity.source_generation() != generation
+ || source_capacity.raw_event_count() != snapshot.capacity.raw_events
+ || source_capacity.raw_tag_count() != snapshot.capacity.raw_tags
+ || source_capacity.raw_event_text_bytes() != snapshot.capacity.raw_event_bytes
+ || source_capacity.raw_tag_text_bytes() != snapshot.capacity.raw_tag_bytes
+ || source_capacity.raw_high_water_seq() != raw_high_water_seq
+ || source_capacity.retained_generation_count()
+ != u32::try_from(generation_ordinal).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "generation ordinal exceeds u32 range",
+ )
+ })?
+ || prior.generation == generation
+ {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ "committed rebuild report authority is inconsistent",
+ );
+ }
+ Ok(RadrootsEventStoreRawSourceRebuildReportV1 {
+ prior_source_generation: prior.generation,
+ new_source_generation: generation,
+ source_capacity,
+ immutable_raw_digest,
+ active_product_state_digest,
+ })
+}
+
+async fn preflight_caller_owned_schema_dependencies_v1(
+ connection: &mut SqliteConnection,
+ limits: RawSourceRebuildCallerSchemaLimitsV1,
+) -> Result<(), RadrootsEventStoreError> {
+ let governed_names_json = governed_schema_names_json_v1()?;
+ let mutated_parent_tables_json =
+ serde_json::to_string(RAW_SOURCE_REBUILD_MUTATED_PARENT_TABLES_V1)?;
+
+ let caller_main_table_count: i64 = sqlx::query_scalar(
+ "WITH governed(name) AS (
+ SELECT CAST(value AS TEXT) COLLATE NOCASE FROM main.json_each(?)
+ )
+ SELECT COUNT(*)
+ FROM (
+ SELECT 1
+ FROM main.sqlite_schema AS child
+ WHERE child.type = 'table'
+ AND lower(substr(child.name, 1, 7)) != 'sqlite_'
+ AND child.name COLLATE NOCASE NOT IN (SELECT name FROM governed)
+ AND lower(substr(child.name, 1, length(?))) != lower(?)
+ LIMIT ?
+ )",
+ )
+ .bind(&governed_names_json)
+ .bind(EVENT_STORE_RESERVED_PREFIX)
+ .bind(EVENT_STORE_RESERVED_PREFIX)
+ .bind(i64::from(limits.main_tables) + 1)
+ .fetch_one(&mut *connection)
+ .await?;
+ let caller_main_table_count = caller_schema_count_v1(caller_main_table_count)?;
+ if caller_main_table_count > u64::from(limits.main_tables) {
+ return Err(
+ RadrootsEventStoreError::RawSourceRebuildCallerTableCapacityExceeded {
+ observed_at_least: caller_main_table_count,
+ limit: u64::from(limits.main_tables),
+ },
+ );
+ }
+
+ let caller_foreign_key_row_count: i64 = sqlx::query_scalar(
+ "WITH governed(name) AS (
+ SELECT CAST(value AS TEXT) COLLATE NOCASE FROM main.json_each(?)
+ )
+ SELECT COUNT(*)
+ FROM (
+ SELECT 1
+ FROM main.sqlite_schema AS child
+ JOIN main.pragma_foreign_key_list(child.name, 'main') AS foreign_key
+ WHERE child.type = 'table'
+ AND lower(substr(child.name, 1, 7)) != 'sqlite_'
+ AND child.name COLLATE NOCASE NOT IN (SELECT name FROM governed)
+ AND lower(substr(child.name, 1, length(?))) != lower(?)
+ LIMIT ?
+ )",
+ )
+ .bind(&governed_names_json)
+ .bind(EVENT_STORE_RESERVED_PREFIX)
+ .bind(EVENT_STORE_RESERVED_PREFIX)
+ .bind(i64::from(limits.foreign_key_rows) + 1)
+ .fetch_one(&mut *connection)
+ .await?;
+ let caller_foreign_key_row_count = caller_schema_count_v1(caller_foreign_key_row_count)?;
+ if caller_foreign_key_row_count > u64::from(limits.foreign_key_rows) {
+ return Err(
+ RadrootsEventStoreError::RawSourceRebuildCallerForeignKeyCapacityExceeded {
+ observed_at_least: caller_foreign_key_row_count,
+ limit: u64::from(limits.foreign_key_rows),
+ },
+ );
+ }
+
+ let dependency = sqlx::query(
+ "WITH governed(name) AS (
+ SELECT CAST(value AS TEXT) COLLATE NOCASE FROM main.json_each(?)
+ ), rebuild_parent(name) AS (
+ SELECT CAST(value AS TEXT) COLLATE NOCASE FROM main.json_each(?)
+ )
+ SELECT
+ child.name AS child_table,
+ foreign_key.id AS foreign_key_id,
+ foreign_key.seq AS foreign_key_sequence,
+ foreign_key.\"from\" AS child_column,
+ rebuild_parent.name AS parent_table,
+ foreign_key.\"to\" AS parent_column,
+ foreign_key.on_update AS on_update,
+ foreign_key.on_delete AS on_delete,
+ foreign_key.\"match\" AS match_clause
+ FROM main.sqlite_schema AS child
+ JOIN main.pragma_foreign_key_list(child.name, 'main') AS foreign_key
+ JOIN rebuild_parent
+ ON foreign_key.\"table\" COLLATE NOCASE = rebuild_parent.name
+ WHERE child.type = 'table'
+ AND lower(substr(child.name, 1, 7)) != 'sqlite_'
+ AND child.name COLLATE NOCASE NOT IN (SELECT name FROM governed)
+ AND lower(substr(child.name, 1, length(?))) != lower(?)
+ ORDER BY child.name COLLATE NOCASE, child.name, foreign_key.id, foreign_key.seq
+ LIMIT 1",
+ )
+ .bind(&governed_names_json)
+ .bind(&mutated_parent_tables_json)
+ .bind(EVENT_STORE_RESERVED_PREFIX)
+ .bind(EVENT_STORE_RESERVED_PREFIX)
+ .fetch_optional(&mut *connection)
+ .await?;
+ if let Some(dependency) = dependency {
+ return Err(
+ RadrootsEventStoreError::RawSourceRebuildCallerInboundForeignKeyUnsupported {
+ dependency: Box::new(RadrootsEventStoreCallerInboundForeignKeyV1 {
+ child_table: dependency.try_get("child_table")?,
+ foreign_key_id: dependency.try_get("foreign_key_id")?,
+ foreign_key_sequence: dependency.try_get("foreign_key_sequence")?,
+ child_column: dependency.try_get("child_column")?,
+ parent_table: dependency.try_get("parent_table")?,
+ parent_column: dependency.try_get("parent_column")?,
+ on_update: dependency.try_get("on_update")?,
+ on_delete: dependency.try_get("on_delete")?,
+ match_clause: dependency.try_get("match_clause")?,
+ }),
+ },
+ );
+ }
+ Ok(())
+}
+
+fn governed_schema_names_json_v1() -> Result<String, RadrootsEventStoreError> {
+ let mut names = EVENT_STORE_MIGRATIONS
+ .iter()
+ .flat_map(|migration| migration.owned_object_names.iter().copied())
+ .collect::<BTreeSet<_>>();
+ names.insert(EVENT_STORE_LEDGER_NAME);
+ Ok(serde_json::to_string(&names)?)
+}
+
+fn caller_schema_count_v1(count: i64) -> Result<u64, RadrootsEventStoreError> {
+ u64::try_from(count).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ "caller-owned schema inventory returned a negative row count",
+ )
+ })
+}
+
+async fn load_derived_visibility_rows_v1(
+ connection: &mut SqliteConnection,
+ generation: RadrootsEventStoreSourceGeneration,
+) -> Result<Vec<VisibilityOracleFactV1>, RadrootsEventStoreError> {
+ let rows = sqlx::query(
+ "SELECT event.event_id, event.contract_status AS admission_status, event.contract_id, event.event_class, visibility.raw_d_tag, visibility.is_raw_head, visibility.raw_head_event_id, visibility.suppression_outcome, visibility.suppression_reason, visibility.event_reference_request_id, visibility.address_reference_request_id, visibility.address_reference_cutoff, visibility.current_visibility, visibility.source_generation FROM event_envelopes AS event JOIN radroots_event_store_current_visibility_v1 AS visibility ON visibility.event_id = event.event_id WHERE visibility.source_generation = ? ORDER BY event.seq",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch_all(&mut *connection)
+ .await?;
+ let mut actual = Vec::with_capacity(rows.len());
+ for row in rows {
+ let stored_generation: Vec<u8> = row.try_get("source_generation")?;
+ if stored_generation.as_slice() != generation.as_bytes().as_slice() {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::DerivedProductStateAuthority,
+ "current visibility exposed a foreign source generation",
+ );
+ }
+ actual.push(VisibilityOracleFactV1 {
+ event_id: row.try_get("event_id")?,
+ admission_status: row.try_get("admission_status")?,
+ contract_id: row.try_get("contract_id")?,
+ event_class: row.try_get("event_class")?,
+ raw_d_tag: row.try_get("raw_d_tag")?,
+ is_raw_head: row.try_get("is_raw_head")?,
+ raw_head_event_id: row.try_get("raw_head_event_id")?,
+ suppression_outcome: row.try_get("suppression_outcome")?,
+ suppression_reason: row.try_get("suppression_reason")?,
+ event_reference_request_id: row.try_get("event_reference_request_id")?,
+ address_reference_request_id: row.try_get("address_reference_request_id")?,
+ address_reference_cutoff: row.try_get("address_reference_cutoff")?,
+ current_visibility: row.try_get("current_visibility")?,
+ });
+ }
+ Ok(actual)
+}
+
+async fn validate_source_lineage_for_rebuild_v1(
+ connection: &mut SqliteConnection,
+ events: &[ReconciledEvent],
+ terminal_transition_high_water: i64,
+) -> Result<SourceState, RadrootsEventStoreError> {
+ let state = read_source_state(connection).await?;
+ validate_source_raw_authority_with_state(connection, &state).await?;
+ validate_baseline_authority(connection, &state, events).await?;
+ let rows = sqlx::query(
+ "SELECT source_generation, generation_ordinal, reconciliation_version, addressable_feed_version, event_contract_registry_version, hook_id, hook_manifest_sha256, transition_floor_seq, baseline_raw_event_count, baseline_raw_tag_count, baseline_raw_high_water_seq FROM radroots_event_store_source_generation ORDER BY generation_ordinal",
+ )
+ .fetch_all(&mut *connection)
+ .await?;
+ let capacity = validate_source_capacity_authority_fast_v1(connection).await?;
+ if rows.len() != usize::try_from(capacity.retained_generation_count()).unwrap_or(usize::MAX) {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "retained source-generation count does not match lineage rows",
+ );
+ }
+ if rows.is_empty() {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "retained source-generation lineage is empty",
+ );
+ }
+ let mut prior_baseline = (0_i64, 0_i64, 0_i64);
+ for (index, row) in rows.iter().enumerate() {
+ let ordinal: i64 = row.try_get("generation_ordinal")?;
+ let expected_ordinal = i64::try_from(index + 1).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "generation lineage exceeds SQLite range",
+ )
+ })?;
+ let generation = generation_from_blob(row.try_get("source_generation")?)?;
+ let hook_id: String = row.try_get("hook_id")?;
+ let hook_manifest_sha256: String = row.try_get("hook_manifest_sha256")?;
+ reconciliation_profile(
+ row.try_get("reconciliation_version")?,
+ row.try_get("addressable_feed_version")?,
+ row.try_get("event_contract_registry_version")?,
+ hook_id.as_str(),
+ hook_manifest_sha256.as_str(),
+ )?;
+ let floor: i64 = row.try_get("transition_floor_seq")?;
+ let baseline_events: i64 = row.try_get("baseline_raw_event_count")?;
+ let baseline_tags: i64 = row.try_get("baseline_raw_tag_count")?;
+ let baseline_high_water: i64 = row.try_get("baseline_raw_high_water_seq")?;
+ let expected_baseline_events =
+ i64::try_from(events.partition_point(|event| event.seq <= baseline_high_water))
+ .map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "historical raw baseline exceeds SQLite range",
+ )
+ })?;
+ let expected_baseline_high_water = if expected_baseline_events == 0 {
+ 0
+ } else {
+ let final_index = usize::try_from(expected_baseline_events - 1).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "historical raw baseline is negative",
+ )
+ })?;
+ events
+ .get(final_index)
+ .map(|event| event.seq)
+ .ok_or_else(|| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "historical raw baseline is out of range",
+ )
+ })?
+ };
+ let expected_baseline_tags: i64 = sqlx::query_scalar(
+ "SELECT COUNT(*) FROM event_envelope_tags AS tag JOIN event_envelopes AS event ON event.event_id = tag.event_id WHERE event.seq <= ?",
+ )
+ .bind(baseline_high_water)
+ .fetch_one(&mut *connection)
+ .await?;
+ let transition_end: i64 = if let Some(next) = rows.get(index + 1) {
+ next.try_get("transition_floor_seq")?
+ } else {
+ terminal_transition_high_water
+ };
+ let transition_bounds = sqlx::query(
+ "SELECT COUNT(*) AS transition_count, MIN(transition_seq) AS first_transition_seq, MAX(transition_seq) AS last_transition_seq FROM radroots_event_store_addressable_head_transition WHERE source_generation = ?",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch_one(&mut *connection)
+ .await?;
+ let transition_count: i64 = transition_bounds.try_get("transition_count")?;
+ let first_transition_seq: Option<i64> =
+ transition_bounds.try_get("first_transition_seq")?;
+ let last_transition_seq: Option<i64> = transition_bounds.try_get("last_transition_seq")?;
+ let expected_transition_count = transition_end.checked_sub(floor).ok_or_else(|| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ format!(
+ "source-generation lineage row {expected_ordinal} has an inverted transition interval"
+ ),
+ )
+ })?;
+ let expected_first_transition = if expected_transition_count == 0 {
+ None
+ } else {
+ Some(floor.checked_add(1).ok_or_else(|| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ "historical transition sequence overflow",
+ )
+ })?)
+ };
+ let expected_last_transition = (expected_transition_count != 0).then_some(transition_end);
+ if ordinal != expected_ordinal
+ || baseline_events < 0
+ || baseline_tags < 0
+ || baseline_high_water < 0
+ || baseline_events > state.raw_event_count
+ || baseline_tags > state.raw_tag_count
+ || baseline_high_water > state.raw_high_water_seq
+ || baseline_events < prior_baseline.0
+ || baseline_tags < prior_baseline.1
+ || baseline_high_water < prior_baseline.2
+ || baseline_events != expected_baseline_events
+ || baseline_tags != expected_baseline_tags
+ || baseline_high_water != expected_baseline_high_water
+ {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ format!("source-generation lineage row {expected_ordinal} is inconsistent"),
+ );
+ }
+ if (index == 0 && floor != 0)
+ || transition_count != expected_transition_count
+ || first_transition_seq != expected_first_transition
+ || last_transition_seq != expected_last_transition
+ {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ format!(
+ "source-generation lineage row {expected_ordinal} has inconsistent addressable transition authority"
+ ),
+ );
+ }
+ if index + 1 == rows.len() && generation != state.generation {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "active source generation is not the terminal lineage row",
+ );
+ }
+ prior_baseline = (baseline_events, baseline_tags, baseline_high_water);
+ }
+ let transition_summary = sqlx::query(
+ "SELECT COUNT(*) AS transition_count, MIN(transition_seq) AS first_transition_seq, MAX(transition_seq) AS last_transition_seq FROM radroots_event_store_addressable_head_transition",
+ )
+ .fetch_one(&mut *connection)
+ .await?;
+ let transition_count: i64 = transition_summary.try_get("transition_count")?;
+ let first_transition_seq: Option<i64> = transition_summary.try_get("first_transition_seq")?;
+ let last_transition_seq: Option<i64> = transition_summary.try_get("last_transition_seq")?;
+ let expected_first_transition = (terminal_transition_high_water != 0).then_some(1);
+ let expected_last_transition =
+ (terminal_transition_high_water != 0).then_some(terminal_transition_high_water);
+ if transition_count != terminal_transition_high_water
+ || first_transition_seq != expected_first_transition
+ || last_transition_seq != expected_last_transition
+ {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ "retained source-generation transition lineage has gaps or foreign rows",
+ );
+ }
+ Ok(state)
+}
+
+async fn transition_high_water_v1(
+ connection: &mut SqliteConnection,
+) -> Result<i64, RadrootsEventStoreError> {
+ Ok(sqlx::query_scalar(
+ "SELECT COALESCE(MAX(transition_seq), 0) FROM radroots_event_store_addressable_head_transition",
+ )
+ .fetch_one(&mut *connection)
+ .await?)
+}
+
+async fn prepare_transition_sqlite_sequence_v1(
+ connection: &mut SqliteConnection,
+ transition_max: i64,
+) -> Result<i64, RadrootsEventStoreError> {
+ if transition_max < 0 || transition_max == i64::MAX {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ format!(
+ "addressable transition sequence high-water {transition_max} cannot be normalized"
+ ),
+ );
+ }
+ let first: Option<(i64, Option<i64>)> = sqlx::query_as(
+ "SELECT rowid, name = ? COLLATE NOCASE FROM main.sqlite_sequence ORDER BY rowid LIMIT 1",
+ )
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .fetch_optional(&mut *connection)
+ .await?;
+ let target_rowid = match first {
+ None => -1,
+ Some((rowid, Some(1))) => rowid,
+ Some((i64::MIN, _)) => {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ "unrelated sqlite_sequence authority exhausts target-first rowid space",
+ );
+ }
+ Some((rowid, _)) => rowid - 1,
+ };
+ sqlx::query("DELETE FROM main.sqlite_sequence WHERE name COLLATE NOCASE = ?")
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(&mut *connection)
+ .await?;
+ sqlx::query("INSERT INTO main.sqlite_sequence(rowid, name, seq) VALUES (?, ?, ?)")
+ .bind(target_rowid)
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .bind(transition_max)
+ .execute(&mut *connection)
+ .await?;
+ validate_transition_sqlite_sequence_v1(connection, target_rowid, transition_max).await?;
+ Ok(target_rowid)
+}
+
+async fn validate_transition_sqlite_sequence_v1(
+ connection: &mut SqliteConnection,
+ target_rowid: i64,
+ transition_max: i64,
+) -> Result<(), RadrootsEventStoreError> {
+ let normalized: Option<(String, Option<i64>)> =
+ sqlx::query_as("SELECT name, seq FROM main.sqlite_sequence WHERE rowid = ?")
+ .bind(target_rowid)
+ .fetch_optional(&mut *connection)
+ .await?;
+ let first_rowid: Option<i64> =
+ sqlx::query_scalar("SELECT rowid FROM main.sqlite_sequence ORDER BY rowid LIMIT 1")
+ .fetch_optional(&mut *connection)
+ .await?;
+ if normalized != Some((TRANSITION_SEQUENCE_NAME.to_owned(), Some(transition_max)))
+ || first_rowid != Some(target_rowid)
+ {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ "addressable transition sqlite_sequence target-first authority is inconsistent",
+ );
+ }
+ Ok(())
+}
+
+async fn immutable_raw_digest_v1(
+ connection: &mut SqliteConnection,
+) -> Result<RadrootsEventStoreImmutableRawDigestV1, RadrootsEventStoreError> {
+ let mut digest = Sha256::new();
+ digest.update(IMMUTABLE_RAW_DIGEST_DOMAIN_V1);
+ digest_section(&mut digest, b"event_envelopes")?;
+ let mut events = sqlx::query(
+ "SELECT seq, event_id, pubkey, created_at, kind, tags_json, content, sig, raw_json, inserted_at_ms FROM event_envelopes ORDER BY seq",
+ )
+ .fetch(&mut *connection);
+ while let Some(row) = events.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_i64(&mut digest, row.try_get("seq")?);
+ digest_text(
+ &mut digest,
+ row.try_get::<String, _>("event_id")?.as_bytes(),
+ )?;
+ digest_text(&mut digest, row.try_get::<String, _>("pubkey")?.as_bytes())?;
+ digest_i64(&mut digest, row.try_get("created_at")?);
+ digest_i64(&mut digest, row.try_get("kind")?);
+ digest_text(
+ &mut digest,
+ row.try_get::<String, _>("tags_json")?.as_bytes(),
+ )?;
+ digest_text(&mut digest, row.try_get::<String, _>("content")?.as_bytes())?;
+ digest_text(&mut digest, row.try_get::<String, _>("sig")?.as_bytes())?;
+ digest_text(
+ &mut digest,
+ row.try_get::<String, _>("raw_json")?.as_bytes(),
+ )?;
+ digest_i64(&mut digest, row.try_get("inserted_at_ms")?);
+ }
+ drop(events);
+ digest_section(&mut digest, b"event_envelope_tags")?;
+ let mut tags = sqlx::query(
+ "SELECT event.seq, tag.event_id, tag.tag_index, tag.tag_name, tag.tag_value, tag.tag_json FROM event_envelope_tags AS tag JOIN event_envelopes AS event ON event.event_id = tag.event_id ORDER BY event.seq, tag.tag_index",
+ )
+ .fetch(&mut *connection);
+ while let Some(row) = tags.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_i64(&mut digest, row.try_get("seq")?);
+ digest_text(
+ &mut digest,
+ row.try_get::<String, _>("event_id")?.as_bytes(),
+ )?;
+ digest_i64(&mut digest, row.try_get("tag_index")?);
+ digest_text(
+ &mut digest,
+ row.try_get::<String, _>("tag_name")?.as_bytes(),
+ )?;
+ digest_optional_text(
+ &mut digest,
+ row.try_get::<Option<String>, _>("tag_value")?.as_deref(),
+ )?;
+ digest_text(
+ &mut digest,
+ row.try_get::<String, _>("tag_json")?.as_bytes(),
+ )?;
+ }
+ drop(tags);
+ Ok(RadrootsEventStoreImmutableRawDigestV1::from_bytes(
+ digest.finalize().into(),
+ ))
+}
+
+async fn active_product_state_digest_v1(
+ connection: &mut SqliteConnection,
+ generation: RadrootsEventStoreSourceGeneration,
+) -> Result<RadrootsEventStoreActiveProductStateDigestV1, RadrootsEventStoreError> {
+ let mut digest = Sha256::new();
+ digest.update(ACTIVE_PRODUCT_STATE_DIGEST_DOMAIN_V1);
+ digest_section(&mut digest, b"envelope_classification")?;
+ let mut envelope_classification = sqlx::query(
+ "SELECT event_id, verification_status, contract_status, contract_id, event_class, projection_eligible FROM event_envelopes ORDER BY event_id",
+ )
+ .fetch(&mut *connection);
+ while let Some(row) = envelope_classification.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "event_id")?;
+ digest_text_field(&mut digest, &row, "verification_status")?;
+ digest_text_field(&mut digest, &row, "contract_status")?;
+ digest_optional_text_field(&mut digest, &row, "contract_id")?;
+ digest_optional_text_field(&mut digest, &row, "event_class")?;
+ digest_bool_field(&mut digest, &row, "projection_eligible")?;
+ }
+ drop(envelope_classification);
+
+ digest_section(&mut digest, b"tag_classification")?;
+ let mut tag_classification = sqlx::query(
+ "SELECT event_id, tag_index, contract_semantic, contract_value_type, relay_indexed FROM event_envelope_tags ORDER BY event_id, tag_index",
+ )
+ .fetch(&mut *connection);
+ while let Some(row) = tag_classification.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "event_id")?;
+ digest_i64_field(&mut digest, &row, "tag_index")?;
+ digest_optional_text_field(&mut digest, &row, "contract_semantic")?;
+ digest_optional_text_field(&mut digest, &row, "contract_value_type")?;
+ digest_bool_field(&mut digest, &row, "relay_indexed")?;
+ }
+ drop(tag_classification);
+
+ digest_section(&mut digest, b"raw_heads")?;
+ let mut raw_heads = sqlx::query(
+ "SELECT coordinate_type, kind, pubkey, d_tag, event_id, created_at FROM event_envelope_head ORDER BY coordinate_type, kind, pubkey, d_tag",
+ )
+ .fetch(&mut *connection);
+ while let Some(row) = raw_heads.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "coordinate_type")?;
+ digest_i64_field(&mut digest, &row, "kind")?;
+ digest_text_field(&mut digest, &row, "pubkey")?;
+ digest_optional_text_field(&mut digest, &row, "d_tag")?;
+ digest_text_field(&mut digest, &row, "event_id")?;
+ digest_i64_field(&mut digest, &row, "created_at")?;
+ }
+ drop(raw_heads);
+
+ digest_section(&mut digest, b"event_coordinates")?;
+ let mut event_coordinates = sqlx::query(
+ "SELECT event_id, coordinate_type, kind, pubkey, created_at, admission_status, admission_code, contract_id, raw_d_tag, nip09_matchable, nip09_d_tag FROM radroots_event_store_event_coordinate WHERE source_generation = ? ORDER BY event_id",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = event_coordinates.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "event_id")?;
+ digest_text_field(&mut digest, &row, "coordinate_type")?;
+ digest_i64_field(&mut digest, &row, "kind")?;
+ digest_text_field(&mut digest, &row, "pubkey")?;
+ digest_i64_field(&mut digest, &row, "created_at")?;
+ digest_text_field(&mut digest, &row, "admission_status")?;
+ digest_optional_text_field(&mut digest, &row, "admission_code")?;
+ digest_optional_text_field(&mut digest, &row, "contract_id")?;
+ digest_text_field(&mut digest, &row, "raw_d_tag")?;
+ digest_bool_field(&mut digest, &row, "nip09_matchable")?;
+ digest_optional_text_field(&mut digest, &row, "nip09_d_tag")?;
+ }
+ drop(event_coordinates);
+
+ digest_section(&mut digest, b"nip09_requests")?;
+ let mut nip09_requests = sqlx::query(
+ "SELECT request_event_id, request_pubkey, request_created_at FROM radroots_event_store_nip09_request WHERE source_generation = ? ORDER BY request_event_id",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = nip09_requests.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "request_event_id")?;
+ digest_text_field(&mut digest, &row, "request_pubkey")?;
+ digest_i64_field(&mut digest, &row, "request_created_at")?;
+ }
+ drop(nip09_requests);
+
+ digest_section(&mut digest, b"nip09_event_targets")?;
+ let mut nip09_event_targets = sqlx::query(
+ "SELECT request_event_id, target_event_id, source_tag_index, source_tag_value FROM radroots_event_store_nip09_event_target WHERE source_generation = ? ORDER BY request_event_id, target_event_id, source_tag_index",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = nip09_event_targets.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "request_event_id")?;
+ digest_text_field(&mut digest, &row, "target_event_id")?;
+ digest_i64_field(&mut digest, &row, "source_tag_index")?;
+ digest_text_field(&mut digest, &row, "source_tag_value")?;
+ }
+ drop(nip09_event_targets);
+
+ digest_section(&mut digest, b"nip09_address_targets")?;
+ let mut nip09_address_targets = sqlx::query(
+ "SELECT request_event_id, target_kind, target_pubkey, target_d_tag, inclusive_cutoff, source_tag_index, source_tag_value, source_kind_text, source_pubkey_text, source_d_tag FROM radroots_event_store_nip09_address_target WHERE source_generation = ? ORDER BY request_event_id, target_kind, target_pubkey, target_d_tag, source_tag_index",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = nip09_address_targets.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "request_event_id")?;
+ digest_i64_field(&mut digest, &row, "target_kind")?;
+ digest_text_field(&mut digest, &row, "target_pubkey")?;
+ digest_text_field(&mut digest, &row, "target_d_tag")?;
+ digest_i64_field(&mut digest, &row, "inclusive_cutoff")?;
+ digest_i64_field(&mut digest, &row, "source_tag_index")?;
+ digest_text_field(&mut digest, &row, "source_tag_value")?;
+ digest_text_field(&mut digest, &row, "source_kind_text")?;
+ digest_text_field(&mut digest, &row, "source_pubkey_text")?;
+ digest_text_field(&mut digest, &row, "source_d_tag")?;
+ }
+ drop(nip09_address_targets);
+
+ digest_section(&mut digest, b"addressable_heads")?;
+ let mut addressable_heads = sqlx::query(
+ "SELECT kind, pubkey, d_tag, raw_head_event_id, raw_head_created_at, admission_status, admission_code, contract_id, visibility, nip09_outcome, nip09_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff FROM radroots_event_store_addressable_head_state WHERE source_generation = ? ORDER BY kind, pubkey, d_tag",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = addressable_heads.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_i64_field(&mut digest, &row, "kind")?;
+ digest_text_field(&mut digest, &row, "pubkey")?;
+ digest_text_field(&mut digest, &row, "d_tag")?;
+ digest_text_field(&mut digest, &row, "raw_head_event_id")?;
+ digest_i64_field(&mut digest, &row, "raw_head_created_at")?;
+ digest_text_field(&mut digest, &row, "admission_status")?;
+ digest_optional_text_field(&mut digest, &row, "admission_code")?;
+ digest_optional_text_field(&mut digest, &row, "contract_id")?;
+ digest_text_field(&mut digest, &row, "visibility")?;
+ digest_optional_text_field(&mut digest, &row, "nip09_outcome")?;
+ digest_optional_text_field(&mut digest, &row, "nip09_reason")?;
+ digest_optional_text_field(&mut digest, &row, "event_reference_request_id")?;
+ digest_optional_text_field(&mut digest, &row, "address_reference_request_id")?;
+ digest_optional_i64_field(&mut digest, &row, "address_reference_cutoff")?;
+ }
+ drop(addressable_heads);
+
+ digest_section(&mut digest, b"current_visibility")?;
+ let mut current_visibility = sqlx::query(
+ "SELECT event_id, admission_status, contract_id, event_class, raw_d_tag, is_raw_head, raw_head_event_id, suppression_outcome, suppression_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff, current_visibility FROM radroots_event_store_current_visibility_v1 WHERE source_generation = ? ORDER BY event_id",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = current_visibility.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "event_id")?;
+ digest_text_field(&mut digest, &row, "admission_status")?;
+ digest_optional_text_field(&mut digest, &row, "contract_id")?;
+ digest_text_field(&mut digest, &row, "event_class")?;
+ digest_optional_text_field(&mut digest, &row, "raw_d_tag")?;
+ digest_bool_field(&mut digest, &row, "is_raw_head")?;
+ digest_optional_text_field(&mut digest, &row, "raw_head_event_id")?;
+ digest_optional_text_field(&mut digest, &row, "suppression_outcome")?;
+ digest_optional_text_field(&mut digest, &row, "suppression_reason")?;
+ digest_optional_text_field(&mut digest, &row, "event_reference_request_id")?;
+ digest_optional_text_field(&mut digest, &row, "address_reference_request_id")?;
+ digest_optional_i64_field(&mut digest, &row, "address_reference_cutoff")?;
+ digest_text_field(&mut digest, &row, "current_visibility")?;
+ }
+ drop(current_visibility);
+
+ digest_section(&mut digest, b"food_projection")?;
+ let mut food_projection = sqlx::query(
+ "SELECT kind, pubkey, d_tag, event_id, created_at, contract_id, content, title, summary, published_at, location, price_amount, price_currency, price_unit, quantity_amount, quantity_unit, status, diagnostic_codes_json FROM radroots_event_store_food_availability_projection WHERE source_generation = ? ORDER BY pubkey, d_tag",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = food_projection.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_i64_field(&mut digest, &row, "kind")?;
+ for field in ["pubkey", "d_tag", "event_id"] {
+ digest_text_field(&mut digest, &row, field)?;
+ }
+ digest_i64_field(&mut digest, &row, "created_at")?;
+ for field in ["contract_id", "content", "title", "summary"] {
+ digest_text_field(&mut digest, &row, field)?;
+ }
+ digest_i64_field(&mut digest, &row, "published_at")?;
+ for field in ["location", "price_amount", "price_currency", "price_unit"] {
+ digest_text_field(&mut digest, &row, field)?;
+ }
+ digest_optional_text_field(&mut digest, &row, "quantity_amount")?;
+ digest_optional_text_field(&mut digest, &row, "quantity_unit")?;
+ digest_text_field(&mut digest, &row, "status")?;
+ digest_text_field(&mut digest, &row, "diagnostic_codes_json")?;
+ }
+ drop(food_projection);
+
+ digest_section(&mut digest, b"food_images")?;
+ let mut food_images = sqlx::query(
+ "SELECT pubkey, d_tag, image_index, raw_tag_json, url, width, height, blossom_sha256, qualifies, diagnostic_codes_json FROM radroots_event_store_food_availability_image WHERE source_generation = ? ORDER BY pubkey, d_tag, image_index",
+ )
+ .bind(generation.as_bytes().as_slice())
+ .fetch(&mut *connection);
+ while let Some(row) = food_images.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_text_field(&mut digest, &row, "pubkey")?;
+ digest_text_field(&mut digest, &row, "d_tag")?;
+ digest_i64_field(&mut digest, &row, "image_index")?;
+ digest_text_field(&mut digest, &row, "raw_tag_json")?;
+ digest_optional_text_field(&mut digest, &row, "url")?;
+ digest_optional_i64_field(&mut digest, &row, "width")?;
+ digest_optional_i64_field(&mut digest, &row, "height")?;
+ digest_optional_text_field(&mut digest, &row, "blossom_sha256")?;
+ digest_bool_field(&mut digest, &row, "qualifies")?;
+ digest_text_field(&mut digest, &row, "diagnostic_codes_json")?;
+ }
+ drop(food_images);
+
+ digest_section(&mut digest, b"food_search")?;
+ let mut food_search = sqlx::query(
+ "SELECT event_id, pubkey, d_tag, title, summary, content, location FROM radroots_event_store_food_availability_search_fts ORDER BY event_id",
+ )
+ .fetch(&mut *connection);
+ while let Some(row) = food_search.try_next().await? {
+ digest_row_start(&mut digest);
+ for field in [
+ "event_id", "pubkey", "d_tag", "title", "summary", "content", "location",
+ ] {
+ digest_text_field(&mut digest, &row, field)?;
+ }
+ }
+ drop(food_search);
+
+ digest_section(&mut digest, b"food_cursor")?;
+ let mut food_cursor = sqlx::query(
+ "SELECT feed_version, projection_version, scope_fingerprint, hook_manifest_sha256, projected_row_count FROM radroots_event_store_food_availability_cursor WHERE singleton = 1",
+ )
+ .fetch(&mut *connection);
+ while let Some(row) = food_cursor.try_next().await? {
+ digest_row_start(&mut digest);
+ digest_i64_field(&mut digest, &row, "feed_version")?;
+ digest_i64_field(&mut digest, &row, "projection_version")?;
+ digest_blob_field(&mut digest, &row, "scope_fingerprint")?;
+ digest_text_field(&mut digest, &row, "hook_manifest_sha256")?;
+ digest_i64_field(&mut digest, &row, "projected_row_count")?;
+ }
+ drop(food_cursor);
+ Ok(RadrootsEventStoreActiveProductStateDigestV1::from_bytes(
+ digest.finalize().into(),
+ ))
+}
+
+fn digest_section(digest: &mut Sha256, name: &[u8]) -> Result<(), RadrootsEventStoreError> {
+ digest.update(b"S");
+ digest_bytes(digest, b'N', name)
+}
+
+fn digest_row_start(digest: &mut Sha256) {
+ digest.update(b"R");
+}
+
+fn digest_i64(digest: &mut Sha256, value: i64) {
+ digest.update(b"I");
+ digest.update(value.to_be_bytes());
+}
+
+fn digest_bytes(
+ digest: &mut Sha256,
+ marker: u8,
+ value: &[u8],
+) -> Result<(), RadrootsEventStoreError> {
+ let length = u64::try_from(value.len()).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ "digest field length exceeds u64",
+ )
+ })?;
+ digest.update([marker]);
+ digest.update(length.to_be_bytes());
+ digest.update(value);
+ Ok(())
+}
+
+fn digest_text(digest: &mut Sha256, value: &[u8]) -> Result<(), RadrootsEventStoreError> {
+ digest_bytes(digest, b'T', value)
+}
+
+fn digest_optional_text(
+ digest: &mut Sha256,
+ value: Option<&str>,
+) -> Result<(), RadrootsEventStoreError> {
+ match value {
+ Some(value) => {
+ digest.update([b'O', 1]);
+ digest_text(digest, value.as_bytes())
+ }
+ None => {
+ digest.update([b'O', 0]);
+ Ok(())
+ }
+ }
+}
+
+fn digest_optional_i64(digest: &mut Sha256, value: Option<i64>) {
+ match value {
+ Some(value) => {
+ digest.update([b'O', 1]);
+ digest_i64(digest, value);
+ }
+ None => digest.update([b'O', 0]),
+ }
+}
+
+fn digest_bool(digest: &mut Sha256, value: i64) -> Result<(), RadrootsEventStoreError> {
+ match value {
+ 0 | 1 => {
+ digest.update([b'B', if value == 0 { 0 } else { 1 }]);
+ Ok(())
+ }
+ _ => rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::DerivedProductStateAuthority,
+ format!("digest boolean field has invalid value {value}"),
+ ),
+ }
+}
+
+fn digest_text_field(
+ digest: &mut Sha256,
+ row: &sqlx::sqlite::SqliteRow,
+ field: &'static str,
+) -> Result<(), RadrootsEventStoreError> {
+ let value: String = row.try_get(field)?;
+ digest_text(digest, value.as_bytes())
+}
+
+fn digest_optional_text_field(
+ digest: &mut Sha256,
+ row: &sqlx::sqlite::SqliteRow,
+ field: &'static str,
+) -> Result<(), RadrootsEventStoreError> {
+ let value: Option<String> = row.try_get(field)?;
+ digest_optional_text(digest, value.as_deref())
+}
+
+fn digest_i64_field(
+ digest: &mut Sha256,
+ row: &sqlx::sqlite::SqliteRow,
+ field: &'static str,
+) -> Result<(), RadrootsEventStoreError> {
+ digest_i64(digest, row.try_get(field)?);
+ Ok(())
+}
+
+fn digest_optional_i64_field(
+ digest: &mut Sha256,
+ row: &sqlx::sqlite::SqliteRow,
+ field: &'static str,
+) -> Result<(), RadrootsEventStoreError> {
+ digest_optional_i64(digest, row.try_get(field)?);
+ Ok(())
+}
+
+fn digest_bool_field(
+ digest: &mut Sha256,
+ row: &sqlx::sqlite::SqliteRow,
+ field: &'static str,
+) -> Result<(), RadrootsEventStoreError> {
+ digest_bool(digest, row.try_get(field)?)
+}
+
+fn digest_blob_field(
+ digest: &mut Sha256,
+ row: &sqlx::sqlite::SqliteRow,
+ field: &'static str,
+) -> Result<(), RadrootsEventStoreError> {
+ let value: Vec<u8> = row.try_get(field)?;
+ digest_bytes(digest, b'X', &value)
+}
+
+async fn validate_scoped_integrity_v1(
+ connection: &mut SqliteConnection,
+) -> Result<(), RadrootsEventStoreError> {
+ for table in REBUILD_OWNED_TABLES_V1 {
+ let integrity_sql = format!("PRAGMA main.integrity_check('{table}')");
+ let rows = sqlx::query(sqlx::AssertSqlSafe(integrity_sql))
+ .fetch_all(&mut *connection)
+ .await?;
+ for row in rows {
+ let detail: String = row.try_get(0)?;
+ if detail != "ok" {
+ return Err(RadrootsEventStoreError::IntegrityCheckFailed { detail });
+ }
+ }
+
+ let foreign_key_sql = format!("PRAGMA main.foreign_key_check('{table}')");
+ if let Some(row) = sqlx::query(sqlx::AssertSqlSafe(foreign_key_sql))
+ .fetch_optional(&mut *connection)
+ .await?
+ {
+ return Err(RadrootsEventStoreError::ForeignKeyViolation {
+ table: row.try_get("table")?,
+ rowid: row.try_get("rowid")?,
+ parent: row.try_get("parent")?,
+ foreign_key_index: row.try_get("fkid")?,
+ });
+ }
+ }
+ sqlx::query(
+ "INSERT INTO radroots_event_store_food_availability_search_fts(radroots_event_store_food_availability_search_fts) VALUES('integrity-check')",
+ )
+ .execute(&mut *connection)
+ .await
+ .map_err(|source| RadrootsEventStoreError::Fts5IntegrityCheckFailed {
+ table: "radroots_event_store_food_availability_search_fts",
+ source,
+ })?;
+ Ok(())
+}
+
+async fn finish_raw_source_rebuild_transaction(
+ transaction: Transaction<'_, Sqlite>,
+ result: Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError>,
+) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ match result {
+ Ok(report) => {
+ transaction.commit().await?;
+ Ok(report)
+ }
+ Err(primary) => {
+ let rollback = transaction.rollback().await;
+ preserve_raw_source_rebuild_primary_failure(primary, rollback)
+ }
+ }
+}
+
+fn preserve_raw_source_rebuild_primary_failure<T>(
+ primary: RadrootsEventStoreError,
+ rollback: Result<(), sqlx::Error>,
+) -> Result<T, RadrootsEventStoreError> {
+ match rollback {
+ Ok(()) => Err(primary),
+ Err(rollback) => Err(
+ RadrootsEventStoreError::RawSourceRebuildTransactionRollbackFailed {
+ primary: Box::new(primary),
+ rollback,
+ },
+ ),
+ }
+}
+
+#[cfg(test)]
+fn inject_raw_source_rebuild_failpoint_v1(
+ selected: Option<RawSourceRebuildFailpointV1>,
+ stage: RawSourceRebuildFailpointV1,
+) -> Result<(), RadrootsEventStoreError> {
+ if selected == Some(stage) {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ format!("injected raw-source rebuild failure at {}", stage.as_str()),
+ );
+ }
+ Ok(())
+}
+
+fn rebuild_state_error(
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1,
+ detail: impl Into<String>,
+) -> RadrootsEventStoreError {
+ RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind,
+ detail: detail.into(),
+ }
+}
+
+fn rebuild_drift<T>(
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1,
+ detail: impl Into<String>,
+) -> Result<T, RadrootsEventStoreError> {
+ Err(rebuild_state_error(kind, detail))
+}
+
+#[cfg(test)]
+pub(crate) fn preserve_raw_source_rebuild_primary_failure_for_test<T>(
+ primary: RadrootsEventStoreError,
+ rollback: Result<(), sqlx::Error>,
+) -> Result<T, RadrootsEventStoreError> {
+ preserve_raw_source_rebuild_primary_failure(primary, rollback)
+}
diff --git a/crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs b/crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs
@@ -0,0 +1,940 @@
+use super::ReconciledEvent;
+use crate::model::reconciliation_v1::{RadrootsEventAdmissionStatus, StoredEventClass};
+use crate::{RadrootsEventStoreError, RadrootsEventStoreRawSourceRebuildDriftV1};
+use radroots_event::envelope::RadrootsEventEnvelope;
+use radroots_event::event_head::v1::{
+ RadrootsCurrentEventHead, RadrootsEventHeadCandidate, RadrootsEventHeadCandidateResult,
+ RadrootsEventHeadCoordinate, RadrootsEventHeadDecision,
+ event_head_candidate_for_nip01_event_v1, select_event_head_v1,
+};
+use radroots_event::ids::RadrootsNip01Coordinate;
+use radroots_event_codec::deletion::reconciliation_v1::admission::{
+ RadrootsAdmittedNip09DeletionRequestEventV1, admit_verified_nip09_deletion_request_event_v1,
+};
+#[cfg(test)]
+use radroots_event_codec::deletion::reconciliation_v1::evaluator::evaluate_nip09_suppression_from_borrowed_requests_v1;
+use radroots_event_codec::deletion::reconciliation_v1::evaluator::{
+ RadrootsNip09SuppressionOutcome, RadrootsNip09SuppressionReason,
+};
+use std::collections::BTreeMap;
+#[cfg(test)]
+use std::collections::BTreeSet;
+
+#[derive(Debug, PartialEq, Eq)]
+pub(super) struct VisibilityOracleFactV1 {
+ pub(super) event_id: String,
+ pub(super) admission_status: String,
+ pub(super) contract_id: Option<String>,
+ pub(super) event_class: String,
+ pub(super) raw_d_tag: Option<String>,
+ pub(super) is_raw_head: i64,
+ pub(super) raw_head_event_id: Option<String>,
+ pub(super) suppression_outcome: Option<String>,
+ pub(super) suppression_reason: Option<String>,
+ pub(super) event_reference_request_id: Option<String>,
+ pub(super) address_reference_request_id: Option<String>,
+ pub(super) address_reference_cutoff: Option<i64>,
+ pub(super) current_visibility: String,
+}
+
+pub(super) async fn audit_current_visibility_from_raw_v1(
+ events: &[ReconciledEvent],
+ actual: Vec<VisibilityOracleFactV1>,
+) -> Result<(), RadrootsEventStoreError> {
+ let expected = expected_visibility(events)?;
+ if actual != expected {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::DerivedProductStateAuthority,
+ "current visibility does not equal the independent immutable-raw oracle",
+ );
+ }
+ Ok(())
+}
+
+fn expected_visibility(
+ events: &[ReconciledEvent],
+) -> Result<Vec<VisibilityOracleFactV1>, RadrootsEventStoreError> {
+ let winners = oracle_head_winners(events);
+ let requests = oracle_deletion_requests(events)?;
+ let request_index = OracleRequestIndexV1::new(&requests);
+ let mut expected = Vec::with_capacity(events.len());
+ for event in events {
+ let envelope = event.verified_event.event();
+ let event_class = StoredEventClass::from_event_kind_class(envelope.kind_class());
+ let raw_d_tag = oracle_raw_d_tag(envelope, event_class);
+ let raw_head_event_id = match event_class {
+ StoredEventClass::Regular => None,
+ StoredEventClass::Replaceable => winners
+ .get(&RadrootsEventHeadCoordinate::Replaceable {
+ kind: envelope.kind_u32(),
+ pubkey: envelope.author().clone(),
+ })
+ .map(|winner| winner.event_id.to_string()),
+ StoredEventClass::Addressable => winners
+ .get(&RadrootsEventHeadCoordinate::Addressable {
+ kind: envelope.kind_u32(),
+ pubkey: envelope.author().clone(),
+ d_tag: raw_d_tag.clone().unwrap_or_default(),
+ })
+ .map(|winner| winner.event_id.to_string()),
+ StoredEventClass::Ephemeral => {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::ImmutableRawAuthority,
+ "the immutable-raw visibility oracle found an ephemeral row",
+ );
+ }
+ };
+ let is_raw_head = event_class == StoredEventClass::Regular
+ || raw_head_event_id.as_deref() == Some(envelope.id_str());
+ let admitted = event.admission.status == RadrootsEventAdmissionStatus::Admitted;
+ let (
+ suppression_outcome,
+ suppression_reason,
+ event_reference_request_id,
+ address_reference_request_id,
+ address_reference_cutoff,
+ ) = if admitted {
+ let decision = request_index.decision(envelope);
+ (
+ Some(decision.outcome.code().to_owned()),
+ Some(decision.reason.code().to_owned()),
+ decision.event_reference_request_id,
+ decision.address_reference_request_id,
+ decision
+ .address_reference_cutoff
+ .map(i64::try_from)
+ .transpose()
+ .map_err(|_| RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind:
+ RadrootsEventStoreRawSourceRebuildDriftV1::DerivedProductStateAuthority,
+ detail: format!(
+ "deletion cutoff for `{}` exceeds SQLite integer range",
+ envelope.id_str()
+ ),
+ })?,
+ )
+ } else {
+ (None, None, None, None, None)
+ };
+ let current_visibility = if !admitted {
+ "not_admitted"
+ } else if !is_raw_head {
+ "not_current"
+ } else if suppression_outcome.as_deref()
+ == Some(RadrootsNip09SuppressionOutcome::Suppressed.code())
+ {
+ "suppressed"
+ } else {
+ "visible"
+ };
+ expected.push(VisibilityOracleFactV1 {
+ event_id: envelope.id_str().to_owned(),
+ admission_status: event.admission.status.as_str().to_owned(),
+ contract_id: event
+ .admission
+ .contract
+ .map(|contract| contract.id.to_owned()),
+ event_class: event_class.as_str().to_owned(),
+ raw_d_tag,
+ is_raw_head: i64::from(is_raw_head),
+ raw_head_event_id,
+ suppression_outcome,
+ suppression_reason,
+ event_reference_request_id,
+ address_reference_request_id,
+ address_reference_cutoff,
+ current_visibility: current_visibility.to_owned(),
+ });
+ }
+ Ok(expected)
+}
+
+fn oracle_head_winners(
+ events: &[ReconciledEvent],
+) -> BTreeMap<RadrootsEventHeadCoordinate, RadrootsEventHeadCandidate> {
+ let mut winners = BTreeMap::new();
+ for event in events {
+ let RadrootsEventHeadCandidateResult::Candidate(candidate) =
+ event_head_candidate_for_nip01_event_v1(event.verified_event.event())
+ else {
+ continue;
+ };
+ let current =
+ winners
+ .get(&candidate.coordinate)
+ .map(
+ |winner: &RadrootsEventHeadCandidate| RadrootsCurrentEventHead {
+ coordinate: winner.coordinate.clone(),
+ event_id: winner.event_id.clone(),
+ created_at: winner.created_at,
+ },
+ );
+ if matches!(
+ select_event_head_v1(candidate.clone(), current.as_ref()),
+ RadrootsEventHeadDecision::Applied(_)
+ ) {
+ winners.insert(candidate.coordinate.clone(), candidate);
+ }
+ }
+ winners
+}
+
+fn oracle_raw_d_tag(
+ event: &RadrootsEventEnvelope,
+ event_class: StoredEventClass,
+) -> Option<String> {
+ match event_class {
+ StoredEventClass::Regular | StoredEventClass::Ephemeral => None,
+ StoredEventClass::Replaceable => Some(String::new()),
+ StoredEventClass::Addressable => Some(
+ event
+ .tag_slices()
+ .iter()
+ .find(|tag| tag.as_slice().first().is_some_and(|name| name == "d"))
+ .and_then(|tag| tag.as_slice().get(1))
+ .cloned()
+ .unwrap_or_default(),
+ ),
+ }
+}
+
+fn oracle_deletion_requests(
+ events: &[ReconciledEvent],
+) -> Result<Vec<RadrootsAdmittedNip09DeletionRequestEventV1>, RadrootsEventStoreError> {
+ let mut requests = events
+ .iter()
+ .filter(|event| {
+ event.admission.status == RadrootsEventAdmissionStatus::Admitted
+ && event.verified_event.event().kind_u32() == 5
+ })
+ .map(|event| {
+ admit_verified_nip09_deletion_request_event_v1(event.verified_event.clone()).map_err(
+ |error| RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::DerivedProductStateAuthority,
+ detail: format!(
+ "oracle could not type admitted deletion request `{}`: {error}",
+ event.verified_event.event().id_str()
+ ),
+ },
+ )
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+ requests.sort_by(|left, right| left.event().id().cmp(right.event().id()));
+ Ok(requests)
+}
+
+struct OracleRequestIndexV1<'a> {
+ requests: &'a [RadrootsAdmittedNip09DeletionRequestEventV1],
+ event_targets: BTreeMap<String, BTreeMap<String, usize>>,
+ address_targets: BTreeMap<(u32, String, String), OracleAddressRequestEvidenceV1>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+struct OracleSuppressionDecisionV1 {
+ outcome: RadrootsNip09SuppressionOutcome,
+ reason: RadrootsNip09SuppressionReason,
+ event_reference_request_id: Option<String>,
+ address_reference_request_id: Option<String>,
+ address_reference_cutoff: Option<u64>,
+}
+
+#[derive(Default)]
+struct OracleAddressRequestEvidenceV1 {
+ authorized: Option<usize>,
+ unauthorized: Option<usize>,
+}
+
+impl<'a> OracleRequestIndexV1<'a> {
+ fn new(requests: &'a [RadrootsAdmittedNip09DeletionRequestEventV1]) -> Self {
+ let mut event_targets = BTreeMap::<String, BTreeMap<String, usize>>::new();
+ let mut address_targets =
+ BTreeMap::<(u32, String, String), OracleAddressRequestEvidenceV1>::new();
+ for (index, request) in requests.iter().enumerate() {
+ let request_author = request.event().author_str();
+ for target in request.projection().event_targets() {
+ event_targets
+ .entry(target.event_id().as_str().to_owned())
+ .or_default()
+ .entry(request_author.to_owned())
+ .and_modify(|current| {
+ if request.event().id() < requests[*current].event().id() {
+ *current = index;
+ }
+ })
+ .or_insert(index);
+ }
+ for target in request.projection().address_targets() {
+ let coordinate = (
+ target.coordinate().kind(),
+ target.coordinate().pubkey().as_str().to_owned(),
+ target.coordinate().identifier().to_owned(),
+ );
+ let evidence = address_targets.entry(coordinate.clone()).or_default();
+ if request_author == coordinate.1 {
+ let replace = evidence.authorized.is_none_or(|current| {
+ let current = requests[current].event();
+ request.event().created_at_u64() > current.created_at_u64()
+ || (request.event().created_at_u64() == current.created_at_u64()
+ && request.event().id() < current.id())
+ });
+ if replace {
+ evidence.authorized = Some(index);
+ }
+ } else if evidence.unauthorized.is_none() {
+ evidence.unauthorized = Some(index);
+ }
+ }
+ }
+ Self {
+ requests,
+ event_targets,
+ address_targets,
+ }
+ }
+
+ fn decision(&self, event: &RadrootsEventEnvelope) -> OracleSuppressionDecisionV1 {
+ if event.kind_u32() == 5 {
+ return OracleSuppressionDecisionV1 {
+ outcome: RadrootsNip09SuppressionOutcome::Visible,
+ reason: RadrootsNip09SuppressionReason::DeletionRequestImmune,
+ event_reference_request_id: None,
+ address_reference_request_id: None,
+ address_reference_cutoff: None,
+ };
+ }
+
+ let (event_reference, unauthorized_event_reference) = self
+ .event_targets
+ .get(event.id_str())
+ .map_or((None, false), |by_author| {
+ let authorized = by_author.get(event.author_str()).copied();
+ (
+ authorized,
+ by_author.len() > usize::from(authorized.is_some()),
+ )
+ });
+ let address_evidence = oracle_nip01_coordinate_key(event)
+ .as_ref()
+ .and_then(|coordinate| self.address_targets.get(coordinate));
+ let address_reference = address_evidence.and_then(|evidence| evidence.authorized);
+ let has_unauthorized_reference = unauthorized_event_reference
+ || address_evidence.is_some_and(|evidence| evidence.unauthorized.is_some());
+
+ let event_reference_request_id =
+ event_reference.map(|index| self.requests[index].event().id().as_str().to_owned());
+ let (address_reference_request_id, address_reference_cutoff) = address_reference
+ .map(|index| {
+ let request = self.requests[index].event();
+ (
+ Some(request.id().as_str().to_owned()),
+ Some(request.created_at_u64()),
+ )
+ })
+ .unwrap_or((None, None));
+ let address_applies =
+ address_reference_cutoff.is_some_and(|cutoff| event.created_at_u64() <= cutoff);
+ let (outcome, reason) = match (event_reference.is_some(), address_applies) {
+ (true, true) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdAndAddressReference,
+ ),
+ (true, false) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdReference,
+ ),
+ (false, true) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff,
+ ),
+ (false, false) if address_reference.is_some() => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget,
+ ),
+ (false, false) if has_unauthorized_reference => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::RequestAuthorMismatch,
+ ),
+ (false, false) => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::NoAuthorizedReference,
+ ),
+ };
+ OracleSuppressionDecisionV1 {
+ outcome,
+ reason,
+ event_reference_request_id,
+ address_reference_request_id,
+ address_reference_cutoff,
+ }
+ }
+}
+
+fn oracle_nip01_coordinate_key(event: &RadrootsEventEnvelope) -> Option<(u32, String, String)> {
+ let kind = event.kind_u32();
+ let identifier = match kind {
+ 0 | 3 | 10_000..=19_999 => String::new(),
+ 30_000..=39_999 => event
+ .tag_slices()
+ .iter()
+ .find(|tag| tag.as_slice().first().is_some_and(|name| name == "d"))?
+ .as_slice()
+ .get(1)?
+ .clone(),
+ _ => return None,
+ };
+ let coordinate =
+ RadrootsNip01Coordinate::parse(format!("{kind}:{}:{identifier}", event.author_str()))
+ .ok()?;
+ Some((
+ coordinate.kind(),
+ coordinate.pubkey().as_str().to_owned(),
+ coordinate.identifier().to_owned(),
+ ))
+}
+
+fn rebuild_drift<T>(
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1,
+ detail: impl Into<String>,
+) -> Result<T, RadrootsEventStoreError> {
+ Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind,
+ detail: detail.into(),
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::nip09::reconciliation_v1::{
+ ReconciliationCapacityLimits, load_reconciliation_snapshot,
+ };
+ use crate::{RadrootsEventIngest, RadrootsEventStore};
+ use nostr::{EventBuilder, Keys, Kind, SecretKey, Tag, TagKind, Timestamp};
+ use radroots_event_codec::verification::v1::RadrootsSignatureVerifiedEvent;
+ use serde_json::Value;
+
+ const FOOD_FIXTURE: &[u8] =
+ include_bytes!("../../../tests/fixtures/food_availability_projection.v1.json");
+ const FIXTURE_SECRET_KEY_HEX: &str =
+ "10c5304d6c9ae3a1a16f7860f1cc8f5e3a76225a2663b3a989a0d775919b7df5";
+ const OTHER_SECRET_KEY_HEX: &str =
+ "0000000000000000000000000000000000000000000000000000000000000002";
+
+ fn signed_ingest(kind: u16, created_at: u64, content: &str) -> RadrootsEventIngest {
+ signed_ingest_with_tags(kind, created_at, content, Vec::new())
+ }
+
+ fn signed_ingest_with_tags(
+ kind: u16,
+ created_at: u64,
+ content: &str,
+ tags: Vec<Vec<String>>,
+ ) -> RadrootsEventIngest {
+ signed_ingest_with_tags_and_key(kind, created_at, content, tags, FIXTURE_SECRET_KEY_HEX)
+ }
+
+ fn signed_ingest_with_tags_and_key(
+ kind: u16,
+ created_at: u64,
+ content: &str,
+ tags: Vec<Vec<String>>,
+ secret_key_hex: &str,
+ ) -> RadrootsEventIngest {
+ let keys = Keys::new(SecretKey::from_hex(secret_key_hex).expect("fixture secret key"));
+ let event = EventBuilder::new(Kind::Custom(kind), content)
+ .tags(
+ tags.into_iter()
+ .map(|mut values| {
+ let name = values.remove(0);
+ Tag::custom(TagKind::Custom(name.into()), values)
+ })
+ .collect::<Vec<_>>(),
+ )
+ .custom_created_at(Timestamp::from_secs(created_at))
+ .sign_with_keys(&keys)
+ .expect("signed oracle event");
+ RadrootsEventIngest::from_raw_json(
+ serde_json::to_string(&event).expect("oracle event JSON"),
+ i64::try_from(created_at * 1_000).expect("oracle observed time"),
+ )
+ .expect("verified oracle ingest")
+ }
+
+ fn fixture_ingests(case_id: &str) -> Vec<RadrootsEventIngest> {
+ let fixture: Value = serde_json::from_slice(FOOD_FIXTURE).expect("Food fixture JSON");
+ let case = fixture["cases"]
+ .as_array()
+ .expect("Food fixture cases")
+ .iter()
+ .find(|case| case["id"].as_str() == Some(case_id))
+ .expect("oracle fixture case");
+ case["events"]
+ .as_array()
+ .expect("oracle fixture events")
+ .iter()
+ .map(|observed| {
+ RadrootsEventIngest::from_raw_json(
+ serde_json::to_string(&observed["event"]).expect("fixture event JSON"),
+ observed["observed_at_ms"]
+ .as_i64()
+ .expect("fixture observed time"),
+ )
+ .expect("verified fixture ingest")
+ })
+ .collect()
+ }
+
+ fn assert_indexed_decision_matches_protocol_v1(
+ target: &RadrootsSignatureVerifiedEvent,
+ requests: &[RadrootsAdmittedNip09DeletionRequestEventV1],
+ index: &OracleRequestIndexV1<'_>,
+ ) -> OracleSuppressionDecisionV1 {
+ let expected = evaluate_nip09_suppression_from_borrowed_requests_v1(target, requests);
+ let actual = index.decision(target.event());
+ assert_eq!(actual.outcome, expected.outcome());
+ assert_eq!(actual.reason, expected.reason());
+ assert_eq!(
+ actual.event_reference_request_id.as_deref(),
+ expected
+ .event_reference()
+ .map(|evidence| evidence.request_id().as_str())
+ );
+ assert_eq!(
+ actual.address_reference_request_id.as_deref(),
+ expected
+ .address_reference()
+ .map(|evidence| evidence.request_id().as_str())
+ );
+ assert_eq!(
+ actual.address_reference_cutoff,
+ expected
+ .address_reference()
+ .map(|evidence| evidence.inclusive_cutoff())
+ );
+ actual
+ }
+
+ fn admitted_request(
+ ingest: RadrootsEventIngest,
+ ) -> RadrootsAdmittedNip09DeletionRequestEventV1 {
+ admit_verified_nip09_deletion_request_event_v1(ingest.verified_event().clone())
+ .expect("admitted deletion request")
+ }
+
+ #[test]
+ fn raw_snapshot_visibility_oracle_bounds_high_fan_in_evidence_v1() {
+ const REQUEST_COUNT: usize = 512;
+ let target = signed_ingest_with_tags(
+ 30_402,
+ 1_700_000_000,
+ "{}",
+ vec![vec!["d".to_owned(), "high-fan-in".to_owned()]],
+ );
+ let coordinate = format!("30402:{}:high-fan-in", target.event().author_str());
+ let mut requests = (0..REQUEST_COUNT)
+ .map(|index| {
+ let ingest = signed_ingest_with_tags(
+ 5,
+ 1_700_001_000 + u64::try_from(index).expect("request timestamp"),
+ "high fan-in",
+ vec![vec!["a".to_owned(), coordinate.clone()]],
+ );
+ admit_verified_nip09_deletion_request_event_v1(ingest.verified_event().clone())
+ .expect("admitted deletion request")
+ })
+ .collect::<Vec<_>>();
+ requests.sort_by(|left, right| left.event().id().cmp(right.event().id()));
+
+ let index = OracleRequestIndexV1::new(&requests);
+ let actual = index.decision(target.event());
+ assert_eq!(
+ actual.address_reference_cutoff,
+ Some(1_700_001_000 + u64::try_from(REQUEST_COUNT - 1).expect("request count"))
+ );
+ assert_indexed_decision_matches_protocol_v1(target.verified_event(), &requests, &index);
+ }
+
+ #[test]
+ fn raw_snapshot_visibility_oracle_matches_wide_event_and_address_requests_v1() {
+ const TARGETS_PER_REFERENCE_KIND: usize = 128;
+ let mut targets = Vec::with_capacity(TARGETS_PER_REFERENCE_KIND * 2);
+ let mut request_tags = Vec::with_capacity(TARGETS_PER_REFERENCE_KIND * 2);
+ for index in 0..TARGETS_PER_REFERENCE_KIND {
+ let offset = u64::try_from(index).expect("target timestamp");
+ let event_target = signed_ingest(
+ 1,
+ 1_700_010_000 + offset,
+ &format!("wide event target {index}"),
+ );
+ request_tags.push(vec![
+ "e".to_owned(),
+ event_target.event().id_str().to_owned(),
+ ]);
+ targets.push(event_target);
+
+ let identifier = format!("wide-address-{index}");
+ let address_target = signed_ingest_with_tags(
+ 30_402,
+ 1_700_020_000 + offset,
+ "{}",
+ vec![vec!["d".to_owned(), identifier.clone()]],
+ );
+ request_tags.push(vec![
+ "a".to_owned(),
+ format!("30402:{}:{identifier}", address_target.event().author_str()),
+ ]);
+ targets.push(address_target);
+ }
+ let request = signed_ingest_with_tags(5, 1_700_030_000, "wide request", request_tags);
+ let requests = vec![
+ admit_verified_nip09_deletion_request_event_v1(request.verified_event().clone())
+ .expect("admitted wide deletion request"),
+ ];
+ let index = OracleRequestIndexV1::new(&requests);
+ for target in &targets {
+ assert_indexed_decision_matches_protocol_v1(target.verified_event(), &requests, &index);
+ }
+ }
+
+ #[test]
+ fn raw_snapshot_visibility_oracle_matches_all_protocol_decision_branches_v1() {
+ let no_reference = signed_ingest(1, 1_700_100_000, "no reference");
+ let no_reference_requests = Vec::new();
+ let no_reference_index = OracleRequestIndexV1::new(&no_reference_requests);
+ assert_eq!(
+ assert_indexed_decision_matches_protocol_v1(
+ no_reference.verified_event(),
+ &no_reference_requests,
+ &no_reference_index,
+ )
+ .reason,
+ RadrootsNip09SuppressionReason::NoAuthorizedReference
+ );
+
+ let immune = signed_ingest(5, 1_700_100_010, "immune");
+ let immune_requests = vec![admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_100_020,
+ "references deletion request",
+ vec![vec!["e".to_owned(), immune.event().id_str().to_owned()]],
+ ))];
+ let immune_index = OracleRequestIndexV1::new(&immune_requests);
+ assert_eq!(
+ assert_indexed_decision_matches_protocol_v1(
+ immune.verified_event(),
+ &immune_requests,
+ &immune_index,
+ )
+ .reason,
+ RadrootsNip09SuppressionReason::DeletionRequestImmune
+ );
+
+ let unauthorized = signed_ingest(1, 1_700_100_030, "unauthorized target");
+ let unauthorized_requests = vec![admitted_request(signed_ingest_with_tags_and_key(
+ 5,
+ 1_700_100_040,
+ "wrong author",
+ vec![vec![
+ "e".to_owned(),
+ unauthorized.event().id_str().to_owned(),
+ ]],
+ OTHER_SECRET_KEY_HEX,
+ ))];
+ let unauthorized_index = OracleRequestIndexV1::new(&unauthorized_requests);
+ assert_eq!(
+ assert_indexed_decision_matches_protocol_v1(
+ unauthorized.verified_event(),
+ &unauthorized_requests,
+ &unauthorized_index,
+ )
+ .reason,
+ RadrootsNip09SuppressionReason::RequestAuthorMismatch
+ );
+
+ let stale = signed_ingest_with_tags(
+ 30_402,
+ 1_700_100_100,
+ "{}",
+ vec![vec!["d".to_owned(), "stale".to_owned()]],
+ );
+ let stale_requests = vec![
+ admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_100_090,
+ "stale address",
+ vec![vec![
+ "a".to_owned(),
+ format!("30402:{}:stale", stale.event().author_str()),
+ ]],
+ )),
+ admitted_request(signed_ingest_with_tags_and_key(
+ 5,
+ 1_700_100_110,
+ "unauthorized exact reference",
+ vec![vec!["e".to_owned(), stale.event().id_str().to_owned()]],
+ OTHER_SECRET_KEY_HEX,
+ )),
+ ];
+ let stale_index = OracleRequestIndexV1::new(&stale_requests);
+ let stale_decision = assert_indexed_decision_matches_protocol_v1(
+ stale.verified_event(),
+ &stale_requests,
+ &stale_index,
+ );
+ assert_eq!(
+ stale_decision.reason,
+ RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget
+ );
+ assert!(stale_decision.address_reference_request_id.is_some());
+
+ let exact = signed_ingest_with_tags(
+ 30_402,
+ 1_700_100_200,
+ "{}",
+ vec![vec!["d".to_owned(), "exact".to_owned()]],
+ );
+ let exact_requests = vec![
+ admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_100_190,
+ "stale address",
+ vec![vec![
+ "a".to_owned(),
+ format!("30402:{}:exact", exact.event().author_str()),
+ ]],
+ )),
+ admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_100_210,
+ "exact event",
+ vec![vec!["e".to_owned(), exact.event().id_str().to_owned()]],
+ )),
+ ];
+ let exact_index = OracleRequestIndexV1::new(&exact_requests);
+ let exact_decision = assert_indexed_decision_matches_protocol_v1(
+ exact.verified_event(),
+ &exact_requests,
+ &exact_index,
+ );
+ assert_eq!(
+ exact_decision.reason,
+ RadrootsNip09SuppressionReason::EventIdReference
+ );
+ assert!(exact_decision.event_reference_request_id.is_some());
+ assert!(exact_decision.address_reference_request_id.is_some());
+
+ let address = signed_ingest_with_tags(
+ 30_402,
+ 1_700_100_300,
+ "{}",
+ vec![vec!["d".to_owned(), "address".to_owned()]],
+ );
+ let address_requests = vec![admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_100_310,
+ "address reference",
+ vec![vec![
+ "a".to_owned(),
+ format!("30402:{}:address", address.event().author_str()),
+ ]],
+ ))];
+ let address_index = OracleRequestIndexV1::new(&address_requests);
+ assert_eq!(
+ assert_indexed_decision_matches_protocol_v1(
+ address.verified_event(),
+ &address_requests,
+ &address_index,
+ )
+ .reason,
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff
+ );
+
+ let both = signed_ingest_with_tags(
+ 30_402,
+ 1_700_100_400,
+ "{}",
+ vec![vec!["d".to_owned(), "both".to_owned()]],
+ );
+ let both_requests = vec![admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_100_410,
+ "both references",
+ vec![
+ vec!["e".to_owned(), both.event().id_str().to_owned()],
+ vec![
+ "a".to_owned(),
+ format!("30402:{}:both", both.event().author_str()),
+ ],
+ ],
+ ))];
+ let both_index = OracleRequestIndexV1::new(&both_requests);
+ let both_decision = assert_indexed_decision_matches_protocol_v1(
+ both.verified_event(),
+ &both_requests,
+ &both_index,
+ );
+ assert_eq!(
+ both_decision.reason,
+ RadrootsNip09SuppressionReason::EventIdAndAddressReference
+ );
+ assert!(both_decision.event_reference_request_id.is_some());
+ assert!(both_decision.address_reference_request_id.is_some());
+ }
+
+ #[test]
+ fn raw_snapshot_visibility_oracle_is_order_and_repeat_invariant_v1() {
+ let target = signed_ingest_with_tags(
+ 30_402,
+ 1_700_200_000,
+ "{}",
+ vec![vec!["d".to_owned(), "invariant".to_owned()]],
+ );
+ let coordinate = format!("30402:{}:invariant", target.event().author_str());
+ let first = admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_200_010,
+ "first exact",
+ vec![vec!["e".to_owned(), target.event().id_str().to_owned()]],
+ ));
+ let second = admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_200_020,
+ "second exact and address",
+ vec![
+ vec!["e".to_owned(), target.event().id_str().to_owned()],
+ vec!["a".to_owned(), coordinate.clone()],
+ ],
+ ));
+ let third = admitted_request(signed_ingest_with_tags(
+ 5,
+ 1_700_200_020,
+ "address tie",
+ vec![vec!["a".to_owned(), coordinate]],
+ ));
+ let canonical_requests = vec![first.clone(), second.clone(), third.clone()];
+ let repeated_reverse_requests = vec![
+ third.clone(),
+ second.clone(),
+ first.clone(),
+ third,
+ second,
+ first,
+ ];
+ let canonical_index = OracleRequestIndexV1::new(&canonical_requests);
+ let repeated_reverse_index = OracleRequestIndexV1::new(&repeated_reverse_requests);
+ let canonical = assert_indexed_decision_matches_protocol_v1(
+ target.verified_event(),
+ &canonical_requests,
+ &canonical_index,
+ );
+ let repeated_reverse = assert_indexed_decision_matches_protocol_v1(
+ target.verified_event(),
+ &repeated_reverse_requests,
+ &repeated_reverse_index,
+ );
+ assert_eq!(repeated_reverse, canonical);
+ }
+
+ #[tokio::test]
+ async fn raw_snapshot_visibility_oracle_covers_regular_replaceable_addressable_and_deletion_v1()
+ {
+ let store = RadrootsEventStore::open_memory().await.expect("open store");
+ let fixture_pubkey = "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df";
+ for ingest in [
+ signed_ingest(1, 1_700_000_010, "Victoria harvest update"),
+ signed_ingest(0, 1_700_000_020, "{}"),
+ signed_ingest_with_tags(
+ 5,
+ 1_700_000_030,
+ "Profile withdrawn",
+ vec![vec!["a".to_owned(), format!("0:{fixture_pubkey}:")]],
+ ),
+ ]
+ .into_iter()
+ .chain(fixture_ingests(
+ "authorized_address_deletion_retracts_projection",
+ )) {
+ store.ingest_event(ingest).await.expect("ingest oracle row");
+ }
+
+ let mut connection = store.pool().acquire().await.expect("connection");
+ let snapshot = load_reconciliation_snapshot(
+ &mut connection,
+ ReconciliationCapacityLimits::production(),
+ )
+ .await
+ .expect("load immutable raw snapshot");
+ let requests = oracle_deletion_requests(&snapshot.events).expect("oracle requests");
+ let request_index = OracleRequestIndexV1::new(&requests);
+ for event in &snapshot.events {
+ assert_indexed_decision_matches_protocol_v1(
+ &event.verified_event,
+ &requests,
+ &request_index,
+ );
+ }
+ let expected = expected_visibility(&snapshot.events).expect("oracle facts");
+ let classes = expected
+ .iter()
+ .map(|fact| fact.event_class.as_str())
+ .collect::<BTreeSet<_>>();
+ assert_eq!(
+ classes,
+ BTreeSet::from(["regular", "replaceable", "addressable"])
+ );
+
+ let deletion_id = snapshot
+ .events
+ .iter()
+ .find(|event| event.verified_event.event().kind_u32() == 5)
+ .map(|event| event.verified_event.event().id_str())
+ .expect("deletion request");
+ let deletion_fact = expected
+ .iter()
+ .find(|fact| fact.event_id == deletion_id)
+ .expect("deletion oracle fact");
+ assert_eq!(deletion_fact.current_visibility, "visible");
+ assert_eq!(
+ deletion_fact.suppression_reason.as_deref(),
+ Some("deletion_request_immune")
+ );
+
+ let addressable_id = snapshot
+ .events
+ .iter()
+ .find(|event| event.verified_event.event().kind_u32() == 30_402)
+ .map(|event| event.verified_event.event().id_str())
+ .expect("addressable Food event");
+ assert_eq!(
+ expected
+ .iter()
+ .find(|fact| fact.event_id == addressable_id)
+ .expect("addressable oracle fact")
+ .current_visibility,
+ "suppressed"
+ );
+ let replaceable_id = snapshot
+ .events
+ .iter()
+ .find(|event| event.verified_event.event().kind_u32() == 0)
+ .map(|event| event.verified_event.event().id_str())
+ .expect("replaceable profile event");
+ let replaceable_fact = expected
+ .iter()
+ .find(|fact| fact.event_id == replaceable_id)
+ .expect("replaceable oracle fact");
+ assert_eq!(replaceable_fact.current_visibility, "suppressed");
+ assert!(replaceable_fact.address_reference_request_id.is_some());
+ audit_current_visibility_from_raw_v1(&snapshot.events, expected)
+ .await
+ .expect("matching oracle audit");
+
+ let mut drift = expected_visibility(&snapshot.events).expect("second oracle facts");
+ drift[0].current_visibility = "forged".to_owned();
+ assert!(
+ audit_current_visibility_from_raw_v1(&snapshot.events, drift)
+ .await
+ .is_err()
+ );
+ }
+}
diff --git a/crates/event_store/src/schema.rs b/crates/event_store/src/schema.rs
@@ -1,4 +1,3 @@
-use crate::RadrootsEventStoreError;
use crate::migrations::{
EVENT_STORE_LEDGER_CREATE_DDL, EVENT_STORE_LEDGER_DDL, EVENT_STORE_LEDGER_NAME,
EVENT_STORE_MIGRATIONS, EventStoreMigration, EventStoreMigrationHook,
@@ -7,6 +6,7 @@ use crate::migrations::{
sqlite_identifier_starts_with, validate_embedded_migration_registry,
validate_migration_registry,
};
+use crate::{RadrootsEventStoreError, RadrootsEventStoreRawSourceRebuildDriftV1};
use sha2::{Digest, Sha256};
use sqlx::{Row, Sqlite, SqliteConnection, SqlitePool, Transaction};
use std::collections::{BTreeMap, BTreeSet};
@@ -24,6 +24,8 @@ use crate::store::food_availability_projection_v1::{
validate_food_availability_projection_hook_state_fast_v1,
};
+const RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1: u32 = 4;
+
#[cfg(test)]
const EMPTY_SCHEMA_SHA256: &str =
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
@@ -88,6 +90,54 @@ pub(crate) async fn migrate_event_store_schema(
migrate_event_store_schema_with_generation_provider(pool, &OsSourceGenerationProvider).await
}
+/// Validates the exact current managed catalog and ledger without consulting
+/// derived hook state. Raw-source repair uses this before it starts replacing
+/// derived authority; ordinary open continues through the stricter hook path.
+pub(crate) async fn validate_exact_managed_v4_for_raw_source_rebuild_v1(
+ connection: &mut SqliteConnection,
+) -> Result<(), RadrootsEventStoreError> {
+ validate_embedded_migration_registry()?;
+ validate_repair_temp_schema_bounded_v1(connection, EVENT_STORE_MIGRATIONS).await?;
+ let catalog = read_repair_catalog_bounded_v1(connection, EVENT_STORE_MIGRATIONS).await?;
+ if !validate_ledger_catalog(&catalog)? {
+ return Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ detail: "maintenance repair requires an exact managed-v4 migration ledger".to_owned(),
+ });
+ }
+ let history =
+ read_repair_history_bounded_v1(connection, RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1).await?;
+ let current = validate_history_against_registry(
+ &history,
+ EVENT_STORE_MIGRATIONS,
+ RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1,
+ )?;
+ if current != RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1 {
+ return Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ detail: format!(
+ "maintenance repair requires managed schema version {}, found {current}",
+ RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1
+ ),
+ });
+ }
+ let migration =
+ migration_for_version(EVENT_STORE_MIGRATIONS, RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1).ok_or(
+ RadrootsEventStoreError::UnknownMigration {
+ version: RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1,
+ },
+ )?;
+ let actual = catalog_fingerprint(&governed_catalog(&catalog, EVENT_STORE_MIGRATIONS));
+ if actual != migration.schema_sha256 {
+ return Err(RadrootsEventStoreError::SchemaFingerprintMismatch {
+ version: migration.version,
+ expected: migration.schema_sha256,
+ actual,
+ });
+ }
+ Ok(())
+}
+
pub(crate) async fn migrate_event_store_schema_with_generation_provider(
pool: &SqlitePool,
generation_provider: &dyn SourceGenerationProvider,
@@ -697,6 +747,106 @@ pub(crate) async fn validate_event_store_temp_schema(
validate_event_store_temp_schema_with_registry(connection, EVENT_STORE_MIGRATIONS).await
}
+fn repair_governed_catalog_authority_v1(
+ registry: &[EventStoreMigration],
+) -> Result<(String, i64), RadrootsEventStoreError> {
+ let mut names = registry
+ .iter()
+ .flat_map(|migration| migration.owned_object_names.iter().copied())
+ .collect::<BTreeSet<_>>();
+ names.insert(EVENT_STORE_LEDGER_NAME);
+ let canonical_row_count = i64::try_from(names.len()).map_err(|_| {
+ RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ detail: "managed catalog authority exceeds the SQLite row-count range".to_owned(),
+ }
+ })?;
+ let row_limit = canonical_row_count.checked_add(1).ok_or_else(|| {
+ RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ detail: "managed catalog authority cannot reserve a collision row".to_owned(),
+ }
+ })?;
+ Ok((serde_json::to_string(&names)?, row_limit))
+}
+
+async fn read_repair_catalog_bounded_v1(
+ connection: &mut SqliteConnection,
+ registry: &[EventStoreMigration],
+) -> Result<Vec<CatalogRow>, RadrootsEventStoreError> {
+ let (governed_names_json, row_limit) = repair_governed_catalog_authority_v1(registry)?;
+ let rows = sqlx::query(
+ "WITH governed(name) AS (
+ SELECT CAST(value AS TEXT) COLLATE NOCASE FROM json_each(?)
+ )
+ SELECT type, name, tbl_name, sql
+ FROM main.sqlite_schema
+ WHERE lower(substr(name, 1, 7)) != 'sqlite_'
+ AND (
+ name COLLATE NOCASE IN (SELECT name FROM governed)
+ OR tbl_name COLLATE NOCASE IN (SELECT name FROM governed)
+ OR lower(substr(name, 1, length(?))) = lower(?)
+ OR lower(substr(tbl_name, 1, length(?))) = lower(?)
+ )
+ ORDER BY type, name, tbl_name
+ LIMIT ?",
+ )
+ .bind(&governed_names_json)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .bind(row_limit)
+ .fetch_all(&mut *connection)
+ .await?;
+ rows.into_iter()
+ .map(|row| {
+ Ok(CatalogRow {
+ object_type: row.try_get("type")?,
+ name: row.try_get("name")?,
+ table_name: row.try_get("tbl_name")?,
+ sql: row.try_get("sql")?,
+ })
+ })
+ .collect()
+}
+
+pub(crate) async fn validate_repair_temp_schema_bounded_v1(
+ connection: &mut SqliteConnection,
+ registry: &[EventStoreMigration],
+) -> Result<(), RadrootsEventStoreError> {
+ let (governed_names_json, _) = repair_governed_catalog_authority_v1(registry)?;
+ let collision = sqlx::query(
+ "WITH governed(name) AS (
+ SELECT CAST(value AS TEXT) COLLATE NOCASE FROM json_each(?)
+ )
+ SELECT type, name, tbl_name
+ FROM temp.sqlite_schema
+ WHERE type IN ('trigger', 'view')
+ OR name COLLATE NOCASE IN (SELECT name FROM governed)
+ OR tbl_name COLLATE NOCASE IN (SELECT name FROM governed)
+ OR lower(substr(name, 1, length(?))) = lower(?)
+ OR lower(substr(tbl_name, 1, length(?))) = lower(?)
+ ORDER BY type, name, tbl_name
+ LIMIT 1",
+ )
+ .bind(&governed_names_json)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .bind(crate::migrations::EVENT_STORE_RESERVED_PREFIX)
+ .fetch_optional(&mut *connection)
+ .await?;
+ if let Some(row) = collision {
+ return Err(RadrootsEventStoreError::TemporarySchemaCollision {
+ object_type: row.try_get("type")?,
+ name: row.try_get("name")?,
+ table_name: row.try_get("tbl_name")?,
+ });
+ }
+ Ok(())
+}
+
async fn validate_event_store_temp_schema_with_registry(
connection: &mut SqliteConnection,
registry: &[EventStoreMigration],
@@ -896,6 +1046,38 @@ async fn read_history(
.collect()
}
+async fn read_repair_history_bounded_v1(
+ connection: &mut SqliteConnection,
+ supported_current: u32,
+) -> Result<Vec<AppliedMigration>, RadrootsEventStoreError> {
+ let row_limit = i64::from(supported_current).checked_add(1).ok_or_else(|| {
+ RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ detail: "managed migration-history authority cannot reserve a drift row".to_owned(),
+ }
+ })?;
+ let rows = sqlx::query(
+ "SELECT version, name, up_sha256, down_sha256, schema_sha256
+ FROM main.radroots_event_store_schema_migrations
+ ORDER BY version
+ LIMIT ?",
+ )
+ .bind(row_limit)
+ .fetch_all(&mut *connection)
+ .await?;
+ rows.into_iter()
+ .map(|row| {
+ Ok(AppliedMigration {
+ version: row.try_get("version")?,
+ name: row.try_get("name")?,
+ up_sha256: row.try_get("up_sha256")?,
+ down_sha256: row.try_get("down_sha256")?,
+ schema_sha256: row.try_get("schema_sha256")?,
+ })
+ })
+ .collect()
+}
+
fn validate_history_against_registry(
history: &[AppliedMigration],
registry: &[EventStoreMigration],
diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs
@@ -9,6 +9,8 @@ mod post_core_storage_v1;
mod post_core_storage_v2;
mod protocol_reconciliation_v1;
mod protocol_storage_v1;
+#[cfg(test)]
+mod raw_source_rebuild_v1_tests;
use self::current_visibility_v1::current_visibility_in_transaction;
use self::post_core_extension_capabilities::PostCoreExtensionCapabilities;
@@ -37,14 +39,15 @@ use self::protocol_storage_v1::{raw_head_snapshot_in_transaction, stored_raw_eve
use crate::RadrootsEventStoreError;
use crate::model::{
RadrootsCurrentVisibilityDecisionV1, RadrootsEventIngest, RadrootsEventIngestReceipt,
- RadrootsEventStoreSourceGeneration, RadrootsEventStoreStatusSummary, RadrootsEventVisibility,
- RadrootsProjectionCursor, RadrootsProjectionRebuildPrior, RadrootsProjectionRebuildTicket,
- RadrootsStoredEventTag, RadrootsStoredRawEvent, RadrootsStoredRawEventHead,
- RadrootsStoredSellerReservation, RadrootsStoredSellerReservationLine,
- RadrootsStoredTradeMissingParent, RadrootsStoredTradeMutation,
- RadrootsStoredTradeMutationParent, RadrootsStoredTradeTransportEnvelope,
- RadrootsStoredValidEvent, RadrootsStoredVisibleEvent, RadrootsStoredVisibleEventHead,
- RadrootsTradeProjectionCheckpoint, RadrootsTransportObservationType,
+ RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreSourceGeneration,
+ RadrootsEventStoreStatusSummary, RadrootsEventVisibility, RadrootsProjectionCursor,
+ RadrootsProjectionRebuildPrior, RadrootsProjectionRebuildTicket, RadrootsStoredEventTag,
+ RadrootsStoredRawEvent, RadrootsStoredRawEventHead, RadrootsStoredSellerReservation,
+ RadrootsStoredSellerReservationLine, RadrootsStoredTradeMissingParent,
+ RadrootsStoredTradeMutation, RadrootsStoredTradeMutationParent,
+ RadrootsStoredTradeTransportEnvelope, RadrootsStoredValidEvent, RadrootsStoredVisibleEvent,
+ RadrootsStoredVisibleEventHead, RadrootsTradeProjectionCheckpoint,
+ RadrootsTransportObservationType,
};
#[cfg(test)]
use crate::model::{
@@ -53,7 +56,9 @@ use crate::model::{
};
#[cfg(test)]
use crate::nip09::reconciliation_v1::ReconciliationProfile;
-use crate::nip09::reconciliation_v1::{active_source_generation, generation_from_blob};
+use crate::nip09::reconciliation_v1::{
+ active_source_generation, generation_from_blob, preflight_projection_cursor_insert_v1,
+};
use crate::schema::{
RadrootsEventStoreSchemaStatus, inspect_event_store_schema_status, migrate_event_store_schema,
rollback_event_store_schema_offline,
@@ -82,7 +87,7 @@ use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
use sqlx::{Connection, Row, SqliteConnection, SqlitePool};
use std::collections::BTreeMap;
use std::future::Future;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
@@ -209,6 +214,64 @@ impl RadrootsEventStore {
Ok(capacity)
}
+ /// Rebuilds active product state solely from retained immutable raw rows.
+ ///
+ /// Every successful call appends one irreversible retained source
+ /// generation, up to the governed history limit, and invalidates generic
+ /// projection cursors by rotating the active generation. Calls at the
+ /// history limit return
+ /// [`RadrootsEventStoreError::SourceGenerationHistoryLimitReached`].
+ pub async fn rebuild_from_raw_v1(
+ &self,
+ ) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError> {
+ crate::nip09::reconciliation_v1::rebuild_from_raw_v1_on_pool(&self.pool).await
+ }
+
+ /// Repairs an exact managed-v4 file without exposing its invalid state.
+ ///
+ /// The database file must already exist, and the caller must quiesce every
+ /// store alias, independent pool, and direct SQL user of that file for the
+ /// duration of repair. The canonical path, symlink targets, and file
+ /// replacement or rename operations must also remain quiesced.
+ /// Caller-provided pools are intentionally unavailable;
+ /// their callbacks and session state cannot be sealed. This path creates a
+ /// fresh governed pool, never creates or migrates a schema, requires the
+ /// existing file to use WAL journal mode, and proves its canonical path
+ /// shares the reserved SQLite writer-lock domain before rebuilding in the
+ /// same validated transaction. It returns a usable store only after rebuild
+ /// commit. Every successful call appends one irreversible retained source
+ /// generation, up to the governed history limit, and invalidates generic
+ /// projection cursors by rotating the active generation. Calls at the
+ /// history limit return
+ /// [`RadrootsEventStoreError::SourceGenerationHistoryLimitReached`].
+ pub async fn repair_file_from_raw_v1(
+ path: impl AsRef<Path>,
+ ) -> Result<(Self, RadrootsEventStoreRawSourceRebuildReportV1), RadrootsEventStoreError> {
+ let canonical_path = canonical_raw_source_repair_main_path_v1(path.as_ref())?;
+ let options = SqliteConnectOptions::new()
+ .filename(&canonical_path)
+ .create_if_missing(false);
+ let pool = SqlitePoolOptions::new()
+ .max_connections(1)
+ .connect_with(options)
+ .await?;
+ pool.set_connect_options(raw_source_repair_connect_options_v1(&canonical_path));
+ let mut connection = pool.acquire().await?;
+ prepare_raw_source_repair_connection_v1(&mut connection, &canonical_path).await?;
+ let transaction = connection.begin_with("BEGIN IMMEDIATE").await?;
+ if let Err(primary) =
+ validate_raw_source_repair_canonical_lock_domain_v1(&canonical_path).await
+ {
+ return preserve_raw_source_repair_probe_failure(primary, transaction.rollback().await);
+ }
+ let report = crate::nip09::reconciliation_v1::rebuild_from_raw_v1_in_existing_transaction(
+ transaction,
+ )
+ .await?;
+ drop(connection);
+ Ok((Self { pool }, report))
+ }
+
/// Begins a serialized write transaction suitable for composed event-store writes.
///
/// Call this before performing any reads that will precede
@@ -540,9 +603,13 @@ impl RadrootsEventStore {
},
);
}
- projection_cursor_unchecked(&mut tx, cursor.projection_id(), active_generation).await?;
+ let existing =
+ projection_cursor_unchecked(&mut tx, cursor.projection_id(), active_generation).await?;
match expected_prior_sequence {
None => {
+ if existing.is_none() {
+ preflight_projection_cursor_insert_v1(&mut tx).await?;
+ }
let inserted = sqlx::query(
"INSERT OR IGNORE INTO projection_cursor(projection_id, projection_version, last_event_seq, updated_at_ms) VALUES (?, ?, ?, ?)",
)
@@ -724,6 +791,7 @@ impl RadrootsEventStore {
.await?;
match (expected_prior, actual_prior) {
(RadrootsProjectionRebuildPrior::Missing, None) => {
+ preflight_projection_cursor_insert_v1(&mut tx).await?;
let inserted = sqlx::query(
"INSERT OR IGNORE INTO projection_cursor(projection_id, projection_version, last_event_seq, updated_at_ms) VALUES (?, ?, ?, ?)",
)
@@ -1167,7 +1235,6 @@ async fn configure_pool(
file_backed: bool,
) -> Result<(), RadrootsEventStoreError> {
let max_connections = pool.options().get_max_connections();
- let existing_options = pool.connect_options();
if !file_backed && max_connections != 1 {
return Err(RadrootsEventStoreError::UnsafeInMemoryPoolConnectionCount {
actual: max_connections,
@@ -1189,19 +1256,6 @@ async fn configure_pool(
}
validate_main_database_encoding(connection).await?;
crate::schema::validate_event_store_temp_schema(connection).await?;
- }
-
- let mut connect_options = existing_options
- .as_ref()
- .clone()
- .foreign_keys(true)
- .busy_timeout(Duration::from_millis(5_000));
- if file_backed {
- connect_options = connect_options.journal_mode(SqliteJournalMode::Wal);
- }
- pool.set_connect_options(connect_options);
-
- for connection in &mut connections {
sqlx::query("PRAGMA foreign_keys = ON")
.execute(&mut **connection)
.await?;
@@ -1212,9 +1266,133 @@ async fn configure_pool(
configure_file_journal_mode(connection).await?;
}
}
+ let existing_options = pool.connect_options();
+ let connect_options = existing_options
+ .as_ref()
+ .clone()
+ .foreign_keys(true)
+ .busy_timeout(Duration::from_millis(5_000));
+ let connect_options = if file_backed {
+ connect_options.journal_mode(SqliteJournalMode::Wal)
+ } else {
+ connect_options
+ };
+ pool.set_connect_options(connect_options);
+ Ok(())
+}
+
+async fn prepare_raw_source_repair_connection_v1(
+ connection: &mut SqliteConnection,
+ canonical_path: &Path,
+) -> Result<(), RadrootsEventStoreError> {
+ let main_filename = main_database_filename(connection).await?;
+ let actual = canonical_raw_source_repair_main_path_v1(Path::new(&main_filename))?;
+ if actual != canonical_path {
+ return Err(
+ RadrootsEventStoreError::RawSourceRepairDatabaseIdentityMismatch {
+ expected: canonical_path.display().to_string(),
+ actual: actual.display().to_string(),
+ },
+ );
+ }
+ validate_main_database_encoding(connection).await?;
+ crate::schema::validate_exact_managed_v4_for_raw_source_rebuild_v1(connection).await?;
+ validate_file_journal_mode_is_wal(connection).await?;
+ sqlx::query("PRAGMA foreign_keys = ON")
+ .execute(&mut *connection)
+ .await?;
+ sqlx::query("PRAGMA busy_timeout = 5000")
+ .execute(&mut *connection)
+ .await?;
Ok(())
}
+fn raw_source_repair_connect_options_v1(canonical_path: &Path) -> SqliteConnectOptions {
+ SqliteConnectOptions::new()
+ .filename(canonical_path)
+ .create_if_missing(false)
+ .journal_mode(SqliteJournalMode::Wal)
+ .foreign_keys(true)
+ .busy_timeout(Duration::from_millis(5_000))
+}
+
+async fn validate_raw_source_repair_canonical_lock_domain_v1(
+ canonical_path: &Path,
+) -> Result<(), RadrootsEventStoreError> {
+ let mut candidate = SqliteConnection::connect_with(
+ &SqliteConnectOptions::new()
+ .filename(canonical_path)
+ .create_if_missing(false)
+ .foreign_keys(true)
+ .busy_timeout(Duration::ZERO),
+ )
+ .await?;
+ let candidate_filename = main_database_filename(&mut candidate).await?;
+ let candidate_path = canonical_raw_source_repair_main_path_v1(Path::new(&candidate_filename))?;
+ if candidate_path != canonical_path {
+ return Err(
+ RadrootsEventStoreError::RawSourceRepairDatabaseIdentityMismatch {
+ expected: canonical_path.display().to_string(),
+ actual: candidate_path.display().to_string(),
+ },
+ );
+ }
+ validate_main_database_encoding(&mut candidate).await?;
+ crate::schema::validate_exact_managed_v4_for_raw_source_rebuild_v1(&mut candidate).await?;
+ validate_file_journal_mode_is_wal(&mut candidate).await?;
+
+ let mut probe = candidate.begin().await?;
+ let write = sqlx::query(
+ "UPDATE main.radroots_event_store_write_lock SET lock_version = lock_version WHERE singleton = 1",
+ )
+ .execute(&mut *probe)
+ .await;
+ let rollback = probe.rollback().await;
+ match write {
+ Ok(_) => preserve_raw_source_repair_probe_failure(
+ RadrootsEventStoreError::RawSourceRepairCanonicalPathLockDomainMismatch {
+ canonical_path: canonical_path.display().to_string(),
+ },
+ rollback,
+ ),
+ Err(error) => {
+ if sqlite_error_is_busy_or_locked(&error) {
+ rollback?;
+ Ok(())
+ } else {
+ preserve_raw_source_repair_probe_failure(error.into(), rollback)
+ }
+ }
+ }
+}
+
+fn preserve_raw_source_repair_probe_failure<T>(
+ primary: RadrootsEventStoreError,
+ rollback: Result<(), sqlx::Error>,
+) -> Result<T, RadrootsEventStoreError> {
+ match rollback {
+ Ok(()) => Err(primary),
+ Err(rollback) => Err(
+ RadrootsEventStoreError::RawSourceRebuildTransactionRollbackFailed {
+ primary: Box::new(primary),
+ rollback,
+ },
+ ),
+ }
+}
+
+fn canonical_raw_source_repair_main_path_v1(
+ path: &Path,
+) -> Result<PathBuf, RadrootsEventStoreError> {
+ let filename = path.display().to_string();
+ std::fs::canonicalize(path).map_err(|source| {
+ RadrootsEventStoreError::RawSourceRepairMainDatabaseCanonicalizationFailed {
+ filename,
+ source,
+ }
+ })
+}
+
async fn validate_main_database_encoding(
connection: &mut SqliteConnection,
) -> Result<(), RadrootsEventStoreError> {
@@ -1253,6 +1431,18 @@ async fn configure_file_journal_mode(
}
}
+async fn validate_file_journal_mode_is_wal(
+ connection: &mut SqliteConnection,
+) -> Result<(), RadrootsEventStoreError> {
+ let actual: String = sqlx::query_scalar("PRAGMA main.journal_mode")
+ .fetch_one(&mut *connection)
+ .await?;
+ if actual == "wal" {
+ return Ok(());
+ }
+ Err(RadrootsEventStoreError::SqliteFileJournalModeNotWal { actual })
+}
+
fn sqlite_error_is_busy(error: &sqlx::Error) -> bool {
let sqlx::Error::Database(error) = error else {
return false;
@@ -1263,6 +1453,16 @@ fn sqlite_error_is_busy(error: &sqlx::Error) -> bool {
.is_some_and(|code| code & 0xff == 5)
}
+fn sqlite_error_is_busy_or_locked(error: &sqlx::Error) -> bool {
+ let sqlx::Error::Database(error) = error else {
+ return false;
+ };
+ error
+ .code()
+ .and_then(|code| code.parse::<i32>().ok())
+ .is_some_and(|code| code & 0xff == 5 || code & 0xff == 6)
+}
+
async fn main_database_filename(
connection: &mut SqliteConnection,
) -> Result<String, RadrootsEventStoreError> {
diff --git a/crates/event_store/src/store/food_availability_projection_v1.rs b/crates/event_store/src/store/food_availability_projection_v1.rs
@@ -2,7 +2,6 @@ use super::addressable_transition_feed_v1::addressable_transition_page_in_transa
use super::{
RADROOTS_EVENT_STORE_QUERY_LIMIT_MAX, RadrootsEventStore, bool_from_i64, u64_from_i64,
};
-use crate::RadrootsEventStoreError;
use crate::generated::food_availability_projection_manifest as food_manifest;
use crate::model::{
RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1,
@@ -16,6 +15,7 @@ use crate::model::{
use crate::nip09::reconciliation_v1::{
EventAdmission, ReconciliationProfile, generation_from_blob,
};
+use crate::{RadrootsEventStoreError, RadrootsEventStoreRawSourceRebuildDriftV1};
use radroots_event::food_availability::RadrootsFoodIdentifier;
use radroots_event::ids::{RadrootsEventId, RadrootsPublicKey};
use radroots_event_codec::food_availability::inbound::{
@@ -138,6 +138,46 @@ pub(crate) async fn apply_food_availability_projection_hook_v1(
validate_food_availability_projection_hook_v1(connection).await
}
+pub(crate) async fn reset_and_replay_food_availability_from_raw_v1(
+ connection: &mut SqliteConnection,
+ active_generation: RadrootsEventStoreSourceGeneration,
+) -> Result<(), RadrootsEventStoreError> {
+ sqlx::query(
+ "DELETE FROM radroots_event_store_food_availability_image WHERE source_generation != ?",
+ )
+ .bind(active_generation.as_bytes().as_slice())
+ .execute(&mut *connection)
+ .await?;
+ sqlx::query(
+ "DELETE FROM radroots_event_store_food_availability_projection WHERE source_generation != ?",
+ )
+ .bind(active_generation.as_bytes().as_slice())
+ .execute(&mut *connection)
+ .await?;
+ sqlx::query("DELETE FROM radroots_event_store_food_availability_search_fts")
+ .execute(&mut *connection)
+ .await?;
+ sqlx::query(
+ "DELETE FROM radroots_event_store_food_availability_cursor WHERE source_generation != ?",
+ )
+ .bind(active_generation.as_bytes().as_slice())
+ .execute(&mut *connection)
+ .await?;
+
+ let residual_count: i64 = sqlx::query_scalar(
+ "SELECT (SELECT COUNT(*) FROM radroots_event_store_food_availability_image) + (SELECT COUNT(*) FROM radroots_event_store_food_availability_projection) + (SELECT COUNT(*) FROM radroots_event_store_food_availability_cursor) + (SELECT COUNT(*) FROM radroots_event_store_food_availability_search_fts)",
+ )
+ .fetch_one(&mut *connection)
+ .await?;
+ if residual_count != 0 {
+ return Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::DerivedProductStateAuthority,
+ detail: format!("FoodAvailability reset left {residual_count} stale derived row(s)"),
+ });
+ }
+ apply_pending_food_availability_transitions_v1(connection).await
+}
+
pub(crate) async fn apply_pending_food_availability_transitions_v1(
connection: &mut SqliteConnection,
) -> Result<(), RadrootsEventStoreError> {
diff --git a/crates/event_store/src/store/raw_source_rebuild_v1_tests.rs b/crates/event_store/src/store/raw_source_rebuild_v1_tests.rs
@@ -0,0 +1,2534 @@
+use super::RadrootsEventStore;
+use crate::model::{RadrootsEventIngest, RadrootsProjectionCursor};
+use crate::nip09::reconciliation_v1::{
+ RawSourceRebuildFailpointV1, SourceGenerationProvider,
+ preserve_raw_source_rebuild_primary_failure_for_test,
+ rebuild_from_raw_v1_in_transaction_for_test, rebuild_from_raw_v1_on_pool_for_test,
+ rebuild_from_raw_v1_on_pool_with_caller_schema_limits_for_test,
+};
+use crate::schema::rollback_event_store_schema_offline_destructive_for_migration_test;
+use crate::{
+ RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1,
+ RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1, RadrootsEventStoreError,
+ RadrootsEventStoreRawSourceRebuildDriftV1,
+};
+use serde_json::Value;
+use sqlx::Connection;
+use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
+use sqlx::{SqliteConnection, SqlitePool};
+use std::path::Path;
+
+const FOOD_FIXTURE: &[u8] =
+ include_bytes!("../../tests/fixtures/food_availability_projection.v1.json");
+const NIP09_FIXTURE: &[u8] = include_bytes!("../../tests/fixtures/nip09_reconciliation.v1.json");
+const TRANSITION_SEQUENCE_NAME: &str = "radroots_event_store_addressable_head_transition";
+
+struct FixedGeneration(u8);
+
+impl SourceGenerationProvider for FixedGeneration {
+ fn fill_generation(&self, generation: &mut [u8; 32]) -> Result<(), RadrootsEventStoreError> {
+ generation.fill(self.0);
+ Ok(())
+ }
+}
+
+struct PanickingGeneration;
+
+impl SourceGenerationProvider for PanickingGeneration {
+ fn fill_generation(&self, _generation: &mut [u8; 32]) -> Result<(), RadrootsEventStoreError> {
+ panic!("generation entropy was requested after the retained-history preflight")
+ }
+}
+
+struct FailingGeneration;
+
+impl SourceGenerationProvider for FailingGeneration {
+ fn fill_generation(&self, _generation: &mut [u8; 32]) -> Result<(), RadrootsEventStoreError> {
+ Err(RadrootsEventStoreError::SourceGenerationEntropyUnavailable)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+struct RebuildAuthoritySnapshot {
+ source_state: Vec<String>,
+ source_capacity: Vec<String>,
+ migration_history: Vec<String>,
+ commit_barrier: Vec<String>,
+ write_lock: Vec<String>,
+ feed_integrity: Vec<String>,
+ generations: Vec<String>,
+ markers: Vec<String>,
+ envelopes: Vec<String>,
+ tags: Vec<String>,
+ raw_heads: Vec<String>,
+ coordinates: Vec<String>,
+ nip09_requests: Vec<String>,
+ nip09_event_targets: Vec<String>,
+ nip09_address_targets: Vec<String>,
+ addressable_heads: Vec<String>,
+ transitions: Vec<String>,
+ food_cursor: Vec<String>,
+ food_projection: Vec<String>,
+ food_images: Vec<String>,
+ food_search: Vec<String>,
+ sqlite_sequences: Vec<String>,
+}
+
+fn food_fixture_ingest() -> RadrootsEventIngest {
+ fixture_case_ingests(
+ FOOD_FIXTURE,
+ "visible_food_availability_projects_and_searches",
+ "events",
+ )
+ .into_iter()
+ .next()
+ .expect("Food fixture event")
+}
+
+fn fixture_case_ingests(
+ bytes: &[u8],
+ case_id: &str,
+ events_field: &str,
+) -> Vec<RadrootsEventIngest> {
+ let fixture: Value = serde_json::from_slice(bytes).expect("parse event fixture");
+ let case = fixture["cases"]
+ .as_array()
+ .expect("fixture cases")
+ .iter()
+ .find(|case| case["id"].as_str() == Some(case_id))
+ .unwrap_or_else(|| panic!("missing fixture case {case_id}"));
+ case[events_field]
+ .as_array()
+ .expect("fixture events")
+ .iter()
+ .map(|observed| {
+ let raw_json =
+ serde_json::to_string(&observed["event"]).expect("serialize fixture event");
+ let observed_at_ms = observed["observed_at_ms"]
+ .as_i64()
+ .expect("fixture observed_at_ms");
+ RadrootsEventIngest::from_raw_json(raw_json, observed_at_ms)
+ .expect("verify fixture event")
+ })
+ .collect()
+}
+
+async fn seed_food_fixture(store: &RadrootsEventStore) {
+ let receipt = store
+ .ingest_event(food_fixture_ingest())
+ .await
+ .expect("ingest Food fixture");
+ assert!(receipt.persistence.is_inserted());
+}
+
+async fn seed_fixture_case(
+ store: &RadrootsEventStore,
+ bytes: &[u8],
+ case_id: &str,
+ events_field: &str,
+) {
+ for ingest in fixture_case_ingests(bytes, case_id, events_field) {
+ store
+ .ingest_event(ingest)
+ .await
+ .unwrap_or_else(|error| panic!("ingest fixture case {case_id}: {error}"));
+ }
+}
+
+async fn query_string_rows(pool: &SqlitePool, sql: &'static str) -> Vec<String> {
+ sqlx::query_scalar(sql)
+ .fetch_all(pool)
+ .await
+ .expect("snapshot query")
+}
+
+async fn rebuild_authority_snapshot(store: &RadrootsEventStore) -> RebuildAuthoritySnapshot {
+ RebuildAuthoritySnapshot {
+ source_state: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%d|%d|%d', hex(active_generation), raw_event_count, raw_tag_count, raw_high_water_seq, last_transition_seq) FROM radroots_event_store_source_state ORDER BY singleton",
+ )
+ .await,
+ source_capacity: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%d|%d|%d|%d|%d|%d', hex(source_generation), raw_event_count, raw_tag_count, raw_event_bytes, raw_tag_bytes, raw_high_water_seq, retained_generation_count, retained_generation_limit) FROM radroots_event_store_source_capacity_v1 ORDER BY singleton",
+ )
+ .await,
+ migration_history: query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s|%s|%s', version, name, up_sha256, down_sha256, schema_sha256) FROM radroots_event_store_schema_migrations ORDER BY version",
+ )
+ .await,
+ commit_barrier: query_string_rows(
+ store.pool(),
+ "SELECT printf('%d', barrier_key) FROM radroots_event_store_source_rebuild_commit_barrier ORDER BY barrier_key",
+ )
+ .await,
+ write_lock: query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%d', singleton, lock_version) FROM radroots_event_store_write_lock ORDER BY singleton",
+ )
+ .await,
+ feed_integrity: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%d|%d', hex(source_generation), transition_floor_seq, last_transition_seq, transition_count) FROM radroots_event_store_addressable_feed_integrity_v1 ORDER BY hex(source_generation)",
+ )
+ .await,
+ generations: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%d|%d|%d|%s|%s|%d|%d|%d|%d', hex(source_generation), generation_ordinal, reconciliation_version, addressable_feed_version, event_contract_registry_version, hook_id, hook_manifest_sha256, transition_floor_seq, baseline_raw_event_count, baseline_raw_tag_count, baseline_raw_high_water_seq) FROM radroots_event_store_source_generation ORDER BY generation_ordinal",
+ )
+ .await,
+ markers: query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%d', singleton, hex(target_generation), target_generation_ordinal) FROM radroots_event_store_source_rebuild_marker ORDER BY singleton",
+ )
+ .await,
+ envelopes: query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%d|%d|%d', seq, quote(event_id), quote(pubkey), quote(tags_json), quote(content), quote(sig), quote(raw_json), quote(verification_status), quote(contract_status), quote(contract_id), quote(event_class), quote(created_at), quote(kind), projection_eligible, inserted_at_ms, updated_at_ms) FROM event_envelopes ORDER BY seq",
+ )
+ .await,
+ tags: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%s|%s|%s|%d', quote(event_id), tag_index, quote(tag_name), quote(tag_value), quote(tag_json), quote(contract_semantic), quote(contract_value_type), relay_indexed) FROM event_envelope_tags ORDER BY event_id, tag_index",
+ )
+ .await,
+ raw_heads: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%s|%d|%d', coordinate_type, kind, pubkey, quote(d_tag), event_id, created_at, updated_at_ms) FROM event_envelope_head ORDER BY coordinate_type, kind, pubkey, d_tag",
+ )
+ .await,
+ coordinates: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%d|%s|%s|%s', hex(source_generation), event_id, event_seq, coordinate_type, admission_status, quote(nip09_d_tag)) FROM radroots_event_store_event_coordinate ORDER BY hex(source_generation), event_id",
+ )
+ .await,
+ nip09_requests: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%d|%d', request_event_id, request_pubkey, request_created_at, request_event_seq) FROM radroots_event_store_nip09_request ORDER BY hex(source_generation), request_event_id",
+ )
+ .await,
+ nip09_event_targets: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%d|%s', request_event_id, target_event_id, source_tag_index, source_tag_value) FROM radroots_event_store_nip09_event_target ORDER BY hex(source_generation), request_event_id, target_event_id, source_tag_index",
+ )
+ .await,
+ nip09_address_targets: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%d|%d', request_event_id, target_kind, target_pubkey, target_d_tag, inclusive_cutoff, source_tag_index) FROM radroots_event_store_nip09_address_target ORDER BY hex(source_generation), request_event_id, target_kind, target_pubkey, target_d_tag, source_tag_index",
+ )
+ .await,
+ addressable_heads: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%s|%s|%s', hex(source_generation), kind, pubkey, d_tag, raw_head_event_id, visibility, quote(nip09_reason)) FROM radroots_event_store_addressable_head_state ORDER BY hex(source_generation), kind, pubkey, d_tag",
+ )
+ .await,
+ transitions: query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s|%d|%s|%s|%s|%s', transition_seq, hex(source_generation), origin, kind, pubkey, d_tag, raw_head_event_id, visibility) FROM radroots_event_store_addressable_head_transition ORDER BY transition_seq",
+ )
+ .await,
+ food_cursor: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%d|%s|%s|%d', hex(source_generation), feed_version, projection_version, hex(scope_fingerprint), hook_manifest_sha256, projected_row_count) FROM radroots_event_store_food_availability_cursor ORDER BY singleton",
+ )
+ .await,
+ food_projection: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%s|%s|%s|%s', hex(source_generation), kind, pubkey, d_tag, event_id, title, location, status) FROM radroots_event_store_food_availability_projection ORDER BY pubkey, d_tag",
+ )
+ .await,
+ food_images: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%d|%s|%d', hex(source_generation), pubkey, d_tag, image_index, quote(url), qualifies) FROM radroots_event_store_food_availability_image ORDER BY pubkey, d_tag, image_index",
+ )
+ .await,
+ food_search: query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%s|%s', event_id, pubkey, d_tag, title, summary, content, location) FROM radroots_event_store_food_availability_search_fts ORDER BY event_id",
+ )
+ .await,
+ sqlite_sequences: query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s', rowid, quote(name), quote(seq)) FROM main.sqlite_sequence ORDER BY rowid",
+ )
+ .await,
+ }
+}
+
+async fn cold_file_authority_snapshot(path: &Path) -> (RebuildAuthoritySnapshot, String) {
+ let pool = SqlitePoolOptions::new()
+ .max_connections(1)
+ .connect_with(
+ SqliteConnectOptions::new()
+ .filename(path)
+ .create_if_missing(false),
+ )
+ .await
+ .expect("open cold snapshot pool");
+ let store = RadrootsEventStore { pool };
+ let snapshot = rebuild_authority_snapshot(&store).await;
+ let journal_mode = sqlx::query_scalar("PRAGMA main.journal_mode")
+ .fetch_one(store.pool())
+ .await
+ .expect("cold snapshot journal mode");
+ store.pool().close().await;
+ (snapshot, journal_mode)
+}
+
+async fn logical_product_snapshot(store: &RadrootsEventStore) -> Vec<Vec<String>> {
+ vec![
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%d', event_id, contract_status, quote(contract_id), quote(event_class), projection_eligible) FROM event_envelopes ORDER BY event_id",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%d', event_id, tag_index, quote(contract_semantic), quote(contract_value_type), relay_indexed) FROM event_envelope_tags ORDER BY event_id, tag_index",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%s', coordinate_type, kind, pubkey, quote(d_tag), event_id) FROM event_envelope_head ORDER BY coordinate_type, kind, pubkey, d_tag",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%d|%s|%s|%s|%s|%d|%s', event_id, event_seq, coordinate_type, kind, pubkey, admission_status, quote(admission_code), quote(contract_id), nip09_matchable, quote(nip09_d_tag)) FROM radroots_event_store_event_coordinate WHERE source_generation = (SELECT active_generation FROM radroots_event_store_source_state WHERE singleton = 1) ORDER BY event_id",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s|%s|%d|%s|%s|%s|%s', kind, pubkey, d_tag, raw_head_event_id, raw_head_created_at, admission_status, quote(admission_code), quote(contract_id), visibility) FROM radroots_event_store_addressable_head_state WHERE source_generation = (SELECT active_generation FROM radroots_event_store_source_state WHERE singleton = 1) ORDER BY kind, pubkey, d_tag",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s', event_id, admission_status, quote(contract_id), current_visibility, quote(suppression_reason)) FROM radroots_event_store_current_visibility_v1 ORDER BY event_id",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%d', request_event_id, request_pubkey, request_created_at) FROM radroots_event_store_nip09_request WHERE source_generation = (SELECT active_generation FROM radroots_event_store_source_state WHERE singleton = 1) ORDER BY request_event_id",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%d|%s', request_event_id, target_event_id, source_tag_index, source_tag_value) FROM radroots_event_store_nip09_event_target WHERE source_generation = (SELECT active_generation FROM radroots_event_store_source_state WHERE singleton = 1) ORDER BY request_event_id, target_event_id, source_tag_index",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%s|%s|%d|%d', request_event_id, target_kind, target_pubkey, target_d_tag, inclusive_cutoff, source_tag_index) FROM radroots_event_store_nip09_address_target WHERE source_generation = (SELECT active_generation FROM radroots_event_store_source_state WHERE singleton = 1) ORDER BY request_event_id, target_kind, target_pubkey, target_d_tag, source_tag_index",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s|%s|%s|%s|%s', kind, pubkey, d_tag, event_id, title, location, status) FROM radroots_event_store_food_availability_projection ORDER BY pubkey, d_tag",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%d|%s|%s|%s|%s|%d', pubkey, d_tag, image_index, raw_tag_json, quote(url), quote(width), quote(height), qualifies) FROM radroots_event_store_food_availability_image ORDER BY pubkey, d_tag, image_index",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%s|%s', event_id, pubkey, d_tag, title, summary, content, location) FROM radroots_event_store_food_availability_search_fts ORDER BY event_id",
+ )
+ .await,
+ ]
+}
+
+async fn set_trigger_guarded_drift(
+ store: &RadrootsEventStore,
+ trigger: &'static str,
+ mutation: &'static str,
+) {
+ let trigger_sql: String = sqlx::query_scalar(
+ "SELECT sql FROM main.sqlite_schema WHERE type = 'trigger' AND name = ?",
+ )
+ .bind(trigger)
+ .fetch_one(store.pool())
+ .await
+ .expect("load guard SQL");
+ sqlx::query(sqlx::AssertSqlSafe(format!("DROP TRIGGER main.{trigger}")))
+ .execute(store.pool())
+ .await
+ .expect("drop guard");
+ sqlx::query(mutation)
+ .execute(store.pool())
+ .await
+ .expect("forge drift");
+ sqlx::query(sqlx::AssertSqlSafe(trigger_sql))
+ .execute(store.pool())
+ .await
+ .expect("restore guard");
+}
+
+async fn transition_high_water(store: &RadrootsEventStore) -> i64 {
+ sqlx::query_scalar(
+ "SELECT COALESCE(MAX(transition_seq), 0) FROM radroots_event_store_addressable_head_transition",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("transition high-water")
+}
+
+async fn assert_nonempty_transition_high_water_drift_is_repaired(
+ drift_sql: &'static str,
+ pristine_generation: u8,
+ repair_generation: u8,
+ repeat_generation: u8,
+) {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ let pristine = rebuild_from_raw_v1_on_pool_for_test(
+ store.pool(),
+ &FixedGeneration(pristine_generation),
+ None,
+ )
+ .await
+ .expect("establish pristine rebuild");
+ let pristine_product = logical_product_snapshot(&store).await;
+ let prior_transition_high_water = transition_high_water(&store).await;
+ assert!(prior_transition_high_water > 0);
+
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_source_state_authority_update_guard",
+ drift_sql,
+ )
+ .await;
+ let drifted_high_water: i64 = sqlx::query_scalar(
+ "SELECT last_transition_seq FROM radroots_event_store_source_state WHERE singleton = 1",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("drifted source-state high-water");
+ assert_ne!(drifted_high_water, prior_transition_high_water);
+
+ let repaired = rebuild_from_raw_v1_on_pool_for_test(
+ store.pool(),
+ &FixedGeneration(repair_generation),
+ None,
+ )
+ .await
+ .expect("repair active transition high-water drift");
+ assert_eq!(
+ repaired.immutable_raw_digest(),
+ pristine.immutable_raw_digest()
+ );
+ assert_eq!(
+ repaired.active_product_state_digest(),
+ pristine.active_product_state_digest()
+ );
+ assert_eq!(logical_product_snapshot(&store).await, pristine_product);
+
+ let repaired_generation = repaired.new_source_generation();
+ let repaired_floor: i64 = sqlx::query_scalar(
+ "SELECT transition_floor_seq FROM radroots_event_store_source_generation WHERE source_generation = ?",
+ )
+ .bind(repaired_generation.as_bytes().as_slice())
+ .fetch_one(store.pool())
+ .await
+ .expect("repaired generation transition floor");
+ assert_eq!(repaired_floor, prior_transition_high_water);
+ let repaired_state_high_water: i64 = sqlx::query_scalar(
+ "SELECT last_transition_seq FROM radroots_event_store_source_state WHERE singleton = 1",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("repaired source-state high-water");
+ assert_eq!(
+ repaired_state_high_water,
+ transition_high_water(&store).await
+ );
+
+ store
+ .migrate_to_current_schema()
+ .await
+ .expect("ordinary reopen validation after repair");
+ let repeated = rebuild_from_raw_v1_on_pool_for_test(
+ store.pool(),
+ &FixedGeneration(repeat_generation),
+ None,
+ )
+ .await
+ .expect("repeat rebuild after repair");
+ assert_eq!(
+ repeated.immutable_raw_digest(),
+ repaired.immutable_raw_digest()
+ );
+ assert_eq!(
+ repeated.active_product_state_digest(),
+ repaired.active_product_state_digest()
+ );
+}
+
+async fn insert_projection_cursor_capacity(store: &RadrootsEventStore) {
+ sqlx::query(
+ "WITH digits(n) AS (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) INSERT INTO projection_cursor(projection_id, projection_version, last_event_seq, updated_at_ms) SELECT printf('projection-%04d', high.n * 256 + middle.n * 16 + low.n), 1, 0, 1 FROM digits AS high CROSS JOIN digits AS middle CROSS JOIN digits AS low",
+ )
+ .execute(store.pool())
+ .await
+ .expect("fill governed cursor capacity");
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_incremental_reopen_and_repeat_parity_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let path = tempdir.path().join("raw-rebuild-parity.sqlite");
+ let store = RadrootsEventStore::open_file(&path)
+ .await
+ .expect("open file");
+ seed_fixture_case(
+ &store,
+ FOOD_FIXTURE,
+ "post_cutoff_replacement_restores_projection",
+ "events",
+ )
+ .await;
+ let incremental = logical_product_snapshot(&store).await;
+
+ let first = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x21), None)
+ .await
+ .expect("first rebuild");
+ assert_eq!(logical_product_snapshot(&store).await, incremental);
+ store.pool().close().await;
+
+ let reopened = RadrootsEventStore::open_file(&path)
+ .await
+ .expect("strict reopen");
+ assert_eq!(logical_product_snapshot(&reopened).await, incremental);
+ let second =
+ rebuild_from_raw_v1_on_pool_for_test(reopened.pool(), &FixedGeneration(0x22), None)
+ .await
+ .expect("second rebuild");
+ let third = rebuild_from_raw_v1_on_pool_for_test(reopened.pool(), &FixedGeneration(0x23), None)
+ .await
+ .expect("repeat rebuild");
+ assert_eq!(logical_product_snapshot(&reopened).await, incremental);
+ assert_eq!(first.immutable_raw_digest(), second.immutable_raw_digest());
+ assert_eq!(second.immutable_raw_digest(), third.immutable_raw_digest());
+ assert_eq!(
+ first.active_product_state_digest(),
+ second.active_product_state_digest()
+ );
+ assert_eq!(
+ second.active_product_state_digest(),
+ third.active_product_state_digest()
+ );
+
+ for (index, (bytes, case_id, events_field)) in [
+ (
+ FOOD_FIXTURE,
+ "invalid_same_timestamp_winner_retracts_projection",
+ "events",
+ ),
+ (
+ FOOD_FIXTURE,
+ "blossom_digest_and_image_diagnostics_are_preserved",
+ "events",
+ ),
+ (
+ FOOD_FIXTURE,
+ "wrong_author_address_deletion_preserves_projection",
+ "events",
+ ),
+ (
+ FOOD_FIXTURE,
+ "operational_listing_head_retracts_food_projection",
+ "events",
+ ),
+ (
+ NIP09_FIXTURE,
+ "maximum_address_cutoff_is_order_independent",
+ "input_events",
+ ),
+ (
+ NIP09_FIXTURE,
+ "later_revision_survives_maximum_address_cutoff",
+ "input_events",
+ ),
+ (
+ NIP09_FIXTURE,
+ "unauthorized_exact_reference_does_not_override_authorized_stale_cutoff",
+ "input_events",
+ ),
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let case_store = RadrootsEventStore::open_memory()
+ .await
+ .expect("open case store");
+ seed_fixture_case(&case_store, bytes, case_id, events_field).await;
+ let incremental = logical_product_snapshot(&case_store).await;
+ let generation = u8::try_from(0x80 + index).expect("fixture generation");
+ let rebuilt = rebuild_from_raw_v1_on_pool_for_test(
+ case_store.pool(),
+ &FixedGeneration(generation),
+ None,
+ )
+ .await
+ .unwrap_or_else(|error| panic!("rebuild fixture case {case_id}: {error}"));
+ assert_eq!(
+ logical_product_snapshot(&case_store).await,
+ incremental,
+ "fixture case {case_id}"
+ );
+ let repeated = rebuild_from_raw_v1_on_pool_for_test(
+ case_store.pool(),
+ &FixedGeneration(generation + 0x10),
+ None,
+ )
+ .await
+ .unwrap_or_else(|error| panic!("repeat fixture case {case_id}: {error}"));
+ assert_eq!(
+ rebuilt.immutable_raw_digest(),
+ repeated.immutable_raw_digest(),
+ "fixture case {case_id} raw digest"
+ );
+ assert_eq!(
+ rebuilt.active_product_state_digest(),
+ repeated.active_product_state_digest(),
+ "fixture case {case_id} product digest"
+ );
+ let deletion_count: i64 =
+ sqlx::query_scalar("SELECT COUNT(*) FROM event_envelopes WHERE kind = 5")
+ .fetch_one(case_store.pool())
+ .await
+ .expect("deletion count");
+ let visible_deletion_count: i64 = sqlx::query_scalar(
+ "SELECT COUNT(*) FROM radroots_event_store_current_visibility_v1 AS visibility JOIN event_envelopes AS event USING (event_id) WHERE event.kind = 5 AND visibility.current_visibility = 'visible'",
+ )
+ .fetch_one(case_store.pool())
+ .await
+ .expect("visible deletion count");
+ assert_eq!(visible_deletion_count, deletion_count, "kind-5 immunity");
+ }
+}
+
+#[tokio::test]
+async fn projection_cursor_capacity_accepts_exact_and_rejects_one_over_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ rollback_event_store_schema_offline_destructive_for_migration_test(store.pool(), 1)
+ .await
+ .expect("rollback exact-cap store to v1");
+ insert_projection_cursor_capacity(&store).await;
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM projection_cursor")
+ .fetch_one(store.pool())
+ .await
+ .expect("cursor count"),
+ i64::from(RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1)
+ );
+ store
+ .migrate_to_current_schema()
+ .await
+ .expect("v1 migration accepts exact cursor capacity");
+
+ let generation = store.source_generation().await.expect("generation");
+ let existing_ticket = store
+ .prepare_projection_cursor_rebuild("projection-0000", 1)
+ .await
+ .expect("prepare existing cursor binding at exact capacity");
+ store
+ .reset_projection_cursor_after_rebuild(existing_ticket, 2)
+ .await
+ .expect("bind existing cursor at exact capacity");
+ let existing = RadrootsProjectionCursor::new("projection-0000", 1, generation, 0, 3)
+ .expect("existing cursor update");
+ store
+ .compare_and_swap_projection_cursor(&existing, Some(0))
+ .await
+ .expect("existing cursor remains updateable at exact capacity");
+ assert_eq!(
+ store
+ .projection_cursor("projection-0000", 1)
+ .await
+ .expect("read existing cursor")
+ .expect("existing cursor")
+ .updated_at_ms(),
+ 3
+ );
+ let overflow = RadrootsProjectionCursor::new("projection-overflow", 1, generation, 0, 2)
+ .expect("overflow cursor");
+ assert!(matches!(
+ store
+ .compare_and_swap_projection_cursor(&overflow, None)
+ .await,
+ Err(RadrootsEventStoreError::ProjectionCursorCapacityExceeded { current, limit })
+ if current == limit && limit == RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1
+ ));
+ let ticket = store
+ .prepare_projection_cursor_rebuild("projection-reset-overflow", 1)
+ .await
+ .expect("missing cursor ticket");
+ assert!(matches!(
+ store.reset_projection_cursor_after_rebuild(ticket, 3).await,
+ Err(RadrootsEventStoreError::ProjectionCursorCapacityExceeded { current, limit })
+ if current == limit && limit == RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1
+ ));
+
+ let one_over = RadrootsEventStore::open_memory()
+ .await
+ .expect("open one-over store");
+ rollback_event_store_schema_offline_destructive_for_migration_test(one_over.pool(), 1)
+ .await
+ .expect("rollback one-over store to v1");
+ insert_projection_cursor_capacity(&one_over).await;
+ sqlx::query(
+ "INSERT INTO projection_cursor(projection_id, projection_version, last_event_seq, updated_at_ms) VALUES ('projection-direct-overflow', 1, 0, 4)",
+ )
+ .execute(one_over.pool())
+ .await
+ .expect("forge one-over cursor inventory");
+ assert!(matches!(
+ one_over.migrate_to_current_schema().await,
+ Err(RadrootsEventStoreError::ProjectionCursorCapacityExceeded { current, limit })
+ if current == limit + 1
+ && limit == RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1
+ ));
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_invalidates_generic_cursors_without_enumerating_or_mutating_them_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ let generation = store.source_generation().await.expect("generation");
+ let cursor =
+ RadrootsProjectionCursor::new("generic", 1, generation, 0, 10).expect("generic cursor");
+ store
+ .compare_and_swap_projection_cursor(&cursor, None)
+ .await
+ .expect("insert cursor");
+ let before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%d|%d|%s|%d', cursor.projection_id, cursor.projection_version, cursor.last_event_seq, cursor.updated_at_ms, hex(source.source_generation), source.source_revision) FROM projection_cursor AS cursor JOIN radroots_event_store_projection_cursor_source AS source USING (projection_id) ORDER BY cursor.projection_id",
+ )
+ .await;
+
+ store.rebuild_from_raw_v1().await.expect("rebuild");
+ let after = query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%d|%d|%d|%s|%d', cursor.projection_id, cursor.projection_version, cursor.last_event_seq, cursor.updated_at_ms, hex(source.source_generation), source.source_revision) FROM projection_cursor AS cursor JOIN radroots_event_store_projection_cursor_source AS source USING (projection_id) ORDER BY cursor.projection_id",
+ )
+ .await;
+ assert_eq!(after, before);
+ assert!(matches!(
+ store.projection_cursor("generic", 1).await,
+ Err(RadrootsEventStoreError::ProjectionSourceGenerationMismatch { projection_id })
+ if projection_id == "generic"
+ ));
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_normalizes_only_transition_sqlite_sequence_v1() {
+ for (index, corruption) in ["missing", "low", "high", "duplicate", "case_alias"]
+ .into_iter()
+ .enumerate()
+ {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ for caller_index in 0..64 {
+ let create = format!(
+ "CREATE TABLE caller_autoincrement_{caller_index}(id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL)"
+ );
+ sqlx::query(sqlx::AssertSqlSafe(create))
+ .execute(store.pool())
+ .await
+ .expect("caller table");
+ let insert = format!(
+ "INSERT INTO caller_autoincrement_{caller_index}(value) VALUES ('preserve')"
+ );
+ sqlx::query(sqlx::AssertSqlSafe(insert))
+ .execute(store.pool())
+ .await
+ .expect("caller row");
+ }
+ match corruption {
+ "missing" => {
+ sqlx::query("DELETE FROM main.sqlite_sequence WHERE name = ?")
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(store.pool())
+ .await
+ .expect("remove target sequence");
+ }
+ "low" => {
+ sqlx::query("UPDATE main.sqlite_sequence SET seq = 0 WHERE name = ?")
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(store.pool())
+ .await
+ .expect("lower target sequence");
+ }
+ "high" => {
+ sqlx::query("UPDATE main.sqlite_sequence SET seq = 99 WHERE name = ?")
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(store.pool())
+ .await
+ .expect("raise target sequence");
+ }
+ "duplicate" => {
+ sqlx::query("INSERT INTO main.sqlite_sequence(name, seq) VALUES (?, 99)")
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(store.pool())
+ .await
+ .expect("duplicate target sequence");
+ }
+ "case_alias" => {
+ sqlx::query("UPDATE main.sqlite_sequence SET name = upper(name) WHERE name = ?")
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(store.pool())
+ .await
+ .expect("retarget sequence name casing");
+ }
+ _ => unreachable!("closed sequence corruption matrix"),
+ }
+ let unrelated_before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s', rowid, quote(name), quote(seq)) FROM main.sqlite_sequence WHERE name IS NULL OR name COLLATE NOCASE != 'radroots_event_store_addressable_head_transition' ORDER BY rowid",
+ )
+ .await;
+
+ rebuild_from_raw_v1_on_pool_for_test(
+ store.pool(),
+ &FixedGeneration(u8::try_from(0x31 + index).expect("test generation")),
+ None,
+ )
+ .await
+ .unwrap_or_else(|error| panic!("rebuild {corruption} sequence: {error}"));
+ let target_sequences: Vec<(i64, String, Option<i64>)> = sqlx::query_as(
+ "SELECT rowid, name, seq FROM main.sqlite_sequence WHERE name COLLATE NOCASE = ? ORDER BY rowid",
+ )
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .fetch_all(store.pool())
+ .await
+ .expect("target sequence rows");
+ assert_eq!(target_sequences.len(), 1, "{corruption}");
+ let target = &target_sequences[0];
+ assert_eq!(target.1, TRANSITION_SEQUENCE_NAME, "{corruption}");
+ assert_eq!(target.2, Some(2), "{corruption}");
+ assert_eq!(
+ sqlx::query_as::<_, (i64, String)>(
+ "SELECT rowid, name FROM main.sqlite_sequence ORDER BY rowid LIMIT 1",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("first sequence row"),
+ (target.0, TRANSITION_SEQUENCE_NAME.to_owned()),
+ "{corruption}"
+ );
+ let unrelated_after = query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s', rowid, quote(name), quote(seq)) FROM main.sqlite_sequence WHERE name IS NULL OR name COLLATE NOCASE != 'radroots_event_store_addressable_head_transition' ORDER BY rowid",
+ )
+ .await;
+ assert_eq!(unrelated_after, unrelated_before, "{corruption}");
+ }
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_rejects_unrelated_minimum_transition_sequence_rowid_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ sqlx::query("INSERT INTO main.sqlite_sequence(rowid, name, seq) VALUES (?, ?, 0)")
+ .bind(i64::MIN)
+ .bind("caller_minimum_sequence")
+ .execute(store.pool())
+ .await
+ .expect("occupy reserved sequence rowid");
+ let before = rebuild_authority_snapshot(&store).await;
+
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x3f), None,).await,
+ Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ ..
+ })
+ ));
+ assert_eq!(rebuild_authority_snapshot(&store).await, before);
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_reuses_target_alias_at_minimum_sequence_rowid_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ sqlx::query("DELETE FROM main.sqlite_sequence WHERE name COLLATE NOCASE = ?")
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(store.pool())
+ .await
+ .expect("remove canonical target row");
+ sqlx::query("INSERT INTO main.sqlite_sequence(rowid, name, seq) VALUES (?, upper(?), 99)")
+ .bind(i64::MIN)
+ .bind(TRANSITION_SEQUENCE_NAME)
+ .execute(store.pool())
+ .await
+ .expect("insert minimum target alias");
+
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x40), None)
+ .await
+ .expect("rebuild target alias");
+ assert_eq!(
+ sqlx::query_as::<_, (i64, String, i64)>(
+ "SELECT rowid, name, seq FROM main.sqlite_sequence ORDER BY rowid LIMIT 1",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("canonical minimum target row"),
+ (i64::MIN, TRANSITION_SEQUENCE_NAME.to_owned(), 2)
+ );
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_repairs_derived_drift_and_refuses_raw_drift_atomically_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_fixture_case(
+ &store,
+ FOOD_FIXTURE,
+ "post_cutoff_replacement_restores_projection",
+ "events",
+ )
+ .await;
+ let pristine = logical_product_snapshot(&store).await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_event_envelopes_derived_update_guard",
+ "UPDATE event_envelopes SET contract_status = 'invalid', contract_id = NULL, event_class = NULL, projection_eligible = 0",
+ )
+ .await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_nip09_request_update_guard",
+ "UPDATE radroots_event_store_nip09_request SET request_created_at = request_created_at + 1",
+ )
+ .await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_food_availability_cursor_update_guard",
+ "UPDATE radroots_event_store_food_availability_cursor SET projected_row_count = 0",
+ )
+ .await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_food_availability_projection_delete_guard",
+ "DELETE FROM radroots_event_store_food_availability_projection",
+ )
+ .await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_addressable_state_delete_guard",
+ "DELETE FROM radroots_event_store_addressable_head_state",
+ )
+ .await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_event_head_delete_guard",
+ "DELETE FROM event_envelope_head",
+ )
+ .await;
+ sqlx::query("DELETE FROM radroots_event_store_food_availability_search_fts")
+ .execute(store.pool())
+ .await
+ .expect("forge Food search drift");
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x32), None)
+ .await
+ .expect("repair derived drift");
+ assert_eq!(logical_product_snapshot(&store).await, pristine);
+
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_event_envelopes_raw_update_guard",
+ "UPDATE event_envelopes SET content = 'Parsnip available this week.' WHERE content = 'Carrots available this week.'",
+ )
+ .await;
+ let before = rebuild_authority_snapshot(&store).await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x33), None).await,
+ Err(RadrootsEventStoreError::RawEventReconciliationMismatch { field, .. })
+ if field == "content"
+ ));
+ assert_eq!(rebuild_authority_snapshot(&store).await, before);
+
+ let capacity_store = RadrootsEventStore::open_memory()
+ .await
+ .expect("open capacity-drift store");
+ seed_food_fixture(&capacity_store).await;
+ set_trigger_guarded_drift(
+ &capacity_store,
+ "radroots_event_store_source_capacity_update_guard",
+ "UPDATE radroots_event_store_source_capacity_v1 SET raw_event_count = raw_event_count + 1",
+ )
+ .await;
+ let capacity_before = rebuild_authority_snapshot(&capacity_store).await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(capacity_store.pool(), &FixedGeneration(0x34), None,)
+ .await,
+ Err(RadrootsEventStoreError::SourceCapacityStateDrift { reason })
+ if reason == "capacity seal does not match active source state and generation history"
+ ));
+ assert_eq!(
+ rebuild_authority_snapshot(&capacity_store).await,
+ capacity_before
+ );
+
+ let catalog_store = RadrootsEventStore::open_memory()
+ .await
+ .expect("open catalog-drift store");
+ seed_food_fixture(&catalog_store).await;
+ sqlx::query("DROP INDEX event_envelope_kind_created_idx")
+ .execute(catalog_store.pool())
+ .await
+ .expect("forge governed catalog drift");
+ let catalog_before = rebuild_authority_snapshot(&catalog_store).await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(catalog_store.pool(), &FixedGeneration(0x35), None,)
+ .await,
+ Err(RadrootsEventStoreError::SchemaFingerprintMismatch { .. })
+ ));
+ assert_eq!(
+ rebuild_authority_snapshot(&catalog_store).await,
+ catalog_before
+ );
+
+ let ledger_store = RadrootsEventStore::open_memory()
+ .await
+ .expect("open ledger-drift store");
+ seed_food_fixture(&ledger_store).await;
+ sqlx::query(
+ "UPDATE radroots_event_store_schema_migrations SET up_sha256 = ? WHERE version = 4",
+ )
+ .bind("0".repeat(64))
+ .execute(ledger_store.pool())
+ .await
+ .expect("forge migration-ledger drift");
+ let ledger_before = rebuild_authority_snapshot(&ledger_store).await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(ledger_store.pool(), &FixedGeneration(0x36), None,)
+ .await,
+ Err(RadrootsEventStoreError::MigrationHistoryChecksumDrift {
+ version: 4,
+ field: "up_sha256",
+ ..
+ })
+ ));
+ assert_eq!(
+ rebuild_authority_snapshot(&ledger_store).await,
+ ledger_before
+ );
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_failpoints_roll_back_every_stage_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ for (index, failpoint) in [
+ RawSourceRebuildFailpointV1::AfterMarkerOpen,
+ RawSourceRebuildFailpointV1::AfterGenerationRotation,
+ RawSourceRebuildFailpointV1::AfterCoreReplay,
+ RawSourceRebuildFailpointV1::AfterVisibilityAudit,
+ RawSourceRebuildFailpointV1::AfterFoodResetAndReplay,
+ RawSourceRebuildFailpointV1::AfterFoodAudit,
+ RawSourceRebuildFailpointV1::AfterMarkerClose,
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let path = tempdir.path().join(format!("failpoint-{index}.sqlite"));
+ let store = RadrootsEventStore::open_file(&path).await.expect("open");
+ seed_fixture_case(
+ &store,
+ FOOD_FIXTURE,
+ "authorized_address_deletion_retracts_projection",
+ "events",
+ )
+ .await;
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM radroots_event_store_nip09_request",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("NIP-09 request count"),
+ 1
+ );
+ let before = rebuild_authority_snapshot(&store).await;
+ let error = rebuild_from_raw_v1_on_pool_for_test(
+ store.pool(),
+ &FixedGeneration(0x41),
+ Some(failpoint),
+ )
+ .await
+ .expect_err("injected rebuild must fail");
+ assert!(matches!(
+ error,
+ RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ ..
+ }
+ ));
+ assert_eq!(rebuild_authority_snapshot(&store).await, before);
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM radroots_event_store_source_rebuild_marker",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("marker count"),
+ 0
+ );
+ store.pool().close().await;
+ let reopened = RadrootsEventStore::open_file(&path)
+ .await
+ .expect("strict reopen after rollback");
+ assert_eq!(rebuild_authority_snapshot(&reopened).await, before);
+ }
+}
+
+#[test]
+fn raw_source_rebuild_rollback_failure_preserves_primary_and_rollback_errors_v1() {
+ let primary = RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ detail: "primary".to_owned(),
+ };
+ let rollback = sqlx::Error::Protocol("rollback".to_owned());
+ assert!(matches!(
+ preserve_raw_source_rebuild_primary_failure_for_test::<()>(primary, Err(rollback)),
+ Err(RadrootsEventStoreError::RawSourceRebuildTransactionRollbackFailed {
+ primary,
+ rollback: sqlx::Error::Protocol(_),
+ }) if matches!(
+ *primary,
+ RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ ..
+ }
+ )
+ ));
+}
+
+#[test]
+fn raw_source_rebuild_drift_kind_codes_and_display_are_stable_v1() {
+ for (kind, expected) in [
+ (
+ RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ "managed_schema_authority",
+ ),
+ (
+ RadrootsEventStoreRawSourceRebuildDriftV1::ImmutableRawAuthority,
+ "immutable_raw_authority",
+ ),
+ (
+ RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ "source_generation_lineage",
+ ),
+ (
+ RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ "addressable_transition_authority",
+ ),
+ (
+ RadrootsEventStoreRawSourceRebuildDriftV1::DerivedProductStateAuthority,
+ "derived_product_state_authority",
+ ),
+ (
+ RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ "rebuild_postcondition",
+ ),
+ ] {
+ assert_eq!(kind.code(), expected);
+ assert_eq!(kind.to_string(), expected);
+ }
+
+ let error = RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ImmutableRawAuthority,
+ detail: "diagnostic context".to_owned(),
+ };
+ assert_eq!(
+ error.to_string(),
+ "event-store raw-source rebuild authority is inconsistent (immutable_raw_authority): diagnostic context"
+ );
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_wal_readers_observe_only_committed_generation_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let path = tempdir.path().join("raw-rebuild-wal.sqlite");
+ let writer = RadrootsEventStore::open_file(&path).await.expect("writer");
+ seed_food_fixture(&writer).await;
+ let reader = RadrootsEventStore::open_file(&path).await.expect("reader");
+ assert_eq!(writer.pragma_journal_mode().await.expect("WAL"), "wal");
+ let prior_generation = reader.source_generation().await.expect("prior generation");
+
+ let mut transaction = writer.begin_write_transaction().await.expect("writer tx");
+ let report =
+ rebuild_from_raw_v1_in_transaction_for_test(&mut transaction, &FixedGeneration(0x51))
+ .await
+ .expect("uncommitted rebuild");
+ assert_eq!(
+ reader.source_generation().await.expect("reader snapshot"),
+ prior_generation
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM radroots_event_store_source_rebuild_marker",
+ )
+ .fetch_one(reader.pool())
+ .await
+ .expect("reader marker count"),
+ 0
+ );
+ transaction.commit().await.expect("commit rebuild");
+ assert_eq!(
+ reader
+ .source_generation()
+ .await
+ .expect("committed generation"),
+ report.new_source_generation()
+ );
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_rejects_caller_inbound_foreign_keys_atomically_v1() {
+ for (suffix, on_delete) in [
+ ("cascade", "CASCADE"),
+ ("set_null", "SET NULL"),
+ ("set_default", "SET DEFAULT"),
+ ("restrict", "RESTRICT"),
+ ("no_action", "NO ACTION"),
+ ] {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ let child_table = format!("caller_inbound_{suffix}");
+ sqlx::query(
+ "CREATE TABLE caller_rebuild_side_effect(id INTEGER PRIMARY KEY AUTOINCREMENT, action TEXT NOT NULL)",
+ )
+ .execute(store.pool())
+ .await
+ .expect("caller side-effect table");
+ let parent_table = if suffix == "cascade" {
+ "RADROOTS_EVENT_STORE_FOOD_AVAILABILITY_CURSOR"
+ } else {
+ "radroots_event_store_food_availability_cursor"
+ };
+ let create_child = format!(
+ "CREATE TABLE {child_table}(id INTEGER PRIMARY KEY, parent_singleton INTEGER DEFAULT 1 REFERENCES {parent_table}(singleton) ON DELETE {on_delete}, note TEXT NOT NULL)"
+ );
+ sqlx::query(sqlx::AssertSqlSafe(create_child))
+ .execute(store.pool())
+ .await
+ .expect("caller inbound-FK table");
+ for (trigger_suffix, trigger_event) in [
+ ("delete", "AFTER DELETE"),
+ ("update", "AFTER UPDATE OF parent_singleton"),
+ ] {
+ let create_trigger = format!(
+ "CREATE TRIGGER {child_table}_{trigger_suffix}_side_effect {trigger_event} ON {child_table} BEGIN INSERT INTO caller_rebuild_side_effect(action) VALUES ('{trigger_suffix}'); END"
+ );
+ sqlx::query(sqlx::AssertSqlSafe(create_trigger))
+ .execute(store.pool())
+ .await
+ .expect("caller child side-effect trigger");
+ }
+ let insert_child = format!(
+ "INSERT INTO {child_table}(id, parent_singleton, note) VALUES (1, 1, 'preserve')"
+ );
+ sqlx::query(sqlx::AssertSqlSafe(insert_child))
+ .execute(store.pool())
+ .await
+ .expect("caller dependent row");
+
+ if suffix == "cascade" {
+ let mut connection = store.pool().acquire().await.expect("connection");
+ sqlx::query("CREATE TEMP TABLE pragma_foreign_key_list(value TEXT)")
+ .execute(&mut *connection)
+ .await
+ .expect("temporary pragma decoy");
+ }
+
+ let authority_before = rebuild_authority_snapshot(&store).await;
+ let child_before = sqlx::query_scalar::<_, String>(sqlx::AssertSqlSafe(format!(
+ "SELECT printf('%d|%s|%s', id, quote(parent_singleton), note) FROM {child_table} ORDER BY id"
+ )))
+ .fetch_all(store.pool())
+ .await
+ .expect("caller child snapshot");
+ let schema_before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s', type, name, tbl_name, sql) FROM main.sqlite_schema WHERE name LIKE 'caller_%' ORDER BY type, name",
+ )
+ .await;
+ let side_effect_before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s', id, action) FROM caller_rebuild_side_effect ORDER BY id",
+ )
+ .await;
+
+ let error = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &PanickingGeneration, None)
+ .await
+ .expect_err("caller inbound FK must be rejected before entropy");
+ match error {
+ RadrootsEventStoreError::RawSourceRebuildCallerInboundForeignKeyUnsupported {
+ dependency,
+ } => {
+ assert_eq!(dependency.child_table, child_table);
+ assert_eq!(dependency.foreign_key_id, 0);
+ assert_eq!(dependency.foreign_key_sequence, 0);
+ assert_eq!(dependency.child_column, "parent_singleton");
+ assert_eq!(
+ dependency.parent_table,
+ "radroots_event_store_food_availability_cursor"
+ );
+ assert_eq!(dependency.parent_column.as_deref(), Some("singleton"));
+ assert_eq!(dependency.on_update, "NO ACTION");
+ assert_eq!(dependency.on_delete, on_delete);
+ assert_eq!(dependency.match_clause, "NONE");
+ }
+ other => panic!("unexpected inbound-FK refusal: {other:?}"),
+ }
+
+ assert_eq!(rebuild_authority_snapshot(&store).await, authority_before);
+ assert_eq!(
+ sqlx::query_scalar::<_, String>(sqlx::AssertSqlSafe(format!(
+ "SELECT printf('%d|%s|%s', id, quote(parent_singleton), note) FROM {child_table} ORDER BY id"
+ )))
+ .fetch_all(store.pool())
+ .await
+ .expect("caller child after refusal"),
+ child_before
+ );
+ assert_eq!(
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s', type, name, tbl_name, sql) FROM main.sqlite_schema WHERE name LIKE 'caller_%' ORDER BY type, name",
+ )
+ .await,
+ schema_before
+ );
+ assert_eq!(
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s', id, action) FROM caller_rebuild_side_effect ORDER BY id",
+ )
+ .await,
+ side_effect_before
+ );
+ }
+
+ for (suffix, parent_table, parent_column) in [
+ (
+ "virtual",
+ "radroots_event_store_food_availability_search_fts",
+ "rowid",
+ ),
+ (
+ "config",
+ "radroots_event_store_food_availability_search_fts_config",
+ "k",
+ ),
+ (
+ "content",
+ "radroots_event_store_food_availability_search_fts_content",
+ "id",
+ ),
+ (
+ "data",
+ "radroots_event_store_food_availability_search_fts_data",
+ "id",
+ ),
+ (
+ "docsize",
+ "radroots_event_store_food_availability_search_fts_docsize",
+ "id",
+ ),
+ (
+ "idx",
+ "radroots_event_store_food_availability_search_fts_idx",
+ "segid",
+ ),
+ ("sqlite_sequence", "sqlite_sequence", "rowid"),
+ ] {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ let child_table = format!("caller_mutation_parent_{suffix}");
+ let mut connection = store.pool().acquire().await.expect("connection");
+ sqlx::query("PRAGMA foreign_keys = OFF")
+ .execute(&mut *connection)
+ .await
+ .expect("disable FK checks for mutation-parent fixture setup");
+ sqlx::query(
+ "CREATE TABLE caller_mutation_parent_side_effect(id INTEGER PRIMARY KEY AUTOINCREMENT, action TEXT NOT NULL)",
+ )
+ .execute(&mut *connection)
+ .await
+ .expect("caller mutation-parent side-effect table");
+ let create_child = format!(
+ "CREATE TABLE {child_table}(id INTEGER PRIMARY KEY, parent_key, note TEXT NOT NULL, FOREIGN KEY(parent_key) REFERENCES {parent_table}({parent_column}) ON UPDATE SET NULL ON DELETE CASCADE)"
+ );
+ sqlx::query(sqlx::AssertSqlSafe(create_child))
+ .execute(&mut *connection)
+ .await
+ .expect("caller mutation-parent inbound-FK table");
+ for (trigger_suffix, trigger_event) in [
+ ("delete", "AFTER DELETE"),
+ ("update", "AFTER UPDATE OF parent_key"),
+ ] {
+ let create_trigger = format!(
+ "CREATE TRIGGER {child_table}_{trigger_suffix}_side_effect {trigger_event} ON {child_table} BEGIN INSERT INTO caller_mutation_parent_side_effect(action) VALUES ('{trigger_suffix}'); END"
+ );
+ sqlx::query(sqlx::AssertSqlSafe(create_trigger))
+ .execute(&mut *connection)
+ .await
+ .expect("caller mutation-parent child side-effect trigger");
+ }
+ let insert_child = format!(
+ "INSERT INTO {child_table}(id, parent_key, note) SELECT 1, {parent_column}, 'preserve' FROM {parent_table} LIMIT 1"
+ );
+ let inserted = sqlx::query(sqlx::AssertSqlSafe(insert_child))
+ .execute(&mut *connection)
+ .await
+ .expect("caller mutation-parent dependent row");
+ assert_eq!(
+ inserted.rows_affected(),
+ 1,
+ "empty rebuild mutation parent {parent_table}"
+ );
+ sqlx::query("PRAGMA foreign_keys = ON")
+ .execute(&mut *connection)
+ .await
+ .expect("restore FK checks");
+ drop(connection);
+
+ let authority_before = rebuild_authority_snapshot(&store).await;
+ let child_before = sqlx::query_scalar::<_, String>(sqlx::AssertSqlSafe(format!(
+ "SELECT printf('%d|%s|%s', id, quote(parent_key), note) FROM {child_table} ORDER BY id"
+ )))
+ .fetch_all(store.pool())
+ .await
+ .expect("caller mutation-parent child snapshot");
+ let schema_before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s', type, name, tbl_name, sql) FROM main.sqlite_schema WHERE name LIKE 'caller_mutation_parent_%' ORDER BY type, name",
+ )
+ .await;
+ let side_effect_before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s', id, action) FROM caller_mutation_parent_side_effect ORDER BY id",
+ )
+ .await;
+
+ let error = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &PanickingGeneration, None)
+ .await
+ .expect_err("caller mutation-parent inbound FK must be rejected before entropy");
+ match error {
+ RadrootsEventStoreError::RawSourceRebuildCallerInboundForeignKeyUnsupported {
+ dependency,
+ } => {
+ assert_eq!(dependency.child_table, child_table);
+ assert_eq!(dependency.foreign_key_id, 0);
+ assert_eq!(dependency.foreign_key_sequence, 0);
+ assert_eq!(dependency.child_column, "parent_key");
+ assert_eq!(dependency.parent_table, parent_table);
+ assert_eq!(dependency.parent_column.as_deref(), Some(parent_column));
+ assert_eq!(dependency.on_update, "SET NULL");
+ assert_eq!(dependency.on_delete, "CASCADE");
+ assert_eq!(dependency.match_clause, "NONE");
+ }
+ other => panic!("unexpected mutation-parent inbound-FK refusal: {other:?}"),
+ }
+
+ assert_eq!(rebuild_authority_snapshot(&store).await, authority_before);
+ assert_eq!(
+ sqlx::query_scalar::<_, String>(sqlx::AssertSqlSafe(format!(
+ "SELECT printf('%d|%s|%s', id, quote(parent_key), note) FROM {child_table} ORDER BY id"
+ )))
+ .fetch_all(store.pool())
+ .await
+ .expect("caller mutation-parent child after refusal"),
+ child_before
+ );
+ assert_eq!(
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s', type, name, tbl_name, sql) FROM main.sqlite_schema WHERE name LIKE 'caller_mutation_parent_%' ORDER BY type, name",
+ )
+ .await,
+ schema_before
+ );
+ assert_eq!(
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s', id, action) FROM caller_mutation_parent_side_effect ORDER BY id",
+ )
+ .await,
+ side_effect_before
+ );
+ }
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_caller_schema_inventory_limits_are_typed_and_atomic_v1() {
+ let table_store = RadrootsEventStore::open_memory().await.expect("open");
+ for table in ["caller_inventory_a", "caller_inventory_b"] {
+ let create = format!("CREATE TABLE {table}(id INTEGER PRIMARY KEY, value TEXT NOT NULL)");
+ sqlx::query(sqlx::AssertSqlSafe(create))
+ .execute(table_store.pool())
+ .await
+ .expect("caller inventory table");
+ let insert = format!("INSERT INTO {table}(id, value) VALUES (1, 'preserve')");
+ sqlx::query(sqlx::AssertSqlSafe(insert))
+ .execute(table_store.pool())
+ .await
+ .expect("caller inventory row");
+ }
+ let table_authority_before = rebuild_authority_snapshot(&table_store).await;
+ let table_rows_before = query_string_rows(
+ table_store.pool(),
+ "SELECT (SELECT value FROM caller_inventory_a WHERE id = 1) || '|' || (SELECT value FROM caller_inventory_b WHERE id = 1)",
+ )
+ .await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_with_caller_schema_limits_for_test(
+ table_store.pool(),
+ &PanickingGeneration,
+ 1,
+ 4_096,
+ )
+ .await,
+ Err(
+ RadrootsEventStoreError::RawSourceRebuildCallerTableCapacityExceeded {
+ observed_at_least: 2,
+ limit: 1,
+ }
+ )
+ ));
+ assert_eq!(
+ rebuild_authority_snapshot(&table_store).await,
+ table_authority_before
+ );
+ assert_eq!(
+ query_string_rows(
+ table_store.pool(),
+ "SELECT (SELECT value FROM caller_inventory_a WHERE id = 1) || '|' || (SELECT value FROM caller_inventory_b WHERE id = 1)",
+ )
+ .await,
+ table_rows_before
+ );
+ rebuild_from_raw_v1_on_pool_with_caller_schema_limits_for_test(
+ table_store.pool(),
+ &FixedGeneration(0x91),
+ 2,
+ 4_096,
+ )
+ .await
+ .expect("exact caller-table capacity remains rebuildable");
+ assert_eq!(
+ query_string_rows(
+ table_store.pool(),
+ "SELECT (SELECT value FROM caller_inventory_a WHERE id = 1) || '|' || (SELECT value FROM caller_inventory_b WHERE id = 1)",
+ )
+ .await,
+ table_rows_before
+ );
+
+ let foreign_key_store = RadrootsEventStore::open_memory().await.expect("open");
+ sqlx::query("CREATE TABLE caller_fk_parent(id INTEGER PRIMARY KEY)")
+ .execute(foreign_key_store.pool())
+ .await
+ .expect("caller FK parent");
+ sqlx::query(
+ "CREATE TABLE caller_fk_child(id INTEGER PRIMARY KEY, caller_parent_id INTEGER REFERENCES caller_fk_parent(id), managed_singleton INTEGER REFERENCES radroots_event_store_food_availability_cursor(singleton) ON DELETE CASCADE)",
+ )
+ .execute(foreign_key_store.pool())
+ .await
+ .expect("caller FK child");
+ sqlx::query("INSERT INTO caller_fk_parent(id) VALUES (1)")
+ .execute(foreign_key_store.pool())
+ .await
+ .expect("caller FK parent row");
+ sqlx::query(
+ "INSERT INTO caller_fk_child(id, caller_parent_id, managed_singleton) VALUES (1, 1, 1)",
+ )
+ .execute(foreign_key_store.pool())
+ .await
+ .expect("caller FK child row");
+ let foreign_key_authority_before = rebuild_authority_snapshot(&foreign_key_store).await;
+ let foreign_key_rows_before = query_string_rows(
+ foreign_key_store.pool(),
+ "SELECT printf('%d|%d|%d', id, caller_parent_id, managed_singleton) FROM caller_fk_child ORDER BY id",
+ )
+ .await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_with_caller_schema_limits_for_test(
+ foreign_key_store.pool(),
+ &PanickingGeneration,
+ 2,
+ 1,
+ )
+ .await,
+ Err(
+ RadrootsEventStoreError::RawSourceRebuildCallerForeignKeyCapacityExceeded {
+ observed_at_least: 2,
+ limit: 1,
+ }
+ )
+ ));
+ assert_eq!(
+ rebuild_authority_snapshot(&foreign_key_store).await,
+ foreign_key_authority_before
+ );
+ assert_eq!(
+ query_string_rows(
+ foreign_key_store.pool(),
+ "SELECT printf('%d|%d|%d', id, caller_parent_id, managed_singleton) FROM caller_fk_child ORDER BY id",
+ )
+ .await,
+ foreign_key_rows_before
+ );
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_with_caller_schema_limits_for_test(
+ foreign_key_store.pool(),
+ &PanickingGeneration,
+ 2,
+ 2,
+ )
+ .await,
+ Err(RadrootsEventStoreError::RawSourceRebuildCallerInboundForeignKeyUnsupported {
+ dependency,
+ }) if dependency.child_table == "caller_fk_child"
+ && dependency.parent_table == "radroots_event_store_food_availability_cursor"
+ ));
+ assert_eq!(
+ rebuild_authority_snapshot(&foreign_key_store).await,
+ foreign_key_authority_before
+ );
+ assert_eq!(
+ query_string_rows(
+ foreign_key_store.pool(),
+ "SELECT printf('%d|%d|%d', id, caller_parent_id, managed_singleton) FROM caller_fk_child ORDER BY id",
+ )
+ .await,
+ foreign_key_rows_before
+ );
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_scoped_integrity_preserves_caller_state_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ let (event_seq, event_id, pubkey, created_at): (i64, String, String, i64) = sqlx::query_as(
+ "SELECT seq, event_id, pubkey, created_at FROM event_envelopes ORDER BY seq LIMIT 1",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("raw source row");
+ sqlx::query(
+ "INSERT INTO event_transport_observation(event_id, transport_kind, endpoint_uri, endpoint_fingerprint, observation_type, first_observed_at_ms, last_observed_at_ms, observation_count, redacted_message) VALUES (?, 'nostr', 'wss://relay.example', 'caller-endpoint', 'received', 1, 2, 2, 'preserve')",
+ )
+ .bind(&event_id)
+ .execute(store.pool())
+ .await
+ .expect("legacy transport observation");
+ sqlx::query(
+ "INSERT INTO listing_projection(listing_addr, listing_event_id, seller_pubkey, farm_pubkey, farm_d_tag, listing_d_tag, title, description, product_type, primary_bin_id, quantity_amount, quantity_unit, price_amount, price_currency, inventory_available, availability_status, delivery_method, locality_primary, locality_city, locality_region, locality_country, geohash5, listing_json, source_event_seq, created_at, updated_at_ms) VALUES ('caller-listing', ?, ?, ?, 'farm', 'listing', 'Carrots', 'Fresh carrots', 'vegetable', 'bin-1', '12', 'kg', '5.00', 'CAD', '12', 'available', 'pickup', 'Victoria, BC', 'Victoria', 'BC', 'CA', 'c28', '{}', ?, ?, 3)",
+ )
+ .bind(&event_id)
+ .bind(&pubkey)
+ .bind(&pubkey)
+ .bind(event_seq)
+ .bind(created_at)
+ .execute(store.pool())
+ .await
+ .expect("legacy listing projection");
+ sqlx::query(
+ "INSERT INTO listing_search_fts(listing_addr, title, description, product_type, locality, seller_pubkey) VALUES ('caller-listing', 'Carrots', 'Fresh carrots', 'vegetable', 'Victoria, BC', ?)",
+ )
+ .bind(&pubkey)
+ .execute(store.pool())
+ .await
+ .expect("legacy listing search row");
+ sqlx::query(
+ "INSERT INTO trade_mutation(mutation_id, trade_id, root_mutation_id, contract_id, mutation_kind, schema_version, candidate_id, proposal_mutation_id, target_claim_mutation_id, author_pubkey, counterparty_pubkey, buyer_pubkey, seller_pubkey, farm_id, authored_at_unix_s, canonical_payload_bytes, payload_sha256, first_event_seq, first_transport_event_id, inserted_at_ms) VALUES ('caller-mutation', 'caller-trade', NULL, 'radroots.trade.v1', 'proposal', 1, 'candidate-1', NULL, NULL, ?, ?, ?, ?, 'farm-1', ?, X'7B7D', ?, ?, ?, 4)",
+ )
+ .bind(&pubkey)
+ .bind(&pubkey)
+ .bind(&pubkey)
+ .bind(&pubkey)
+ .bind(created_at)
+ .bind("a".repeat(64))
+ .bind(event_seq)
+ .bind(&event_id)
+ .execute(store.pool())
+ .await
+ .expect("legacy trade mutation");
+ sqlx::query(
+ "INSERT INTO trade_transport_envelope(transport_event_id, mutation_id, trade_id, transport_kind, pubkey, created_at, event_seq, payload_sha256, observed_at_ms) VALUES (?, 'caller-mutation', 'caller-trade', 'nostr', ?, ?, ?, ?, 5)",
+ )
+ .bind(&event_id)
+ .bind(&pubkey)
+ .bind(created_at)
+ .bind(event_seq)
+ .bind("a".repeat(64))
+ .execute(store.pool())
+ .await
+ .expect("legacy trade transport envelope");
+ let mut connection = store.pool().acquire().await.expect("connection");
+ sqlx::query("PRAGMA foreign_keys = OFF")
+ .execute(&mut *connection)
+ .await
+ .expect("disable caller FK checks");
+ sqlx::query("CREATE TABLE caller_parent(id INTEGER PRIMARY KEY)")
+ .execute(&mut *connection)
+ .await
+ .expect("caller parent");
+ sqlx::query(
+ "CREATE TABLE caller_child(id INTEGER PRIMARY KEY AUTOINCREMENT, parent_id INTEGER NOT NULL REFERENCES caller_parent(id), note TEXT NOT NULL)",
+ )
+ .execute(&mut *connection)
+ .await
+ .expect("caller child");
+ sqlx::query("CREATE INDEX caller_child_note_idx ON caller_child(note)")
+ .execute(&mut *connection)
+ .await
+ .expect("caller index");
+ sqlx::query("INSERT INTO caller_child(parent_id, note) VALUES (999, 'preserve')")
+ .execute(&mut *connection)
+ .await
+ .expect("caller FK violation");
+ sqlx::query("PRAGMA foreign_keys = ON")
+ .execute(&mut *connection)
+ .await
+ .expect("restore FK checks");
+ drop(connection);
+ let caller_before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%d|%s', id, parent_id, note) FROM caller_child ORDER BY id",
+ )
+ .await;
+ let sequence_before = query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s', rowid, quote(name), quote(seq)) FROM sqlite_sequence WHERE name = 'caller_child'",
+ )
+ .await;
+ let caller_index_before = query_string_rows(
+ store.pool(),
+ "SELECT sql FROM sqlite_schema WHERE type = 'index' AND name = 'caller_child_note_idx'",
+ )
+ .await;
+ let legacy_before = vec![
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%d|%d|%d|%s', event_id, transport_kind, endpoint_fingerprint, observation_type, first_observed_at_ms, last_observed_at_ms, observation_count, quote(redacted_message)) FROM event_transport_observation ORDER BY event_id, transport_kind, endpoint_fingerprint, observation_type",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%s|%d|%d', listing_addr, listing_event_id, seller_pubkey, title, locality_primary, listing_json, source_event_seq, updated_at_ms) FROM listing_projection ORDER BY listing_addr",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%s', listing_addr, title, description, product_type, locality, seller_pubkey) FROM listing_search_fts ORDER BY listing_addr",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%d|%s|%s|%d|%s', mutation_id, trade_id, contract_id, mutation_kind, schema_version, hex(canonical_payload_bytes), payload_sha256, first_event_seq, first_transport_event_id) FROM trade_mutation ORDER BY mutation_id",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%d|%d|%s|%d', transport_event_id, mutation_id, trade_id, transport_kind, pubkey, created_at, event_seq, payload_sha256, observed_at_ms) FROM trade_transport_envelope ORDER BY transport_event_id",
+ )
+ .await,
+ ];
+
+ store
+ .rebuild_from_raw_v1()
+ .await
+ .expect("scoped rebuild ignores caller violation");
+ assert_eq!(
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%d|%s', id, parent_id, note) FROM caller_child ORDER BY id",
+ )
+ .await,
+ caller_before
+ );
+ assert_eq!(
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%d|%s|%s', rowid, quote(name), quote(seq)) FROM sqlite_sequence WHERE name = 'caller_child'",
+ )
+ .await,
+ sequence_before
+ );
+ assert_eq!(
+ query_string_rows(
+ store.pool(),
+ "SELECT sql FROM sqlite_schema WHERE type = 'index' AND name = 'caller_child_note_idx'",
+ )
+ .await,
+ caller_index_before
+ );
+ let legacy_after = vec![
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%d|%d|%d|%s', event_id, transport_kind, endpoint_fingerprint, observation_type, first_observed_at_ms, last_observed_at_ms, observation_count, quote(redacted_message)) FROM event_transport_observation ORDER BY event_id, transport_kind, endpoint_fingerprint, observation_type",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%s|%d|%d', listing_addr, listing_event_id, seller_pubkey, title, locality_primary, listing_json, source_event_seq, updated_at_ms) FROM listing_projection ORDER BY listing_addr",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%s', listing_addr, title, description, product_type, locality, seller_pubkey) FROM listing_search_fts ORDER BY listing_addr",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%d|%s|%s|%d|%s', mutation_id, trade_id, contract_id, mutation_kind, schema_version, hex(canonical_payload_bytes), payload_sha256, first_event_seq, first_transport_event_id) FROM trade_mutation ORDER BY mutation_id",
+ )
+ .await,
+ query_string_rows(
+ store.pool(),
+ "SELECT printf('%s|%s|%s|%s|%s|%d|%d|%s|%d', transport_event_id, mutation_id, trade_id, transport_kind, pubkey, created_at, event_seq, payload_sha256, observed_at_ms) FROM trade_transport_envelope ORDER BY transport_event_id",
+ )
+ .await,
+ ];
+ assert_eq!(legacy_after, legacy_before);
+ assert_eq!(
+ sqlx::query("PRAGMA foreign_key_check('caller_child')")
+ .fetch_all(store.pool())
+ .await
+ .expect("caller FK audit")
+ .len(),
+ 1
+ );
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_generation_exhaustion_precedes_entropy_and_mutation_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ for generation in 1..RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1 {
+ rebuild_from_raw_v1_on_pool_for_test(
+ store.pool(),
+ &FixedGeneration(u8::try_from(generation).expect("test generation")),
+ None,
+ )
+ .await
+ .expect("fill retained generation history");
+ }
+ let before = rebuild_authority_snapshot(&store).await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &PanickingGeneration, None).await,
+ Err(RadrootsEventStoreError::SourceGenerationHistoryLimitReached { current, limit })
+ if current == limit && limit == RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1
+ ));
+ assert_eq!(rebuild_authority_snapshot(&store).await, before);
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_entropy_failure_is_atomic_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ let before = rebuild_authority_snapshot(&store).await;
+
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FailingGeneration, None).await,
+ Err(RadrootsEventStoreError::SourceGenerationEntropyUnavailable)
+ ));
+ assert_eq!(rebuild_authority_snapshot(&store).await, before);
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_empty_source_without_transitions_is_deterministic_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ let first = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x61), None)
+ .await
+ .expect("first empty rebuild");
+ let second = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x62), None)
+ .await
+ .expect("second empty rebuild");
+ assert_eq!(first.raw_high_water_seq(), 0);
+ assert_eq!(second.raw_high_water_seq(), 0);
+ assert_eq!(first.immutable_raw_digest(), second.immutable_raw_digest());
+ assert_eq!(
+ first.active_product_state_digest(),
+ second.active_product_state_digest()
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM radroots_event_store_addressable_head_transition",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("transition count"),
+ 0
+ );
+ let sequence = sqlx::query_as::<_, (i64, String, i64)>(
+ "SELECT rowid, name, seq FROM sqlite_sequence ORDER BY rowid LIMIT 1",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("transition sequence");
+ assert_eq!(sequence.1, TRANSITION_SEQUENCE_NAME);
+ assert_eq!(sequence.2, 0);
+}
+
+#[tokio::test]
+async fn raw_source_repair_preflights_reject_bounded_authority_drift_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let missing_canonical_path = tempdir.path().join("missing-canonical.sqlite");
+ let missing_canonical_filename = missing_canonical_path.display().to_string();
+ assert!(matches!(
+ super::canonical_raw_source_repair_main_path_v1(&missing_canonical_path),
+ Err(RadrootsEventStoreError::RawSourceRepairMainDatabaseCanonicalizationFailed {
+ filename,
+ source,
+ }) if filename == missing_canonical_filename
+ && source.kind() == std::io::ErrorKind::NotFound
+ ));
+
+ let catalog_path = tempdir.path().join("repair-catalog-drift.sqlite");
+ let catalog_store = RadrootsEventStore::open_file(&catalog_path)
+ .await
+ .expect("open catalog fixture");
+ catalog_store.pool().close().await;
+ let mut catalog_connection =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&catalog_path))
+ .await
+ .expect("catalog drift connection");
+ sqlx::query("CREATE TABLE radroots_event_store_future_authority(value TEXT NOT NULL)")
+ .execute(&mut catalog_connection)
+ .await
+ .expect("add one reserved catalog object beyond managed authority");
+ catalog_connection
+ .close()
+ .await
+ .expect("close catalog drift connection");
+ assert!(matches!(
+ RadrootsEventStore::repair_file_from_raw_v1(&catalog_path).await,
+ Err(RadrootsEventStoreError::SchemaFingerprintMismatch { version: 4, .. })
+ ));
+ let mut catalog_verifier =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&catalog_path))
+ .await
+ .expect("catalog verifier");
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM main.sqlite_schema WHERE type = 'table' AND name = 'radroots_event_store_future_authority'",
+ )
+ .fetch_one(&mut catalog_verifier)
+ .await
+ .expect("reserved catalog object count"),
+ 1
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM radroots_event_store_source_generation",
+ )
+ .fetch_one(&mut catalog_verifier)
+ .await
+ .expect("catalog rejection generation count"),
+ 1
+ );
+ catalog_verifier
+ .close()
+ .await
+ .expect("close catalog verifier");
+
+ let history_path = tempdir.path().join("repair-history-drift.sqlite");
+ let history_store = RadrootsEventStore::open_file(&history_path)
+ .await
+ .expect("open history fixture");
+ history_store.pool().close().await;
+ let mut history_connection =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&history_path))
+ .await
+ .expect("history drift connection");
+ sqlx::query(
+ "INSERT INTO radroots_event_store_schema_migrations(version, name, up_sha256, down_sha256, schema_sha256) VALUES (5, 'future_migration', ?, ?, ?)",
+ )
+ .bind("0".repeat(64))
+ .bind("1".repeat(64))
+ .bind("2".repeat(64))
+ .execute(&mut history_connection)
+ .await
+ .expect("add the bounded fifth history row");
+ history_connection
+ .close()
+ .await
+ .expect("close history drift connection");
+ assert!(matches!(
+ RadrootsEventStore::repair_file_from_raw_v1(&history_path).await,
+ Err(RadrootsEventStoreError::SchemaTooNew {
+ current: 4,
+ database: 5,
+ })
+ ));
+ let mut history_verifier =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&history_path))
+ .await
+ .expect("history verifier");
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM radroots_event_store_schema_migrations",
+ )
+ .fetch_one(&mut history_verifier)
+ .await
+ .expect("history row count"),
+ 5
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM radroots_event_store_source_generation",
+ )
+ .fetch_one(&mut history_verifier)
+ .await
+ .expect("history rejection generation count"),
+ 1
+ );
+ history_verifier
+ .close()
+ .await
+ .expect("close history verifier");
+
+ let encoding_path = tempdir.path().join("repair-utf16.sqlite");
+ let mut encoding_connection = SqliteConnection::connect_with(
+ &SqliteConnectOptions::new()
+ .filename(&encoding_path)
+ .create_if_missing(true),
+ )
+ .await
+ .expect("UTF-16 fixture connection");
+ sqlx::query("PRAGMA main.encoding = 'UTF-16le'")
+ .execute(&mut encoding_connection)
+ .await
+ .expect("set UTF-16LE encoding");
+ sqlx::query("CREATE TABLE encoding_anchor(value TEXT NOT NULL)")
+ .execute(&mut encoding_connection)
+ .await
+ .expect("materialize UTF-16LE database");
+ sqlx::query("DROP TABLE encoding_anchor")
+ .execute(&mut encoding_connection)
+ .await
+ .expect("restore empty UTF-16LE catalog");
+ encoding_connection
+ .close()
+ .await
+ .expect("close UTF-16 fixture");
+ assert!(matches!(
+ RadrootsEventStore::repair_file_from_raw_v1(&encoding_path).await,
+ Err(RadrootsEventStoreError::SqliteMainDatabaseEncodingNotUtf8 { actual })
+ if actual == "UTF-16le"
+ ));
+ let mut encoding_verifier =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&encoding_path))
+ .await
+ .expect("UTF-16 verifier");
+ assert_eq!(
+ sqlx::query_scalar::<_, String>("PRAGMA main.encoding")
+ .fetch_one(&mut encoding_verifier)
+ .await
+ .expect("UTF-16 encoding after rejection"),
+ "UTF-16le"
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, String>("PRAGMA main.journal_mode")
+ .fetch_one(&mut encoding_verifier)
+ .await
+ .expect("UTF-16 journal mode after rejection"),
+ "delete"
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM main.sqlite_schema WHERE name = 'radroots_event_store_schema_migrations' OR name = 'event_envelopes' OR name LIKE 'radroots_event_store_%'",
+ )
+ .fetch_one(&mut encoding_verifier)
+ .await
+ .expect("UTF-16 event-store catalog after rejection"),
+ 0
+ );
+ encoding_verifier
+ .close()
+ .await
+ .expect("close UTF-16 verifier");
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_cold_file_repair_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let missing = tempdir.path().join("missing.sqlite");
+ assert!(matches!(
+ RadrootsEventStore::repair_file_from_raw_v1(&missing).await,
+ Err(RadrootsEventStoreError::RawSourceRepairMainDatabaseCanonicalizationFailed {
+ source,
+ ..
+ }) if source.kind() == std::io::ErrorKind::NotFound
+ ));
+ assert!(
+ !missing.exists(),
+ "cold repair must not create a missing file"
+ );
+
+ let unmanaged = tempdir.path().join("unmanaged.sqlite");
+ let mut unmanaged_connection = SqliteConnection::connect_with(
+ &SqliteConnectOptions::new()
+ .filename(&unmanaged)
+ .create_if_missing(true),
+ )
+ .await
+ .expect("unmanaged connection");
+ sqlx::query("CREATE TABLE caller_only(value TEXT NOT NULL)")
+ .execute(&mut unmanaged_connection)
+ .await
+ .expect("unmanaged caller table");
+ assert_eq!(
+ sqlx::query_scalar::<_, String>("PRAGMA main.journal_mode = DELETE")
+ .fetch_one(&mut unmanaged_connection)
+ .await
+ .expect("set unmanaged journal mode"),
+ "delete"
+ );
+ unmanaged_connection.close().await.expect("close unmanaged");
+ assert!(matches!(
+ RadrootsEventStore::repair_file_from_raw_v1(&unmanaged).await,
+ Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ ..
+ })
+ ));
+ let mut unmanaged_verifier =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&unmanaged))
+ .await
+ .expect("unmanaged verifier");
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM sqlite_schema WHERE name = 'radroots_event_store_schema_migrations'",
+ )
+ .fetch_one(&mut unmanaged_verifier)
+ .await
+ .expect("unmanaged ledger absence"),
+ 0
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT COUNT(*) FROM sqlite_schema WHERE type = 'table' AND name = 'caller_only'",
+ )
+ .fetch_one(&mut unmanaged_verifier)
+ .await
+ .expect("caller-owned table preservation"),
+ 1
+ );
+ assert_eq!(
+ sqlx::query_scalar::<_, String>("PRAGMA main.journal_mode")
+ .fetch_one(&mut unmanaged_verifier)
+ .await
+ .expect("unmanaged journal mode after rejected repair"),
+ "delete",
+ "rejected cold repair must not persistently configure WAL"
+ );
+ unmanaged_verifier.close().await.expect("close verifier");
+
+ let v3_path = tempdir.path().join("managed-v3.sqlite");
+ let v3_store = RadrootsEventStore::open_file(&v3_path)
+ .await
+ .expect("open v3 fixture");
+ rollback_event_store_schema_offline_destructive_for_migration_test(v3_store.pool(), 3)
+ .await
+ .expect("rollback to v3");
+ v3_store.pool().close().await;
+ assert!(matches!(
+ RadrootsEventStore::repair_file_from_raw_v1(&v3_path).await,
+ Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ ..
+ })
+ ));
+ let mut v3_verifier =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&v3_path))
+ .await
+ .expect("v3 verifier");
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT MAX(version) FROM radroots_event_store_schema_migrations",
+ )
+ .fetch_one(&mut v3_verifier)
+ .await
+ .expect("v3 ledger"),
+ 3
+ );
+ v3_verifier.close().await.expect("close v3 verifier");
+
+ let path = tempdir.path().join("cold-raw-repair.sqlite");
+ let store = RadrootsEventStore::open_file(&path).await.expect("open");
+ seed_food_fixture(&store).await;
+ let pristine_product = logical_product_snapshot(&store).await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_event_envelopes_derived_update_guard",
+ "UPDATE event_envelopes SET contract_status = 'invalid', contract_id = NULL, event_class = NULL, projection_eligible = 0",
+ )
+ .await;
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_food_availability_cursor_update_guard",
+ "UPDATE radroots_event_store_food_availability_cursor SET hook_manifest_sha256 = '0000000000000000000000000000000000000000000000000000000000000000'",
+ )
+ .await;
+ store.pool().close().await;
+ let ordinary_open_error = match RadrootsEventStore::open_file(&path).await {
+ Ok(_) => panic!("ordinary open must reject drifted derived authority"),
+ Err(error) => error,
+ };
+ assert!(matches!(
+ ordinary_open_error,
+ RadrootsEventStoreError::FoodAvailabilityProjectionDrift { reason }
+ if reason == "projection cursor identity is inconsistent"
+ ));
+
+ let (repaired, first_report) = RadrootsEventStore::repair_file_from_raw_v1(&path)
+ .await
+ .expect("cold file repair");
+ assert_eq!(first_report.source_capacity().raw_event_count(), 1);
+ assert_eq!(logical_product_snapshot(&repaired).await, pristine_product);
+
+ set_trigger_guarded_drift(
+ &repaired,
+ "radroots_event_store_food_availability_cursor_update_guard",
+ "UPDATE radroots_event_store_food_availability_cursor SET hook_manifest_sha256 = '0000000000000000000000000000000000000000000000000000000000000000'",
+ )
+ .await;
+ repaired.pool().close().await;
+ let (repaired_from_file, second_report) = RadrootsEventStore::repair_file_from_raw_v1(&path)
+ .await
+ .expect("second cold file repair");
+ assert_eq!(
+ first_report.immutable_raw_digest(),
+ second_report.immutable_raw_digest()
+ );
+ assert_eq!(
+ first_report.active_product_state_digest(),
+ second_report.active_product_state_digest()
+ );
+ assert_eq!(
+ logical_product_snapshot(&repaired_from_file).await,
+ pristine_product
+ );
+}
+
+#[tokio::test]
+async fn raw_source_repair_rejects_delete_mode_exact_v4_without_mutation_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let path = tempdir.path().join("repair-delete-mode.sqlite");
+ let store = RadrootsEventStore::open_file(&path)
+ .await
+ .expect("open exact-v4 fixture");
+ seed_food_fixture(&store).await;
+ store.pool().close().await;
+
+ let mut connection =
+ SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&path))
+ .await
+ .expect("DELETE-mode connection");
+ assert_eq!(
+ sqlx::query_scalar::<_, String>("PRAGMA main.journal_mode = DELETE")
+ .fetch_one(&mut connection)
+ .await
+ .expect("set DELETE mode"),
+ "delete"
+ );
+ connection.close().await.expect("close DELETE-mode fixture");
+ let (before, before_journal_mode) = cold_file_authority_snapshot(&path).await;
+ assert_eq!(before_journal_mode, "delete");
+
+ assert!(matches!(
+ RadrootsEventStore::repair_file_from_raw_v1(&path).await,
+ Err(RadrootsEventStoreError::SqliteFileJournalModeNotWal { actual })
+ if actual == "delete"
+ ));
+
+ let (after, after_journal_mode) = cold_file_authority_snapshot(&path).await;
+ assert_eq!(after, before);
+ assert_eq!(after_journal_mode, "delete");
+}
+
+#[tokio::test]
+async fn raw_source_repair_rejects_canonical_path_lock_domain_mismatch_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let primary_path = tempdir.path().join("repair-primary.sqlite");
+ let candidate_path = tempdir.path().join("repair-candidate.sqlite");
+ let primary_store = RadrootsEventStore::open_file(&primary_path)
+ .await
+ .expect("open primary fixture");
+ seed_food_fixture(&primary_store).await;
+ primary_store.pool().close().await;
+ let candidate_store = RadrootsEventStore::open_file(&candidate_path)
+ .await
+ .expect("open candidate fixture");
+ candidate_store.pool().close().await;
+ let primary_before = cold_file_authority_snapshot(&primary_path).await;
+ let candidate_before = cold_file_authority_snapshot(&candidate_path).await;
+ let canonical_candidate =
+ std::fs::canonicalize(&candidate_path).expect("canonical candidate path");
+
+ let mut primary = SqliteConnection::connect_with(
+ &SqliteConnectOptions::new()
+ .filename(&primary_path)
+ .create_if_missing(false),
+ )
+ .await
+ .expect("open primary connection");
+ let transaction = primary
+ .begin_with("BEGIN IMMEDIATE")
+ .await
+ .expect("hold primary write domain");
+ assert!(matches!(
+ super::validate_raw_source_repair_canonical_lock_domain_v1(&canonical_candidate).await,
+ Err(RadrootsEventStoreError::RawSourceRepairCanonicalPathLockDomainMismatch {
+ canonical_path,
+ }) if canonical_path == canonical_candidate.display().to_string()
+ ));
+ transaction
+ .rollback()
+ .await
+ .expect("rollback primary write domain");
+ primary.close().await.expect("close primary connection");
+ assert_eq!(
+ cold_file_authority_snapshot(&primary_path).await,
+ primary_before
+ );
+ assert_eq!(
+ cold_file_authority_snapshot(&candidate_path).await,
+ candidate_before
+ );
+}
+
+#[tokio::test]
+async fn raw_source_repair_post_preflight_failures_preserve_wal_and_state_v1() {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ for (case, trigger, mutation) in [
+ (
+ "raw",
+ "radroots_event_store_event_envelopes_raw_update_guard",
+ "UPDATE event_envelopes SET content = 'Parsnip available this week.' WHERE content = 'Carrots available this week.'",
+ ),
+ (
+ "source",
+ "radroots_event_store_source_capacity_update_guard",
+ "UPDATE radroots_event_store_source_capacity_v1 SET raw_event_count = raw_event_count + 1",
+ ),
+ ] {
+ let path = tempdir.path().join(format!("repair-{case}-drift.sqlite"));
+ let store = RadrootsEventStore::open_file(&path)
+ .await
+ .expect("open drift fixture");
+ seed_food_fixture(&store).await;
+ set_trigger_guarded_drift(&store, trigger, mutation).await;
+ let before = rebuild_authority_snapshot(&store).await;
+ store.pool().close().await;
+
+ let error = match RadrootsEventStore::repair_file_from_raw_v1(&path).await {
+ Ok(_) => panic!("post-preflight authority drift must fail"),
+ Err(error) => error,
+ };
+ match case {
+ "raw" => assert!(matches!(
+ error,
+ RadrootsEventStoreError::RawEventReconciliationMismatch {
+ field: "content",
+ ..
+ }
+ )),
+ "source" => assert!(matches!(
+ error,
+ RadrootsEventStoreError::SourceCapacityStateDrift { reason }
+ if reason
+ == "capacity seal does not match active source state and generation history"
+ )),
+ _ => unreachable!(),
+ }
+
+ let (after, journal_mode) = cold_file_authority_snapshot(&path).await;
+ assert_eq!(after, before, "{case} drift");
+ assert_eq!(journal_mode, "wal", "{case} drift");
+ }
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_repairs_active_transition_high_water_metadata_drift_v1() {
+ assert_nonempty_transition_high_water_drift_is_repaired(
+ "UPDATE radroots_event_store_source_state SET last_transition_seq = 0 WHERE singleton = 1",
+ 0x81,
+ 0x82,
+ 0x83,
+ )
+ .await;
+ assert_nonempty_transition_high_water_drift_is_repaired(
+ "UPDATE radroots_event_store_source_state SET last_transition_seq = (SELECT COALESCE(MAX(transition_seq), 0) + 7 FROM radroots_event_store_addressable_head_transition) WHERE singleton = 1",
+ 0x84,
+ 0x85,
+ 0x86,
+ )
+ .await;
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_repairs_empty_transition_high_water_metadata_drift_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ let pristine = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x87), None)
+ .await
+ .expect("establish empty pristine rebuild");
+ let pristine_product = logical_product_snapshot(&store).await;
+ assert_eq!(transition_high_water(&store).await, 0);
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_source_state_authority_update_guard",
+ "UPDATE radroots_event_store_source_state SET last_transition_seq = 7 WHERE singleton = 1",
+ )
+ .await;
+
+ let repaired = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x88), None)
+ .await
+ .expect("repair empty transition high-water drift");
+ assert_eq!(
+ repaired.immutable_raw_digest(),
+ pristine.immutable_raw_digest()
+ );
+ assert_eq!(
+ repaired.active_product_state_digest(),
+ pristine.active_product_state_digest()
+ );
+ assert_eq!(logical_product_snapshot(&store).await, pristine_product);
+ let repaired_generation = repaired.new_source_generation();
+ let repaired_floor: i64 = sqlx::query_scalar(
+ "SELECT transition_floor_seq FROM radroots_event_store_source_generation WHERE source_generation = ?",
+ )
+ .bind(repaired_generation.as_bytes().as_slice())
+ .fetch_one(store.pool())
+ .await
+ .expect("empty repaired generation transition floor");
+ assert_eq!(repaired_floor, 0);
+ assert_eq!(transition_high_water(&store).await, 0);
+ assert_eq!(
+ sqlx::query_scalar::<_, i64>(
+ "SELECT last_transition_seq FROM radroots_event_store_source_state WHERE singleton = 1",
+ )
+ .fetch_one(store.pool())
+ .await
+ .expect("empty repaired source-state high-water"),
+ 0
+ );
+
+ store
+ .migrate_to_current_schema()
+ .await
+ .expect("ordinary reopen validation after empty repair");
+ let repeated = rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x89), None)
+ .await
+ .expect("repeat empty rebuild after repair");
+ assert_eq!(
+ repeated.immutable_raw_digest(),
+ repaired.immutable_raw_digest()
+ );
+ assert_eq!(
+ repeated.active_product_state_digest(),
+ repaired.active_product_state_digest()
+ );
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_refuses_transition_history_gap_atomically_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_fixture_case(
+ &store,
+ FOOD_FIXTURE,
+ "post_cutoff_replacement_restores_projection",
+ "events",
+ )
+ .await;
+ let transition_count: i64 =
+ sqlx::query_scalar("SELECT COUNT(*) FROM radroots_event_store_addressable_head_transition")
+ .fetch_one(store.pool())
+ .await
+ .expect("transition count before gap");
+ assert!(transition_count >= 2);
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_addressable_transition_delete_guard",
+ "DELETE FROM radroots_event_store_addressable_head_transition WHERE transition_seq = (SELECT MIN(transition_seq) FROM radroots_event_store_addressable_head_transition)",
+ )
+ .await;
+ let before = rebuild_authority_snapshot(&store).await;
+
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x8a), None).await,
+ Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ detail,
+ }) if detail.contains("lineage row") || detail.contains("gaps or foreign rows")
+ ));
+ assert_eq!(rebuild_authority_snapshot(&store).await, before);
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_refuses_historical_generation_lineage_corruption_v1() {
+ let store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&store).await;
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x71), None)
+ .await
+ .expect("first rebuild");
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x72), None)
+ .await
+ .expect("second rebuild");
+ set_trigger_guarded_drift(
+ &store,
+ "radroots_event_store_source_generation_update_guard",
+ "UPDATE radroots_event_store_source_generation SET baseline_raw_event_count = 0 WHERE generation_ordinal = 2",
+ )
+ .await;
+ let before = rebuild_authority_snapshot(&store).await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(store.pool(), &FixedGeneration(0x73), None).await,
+ Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::SourceGenerationLineage,
+ detail,
+ }) if detail.contains("lineage row 2")
+ ));
+ assert_eq!(rebuild_authority_snapshot(&store).await, before);
+
+ let transition_store = RadrootsEventStore::open_memory().await.expect("open");
+ seed_food_fixture(&transition_store).await;
+ rebuild_from_raw_v1_on_pool_for_test(transition_store.pool(), &FixedGeneration(0x74), None)
+ .await
+ .expect("first rebuild");
+ rebuild_from_raw_v1_on_pool_for_test(transition_store.pool(), &FixedGeneration(0x75), None)
+ .await
+ .expect("second rebuild");
+ set_trigger_guarded_drift(
+ &transition_store,
+ "radroots_event_store_addressable_transition_update_guard",
+ "UPDATE radroots_event_store_addressable_head_transition SET source_generation = (SELECT source_generation FROM radroots_event_store_source_generation WHERE generation_ordinal = 2) WHERE transition_seq = 1",
+ )
+ .await;
+ let transition_before = rebuild_authority_snapshot(&transition_store).await;
+ assert!(matches!(
+ rebuild_from_raw_v1_on_pool_for_test(
+ transition_store.pool(),
+ &FixedGeneration(0x76),
+ None,
+ )
+ .await,
+ Err(RadrootsEventStoreError::RawSourceRebuildStateDrift {
+ kind: RadrootsEventStoreRawSourceRebuildDriftV1::AddressableTransitionAuthority,
+ detail,
+ }) if detail.contains("lineage row 1")
+ ));
+ assert_eq!(
+ rebuild_authority_snapshot(&transition_store).await,
+ transition_before
+ );
+}
diff --git a/crates/event_store/tests/fixtures/raw_source_rebuild.v1.json b/crates/event_store/tests/fixtures/raw_source_rebuild.v1.json
@@ -0,0 +1,462 @@
+{
+ "schema_version": 1,
+ "contract_id": "radroots_event_store.raw_source_rebuild_v1",
+ "delegated_suite": {
+ "id": "radroots_event_store.raw_source_rebuild_v1.delegated_rust_test_suite.v1",
+ "lane": "nix run .#contract",
+ "package": "radroots_event_store",
+ "authorities": [
+ {
+ "authority": "raw_source_rebuild_incremental_reopen_and_repeat_parity_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "projection_cursor_capacity_accepts_exact_and_rejects_one_over_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_invalidates_generic_cursors_without_enumerating_or_mutating_them_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_normalizes_only_transition_sqlite_sequence_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_rejects_unrelated_minimum_transition_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_reuses_target_alias_at_minimum_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_repairs_active_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_repairs_empty_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_repairs_derived_drift_and_refuses_raw_drift_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_refuses_transition_history_gap_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_refuses_historical_generation_lineage_corruption_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_rollback_failure_preserves_primary_and_rollback_errors_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_wal_readers_observe_only_committed_generation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_rejects_caller_inbound_foreign_keys_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_caller_schema_inventory_limits_are_typed_and_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_scoped_integrity_preserves_caller_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_generation_exhaustion_precedes_entropy_and_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_entropy_failure_is_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_empty_source_without_transitions_is_deterministic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_rebuild_cold_file_repair_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_preflights_reject_bounded_authority_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_rejects_delete_mode_exact_v4_without_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_rejects_canonical_path_lock_domain_mismatch_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_source_repair_post_preflight_failures_preserve_wal_and_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_covers_regular_replaceable_addressable_and_deletion_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_matches_wide_event_and_address_requests_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_matches_all_protocol_decision_branches_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ },
+ {
+ "authority": "raw_snapshot_visibility_oracle_is_order_and_repeat_invariant_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
+ }
+ ]
+ },
+ "cases": [
+ {
+ "id": "empty_source_repeat_digest_parity",
+ "execution": "direct_executor",
+ "authority": "raw_source_rebuild_v1_result_vector",
+ "authority_path": "crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs",
+ "expected_outcome": "two committed rebuilds rotate generations while preserving zero capacity, raw high-water, immutable-raw digest, and normalized product digest",
+ "expected_immutable_raw_digest": "73e66ea95452e902176d701311e6e82b3cd7895ae77f3d73fd1cbb67bcf9d321",
+ "expected_active_product_state_digest": "bf20fc2ba0e7c64bb0958829e118d87a86efe17e2848bb098d3d9ab2a78c2245"
+ },
+ {
+ "id": "signed_food_fixture_typed_digest_parity",
+ "execution": "direct_executor",
+ "authority": "raw_source_rebuild_v1_result_vector",
+ "authority_path": "crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs",
+ "expected_outcome": "one signed admitted FoodAvailability fixture freezes exact immutable-raw and generation-normalized product digests across text, i64, boolean, optional, and blob framing and preserves them across repeated rebuild",
+ "expected_immutable_raw_digest": "336a6a6cf1d84b0fcb185c4a7550cf5a8d8047c5a3f89df40e0d8f1ead543c68",
+ "expected_active_product_state_digest": "1ed8a22036091f0a492f3848027b313be4ef1202a9cdfa6c3ff35e12ab10f15c"
+ },
+ {
+ "id": "incremental_reopen_repeat_product_parity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_incremental_reopen_and_repeat_parity_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "incremental, file reopen, first rebuild, and repeated rebuild expose identical generation-normalized current visibility, Food image, and logical FTS product witnesses",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "projection_cursor_capacity_exact_and_one_over",
+ "execution": "delegated_rust_test",
+ "authority": "projection_cursor_capacity_accepts_exact_and_rejects_one_over_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "4,096 unique generic cursors are accepted, the next unique insert and a 4,097-row migration/reconciliation inventory probe fail typed, and an existing identity at capacity remains updateable",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "generic_cursor_lazy_generation_invalidation",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_invalidates_generic_cursors_without_enumerating_or_mutating_them_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "generic cursor rows remain byte-identical and become invalid only through active-generation mismatch",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "target_first_transition_sequence_normalization",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_normalizes_only_transition_sqlite_sequence_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "missing, low, high, duplicate, and case-aliased target rows normalize once to a canonical target-first row at the retained transition maximum while every unrelated sqlite_sequence row triple remains unchanged",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "unrelated_minimum_transition_sequence_rowid_refusal",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_rejects_unrelated_minimum_transition_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "an unrelated sqlite_sequence row at the minimum SQLite rowid exhausts target-first placement and rejects atomically",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "minimum_target_alias_sequence_reuse",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_reuses_target_alias_at_minimum_sequence_rowid_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "a case-aliased target already at the minimum SQLite rowid is reused, canonicalized, and advanced to the replay high-water",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "active_transition_high_water_metadata_repair",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_repairs_active_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "low and high active transition high-water metadata drift repairs to the exact retained transition maximum while preserving immutable-raw, normalized product, and logical product parity across repeat rebuild",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "empty_transition_high_water_metadata_repair",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_repairs_empty_transition_high_water_metadata_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "nonzero active transition high-water metadata on an empty source repairs to zero while preserving immutable-raw, normalized product, and logical product parity across reopen validation and repeat rebuild",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "derived_repair_and_raw_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_repairs_derived_drift_and_refuses_raw_drift_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "managed-v4 derived corruption is repaired, while immutable raw, capacity, governed catalog, or migration-ledger drift is refused before mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "transition_history_gap_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_refuses_transition_history_gap_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "a retained transition-history gap is refused as addressable-transition authority drift before any rebuild-owned state mutates",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "historical_generation_lineage_corruption_refusal",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_refuses_historical_generation_lineage_corruption_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "historical generation baselines and generation-bound transition lineage are authenticated before mutation, and any mismatch is refused atomically",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_marker_open",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_marker_open restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_generation_rotation",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_generation_rotation restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_core_replay",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_core_replay restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_visibility_audit",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_visibility_audit restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_food_reset_replay",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_food_reset_replay restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_food_audit",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_food_audit restores the exact prior committed database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_after_marker_close",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "failure after_marker_close restores the exact prior committed database and leaves no marker residue",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "rollback_failure_preserves_both_errors",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_rollback_failure_preserves_primary_and_rollback_errors_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "the typed rollback error preserves both the primary rebuild failure and the SQL rollback failure",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "wal_reader_commit_visibility",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_wal_readers_observe_only_committed_generation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "concurrent WAL readers observe only the prior committed generation until rebuild commit",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "caller_inbound_foreign_key_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_rejects_caller_inbound_foreign_keys_atomically_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "caller-owned inbound foreign keys to directly or indirectly mutated parents are rejected before entropy or mutation; representative managed-parent cases cover CASCADE, SET NULL, SET DEFAULT, RESTRICT, and NO ACTION, while explicit parent-inventory cases cover the Food FTS5 virtual table, all five shadows, and sqlite_sequence, preserving caller rows, schema, triggers, side effects, and rebuild authority",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "caller_schema_inventory_capacity_exact_and_one_over",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_caller_schema_inventory_limits_are_typed_and_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "bounded caller main-table and cumulative foreign-key-row inventories accept their exact limits and return typed atomic refusal one row over before entropy or mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "scoped_integrity_preserves_caller_state",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_scoped_integrity_preserves_caller_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "unrelated caller rows, indices, foreign-key violations, and AUTOINCREMENT counters with no dependency on rebuild-owned tables remain outside rebuild authority and byte-identical",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "generation_exhaustion_preflight",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_generation_exhaustion_precedes_entropy_and_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "the ninth retained generation fails before entropy, marker, sequence, raw, or derived mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "generation_entropy_failure_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_entropy_failure_is_atomic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "source-generation entropy failure returns the typed error before marker, sequence, raw, or derived mutation",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "empty_source_without_transitions",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_empty_source_without_transitions_is_deterministic_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "an empty source and absent target transition sequence rebuild deterministically without creating unrelated sequence state",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_file_repair",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_rebuild_cold_file_repair_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "maintenance-only file repair restores exact managed-v4 derived corruption through a fresh governed connection while refusing nonexistent, unmanaged, and non-v4 databases without weakening ordinary constructors",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_bounded_preflight_authority_drift_refusal",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_preflights_reject_bounded_authority_drift_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "bounded catalog, migration-history, temporary-schema, and encoding preflights refuse authority drift without creating rebuild state or changing persistent database authority",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_non_wal_file_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_rejects_delete_mode_exact_v4_without_mutation_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "cold repair requires an existing WAL database and rejects an exact managed-v4 DELETE-mode file without changing journal mode or governed state",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_canonical_path_lock_domain_refusal_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_rejects_canonical_path_lock_domain_mismatch_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "cold repair proves the caller path shares the validated SQLite writer-lock domain and rejects a mismatched canonical path without mutating either database",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "cold_post_preflight_failure_wal_state_atomicity",
+ "execution": "delegated_rust_test",
+ "authority": "raw_source_repair_post_preflight_failures_preserve_wal_and_state_v1",
+ "authority_path": "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ "expected_outcome": "raw reconciliation and source-capacity failures after cold preflight preserve the exact prior rebuild-owned state and WAL journal mode",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "pure_raw_snapshot_visibility_oracle",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_covers_regular_replaceable_addressable_and_deletion_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "the independent pure oracle covers regular, replaceable, addressable, empty-identifier replaceable address targets, and kind-5 visibility without consulting derived SQL views",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "direct_indexed_visibility_oracle_wide_targets",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_matches_wide_event_and_address_requests_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "one wide deletion request is reduced once into direct indexed event and address evidence whose per-target decisions exactly match the frozen NIP-09 evaluator without projection rescans",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "direct_indexed_visibility_oracle_protocol_matrix",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_matches_all_protocol_decision_branches_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "the direct indexed reducer exactly matches all seven frozen NIP-09 outcomes, preserves stale and winning evidence, and applies authorized-evidence precedence over mismatches",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ },
+ {
+ "id": "direct_indexed_visibility_oracle_order_repeat_invariance",
+ "execution": "delegated_rust_test",
+ "authority": "raw_snapshot_visibility_oracle_is_order_and_repeat_invariant_v1",
+ "authority_path": "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ "expected_outcome": "canonical exact-event and address evidence is invariant under reversed request order and repeated admitted requests, including cutoff ties",
+ "expected_immutable_raw_digest": null,
+ "expected_active_product_state_digest": null
+ }
+ ]
+}
diff --git a/crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs b/crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs
@@ -0,0 +1,656 @@
+#![forbid(unsafe_code)]
+
+use radroots_event_store::{RadrootsEventIngest, RadrootsEventStore};
+use serde::Deserialize;
+use serde_json::Value;
+use std::collections::BTreeSet;
+
+const RESULT_VECTOR_EXECUTOR_ID: &str =
+ "radroots_event_store.raw_source_rebuild_v1.result_vector_executor.v1";
+const RESULT_VECTOR_BYTES: &[u8] =
+ include_bytes!("../../../contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json");
+const FOOD_FIXTURE_BYTES: &[u8] = include_bytes!("fixtures/food_availability_projection.v1.json");
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct RawSourceRebuildVector {
+ schema_version: u32,
+ contract_id: String,
+ delegated_suite: DelegatedSuite,
+ cases: Vec<VectorCase>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct DelegatedSuite {
+ id: String,
+ lane: String,
+ package: String,
+ authorities: Vec<DelegatedAuthority>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct DelegatedAuthority {
+ authority: String,
+ authority_path: String,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct VectorCase {
+ id: String,
+ execution: String,
+ authority: String,
+ authority_path: String,
+ expected_outcome: String,
+ expected_immutable_raw_digest: Option<String>,
+ expected_active_product_state_digest: Option<String>,
+}
+
+#[derive(Clone, Copy)]
+struct ExpectedCase {
+ id: &'static str,
+ execution: &'static str,
+ authority: &'static str,
+ authority_path: &'static str,
+}
+
+#[derive(Clone, Copy)]
+struct ExpectedDelegatedAuthority {
+ authority: &'static str,
+ authority_path: &'static str,
+}
+
+const DIRECT_EXECUTOR: &str = "direct_executor";
+const DELEGATED_RUST_TEST: &str = "delegated_rust_test";
+const EXECUTOR_TEST: &str = "raw_source_rebuild_v1_result_vector";
+const EXECUTOR_PATH: &str = "crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs";
+const REBUILD_TEST_PATH: &str = "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs";
+const ORACLE_TEST_PATH: &str =
+ "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs";
+const DELEGATED_SUITE_ID: &str =
+ "radroots_event_store.raw_source_rebuild_v1.delegated_rust_test_suite.v1";
+const DELEGATED_SUITE_LANE: &str = "nix run .#contract";
+const DELEGATED_SUITE_PACKAGE: &str = "radroots_event_store";
+
+const EXPECTED_DELEGATED_AUTHORITIES: &[ExpectedDelegatedAuthority] = &[
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_incremental_reopen_and_repeat_parity_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "projection_cursor_capacity_accepts_exact_and_rejects_one_over_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_invalidates_generic_cursors_without_enumerating_or_mutating_them_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_normalizes_only_transition_sqlite_sequence_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_rejects_unrelated_minimum_transition_sequence_rowid_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_reuses_target_alias_at_minimum_sequence_rowid_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_repairs_active_transition_high_water_metadata_drift_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_repairs_empty_transition_high_water_metadata_drift_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_repairs_derived_drift_and_refuses_raw_drift_atomically_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_refuses_transition_history_gap_atomically_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_refuses_historical_generation_lineage_corruption_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_rollback_failure_preserves_primary_and_rollback_errors_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_wal_readers_observe_only_committed_generation_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_rejects_caller_inbound_foreign_keys_atomically_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_caller_schema_inventory_limits_are_typed_and_atomic_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_scoped_integrity_preserves_caller_state_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_generation_exhaustion_precedes_entropy_and_mutation_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_entropy_failure_is_atomic_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_empty_source_without_transitions_is_deterministic_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_rebuild_cold_file_repair_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_repair_preflights_reject_bounded_authority_drift_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_repair_rejects_delete_mode_exact_v4_without_mutation_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_repair_rejects_canonical_path_lock_domain_mismatch_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_source_repair_post_preflight_failures_preserve_wal_and_state_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_snapshot_visibility_oracle_covers_regular_replaceable_addressable_and_deletion_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_snapshot_visibility_oracle_matches_wide_event_and_address_requests_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_snapshot_visibility_oracle_matches_all_protocol_decision_branches_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+ ExpectedDelegatedAuthority {
+ authority: "raw_snapshot_visibility_oracle_is_order_and_repeat_invariant_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+];
+
+const EXPECTED_CASES: &[ExpectedCase] = &[
+ ExpectedCase {
+ id: "empty_source_repeat_digest_parity",
+ execution: DIRECT_EXECUTOR,
+ authority: EXECUTOR_TEST,
+ authority_path: EXECUTOR_PATH,
+ },
+ ExpectedCase {
+ id: "signed_food_fixture_typed_digest_parity",
+ execution: DIRECT_EXECUTOR,
+ authority: EXECUTOR_TEST,
+ authority_path: EXECUTOR_PATH,
+ },
+ ExpectedCase {
+ id: "incremental_reopen_repeat_product_parity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_incremental_reopen_and_repeat_parity_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "projection_cursor_capacity_exact_and_one_over",
+ execution: DELEGATED_RUST_TEST,
+ authority: "projection_cursor_capacity_accepts_exact_and_rejects_one_over_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "generic_cursor_lazy_generation_invalidation",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_invalidates_generic_cursors_without_enumerating_or_mutating_them_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "target_first_transition_sequence_normalization",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_normalizes_only_transition_sqlite_sequence_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "unrelated_minimum_transition_sequence_rowid_refusal",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_rejects_unrelated_minimum_transition_sequence_rowid_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "minimum_target_alias_sequence_reuse",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_reuses_target_alias_at_minimum_sequence_rowid_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "active_transition_high_water_metadata_repair",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_repairs_active_transition_high_water_metadata_drift_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "empty_transition_high_water_metadata_repair",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_repairs_empty_transition_high_water_metadata_drift_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "derived_repair_and_raw_refusal_atomicity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_repairs_derived_drift_and_refuses_raw_drift_atomically_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "transition_history_gap_refusal_atomicity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_refuses_transition_history_gap_atomically_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "historical_generation_lineage_corruption_refusal",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_refuses_historical_generation_lineage_corruption_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_after_marker_open",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_after_generation_rotation",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_after_core_replay",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_after_visibility_audit",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_after_food_reset_replay",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_after_food_audit",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_after_marker_close",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_failpoints_roll_back_every_stage_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "rollback_failure_preserves_both_errors",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_rollback_failure_preserves_primary_and_rollback_errors_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "wal_reader_commit_visibility",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_wal_readers_observe_only_committed_generation_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "caller_inbound_foreign_key_refusal_atomicity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_rejects_caller_inbound_foreign_keys_atomically_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "caller_schema_inventory_capacity_exact_and_one_over",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_caller_schema_inventory_limits_are_typed_and_atomic_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "scoped_integrity_preserves_caller_state",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_scoped_integrity_preserves_caller_state_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "generation_exhaustion_preflight",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_generation_exhaustion_precedes_entropy_and_mutation_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "generation_entropy_failure_atomicity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_entropy_failure_is_atomic_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "empty_source_without_transitions",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_empty_source_without_transitions_is_deterministic_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "cold_file_repair",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_rebuild_cold_file_repair_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "cold_bounded_preflight_authority_drift_refusal",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_repair_preflights_reject_bounded_authority_drift_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "cold_non_wal_file_refusal_atomicity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_repair_rejects_delete_mode_exact_v4_without_mutation_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "cold_canonical_path_lock_domain_refusal_atomicity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_repair_rejects_canonical_path_lock_domain_mismatch_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "cold_post_preflight_failure_wal_state_atomicity",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_source_repair_post_preflight_failures_preserve_wal_and_state_v1",
+ authority_path: REBUILD_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "pure_raw_snapshot_visibility_oracle",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_snapshot_visibility_oracle_covers_regular_replaceable_addressable_and_deletion_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "direct_indexed_visibility_oracle_wide_targets",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_snapshot_visibility_oracle_matches_wide_event_and_address_requests_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "direct_indexed_visibility_oracle_protocol_matrix",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_snapshot_visibility_oracle_matches_all_protocol_decision_branches_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+ ExpectedCase {
+ id: "direct_indexed_visibility_oracle_order_repeat_invariance",
+ execution: DELEGATED_RUST_TEST,
+ authority: "raw_snapshot_visibility_oracle_is_order_and_repeat_invariant_v1",
+ authority_path: ORACLE_TEST_PATH,
+ },
+];
+
+fn decode_digest(value: &str) -> [u8; 32] {
+ let mut bytes = [0_u8; 32];
+ hex::decode_to_slice(value, &mut bytes).expect("decode governed lowercase SHA-256 digest");
+ bytes
+}
+
+fn signed_food_fixture_ingest() -> RadrootsEventIngest {
+ let fixture: Value = serde_json::from_slice(FOOD_FIXTURE_BYTES).expect("parse Food fixture");
+ let observed = fixture["cases"]
+ .as_array()
+ .expect("Food fixture cases")
+ .iter()
+ .find(|case| case["id"].as_str() == Some("visible_food_availability_projects_and_searches"))
+ .and_then(|case| case["events"].as_array())
+ .and_then(|events| events.first())
+ .expect("signed Food fixture event");
+ let raw_json = serde_json::to_string(&observed["event"]).expect("serialize Food fixture event");
+ let observed_at_ms = observed["observed_at_ms"]
+ .as_i64()
+ .expect("Food fixture observation time");
+ RadrootsEventIngest::from_raw_json(raw_json, observed_at_ms)
+ .expect("verify signed Food fixture event")
+}
+
+#[tokio::test]
+async fn raw_source_rebuild_v1_result_vector() {
+ assert_eq!(
+ RESULT_VECTOR_EXECUTOR_ID,
+ "radroots_event_store.raw_source_rebuild_v1.result_vector_executor.v1"
+ );
+ let vector: RawSourceRebuildVector =
+ serde_json::from_slice(RESULT_VECTOR_BYTES).expect("parse raw-source rebuild vector");
+ assert_eq!(vector.schema_version, 1);
+ assert_eq!(
+ vector.contract_id,
+ "radroots_event_store.raw_source_rebuild_v1"
+ );
+ assert_eq!(vector.delegated_suite.id, DELEGATED_SUITE_ID);
+ assert_eq!(vector.delegated_suite.lane, DELEGATED_SUITE_LANE);
+ assert_eq!(vector.delegated_suite.package, DELEGATED_SUITE_PACKAGE);
+ assert_eq!(vector.cases.len(), EXPECTED_CASES.len());
+
+ let mut case_ids = BTreeSet::new();
+ for (case, expected) in vector.cases.iter().zip(EXPECTED_CASES) {
+ assert!(
+ case_ids.insert(case.id.as_str()),
+ "duplicate case {}",
+ case.id
+ );
+ assert_eq!(case.id, expected.id);
+ assert_eq!(case.execution, expected.execution);
+ assert_eq!(case.authority, expected.authority);
+ assert_eq!(case.authority_path, expected.authority_path);
+ assert!(!case.expected_outcome.is_empty());
+ for digest in [
+ case.expected_immutable_raw_digest.as_deref(),
+ case.expected_active_product_state_digest.as_deref(),
+ ]
+ .into_iter()
+ .flatten()
+ {
+ assert_eq!(digest.len(), 64);
+ assert!(
+ digest
+ .as_bytes()
+ .iter()
+ .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(byte))
+ );
+ }
+ }
+
+ assert_eq!(
+ vector.delegated_suite.authorities.len(),
+ EXPECTED_DELEGATED_AUTHORITIES.len()
+ );
+ let mut delegated_suite_authorities = BTreeSet::new();
+ for (actual, expected) in vector
+ .delegated_suite
+ .authorities
+ .iter()
+ .zip(EXPECTED_DELEGATED_AUTHORITIES)
+ {
+ assert_eq!(actual.authority, expected.authority);
+ assert_eq!(actual.authority_path, expected.authority_path);
+ assert!(
+ delegated_suite_authorities
+ .insert((actual.authority_path.as_str(), actual.authority.as_str(),)),
+ "duplicate delegated suite authority {}::{}",
+ actual.authority_path,
+ actual.authority
+ );
+ }
+ let delegated_case_authorities = vector
+ .cases
+ .iter()
+ .filter(|case| case.execution == DELEGATED_RUST_TEST)
+ .map(|case| (case.authority_path.as_str(), case.authority.as_str()))
+ .collect::<BTreeSet<_>>();
+ assert_eq!(delegated_suite_authorities, delegated_case_authorities);
+
+ let direct_case = &vector.cases[0];
+ assert_eq!(direct_case.id, "empty_source_repeat_digest_parity");
+ let expected_immutable_raw_digest = decode_digest(
+ direct_case
+ .expected_immutable_raw_digest
+ .as_deref()
+ .expect("direct case immutable-raw digest"),
+ );
+ let expected_active_product_state_digest = decode_digest(
+ direct_case
+ .expected_active_product_state_digest
+ .as_deref()
+ .expect("direct case active-product-state digest"),
+ );
+
+ let store = RadrootsEventStore::open_memory()
+ .await
+ .expect("open managed-v4 in-memory store");
+ let initial_generation = store
+ .source_generation()
+ .await
+ .expect("initial source generation");
+ let initial_capacity = store
+ .source_capacity_v1()
+ .await
+ .expect("initial source capacity");
+ assert_eq!(initial_capacity.raw_event_count(), 0);
+ assert_eq!(initial_capacity.raw_tag_count(), 0);
+ assert_eq!(initial_capacity.raw_event_text_bytes(), 0);
+ assert_eq!(initial_capacity.raw_tag_text_bytes(), 0);
+ assert_eq!(initial_capacity.raw_high_water_seq(), 0);
+
+ let first = store
+ .rebuild_from_raw_v1()
+ .await
+ .expect("first empty-source rebuild");
+ assert_eq!(first.prior_source_generation(), initial_generation);
+ assert_ne!(first.new_source_generation(), initial_generation);
+ assert_eq!(first.source_capacity().raw_event_count(), 0);
+ assert_eq!(first.source_capacity().raw_tag_count(), 0);
+ assert_eq!(first.source_capacity().raw_event_text_bytes(), 0);
+ assert_eq!(first.source_capacity().raw_tag_text_bytes(), 0);
+ assert_eq!(first.raw_high_water_seq(), 0);
+
+ let second = store
+ .rebuild_from_raw_v1()
+ .await
+ .expect("second empty-source rebuild");
+ assert_eq!(
+ second.prior_source_generation(),
+ first.new_source_generation()
+ );
+ assert_ne!(
+ second.new_source_generation(),
+ first.new_source_generation()
+ );
+ assert_eq!(second.source_capacity().raw_event_count(), 0);
+ assert_eq!(second.source_capacity().raw_tag_count(), 0);
+ assert_eq!(second.source_capacity().raw_event_text_bytes(), 0);
+ assert_eq!(second.source_capacity().raw_tag_text_bytes(), 0);
+ assert_eq!(second.raw_high_water_seq(), 0);
+ assert_eq!(second.immutable_raw_digest(), first.immutable_raw_digest());
+ assert_eq!(
+ second.active_product_state_digest(),
+ first.active_product_state_digest()
+ );
+ assert_eq!(
+ first.immutable_raw_digest().as_bytes(),
+ &expected_immutable_raw_digest,
+ "actual empty immutable-raw digest: {}",
+ hex::encode(first.immutable_raw_digest().as_bytes())
+ );
+ assert_eq!(
+ first.active_product_state_digest().as_bytes(),
+ &expected_active_product_state_digest,
+ "actual empty active-product digest: {}",
+ hex::encode(first.active_product_state_digest().as_bytes())
+ );
+
+ let food_case = &vector.cases[1];
+ assert_eq!(food_case.id, "signed_food_fixture_typed_digest_parity");
+ let expected_food_raw_digest = decode_digest(
+ food_case
+ .expected_immutable_raw_digest
+ .as_deref()
+ .expect("Food case immutable-raw digest"),
+ );
+ let expected_food_product_digest = decode_digest(
+ food_case
+ .expected_active_product_state_digest
+ .as_deref()
+ .expect("Food case active-product-state digest"),
+ );
+ let food_store = RadrootsEventStore::open_memory()
+ .await
+ .expect("open Food digest store");
+ food_store
+ .ingest_event(signed_food_fixture_ingest())
+ .await
+ .expect("ingest signed Food fixture");
+ let food_first = food_store
+ .rebuild_from_raw_v1()
+ .await
+ .expect("first Food fixture rebuild");
+ assert_eq!(food_first.source_capacity().raw_event_count(), 1);
+ assert!(food_first.source_capacity().raw_tag_count() > 0);
+ assert_eq!(food_first.raw_high_water_seq(), 1);
+ assert_eq!(
+ food_first.immutable_raw_digest().as_bytes(),
+ &expected_food_raw_digest,
+ "actual Food immutable-raw digest: {}",
+ hex::encode(food_first.immutable_raw_digest().as_bytes())
+ );
+ assert_eq!(
+ food_first.active_product_state_digest().as_bytes(),
+ &expected_food_product_digest,
+ "actual Food active-product digest: {}",
+ hex::encode(food_first.active_product_state_digest().as_bytes())
+ );
+ let food_second = food_store
+ .rebuild_from_raw_v1()
+ .await
+ .expect("second Food fixture rebuild");
+ assert_ne!(
+ food_second.new_source_generation(),
+ food_first.new_source_generation()
+ );
+ assert_eq!(
+ food_second.immutable_raw_digest(),
+ food_first.immutable_raw_digest()
+ );
+ assert_eq!(
+ food_second.active_product_state_digest(),
+ food_first.active_product_state_digest()
+ );
+}
diff --git a/tools/xtask/Cargo.toml b/tools/xtask/Cargo.toml
@@ -7,6 +7,10 @@ description = "Workspace task runner for Radroots Rust crates"
readme = "README"
license.workspace = true
publish = false
+autolib = false
+autotests = false
+autoexamples = false
+autobenches = false
authors = ["Tyson Lupul <tyson@radroots.org>"]
[dependencies]
diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs
@@ -6,6 +6,7 @@ mod comment_authority;
mod deletion_authority;
mod food_availability_projection;
mod nip09_reconciliation;
+mod raw_source_rebuild;
mod registry_v7;
mod source_maintenance;
@@ -15,6 +16,9 @@ pub(crate) use food_availability_projection::{
pub(crate) use nip09_reconciliation::{
validate_nip09_reconciliation_manifest, write_nip09_reconciliation_manifest,
};
+pub(crate) use raw_source_rebuild::{
+ validate_raw_source_rebuild_manifest, write_raw_source_rebuild_manifest,
+};
pub(crate) use registry_v7::{
validate_event_contract_registry_v7_inventory, write_event_contract_registry_v7_inventory,
};
@@ -49,6 +53,7 @@ pub(crate) fn validate_artifact_contracts(workspace_root: &Path) -> Result<(), S
validate_nip09_reconciliation_manifest(workspace_root)?;
validate_food_availability_projection_manifest(workspace_root)?;
validate_source_maintenance_manifest(workspace_root)?;
+ validate_raw_source_rebuild_manifest(workspace_root)?;
validate_knowledge_contract_manifest(workspace_root)
}
@@ -62,10 +67,13 @@ const FOOD_AVAILABILITY_PROJECTION_CONFORMANCE_VECTOR_RELATIVE: &str =
"contracts/conformance/vectors/event_store/food_availability_projection.v1.json";
const SOURCE_MAINTENANCE_CONFORMANCE_VECTOR_RELATIVE: &str =
"contracts/conformance/vectors/event_store/source_maintenance.v1.json";
-const SPECIALIZED_CONFORMANCE_VECTOR_RELATIVES: [&str; 3] = [
+const RAW_SOURCE_REBUILD_CONFORMANCE_VECTOR_RELATIVE: &str =
+ "contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json";
+const SPECIALIZED_CONFORMANCE_VECTOR_RELATIVES: [&str; 4] = [
NIP09_RECONCILIATION_CONFORMANCE_VECTOR_RELATIVE,
FOOD_AVAILABILITY_PROJECTION_CONFORMANCE_VECTOR_RELATIVE,
SOURCE_MAINTENANCE_CONFORMANCE_VECTOR_RELATIVE,
+ RAW_SOURCE_REBUILD_CONFORMANCE_VECTOR_RELATIVE,
];
const KNOWLEDGE_MANIFEST_RELATIVE: &str =
"contracts/knowledge/knowledge_event_contract_manifest.v2.json";
@@ -86,7 +94,7 @@ const REPLICA_CONTRACT_NAME: &str = "radroots_replica_contract";
const REPLICA_TRANSFER_CONSTANT: &str = "RADROOTS_REPLICA_TRANSFER_VERSION";
const REPLICA_TRANSFER_VERSION: u32 = 2;
const VENDORED_WORKSPACE_MEMBER_RELATIVE: &str = "crates/libsqlite3_sys_3_53_3";
-const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 22] = [
+const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 23] = [
(
"contracts/conformance/vectors/blossom/bud11_claims.v1.json",
"crates/blossom/tests/fixtures/bud11_claims.v1.json",
@@ -136,6 +144,10 @@ const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 22] = [
"crates/event_store/tests/fixtures/source_maintenance.v1.json",
),
(
+ RAW_SOURCE_REBUILD_CONFORMANCE_VECTOR_RELATIVE,
+ "crates/event_store/tests/fixtures/raw_source_rebuild.v1.json",
+ ),
+ (
"contracts/conformance/vectors/events/operational_listing_tags_full.v1.json",
"crates/event_codec/tests/fixtures/operational_listing_tags_full.v1.json",
),
diff --git a/tools/xtask/src/contract/food_availability_projection.rs b/tools/xtask/src/contract/food_availability_projection.rs
@@ -3992,7 +3992,8 @@ mod tests {
#[test]
fn downstream_nip09_only_supersession_is_transitively_validated() {
- const SOURCE_MAINTENANCE_SUPERSEDED_PATHS: &[&str] = &[
+ const CURRENT_SUCCESSOR_SUPERSEDED_PATHS: &[&str] = &[
+ "crates/event_store/Cargo.toml",
"crates/event_store/src/error.rs",
"crates/event_store/src/generated.rs",
"crates/event_store/src/lib.rs",
@@ -4001,6 +4002,7 @@ mod tests {
"crates/event_store/src/nip09/reconciliation_v1.rs",
"crates/event_store/src/schema.rs",
"crates/event_store/src/store.rs",
+ "crates/event_store/src/store/food_availability_projection_v1.rs",
"crates/event_store/src/store/protocol_reconciliation_v1.rs",
];
@@ -4023,11 +4025,11 @@ mod tests {
);
validate_food_availability_projection_predecessor_production_sources_under_lock(
&root,
- SOURCE_MAINTENANCE_SUPERSEDED_PATHS,
+ CURRENT_SUCCESSOR_SUPERSEDED_PATHS,
)
.expect("Food and transitive NIP-09 successor source coverage");
- let mut duplicate = SOURCE_MAINTENANCE_SUPERSEDED_PATHS.to_vec();
+ let mut duplicate = CURRENT_SUCCESSOR_SUPERSEDED_PATHS.to_vec();
duplicate.push("crates/event_store/src/store/protocol_reconciliation_v1.rs");
let error =
validate_food_availability_projection_predecessor_production_sources_under_lock(
@@ -4036,7 +4038,7 @@ mod tests {
.expect_err("duplicate transitive supersession must fail");
assert!(error.contains("must be unique"), "{error}");
- let mut unknown = SOURCE_MAINTENANCE_SUPERSEDED_PATHS.to_vec();
+ let mut unknown = CURRENT_SUCCESSOR_SUPERSEDED_PATHS.to_vec();
unknown.push("crates/event_store/src/store/not_predecessor_bound.rs");
let error =
validate_food_availability_projection_predecessor_production_sources_under_lock(
diff --git a/tools/xtask/src/contract/nip09_reconciliation.rs b/tools/xtask/src/contract/nip09_reconciliation.rs
@@ -184,8 +184,9 @@ const CORE_CARGO_MANIFEST_RELATIVE: &str = "crates/core/Cargo.toml";
const BLOSSOM_CARGO_MANIFEST_RELATIVE: &str = "crates/blossom/Cargo.toml";
const TRANSPORT_CARGO_MANIFEST_RELATIVE: &str = "crates/transport/Cargo.toml";
const CARGO_CONFIG_RELATIVE: &str = ".cargo/config.toml";
-const GOVERNED_WORKSPACE_DEPENDENCY_NAMES: [&str; 23] = [
+const GOVERNED_WORKSPACE_DEPENDENCY_NAMES: [&str; 24] = [
"dto_bindgen",
+ "futures",
"getrandom",
"hex",
"jiff-tzdb",
@@ -236,7 +237,7 @@ const GOVERNED_DEPENDENCY_TABLE_SHA256: [(&str, &str); 8] = [
),
(
"Cargo.toml#governed-workspace-dependencies",
- "0aa0aeb7988745aad1101c820a340c8ac04a5833c2ec7f7c997b5d9dfac010a3",
+ "edb48180d3cc3d00fead18984159487bb07afedeb646249a84c1d64ec0529e24",
),
(
"Cargo.toml#patch",
@@ -395,6 +396,17 @@ const SUCCESSOR_08C_EXCLUSIVE_SOURCE_PATHS: [&str; 8] = [
];
const SUCCESSOR_08D_SOURCE_PATHS: [&str; 1] = ["crates/event_store/src/source_maintenance_v1.rs"];
const SUCCESSOR_08D_LIB_MODULES: [&str; 1] = ["source_maintenance_v1"];
+const RAW_SOURCE_REBUILD_SOURCE_RELATIVE: &str =
+ "crates/event_store/src/nip09/reconciliation_v1/raw_source_rebuild.rs";
+const RAW_SOURCE_REBUILD_TEST_SOURCE_RELATIVE: &str =
+ "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs";
+const SUCCESSOR_08D1_EXCLUSIVE_SOURCE_PATHS: [&str; 5] = [
+ "crates/event_store/src/generated/raw_source_rebuild_manifest.rs",
+ "crates/event_store/src/model/raw_source_rebuild_v1.rs",
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ RAW_SOURCE_REBUILD_TEST_SOURCE_RELATIVE,
+];
const EVENT_STORE_FIXED_PUBLIC_REEXPORTS: [&str; 40] = [
"error::RadrootsEventStoreError",
"error::RadrootsEventStoreReconciliationResource",
@@ -482,6 +494,14 @@ const SUCCESSOR_08D_PUBLIC_REEXPORTS: [&str; 7] = [
"error::RadrootsEventStoreSourceCapacityResourceV1",
"source_maintenance_v1::RadrootsEventStoreSourceCapacityV1",
];
+const SUCCESSOR_08D1_PUBLIC_REEXPORTS: [&str; 6] = [
+ "error::RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1",
+ "error::RadrootsEventStoreCallerInboundForeignKeyV1",
+ "error::RadrootsEventStoreRawSourceRebuildDriftV1",
+ "model::RadrootsEventStoreActiveProductStateDigestV1",
+ "model::RadrootsEventStoreImmutableRawDigestV1",
+ "model::RadrootsEventStoreRawSourceRebuildReportV1",
+];
const POST_CORE_STORAGE_METHODS: [&str; 4] = [
"new",
"quarantine_trade",
@@ -2468,35 +2488,7 @@ pub(super) fn validate_nip09_predecessor_production_sources_under_lock(
|witness| witness.path.as_str(),
)?;
- let predecessor_impl_paths = manifest
- .impl_resolution_witness
- .impls
- .iter()
- .map(|item| item.path.as_str())
- .collect::<BTreeSet<_>>();
- let expected_impls = manifest
- .impl_resolution_witness
- .impls
- .iter()
- .filter(|item| !superseded.contains(item.path.as_str()))
- .cloned()
- .collect::<Vec<_>>();
- if !expected_impls.is_empty() {
- let current_impls = describe_impl_resolution_witness(workspace_root)?
- .impls
- .into_iter()
- .filter(|item| {
- predecessor_impl_paths.contains(item.path.as_str())
- && !superseded.contains(item.path.as_str())
- })
- .collect::<Vec<_>>();
- if current_impls != expected_impls {
- return Err(
- "unchanged predecessor impl-resolution authority drifted from the immutable manifest"
- .to_owned(),
- );
- }
- }
+ validate_predecessor_impl_resolution_authority(workspace_root, &manifest, superseded_paths)?;
let post_core_paths = [
POST_CORE_CAPABILITIES_SOURCE_RELATIVE,
@@ -2527,6 +2519,74 @@ pub(super) fn validate_nip09_predecessor_production_sources_under_lock(
Ok(())
}
+fn validate_predecessor_impl_resolution_authority(
+ workspace_root: &Path,
+ manifest: &Nip09ReconciliationManifest,
+ superseded_paths: &[&str],
+) -> Result<(), String> {
+ let superseded = superseded_paths.iter().copied().collect::<BTreeSet<_>>();
+ let predecessor_impl_paths = manifest
+ .impl_resolution_witness
+ .impls
+ .iter()
+ .map(|item| item.path.as_str())
+ .collect::<BTreeSet<_>>();
+ let expected_impls = manifest
+ .impl_resolution_witness
+ .impls
+ .iter()
+ .filter(|item| !superseded.contains(item.path.as_str()))
+ .cloned()
+ .collect::<Vec<_>>();
+ if expected_impls.is_empty() {
+ return Ok(());
+ }
+
+ let excluded_paths = superseded_paths
+ .iter()
+ .copied()
+ .chain(SUCCESSOR_08C_EXCLUSIVE_SOURCE_PATHS)
+ .chain(SUCCESSOR_08D_SOURCE_PATHS)
+ .chain(SUCCESSOR_08D1_EXCLUSIVE_SOURCE_PATHS)
+ .collect::<BTreeSet<_>>()
+ .into_iter()
+ .collect::<Vec<_>>();
+ let predecessor_protected_members = manifest
+ .impl_resolution_witness
+ .impls
+ .iter()
+ .filter_map(|item| item.member.as_ref())
+ .filter(|member| member.as_str() != "<macro>")
+ .cloned()
+ .collect::<BTreeSet<_>>();
+ let current_impls = describe_impl_resolution_witness_excluding_paths(
+ workspace_root,
+ &excluded_paths,
+ &manifest.impl_resolution_witness.protected_self_types,
+ &predecessor_protected_members,
+ )?
+ .impls
+ .into_iter()
+ .filter(|item| {
+ predecessor_impl_paths.contains(item.path.as_str())
+ && !superseded.contains(item.path.as_str())
+ })
+ .collect::<Vec<_>>();
+ if current_impls == expected_impls {
+ return Ok(());
+ }
+
+ let current = current_impls.iter().collect::<BTreeSet<_>>();
+ let expected = expected_impls.iter().collect::<BTreeSet<_>>();
+ let missing = expected.difference(¤t).copied().collect::<Vec<_>>();
+ let unexpected = current.difference(&expected).copied().collect::<Vec<_>>();
+ Err(format!(
+ "unchanged predecessor impl-resolution authority drifted from the immutable manifest: expected {} entries, found {}; missing {missing:?}; unexpected {unexpected:?}",
+ expected_impls.len(),
+ current_impls.len(),
+ ))
+}
+
fn require_predecessor_frozen_source_match(
expected: &FrozenSourceDescriptor,
current: &FrozenSourceDescriptor,
@@ -4734,6 +4794,15 @@ fn describe_source_route_witness(
fn describe_impl_resolution_witness(
workspace_root: &Path,
) -> Result<ImplResolutionWitnessDescriptor, String> {
+ describe_impl_resolution_witness_excluding_paths(workspace_root, &[], &[], &BTreeSet::new())
+}
+
+fn describe_impl_resolution_witness_excluding_paths(
+ workspace_root: &Path,
+ excluded_paths: &[&str],
+ frozen_protected_self_types: &[String],
+ frozen_protected_members: &BTreeSet<String>,
+) -> Result<ImplResolutionWitnessDescriptor, String> {
use syn::visit::Visit;
fn impl_member_name(item: &syn::ImplItem) -> Option<String> {
@@ -4938,6 +5007,7 @@ fn describe_impl_resolution_witness(
}
}
+ let excluded_paths = excluded_paths.iter().copied().collect::<BTreeSet<_>>();
let mut protected_paths = FROZEN_SOURCE_SPECS
.iter()
.map(|spec| spec.path)
@@ -4955,6 +5025,7 @@ fn describe_impl_resolution_witness(
POST_CORE_CAPABILITIES_SOURCE_RELATIVE,
POST_CORE_DISPATCHER_SOURCE_RELATIVE,
])
+ .filter(|relative| !excluded_paths.contains(relative))
.collect::<Vec<_>>();
protected_paths.sort_unstable();
protected_paths.dedup();
@@ -4967,6 +5038,9 @@ fn describe_impl_resolution_witness(
let file = parse_canonical_production_rust(relative, &bytes)?;
declaration_audit.visit_file(&file);
}
+ declaration_audit
+ .names
+ .extend(frozen_protected_self_types.iter().cloned());
if declaration_audit.names.is_empty() {
return Err("protected v1 impl-resolution type inventory must not be empty".to_owned());
}
@@ -4979,6 +5053,9 @@ fn describe_impl_resolution_witness(
};
for root in IMPL_RESOLUTION_SOURCE_ROOTS {
for relative in governed_regular_file_inventory(workspace_root, root)? {
+ if excluded_paths.contains(relative.as_str()) {
+ continue;
+ }
if !relative.ends_with(".rs") {
return Err(format!(
"{root} impl-resolution source inventory may contain only Rust files; found {relative}"
@@ -5031,6 +5108,7 @@ fn describe_impl_resolution_witness(
POST_CORE_EXTENSION_SOURCE_RELATIVE,
POST_CORE_STORAGE_SOURCE_RELATIVE,
])
+ .filter(|relative| !excluded_paths.contains(relative))
.collect::<Vec<_>>();
protected_member_paths.sort_unstable();
protected_member_paths.dedup();
@@ -5039,62 +5117,67 @@ fn describe_impl_resolution_witness(
let file = parse_canonical_production_rust(relative, &bytes)?;
protected_member_audit.visit_file(&file);
}
+ protected_member_audit
+ .names
+ .extend(frozen_protected_members.iter().cloned());
- let store_bytes = read_regular_file(workspace_root, EVENT_STORE_STORE_SOURCE_RELATIVE)?;
- let store_file =
- parse_canonical_production_rust(EVENT_STORE_STORE_SOURCE_RELATIVE, &store_bytes)?;
- let store_free_functions = store_file
- .items
- .iter()
- .filter_map(|item| match item {
- syn::Item::Fn(function) => Some((function.sig.ident.to_string(), function)),
- _ => None,
- })
- .collect::<BTreeMap<_, _>>();
- let mut local_resolution_queue = VecDeque::new();
- for spec in RUST_ITEM_WITNESS_ROOT_SPECS {
- match spec.callable {
- RustWitnessCallable::Associated { owner, name } => {
- let function = exact_associated_function(
- EVENT_STORE_STORE_SOURCE_RELATIVE,
- &store_file,
- owner,
- name,
- )?;
- protected_member_audit.visit_impl_item_fn(function);
- local_resolution_queue.extend(
- WitnessedFunction::Associated(function)
- .collect_call_routes()
- .into_iter()
- .filter_map(|route| route.strip_prefix("fn:").map(str::to_owned))
- .filter(|route| !route.contains("::"))
- .filter(|route| store_free_functions.contains_key(route)),
- );
- }
- RustWitnessCallable::Free { name } => {
- local_resolution_queue.push_back(name.to_owned());
+ if !excluded_paths.contains(EVENT_STORE_STORE_SOURCE_RELATIVE) {
+ let store_bytes = read_regular_file(workspace_root, EVENT_STORE_STORE_SOURCE_RELATIVE)?;
+ let store_file =
+ parse_canonical_production_rust(EVENT_STORE_STORE_SOURCE_RELATIVE, &store_bytes)?;
+ let store_free_functions = store_file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ syn::Item::Fn(function) => Some((function.sig.ident.to_string(), function)),
+ _ => None,
+ })
+ .collect::<BTreeMap<_, _>>();
+ let mut local_resolution_queue = VecDeque::new();
+ for spec in RUST_ITEM_WITNESS_ROOT_SPECS {
+ match spec.callable {
+ RustWitnessCallable::Associated { owner, name } => {
+ let function = exact_associated_function(
+ EVENT_STORE_STORE_SOURCE_RELATIVE,
+ &store_file,
+ owner,
+ name,
+ )?;
+ protected_member_audit.visit_impl_item_fn(function);
+ local_resolution_queue.extend(
+ WitnessedFunction::Associated(function)
+ .collect_call_routes()
+ .into_iter()
+ .filter_map(|route| route.strip_prefix("fn:").map(str::to_owned))
+ .filter(|route| !route.contains("::"))
+ .filter(|route| store_free_functions.contains_key(route)),
+ );
+ }
+ RustWitnessCallable::Free { name } => {
+ local_resolution_queue.push_back(name.to_owned());
+ }
}
}
- }
- let mut visited_local_resolution_functions = BTreeSet::new();
- while let Some(name) = local_resolution_queue.pop_front() {
- if !visited_local_resolution_functions.insert(name.clone()) {
- continue;
+ let mut visited_local_resolution_functions = BTreeSet::new();
+ while let Some(name) = local_resolution_queue.pop_front() {
+ if !visited_local_resolution_functions.insert(name.clone()) {
+ continue;
+ }
+ let function = store_free_functions.get(&name).ok_or_else(|| {
+ format!(
+ "{EVENT_STORE_STORE_SOURCE_RELATIVE} v1 resolution closure references missing local function `{name}`"
+ )
+ })?;
+ protected_member_audit.visit_item_fn(function);
+ local_resolution_queue.extend(
+ WitnessedFunction::Free(function)
+ .collect_call_routes()
+ .into_iter()
+ .filter_map(|route| route.strip_prefix("fn:").map(str::to_owned))
+ .filter(|route| !route.contains("::"))
+ .filter(|route| store_free_functions.contains_key(route)),
+ );
}
- let function = store_free_functions.get(&name).ok_or_else(|| {
- format!(
- "{EVENT_STORE_STORE_SOURCE_RELATIVE} v1 resolution closure references missing local function `{name}`"
- )
- })?;
- protected_member_audit.visit_item_fn(function);
- local_resolution_queue.extend(
- WitnessedFunction::Free(function)
- .collect_call_routes()
- .into_iter()
- .filter_map(|route| route.strip_prefix("fn:").map(str::to_owned))
- .filter(|route| !route.contains("::"))
- .filter(|route| store_free_functions.contains_key(route)),
- );
}
for spec in ENTRY_POINT_SPECS {
if let CallableSpec::Associated { name, .. } = spec.callable {
@@ -5889,7 +5972,6 @@ fn validate_event_store_schema_import_authority(
relative,
file,
&[
- "use crate::RadrootsEventStoreError;",
r#"use crate::migrations::{
EVENT_STORE_LEDGER_CREATE_DDL, EVENT_STORE_LEDGER_DDL, EVENT_STORE_LEDGER_NAME,
EVENT_STORE_MIGRATIONS, EventStoreMigration, EventStoreMigrationHook,
@@ -5898,6 +5980,7 @@ fn validate_event_store_schema_import_authority(
migration_for_version, sqlite_identifier_starts_with,
validate_embedded_migration_registry, validate_migration_registry,
};"#,
+ "use crate::{RadrootsEventStoreError, RadrootsEventStoreRawSourceRebuildDriftV1};",
"use sha2::{Digest, Sha256};",
"use sqlx::{Row, Sqlite, SqliteConnection, SqlitePool, Transaction};",
"use std::collections::{BTreeMap, BTreeSet};",
@@ -5994,6 +6077,7 @@ fn validate_privileged_store_authority(workspace_root: &Path) -> Result<(), Stri
let expected_module_sources = PRIVILEGED_STORE_MODULE_SOURCES
.into_iter()
.chain(SUCCESSOR_08C_STORE_MODULE_SOURCES)
+ .chain([RAW_SOURCE_REBUILD_TEST_SOURCE_RELATIVE])
.map(str::to_owned)
.collect::<BTreeSet<_>>();
let actual_module_sources = actual_module_sources.into_iter().collect::<BTreeSet<_>>();
@@ -6143,6 +6227,16 @@ fn validate_privileged_store_authority(workspace_root: &Path) -> Result<(), Stri
),
(
EVENT_STORE_STORE_SOURCE_RELATIVE,
+ "free:prepare_raw_source_repair_connection_v1",
+ "validate_main_database_encoding",
+ ),
+ (
+ EVENT_STORE_STORE_SOURCE_RELATIVE,
+ "free:validate_raw_source_repair_canonical_lock_domain_v1",
+ "validate_main_database_encoding",
+ ),
+ (
+ EVENT_STORE_STORE_SOURCE_RELATIVE,
"free:ingest_event_in_transaction",
"crate::schema::validate_event_store_temp_schema",
),
@@ -6247,6 +6341,12 @@ fn validate_event_store_privileged_terminal_authority(workspace_root: &Path) ->
]
.map(str::to_owned)
.to_vec(),
+ RAW_SOURCE_REBUILD_TEST_SOURCE_RELATIVE => [
+ "include_bytes!(\"../../tests/fixtures/food_availability_projection.v1.json\")",
+ "include_bytes!(\"../../tests/fixtures/nip09_reconciliation.v1.json\")",
+ ]
+ .map(str::to_owned)
+ .to_vec(),
_ => Vec::new(),
};
validate_compiler_macro_inputs(&relative, &file, &expected_macro_inputs)?;
@@ -6332,6 +6432,22 @@ fn validate_event_store_privileged_terminal_authority(workspace_root: &Path) ->
EVENT_STORE_PROTOCOL_RECONCILIATION_SOURCE_RELATIVE,
"crate::source_maintenance_v1::validate_source_capacity_authority_fast_v1",
),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "crate::source_maintenance_v1::preflight_source_generation_append_v1",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "crate::source_maintenance_v1::validate_source_capacity_authority_fast_v1",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "crate::source_maintenance_v1::validate_source_capacity_authority_full_v1",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "super::validate_active_hook_state_fast",
+ ),
]
.into_iter()
.map(|(relative, route)| (relative.to_owned(), route.to_owned()))
@@ -6475,6 +6591,16 @@ fn validate_event_store_privileged_terminal_authority(workspace_root: &Path) ->
"crate::schema::validate_event_store_temp_schema",
),
(
+ EVENT_STORE_STORE_SOURCE_RELATIVE,
+ "free:prepare_raw_source_repair_connection_v1",
+ "validate_main_database_encoding",
+ ),
+ (
+ EVENT_STORE_STORE_SOURCE_RELATIVE,
+ "free:validate_raw_source_repair_canonical_lock_domain_v1",
+ "validate_main_database_encoding",
+ ),
+ (
POST_CORE_CAPABILITIES_SOURCE_RELATIVE,
"associated:apply_v1",
"PostCoreStorageV1::new",
@@ -6624,6 +6750,36 @@ fn validate_event_store_privileged_terminal_authority(workspace_root: &Path) ->
"free:read_protocol_post_extension_authority_seal",
"validate_source_capacity_authority_fast_v1",
),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "free:rebuild_from_raw_v1_in_transaction_inner",
+ "crate::source_maintenance_v1::bind_source_capacity_to_generation_v1",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "free:rebuild_from_raw_v1_in_transaction_inner",
+ "preflight_source_generation_append_v1",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "free:rebuild_from_raw_v1_in_transaction_inner",
+ "validate_active_hook_state_fast",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "free:rebuild_from_raw_v1_in_transaction_inner",
+ "validate_source_capacity_authority_fast_v1",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "free:rebuild_from_raw_v1_in_transaction_inner",
+ "validate_source_capacity_authority_full_v1",
+ ),
+ (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "free:validate_source_lineage_for_rebuild_v1",
+ "validate_source_capacity_authority_fast_v1",
+ ),
]
.into_iter()
.map(|(relative, function, route)| PrivilegedStoreCallSite {
@@ -6679,6 +6835,16 @@ fn validate_event_store_module_source_graph(
modules: Vec::new(),
};
audit.visit_file(file);
+ if relative == "crates/event_store/src/nip09/reconciliation_v1.rs" {
+ let expected = ["modraw_source_rebuild;", "modvisibility_oracle_v1;"];
+ if audit.modules == expected {
+ return Ok(());
+ }
+ return Err(format!(
+ "{relative} raw-source rebuild module graph drifted: expected {expected:?}, found {:?}",
+ audit.modules
+ ));
+ }
if !audit.modules.is_empty() {
return Err(format!(
"{relative} event-store production module source graph is closed outside governed facade roots; found {:?}",
@@ -6741,17 +6907,29 @@ fn validate_event_store_trait_impl_authority(
"core::fmt::Display",
"RadrootsEventStoreSourceCapacityResourceV1",
),
+ (
+ "core::fmt::Display",
+ "RadrootsEventStoreRawSourceRebuildDriftV1",
+ ),
+ (
+ "core::fmt::Display",
+ "RadrootsEventStoreCallerInboundForeignKeyV1",
+ ),
("From<RadrootsTransportError>", "RadrootsEventStoreError"),
],
- "crates/event_store/src/nip09/reconciliation_v1.rs" => &[
- ("SourceGenerationProvider", "OsSourceGenerationProvider"),
- ("Iterator", "MergedRequestIndices<'_>"),
- ],
+ "crates/event_store/src/nip09/reconciliation_v1.rs" => {
+ &[("SourceGenerationProvider", "OsSourceGenerationProvider")]
+ }
"crates/event_store/src/model.rs" => &[
("AsRef<str>", "RadrootsTransportObservationMessage"),
("core::ops::Deref", "RadrootsTransportObservationMessage"),
],
RESULT_VECTOR_EXECUTOR_RELATIVE => &[("SourceGenerationProvider", "FixedGeneration")],
+ RAW_SOURCE_REBUILD_TEST_SOURCE_RELATIVE => &[
+ ("SourceGenerationProvider", "FixedGeneration"),
+ ("SourceGenerationProvider", "PanickingGeneration"),
+ ("SourceGenerationProvider", "FailingGeneration"),
+ ],
_ => &[],
};
let actual_trait_impls = audit
@@ -6765,7 +6943,10 @@ fn validate_event_store_trait_impl_authority(
));
}
let expected_inherent_self_types: &[&str] = match relative {
- "crates/event_store/src/error.rs" => &["RadrootsEventStoreSourceCapacityResourceV1"],
+ "crates/event_store/src/error.rs" => &[
+ "RadrootsEventStoreSourceCapacityResourceV1",
+ "RadrootsEventStoreRawSourceRebuildDriftV1",
+ ],
EVENT_STORE_MIGRATIONS_SOURCE_RELATIVE => &["EventStoreMigrationHook"],
"crates/event_store/src/model.rs" => &[
"RadrootsTransportObservationMessage",
@@ -6786,14 +6967,22 @@ fn validate_event_store_trait_impl_authority(
"RadrootsRawHeadDecision",
"RadrootsEventStoreSourceGeneration",
],
+ "crates/event_store/src/model/raw_source_rebuild_v1.rs" => &[
+ "RadrootsEventStoreImmutableRawDigestV1",
+ "RadrootsEventStoreActiveProductStateDigestV1",
+ "RadrootsEventStoreRawSourceRebuildReportV1",
+ ],
"crates/event_store/src/nip09/reconciliation_v1.rs" => &[
"ReconciliationCapacityLimits",
"ReconciliationCapacity",
"EventAdmission",
- "RequestIndex<'a>",
- "MergedRequestIndices<'a>",
+ "RequestIndex",
"TransitionOrigin",
],
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE => &["RawSourceRebuildCallerSchemaLimitsV1"],
+ "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs" => {
+ &["OracleRequestIndexV1<'a>"]
+ }
EVENT_STORE_STORE_SOURCE_RELATIVE => &["RadrootsEventStore"],
"crates/event_store/src/source_maintenance_v1.rs" => {
&["RadrootsEventStoreSourceCapacityV1"]
@@ -7346,6 +7535,18 @@ fn is_approved_privileged_terminal_import(relative: &str, route: &str) -> bool {
) | (
EVENT_STORE_PROTOCOL_RECONCILIATION_SOURCE_RELATIVE,
"crate::source_maintenance_v1::validate_source_capacity_authority_fast_v1"
+ ) | (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "crate::source_maintenance_v1::preflight_source_generation_append_v1"
+ ) | (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "crate::source_maintenance_v1::validate_source_capacity_authority_fast_v1"
+ ) | (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "crate::source_maintenance_v1::validate_source_capacity_authority_full_v1"
+ ) | (
+ RAW_SOURCE_REBUILD_SOURCE_RELATIVE,
+ "super::validate_active_hook_state_fast"
)
)
}
@@ -7480,6 +7681,7 @@ fn validate_event_store_lib_resolution_authority(
if !inherited_current
&& !SUCCESSOR_08C_PUBLIC_REEXPORTS.contains(&route.as_str())
&& !SUCCESSOR_08D_PUBLIC_REEXPORTS.contains(&route.as_str())
+ && !SUCCESSOR_08D1_PUBLIC_REEXPORTS.contains(&route.as_str())
{
return Err(format!(
"{relative} public export inventory is closed for this contract version; found unsupported reexport `{route}`"
@@ -7493,6 +7695,7 @@ fn validate_event_store_lib_resolution_authority(
.filter(|route| !SUCCESSOR_08D_RETIRED_PUBLIC_REEXPORTS.contains(route))
.chain(SUCCESSOR_08C_PUBLIC_REEXPORTS)
.chain(SUCCESSOR_08D_PUBLIC_REEXPORTS)
+ .chain(SUCCESSOR_08D1_PUBLIC_REEXPORTS)
.map(str::to_owned)
.collect::<BTreeSet<_>>();
let actual_use_set = actual_uses.iter().cloned().collect::<BTreeSet<_>>();
@@ -7846,6 +8049,22 @@ impl<'ast> syn::visit::Visit<'ast> for PrivilegedStoreReferenceAudit<'_> {
}
fn visit_item_enum(&mut self, item: &'ast syn::ItemEnum) {
+ if self.relative == EVENT_STORE_STORE_SOURCE_RELATIVE
+ && item.ident == "PoolTempSchemaPolicy"
+ {
+ let expected = syn::parse_str::<syn::ItemEnum>(
+ r#"#[derive(Clone, Copy)]
+ enum PoolTempSchemaPolicy {
+ Standard,
+ RawSourceRepairV1,
+ }"#,
+ )
+ .expect("parse governed pool TEMP-schema policy");
+ if compact_tokens(item) != compact_tokens(&expected) {
+ self.fail("raw-source rebuild pool TEMP-schema policy drifted");
+ }
+ return;
+ }
if is_privileged_store_value_binding(&item.ident.to_string()) {
self.fail(format!(
"shadows privileged authority with enum `{}`",
@@ -12495,7 +12714,6 @@ fn validate_sqlite_encoding_preflight_authority(
file_backed: bool,
) -> Result<(), RadrootsEventStoreError> {
let max_connections = pool.options().get_max_connections();
- let existing_options = pool.connect_options();
if !file_backed && max_connections != 1 {
return Err(RadrootsEventStoreError::UnsafeInMemoryPoolConnectionCount {
actual: max_connections,
@@ -12517,19 +12735,6 @@ fn validate_sqlite_encoding_preflight_authority(
}
validate_main_database_encoding(connection).await?;
crate::schema::validate_event_store_temp_schema(connection).await?;
- }
-
- let mut connect_options = existing_options
- .as_ref()
- .clone()
- .foreign_keys(true)
- .busy_timeout(Duration::from_millis(5_000));
- if file_backed {
- connect_options = connect_options.journal_mode(SqliteJournalMode::Wal);
- }
- pool.set_connect_options(connect_options);
-
- for connection in &mut connections {
sqlx::query("PRAGMA foreign_keys = ON")
.execute(&mut **connection)
.await?;
@@ -12540,12 +12745,24 @@ fn validate_sqlite_encoding_preflight_authority(
configure_file_journal_mode(connection).await?;
}
}
+ let existing_options = pool.connect_options();
+ let connect_options = existing_options
+ .as_ref()
+ .clone()
+ .foreign_keys(true)
+ .busy_timeout(Duration::from_millis(5_000));
+ let connect_options = if file_backed {
+ connect_options.journal_mode(SqliteJournalMode::Wal)
+ } else {
+ connect_options
+ };
+ pool.set_connect_options(connect_options);
Ok(())
}
"#;
if compact_tokens(configure_pool) != compact_source_tokens(expected_configure_pool) {
return Err(format!(
- "{relative} `configure_pool` must validate every main database as UTF-8 after backing classification and before TEMP-schema, connection-option, PRAGMA, or journal mutation"
+ "{relative} `configure_pool` must validate every main database as UTF-8 after backing classification and before TEMP-schema, PRAGMA, journal, or connection-option mutation"
));
}
@@ -12756,7 +12973,7 @@ fn validate_source_maintenance_runtime_token_authority(
(
"crates/event_store/src/nip09/reconciliation_v1.rs",
"apply_reconciliation_hook",
- "41a0bc1f4e529528f9bc13be28b4a31305156124282c1c7e955ed2e4a56e86d2",
+ "c73869559afe06b51c7df019f620509508bb574eaf51f2224493b3be28048682",
),
(
EVENT_STORE_STORE_SOURCE_RELATIVE,
@@ -14118,7 +14335,7 @@ fn describe_local_sqlite_source(
})
}
-fn governed_regular_file_inventory(
+pub(super) fn governed_regular_file_inventory(
workspace_root: &Path,
relative_root: &str,
) -> Result<Vec<String>, String> {
@@ -14326,6 +14543,23 @@ fn validate_support_source_graph_authority(relative: &str, file: &syn::File) ->
}
fn validate_governed_compiler_inputs(workspace_root: &Path) -> Result<(), String> {
+ validate_governed_compiler_inputs_with_event_store_successor(workspace_root, None)
+}
+
+pub(super) fn validate_raw_source_rebuild_successor_compiler_inputs(
+ workspace_root: &Path,
+ event_store_compiler_tables_sha256: &str,
+) -> Result<(), String> {
+ validate_governed_compiler_inputs_with_event_store_successor(
+ workspace_root,
+ Some(event_store_compiler_tables_sha256),
+ )
+}
+
+fn validate_governed_compiler_inputs_with_event_store_successor(
+ workspace_root: &Path,
+ event_store_successor_sha256: Option<&str>,
+) -> Result<(), String> {
let toolchain = parse_cargo_manifest(workspace_root, RUST_TOOLCHAIN_RELATIVE)?;
let expected_toolchain: toml::Value = toml::from_str(
r#"
@@ -14595,7 +14829,14 @@ xtask = "run -q -p xtask --"
let expected_identities = GOVERNED_DEPENDENCY_TABLE_SHA256
.iter()
- .map(|(relative, sha256)| ((*relative).to_owned(), (*sha256).to_owned()))
+ .map(|(relative, sha256)| {
+ let sha256 = if *relative == EVENT_STORE_CARGO_MANIFEST_RELATIVE {
+ event_store_successor_sha256.unwrap_or(sha256)
+ } else {
+ sha256
+ };
+ ((*relative).to_owned(), sha256.to_owned())
+ })
.collect::<Vec<_>>();
if actual_identities != expected_identities {
return Err(format!(
@@ -16972,6 +17213,20 @@ mod tests {
use super::*;
use std::fs;
+ const RAW_SOURCE_REBUILD_PREDECESSOR_SUPERSEDED_PATHS: [&str; 11] = [
+ "crates/event_store/Cargo.toml",
+ "crates/event_store/src/error.rs",
+ "crates/event_store/src/generated.rs",
+ "crates/event_store/src/lib.rs",
+ "crates/event_store/src/migrations.rs",
+ "crates/event_store/src/model.rs",
+ "crates/event_store/src/nip09/reconciliation_v1.rs",
+ "crates/event_store/src/schema.rs",
+ "crates/event_store/src/store.rs",
+ "crates/event_store/src/store/food_availability_projection_v1.rs",
+ "crates/event_store/src/store/protocol_reconciliation_v1.rs",
+ ];
+
fn repository_root() -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
@@ -16997,6 +17252,20 @@ mod tests {
.get_mut("dependencies")
.and_then(toml::Value::as_table_mut)
.expect("event-store dependencies");
+ let futures = dependencies
+ .remove("futures")
+ .expect("RawSourceRebuild futures compiler edge must be present in the live fixture");
+ let expected_futures: toml::Value =
+ toml::from_str("dependency = { workspace = true, optional = true }")
+ .expect("parse expected futures dependency");
+ assert_eq!(
+ futures,
+ expected_futures
+ .get("dependency")
+ .expect("expected futures dependency")
+ .clone(),
+ "RawSourceRebuild futures compiler edge must retain its exact semantic shape"
+ );
let blossom = dependencies
.remove("radroots_blossom")
.expect("successor Blossom compiler edge must be present in the live fixture");
@@ -17012,6 +17281,17 @@ mod tests {
.clone(),
"successor Blossom compiler edge must retain its exact semantic shape"
);
+ let sqlite_features = manifest
+ .get_mut("features")
+ .and_then(toml::Value::as_table_mut)
+ .and_then(|features| features.get_mut("sqlite"))
+ .and_then(toml::Value::as_array_mut)
+ .expect("event-store sqlite features");
+ let futures_index = sqlite_features
+ .iter()
+ .position(|feature| feature.as_str() == Some("dep:futures"))
+ .expect("RawSourceRebuild futures feature edge must be present in the live fixture");
+ sqlite_features.remove(futures_index);
let tokio_features = manifest
.get_mut("dev-dependencies")
.and_then(toml::Value::as_table_mut)
@@ -17114,6 +17394,7 @@ mod tests {
paths.extend(SOURCE_ROUTE_WITNESS_SPECS.iter().map(|source| source.path));
paths.extend(SUCCESSOR_08C_EXCLUSIVE_SOURCE_PATHS);
paths.extend(SUCCESSOR_08D_SOURCE_PATHS);
+ paths.extend(SUCCESSOR_08D1_EXCLUSIVE_SOURCE_PATHS);
paths.extend(super::super::source_maintenance::source_contract_fixture_source_paths());
paths.sort_unstable();
paths.dedup();
@@ -17806,8 +18087,8 @@ route!(r#hex);
1,
),
lib_original.replacen(
- "RadrootsEventStoreStatusSummary,\n RadrootsEventVisibility,",
- "RadrootsEventVisibility,",
+ "RadrootsEventStoreSourceGeneration, RadrootsEventStoreStatusSummary, RadrootsEventVisibility,",
+ "RadrootsEventStoreSourceGeneration, RadrootsEventVisibility,",
1,
),
];
@@ -18410,18 +18691,53 @@ route!(r#hex);
_ => None,
})
.expect("configure_pool");
- let syn::Stmt::Expr(syn::Expr::ForLoop(preflight), _) =
- &mut configure_pool.block.stmts[5]
- else {
- panic!("encoding preflight loop");
- };
+ let preflight = configure_pool
+ .block
+ .stmts
+ .iter_mut()
+ .find_map(|statement| match statement {
+ syn::Stmt::Expr(syn::Expr::ForLoop(preflight), _)
+ if compact_tokens(&preflight.body)
+ .contains("validate_main_database_encoding(connection).await?") =>
+ {
+ Some(preflight)
+ }
+ _ => None,
+ })
+ .expect("encoding preflight loop");
+ let encoding_index = preflight
+ .body
+ .stmts
+ .iter()
+ .position(|statement| {
+ compact_tokens(statement)
+ == "validate_main_database_encoding(connection).await?;"
+ })
+ .expect("encoding preflight statement");
+ let backing_index = preflight
+ .body
+ .stmts
+ .iter()
+ .position(|statement| {
+ compact_tokens(statement).starts_with("iffile_backed==database_is_memory")
+ })
+ .expect("backing classification statement");
+ let temp_index = preflight
+ .body
+ .stmts
+ .iter()
+ .position(|statement| {
+ compact_tokens(statement)
+ .starts_with("crate::schema::validate_event_store_temp_schema")
+ })
+ .expect("TEMP-schema validation statement");
match mutation {
"remove" => {
- preflight.body.stmts.remove(3);
+ preflight.body.stmts.remove(encoding_index);
}
- "discard" => strip_outer_try(&mut preflight.body.stmts[3]),
- "after_temp" => preflight.body.stmts.swap(3, 4),
- "before_backing" => preflight.body.stmts.swap(2, 3),
+ "discard" => strip_outer_try(&mut preflight.body.stmts[encoding_index]),
+ "after_temp" => preflight.body.stmts.swap(encoding_index, temp_index),
+ "before_backing" => preflight.body.stmts.swap(backing_index, encoding_index),
_ => unreachable!(),
}
assert!(
@@ -20370,6 +20686,69 @@ pub(crate) fn migration_for_version"#,
}
#[test]
+ fn predecessor_impl_resolution_projection_is_fail_closed() {
+ let workspace = synthetic_workspace();
+ let manifest = immutable_manifest();
+ validate_predecessor_impl_resolution_authority(
+ workspace.path(),
+ &manifest,
+ &RAW_SOURCE_REBUILD_PREDECESSOR_SUPERSEDED_PATHS,
+ )
+ .expect("current successor must preserve predecessor impl authority");
+ let unchanged_relative = "crates/event_codec/src/deletion/reconciliation_v1.rs";
+ let unchanged_path = workspace.path().join(unchanged_relative);
+ let unchanged = fs::read_to_string(&unchanged_path).expect("unchanged predecessor source");
+
+ fs::write(
+ &unchanged_path,
+ format!(
+ "{unchanged}\ntrait UnexpectedPredecessorResolution {{}}\nimpl UnexpectedPredecessorResolution for RadrootsNip09SuppressionDecision {{}}\n"
+ ),
+ )
+ .expect("add unexpected predecessor impl authority");
+ let error = validate_predecessor_impl_resolution_authority(
+ workspace.path(),
+ &manifest,
+ &RAW_SOURCE_REBUILD_PREDECESSOR_SUPERSEDED_PATHS,
+ )
+ .expect_err("new predecessor-bound impl authority must fail closed");
+ assert!(error.contains("unexpected ["), "{error}");
+ assert!(error.contains("UnexpectedPredecessorResolution"), "{error}");
+
+ let changed = unchanged.replacen("self.address_reference.as_ref()", "None", 1);
+ assert_ne!(changed, unchanged, "expected impl fixture must mutate");
+ fs::write(&unchanged_path, changed).expect("change expected predecessor impl authority");
+ let error = validate_predecessor_impl_resolution_authority(
+ workspace.path(),
+ &manifest,
+ &RAW_SOURCE_REBUILD_PREDECESSOR_SUPERSEDED_PATHS,
+ )
+ .expect_err("changed predecessor-bound impl authority must fail closed");
+ assert!(error.contains("missing ["), "{error}");
+ assert!(error.contains("unexpected ["), "{error}");
+ assert!(error.matches("address_reference").count() >= 2, "{error}");
+ fs::write(&unchanged_path, unchanged).expect("restore unchanged predecessor source");
+
+ let successor_relative =
+ "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs";
+ let successor_path = workspace.path().join(successor_relative);
+ let successor = fs::read_to_string(&successor_path).expect("successor-only source");
+ fs::write(
+ successor_path,
+ format!(
+ "{successor}\ntrait SuccessorOnlyResolution {{}}\nimpl SuccessorOnlyResolution for RadrootsNip09SuppressionDecision {{}}\n"
+ ),
+ )
+ .expect("change successor-only impl authority");
+ validate_predecessor_impl_resolution_authority(
+ workspace.path(),
+ &manifest,
+ &RAW_SOURCE_REBUILD_PREDECESSOR_SUPERSEDED_PATHS,
+ )
+ .expect("successor-only authority must not rotate predecessor projection");
+ }
+
+ #[test]
fn nip09_v1_manifest_is_independent_of_post_core_transport_evolution() {
let workspace = synthetic_workspace();
let before = immutable_manifest();
diff --git a/tools/xtask/src/contract/raw_source_rebuild.rs b/tools/xtask/src/contract/raw_source_rebuild.rs
@@ -0,0 +1,7433 @@
+use super::artifact_bundle::{
+ GeneratedArtifact, read_regular_file, with_artifact_bundle_transaction,
+};
+use super::food_availability_projection::validate_food_availability_projection_predecessor_production_sources_under_lock;
+use super::nip09_reconciliation::{
+ governed_regular_file_inventory, validate_current_event_store_successor_authority,
+ validate_raw_source_rebuild_successor_compiler_inputs,
+};
+use super::source_maintenance::validate_source_maintenance_manifest_under_lock;
+use quote::ToTokens;
+use serde::{Deserialize, Serialize};
+use serde_json::{Value, json};
+use sha2::{Digest, Sha256};
+use std::collections::{BTreeMap, BTreeSet};
+use std::fs;
+use std::path::Path;
+use syn::{Item, UseTree};
+
+const SCHEMA_VERSION: u32 = 1;
+const CONTRACT_ID: &str = "radroots_event_store.raw_source_rebuild_v1";
+const AUTHORITY_ID: &str = "raw_source_rebuild_v1";
+const PREDECESSOR_CONTRACT_ID: &str = "radroots_event_store.source_maintenance_v1";
+const PREDECESSOR_MANIFEST_RELATIVE: &str =
+ "crates/event_store/contracts/source_maintenance_v1.manifest.json";
+const PREDECESSOR_MANIFEST_BYTE_LENGTH: usize = 14_216;
+const PREDECESSOR_MANIFEST_SHA256: &str =
+ "e8911e6e5710278969cbd15557a5b856b1575dfd11a655711403598370b41221";
+const EVENT_STORE_SCHEMA_VERSION: u32 = 4;
+const EVENT_CONTRACT_REGISTRY_VERSION: u32 = 7;
+const PROJECTION_CURSOR_COUNT_LIMIT: u32 = 4_096;
+const PROJECTION_CURSOR_REJECTION_PROBE_LIMIT: u32 = PROJECTION_CURSOR_COUNT_LIMIT + 1;
+const CALLER_MAIN_TABLE_COUNT_LIMIT: u32 = 4_096;
+const CALLER_FOREIGN_KEY_ROW_COUNT_LIMIT: u32 = 4_096;
+const CALLER_INBOUND_FOREIGN_KEY_POLICY: &str =
+ "reject_all_rebuild_mutated_parent_dependencies_before_entropy_v1";
+const CALLER_SCHEMA_PREFLIGHT_AST_SHA256: &str =
+ "81396b4c375ea40c7f928ec5e4599de5b0d64aab51b7e08e84b79ab9dca6ab64";
+const COLD_REPAIR_MODE: &str = "canonical_file_only_single_connection_lock_domain_probe_v1";
+const TRANSACTION_MODE: &str = "begin_immediate_v1";
+const DIGEST_ALGORITHM: &str = "sha256_domain_nul_typed_fields_v1";
+const DIGEST_DOMAIN_TERMINATOR: &str = "nul_byte";
+const RAW_DIGEST_DOMAIN_UTF8: &str = "radroots:event-store:immutable-raw-digest:v1";
+const PRODUCT_DIGEST_DOMAIN_UTF8: &str = "radroots:event-store:active-product-state-digest:v1";
+const VISIBILITY_ORACLE: &str = "pure_verified_raw_snapshot_direct_indexed_evidence_v1";
+const VISIBILITY_ORACLE_EXPECTED_VISIBILITY_AST_SHA256: &str =
+ "88155fb497668bc65d1adb107c8692483c44f8be1be7dea4663772aa6df23897";
+const VISIBILITY_ORACLE_DECISION_AST_SHA256: &str =
+ "be2034bc552829af7cb8f8f77ce7c0b97e2e23b94551994f6463877ef87c9404";
+const RECONCILIATION_REQUEST_INDEX_INSERT_AST_SHA256: &str =
+ "81d1e02d42dda1b34fd6ab30873765072d7030abf0bb42f7dc117b88eb206933";
+const RECONCILIATION_REQUEST_INDEX_DECISION_AST_SHA256: &str =
+ "2920383a13dc1f7f701039147cb3e5595797a5fe68217a7de6d26cb27715add1";
+const RECONCILIATION_AFFECTED_COORDINATES_AST_SHA256: &str =
+ "8b1aca89e5a20be8f5eb44e08e205e693ba6f2ea0cf4e3a5bd25910f5f4e25cf";
+const EVENT_STORE_SUCCESSOR_COMPILER_TABLES_SHA256: &str =
+ "10e6177bb51094e1775994e1cb3c7c72d01d71890ce45df6c3129ff3d7ee301c";
+const SCOPED_INTEGRITY_MODE: &str = "event_store_owned_tables_and_indices_v1";
+const SQLITE_SEQUENCE_SCOPE: &str = "target_first_after_single_shared_sequence_scan_v1";
+const HASH_ALGORITHM: &str = "sha256_bytes_v1";
+const WRITE_COMMAND: &str = "cargo xtask contract raw-source-rebuild-manifest --write";
+
+const MANIFEST_RELATIVE: &str = "crates/event_store/contracts/raw_source_rebuild_v1.manifest.json";
+const MANIFEST_SCHEMA_RELATIVE: &str =
+ "crates/event_store/contracts/raw_source_rebuild_v1.manifest.schema.json";
+const MANIFEST_SHA256_RELATIVE: &str =
+ "crates/event_store/contracts/raw_source_rebuild_v1.manifest.sha256";
+const GENERATED_DESCRIPTOR_RELATIVE: &str =
+ "crates/event_store/src/generated/raw_source_rebuild_manifest.rs";
+const RESULT_VECTOR_CANONICAL_RELATIVE: &str =
+ "contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json";
+const RESULT_VECTOR_MIRROR_RELATIVE: &str =
+ "crates/event_store/tests/fixtures/raw_source_rebuild.v1.json";
+const RESULT_VECTOR_EXECUTOR_RELATIVE: &str =
+ "crates/event_store/tests/raw_source_rebuild_v1_result_vector.rs";
+const RESULT_VECTOR_EXECUTOR_ID: &str =
+ "radroots_event_store.raw_source_rebuild_v1.result_vector_executor.v1";
+const RESULT_VECTOR_EXECUTOR_TEST: &str = "raw_source_rebuild_v1_result_vector";
+const REBUILD_RUNTIME_SOURCE_RELATIVE: &str =
+ "crates/event_store/src/nip09/reconciliation_v1/raw_source_rebuild.rs";
+const REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE: &str =
+ "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs";
+const REBUILD_FAILPOINT_TEST: &str = "raw_source_rebuild_failpoints_roll_back_every_stage_v1";
+const RESULT_VECTOR_DELEGATED_SUITE_ID: &str =
+ "radroots_event_store.raw_source_rebuild_v1.delegated_rust_test_suite.v1";
+const RESULT_VECTOR_DELEGATED_SUITE_LANE: &str = "nix run .#contract";
+const RESULT_VECTOR_DELEGATED_SUITE_PACKAGE: &str = "radroots_event_store";
+const RESULT_VECTOR_BYTE_LENGTH: usize = 26_833;
+const RESULT_VECTOR_SHA256: &str =
+ "c37a2bf3714f53ab04fae8c5c9dbe2ad4b3f5310efa51f46bd8b116660f1fe15";
+const RESULT_VECTOR_DIRECT_CASE_IDS: &[&str] = &[
+ "empty_source_repeat_digest_parity",
+ "signed_food_fixture_typed_digest_parity",
+];
+const WORKSPACE_MANIFEST_RELATIVE: &str = "Cargo.toml";
+const FLAKE_SOURCE_RELATIVE: &str = "flake.nix";
+const FLAKE_LOCK_RELATIVE: &str = "flake.lock";
+const CONTRACT_APP_SOURCE_RELATIVE: &str = "build/nix/apps.nix";
+const CONTRACT_LANE_SOURCE_RELATIVE: &str = "build/nix/common.nix";
+const TOOLCHAIN_ROUTING_SOURCE_RELATIVE: &str = "build/nix/toolchains.nix";
+const RUST_TOOLCHAIN_RELATIVE: &str = "rust-toolchain.toml";
+const XTASK_MANIFEST_RELATIVE: &str = "tools/xtask/Cargo.toml";
+const XTASK_REQUIRED_DISABLED_AUTO_TARGET_FLAGS: &[&str] =
+ &["autolib", "autotests", "autoexamples", "autobenches"];
+const XTASK_FORBIDDEN_AUTO_TARGET_PATHS: &[&str] = &[
+ "tools/xtask/build.rs",
+ "tools/xtask/src/lib.rs",
+ "tools/xtask/src/bin.rs",
+ "tools/xtask/src/bin",
+ "tools/xtask/tests",
+ "tools/xtask/examples",
+ "tools/xtask/benches",
+];
+const CONTRACT_COMMAND_SOURCE_RELATIVE: &str = "tools/xtask/src/contract.rs";
+const XTASK_MAIN_SOURCE_RELATIVE: &str = "tools/xtask/src/main.rs";
+const RELEASE_RECORD_RELATIVE: &str = "contracts/releases/1.0.0-alpha.1.toml";
+const CHANGELOG_RELATIVE: &str = "CHANGELOG.md";
+const RELEASE_CHANGE_ID: &str = "event-store-raw-source-rebuild-authority";
+const RELEASE_CHANGE_SUMMARY: &str = "Add an authenticated managed-v4 raw-source rebuild and file-only cold-repair authority with stable typed drift categories, serialized generation rotation, independent immutable-raw visibility audit, typed generation-normalized product-state digests, bounded generic projection cursors, a bounded caller-schema dependency preflight over every directly or indirectly mutated parent including the full Food FTS5 table family and sqlite_sequence, target-first transition sequence normalization, separately scoped integrity checks, exact rollback failpoints, a canonical-path SQLite lock-domain probe, deterministic crate-owned connection policy, and an executable successor contract while freezing the SourceMaintenance predecessor and migration inventory.";
+const RELEASE_CHANGE_IMPACTS: &[&str] = &[
+ "add_exported_type",
+ "add_exported_function",
+ "add_exported_constant",
+ "add_exported_field",
+ "add_enum_variant",
+ "add_conformance_vector",
+ "change_exported_enum_variant",
+ "change_exported_algorithm_behavior",
+];
+const CHANGELOG_RELEASE_MARKER: &str =
+ "<!-- release-change: event-store-raw-source-rebuild-authority -->";
+
+const MIGRATION_RELATIVES: &[&str] = &[
+ "crates/event_store/migrations/0001_event_store.down.sql",
+ "crates/event_store/migrations/0001_event_store.up.sql",
+ "crates/event_store/migrations/0002_nip09.down.sql",
+ "crates/event_store/migrations/0002_nip09.up.sql",
+ "crates/event_store/migrations/0003_food_availability_projection.down.sql",
+ "crates/event_store/migrations/0003_food_availability_projection.up.sql",
+ "crates/event_store/migrations/0004_source_maintenance.down.sql",
+ "crates/event_store/migrations/0004_source_maintenance.up.sql",
+];
+
+const REBUILD_STAGES: &[&str] = &[
+ "after_marker_open",
+ "after_generation_rotation",
+ "after_core_replay",
+ "after_visibility_audit",
+ "after_food_reset_replay",
+ "after_food_audit",
+ "after_marker_close",
+];
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+struct RebuildFailpointSpec {
+ id: &'static str,
+ variant: &'static str,
+ rollback_case_id: &'static str,
+}
+
+const REBUILD_FAILPOINTS: &[RebuildFailpointSpec] = &[
+ RebuildFailpointSpec {
+ id: "after_marker_open",
+ variant: "AfterMarkerOpen",
+ rollback_case_id: "rollback_after_marker_open",
+ },
+ RebuildFailpointSpec {
+ id: "after_generation_rotation",
+ variant: "AfterGenerationRotation",
+ rollback_case_id: "rollback_after_generation_rotation",
+ },
+ RebuildFailpointSpec {
+ id: "after_core_replay",
+ variant: "AfterCoreReplay",
+ rollback_case_id: "rollback_after_core_replay",
+ },
+ RebuildFailpointSpec {
+ id: "after_visibility_audit",
+ variant: "AfterVisibilityAudit",
+ rollback_case_id: "rollback_after_visibility_audit",
+ },
+ RebuildFailpointSpec {
+ id: "after_food_reset_replay",
+ variant: "AfterFoodResetAndReplay",
+ rollback_case_id: "rollback_after_food_reset_replay",
+ },
+ RebuildFailpointSpec {
+ id: "after_food_audit",
+ variant: "AfterFoodAudit",
+ rollback_case_id: "rollback_after_food_audit",
+ },
+ RebuildFailpointSpec {
+ id: "after_marker_close",
+ variant: "AfterMarkerClose",
+ rollback_case_id: "rollback_after_marker_close",
+ },
+];
+
+const PRESERVED_AUTHORITIES: &[&str] = &[
+ "legacy_listing",
+ "trade",
+ "transport_observation",
+ "generic_projection_cursor",
+ "unrelated_caller_state_without_dependencies_on_rebuild_owned_tables",
+];
+
+const PRODUCT_DIGEST_COMPONENTS: &[&str] = &[
+ "logical_current_classifications",
+ "raw_heads",
+ "active_addressable_head_state",
+ "active_nip09_facts",
+ "current_visibility",
+ "food_availability_rows",
+ "food_availability_images",
+ "logical_food_fts_rows",
+ "stable_food_cursor_metadata",
+];
+
+const PRODUCT_DIGEST_EXCLUSIONS: &[&str] = &[
+ "source_generation",
+ "absolute_transition_sequence",
+ "transition_history",
+ "rebuild_origin",
+ "rebuild_cause",
+ "operational_timestamps",
+ "generic_projection_cursors",
+ "caller_owned_state",
+];
+
+const SCOPED_INTEGRITY_TABLES: &[&str] = &[
+ "event_envelopes",
+ "event_envelope_tags",
+ "event_envelope_head",
+ "radroots_event_store_source_generation",
+ "radroots_event_store_source_rebuild_commit_barrier",
+ "radroots_event_store_source_rebuild_marker",
+ "radroots_event_store_source_state",
+ "radroots_event_store_write_lock",
+ "radroots_event_store_source_capacity_v1",
+ "radroots_event_store_event_coordinate",
+ "radroots_event_store_nip09_request",
+ "radroots_event_store_nip09_event_target",
+ "radroots_event_store_nip09_address_target",
+ "radroots_event_store_addressable_head_state",
+ "radroots_event_store_addressable_head_transition",
+ "radroots_event_store_addressable_feed_integrity_v1",
+ "radroots_event_store_food_availability_cursor",
+ "radroots_event_store_food_availability_projection",
+ "radroots_event_store_food_availability_image",
+];
+
+const CALLER_INBOUND_FOREIGN_KEY_PARENT_TABLES: &[&str] = &[
+ "event_envelopes",
+ "event_envelope_tags",
+ "event_envelope_head",
+ "radroots_event_store_source_generation",
+ "radroots_event_store_source_rebuild_commit_barrier",
+ "radroots_event_store_source_rebuild_marker",
+ "radroots_event_store_source_state",
+ "radroots_event_store_write_lock",
+ "radroots_event_store_source_capacity_v1",
+ "radroots_event_store_event_coordinate",
+ "radroots_event_store_nip09_request",
+ "radroots_event_store_nip09_event_target",
+ "radroots_event_store_nip09_address_target",
+ "radroots_event_store_addressable_head_state",
+ "radroots_event_store_addressable_head_transition",
+ "radroots_event_store_addressable_feed_integrity_v1",
+ "radroots_event_store_food_availability_cursor",
+ "radroots_event_store_food_availability_projection",
+ "radroots_event_store_food_availability_image",
+ "radroots_event_store_food_availability_search_fts",
+ "radroots_event_store_food_availability_search_fts_config",
+ "radroots_event_store_food_availability_search_fts_content",
+ "radroots_event_store_food_availability_search_fts_data",
+ "radroots_event_store_food_availability_search_fts_docsize",
+ "radroots_event_store_food_availability_search_fts_idx",
+ "sqlite_sequence",
+];
+
+const RAW_DIGEST_QUERY_SPECS: &[DigestQuerySpec] = &[
+ DigestQuerySpec {
+ section: "event_envelopes",
+ sql: "SELECT seq, event_id, pubkey, created_at, kind, tags_json, content, sig, raw_json, inserted_at_ms FROM event_envelopes ORDER BY seq",
+ fields: &[
+ "seq",
+ "event_id",
+ "pubkey",
+ "created_at",
+ "kind",
+ "tags_json",
+ "content",
+ "sig",
+ "raw_json",
+ "inserted_at_ms",
+ ],
+ },
+ DigestQuerySpec {
+ section: "event_envelope_tags",
+ sql: "SELECT event.seq, tag.event_id, tag.tag_index, tag.tag_name, tag.tag_value, tag.tag_json FROM event_envelope_tags AS tag JOIN event_envelopes AS event ON event.event_id = tag.event_id ORDER BY event.seq, tag.tag_index",
+ fields: &[
+ "seq",
+ "event_id",
+ "tag_index",
+ "tag_name",
+ "tag_value",
+ "tag_json",
+ ],
+ },
+];
+
+const PRODUCT_DIGEST_QUERY_SPECS: &[DigestQuerySpec] = &[
+ DigestQuerySpec {
+ section: "envelope_classification",
+ sql: "SELECT event_id, verification_status, contract_status, contract_id, event_class, projection_eligible FROM event_envelopes ORDER BY event_id",
+ fields: &[
+ "event_id",
+ "verification_status",
+ "contract_status",
+ "contract_id",
+ "event_class",
+ "projection_eligible",
+ ],
+ },
+ DigestQuerySpec {
+ section: "tag_classification",
+ sql: "SELECT event_id, tag_index, contract_semantic, contract_value_type, relay_indexed FROM event_envelope_tags ORDER BY event_id, tag_index",
+ fields: &[
+ "event_id",
+ "tag_index",
+ "contract_semantic",
+ "contract_value_type",
+ "relay_indexed",
+ ],
+ },
+ DigestQuerySpec {
+ section: "raw_heads",
+ sql: "SELECT coordinate_type, kind, pubkey, d_tag, event_id, created_at FROM event_envelope_head ORDER BY coordinate_type, kind, pubkey, d_tag",
+ fields: &[
+ "coordinate_type",
+ "kind",
+ "pubkey",
+ "d_tag",
+ "event_id",
+ "created_at",
+ ],
+ },
+ DigestQuerySpec {
+ section: "event_coordinates",
+ sql: "SELECT event_id, coordinate_type, kind, pubkey, created_at, admission_status, admission_code, contract_id, raw_d_tag, nip09_matchable, nip09_d_tag FROM radroots_event_store_event_coordinate WHERE source_generation = ? ORDER BY event_id",
+ fields: &[
+ "event_id",
+ "coordinate_type",
+ "kind",
+ "pubkey",
+ "created_at",
+ "admission_status",
+ "admission_code",
+ "contract_id",
+ "raw_d_tag",
+ "nip09_matchable",
+ "nip09_d_tag",
+ ],
+ },
+ DigestQuerySpec {
+ section: "nip09_requests",
+ sql: "SELECT request_event_id, request_pubkey, request_created_at FROM radroots_event_store_nip09_request WHERE source_generation = ? ORDER BY request_event_id",
+ fields: &["request_event_id", "request_pubkey", "request_created_at"],
+ },
+ DigestQuerySpec {
+ section: "nip09_event_targets",
+ sql: "SELECT request_event_id, target_event_id, source_tag_index, source_tag_value FROM radroots_event_store_nip09_event_target WHERE source_generation = ? ORDER BY request_event_id, target_event_id, source_tag_index",
+ fields: &[
+ "request_event_id",
+ "target_event_id",
+ "source_tag_index",
+ "source_tag_value",
+ ],
+ },
+ DigestQuerySpec {
+ section: "nip09_address_targets",
+ sql: "SELECT request_event_id, target_kind, target_pubkey, target_d_tag, inclusive_cutoff, source_tag_index, source_tag_value, source_kind_text, source_pubkey_text, source_d_tag FROM radroots_event_store_nip09_address_target WHERE source_generation = ? ORDER BY request_event_id, target_kind, target_pubkey, target_d_tag, source_tag_index",
+ fields: &[
+ "request_event_id",
+ "target_kind",
+ "target_pubkey",
+ "target_d_tag",
+ "inclusive_cutoff",
+ "source_tag_index",
+ "source_tag_value",
+ "source_kind_text",
+ "source_pubkey_text",
+ "source_d_tag",
+ ],
+ },
+ DigestQuerySpec {
+ section: "addressable_heads",
+ sql: "SELECT kind, pubkey, d_tag, raw_head_event_id, raw_head_created_at, admission_status, admission_code, contract_id, visibility, nip09_outcome, nip09_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff FROM radroots_event_store_addressable_head_state WHERE source_generation = ? ORDER BY kind, pubkey, d_tag",
+ fields: &[
+ "kind",
+ "pubkey",
+ "d_tag",
+ "raw_head_event_id",
+ "raw_head_created_at",
+ "admission_status",
+ "admission_code",
+ "contract_id",
+ "visibility",
+ "nip09_outcome",
+ "nip09_reason",
+ "event_reference_request_id",
+ "address_reference_request_id",
+ "address_reference_cutoff",
+ ],
+ },
+ DigestQuerySpec {
+ section: "current_visibility",
+ sql: "SELECT event_id, admission_status, contract_id, event_class, raw_d_tag, is_raw_head, raw_head_event_id, suppression_outcome, suppression_reason, event_reference_request_id, address_reference_request_id, address_reference_cutoff, current_visibility FROM radroots_event_store_current_visibility_v1 WHERE source_generation = ? ORDER BY event_id",
+ fields: &[
+ "event_id",
+ "admission_status",
+ "contract_id",
+ "event_class",
+ "raw_d_tag",
+ "is_raw_head",
+ "raw_head_event_id",
+ "suppression_outcome",
+ "suppression_reason",
+ "event_reference_request_id",
+ "address_reference_request_id",
+ "address_reference_cutoff",
+ "current_visibility",
+ ],
+ },
+ DigestQuerySpec {
+ section: "food_projection",
+ sql: "SELECT kind, pubkey, d_tag, event_id, created_at, contract_id, content, title, summary, published_at, location, price_amount, price_currency, price_unit, quantity_amount, quantity_unit, status, diagnostic_codes_json FROM radroots_event_store_food_availability_projection WHERE source_generation = ? ORDER BY pubkey, d_tag",
+ fields: &[
+ "kind",
+ "pubkey",
+ "d_tag",
+ "event_id",
+ "created_at",
+ "contract_id",
+ "content",
+ "title",
+ "summary",
+ "published_at",
+ "location",
+ "price_amount",
+ "price_currency",
+ "price_unit",
+ "quantity_amount",
+ "quantity_unit",
+ "status",
+ "diagnostic_codes_json",
+ ],
+ },
+ DigestQuerySpec {
+ section: "food_images",
+ sql: "SELECT pubkey, d_tag, image_index, raw_tag_json, url, width, height, blossom_sha256, qualifies, diagnostic_codes_json FROM radroots_event_store_food_availability_image WHERE source_generation = ? ORDER BY pubkey, d_tag, image_index",
+ fields: &[
+ "pubkey",
+ "d_tag",
+ "image_index",
+ "raw_tag_json",
+ "url",
+ "width",
+ "height",
+ "blossom_sha256",
+ "qualifies",
+ "diagnostic_codes_json",
+ ],
+ },
+ DigestQuerySpec {
+ section: "food_search",
+ sql: "SELECT event_id, pubkey, d_tag, title, summary, content, location FROM radroots_event_store_food_availability_search_fts ORDER BY event_id",
+ fields: &[
+ "event_id", "pubkey", "d_tag", "title", "summary", "content", "location",
+ ],
+ },
+ DigestQuerySpec {
+ section: "food_cursor",
+ sql: "SELECT feed_version, projection_version, scope_fingerprint, hook_manifest_sha256, projected_row_count FROM radroots_event_store_food_availability_cursor WHERE singleton = 1",
+ fields: &[
+ "feed_version",
+ "projection_version",
+ "scope_fingerprint",
+ "hook_manifest_sha256",
+ "projected_row_count",
+ ],
+ },
+];
+
+const ADDED_PUBLIC_SYMBOLS: &[&str] = &[
+ "RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1",
+ "RadrootsEventStoreCallerInboundForeignKeyV1",
+ "RadrootsEventStoreActiveProductStateDigestV1",
+ "RadrootsEventStoreImmutableRawDigestV1",
+ "RadrootsEventStoreRawSourceRebuildDriftV1",
+ "RadrootsEventStoreRawSourceRebuildReportV1",
+];
+
+const PUBLIC_METHODS: &[&str] = &[
+ "RadrootsEventStore::rebuild_from_raw_v1",
+ "RadrootsEventStore::repair_file_from_raw_v1",
+ "RadrootsEventStoreRawSourceRebuildReportV1::prior_source_generation",
+ "RadrootsEventStoreRawSourceRebuildReportV1::new_source_generation",
+ "RadrootsEventStoreRawSourceRebuildReportV1::source_capacity",
+ "RadrootsEventStoreRawSourceRebuildReportV1::raw_high_water_seq",
+ "RadrootsEventStoreRawSourceRebuildReportV1::immutable_raw_digest",
+ "RadrootsEventStoreRawSourceRebuildReportV1::active_product_state_digest",
+ "RadrootsEventStoreImmutableRawDigestV1::as_bytes",
+ "RadrootsEventStoreActiveProductStateDigestV1::as_bytes",
+ "RadrootsEventStoreRawSourceRebuildDriftV1::code",
+];
+
+const RAW_SOURCE_REBUILD_DRIFT_KINDS: &[(&str, &str)] = &[
+ ("ManagedSchemaAuthority", "managed_schema_authority"),
+ ("ImmutableRawAuthority", "immutable_raw_authority"),
+ ("SourceGenerationLineage", "source_generation_lineage"),
+ (
+ "AddressableTransitionAuthority",
+ "addressable_transition_authority",
+ ),
+ (
+ "DerivedProductStateAuthority",
+ "derived_product_state_authority",
+ ),
+ ("RebuildPostcondition", "rebuild_postcondition"),
+];
+
+const ERROR_VARIANTS: &[&str] = &[
+ "ProjectionCursorCapacityExceeded",
+ "RawSourceRepairDatabaseIdentityMismatch",
+ "RawSourceRepairCanonicalPathLockDomainMismatch",
+ "RawSourceRepairMainDatabaseCanonicalizationFailed",
+ "RawSourceRebuildCallerForeignKeyCapacityExceeded",
+ "RawSourceRebuildCallerInboundForeignKeyUnsupported",
+ "RawSourceRebuildCallerTableCapacityExceeded",
+ "RawSourceRebuildStateDrift",
+ "RawSourceRebuildTransactionRollbackFailed",
+];
+
+const ENTRY_POINTS: &[(&str, &str)] = &[
+ (
+ "live_rebuild",
+ "radroots_event_store::RadrootsEventStore::rebuild_from_raw_v1",
+ ),
+ (
+ "cold_file_repair",
+ "radroots_event_store::RadrootsEventStore::repair_file_from_raw_v1",
+ ),
+ (
+ "projection_cursor_insert_preflight",
+ "radroots_event_store::nip09::reconciliation_v1::preflight_projection_cursor_insert_v1",
+ ),
+ (
+ "serialized_rebuild_runtime",
+ "radroots_event_store::nip09::reconciliation_v1::raw_source_rebuild::rebuild_from_raw_v1_on_pool",
+ ),
+ (
+ "independent_visibility_oracle",
+ "radroots_event_store::nip09::reconciliation_v1::visibility_oracle_v1::audit_current_visibility_from_raw_v1",
+ ),
+ ("result_vector_executor", RESULT_VECTOR_EXECUTOR_TEST),
+];
+
+#[derive(Clone, Copy)]
+struct SourceSpec {
+ role: &'static str,
+ path: &'static str,
+}
+
+#[derive(Clone, Copy)]
+struct DigestQuerySpec {
+ section: &'static str,
+ sql: &'static str,
+ fields: &'static [&'static str],
+}
+
+const DELEGATED_COMPILER_SOURCE_PINS: &[(&str, &str)] = &[
+ (
+ FLAKE_SOURCE_RELATIVE,
+ "0251b26040cf5338c12dc777a4deaadb8f63eb4e88bc05929dcec67db88ff2bf",
+ ),
+ (
+ FLAKE_LOCK_RELATIVE,
+ "41b569739bfa0c488625326f4f0a874561601787951cdf7a3f171e60572fa20e",
+ ),
+ (
+ CONTRACT_APP_SOURCE_RELATIVE,
+ "41a185ac87379e24c1ede09c0f1aac820653dffc09f99cd803b145b44bed982c",
+ ),
+ (
+ CONTRACT_LANE_SOURCE_RELATIVE,
+ "b3340e1b4973e6a1e02899d164ca74842757f22b6b1a03f90461532fcd844df5",
+ ),
+ (
+ TOOLCHAIN_ROUTING_SOURCE_RELATIVE,
+ "cd664be945e28bf6c25c7758182ff8d01e03248832dfc2c045c01b4f4aff960f",
+ ),
+ (
+ RUST_TOOLCHAIN_RELATIVE,
+ "c33aa38292bab6513bf79ed2f69c1525b736dd738b15ca78af713b70b29265c9",
+ ),
+ (
+ XTASK_MANIFEST_RELATIVE,
+ "7e858f4f33913f986c565be2a31c41615ea0585c9e19572363ef5cae36cafdc9",
+ ),
+];
+
+const REQUIRED_DELEGATED_COMPILER_SOURCES: &[(&str, &str)] = &[
+ ("workspace_manifest_authority", WORKSPACE_MANIFEST_RELATIVE),
+ ("workspace_lockfile_authority", "Cargo.lock"),
+ ("nix_flake_app_export_authority", FLAKE_SOURCE_RELATIVE),
+ ("nix_input_lock_authority", FLAKE_LOCK_RELATIVE),
+ (
+ "nix_contract_app_routing_authority",
+ CONTRACT_APP_SOURCE_RELATIVE,
+ ),
+ (
+ "nix_contract_test_lane_authority",
+ CONTRACT_LANE_SOURCE_RELATIVE,
+ ),
+ (
+ "nix_toolchain_routing_authority",
+ TOOLCHAIN_ROUTING_SOURCE_RELATIVE,
+ ),
+ ("rust_toolchain_authority", RUST_TOOLCHAIN_RELATIVE),
+ ("xtask_manifest_authority", XTASK_MANIFEST_RELATIVE),
+];
+
+const SOURCE_SPECS: &[SourceSpec] = &[
+ SourceSpec {
+ role: "workspace_manifest_authority",
+ path: WORKSPACE_MANIFEST_RELATIVE,
+ },
+ SourceSpec {
+ role: "workspace_lockfile_authority",
+ path: "Cargo.lock",
+ },
+ SourceSpec {
+ role: "nix_flake_app_export_authority",
+ path: FLAKE_SOURCE_RELATIVE,
+ },
+ SourceSpec {
+ role: "nix_input_lock_authority",
+ path: FLAKE_LOCK_RELATIVE,
+ },
+ SourceSpec {
+ role: "nix_contract_app_routing_authority",
+ path: CONTRACT_APP_SOURCE_RELATIVE,
+ },
+ SourceSpec {
+ role: "nix_contract_test_lane_authority",
+ path: CONTRACT_LANE_SOURCE_RELATIVE,
+ },
+ SourceSpec {
+ role: "nix_toolchain_routing_authority",
+ path: TOOLCHAIN_ROUTING_SOURCE_RELATIVE,
+ },
+ SourceSpec {
+ role: "rust_toolchain_authority",
+ path: RUST_TOOLCHAIN_RELATIVE,
+ },
+ SourceSpec {
+ role: "xtask_manifest_authority",
+ path: XTASK_MANIFEST_RELATIVE,
+ },
+ SourceSpec {
+ role: "event_store_dependency_feature_authority",
+ path: "crates/event_store/Cargo.toml",
+ },
+ SourceSpec {
+ role: "event_store_error_surface",
+ path: "crates/event_store/src/error.rs",
+ },
+ SourceSpec {
+ role: "generated_descriptor_registration",
+ path: "crates/event_store/src/generated.rs",
+ },
+ SourceSpec {
+ role: "food_generated_descriptor_input",
+ path: "crates/event_store/src/generated/food_availability_projection_manifest.rs",
+ },
+ SourceSpec {
+ role: "nip09_generated_descriptor_input",
+ path: "crates/event_store/src/generated/nip09_reconciliation_manifest.rs",
+ },
+ SourceSpec {
+ role: "source_maintenance_generated_descriptor_input",
+ path: "crates/event_store/src/generated/source_maintenance_manifest.rs",
+ },
+ SourceSpec {
+ role: "public_surface",
+ path: "crates/event_store/src/lib.rs",
+ },
+ SourceSpec {
+ role: "migration_runtime_registry",
+ path: "crates/event_store/src/migrations.rs",
+ },
+ SourceSpec {
+ role: "model_registration",
+ path: "crates/event_store/src/model.rs",
+ },
+ SourceSpec {
+ role: "addressable_transition_feed_model",
+ path: "crates/event_store/src/model/addressable_transition_feed_v1.rs",
+ },
+ SourceSpec {
+ role: "current_visibility_model",
+ path: "crates/event_store/src/model/current_visibility_v1.rs",
+ },
+ SourceSpec {
+ role: "food_availability_projection_model",
+ path: "crates/event_store/src/model/food_availability_projection_v1.rs",
+ },
+ SourceSpec {
+ role: "ingest_reconciliation_model",
+ path: "crates/event_store/src/model/ingest_reconciliation_v1.rs",
+ },
+ SourceSpec {
+ role: "rebuild_report_and_digest_models",
+ path: "crates/event_store/src/model/raw_source_rebuild_v1.rs",
+ },
+ SourceSpec {
+ role: "reconciliation_model",
+ path: "crates/event_store/src/model/reconciliation_v1.rs",
+ },
+ SourceSpec {
+ role: "nip09_module_registration",
+ path: "crates/event_store/src/nip09.rs",
+ },
+ SourceSpec {
+ role: "reconciliation_runtime_registration",
+ path: "crates/event_store/src/nip09/reconciliation_v1.rs",
+ },
+ SourceSpec {
+ role: "serialized_raw_source_rebuild",
+ path: REBUILD_RUNTIME_SOURCE_RELATIVE,
+ },
+ SourceSpec {
+ role: "nip09_result_vector_executor_input",
+ path: "crates/event_store/src/nip09/reconciliation_v1/result_vector_executor.rs",
+ },
+ SourceSpec {
+ role: "independent_raw_visibility_oracle",
+ path: "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs",
+ },
+ SourceSpec {
+ role: "managed_v4_validation_and_scoped_integrity",
+ path: "crates/event_store/src/schema.rs",
+ },
+ SourceSpec {
+ role: "source_capacity_rebuild_authority",
+ path: "crates/event_store/src/source_maintenance_v1.rs",
+ },
+ SourceSpec {
+ role: "public_rebuild_and_cold_repair_boundary",
+ path: "crates/event_store/src/store.rs",
+ },
+ SourceSpec {
+ role: "addressable_transition_feed_storage",
+ path: "crates/event_store/src/store/addressable_transition_feed_v1.rs",
+ },
+ SourceSpec {
+ role: "current_visibility_storage",
+ path: "crates/event_store/src/store/current_visibility_v1.rs",
+ },
+ SourceSpec {
+ role: "raw_source_rebuild_focused_tests",
+ path: "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs",
+ },
+ SourceSpec {
+ role: "signed_food_digest_fixture",
+ path: "crates/event_store/tests/fixtures/food_availability_projection.v1.json",
+ },
+ SourceSpec {
+ role: "food_projection_reset_and_replay",
+ path: "crates/event_store/src/store/food_availability_projection_v1.rs",
+ },
+ SourceSpec {
+ role: "post_core_extension_capabilities",
+ path: "crates/event_store/src/store/post_core_extension_capabilities.rs",
+ },
+ SourceSpec {
+ role: "post_core_extension_dispatcher",
+ path: "crates/event_store/src/store/post_core_extension_dispatcher.rs",
+ },
+ SourceSpec {
+ role: "post_core_extensions_v1",
+ path: "crates/event_store/src/store/post_core_extensions_v1.rs",
+ },
+ SourceSpec {
+ role: "post_core_extensions_v2",
+ path: "crates/event_store/src/store/post_core_extensions_v2.rs",
+ },
+ SourceSpec {
+ role: "post_core_storage_v1",
+ path: "crates/event_store/src/store/post_core_storage_v1.rs",
+ },
+ SourceSpec {
+ role: "post_core_storage_v2",
+ path: "crates/event_store/src/store/post_core_storage_v2.rs",
+ },
+ SourceSpec {
+ role: "protocol_reconciliation_storage",
+ path: "crates/event_store/src/store/protocol_reconciliation_v1.rs",
+ },
+ SourceSpec {
+ role: "protocol_storage_boundary",
+ path: "crates/event_store/src/store/protocol_storage_v1.rs",
+ },
+ SourceSpec {
+ role: "event_store_package_readme",
+ path: "crates/event_store/README",
+ },
+ SourceSpec {
+ role: "signed_nip09_reconciliation_fixture",
+ path: "crates/event_store/tests/fixtures/nip09_reconciliation.v1.json",
+ },
+ SourceSpec {
+ role: "transitive_food_predecessor_governance",
+ path: "tools/xtask/src/contract/food_availability_projection.rs",
+ },
+ SourceSpec {
+ role: "immutable_predecessor_governance",
+ path: "tools/xtask/src/contract/source_maintenance.rs",
+ },
+ SourceSpec {
+ role: "transitive_nip09_predecessor_governance",
+ path: "tools/xtask/src/contract/nip09_reconciliation.rs",
+ },
+ SourceSpec {
+ role: "raw_source_rebuild_governance",
+ path: "tools/xtask/src/contract/raw_source_rebuild.rs",
+ },
+ SourceSpec {
+ role: "contract_command_authority",
+ path: CONTRACT_COMMAND_SOURCE_RELATIVE,
+ },
+ SourceSpec {
+ role: "xtask_dispatch_and_release_preflight",
+ path: XTASK_MAIN_SOURCE_RELATIVE,
+ },
+ SourceSpec {
+ role: "release_breaking_change_authority",
+ path: RELEASE_RECORD_RELATIVE,
+ },
+ SourceSpec {
+ role: "release_note_authority",
+ path: CHANGELOG_RELATIVE,
+ },
+];
+
+const EXPECTED_SOURCE_MAINTENANCE_DRIFT_PATHS: &[&str] = &[
+ "crates/event_store/src/error.rs",
+ "crates/event_store/src/generated.rs",
+ "crates/event_store/src/lib.rs",
+ "crates/event_store/src/model.rs",
+ "crates/event_store/src/nip09/reconciliation_v1.rs",
+ "crates/event_store/src/schema.rs",
+ "crates/event_store/src/store.rs",
+ "tools/xtask/src/contract/food_availability_projection.rs",
+ "tools/xtask/src/contract/nip09_reconciliation.rs",
+ "tools/xtask/src/contract/source_maintenance.rs",
+ "tools/xtask/src/contract.rs",
+ "tools/xtask/src/main.rs",
+];
+
+const TRANSITIVE_PREDECESSOR_SUPERSEDED_PATHS: &[&str] = &[
+ "crates/event_store/Cargo.toml",
+ "crates/event_store/src/error.rs",
+ "crates/event_store/src/generated.rs",
+ "crates/event_store/src/lib.rs",
+ "crates/event_store/src/migrations.rs",
+ "crates/event_store/src/model.rs",
+ "crates/event_store/src/nip09/reconciliation_v1.rs",
+ "crates/event_store/src/schema.rs",
+ "crates/event_store/src/store.rs",
+ "crates/event_store/src/store/food_availability_projection_v1.rs",
+ "crates/event_store/src/store/protocol_reconciliation_v1.rs",
+];
+
+const GENERATED_ARTIFACT_PATHS: &[&str] = &[
+ MANIFEST_RELATIVE,
+ MANIFEST_SCHEMA_RELATIVE,
+ MANIFEST_SHA256_RELATIVE,
+ GENERATED_DESCRIPTOR_RELATIVE,
+ RESULT_VECTOR_MIRROR_RELATIVE,
+];
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct RawSourceRebuildManifest {
+ schema_version: u32,
+ contract_id: String,
+ authority_id: String,
+ manifest_schema: FileDescriptor,
+ predecessor: PredecessorDescriptor,
+ migration_inventory: Vec<FileDescriptor>,
+ runtime: RuntimeDescriptor,
+ entry_points: Vec<EntryPointDescriptor>,
+ source_files: Vec<SourceFileDescriptor>,
+ public_api: PublicApiDescriptor,
+ result_vector: ResultVectorDescriptor,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct FileDescriptor {
+ path: String,
+ byte_length: u64,
+ sha256: String,
+ hash_algorithm: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct PredecessorDescriptor {
+ contract_id: String,
+ manifest: FileDescriptor,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct RuntimeDescriptor {
+ event_store_schema_version: u32,
+ event_contract_registry_version: u32,
+ transaction_mode: String,
+ projection_cursor_count_limit: u32,
+ projection_cursor_rejection_probe_limit: u32,
+ caller_main_table_count_limit: u32,
+ caller_foreign_key_row_count_limit: u32,
+ caller_inbound_foreign_key_policy: String,
+ caller_inbound_foreign_key_parent_tables: Vec<String>,
+ cold_repair_mode: String,
+ immutable_raw_digest: DigestDescriptor,
+ active_product_state_digest: ProductDigestDescriptor,
+ visibility_oracle: String,
+ scoped_integrity_mode: String,
+ scoped_integrity_tables: Vec<String>,
+ sqlite_sequence_scope: String,
+ stages: Vec<String>,
+ failpoints: Vec<String>,
+ preserved_authorities: Vec<String>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DigestDescriptor {
+ algorithm: String,
+ domain_utf8: String,
+ domain_terminator: String,
+ framing: DigestFramingDescriptor,
+ output_bytes: u32,
+ source_queries: Vec<DigestQueryDescriptor>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct ProductDigestDescriptor {
+ algorithm: String,
+ domain_utf8: String,
+ domain_terminator: String,
+ framing: DigestFramingDescriptor,
+ output_bytes: u32,
+ components: Vec<String>,
+ exclusions: Vec<String>,
+ component_queries: Vec<DigestQueryDescriptor>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DigestFramingDescriptor {
+ section: String,
+ row: String,
+ signed_i64: String,
+ boolean: String,
+ optional: String,
+ text: String,
+ blob: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DigestQueryDescriptor {
+ section: String,
+ sql: String,
+ fields: Vec<DigestFieldDescriptor>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DigestFieldDescriptor {
+ name: String,
+ framing: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct EntryPointDescriptor {
+ role: String,
+ rust_path: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct SourceFileDescriptor {
+ role: String,
+ path: String,
+ byte_length: u64,
+ sha256: String,
+ hash_algorithm: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct PublicApiDescriptor {
+ added_symbols: Vec<String>,
+ methods: Vec<String>,
+ error_variants: Vec<String>,
+ drift_kinds: Vec<DriftKindDescriptor>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DriftKindDescriptor {
+ variant: String,
+ code: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct ResultVectorDescriptor {
+ canonical_path: String,
+ mirror_path: String,
+ byte_length: u64,
+ sha256: String,
+ hash_algorithm: String,
+ executor_id: String,
+ executor_path: String,
+ executor_test: String,
+ executor_byte_length: u64,
+ executor_sha256: String,
+ executor_hash_algorithm: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct RawSourceRebuildVector {
+ schema_version: u32,
+ contract_id: String,
+ delegated_suite: DelegatedSuite,
+ cases: Vec<VectorCase>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DelegatedSuite {
+ id: String,
+ lane: String,
+ package: String,
+ authorities: Vec<DelegatedAuthority>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DelegatedAuthority {
+ authority: String,
+ authority_path: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct VectorCase {
+ id: String,
+ execution: String,
+ authority: String,
+ authority_path: String,
+ expected_outcome: String,
+ expected_immutable_raw_digest: Option<String>,
+ expected_active_product_state_digest: Option<String>,
+}
+
+pub(crate) fn write_raw_source_rebuild_manifest(workspace_root: &Path) -> Result<(), String> {
+ with_artifact_bundle_transaction(workspace_root, |transaction| {
+ transaction.write(expected_artifacts(workspace_root)?)?;
+ validate_raw_source_rebuild_manifest_under_lock(workspace_root)
+ })
+}
+
+pub(crate) fn validate_raw_source_rebuild_manifest(workspace_root: &Path) -> Result<(), String> {
+ with_artifact_bundle_transaction(workspace_root, |_| {
+ validate_raw_source_rebuild_manifest_under_lock(workspace_root)
+ })
+}
+
+fn validate_raw_source_rebuild_manifest_under_lock(workspace_root: &Path) -> Result<(), String> {
+ validate_source_maintenance_manifest_under_lock(workspace_root)?;
+ for artifact in expected_artifacts(workspace_root)? {
+ let actual = read_regular_file(workspace_root, artifact.relative)?;
+ if actual != artifact.contents {
+ return Err(format!(
+ "generated raw-source rebuild artifact {} is stale; run `{WRITE_COMMAND}`",
+ artifact.relative
+ ));
+ }
+ }
+
+ let manifest_bytes = read_regular_file(workspace_root, MANIFEST_RELATIVE)?;
+ let manifest_value: Value = serde_json::from_slice(&manifest_bytes)
+ .map_err(|error| format!("parse {MANIFEST_RELATIVE}: {error}"))?;
+ let manifest: RawSourceRebuildManifest = serde_json::from_value(manifest_value.clone())
+ .map_err(|error| format!("parse typed {MANIFEST_RELATIVE}: {error}"))?;
+ validate_canonical_json(MANIFEST_RELATIVE, &manifest_bytes, &manifest)?;
+ validate_manifest_shape(&manifest)?;
+
+ let schema_bytes = read_regular_file(workspace_root, MANIFEST_SCHEMA_RELATIVE)?;
+ let schema: Value = serde_json::from_slice(&schema_bytes)
+ .map_err(|error| format!("parse {MANIFEST_SCHEMA_RELATIVE}: {error}"))?;
+ validate_canonical_json(MANIFEST_SCHEMA_RELATIVE, &schema_bytes, &schema)?;
+ validate_json_schema(&schema, &manifest_value)?;
+
+ let sidecar = read_regular_file(workspace_root, MANIFEST_SHA256_RELATIVE)?;
+ validate_digest_sidecar(MANIFEST_SHA256_RELATIVE, &sidecar)?;
+ if sidecar != format!("{}\n", sha256_hex(&manifest_bytes)).as_bytes() {
+ return Err(format!(
+ "{MANIFEST_SHA256_RELATIVE} must match the checked-in manifest bytes"
+ ));
+ }
+
+ let vector_bytes = read_regular_file(workspace_root, RESULT_VECTOR_CANONICAL_RELATIVE)?;
+ validate_result_vector_identity(&vector_bytes)?;
+ let mirror_bytes = read_regular_file(workspace_root, RESULT_VECTOR_MIRROR_RELATIVE)?;
+ if vector_bytes != mirror_bytes {
+ return Err(format!(
+ "{RESULT_VECTOR_MIRROR_RELATIVE} must exactly mirror {RESULT_VECTOR_CANONICAL_RELATIVE}"
+ ));
+ }
+ let vector: RawSourceRebuildVector = serde_json::from_slice(&vector_bytes)
+ .map_err(|error| format!("parse {RESULT_VECTOR_CANONICAL_RELATIVE}: {error}"))?;
+ validate_canonical_json(RESULT_VECTOR_CANONICAL_RELATIVE, &vector_bytes, &vector)?;
+ validate_result_vector(workspace_root, &vector)?;
+ validate_source_contract(workspace_root)
+}
+
+fn expected_artifacts(workspace_root: &Path) -> Result<Vec<GeneratedArtifact>, String> {
+ let schema_bytes = canonical_json_bytes(&manifest_schema())?;
+ let manifest = describe_manifest(workspace_root, &schema_bytes)?;
+ let manifest_bytes = canonical_json_bytes(&manifest)?;
+ let manifest_sha256 = sha256_hex(&manifest_bytes);
+ let descriptor = generated_descriptor(&manifest, &manifest_bytes, &manifest_sha256);
+ let vector_bytes = read_regular_file(workspace_root, RESULT_VECTOR_CANONICAL_RELATIVE)?;
+ Ok(vec![
+ GeneratedArtifact {
+ relative: MANIFEST_RELATIVE,
+ contents: manifest_bytes,
+ },
+ GeneratedArtifact {
+ relative: MANIFEST_SCHEMA_RELATIVE,
+ contents: schema_bytes,
+ },
+ GeneratedArtifact {
+ relative: MANIFEST_SHA256_RELATIVE,
+ contents: format!("{manifest_sha256}\n").into_bytes(),
+ },
+ GeneratedArtifact {
+ relative: GENERATED_DESCRIPTOR_RELATIVE,
+ contents: descriptor.into_bytes(),
+ },
+ GeneratedArtifact {
+ relative: RESULT_VECTOR_MIRROR_RELATIVE,
+ contents: vector_bytes,
+ },
+ ])
+}
+
+fn describe_manifest(
+ workspace_root: &Path,
+ schema_bytes: &[u8],
+) -> Result<RawSourceRebuildManifest, String> {
+ validate_source_maintenance_manifest_under_lock(workspace_root)?;
+ validate_source_contract(workspace_root)?;
+
+ let predecessor_bytes = read_regular_file(workspace_root, PREDECESSOR_MANIFEST_RELATIVE)?;
+ validate_predecessor_identity(&predecessor_bytes)?;
+ validate_predecessor_source_supersession(workspace_root, &predecessor_bytes)?;
+
+ let vector_bytes = read_regular_file(workspace_root, RESULT_VECTOR_CANONICAL_RELATIVE)?;
+ validate_result_vector_identity(&vector_bytes)?;
+ let vector: RawSourceRebuildVector = serde_json::from_slice(&vector_bytes)
+ .map_err(|error| format!("parse {RESULT_VECTOR_CANONICAL_RELATIVE}: {error}"))?;
+ validate_canonical_json(RESULT_VECTOR_CANONICAL_RELATIVE, &vector_bytes, &vector)?;
+ validate_result_vector(workspace_root, &vector)?;
+
+ let source_files = SOURCE_SPECS
+ .iter()
+ .map(|spec| {
+ let bytes = read_regular_file(workspace_root, spec.path)?;
+ Ok(SourceFileDescriptor {
+ role: spec.role.to_owned(),
+ path: spec.path.to_owned(),
+ byte_length: byte_length(spec.path, &bytes)?,
+ sha256: sha256_hex(&bytes),
+ hash_algorithm: HASH_ALGORITHM.to_owned(),
+ })
+ })
+ .collect::<Result<Vec<_>, String>>()?;
+
+ let executor = descriptor_for_file(workspace_root, RESULT_VECTOR_EXECUTOR_RELATIVE)?;
+ Ok(RawSourceRebuildManifest {
+ schema_version: SCHEMA_VERSION,
+ contract_id: CONTRACT_ID.to_owned(),
+ authority_id: AUTHORITY_ID.to_owned(),
+ manifest_schema: descriptor_for_bytes(MANIFEST_SCHEMA_RELATIVE, schema_bytes)?,
+ predecessor: PredecessorDescriptor {
+ contract_id: PREDECESSOR_CONTRACT_ID.to_owned(),
+ manifest: descriptor_for_bytes(PREDECESSOR_MANIFEST_RELATIVE, &predecessor_bytes)?,
+ },
+ migration_inventory: MIGRATION_RELATIVES
+ .iter()
+ .map(|relative| descriptor_for_file(workspace_root, relative))
+ .collect::<Result<Vec<_>, _>>()?,
+ runtime: expected_runtime(),
+ entry_points: ENTRY_POINTS
+ .iter()
+ .map(|(role, rust_path)| EntryPointDescriptor {
+ role: (*role).to_owned(),
+ rust_path: (*rust_path).to_owned(),
+ })
+ .collect(),
+ source_files,
+ public_api: PublicApiDescriptor {
+ added_symbols: owned(ADDED_PUBLIC_SYMBOLS),
+ methods: owned(PUBLIC_METHODS),
+ error_variants: owned(ERROR_VARIANTS),
+ drift_kinds: expected_drift_kinds(),
+ },
+ result_vector: ResultVectorDescriptor {
+ canonical_path: RESULT_VECTOR_CANONICAL_RELATIVE.to_owned(),
+ mirror_path: RESULT_VECTOR_MIRROR_RELATIVE.to_owned(),
+ byte_length: byte_length(RESULT_VECTOR_CANONICAL_RELATIVE, &vector_bytes)?,
+ sha256: sha256_hex(&vector_bytes),
+ hash_algorithm: HASH_ALGORITHM.to_owned(),
+ executor_id: RESULT_VECTOR_EXECUTOR_ID.to_owned(),
+ executor_path: RESULT_VECTOR_EXECUTOR_RELATIVE.to_owned(),
+ executor_test: RESULT_VECTOR_EXECUTOR_TEST.to_owned(),
+ executor_byte_length: executor.byte_length,
+ executor_sha256: executor.sha256,
+ executor_hash_algorithm: HASH_ALGORITHM.to_owned(),
+ },
+ })
+}
+
+fn expected_runtime() -> RuntimeDescriptor {
+ RuntimeDescriptor {
+ event_store_schema_version: EVENT_STORE_SCHEMA_VERSION,
+ event_contract_registry_version: EVENT_CONTRACT_REGISTRY_VERSION,
+ transaction_mode: TRANSACTION_MODE.to_owned(),
+ projection_cursor_count_limit: PROJECTION_CURSOR_COUNT_LIMIT,
+ projection_cursor_rejection_probe_limit: PROJECTION_CURSOR_REJECTION_PROBE_LIMIT,
+ caller_main_table_count_limit: CALLER_MAIN_TABLE_COUNT_LIMIT,
+ caller_foreign_key_row_count_limit: CALLER_FOREIGN_KEY_ROW_COUNT_LIMIT,
+ caller_inbound_foreign_key_policy: CALLER_INBOUND_FOREIGN_KEY_POLICY.to_owned(),
+ caller_inbound_foreign_key_parent_tables: owned(CALLER_INBOUND_FOREIGN_KEY_PARENT_TABLES),
+ cold_repair_mode: COLD_REPAIR_MODE.to_owned(),
+ immutable_raw_digest: DigestDescriptor {
+ algorithm: DIGEST_ALGORITHM.to_owned(),
+ domain_utf8: RAW_DIGEST_DOMAIN_UTF8.to_owned(),
+ domain_terminator: DIGEST_DOMAIN_TERMINATOR.to_owned(),
+ framing: expected_digest_framing(),
+ output_bytes: 32,
+ source_queries: digest_query_descriptors(RAW_DIGEST_QUERY_SPECS),
+ },
+ active_product_state_digest: ProductDigestDescriptor {
+ algorithm: DIGEST_ALGORITHM.to_owned(),
+ domain_utf8: PRODUCT_DIGEST_DOMAIN_UTF8.to_owned(),
+ domain_terminator: DIGEST_DOMAIN_TERMINATOR.to_owned(),
+ framing: expected_digest_framing(),
+ output_bytes: 32,
+ components: owned(PRODUCT_DIGEST_COMPONENTS),
+ exclusions: owned(PRODUCT_DIGEST_EXCLUSIONS),
+ component_queries: digest_query_descriptors(PRODUCT_DIGEST_QUERY_SPECS),
+ },
+ visibility_oracle: VISIBILITY_ORACLE.to_owned(),
+ scoped_integrity_mode: SCOPED_INTEGRITY_MODE.to_owned(),
+ scoped_integrity_tables: owned(SCOPED_INTEGRITY_TABLES),
+ sqlite_sequence_scope: SQLITE_SEQUENCE_SCOPE.to_owned(),
+ stages: owned(REBUILD_STAGES),
+ failpoints: REBUILD_FAILPOINTS
+ .iter()
+ .map(|failpoint| failpoint.id.to_owned())
+ .collect(),
+ preserved_authorities: owned(PRESERVED_AUTHORITIES),
+ }
+}
+
+fn expected_drift_kinds() -> Vec<DriftKindDescriptor> {
+ RAW_SOURCE_REBUILD_DRIFT_KINDS
+ .iter()
+ .map(|(variant, code)| DriftKindDescriptor {
+ variant: (*variant).to_owned(),
+ code: (*code).to_owned(),
+ })
+ .collect()
+}
+
+fn expected_digest_framing() -> DigestFramingDescriptor {
+ DigestFramingDescriptor {
+ section: "S_then_N_then_u64be_length_then_utf8_name".to_owned(),
+ row: "R".to_owned(),
+ signed_i64: "I_then_i64be".to_owned(),
+ boolean: "B_then_u8_0_or_1".to_owned(),
+ optional: "O_then_presence_u8_then_nested_value_when_present".to_owned(),
+ text: "T_then_u64be_length_then_utf8_bytes".to_owned(),
+ blob: "X_then_u64be_length_then_bytes".to_owned(),
+ }
+}
+
+fn digest_query_descriptors(specs: &[DigestQuerySpec]) -> Vec<DigestQueryDescriptor> {
+ specs
+ .iter()
+ .map(|spec| {
+ let framing = expected_digest_field_framing(spec.section);
+ assert_eq!(
+ spec.fields.len(),
+ framing.len(),
+ "governed digest field/framing inventory length"
+ );
+ DigestQueryDescriptor {
+ section: spec.section.to_owned(),
+ sql: spec.sql.to_owned(),
+ fields: spec
+ .fields
+ .iter()
+ .zip(framing)
+ .map(|(name, framing)| DigestFieldDescriptor {
+ name: (*name).to_owned(),
+ framing: (*framing).to_owned(),
+ })
+ .collect(),
+ }
+ })
+ .collect()
+}
+
+fn expected_digest_field_framing(section: &str) -> &'static [&'static str] {
+ match section {
+ "event_envelopes" => &[
+ "i64", "text", "text", "i64", "i64", "text", "text", "text", "text", "i64",
+ ],
+ "event_envelope_tags" => &["i64", "text", "i64", "text", "optional_text", "text"],
+ "envelope_classification" => &[
+ "text",
+ "text",
+ "text",
+ "optional_text",
+ "optional_text",
+ "boolean",
+ ],
+ "tag_classification" => &["text", "i64", "optional_text", "optional_text", "boolean"],
+ "raw_heads" => &["text", "i64", "text", "optional_text", "text", "i64"],
+ "event_coordinates" => &[
+ "text",
+ "text",
+ "i64",
+ "text",
+ "i64",
+ "text",
+ "optional_text",
+ "optional_text",
+ "text",
+ "boolean",
+ "optional_text",
+ ],
+ "nip09_requests" => &["text", "text", "i64"],
+ "nip09_event_targets" => &["text", "text", "i64", "text"],
+ "nip09_address_targets" => &[
+ "text", "i64", "text", "text", "i64", "i64", "text", "text", "text", "text",
+ ],
+ "addressable_heads" => &[
+ "i64",
+ "text",
+ "text",
+ "text",
+ "i64",
+ "text",
+ "optional_text",
+ "optional_text",
+ "text",
+ "optional_text",
+ "optional_text",
+ "optional_text",
+ "optional_text",
+ "optional_i64",
+ ],
+ "current_visibility" => &[
+ "text",
+ "text",
+ "optional_text",
+ "text",
+ "optional_text",
+ "boolean",
+ "optional_text",
+ "optional_text",
+ "optional_text",
+ "optional_text",
+ "optional_text",
+ "optional_i64",
+ "text",
+ ],
+ "food_projection" => &[
+ "i64",
+ "text",
+ "text",
+ "text",
+ "i64",
+ "text",
+ "text",
+ "text",
+ "text",
+ "i64",
+ "text",
+ "text",
+ "text",
+ "text",
+ "optional_text",
+ "optional_text",
+ "text",
+ "text",
+ ],
+ "food_images" => &[
+ "text",
+ "text",
+ "i64",
+ "text",
+ "optional_text",
+ "optional_i64",
+ "optional_i64",
+ "optional_text",
+ "boolean",
+ "text",
+ ],
+ "food_search" => &["text", "text", "text", "text", "text", "text", "text"],
+ "food_cursor" => &["i64", "i64", "blob", "text", "i64"],
+ other => panic!("unrecognized governed digest section `{other}`"),
+ }
+}
+
+fn validate_manifest_shape(manifest: &RawSourceRebuildManifest) -> Result<(), String> {
+ let expected_entries = ENTRY_POINTS
+ .iter()
+ .map(|(role, rust_path)| EntryPointDescriptor {
+ role: (*role).to_owned(),
+ rust_path: (*rust_path).to_owned(),
+ })
+ .collect::<Vec<_>>();
+ let expected_public_api = PublicApiDescriptor {
+ added_symbols: owned(ADDED_PUBLIC_SYMBOLS),
+ methods: owned(PUBLIC_METHODS),
+ error_variants: owned(ERROR_VARIANTS),
+ drift_kinds: expected_drift_kinds(),
+ };
+ if manifest.schema_version != SCHEMA_VERSION
+ || manifest.contract_id != CONTRACT_ID
+ || manifest.authority_id != AUTHORITY_ID
+ || manifest.manifest_schema.path != MANIFEST_SCHEMA_RELATIVE
+ || manifest.predecessor.contract_id != PREDECESSOR_CONTRACT_ID
+ || manifest.predecessor.manifest.path != PREDECESSOR_MANIFEST_RELATIVE
+ || manifest.predecessor.manifest.byte_length
+ != u64::try_from(PREDECESSOR_MANIFEST_BYTE_LENGTH)
+ .map_err(|_| "predecessor byte length does not fit u64".to_owned())?
+ || manifest.predecessor.manifest.sha256 != PREDECESSOR_MANIFEST_SHA256
+ || manifest.runtime != expected_runtime()
+ || manifest.entry_points != expected_entries
+ || manifest.public_api != expected_public_api
+ {
+ return Err(format!(
+ "{MANIFEST_RELATIVE} has inconsistent raw-source rebuild identity or semantics"
+ ));
+ }
+ let expected_sources = SOURCE_SPECS
+ .iter()
+ .map(|spec| (spec.role, spec.path))
+ .collect::<Vec<_>>();
+ let actual_sources = manifest
+ .source_files
+ .iter()
+ .map(|source| (source.role.as_str(), source.path.as_str()))
+ .collect::<Vec<_>>();
+ if actual_sources != expected_sources {
+ return Err(format!(
+ "{MANIFEST_RELATIVE} source-file inventory is not exact"
+ ));
+ }
+ let expected_migrations = MIGRATION_RELATIVES.to_vec();
+ let actual_migrations = manifest
+ .migration_inventory
+ .iter()
+ .map(|descriptor| descriptor.path.as_str())
+ .collect::<Vec<_>>();
+ if actual_migrations != expected_migrations {
+ return Err(format!(
+ "{MANIFEST_RELATIVE} migration inventory must remain exactly versions 0001 through 0004"
+ ));
+ }
+ validate_unique(
+ "raw-source rebuild source roles",
+ manifest
+ .source_files
+ .iter()
+ .map(|source| source.role.as_str()),
+ )?;
+ validate_unique(
+ "raw-source rebuild source paths",
+ manifest
+ .source_files
+ .iter()
+ .map(|source| source.path.as_str()),
+ )?;
+ for source in &manifest.source_files {
+ if GENERATED_ARTIFACT_PATHS.contains(&source.path.as_str()) {
+ return Err(format!(
+ "{MANIFEST_RELATIVE} recursively hashes generated artifact `{}`",
+ source.path
+ ));
+ }
+ validate_file_descriptor(
+ source.path.as_str(),
+ source.byte_length,
+ &source.sha256,
+ &source.hash_algorithm,
+ )?;
+ }
+ for descriptor in manifest
+ .migration_inventory
+ .iter()
+ .chain([&manifest.manifest_schema, &manifest.predecessor.manifest])
+ {
+ validate_file_descriptor(
+ descriptor.path.as_str(),
+ descriptor.byte_length,
+ &descriptor.sha256,
+ &descriptor.hash_algorithm,
+ )?;
+ }
+ if manifest.result_vector.canonical_path != RESULT_VECTOR_CANONICAL_RELATIVE
+ || manifest.result_vector.mirror_path != RESULT_VECTOR_MIRROR_RELATIVE
+ || manifest.result_vector.hash_algorithm != HASH_ALGORITHM
+ || manifest.result_vector.executor_id != RESULT_VECTOR_EXECUTOR_ID
+ || manifest.result_vector.executor_path != RESULT_VECTOR_EXECUTOR_RELATIVE
+ || manifest.result_vector.executor_test != RESULT_VECTOR_EXECUTOR_TEST
+ || manifest.result_vector.executor_hash_algorithm != HASH_ALGORITHM
+ || manifest.result_vector.byte_length == 0
+ || manifest.result_vector.executor_byte_length == 0
+ {
+ return Err(format!(
+ "{MANIFEST_RELATIVE} result-vector descriptor is invalid"
+ ));
+ }
+ validate_sha256("result vector", &manifest.result_vector.sha256)?;
+ validate_sha256(
+ "result-vector executor",
+ &manifest.result_vector.executor_sha256,
+ )
+}
+
+fn validate_source_contract(workspace_root: &Path) -> Result<(), String> {
+ validate_source_inventory()?;
+ validate_complete_event_store_source_closure(workspace_root)?;
+ validate_migration_inventory(workspace_root)?;
+ validate_current_event_store_successor_authority(workspace_root)?;
+ validate_raw_source_rebuild_successor_compiler_inputs(
+ workspace_root,
+ EVENT_STORE_SUCCESSOR_COMPILER_TABLES_SHA256,
+ )?;
+ validate_delegated_compiler_source_pins(workspace_root)?;
+ validate_xtask_manifest_authority(workspace_root)?;
+ validate_predecessor_source_supersession(
+ workspace_root,
+ &read_regular_file(workspace_root, PREDECESSOR_MANIFEST_RELATIVE)?,
+ )?;
+ validate_successor_compiler_input_authority(workspace_root)?;
+ validate_public_api_authority(workspace_root)?;
+ validate_error_authority(workspace_root)?;
+ validate_runtime_authority(workspace_root)?;
+ validate_release_authority(workspace_root)?;
+ validate_command_reachability(workspace_root)
+}
+
+fn validate_source_inventory() -> Result<(), String> {
+ validate_source_inventory_specs(SOURCE_SPECS)
+}
+
+fn validate_source_inventory_specs(source_specs: &[SourceSpec]) -> Result<(), String> {
+ validate_unique(
+ "raw-source rebuild source roles",
+ source_specs.iter().map(|spec| spec.role),
+ )?;
+ validate_unique(
+ "raw-source rebuild source paths",
+ source_specs.iter().map(|spec| spec.path),
+ )?;
+ for (role, path) in REQUIRED_DELEGATED_COMPILER_SOURCES {
+ let matches = source_specs
+ .iter()
+ .filter(|spec| spec.role == *role && spec.path == *path)
+ .count();
+ if matches != 1 {
+ return Err(format!(
+ "raw-source rebuild source inventory must bind delegated compiler input `{role}` at `{path}` exactly once; found {matches}"
+ ));
+ }
+ }
+ validate_unique("raw-source rebuild stages", REBUILD_STAGES.iter().copied())?;
+ validate_unique(
+ "raw-source rebuild failpoint IDs",
+ REBUILD_FAILPOINTS.iter().map(|failpoint| failpoint.id),
+ )?;
+ validate_unique(
+ "raw-source rebuild failpoint variants",
+ REBUILD_FAILPOINTS.iter().map(|failpoint| failpoint.variant),
+ )?;
+ validate_unique(
+ "raw-source rebuild rollback vector case IDs",
+ REBUILD_FAILPOINTS
+ .iter()
+ .map(|failpoint| failpoint.rollback_case_id),
+ )?;
+ let sources = source_specs
+ .iter()
+ .map(|spec| spec.path)
+ .collect::<BTreeSet<_>>();
+ for generated in GENERATED_ARTIFACT_PATHS {
+ if sources.contains(generated) {
+ return Err(format!(
+ "raw-source rebuild generated artifact `{generated}` must not participate in its own source hash graph"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_complete_event_store_source_closure(workspace_root: &Path) -> Result<(), String> {
+ let actual = governed_regular_file_inventory(workspace_root, "crates/event_store/src")?
+ .into_iter()
+ .filter(|relative| relative.ends_with(".rs"))
+ .collect::<Vec<_>>();
+ let mut expected = SOURCE_SPECS
+ .iter()
+ .map(|spec| spec.path)
+ .filter(|relative| {
+ relative.starts_with("crates/event_store/src/") && relative.ends_with(".rs")
+ })
+ .map(str::to_owned)
+ .collect::<Vec<_>>();
+ expected.push(GENERATED_DESCRIPTOR_RELATIVE.to_owned());
+ expected.sort();
+
+ if actual != expected {
+ let actual_set = actual.iter().map(String::as_str).collect::<BTreeSet<_>>();
+ let expected_set = expected.iter().map(String::as_str).collect::<BTreeSet<_>>();
+ let missing = expected_set
+ .difference(&actual_set)
+ .copied()
+ .collect::<Vec<_>>();
+ let unexpected = actual_set
+ .difference(&expected_set)
+ .copied()
+ .collect::<Vec<_>>();
+ return Err(format!(
+ "event-store Rust source closure drifted: missing {missing:?}, unexpected {unexpected:?}"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_delegated_compiler_source_pins(workspace_root: &Path) -> Result<(), String> {
+ for (relative, expected_sha256) in DELEGATED_COMPILER_SOURCE_PINS {
+ let actual_sha256 = sha256_hex(&read_regular_file(workspace_root, relative)?);
+ if actual_sha256 != *expected_sha256 {
+ return Err(format!(
+ "delegated compiler source `{relative}` drifted: expected {expected_sha256}, found {actual_sha256}"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_xtask_manifest_authority(workspace_root: &Path) -> Result<(), String> {
+ let source = regular_utf8_source(workspace_root, XTASK_MANIFEST_RELATIVE)?;
+ let manifest: toml::Value = toml::from_str(&source)
+ .map_err(|error| format!("parse {XTASK_MANIFEST_RELATIVE}: {error}"))?;
+ let package = manifest
+ .get("package")
+ .and_then(toml::Value::as_table)
+ .ok_or_else(|| format!("{XTASK_MANIFEST_RELATIVE} must define one package table"))?;
+ for flag in XTASK_REQUIRED_DISABLED_AUTO_TARGET_FLAGS {
+ if package.get(*flag).and_then(toml::Value::as_bool) != Some(false) {
+ return Err(format!(
+ "{XTASK_MANIFEST_RELATIVE} package.{flag} must be exactly false so delegated compiler targets cannot be auto-discovered"
+ ));
+ }
+ }
+ if package.get("name").and_then(toml::Value::as_str) != Some("xtask")
+ || package.get("publish").and_then(toml::Value::as_bool) != Some(false)
+ || package.contains_key("build")
+ || package.contains_key("autobins")
+ || ["lib", "bin", "example", "test", "bench"]
+ .iter()
+ .any(|target| manifest.get(*target).is_some())
+ {
+ return Err(format!(
+ "{XTASK_MANIFEST_RELATIVE} must remain the unpublished single default-binary xtask package with no build script, autobins override, or explicit additional Cargo targets"
+ ));
+ }
+ for relative in XTASK_FORBIDDEN_AUTO_TARGET_PATHS {
+ match fs::symlink_metadata(workspace_root.join(relative)) {
+ Ok(_) => {
+ return Err(format!(
+ "delegated xtask compiler authority forbids auto-target path `{relative}`"
+ ));
+ }
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
+ Err(error) => {
+ return Err(format!(
+ "inspect delegated compiler auto-target path `{relative}`: {error}"
+ ));
+ }
+ }
+ }
+ Ok(())
+}
+
+fn validate_successor_compiler_input_authority(workspace_root: &Path) -> Result<(), String> {
+ let mut sources = governed_regular_file_inventory(workspace_root, "crates/event_store/src")?
+ .into_iter()
+ .filter(|relative| relative.ends_with(".rs"))
+ .collect::<Vec<_>>();
+ sources.push(RESULT_VECTOR_EXECUTOR_RELATIVE.to_owned());
+ for relative in sources {
+ let file = rust_file(workspace_root, &relative)?;
+ let expected_inputs = expected_successor_compiler_inputs(&relative);
+ validate_exact_successor_compiler_inputs(&relative, &file, expected_inputs)?;
+ }
+ Ok(())
+}
+
+fn expected_successor_compiler_inputs(relative: &str) -> &'static [&'static str] {
+ match relative {
+ "crates/event_store/src/migrations.rs" => &[
+ "include_str!(\"../migrations/0001_event_store.up.sql\")",
+ "include_str!(\"../migrations/0001_event_store.down.sql\")",
+ "include_str!(\"../migrations/0002_nip09.up.sql\")",
+ "include_str!(\"../migrations/0002_nip09.down.sql\")",
+ "include_str!(\"../migrations/0003_food_availability_projection.up.sql\")",
+ "include_str!(\"../migrations/0003_food_availability_projection.down.sql\")",
+ "include_str!(\"../migrations/0004_source_maintenance.up.sql\")",
+ "include_str!(\"../migrations/0004_source_maintenance.down.sql\")",
+ "env!(\"CARGO_MANIFEST_DIR\")",
+ ],
+ "crates/event_store/src/nip09/reconciliation_v1.rs" => &[
+ "include_str!(\"../../migrations/0001_event_store.up.sql\")",
+ "include_str!(\"../../migrations/0002_nip09.up.sql\")",
+ ],
+ "crates/event_store/src/nip09/reconciliation_v1/result_vector_executor.rs" => &[
+ "include_bytes!(\"../../../tests/fixtures/nip09_reconciliation.v1.json\")",
+ "include_str!(\"../../../migrations/0001_event_store.up.sql\")",
+ "include_str!(\"../../../migrations/0002_nip09.up.sql\")",
+ ],
+ "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs" => {
+ &["include_bytes!(\"../../../tests/fixtures/food_availability_projection.v1.json\")"]
+ }
+ "crates/event_store/src/store/raw_source_rebuild_v1_tests.rs" => &[
+ "include_bytes!(\"../../tests/fixtures/food_availability_projection.v1.json\")",
+ "include_bytes!(\"../../tests/fixtures/nip09_reconciliation.v1.json\")",
+ ],
+ RESULT_VECTOR_EXECUTOR_RELATIVE => &[
+ "include_bytes!(\"../../../contracts/conformance/vectors/event_store/raw_source_rebuild.v1.json\")",
+ "include_bytes!(\"fixtures/food_availability_projection.v1.json\")",
+ ],
+ _ => &[],
+ }
+}
+
+fn validate_exact_successor_compiler_inputs(
+ relative: &str,
+ file: &syn::File,
+ expected_inputs: &[&str],
+) -> Result<(), String> {
+ struct Audit {
+ inputs: Vec<String>,
+ path_attributes: Vec<String>,
+ }
+
+ impl<'ast> syn::visit::Visit<'ast> for Audit {
+ fn visit_macro(&mut self, item: &'ast syn::Macro) {
+ if item.path.segments.last().is_some_and(|segment| {
+ matches!(
+ segment.ident.to_string().as_str(),
+ "include" | "include_bytes" | "include_str" | "env" | "option_env"
+ )
+ }) {
+ self.inputs.push(compact_tokens(item));
+ }
+ syn::visit::visit_macro(self, item);
+ }
+
+ fn visit_attribute(&mut self, attribute: &'ast syn::Attribute) {
+ if attribute.path().is_ident("path")
+ || (attribute.path().is_ident("cfg_attr")
+ && token_stream_contains_ident(attribute.meta.to_token_stream(), "path"))
+ {
+ self.path_attributes.push(compact_tokens(attribute));
+ }
+ syn::visit::visit_attribute(self, attribute);
+ }
+ }
+
+ use syn::visit::Visit;
+ let mut audit = Audit {
+ inputs: Vec::new(),
+ path_attributes: Vec::new(),
+ };
+ audit.visit_file(file);
+ let expected_inputs = expected_inputs
+ .iter()
+ .map(|input| (*input).to_owned())
+ .collect::<Vec<_>>();
+ if audit.inputs != expected_inputs || !audit.path_attributes.is_empty() {
+ return Err(format!(
+ "{relative} successor compiler-input authority drifted: expected {expected_inputs:?} and no path retargeting, found {:?} and {:?}",
+ audit.inputs, audit.path_attributes
+ ));
+ }
+ Ok(())
+}
+
+fn token_stream_contains_ident(tokens: proc_macro2::TokenStream, expected: &str) -> bool {
+ tokens.into_iter().any(|token| match token {
+ proc_macro2::TokenTree::Ident(ident) => ident == expected,
+ proc_macro2::TokenTree::Group(group) => {
+ token_stream_contains_ident(group.stream(), expected)
+ }
+ proc_macro2::TokenTree::Punct(_) | proc_macro2::TokenTree::Literal(_) => false,
+ })
+}
+
+fn validate_predecessor_identity(bytes: &[u8]) -> Result<(), String> {
+ if bytes.len() != PREDECESSOR_MANIFEST_BYTE_LENGTH
+ || sha256_hex(bytes) != PREDECESSOR_MANIFEST_SHA256
+ {
+ return Err(format!(
+ "{PREDECESSOR_MANIFEST_RELATIVE} does not match the immutable SourceMaintenance predecessor identity"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_predecessor_source_supersession(
+ workspace_root: &Path,
+ predecessor_bytes: &[u8],
+) -> Result<(), String> {
+ validate_predecessor_identity(predecessor_bytes)?;
+ let predecessor: Value = serde_json::from_slice(predecessor_bytes)
+ .map_err(|error| format!("parse {PREDECESSOR_MANIFEST_RELATIVE}: {error}"))?;
+ let descriptors = predecessor
+ .get("source_files")
+ .and_then(Value::as_array)
+ .ok_or_else(|| format!("{PREDECESSOR_MANIFEST_RELATIVE} has no source_files array"))?;
+ let successor_paths = SOURCE_SPECS
+ .iter()
+ .map(|spec| spec.path)
+ .collect::<BTreeSet<_>>();
+ let expected_drift = EXPECTED_SOURCE_MAINTENANCE_DRIFT_PATHS
+ .iter()
+ .copied()
+ .collect::<BTreeSet<_>>();
+ if expected_drift.len() != EXPECTED_SOURCE_MAINTENANCE_DRIFT_PATHS.len() {
+ return Err(
+ "raw-source rebuild expected predecessor drift paths must be unique".to_owned(),
+ );
+ }
+ if let Some(path) = expected_drift
+ .iter()
+ .find(|path| !successor_paths.contains(**path))
+ {
+ return Err(format!(
+ "raw-source rebuild successor does not current-byte-bind expected changed SourceMaintenance source `{path}`"
+ ));
+ }
+ let mut predecessor_paths = BTreeSet::new();
+ let mut actual_drift = BTreeSet::new();
+ for descriptor in descriptors {
+ let path = descriptor
+ .get("path")
+ .and_then(Value::as_str)
+ .ok_or_else(|| "predecessor source descriptor has no path".to_owned())?;
+ let predecessor_sha256 = descriptor
+ .get("sha256")
+ .and_then(Value::as_str)
+ .ok_or_else(|| format!("predecessor source descriptor `{path}` has no sha256"))?;
+ if !predecessor_paths.insert(path) {
+ return Err(format!(
+ "{PREDECESSOR_MANIFEST_RELATIVE} contains duplicate source descriptor `{path}`"
+ ));
+ }
+ let current = read_regular_file(workspace_root, path)?;
+ if sha256_hex(¤t) != predecessor_sha256 {
+ actual_drift.insert(path);
+ }
+ }
+ if actual_drift != expected_drift {
+ return Err(format!(
+ "raw-source rebuild SourceMaintenance drift inventory differs: expected {expected_drift:?}, found {actual_drift:?}"
+ ));
+ }
+
+ let transitive = TRANSITIVE_PREDECESSOR_SUPERSEDED_PATHS
+ .iter()
+ .copied()
+ .collect::<BTreeSet<_>>();
+ if transitive.len() != TRANSITIVE_PREDECESSOR_SUPERSEDED_PATHS.len() {
+ return Err(
+ "raw-source rebuild transitive predecessor supersession paths must be unique"
+ .to_owned(),
+ );
+ }
+ if let Some(path) = transitive
+ .iter()
+ .find(|path| !successor_paths.contains(**path) && !predecessor_paths.contains(**path))
+ {
+ return Err(format!(
+ "raw-source rebuild transitive supersession path `{path}` is not bound by the current successor or immutable SourceMaintenance predecessor"
+ ));
+ }
+ validate_food_availability_projection_predecessor_production_sources_under_lock(
+ workspace_root,
+ TRANSITIVE_PREDECESSOR_SUPERSEDED_PATHS,
+ )?;
+ Ok(())
+}
+
+fn validate_migration_inventory(workspace_root: &Path) -> Result<(), String> {
+ let migration_root = workspace_root.join("crates/event_store/migrations");
+ let mut actual = fs::read_dir(&migration_root)
+ .map_err(|error| format!("read {}: {error}", migration_root.display()))?
+ .map(|entry| {
+ let entry = entry.map_err(|error| format!("read migration entry: {error}"))?;
+ if !entry
+ .file_type()
+ .map_err(|error| format!("inspect {}: {error}", entry.path().display()))?
+ .is_file()
+ {
+ return Err(format!(
+ "migration inventory entry {} must be a regular file",
+ entry.path().display()
+ ));
+ }
+ entry
+ .path()
+ .strip_prefix(workspace_root)
+ .map(|path| path.to_string_lossy().into_owned())
+ .map_err(|error| format!("relativize migration path: {error}"))
+ })
+ .collect::<Result<Vec<_>, String>>()?;
+ actual.sort();
+ if actual != MIGRATION_RELATIVES {
+ return Err(format!(
+ "raw-source rebuild is runtime-only and requires the exact 0001-through-0004 migration inventory; found {actual:?}"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_public_api_authority(workspace_root: &Path) -> Result<(), String> {
+ let model = rust_file(
+ workspace_root,
+ "crates/event_store/src/model/raw_source_rebuild_v1.rs",
+ )?;
+ let error = rust_file(workspace_root, "crates/event_store/src/error.rs")?;
+ let lib = rust_file(workspace_root, "crates/event_store/src/lib.rs")?;
+ let store = rust_file(workspace_root, "crates/event_store/src/store.rs")?;
+
+ validate_digest_newtype(&model, "RadrootsEventStoreImmutableRawDigestV1")?;
+ validate_digest_newtype(&model, "RadrootsEventStoreActiveProductStateDigestV1")?;
+ validate_report_model(&model)?;
+ validate_caller_inbound_foreign_key_model(&error)?;
+
+ let routes = collect_top_level_public_use_routes(&lib);
+ for symbol in ADDED_PUBLIC_SYMBOLS {
+ let expected_module = match *symbol {
+ "RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1"
+ | "RadrootsEventStoreCallerInboundForeignKeyV1"
+ | "RadrootsEventStoreRawSourceRebuildDriftV1" => "error",
+ _ => "model",
+ };
+ let matches = routes
+ .iter()
+ .filter(|route| route.exported_name == *symbol)
+ .collect::<Vec<_>>();
+ let [route] = matches.as_slice() else {
+ return Err(format!(
+ "crate root must export raw-source rebuild symbol `{symbol}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ if route.attributes != ["#[cfg(feature=\"sqlite\")]"].map(str::to_owned)
+ || route.absolute
+ || route.renamed
+ || route.glob
+ || route.segments.as_slice() != [expected_module, *symbol]
+ {
+ return Err(format!(
+ "crate-root raw-source rebuild export `{symbol}` must be direct, non-renamed, and sqlite-gated from {expected_module}"
+ ));
+ }
+ }
+
+ validate_public_method_signatures(&store)?;
+ validate_store_public_method_surface(&store)
+}
+
+fn validate_public_method_signatures(store: &syn::File) -> Result<(), String> {
+ for (method, expected_signature) in [
+ (
+ "rebuild_from_raw_v1",
+ "pub async fn rebuild_from_raw_v1(&self,) -> Result<RadrootsEventStoreRawSourceRebuildReportV1, RadrootsEventStoreError>",
+ ),
+ (
+ "repair_file_from_raw_v1",
+ "pub async fn repair_file_from_raw_v1(path: impl AsRef<Path>,) -> Result<(Self, RadrootsEventStoreRawSourceRebuildReportV1), RadrootsEventStoreError>",
+ ),
+ ] {
+ let function = exact_associated_method(store, "RadrootsEventStore", method)?;
+ let actual = compact_tokens(&function.sig);
+ let expected = compact_signature(expected_signature)?;
+ if actual != expected {
+ return Err(format!(
+ "RadrootsEventStore::{method} signature drifted: expected `{expected}`, found `{actual}`"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_store_public_method_surface(file: &syn::File) -> Result<(), String> {
+ const EXPECTED_SHA256: &str =
+ "f96da738c7c24b9ebdc99f405035f7f9e0758d4e1d83f2a8de0b2877309eeb87";
+ let signatures = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Impl(item)
+ if item.trait_.is_none()
+ && compact_tokens(item.self_ty.as_ref()) == "RadrootsEventStore" =>
+ {
+ Some(item)
+ }
+ _ => None,
+ })
+ .flat_map(|item| &item.items)
+ .filter_map(|item| match item {
+ syn::ImplItem::Fn(function) if matches!(function.vis, syn::Visibility::Public(_)) => {
+ Some(compact_tokens(&function.sig))
+ }
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ validate_unique(
+ "RadrootsEventStore public method signatures",
+ signatures.iter().map(String::as_str),
+ )?;
+ let actual_sha256 = sha256_hex(signatures.join("\n").as_bytes());
+ if actual_sha256 != EXPECTED_SHA256 {
+ return Err(format!(
+ "RadrootsEventStore complete public method surface drifted: expected {EXPECTED_SHA256}, found {actual_sha256}"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_digest_newtype(file: &syn::File, name: &str) -> Result<(), String> {
+ let item = exact_struct(file, name)?;
+ let mut item = item.clone();
+ strip_doc_attributes(&mut item.attrs);
+ for field in &mut item.fields {
+ strip_doc_attributes(&mut field.attrs);
+ }
+ let expected = syn::parse_str::<syn::ItemStruct>(&format!(
+ "#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct {name}(pub(crate) [u8; 32]);"
+ ))
+ .map_err(|error| format!("parse authoritative digest model `{name}`: {error}"))?;
+ if compact_tokens(&item) != compact_tokens(&expected) {
+ return Err(format!(
+ "{name} must remain an opaque, fixed-width, Copy SHA-256 newtype"
+ ));
+ }
+ let inherent = exact_impl(file, name)?;
+ let methods = inherent
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ syn::ImplItem::Fn(function) => Some((function.sig.ident.to_string(), function)),
+ _ => None,
+ })
+ .collect::<BTreeMap<_, _>>();
+ if methods.keys().cloned().collect::<Vec<_>>() != ["as_bytes", "from_bytes"] {
+ return Err(format!(
+ "{name} must expose only public as_bytes plus crate-private from_bytes"
+ ));
+ }
+ let from_bytes = methods["from_bytes"];
+ let as_bytes = methods["as_bytes"];
+ if compact_tokens(&from_bytes.sig)
+ != compact_signature("pub(crate) const fn from_bytes(bytes: [u8; 32]) -> Self")?
+ || compact_tokens(&as_bytes.sig)
+ != compact_signature("pub const fn as_bytes(&self) -> &[u8; 32]")?
+ {
+ return Err(format!("{name} constructor or accessor signature drifted"));
+ }
+ Ok(())
+}
+
+fn validate_report_model(file: &syn::File) -> Result<(), String> {
+ let item = exact_struct(file, "RadrootsEventStoreRawSourceRebuildReportV1")?;
+ let mut item = item.clone();
+ strip_doc_attributes(&mut item.attrs);
+ for field in &mut item.fields {
+ strip_doc_attributes(&mut field.attrs);
+ }
+ let expected = syn::parse_str::<syn::ItemStruct>(
+ r#"#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ pub struct RadrootsEventStoreRawSourceRebuildReportV1 {
+ pub(crate) prior_source_generation: RadrootsEventStoreSourceGeneration,
+ pub(crate) new_source_generation: RadrootsEventStoreSourceGeneration,
+ pub(crate) source_capacity: RadrootsEventStoreSourceCapacityV1,
+ pub(crate) immutable_raw_digest: RadrootsEventStoreImmutableRawDigestV1,
+ pub(crate) active_product_state_digest: RadrootsEventStoreActiveProductStateDigestV1,
+ }"#,
+ )
+ .map_err(|error| format!("parse authoritative rebuild report: {error}"))?;
+ if compact_tokens(&item) != compact_tokens(&expected) {
+ return Err(
+ "RadrootsEventStoreRawSourceRebuildReportV1 field or visibility authority drifted"
+ .to_owned(),
+ );
+ }
+ let inherent = exact_impl(file, "RadrootsEventStoreRawSourceRebuildReportV1")?;
+ let actual = inherent
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ syn::ImplItem::Fn(function) => Some(function.sig.ident.to_string()),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let expected = [
+ "prior_source_generation",
+ "new_source_generation",
+ "source_capacity",
+ "raw_high_water_seq",
+ "immutable_raw_digest",
+ "active_product_state_digest",
+ ];
+ if actual != expected {
+ return Err(format!(
+ "raw-source rebuild report accessor inventory differs: expected {expected:?}, found {actual:?}"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_caller_inbound_foreign_key_model(file: &syn::File) -> Result<(), String> {
+ let item = exact_struct(file, "RadrootsEventStoreCallerInboundForeignKeyV1")?;
+ let mut item = item.clone();
+ strip_doc_attributes(&mut item.attrs);
+ for field in &mut item.fields {
+ strip_doc_attributes(&mut field.attrs);
+ }
+ let expected = syn::parse_str::<syn::ItemStruct>(
+ r#"
+ #[non_exhaustive]
+ #[derive(Debug, PartialEq, Eq)]
+ pub struct RadrootsEventStoreCallerInboundForeignKeyV1 {
+ pub child_table: String,
+ pub foreign_key_id: i64,
+ pub foreign_key_sequence: i64,
+ pub child_column: String,
+ pub parent_table: String,
+ pub parent_column: Option<String>,
+ pub on_update: String,
+ pub on_delete: String,
+ pub match_clause: String,
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse caller inbound foreign-key model: {error}"))?;
+ if compact_tokens(&item) != compact_tokens(&expected) {
+ return Err(
+ "RadrootsEventStoreCallerInboundForeignKeyV1 field, visibility, or derive authority drifted"
+ .to_owned(),
+ );
+ }
+
+ let display = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Impl(item)
+ if compact_tokens(item.self_ty.as_ref())
+ == "RadrootsEventStoreCallerInboundForeignKeyV1"
+ && item.trait_.as_ref().is_some_and(|(_, path, _)| {
+ compact_tokens(path) == "core::fmt::Display"
+ }) =>
+ {
+ Some(item)
+ }
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [display] = display.as_slice() else {
+ return Err(format!(
+ "RadrootsEventStoreCallerInboundForeignKeyV1 must define one Display authority; found {}",
+ display.len()
+ ));
+ };
+ let expected_display = syn::parse_str::<syn::ItemImpl>(
+ r#"
+ impl core::fmt::Display for RadrootsEventStoreCallerInboundForeignKeyV1 {
+ fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(
+ formatter,
+ "{}:{} on `{}` (`{}` -> `{}`.",
+ self.foreign_key_id,
+ self.foreign_key_sequence,
+ self.child_table,
+ self.child_column,
+ self.parent_table,
+ )?;
+ match self.parent_column.as_deref() {
+ Some(parent_column) => write!(formatter, "`{parent_column}`")?,
+ None => formatter.write_str("<implicit primary key>")?,
+ }
+ write!(
+ formatter,
+ ", on update {}, on delete {}, match {})",
+ self.on_update,
+ self.on_delete,
+ self.match_clause,
+ )
+ }
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse caller inbound foreign-key Display authority: {error}"))?;
+ if compact_tokens(*display) != compact_tokens(&expected_display) {
+ return Err(
+ "RadrootsEventStoreCallerInboundForeignKeyV1 Display authority drifted".to_owned(),
+ );
+ }
+ Ok(())
+}
+
+fn validate_error_authority(workspace_root: &Path) -> Result<(), String> {
+ let file = rust_file(workspace_root, "crates/event_store/src/error.rs")?;
+ validate_raw_source_rebuild_drift_taxonomy(&file)?;
+ let limit = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Const(item)
+ if item.ident == "RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1" =>
+ {
+ Some(item)
+ }
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [limit] = limit.as_slice() else {
+ return Err(format!(
+ "projection-cursor capacity authority must define its public limit exactly once; found {}",
+ limit.len()
+ ));
+ };
+ let mut limit = (*limit).clone();
+ strip_doc_attributes(&mut limit.attrs);
+ let expected_limit = syn::parse_str::<syn::ItemConst>(
+ "pub const RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1: u32 = 4_096;",
+ )
+ .map_err(|error| format!("parse projection-cursor limit authority: {error}"))?;
+ if compact_tokens(&limit) != compact_tokens(&expected_limit) {
+ return Err(
+ "RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1 must remain exactly 4,096"
+ .to_owned(),
+ );
+ }
+ let errors = exact_enum(&file, "RadrootsEventStoreError")?;
+ const EXPECTED_ERROR_ENUM_SHA256: &str =
+ "dcb9416ca05bda35845f8708fe73132df7137b0c0e002e8ba6d709989bc31939";
+ let actual_error_enum_sha256 = sha256_hex(compact_tokens(errors).as_bytes());
+ if actual_error_enum_sha256 != EXPECTED_ERROR_ENUM_SHA256 {
+ return Err(format!(
+ "RadrootsEventStoreError complete variant surface drifted: expected {EXPECTED_ERROR_ENUM_SHA256}, found {actual_error_enum_sha256}"
+ ));
+ }
+ let variants = errors
+ .variants
+ .iter()
+ .map(|variant| (variant.ident.to_string(), variant))
+ .collect::<BTreeMap<_, _>>();
+ for name in ERROR_VARIANTS {
+ if !variants.contains_key(*name) {
+ return Err(format!(
+ "RadrootsEventStoreError must define raw-source rebuild variant `{name}`"
+ ));
+ }
+ }
+ let drift = variants["RawSourceRebuildStateDrift"];
+ let rollback = variants["RawSourceRebuildTransactionRollbackFailed"];
+ let cursor_capacity = variants["ProjectionCursorCapacityExceeded"];
+ let repair_identity = variants["RawSourceRepairDatabaseIdentityMismatch"];
+ let repair_lock_domain = variants["RawSourceRepairCanonicalPathLockDomainMismatch"];
+ let repair_canonicalization = variants["RawSourceRepairMainDatabaseCanonicalizationFailed"];
+ let caller_table_capacity = variants["RawSourceRebuildCallerTableCapacityExceeded"];
+ let caller_foreign_key_capacity = variants["RawSourceRebuildCallerForeignKeyCapacityExceeded"];
+ let caller_inbound_foreign_key = variants["RawSourceRebuildCallerInboundForeignKeyUnsupported"];
+ let drift_fields = compact_tokens(&drift.fields);
+ let rollback_fields = compact_tokens(&rollback.fields);
+ if drift_fields != "{kind:RadrootsEventStoreRawSourceRebuildDriftV1,detail:String,}" {
+ return Err(
+ "RawSourceRebuildStateDrift must carry one stable drift kind and diagnostic detail"
+ .to_owned(),
+ );
+ }
+ if rollback_fields != "{#[source]primary:Box<RadrootsEventStoreError>,rollback:sqlx::Error,}" {
+ return Err(format!(
+ "RawSourceRebuildTransactionRollbackFailed must preserve typed primary and SQL rollback errors; found `{rollback_fields}`"
+ ));
+ }
+ if compact_tokens(&cursor_capacity.fields) != "{current:u32,limit:u32}" {
+ return Err(
+ "ProjectionCursorCapacityExceeded must carry exact current and limit u32 fields"
+ .to_owned(),
+ );
+ }
+ if compact_tokens(&repair_identity.fields) != "{expected:String,actual:String}"
+ || compact_tokens(&repair_lock_domain.fields) != "{canonical_path:String}"
+ || compact_tokens(&repair_canonicalization.fields)
+ != "{filename:String,#[source]source:std::io::Error,}"
+ {
+ return Err(
+ "raw-source file repair errors must retain their exact typed fields".to_owned(),
+ );
+ }
+ if compact_tokens(&caller_table_capacity.fields) != "{observed_at_least:u64,limit:u64}"
+ || compact_tokens(&caller_foreign_key_capacity.fields)
+ != "{observed_at_least:u64,limit:u64}"
+ || compact_tokens(&caller_inbound_foreign_key.fields)
+ != "{dependency:Box<RadrootsEventStoreCallerInboundForeignKeyV1>,}"
+ {
+ return Err(
+ "raw-source rebuild caller-schema errors must retain their exact typed fields"
+ .to_owned(),
+ );
+ }
+ let drift_attrs = drift.attrs.iter().map(compact_tokens).collect::<Vec<_>>();
+ let rollback_attrs = rollback
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .collect::<Vec<_>>();
+ if !drift_attrs.iter().any(|attribute| {
+ attribute.contains("event-storeraw-sourcerebuildauthorityisinconsistent({kind}):{detail}")
+ }) || !rollback_attrs.iter().any(|attribute| {
+ attribute
+ .contains("raw-sourcerebuildfailed:{primary};transactionrollbackalsofailed:{rollback}")
+ }) || !cursor_capacity
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .any(|attribute| {
+ attribute.contains(
+ "event-storegenericprojectioncursorcapacityexceeded:current{current},limit{limit}",
+ )
+ })
+ || !repair_identity.attrs.iter().map(compact_tokens).any(|attribute| {
+ attribute.contains(
+ "raw-sourcerepairSQLitemaindatabaseidentitymismatch:expected`{expected}`,found`{actual}`",
+ )
+ })
+ || !repair_lock_domain
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .any(|attribute| {
+ attribute.contains(
+ "raw-sourcerepaircanonicalpath`{canonical_path}`doesnotsharethevalidatedSQLitemainlockdomain",
+ )
+ })
+ || !repair_canonicalization
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .any(|attribute| {
+ attribute.contains(
+ "raw-sourcerepaircouldnotcanonicalizeSQLitemaindatabase`{filename}`:{source}",
+ )
+ })
+ || !caller_table_capacity
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .any(|attribute| {
+ attribute.contains(
+ "event-storeraw-sourcerebuildcallermain-tableinventoryexceedsboundedpreflightcapacity:observedatleast{observed_at_least},limit{limit}",
+ )
+ })
+ || !caller_foreign_key_capacity
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .any(|attribute| {
+ attribute.contains(
+ "event-storeraw-sourcerebuildcallerforeign-keyinventoryexceedsboundedpreflightcapacity:observedatleast{observed_at_least}rows,limit{limit}",
+ )
+ })
+ || !caller_inbound_foreign_key
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .any(|attribute| {
+ attribute.contains(
+ "event-storeraw-sourcerebuilddoesnotsupportcaller-ownedforeignkey{dependency}",
+ )
+ })
+ {
+ return Err("raw-source rebuild typed error display contract drifted".to_owned());
+ }
+ Ok(())
+}
+
+fn validate_raw_source_rebuild_drift_taxonomy(file: &syn::File) -> Result<(), String> {
+ let item = exact_enum(file, "RadrootsEventStoreRawSourceRebuildDriftV1")?;
+ let mut item = item.clone();
+ strip_doc_attributes(&mut item.attrs);
+ for variant in &mut item.variants {
+ strip_doc_attributes(&mut variant.attrs);
+ }
+ let expected = syn::parse_str::<syn::ItemEnum>(
+ r#"
+ #[non_exhaustive]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ pub enum RadrootsEventStoreRawSourceRebuildDriftV1 {
+ ManagedSchemaAuthority,
+ ImmutableRawAuthority,
+ SourceGenerationLineage,
+ AddressableTransitionAuthority,
+ DerivedProductStateAuthority,
+ RebuildPostcondition,
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse raw-source rebuild drift taxonomy: {error}"))?;
+ if compact_tokens(&item) != compact_tokens(&expected) {
+ return Err(
+ "RadrootsEventStoreRawSourceRebuildDriftV1 variant or derive authority drifted"
+ .to_owned(),
+ );
+ }
+
+ let code = exact_associated_method(file, "RadrootsEventStoreRawSourceRebuildDriftV1", "code")?;
+ let mut code = code.clone();
+ strip_doc_attributes(&mut code.attrs);
+ let expected_code = syn::parse_str::<syn::ImplItemFn>(
+ r#"
+ pub const fn code(self) -> &'static str {
+ match self {
+ Self::ManagedSchemaAuthority => "managed_schema_authority",
+ Self::ImmutableRawAuthority => "immutable_raw_authority",
+ Self::SourceGenerationLineage => "source_generation_lineage",
+ Self::AddressableTransitionAuthority => "addressable_transition_authority",
+ Self::DerivedProductStateAuthority => "derived_product_state_authority",
+ Self::RebuildPostcondition => "rebuild_postcondition",
+ }
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse raw-source rebuild drift code authority: {error}"))?;
+ if compact_tokens(&code) != compact_tokens(&expected_code) {
+ return Err("RadrootsEventStoreRawSourceRebuildDriftV1::code mapping drifted".to_owned());
+ }
+
+ let display_impls = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Impl(item)
+ if compact_tokens(item.self_ty.as_ref())
+ == "RadrootsEventStoreRawSourceRebuildDriftV1"
+ && item.trait_.as_ref().is_some_and(|(_, path, _)| {
+ compact_tokens(path) == "core::fmt::Display"
+ }) =>
+ {
+ Some(item)
+ }
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [display] = display_impls.as_slice() else {
+ return Err(format!(
+ "RadrootsEventStoreRawSourceRebuildDriftV1 must implement Display exactly once; found {}",
+ display_impls.len()
+ ));
+ };
+ let expected_display = syn::parse_str::<syn::ItemImpl>(
+ r#"
+ impl core::fmt::Display for RadrootsEventStoreRawSourceRebuildDriftV1 {
+ fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ formatter.write_str(self.code())
+ }
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse raw-source rebuild drift Display authority: {error}"))?;
+ if compact_tokens(*display) != compact_tokens(&expected_display) {
+ return Err("RadrootsEventStoreRawSourceRebuildDriftV1 Display mapping drifted".to_owned());
+ }
+ Ok(())
+}
+
+fn validate_runtime_authority(workspace_root: &Path) -> Result<(), String> {
+ let rebuild_relative = REBUILD_RUNTIME_SOURCE_RELATIVE;
+ let rebuild = rust_source(workspace_root, rebuild_relative)?;
+ let rebuild_file =
+ syn::parse_file(&rebuild).map_err(|error| format!("parse {rebuild_relative}: {error}"))?;
+ for (name, domain) in [
+ ("IMMUTABLE_RAW_DIGEST_DOMAIN_V1", RAW_DIGEST_DOMAIN_UTF8),
+ (
+ "ACTIVE_PRODUCT_STATE_DIGEST_DOMAIN_V1",
+ PRODUCT_DIGEST_DOMAIN_UTF8,
+ ),
+ ] {
+ let mut expected = domain.as_bytes().to_vec();
+ expected.push(0);
+ if exact_byte_string_const(&rebuild_file, name)? != expected {
+ return Err(format!(
+ "{rebuild_relative} `{name}` must be exact UTF-8 domain bytes followed by one NUL terminator"
+ ));
+ }
+ }
+ validate_failpoint_authority(&rebuild_file, rebuild_relative)?;
+ let failpoint_test_file = rust_file(workspace_root, REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE)?;
+ validate_failpoint_test_array_authority(
+ &failpoint_test_file,
+ REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE,
+ )?;
+ let reconciliation_relative = "crates/event_store/src/nip09/reconciliation_v1.rs";
+ let reconciliation_file = rust_file(workspace_root, reconciliation_relative)?;
+ validate_rebuild_marker_token_authority(
+ &reconciliation_file,
+ reconciliation_relative,
+ &rebuild_file,
+ rebuild_relative,
+ )?;
+ validate_coordinator_authority(&rebuild_file, rebuild_relative)?;
+ validate_caller_schema_dependency_authority(&rebuild_file, rebuild_relative)?;
+ validate_transition_sequence_authority(&rebuild_file, rebuild_relative)?;
+ validate_scoped_integrity_authority(&rebuild_file, rebuild_relative)?;
+ validate_digest_query_authority(&rebuild_file, rebuild_relative)?;
+ if rebuild.contains("json_array(")
+ || rebuild.contains("Vec<String>")
+ || rebuild.contains("update_product_rows")
+ || rebuild.contains("update_active_product_rows")
+ {
+ return Err(
+ "active product digest must hash typed binary fields, not SQLite JSON row serialization"
+ .to_owned(),
+ );
+ }
+ for helper in [
+ "digest_section",
+ "digest_row_start",
+ "digest_i64",
+ "digest_text",
+ "digest_optional_text",
+ ] {
+ exact_free_function(&rebuild_file, helper).map_err(|error| {
+ format!("typed digest framing helper `{helper}` is missing: {error}")
+ })?;
+ }
+
+ let oracle_relative = "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs";
+ let oracle = rust_source(workspace_root, oracle_relative)?;
+ let oracle_file =
+ syn::parse_file(&oracle).map_err(|error| format!("parse {oracle_relative}: {error}"))?;
+ for marker in [
+ "audit_current_visibility_from_raw_v1",
+ "ReconciledEvent",
+ "StoredEventClass::Regular",
+ "StoredEventClass::Replaceable",
+ "StoredEventClass::Addressable",
+ "kind_u32",
+ ] {
+ if !oracle.contains(marker) {
+ return Err(format!(
+ "{oracle_relative} is missing independent visibility-oracle witness `{marker}`"
+ ));
+ }
+ }
+ for forbidden in ["radroots_event_store_current_visibility_v1", "sqlx::query"] {
+ if oracle.contains(forbidden) {
+ return Err(format!(
+ "independent raw-snapshot visibility oracle must not query derived visibility authority `{forbidden}`"
+ ));
+ }
+ }
+ validate_visibility_oracle_index_authority(&oracle_file, oracle_relative)?;
+
+ let store_relative = "crates/event_store/src/store.rs";
+ let store = rust_source(workspace_root, store_relative)?;
+ let store_file =
+ syn::parse_file(&store).map_err(|error| format!("parse {store_relative}: {error}"))?;
+ validate_public_entry_point_authority(&store_file, store_relative)?;
+ validate_streaming_dependency_authority(workspace_root)?;
+
+ let reconciliation_relative = "crates/event_store/src/nip09/reconciliation_v1.rs";
+ let reconciliation = rust_source(workspace_root, reconciliation_relative)?;
+ let reconciliation_file = syn::parse_file(&reconciliation)
+ .map_err(|error| format!("parse {reconciliation_relative}: {error}"))?;
+ validate_reconciliation_direct_request_index_authority(
+ &reconciliation_file,
+ reconciliation_relative,
+ )?;
+ let validation = compact_tokens(exact_free_function(
+ &reconciliation_file,
+ "validate_projection_cursor_authority",
+ )?);
+ for marker in [
+ "RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1",
+ "+1",
+ "SELECT1FROMprojection_cursorLIMIT?",
+ "SELECT1FROMradroots_event_store_projection_cursor_sourceLIMIT?",
+ "validate_projection_cursor_cardinality_v1(cursor_probe.len())?",
+ "validate_projection_cursor_cardinality_v1(identity_probe.len())?",
+ "LIMIT1",
+ ] {
+ if !validation.contains(marker) {
+ return Err(format!(
+ "bounded generic projection-cursor validation is missing `{marker}`"
+ ));
+ }
+ }
+ if validation.contains("COUNT(") {
+ return Err(
+ "generic projection-cursor validation must use cap-plus-one probes, not unbounded counts"
+ .to_owned(),
+ );
+ }
+ let preflight = compact_tokens(exact_free_function(
+ &reconciliation_file,
+ "preflight_projection_cursor_insert_v1",
+ )?);
+ for marker in [
+ "SELECT1FROMprojection_cursorLIMIT?",
+ "RADROOTS_EVENT_STORE_PROJECTION_CURSOR_COUNT_LIMIT_V1",
+ "ProjectionCursorCapacityExceeded",
+ ] {
+ if !preflight.contains(marker) {
+ return Err(format!(
+ "projection-cursor prospective insert preflight is missing `{marker}`"
+ ));
+ }
+ }
+ if rebuild.contains("validate_projection_cursor_authority")
+ || rebuild.contains("preflight_projection_cursor_insert_v1")
+ || oracle.contains("projection_cursor")
+ {
+ return Err(
+ "public raw-source rebuild must not enumerate caller-owned generic projection cursors"
+ .to_owned(),
+ );
+ }
+ let schema_relative = "crates/event_store/src/schema.rs";
+ let schema_source = rust_source(workspace_root, schema_relative)?;
+ let schema_file = syn::parse_file(&schema_source)
+ .map_err(|error| format!("parse {schema_relative}: {error}"))?;
+ let schema_version = schema_file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Const(item) if item.ident == "RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1" => Some(item),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [schema_version] = schema_version.as_slice() else {
+ return Err(format!(
+ "{schema_relative} must define one dedicated raw-source rebuild schema version; found {}",
+ schema_version.len()
+ ));
+ };
+ if compact_tokens(schema_version) != "constRAW_SOURCE_REBUILD_SCHEMA_VERSION_V1:u32=4;" {
+ return Err(
+ "raw-source rebuild maintenance authority must remain pinned to literal schema v4"
+ .to_owned(),
+ );
+ }
+ let exact_v4 = compact_tokens(exact_free_function(
+ &schema_file,
+ "validate_exact_managed_v4_for_raw_source_rebuild_v1",
+ )?);
+ for marker in [
+ "validate_embedded_migration_registry()?",
+ "validate_repair_temp_schema_bounded_v1(connection,EVENT_STORE_MIGRATIONS).await?",
+ "read_repair_catalog_bounded_v1(connection,EVENT_STORE_MIGRATIONS).await?",
+ "validate_ledger_catalog(&catalog)?",
+ "read_repair_history_bounded_v1(connection,RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1)",
+ "validate_history_against_registry(&history,EVENT_STORE_MIGRATIONS,RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1,)?",
+ "current!=RAW_SOURCE_REBUILD_SCHEMA_VERSION_V1",
+ "catalog_fingerprint(&governed_catalog(&catalog,EVENT_STORE_MIGRATIONS))",
+ "actual!=migration.schema_sha256",
+ ] {
+ if !exact_v4.contains(marker) {
+ return Err(format!(
+ "exact managed-v4 maintenance validator is missing `{marker}`"
+ ));
+ }
+ }
+ for forbidden in [
+ "RADROOTS_EVENT_STORE_SCHEMA_VERSION_CURRENT",
+ "validate_event_store_temp_schema_with_registry",
+ "read_catalog(",
+ "read_history(",
+ "validate_active_hook_state",
+ "validate_food_availability_projection_hook",
+ ] {
+ if exact_v4.contains(forbidden) {
+ return Err(format!(
+ "exact managed-v4 maintenance validator must not depend on `{forbidden}`"
+ ));
+ }
+ }
+ validate_bounded_repair_schema_authority(&schema_file, schema_relative)?;
+ let store_tokens = compact_tokens(&store_file);
+ if store_tokens
+ .matches("preflight_projection_cursor_insert_v1")
+ .count()
+ < 3
+ {
+ return Err(
+ "both supported generic projection-cursor insert paths must reach prospective capacity preflight"
+ .to_owned(),
+ );
+ }
+ Ok(())
+}
+
+fn validate_rebuild_marker_token_authority(
+ reconciliation: &syn::File,
+ reconciliation_relative: &str,
+ rebuild: &syn::File,
+ rebuild_relative: &str,
+) -> Result<(), String> {
+ let token = exact_struct(reconciliation, "SourceRebuildMarkerTokenV1")?;
+ let mut token = token.clone();
+ strip_doc_attributes(&mut token.attrs);
+ for field in &mut token.fields {
+ strip_doc_attributes(&mut field.attrs);
+ }
+ let expected_token = syn::parse_str::<syn::ItemStruct>(
+ "struct SourceRebuildMarkerTokenV1 { generation: RadrootsEventStoreSourceGeneration, }",
+ )
+ .map_err(|error| format!("parse rebuild marker token authority: {error}"))?;
+ if compact_tokens(&token) != compact_tokens(&expected_token) {
+ return Err(format!(
+ "{reconciliation_relative} rebuild marker token must remain private, single-field, and non-Clone/non-Copy"
+ ));
+ }
+
+ let open = exact_free_function(reconciliation, "open_source_rebuild_marker")?;
+ let close = exact_free_function(reconciliation, "close_source_rebuild_marker")?;
+ if compact_tokens(&open.sig)
+ != compact_signature(
+ "async fn open_source_rebuild_marker(connection: &mut SqliteConnection, plan: &SourceRebuildPlan,) -> Result<SourceRebuildMarkerTokenV1, RadrootsEventStoreError>",
+ )?
+ || compact_tokens(&close.sig)
+ != compact_signature(
+ "async fn close_source_rebuild_marker(connection: &mut SqliteConnection, marker: SourceRebuildMarkerTokenV1,) -> Result<(), RadrootsEventStoreError>",
+ )?
+ {
+ return Err(format!(
+ "{reconciliation_relative} marker open/close signatures must create and consume the exact rebuild token"
+ ));
+ }
+ let open_body = compact_tokens(&open.block);
+ let close_body = compact_tokens(&close.block);
+ if open_body
+ .matches("SourceRebuildMarkerTokenV1{generation:plan.generation,}")
+ .count()
+ != 1
+ || close_body.matches("marker.generation").count() != 1
+ {
+ return Err(format!(
+ "{reconciliation_relative} marker token construction or generation-bound close authority drifted"
+ ));
+ }
+
+ struct MarkerTokenConstructionCounter(usize);
+ impl<'ast> syn::visit::Visit<'ast> for MarkerTokenConstructionCounter {
+ fn visit_expr_struct(&mut self, expression: &'ast syn::ExprStruct) {
+ if expression
+ .path
+ .segments
+ .last()
+ .is_some_and(|segment| segment.ident == "SourceRebuildMarkerTokenV1")
+ {
+ self.0 += 1;
+ }
+ syn::visit::visit_expr_struct(self, expression);
+ }
+ }
+ use syn::visit::Visit;
+ let mut constructions = MarkerTokenConstructionCounter(0);
+ constructions.visit_file(reconciliation);
+ constructions.visit_file(rebuild);
+ if constructions.0 != 1 {
+ return Err(format!(
+ "governed rebuild sources must construct SourceRebuildMarkerTokenV1 exactly once inside marker open; found {}",
+ constructions.0
+ ));
+ }
+
+ for (relative, function) in [
+ (
+ reconciliation_relative,
+ exact_free_function(reconciliation, "apply_reconciliation_hook")?,
+ ),
+ (
+ rebuild_relative,
+ exact_free_function(rebuild, "rebuild_from_raw_v1_in_transaction_inner")?,
+ ),
+ ] {
+ let body = compact_tokens(&function.block);
+ if body
+ .matches("letmarker=open_source_rebuild_marker(connection,&plan).await?;")
+ .count()
+ != 1
+ || body
+ .matches("close_source_rebuild_marker(connection,marker).await?;")
+ .count()
+ != 1
+ || body.contains("marker.clone()")
+ {
+ return Err(format!(
+ "{relative}::{} must acquire and consume one non-cloned rebuild marker token",
+ function.sig.ident
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_reconciliation_direct_request_index_authority(
+ file: &syn::File,
+ relative: &str,
+) -> Result<(), String> {
+ let request_index = exact_impl(file, "RequestIndex")?;
+ let methods = request_index
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ syn::ImplItem::Fn(function) => Some(function.sig.ident.to_string()),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ if methods != ["new", "insert", "decision"] {
+ return Err(format!(
+ "{relative} RequestIndex method inventory must be exactly [new, insert, decision]; found {methods:?}"
+ ));
+ }
+
+ let constructor = compact_tokens(exact_associated_method(file, "RequestIndex", "new")?);
+ for marker in ["forrequestinrequests", "index.insert(request)"] {
+ if !constructor.contains(marker) {
+ return Err(format!(
+ "{relative} RequestIndex::new is missing incremental construction authority `{marker}`"
+ ));
+ }
+ }
+ for forbidden in [".projection()", ".event_targets()", ".address_targets()"] {
+ if constructor.contains(forbidden) {
+ return Err(format!(
+ "{relative} RequestIndex::new must delegate each request once, not scan `{forbidden}`"
+ ));
+ }
+ }
+
+ let insert_function = exact_associated_method(file, "RequestIndex", "insert")?;
+ let insert = compact_tokens(insert_function);
+ for (marker, count) in [
+ (".projection()", 2),
+ (".event_targets()", 1),
+ (".address_targets()", 1),
+ ] {
+ if insert.matches(marker).count() != count {
+ return Err(format!(
+ "{relative} RequestIndex::insert must scan each admitted request target projection exactly once through `{marker}`"
+ ));
+ }
+ }
+ for marker in [
+ "request_id<current.as_str()",
+ "request_event.created_at_u64()>current.created_at",
+ "request_event.created_at_u64()==current.created_at",
+ "request_id<current.request_id.as_str()",
+ "evidence.unauthorized=true",
+ ] {
+ if !insert.contains(marker) {
+ return Err(format!(
+ "{relative} RequestIndex::insert is missing canonical evidence reduction `{marker}`"
+ ));
+ }
+ }
+ let insert_sha256 = sha256_hex(insert.as_bytes());
+ if insert_sha256 != RECONCILIATION_REQUEST_INDEX_INSERT_AST_SHA256 {
+ return Err(format!(
+ "{relative} RequestIndex::insert AST drifted: expected {RECONCILIATION_REQUEST_INDEX_INSERT_AST_SHA256}, found {insert_sha256}"
+ ));
+ }
+
+ let decision_function = exact_associated_method(file, "RequestIndex", "decision")?;
+ let decision = compact_tokens(decision_function);
+ for marker in [
+ "self.event_targets.get(event.id_str())",
+ "by_author.get(event.author_str())",
+ "self.address_targets.get(coordinate)",
+ "RadrootsNip09SuppressionReason::DeletionRequestImmune",
+ "RadrootsNip09SuppressionReason::NoAuthorizedReference",
+ "RadrootsNip09SuppressionReason::RequestAuthorMismatch",
+ "RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget",
+ "RadrootsNip09SuppressionReason::EventIdReference",
+ "RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff",
+ "RadrootsNip09SuppressionReason::EventIdAndAddressReference",
+ ] {
+ if !decision.contains(marker) {
+ return Err(format!(
+ "{relative} RequestIndex::decision is missing direct evidence authority `{marker}`"
+ ));
+ }
+ }
+ for forbidden in [
+ "matching(",
+ "evaluate_nip09_suppression",
+ ".projection()",
+ ".event_targets()",
+ ".address_targets()",
+ ".iter()",
+ ".into_iter()",
+ ] {
+ if decision.contains(forbidden) {
+ return Err(format!(
+ "{relative} RequestIndex::decision must not iterate or rescan through `{forbidden}`"
+ ));
+ }
+ }
+ let decision_sha256 = sha256_hex(decision.as_bytes());
+ if decision_sha256 != RECONCILIATION_REQUEST_INDEX_DECISION_AST_SHA256 {
+ return Err(format!(
+ "{relative} RequestIndex::decision AST drifted: expected {RECONCILIATION_REQUEST_INDEX_DECISION_AST_SHA256}, found {decision_sha256}"
+ ));
+ }
+
+ let affected = compact_tokens(exact_free_function(
+ file,
+ "request_affected_addressable_coordinates",
+ )?);
+ for marker in [
+ "for target in request.projection().event_targets()",
+ "event_by_id.get(target.event_id().as_str())",
+ "winners.get(coordinate)",
+ "for target in request.projection().address_targets()",
+ "winners.contains_key(&coordinate)",
+ ] {
+ let marker = marker.replace(' ', "");
+ if !affected.contains(&marker) {
+ return Err(format!(
+ "{relative} affected-coordinate reducer is missing `{marker}`"
+ ));
+ }
+ }
+ let affected_sha256 = sha256_hex(affected.as_bytes());
+ if affected_sha256 != RECONCILIATION_AFFECTED_COORDINATES_AST_SHA256 {
+ return Err(format!(
+ "{relative} affected-coordinate reducer AST drifted: expected {RECONCILIATION_AFFECTED_COORDINATES_AST_SHA256}, found {affected_sha256}"
+ ));
+ }
+
+ let desired = compact_tokens(exact_free_function(file, "desired_addressable_states")?);
+ let history = compact_tokens(exact_free_function(file, "expected_transition_history")?);
+ let state = compact_tokens(exact_free_function(file, "addressable_state_for_event")?);
+ for (function, source, markers) in [
+ (
+ "desired_addressable_states",
+ desired.as_str(),
+ &["RequestIndex::new(requests)", "&request_index"][..],
+ ),
+ (
+ "expected_transition_history",
+ history.as_str(),
+ &[
+ "letmutrequest_index=RequestIndex::new(&requests)",
+ "request_affected_addressable_coordinates(",
+ "request_index.insert(&request)",
+ "&request_index",
+ ][..],
+ ),
+ (
+ "addressable_state_for_event",
+ state.as_str(),
+ &["request_index.decision(event.verified_event.event())?"][..],
+ ),
+ ] {
+ for marker in markers {
+ if !source.contains(marker) {
+ return Err(format!(
+ "{relative}::{function} is missing direct indexed authority `{marker}`"
+ ));
+ }
+ }
+ for forbidden in [
+ "matching(",
+ "request_references_event",
+ "evaluate_nip09_suppression",
+ ] {
+ if source.contains(forbidden) {
+ return Err(format!(
+ "{relative}::{function} contains projection-rescanning authority `{forbidden}`"
+ ));
+ }
+ }
+ }
+ if history.matches("RequestIndex::new(&requests)").count() != 1 {
+ return Err(format!(
+ "{relative}::expected_transition_history must build its request index exactly once"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_visibility_oracle_index_authority(
+ file: &syn::File,
+ relative: &str,
+) -> Result<(), String> {
+ let expected_visibility = compact_tokens(exact_free_function(file, "expected_visibility")?);
+ if expected_visibility
+ .matches("request_index.decision(envelope)")
+ .count()
+ != 1
+ {
+ return Err(format!(
+ "{relative} expected visibility must make exactly one direct indexed suppression decision per admitted event"
+ ));
+ }
+ for forbidden in [
+ "matching(",
+ "evaluate_nip09_suppression",
+ ".projection()",
+ ".event_targets()",
+ ".address_targets()",
+ ] {
+ if expected_visibility.contains(forbidden) {
+ return Err(format!(
+ "{relative} expected visibility must not use projection-rescanning authority `{forbidden}`"
+ ));
+ }
+ }
+ let expected_visibility_sha256 = sha256_hex(expected_visibility.as_bytes());
+ if expected_visibility_sha256 != VISIBILITY_ORACLE_EXPECTED_VISIBILITY_AST_SHA256 {
+ return Err(format!(
+ "{relative} expected_visibility AST drifted: expected {VISIBILITY_ORACLE_EXPECTED_VISIBILITY_AST_SHA256}, found {expected_visibility_sha256}"
+ ));
+ }
+
+ let request_index = exact_impl(file, "OracleRequestIndexV1<'a>")?;
+ let methods = request_index
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ syn::ImplItem::Fn(function) => Some(function.sig.ident.to_string()),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ if methods != ["new", "decision"] {
+ return Err(format!(
+ "{relative} OracleRequestIndexV1 method inventory must be exactly [new, decision]; found {methods:?}"
+ ));
+ }
+ let constructor = compact_tokens(exact_associated_method(
+ file,
+ "OracleRequestIndexV1<'a>",
+ "new",
+ )?);
+ for (marker, count) in [
+ (".projection()", 2),
+ (".event_targets()", 1),
+ (".address_targets()", 1),
+ ] {
+ if constructor.matches(marker).count() != count {
+ return Err(format!(
+ "{relative} OracleRequestIndexV1::new must contain exactly {count} indexed construction use(s) of `{marker}`"
+ ));
+ }
+ }
+
+ let decision_function = exact_associated_method(file, "OracleRequestIndexV1<'a>", "decision")?;
+ let decision = compact_tokens(decision_function);
+ for marker in [
+ "self.event_targets.get(event.id_str())",
+ "by_author.get(event.author_str())",
+ "self.address_targets.get(coordinate)",
+ "self.requests[index].event()",
+ "RadrootsNip09SuppressionReason::DeletionRequestImmune",
+ "RadrootsNip09SuppressionReason::NoAuthorizedReference",
+ "RadrootsNip09SuppressionReason::RequestAuthorMismatch",
+ "RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget",
+ "RadrootsNip09SuppressionReason::EventIdReference",
+ "RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff",
+ "RadrootsNip09SuppressionReason::EventIdAndAddressReference",
+ "event_reference_request_id",
+ "address_reference_request_id",
+ "address_reference_cutoff",
+ ] {
+ if !decision.contains(marker) {
+ return Err(format!(
+ "{relative} direct indexed suppression decision is missing `{marker}`"
+ ));
+ }
+ }
+ for forbidden in [
+ "matching(",
+ "evaluate_nip09_suppression",
+ ".projection()",
+ ".event_targets()",
+ ".address_targets()",
+ ".iter()",
+ ".into_iter()",
+ ] {
+ if decision.contains(forbidden) {
+ return Err(format!(
+ "{relative} direct indexed suppression decision must not iterate or rescan through `{forbidden}`"
+ ));
+ }
+ }
+ #[derive(Default)]
+ struct IterationAudit {
+ for_loops: usize,
+ while_loops: usize,
+ loops: usize,
+ }
+ impl<'ast> syn::visit::Visit<'ast> for IterationAudit {
+ fn visit_expr_for_loop(&mut self, expression: &'ast syn::ExprForLoop) {
+ self.for_loops += 1;
+ syn::visit::visit_expr_for_loop(self, expression);
+ }
+
+ fn visit_expr_while(&mut self, expression: &'ast syn::ExprWhile) {
+ self.while_loops += 1;
+ syn::visit::visit_expr_while(self, expression);
+ }
+
+ fn visit_expr_loop(&mut self, expression: &'ast syn::ExprLoop) {
+ self.loops += 1;
+ syn::visit::visit_expr_loop(self, expression);
+ }
+ }
+ use syn::visit::Visit;
+ let mut iteration_audit = IterationAudit::default();
+ iteration_audit.visit_block(&decision_function.block);
+ if iteration_audit.for_loops != 0
+ || iteration_audit.while_loops != 0
+ || iteration_audit.loops != 0
+ {
+ return Err(format!(
+ "{relative} direct indexed suppression decision must not contain loops"
+ ));
+ }
+ let decision_sha256 = sha256_hex(decision.as_bytes());
+ if decision_sha256 != VISIBILITY_ORACLE_DECISION_AST_SHA256 {
+ return Err(format!(
+ "{relative} OracleRequestIndexV1::decision AST drifted: expected {VISIBILITY_ORACLE_DECISION_AST_SHA256}, found {decision_sha256}"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_public_entry_point_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ validate_public_method_signatures(file)?;
+ let expected_methods = [
+ (
+ "rebuild_from_raw_v1",
+ r#"{
+ crate::nip09::reconciliation_v1::rebuild_from_raw_v1_on_pool(&self.pool).await
+ }"#,
+ ),
+ (
+ "repair_file_from_raw_v1",
+ r#"{
+ let canonical_path = canonical_raw_source_repair_main_path_v1(path.as_ref())?;
+ let options = SqliteConnectOptions::new()
+ .filename(&canonical_path)
+ .create_if_missing(false);
+ let pool = SqlitePoolOptions::new()
+ .max_connections(1)
+ .connect_with(options)
+ .await?;
+ pool.set_connect_options(raw_source_repair_connect_options_v1(&canonical_path));
+ let mut connection = pool.acquire().await?;
+ prepare_raw_source_repair_connection_v1(&mut connection, &canonical_path).await?;
+ let transaction = connection.begin_with("BEGIN IMMEDIATE").await?;
+ if let Err(primary) =
+ validate_raw_source_repair_canonical_lock_domain_v1(&canonical_path).await
+ {
+ return preserve_raw_source_repair_probe_failure(primary, transaction.rollback().await);
+ }
+ let report =
+ crate::nip09::reconciliation_v1::rebuild_from_raw_v1_in_existing_transaction(
+ transaction,
+ )
+ .await?;
+ drop(connection);
+ Ok((Self { pool }, report))
+ }"#,
+ ),
+ ];
+ for (method, expected_body) in expected_methods {
+ let actual = exact_associated_method(file, "RadrootsEventStore", method)?;
+ let expected = syn::parse_str::<syn::Block>(expected_body)
+ .map_err(|error| format!("parse governed {method} body: {error}"))?;
+ if compact_tokens(&actual.block) != compact_tokens(&expected) {
+ return Err(format!(
+ "{relative}::RadrootsEventStore::{method} must retain its exact governed rebuild/cold-repair call path; expected `{}`, found `{}`",
+ compact_tokens(&expected),
+ compact_tokens(&actual.block),
+ ));
+ }
+ }
+
+ let full = compact_tokens(file);
+ for forbidden in [
+ "repair_pool_from_raw_v1",
+ "RADROOTS_EVENT_STORE_RAW_SOURCE_REPAIR_POOL_CONNECTION_LIMIT_V1",
+ "RawSourceRepairPoolConnectionLimitExceeded",
+ "RawSourceRepairPoolDatabaseIdentityMismatch",
+ "RawSourceRepairRequiresFileBackedDatabase",
+ "PoolTempSchemaPolicy::RawSourceRepairV1",
+ ] {
+ if full.contains(forbidden) {
+ return Err(format!(
+ "{relative} file-only cold repair must not retain obsolete authority `{forbidden}`"
+ ));
+ }
+ }
+
+ let expected_functions = [
+ (
+ "prepare_raw_source_repair_connection_v1",
+ "validated cold-repair connection preflight",
+ r#"async fn prepare_raw_source_repair_connection_v1(
+ connection: &mut SqliteConnection,
+ canonical_path: &Path,
+ ) -> Result<(), RadrootsEventStoreError> {
+ let main_filename = main_database_filename(connection).await?;
+ let actual = canonical_raw_source_repair_main_path_v1(Path::new(&main_filename))?;
+ if actual != canonical_path {
+ return Err(
+ RadrootsEventStoreError::RawSourceRepairDatabaseIdentityMismatch {
+ expected: canonical_path.display().to_string(),
+ actual: actual.display().to_string(),
+ },
+ );
+ }
+ validate_main_database_encoding(connection).await?;
+ crate::schema::validate_exact_managed_v4_for_raw_source_rebuild_v1(connection)
+ .await?;
+ validate_file_journal_mode_is_wal(connection).await?;
+ sqlx::query("PRAGMA foreign_keys = ON")
+ .execute(&mut *connection)
+ .await?;
+ sqlx::query("PRAGMA busy_timeout = 5000")
+ .execute(&mut *connection)
+ .await?;
+ Ok(())
+ }"#,
+ ),
+ (
+ "raw_source_repair_connect_options_v1",
+ "sealed future cold-repair connection options",
+ r#"fn raw_source_repair_connect_options_v1(canonical_path: &Path) -> SqliteConnectOptions {
+ SqliteConnectOptions::new()
+ .filename(canonical_path)
+ .create_if_missing(false)
+ .journal_mode(SqliteJournalMode::Wal)
+ .foreign_keys(true)
+ .busy_timeout(Duration::from_millis(5_000))
+ }"#,
+ ),
+ (
+ "validate_raw_source_repair_canonical_lock_domain_v1",
+ "canonical-path SQLite writer-lock-domain probe",
+ r#"async fn validate_raw_source_repair_canonical_lock_domain_v1(
+ canonical_path: &Path,
+ ) -> Result<(), RadrootsEventStoreError> {
+ let mut candidate = SqliteConnection::connect_with(
+ &SqliteConnectOptions::new()
+ .filename(canonical_path)
+ .create_if_missing(false)
+ .foreign_keys(true)
+ .busy_timeout(Duration::ZERO),
+ )
+ .await?;
+ let candidate_filename = main_database_filename(&mut candidate).await?;
+ let candidate_path =
+ canonical_raw_source_repair_main_path_v1(Path::new(&candidate_filename))?;
+ if candidate_path != canonical_path {
+ return Err(
+ RadrootsEventStoreError::RawSourceRepairDatabaseIdentityMismatch {
+ expected: canonical_path.display().to_string(),
+ actual: candidate_path.display().to_string(),
+ },
+ );
+ }
+ validate_main_database_encoding(&mut candidate).await?;
+ crate::schema::validate_exact_managed_v4_for_raw_source_rebuild_v1(&mut candidate)
+ .await?;
+ validate_file_journal_mode_is_wal(&mut candidate).await?;
+
+ let mut probe = candidate.begin().await?;
+ let write = sqlx::query(
+ "UPDATE main.radroots_event_store_write_lock SET lock_version = lock_version WHERE singleton = 1",
+ )
+ .execute(&mut *probe)
+ .await;
+ let rollback = probe.rollback().await;
+ match write {
+ Ok(_) => preserve_raw_source_repair_probe_failure(
+ RadrootsEventStoreError::RawSourceRepairCanonicalPathLockDomainMismatch {
+ canonical_path: canonical_path.display().to_string(),
+ },
+ rollback,
+ ),
+ Err(error) => {
+ if sqlite_error_is_busy_or_locked(&error) {
+ rollback?;
+ Ok(())
+ } else {
+ preserve_raw_source_repair_probe_failure(error.into(), rollback)
+ }
+ }
+ }
+ }"#,
+ ),
+ (
+ "preserve_raw_source_repair_probe_failure",
+ "cold-repair lock-probe rollback error preservation",
+ r#"fn preserve_raw_source_repair_probe_failure<T>(
+ primary: RadrootsEventStoreError,
+ rollback: Result<(), sqlx::Error>,
+ ) -> Result<T, RadrootsEventStoreError> {
+ match rollback {
+ Ok(()) => Err(primary),
+ Err(rollback) => Err(
+ RadrootsEventStoreError::RawSourceRebuildTransactionRollbackFailed {
+ primary: Box::new(primary),
+ rollback,
+ },
+ ),
+ }
+ }"#,
+ ),
+ (
+ "canonical_raw_source_repair_main_path_v1",
+ "existing canonical cold-repair file identity",
+ r#"fn canonical_raw_source_repair_main_path_v1(
+ path: &Path,
+ ) -> Result<PathBuf, RadrootsEventStoreError> {
+ let filename = path.display().to_string();
+ std::fs::canonicalize(path).map_err(|source| {
+ RadrootsEventStoreError::RawSourceRepairMainDatabaseCanonicalizationFailed {
+ filename,
+ source,
+ }
+ })
+ }"#,
+ ),
+ (
+ "sqlite_error_is_busy_or_locked",
+ "SQLite writer-lock error classification",
+ r#"fn sqlite_error_is_busy_or_locked(error: &sqlx::Error) -> bool {
+ let sqlx::Error::Database(error) = error else {
+ return false;
+ };
+ error
+ .code()
+ .and_then(|code| code.parse::<i32>().ok())
+ .is_some_and(|code| code & 0xff == 5 || code & 0xff == 6)
+ }"#,
+ ),
+ ];
+ for (function, authority, expected_source) in expected_functions {
+ let actual = exact_free_function(file, function)?;
+ let expected = syn::parse_str::<syn::ItemFn>(expected_source)
+ .map_err(|error| format!("parse governed {function}: {error}"))?;
+ if compact_tokens(actual) != compact_tokens(&expected) {
+ return Err(format!(
+ "{relative}::{function} must retain its exact {authority}; expected `{}`, found `{}`",
+ compact_tokens(&expected),
+ compact_tokens(actual),
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_streaming_dependency_authority(workspace_root: &Path) -> Result<(), String> {
+ let relative = "crates/event_store/Cargo.toml";
+ let bytes = read_regular_file(workspace_root, relative)?;
+ let source = std::str::from_utf8(&bytes)
+ .map_err(|error| format!("{relative} must be UTF-8 TOML: {error}"))?;
+ let manifest: toml::Value =
+ toml::from_str(source).map_err(|error| format!("parse {relative}: {error}"))?;
+ let sqlite = manifest
+ .get("features")
+ .and_then(|features| features.get("sqlite"))
+ .and_then(toml::Value::as_array)
+ .ok_or_else(|| format!("{relative} must define the sqlite feature array"))?;
+ if !sqlite
+ .iter()
+ .any(|feature| feature.as_str() == Some("dep:futures"))
+ {
+ return Err(format!(
+ "{relative} sqlite feature must enable the optional futures streaming dependency"
+ ));
+ }
+ let futures = manifest
+ .get("dependencies")
+ .and_then(|dependencies| dependencies.get("futures"))
+ .and_then(toml::Value::as_table)
+ .ok_or_else(|| format!("{relative} must define futures as a dependency table"))?;
+ if futures.get("workspace").and_then(toml::Value::as_bool) != Some(true)
+ || futures.get("optional").and_then(toml::Value::as_bool) != Some(true)
+ {
+ return Err(format!(
+ "{relative} futures dependency must remain workspace-governed and optional"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_coordinator_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ let serialized = compact_tokens(exact_free_function(
+ file,
+ "rebuild_from_raw_v1_on_pool_inner",
+ )?);
+ require_ordered_markers(
+ relative,
+ "serialized rebuild transaction",
+ &serialized,
+ &[
+ "begin_with(\"BEGINIMMEDIATE\").await?",
+ "rebuild_from_raw_v1_in_transaction_inner(",
+ "finish_raw_source_rebuild_transaction(transaction,result).await",
+ ],
+ )?;
+
+ let existing_transaction = compact_tokens(exact_free_function(
+ file,
+ "rebuild_from_raw_v1_in_existing_transaction",
+ )?);
+ require_ordered_markers(
+ relative,
+ "validated existing rebuild transaction",
+ &existing_transaction,
+ &[
+ "rebuild_from_raw_v1_in_transaction_inner(",
+ "&OsSourceGenerationProvider",
+ "finish_raw_source_rebuild_transaction(transaction,result).await",
+ ],
+ )?;
+
+ let coordinator = compact_tokens(exact_free_function(
+ file,
+ "rebuild_from_raw_v1_in_transaction_inner",
+ )?);
+ require_ordered_markers(
+ relative,
+ "raw-source rebuild coordinator",
+ &coordinator,
+ &[
+ "validate_exact_managed_v4_for_raw_source_rebuild_v1(connection).await?",
+ "preflight_caller_owned_schema_dependencies_v1(connection,caller_schema_limits).await?",
+ "validate_rebuild_marker_absent(connection).await?",
+ "validate_source_capacity_authority_full_v1(connection).await?",
+ "preflight_source_generation_append_v1(connection).await?",
+ "load_reconciliation_snapshot(",
+ "transition_high_water_v1(connection).await?",
+ "validate_source_lineage_for_rebuild_v1(connection,&snapshot.events,transition_floor_seq)",
+ "immutable_raw_digest_v1(connection).await?",
+ "generation_provider.fill_generation(&mutgeneration_bytes)?",
+ "open_source_rebuild_marker(connection,&plan).await?",
+ "RawSourceRebuildFailpointV1::AfterMarkerOpen",
+ "append_source_generation(connection,&plan).await?",
+ "rotate_source_state(connection,&plan).await?",
+ "RawSourceRebuildFailpointV1::AfterGenerationRotation",
+ "prepare_transition_sqlite_sequence_v1(connection,transition_floor_seq).await?",
+ "reconcile_raw_events(connection,&snapshot.events).await?",
+ "persist_event_coordinate_facts(connection,generation,&snapshot.events).await?",
+ "rebuild_raw_heads(connection,&snapshot.events).await?",
+ "persist_nip09_facts(connection,generation,&snapshot.events).await?",
+ "synchronize_addressable_heads(",
+ "update_source_authority(",
+ "transition_high_water_v1(connection).await?",
+ "validate_transition_sqlite_sequence_v1(connection,transition_sequence_rowid,replay_transition_high_water,).await?",
+ "validate_raw_source_rebuild_core_with_events_v1(connection,generation,&snapshot.events)",
+ "RawSourceRebuildFailpointV1::AfterCoreReplay",
+ "load_derived_visibility_rows_v1(connection,generation).await?",
+ "audit_current_visibility_from_raw_v1(&snapshot.events,derived_visibility).await?",
+ "RawSourceRebuildFailpointV1::AfterVisibilityAudit",
+ "bind_source_capacity_to_generation_v1(connection,generation)",
+ "reset_and_replay_food_availability_from_raw_v1(connection,generation).await?",
+ "RawSourceRebuildFailpointV1::AfterFoodResetAndReplay",
+ "validate_food_availability_projection_hook_v1(connection).await?",
+ "RawSourceRebuildFailpointV1::AfterFoodAudit",
+ "immutable_raw_digest_v1(connection).await?",
+ "final_raw_digest!=immutable_raw_digest",
+ "close_source_rebuild_marker(connection,marker).await?",
+ "RawSourceRebuildFailpointV1::AfterMarkerClose",
+ "validate_active_hook_state_fast(connection).await?",
+ "validate_source_capacity_authority_fast_v1(connection).await?",
+ "validate_food_availability_projection_hook_state_fast_v1(connection).await?",
+ "validate_scoped_integrity_v1(connection).await?",
+ "active_product_state_digest_v1(connection,generation).await?",
+ "RadrootsEventStoreRawSourceRebuildReportV1",
+ ],
+ )?;
+ if coordinator
+ .matches("preflight_caller_owned_schema_dependencies_v1(")
+ .count()
+ != 1
+ {
+ return Err(format!(
+ "{relative} rebuild coordinator must run the caller-schema dependency preflight exactly once"
+ ));
+ }
+ if coordinator
+ .matches("inject_raw_source_rebuild_failpoint_v1(")
+ .count()
+ != REBUILD_FAILPOINTS.len()
+ {
+ return Err(format!(
+ "{relative} rebuild coordinator must inject exactly one failpoint for each governed stage"
+ ));
+ }
+ for variant in REBUILD_FAILPOINTS.iter().map(|failpoint| failpoint.variant) {
+ if coordinator
+ .matches(&format!("RawSourceRebuildFailpointV1::{variant}"))
+ .count()
+ != 1
+ {
+ return Err(format!(
+ "{relative} rebuild coordinator must inject `{variant}` exactly once"
+ ));
+ }
+ }
+ if coordinator.contains("projection_cursor") {
+ return Err(
+ "raw-source rebuild coordinator must not enumerate or mutate generic projection cursors"
+ .to_owned(),
+ );
+ }
+
+ let finish = compact_tokens(exact_free_function(
+ file,
+ "finish_raw_source_rebuild_transaction",
+ )?);
+ for marker in [
+ "transaction.commit().await?",
+ "transaction.rollback().await",
+ "preserve_raw_source_rebuild_primary_failure(primary,rollback)",
+ ] {
+ if !finish.contains(marker) {
+ return Err(format!(
+ "{relative} transaction finalizer is missing `{marker}`"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_failpoint_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ let mut actual_enum = exact_enum(file, "RawSourceRebuildFailpointV1")?.clone();
+ strip_doc_attributes(&mut actual_enum.attrs);
+ let variants = REBUILD_FAILPOINTS
+ .iter()
+ .map(|failpoint| format!("{},", failpoint.variant))
+ .collect::<Vec<_>>()
+ .join("\n");
+ let expected_enum = syn::parse_str::<syn::ItemEnum>(&format!(
+ r#"
+ #[cfg(test)]
+ #[allow(clippy::enum_variant_names)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ pub(crate) enum RawSourceRebuildFailpointV1 {{
+ {variants}
+ }}
+ "#,
+ ))
+ .map_err(|error| format!("parse governed failpoint enum: {error}"))?;
+ if compact_tokens(&actual_enum) != compact_tokens(&expected_enum) {
+ return Err(format!(
+ "{relative} must retain the exact governed test-only rebuild failpoint enum"
+ ));
+ }
+
+ let actual_impl = exact_impl(file, "RawSourceRebuildFailpointV1")?;
+ let match_arms = REBUILD_FAILPOINTS
+ .iter()
+ .map(|failpoint| format!("Self::{} => {:?},", failpoint.variant, failpoint.id))
+ .collect::<Vec<_>>()
+ .join("\n");
+ let expected_impl = syn::parse_str::<syn::ItemImpl>(&format!(
+ r#"
+ #[cfg(test)]
+ impl RawSourceRebuildFailpointV1 {{
+ const fn as_str(self) -> &'static str {{
+ match self {{
+ {match_arms}
+ }}
+ }}
+ }}
+ "#,
+ ))
+ .map_err(|error| format!("parse governed failpoint mapping: {error}"))?;
+ if compact_tokens(actual_impl) != compact_tokens(&expected_impl) {
+ return Err(format!(
+ "{relative} must retain the exact failpoint-to-stage mapping"
+ ));
+ }
+
+ let mut actual_injector =
+ exact_free_function(file, "inject_raw_source_rebuild_failpoint_v1")?.clone();
+ strip_doc_attributes(&mut actual_injector.attrs);
+ let expected_injector = syn::parse_str::<syn::ItemFn>(
+ r#"
+ #[cfg(test)]
+ fn inject_raw_source_rebuild_failpoint_v1(
+ selected: Option<RawSourceRebuildFailpointV1>,
+ stage: RawSourceRebuildFailpointV1,
+ ) -> Result<(), RadrootsEventStoreError> {
+ if selected == Some(stage) {
+ return rebuild_drift(
+ RadrootsEventStoreRawSourceRebuildDriftV1::RebuildPostcondition,
+ format!("injected raw-source rebuild failure at {}", stage.as_str()),
+ );
+ }
+ Ok(())
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse governed failpoint injector: {error}"))?;
+ if compact_tokens(&actual_injector) != compact_tokens(&expected_injector) {
+ return Err(format!(
+ "{relative} must retain the exact rollback failpoint injector"
+ ));
+ }
+ validate_failpoint_injection_authority(file, relative)
+}
+
+fn validate_failpoint_injection_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ #[derive(Default)]
+ struct InjectionAudit {
+ calls: Vec<String>,
+ propagated_calls: Vec<String>,
+ }
+
+ impl<'ast> syn::visit::Visit<'ast> for InjectionAudit {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ if compact_tokens(call.func.as_ref()) == "inject_raw_source_rebuild_failpoint_v1" {
+ self.calls.push(compact_tokens(call));
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+
+ fn visit_expr_try(&mut self, expression: &'ast syn::ExprTry) {
+ if let syn::Expr::Call(call) = expression.expr.as_ref()
+ && compact_tokens(call.func.as_ref()) == "inject_raw_source_rebuild_failpoint_v1"
+ {
+ self.propagated_calls.push(compact_tokens(call));
+ }
+ syn::visit::visit_expr_try(self, expression);
+ }
+ }
+
+ let expected = REBUILD_FAILPOINTS
+ .iter()
+ .map(|failpoint| {
+ format!(
+ "inject_raw_source_rebuild_failpoint_v1(_failpoint,RawSourceRebuildFailpointV1::{},)",
+ failpoint.variant
+ )
+ })
+ .collect::<Vec<_>>();
+ use syn::visit::Visit;
+ let coordinator = exact_free_function(file, "rebuild_from_raw_v1_in_transaction_inner")?;
+ let mut audit = InjectionAudit::default();
+ audit.visit_block(&coordinator.block);
+ if audit.calls != expected || audit.propagated_calls != expected {
+ return Err(format!(
+ "{relative} rebuild coordinator must contain exactly one ordered, error-propagating injection call for every governed failpoint: expected {expected:?}, calls {:?}, propagated {:?}",
+ audit.calls, audit.propagated_calls,
+ ));
+ }
+ Ok(())
+}
+
+fn validate_failpoint_test_array_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ struct FailpointArrayAudit {
+ arrays: Vec<Vec<String>>,
+ }
+
+ impl<'ast> syn::visit::Visit<'ast> for FailpointArrayAudit {
+ fn visit_expr_for_loop(&mut self, expression: &'ast syn::ExprForLoop) {
+ if compact_tokens(expression.pat.as_ref()) == "(index,failpoint)"
+ && let syn::Expr::MethodCall(enumerate) = expression.expr.as_ref()
+ && enumerate.method == "enumerate"
+ && enumerate.args.is_empty()
+ && let syn::Expr::MethodCall(into_iter) = enumerate.receiver.as_ref()
+ && into_iter.method == "into_iter"
+ && into_iter.args.is_empty()
+ && let syn::Expr::Array(array) = into_iter.receiver.as_ref()
+ {
+ self.arrays
+ .push(array.elems.iter().map(compact_tokens).collect());
+ }
+ syn::visit::visit_expr_for_loop(self, expression);
+ }
+ }
+
+ let expected = REBUILD_FAILPOINTS
+ .iter()
+ .map(|failpoint| format!("RawSourceRebuildFailpointV1::{}", failpoint.variant))
+ .collect::<Vec<_>>();
+ use syn::visit::Visit;
+ let test = exact_top_level_function(file, REBUILD_FAILPOINT_TEST)?;
+ let mut audit = FailpointArrayAudit { arrays: Vec::new() };
+ audit.visit_block(&test.block);
+ let [actual] = audit.arrays.as_slice() else {
+ return Err(format!(
+ "{relative}::{REBUILD_FAILPOINT_TEST} must contain exactly one governed failpoint array loop"
+ ));
+ };
+ if actual != &expected {
+ return Err(format!(
+ "{relative}::{REBUILD_FAILPOINT_TEST} must enumerate the exact governed failpoint array once and in order"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_bounded_repair_schema_authority(
+ file: &syn::File,
+ relative: &str,
+) -> Result<(), String> {
+ let authority = compact_tokens(exact_free_function(
+ file,
+ "repair_governed_catalog_authority_v1",
+ )?);
+ for marker in [
+ "owned_object_names.iter().copied()",
+ "names.insert(EVENT_STORE_LEDGER_NAME)",
+ "canonical_row_count.checked_add(1)",
+ ] {
+ if !authority.contains(marker) {
+ return Err(format!(
+ "{relative} repair catalog bound authority is missing `{marker}`"
+ ));
+ }
+ }
+
+ let catalog = compact_tokens(exact_free_function(file, "read_repair_catalog_bounded_v1")?);
+ for marker in [
+ "repair_governed_catalog_authority_v1(registry)?",
+ "json_each(?)",
+ "FROMmain.sqlite_schema",
+ "lower(substr(name,1,7))!='sqlite_'",
+ "nameCOLLATENOCASEIN(SELECTnameFROMgoverned)",
+ "tbl_nameCOLLATENOCASEIN(SELECTnameFROMgoverned)",
+ "EVENT_STORE_RESERVED_PREFIX",
+ "LIMIT?",
+ ".bind(row_limit)",
+ ".fetch_all(&mut*connection)",
+ ] {
+ if !catalog.contains(marker) {
+ return Err(format!(
+ "{relative} bounded repair catalog reader is missing `{marker}`"
+ ));
+ }
+ }
+
+ let temp = compact_tokens(exact_free_function(
+ file,
+ "validate_repair_temp_schema_bounded_v1",
+ )?);
+ for marker in [
+ "repair_governed_catalog_authority_v1(registry)?",
+ "json_each(?)",
+ "FROMtemp.sqlite_schema",
+ "typeIN('trigger','view')",
+ "nameCOLLATENOCASEIN(SELECTnameFROMgoverned)",
+ "tbl_nameCOLLATENOCASEIN(SELECTnameFROMgoverned)",
+ "EVENT_STORE_RESERVED_PREFIX",
+ "LIMIT1",
+ ".fetch_optional(&mut*connection)",
+ "TemporarySchemaCollision",
+ ] {
+ if !temp.contains(marker) {
+ return Err(format!(
+ "{relative} bounded repair temp-collision probe is missing `{marker}`"
+ ));
+ }
+ }
+
+ let history = compact_tokens(exact_free_function(file, "read_repair_history_bounded_v1")?);
+ for marker in [
+ "i64::from(supported_current).checked_add(1)",
+ "FROMmain.radroots_event_store_schema_migrations",
+ "ORDERBYversion",
+ "LIMIT?",
+ ".bind(row_limit)",
+ ".fetch_all(&mut*connection)",
+ ] {
+ if !history.contains(marker) {
+ return Err(format!(
+ "{relative} bounded repair migration-history reader is missing `{marker}`"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_transition_sequence_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ if exact_string_const(file, "TRANSITION_SEQUENCE_NAME")?
+ != "radroots_event_store_addressable_head_transition"
+ {
+ return Err(format!(
+ "{relative} transition sqlite_sequence target identity drifted"
+ ));
+ }
+ let prepare = compact_tokens(exact_free_function(
+ file,
+ "prepare_transition_sqlite_sequence_v1",
+ )?);
+ require_ordered_markers(
+ relative,
+ "target-first sqlite_sequence preparation",
+ &prepare,
+ &[
+ "transition_max<0||transition_max==i64::MAX",
+ "SELECTrowid,name=?COLLATENOCASEFROMmain.sqlite_sequenceORDERBYrowidLIMIT1",
+ ".bind(TRANSITION_SEQUENCE_NAME)",
+ "None=>-1",
+ "Some((rowid,Some(1)))=>rowid",
+ "Some((i64::MIN,_))=>",
+ "Some((rowid,_))=>rowid-1",
+ "DELETEFROMmain.sqlite_sequenceWHEREnameCOLLATENOCASE=?",
+ "INSERTINTOmain.sqlite_sequence(rowid,name,seq)VALUES(?,?,?)",
+ ".bind(target_rowid)",
+ ".bind(TRANSITION_SEQUENCE_NAME)",
+ ".bind(transition_max)",
+ "validate_transition_sqlite_sequence_v1(connection,target_rowid,transition_max).await?",
+ "Ok(target_rowid)",
+ ],
+ )?;
+ if prepare
+ .matches("DELETEFROMmain.sqlite_sequenceWHEREnameCOLLATENOCASE=?")
+ .count()
+ != 1
+ {
+ return Err(format!(
+ "{relative} target aliases must be removed by exactly one shared sqlite_sequence scan"
+ ));
+ }
+
+ let validate = compact_tokens(exact_free_function(
+ file,
+ "validate_transition_sqlite_sequence_v1",
+ )?);
+ require_ordered_markers(
+ relative,
+ "target-first sqlite_sequence validation",
+ &validate,
+ &[
+ "SELECTname,seqFROMmain.sqlite_sequenceWHERErowid=?",
+ ".bind(target_rowid)",
+ "SELECTrowidFROMmain.sqlite_sequenceORDERBYrowidLIMIT1",
+ "first_rowid!=Some(target_rowid)",
+ "AddressableTransitionAuthority",
+ ],
+ )?;
+ let full_source = compact_tokens(file);
+ for forbidden in [
+ "normalize_transition_sqlite_sequence_v1",
+ "TRANSITION_SEQUENCE_RESERVED_ROWID_V1",
+ "unrelated_sqlite_sequence_snapshot_v1",
+ "validate_unrelated_sqlite_sequences_v1",
+ "quote(name)",
+ "nameISNULLORname!=?",
+ ] {
+ if full_source.contains(forbidden) {
+ return Err(format!(
+ "{relative} must not scan or promote unrelated sqlite_sequence rows through `{forbidden}`"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_caller_schema_dependency_authority(
+ file: &syn::File,
+ relative: &str,
+) -> Result<(), String> {
+ for name in [
+ "RAW_SOURCE_REBUILD_CALLER_MAIN_TABLE_COUNT_LIMIT_V1",
+ "RAW_SOURCE_REBUILD_CALLER_FOREIGN_KEY_ROW_COUNT_LIMIT_V1",
+ ] {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Const(item) if item.ident == name => Some(item),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [actual] = matches.as_slice() else {
+ return Err(format!(
+ "{relative} must define caller-schema limit `{name}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ let mut actual = (*actual).clone();
+ strip_doc_attributes(&mut actual.attrs);
+ let expected = syn::parse_str::<syn::ItemConst>(&format!("const {name}: u32 = 4_096;"))
+ .map_err(|error| format!("parse caller-schema limit `{name}`: {error}"))?;
+ if compact_tokens(&actual) != compact_tokens(&expected) {
+ return Err(format!(
+ "{relative} caller-schema limit `{name}` must remain exactly 4,096"
+ ));
+ }
+ }
+
+ let limits = exact_struct(file, "RawSourceRebuildCallerSchemaLimitsV1")?;
+ let expected_limits = syn::parse_str::<syn::ItemStruct>(
+ r#"
+ #[derive(Clone, Copy)]
+ struct RawSourceRebuildCallerSchemaLimitsV1 {
+ main_tables: u32,
+ foreign_key_rows: u32,
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse caller-schema limits model: {error}"))?;
+ if compact_tokens(limits) != compact_tokens(&expected_limits) {
+ return Err(format!(
+ "{relative} caller-schema limits model field or derive authority drifted"
+ ));
+ }
+ let limits_impl = exact_impl(file, "RawSourceRebuildCallerSchemaLimitsV1")?;
+ let expected_limits_impl = syn::parse_str::<syn::ItemImpl>(
+ r#"
+ impl RawSourceRebuildCallerSchemaLimitsV1 {
+ const fn production() -> Self {
+ Self {
+ main_tables: RAW_SOURCE_REBUILD_CALLER_MAIN_TABLE_COUNT_LIMIT_V1,
+ foreign_key_rows: RAW_SOURCE_REBUILD_CALLER_FOREIGN_KEY_ROW_COUNT_LIMIT_V1,
+ }
+ }
+ }
+ "#,
+ )
+ .map_err(|error| format!("parse caller-schema production limits authority: {error}"))?;
+ if compact_tokens(limits_impl) != compact_tokens(&expected_limits_impl) {
+ return Err(format!(
+ "{relative} caller-schema production limits must route through both governed constants"
+ ));
+ }
+
+ for (function, expected_source) in [
+ (
+ "governed_schema_names_json_v1",
+ r#"fn governed_schema_names_json_v1() -> Result<String, RadrootsEventStoreError> {
+ let mut names = EVENT_STORE_MIGRATIONS
+ .iter()
+ .flat_map(|migration| migration.owned_object_names.iter().copied())
+ .collect::<BTreeSet<_>>();
+ names.insert(EVENT_STORE_LEDGER_NAME);
+ Ok(serde_json::to_string(&names)?)
+ }"#,
+ ),
+ (
+ "caller_schema_count_v1",
+ r#"fn caller_schema_count_v1(count: i64) -> Result<u64, RadrootsEventStoreError> {
+ u64::try_from(count).map_err(|_| {
+ rebuild_state_error(
+ RadrootsEventStoreRawSourceRebuildDriftV1::ManagedSchemaAuthority,
+ "caller-owned schema inventory returned a negative row count",
+ )
+ })
+ }"#,
+ ),
+ ] {
+ let actual = exact_free_function(file, function)?;
+ let expected = syn::parse_str::<syn::ItemFn>(expected_source)
+ .map_err(|error| format!("parse caller-schema helper `{function}`: {error}"))?;
+ if compact_tokens(actual) != compact_tokens(&expected) {
+ return Err(format!(
+ "{relative}::{function} caller-schema authority drifted"
+ ));
+ }
+ }
+
+ let preflight_function =
+ exact_free_function(file, "preflight_caller_owned_schema_dependencies_v1")?;
+ let preflight = compact_tokens(preflight_function);
+ let query_literals = sqlx_query_family_literals(preflight_function);
+ if query_literals.len() != 3 {
+ return Err(format!(
+ "{relative} caller-schema preflight must contain exactly three literal sqlx queries; found {}",
+ query_literals.len()
+ ));
+ }
+ let query_authority = query_literals
+ .iter()
+ .map(|query| query.split_whitespace().collect::<String>())
+ .collect::<Vec<_>>()
+ .join("\0");
+ require_ordered_markers(
+ relative,
+ "bounded caller-schema dependency preflight",
+ &preflight,
+ &[
+ "governed_schema_names_json_v1()?",
+ "serde_json::to_string(RAW_SOURCE_REBUILD_MUTATED_PARENT_TABLES_V1)?",
+ "i64::from(limits.main_tables)+1",
+ "RawSourceRebuildCallerTableCapacityExceeded",
+ "i64::from(limits.foreign_key_rows)+1",
+ "RawSourceRebuildCallerForeignKeyCapacityExceeded",
+ "RawSourceRebuildCallerInboundForeignKeyUnsupported",
+ "Box::new(RadrootsEventStoreCallerInboundForeignKeyV1",
+ ],
+ )?;
+ require_ordered_markers(
+ relative,
+ "caller-schema dependency SQL",
+ &query_authority,
+ &[
+ "FROMmain.sqlite_schemaASchild",
+ "LIMIT?",
+ "main.pragma_foreign_key_list(child.name,'main')ASforeign_key",
+ "JOINrebuild_parentONforeign_key.\"table\"COLLATENOCASE=rebuild_parent.name",
+ "ORDERBYchild.nameCOLLATENOCASE,child.name,foreign_key.id,foreign_key.seq",
+ "LIMIT1",
+ ],
+ )?;
+ for (marker, expected_count) in [
+ ("FROMmain.sqlite_schemaASchild", 3),
+ (
+ "main.pragma_foreign_key_list(child.name,'main')ASforeign_key",
+ 2,
+ ),
+ ("LIMIT?", 2),
+ ("LIMIT1", 1),
+ ] {
+ if query_authority.matches(marker).count() != expected_count {
+ return Err(format!(
+ "{relative} caller-schema preflight must contain `{marker}` exactly {expected_count} time(s)"
+ ));
+ }
+ }
+ for forbidden in [
+ "temp.sqlite_schema",
+ "temp.pragma_foreign_key_list",
+ "foreign_key.on_delete=",
+ "foreign_key.on_update=",
+ ] {
+ if query_authority.contains(forbidden) {
+ return Err(format!(
+ "{relative} caller-schema preflight must not narrow or redirect dependency discovery through `{forbidden}`"
+ ));
+ }
+ }
+ let actual_sha256 = sha256_hex(preflight.as_bytes());
+ if actual_sha256 != CALLER_SCHEMA_PREFLIGHT_AST_SHA256 {
+ return Err(format!(
+ "{relative} caller-schema dependency preflight AST drifted: expected {CALLER_SCHEMA_PREFLIGHT_AST_SHA256}, found {actual_sha256}"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_scoped_integrity_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ let actual_tables = exact_string_slice_const(file, "REBUILD_OWNED_TABLES_V1")?;
+ let expected_tables = owned(SCOPED_INTEGRITY_TABLES);
+ if actual_tables != expected_tables {
+ return Err(format!(
+ "{relative} scoped integrity table inventory differs: expected {expected_tables:?}, found {actual_tables:?}"
+ ));
+ }
+ let actual_mutated_parents =
+ exact_string_slice_const(file, "RAW_SOURCE_REBUILD_MUTATED_PARENT_TABLES_V1")?;
+ let expected_mutated_parents = owned(CALLER_INBOUND_FOREIGN_KEY_PARENT_TABLES);
+ if actual_mutated_parents != expected_mutated_parents {
+ return Err(format!(
+ "{relative} rebuild-mutated parent inventory differs: expected {expected_mutated_parents:?}, found {actual_mutated_parents:?}"
+ ));
+ }
+ let integrity = compact_tokens(exact_free_function(file, "validate_scoped_integrity_v1")?);
+ for marker in [
+ "fortableinREBUILD_OWNED_TABLES_V1",
+ "PRAGMAmain.integrity_check('{table}')",
+ "sqlx::AssertSqlSafe(integrity_sql)",
+ "detail!=\"ok\"",
+ "PRAGMAmain.foreign_key_check('{table}')",
+ "sqlx::AssertSqlSafe(foreign_key_sql)",
+ ".fetch_optional(&mut*connection)",
+ "ForeignKeyViolation",
+ "radroots_event_store_food_availability_search_fts(radroots_event_store_food_availability_search_fts)VALUES('integrity-check')",
+ "Fts5IntegrityCheckFailed",
+ ] {
+ if !integrity.contains(marker) {
+ return Err(format!(
+ "{relative} scoped integrity authority is missing `{marker}`"
+ ));
+ }
+ }
+ if integrity.matches(".fetch_all(&mut*connection)").count() != 1
+ || integrity
+ .matches(".fetch_optional(&mut*connection)")
+ .count()
+ != 1
+ {
+ return Err(format!(
+ "{relative} scoped integrity must materialize only bounded integrity-check rows and fetch at most one foreign-key violation"
+ ));
+ }
+ for forbidden in [
+ "PRAGMAintegrity_check\"",
+ "PRAGMAmain.integrity_check\"",
+ "PRAGMAforeign_key_check\"",
+ "PRAGMAmain.foreign_key_check\"",
+ "quick_check",
+ ] {
+ if integrity.contains(forbidden) {
+ return Err(format!(
+ "{relative} scoped integrity authority contains forbidden global scan `{forbidden}`"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_digest_query_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ validate_one_digest_query_authority(
+ file,
+ relative,
+ "immutable_raw_digest_v1",
+ "IMMUTABLE_RAW_DIGEST_DOMAIN_V1",
+ RAW_DIGEST_QUERY_SPECS,
+ )?;
+ validate_one_digest_query_authority(
+ file,
+ relative,
+ "active_product_state_digest_v1",
+ "ACTIVE_PRODUCT_STATE_DIGEST_DOMAIN_V1",
+ PRODUCT_DIGEST_QUERY_SPECS,
+ )?;
+ validate_digest_framing_authority(file, relative)
+}
+
+fn validate_one_digest_query_authority(
+ file: &syn::File,
+ relative: &str,
+ function_name: &str,
+ domain_name: &str,
+ specs: &[DigestQuerySpec],
+) -> Result<(), String> {
+ let function = exact_free_function(file, function_name)?;
+ let compact = compact_tokens(function);
+ if !compact.contains(&format!("digest.update({domain_name})")) {
+ return Err(format!(
+ "{relative}::{function_name} does not begin from governed domain `{domain_name}`"
+ ));
+ }
+ let actual_queries = sqlx_query_literals(function);
+ let expected_queries = specs.iter().map(|spec| spec.sql).collect::<Vec<_>>();
+ if actual_queries != expected_queries {
+ return Err(format!(
+ "{relative}::{function_name} digest query inventory differs from the governed source/component queries"
+ ));
+ }
+ let actual_sections = digest_section_literals(function);
+ let expected_sections = specs.iter().map(|spec| spec.section).collect::<Vec<_>>();
+ if actual_sections != expected_sections {
+ return Err(format!(
+ "{relative}::{function_name} digest section order differs: expected {expected_sections:?}, found {actual_sections:?}"
+ ));
+ }
+ let expected_fields = specs
+ .iter()
+ .flat_map(|spec| {
+ spec.fields
+ .iter()
+ .copied()
+ .zip(expected_digest_field_framing(spec.section).iter().copied())
+ .map(|(name, framing)| (name.to_owned(), framing.to_owned()))
+ })
+ .collect::<Vec<_>>();
+ let actual_fields = digest_field_witnesses(function)?;
+ if actual_fields != expected_fields {
+ return Err(format!(
+ "{relative}::{function_name} typed digest field witness order differs from its governed queries: expected {expected_fields:?}, found {actual_fields:?}"
+ ));
+ }
+ validate_digest_streaming_authority(function, relative, function_name, specs)?;
+ Ok(())
+}
+
+fn validate_digest_streaming_authority(
+ function: &syn::ItemFn,
+ relative: &str,
+ function_name: &str,
+ specs: &[DigestQuerySpec],
+) -> Result<(), String> {
+ let compact = compact_tokens(function);
+ if compact.contains(".fetch_all(") {
+ return Err(format!(
+ "{relative}::{function_name} must stream digest rows and must not materialize them with fetch_all"
+ ));
+ }
+
+ let statements = &function.block.stmts;
+ let mut witnessed_queries = Vec::new();
+ for (index, statement) in statements.iter().enumerate() {
+ let syn::Stmt::Local(local) = statement else {
+ continue;
+ };
+ let Some(initializer) = &local.init else {
+ continue;
+ };
+ let query_literals = sqlx_query_literals_in_expr(&initializer.expr);
+ if query_literals.is_empty() {
+ continue;
+ }
+ let [query] = query_literals.as_slice() else {
+ return Err(format!(
+ "{relative}::{function_name} digest stream binding must contain exactly one SQL query"
+ ));
+ };
+ let syn::Pat::Ident(binding) = &local.pat else {
+ return Err(format!(
+ "{relative}::{function_name} digest query `{query}` must bind one named mutable stream"
+ ));
+ };
+ if binding.mutability.is_none() || binding.by_ref.is_some() || binding.subpat.is_some() {
+ return Err(format!(
+ "{relative}::{function_name} digest query `{query}` must bind one plain mutable stream"
+ ));
+ }
+ let stream = binding.ident.to_string();
+ let syn::Expr::MethodCall(fetch) = initializer.expr.as_ref() else {
+ return Err(format!(
+ "{relative}::{function_name} digest query `{query}` must terminate in fetch"
+ ));
+ };
+ if fetch.method != "fetch"
+ || fetch.args.len() != 1
+ || compact_tokens(fetch.args.first().expect("one fetch argument")) != "&mut*connection"
+ {
+ return Err(format!(
+ "{relative}::{function_name} digest query `{query}` must stream via fetch(&mut *connection)"
+ ));
+ }
+
+ let Some(syn::Stmt::Expr(syn::Expr::While(row_loop), _)) = statements.get(index + 1) else {
+ return Err(format!(
+ "{relative}::{function_name} digest stream `{stream}` must be consumed immediately by while let"
+ ));
+ };
+ let syn::Expr::Let(condition) = row_loop.cond.as_ref() else {
+ return Err(format!(
+ "{relative}::{function_name} digest stream `{stream}` must use while let Some(row)"
+ ));
+ };
+ if compact_tokens(&condition.pat) != "Some(row)"
+ || compact_tokens(&condition.expr) != format!("{stream}.try_next().await?")
+ {
+ return Err(format!(
+ "{relative}::{function_name} digest stream `{stream}` must terminate through try_next().await?"
+ ));
+ }
+
+ let top_level_row_starts = row_loop
+ .body
+ .stmts
+ .iter()
+ .enumerate()
+ .filter_map(|(index, statement)| {
+ is_digest_row_start_statement(statement).then_some(index)
+ })
+ .collect::<Vec<_>>();
+ struct RowStartCounter(usize);
+ impl<'ast> syn::visit::Visit<'ast> for RowStartCounter {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ if compact_tokens(call) == "digest_row_start(&mutdigest)" {
+ self.0 += 1;
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+ }
+ use syn::visit::Visit;
+ let mut row_start_calls = RowStartCounter(0);
+ row_start_calls.visit_block(&row_loop.body);
+ if top_level_row_starts != [0] || row_start_calls.0 != 1 {
+ return Err(format!(
+ "{relative}::{function_name} digest stream `{stream}` must begin every row with exactly one top-level digest_row_start marker"
+ ));
+ }
+
+ let Some(syn::Stmt::Expr(drop_expression, _)) = statements.get(index + 2) else {
+ return Err(format!(
+ "{relative}::{function_name} digest stream `{stream}` must be dropped before the next query"
+ ));
+ };
+ if compact_tokens(drop_expression) != format!("drop({stream})") {
+ return Err(format!(
+ "{relative}::{function_name} digest stream `{stream}` must be explicitly dropped after consumption"
+ ));
+ }
+ witnessed_queries.push(query.clone());
+ }
+
+ let expected_queries = specs.iter().map(|spec| spec.sql).collect::<Vec<_>>();
+ if witnessed_queries != expected_queries {
+ return Err(format!(
+ "{relative}::{function_name} must bind, consume, and drop exactly one streaming cursor for every governed digest query"
+ ));
+ }
+ Ok(())
+}
+
+fn is_digest_row_start_statement(statement: &syn::Stmt) -> bool {
+ let syn::Stmt::Expr(syn::Expr::Call(call), Some(_)) = statement else {
+ return false;
+ };
+ compact_tokens(call) == "digest_row_start(&mutdigest)"
+}
+
+fn validate_digest_framing_authority(file: &syn::File, relative: &str) -> Result<(), String> {
+ let expectations: &[(&str, &[&str])] = &[
+ (
+ "digest_section",
+ &["digest.update(b\"S\")", "digest_bytes(digest,b'N',name)"],
+ ),
+ ("digest_row_start", &["digest.update(b\"R\")"]),
+ (
+ "digest_i64",
+ &[
+ "digest.update(b\"I\")",
+ "digest.update(value.to_be_bytes())",
+ ],
+ ),
+ (
+ "digest_bytes",
+ &[
+ "u64::try_from(value.len())",
+ "digest.update([marker])",
+ "digest.update(length.to_be_bytes())",
+ "digest.update(value)",
+ ],
+ ),
+ ("digest_text", &["digest_bytes(digest,b'T',value)"]),
+ (
+ "digest_optional_text",
+ &[
+ "digest.update([b'O',1])",
+ "digest_text(digest,value.as_bytes())",
+ "digest.update([b'O',0])",
+ ],
+ ),
+ (
+ "digest_optional_i64",
+ &[
+ "digest.update([b'O',1])",
+ "digest_i64(digest,value)",
+ "digest.update([b'O',0])",
+ ],
+ ),
+ (
+ "digest_bool",
+ &["digest.update([b'B',ifvalue==0{0}else{1}])"],
+ ),
+ ("digest_blob_field", &["digest_bytes(digest,b'X',&value)"]),
+ ];
+ for (name, markers) in expectations {
+ let function = compact_tokens(exact_free_function(file, name)?);
+ require_ordered_markers(relative, name, &function, markers)?;
+ }
+ Ok(())
+}
+
+fn require_ordered_markers(
+ relative: &str,
+ authority: &str,
+ source: &str,
+ markers: &[&str],
+) -> Result<(), String> {
+ let mut offset = 0_usize;
+ for marker in markers {
+ let Some(found) = source[offset..].find(marker) else {
+ return Err(format!(
+ "{relative} {authority} is missing ordered authority witness `{marker}`"
+ ));
+ };
+ offset += found + marker.len();
+ }
+ Ok(())
+}
+
+fn validate_command_reachability(workspace_root: &Path) -> Result<(), String> {
+ let contract = rust_source(workspace_root, CONTRACT_COMMAND_SOURCE_RELATIVE)?;
+ let main = rust_source(workspace_root, XTASK_MAIN_SOURCE_RELATIVE)?;
+ let main = syn::parse_file(&main)
+ .map_err(|error| format!("parse {XTASK_MAIN_SOURCE_RELATIVE}: {error}"))?;
+ let aggregate = compact_tokens(exact_top_level_function(
+ &syn::parse_file(&contract)
+ .map_err(|error| format!("parse {CONTRACT_COMMAND_SOURCE_RELATIVE}: {error}"))?,
+ "validate_artifact_contracts",
+ )?);
+ for ordered in [
+ "validate_source_maintenance_manifest(workspace_root)?",
+ "validate_raw_source_rebuild_manifest(workspace_root)?",
+ "validate_knowledge_contract_manifest(workspace_root)",
+ ] {
+ if !aggregate.contains(ordered) {
+ return Err(format!(
+ "aggregate contract authority does not reach raw-source rebuild validation through `{ordered}`"
+ ));
+ }
+ }
+ let source_index = aggregate
+ .find("validate_source_maintenance_manifest(workspace_root)?")
+ .expect("checked above");
+ let rebuild_index = aggregate
+ .find("validate_raw_source_rebuild_manifest(workspace_root)?")
+ .expect("checked above");
+ let knowledge_index = aggregate
+ .find("validate_knowledge_contract_manifest(workspace_root)")
+ .expect("checked above");
+ if !(source_index < rebuild_index && rebuild_index < knowledge_index) {
+ return Err(
+ "aggregate contract authority must validate the immutable predecessor before raw-source rebuild and knowledge contracts"
+ .to_owned(),
+ );
+ }
+
+ for (function_name, expected_call) in [
+ (
+ "validate_contract",
+ "contract::validate_artifact_contracts(&root)",
+ ),
+ (
+ "release_preflight_at",
+ "contract::validate_artifact_contracts(root)",
+ ),
+ ] {
+ let function = exact_top_level_function(&main, function_name)?;
+ let calls = direct_call_tokens(function);
+ if calls.iter().filter(|call| *call == expected_call).count() != 1 {
+ return Err(format!(
+ "{XTASK_MAIN_SOURCE_RELATIVE}::{function_name} must directly reach `{expected_call}` exactly once"
+ ));
+ }
+ }
+
+ let run_contract = exact_top_level_match(&main, "run_contract")?;
+ let raw_arm = exact_match_arm(run_contract, "Some(\"raw-source-rebuild-manifest\")")?;
+ let expected_raw_arm = syn::parse_str::<syn::Expr>(
+ r#"match &args[1..] {
+ [] => contract::validate_raw_source_rebuild_manifest(&workspace_root()),
+ [flag] if flag == "--write" => {
+ contract::write_raw_source_rebuild_manifest(&workspace_root())
+ }
+ _ => Err(
+ "raw-source-rebuild-manifest accepts no arguments or exactly --write".to_string(),
+ ),
+ }"#,
+ )
+ .map_err(|error| format!("parse governed raw-source rebuild command arm: {error}"))?;
+ if raw_arm.guard.is_some()
+ || compact_tokens(raw_arm.body.as_ref()) != compact_tokens(&expected_raw_arm)
+ {
+ return Err(format!(
+ "{XTASK_MAIN_SOURCE_RELATIVE} raw-source rebuild command arm drifted"
+ ));
+ }
+
+ let run_release = exact_top_level_match(&main, "run_release")?;
+ let preflight_arm = exact_match_arm(run_release, "Some(\"preflight\")")?;
+ if preflight_arm.guard.is_some()
+ || compact_tokens(preflight_arm.body.as_ref()) != "release_preflight()"
+ {
+ return Err(format!(
+ "{XTASK_MAIN_SOURCE_RELATIVE} release preflight command arm drifted"
+ ));
+ }
+ Ok(())
+}
+
+fn direct_call_tokens(function: &syn::ItemFn) -> Vec<String> {
+ struct Audit(Vec<String>);
+
+ impl<'ast> syn::visit::Visit<'ast> for Audit {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ self.0.push(compact_tokens(call));
+ syn::visit::visit_expr_call(self, call);
+ }
+ }
+
+ use syn::visit::Visit;
+ let mut audit = Audit(Vec::new());
+ audit.visit_block(&function.block);
+ audit.0
+}
+
+fn exact_top_level_match<'a>(
+ file: &'a syn::File,
+ function_name: &str,
+) -> Result<&'a syn::ExprMatch, String> {
+ let function = exact_top_level_function(file, function_name)?;
+ let [syn::Stmt::Expr(syn::Expr::Match(expression), None)] = function.block.stmts.as_slice()
+ else {
+ return Err(format!(
+ "{XTASK_MAIN_SOURCE_RELATIVE}::{function_name} must contain exactly one top-level match expression"
+ ));
+ };
+ Ok(expression)
+}
+
+fn exact_match_arm<'a>(
+ expression: &'a syn::ExprMatch,
+ pattern: &str,
+) -> Result<&'a syn::Arm, String> {
+ let matching = expression
+ .arms
+ .iter()
+ .filter(|arm| compact_tokens(&arm.pat) == pattern)
+ .collect::<Vec<_>>();
+ let [arm] = matching.as_slice() else {
+ return Err(format!(
+ "{XTASK_MAIN_SOURCE_RELATIVE} must contain exactly one command arm `{pattern}`; found {}",
+ matching.len()
+ ));
+ };
+ Ok(arm)
+}
+
+fn validate_release_authority(workspace_root: &Path) -> Result<(), String> {
+ let release_bytes = read_regular_file(workspace_root, RELEASE_RECORD_RELATIVE)?;
+ let release_source = std::str::from_utf8(&release_bytes)
+ .map_err(|error| format!("{RELEASE_RECORD_RELATIVE} must be UTF-8: {error}"))?;
+ let release: toml::Value = toml::from_str(release_source)
+ .map_err(|error| format!("parse {RELEASE_RECORD_RELATIVE}: {error}"))?;
+ let changes = release
+ .get("changes")
+ .and_then(toml::Value::as_array)
+ .ok_or_else(|| format!("{RELEASE_RECORD_RELATIVE} must define changes"))?;
+ let matching = changes
+ .iter()
+ .filter(|change| change.get("id").and_then(toml::Value::as_str) == Some(RELEASE_CHANGE_ID))
+ .collect::<Vec<_>>();
+ let [change] = matching.as_slice() else {
+ return Err(format!(
+ "{RELEASE_RECORD_RELATIVE} must define exactly one `{RELEASE_CHANGE_ID}` change; found {}",
+ matching.len()
+ ));
+ };
+ let impacts = change
+ .get("semver_impacts")
+ .and_then(toml::Value::as_array)
+ .ok_or_else(|| format!("release change `{RELEASE_CHANGE_ID}` has no semver impacts"))?
+ .iter()
+ .map(|impact| {
+ impact.as_str().ok_or_else(|| {
+ format!("release change `{RELEASE_CHANGE_ID}` semver impacts must be strings")
+ })
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+ if change.get("classification").and_then(toml::Value::as_str) != Some("breaking")
+ || impacts != RELEASE_CHANGE_IMPACTS
+ || change.get("summary").and_then(toml::Value::as_str) != Some(RELEASE_CHANGE_SUMMARY)
+ {
+ return Err(format!(
+ "release change `{RELEASE_CHANGE_ID}` must retain its exact breaking classification, semver impacts, and summary"
+ ));
+ }
+
+ let changelog_bytes = read_regular_file(workspace_root, CHANGELOG_RELATIVE)?;
+ let changelog = std::str::from_utf8(&changelog_bytes)
+ .map_err(|error| format!("{CHANGELOG_RELATIVE} must be UTF-8: {error}"))?;
+ if changelog.matches(CHANGELOG_RELEASE_MARKER).count() != 1 {
+ return Err(format!(
+ "{CHANGELOG_RELATIVE} must contain exactly one `{CHANGELOG_RELEASE_MARKER}` marker"
+ ));
+ }
+ let current_start = changelog
+ .find("## [1.0.0-alpha.1]")
+ .ok_or_else(|| format!("{CHANGELOG_RELATIVE} has no current release section"))?;
+ let current = &changelog[current_start..];
+ let current_end = current["## [1.0.0-alpha.1]".len()..]
+ .find("\n## [")
+ .map_or(current.len(), |offset| offset + "## [1.0.0-alpha.1]".len());
+ let current = ¤t[..current_end];
+ if !current.contains(&format!(
+ "{CHANGELOG_RELEASE_MARKER}\n- Event-store schema v4 now exposes a versioned raw-source rebuild operation"
+ )) {
+ return Err(format!(
+ "{CHANGELOG_RELATIVE} current release must bind the raw-source rebuild note to `{RELEASE_CHANGE_ID}`"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_result_vector(
+ workspace_root: &Path,
+ vector: &RawSourceRebuildVector,
+) -> Result<(), String> {
+ if vector.schema_version != SCHEMA_VERSION || vector.contract_id != CONTRACT_ID {
+ return Err(format!(
+ "{RESULT_VECTOR_CANONICAL_RELATIVE} has inconsistent identity"
+ ));
+ }
+ if vector.cases.is_empty() {
+ return Err(format!(
+ "{RESULT_VECTOR_CANONICAL_RELATIVE} must contain executable cases"
+ ));
+ }
+ validate_delegated_suite_inventory(vector)?;
+ validate_unique(
+ "raw-source rebuild vector case IDs",
+ vector.cases.iter().map(|case| case.id.as_str()),
+ )?;
+ validate_failpoint_result_vector_cases(vector)?;
+ let mut direct_count = 0_usize;
+ for case in &vector.cases {
+ if case.expected_outcome.trim().is_empty() {
+ return Err(format!("vector case `{}` has no expected outcome", case.id));
+ }
+ match case.execution.as_str() {
+ "direct_executor" => {
+ direct_count += 1;
+ if case.authority != RESULT_VECTOR_EXECUTOR_TEST
+ || case.authority_path != RESULT_VECTOR_EXECUTOR_RELATIVE
+ || !RESULT_VECTOR_DIRECT_CASE_IDS.contains(&case.id.as_str())
+ {
+ return Err(format!(
+ "direct vector case `{}` must bind the canonical executor",
+ case.id
+ ));
+ }
+ if case.expected_immutable_raw_digest.is_none()
+ || case.expected_active_product_state_digest.is_none()
+ {
+ return Err(format!(
+ "direct vector case `{}` must freeze exact immutable-raw and active-product digest bytes",
+ case.id
+ ));
+ }
+ }
+ "delegated_rust_test" => {}
+ other => {
+ return Err(format!(
+ "vector case `{}` has unsupported execution mode `{other}`",
+ case.id
+ ));
+ }
+ }
+ for digest in [
+ case.expected_immutable_raw_digest.as_deref(),
+ case.expected_active_product_state_digest.as_deref(),
+ ]
+ .into_iter()
+ .flatten()
+ {
+ validate_vector_expected_digest(digest)?;
+ }
+ }
+ if direct_count != RESULT_VECTOR_DIRECT_CASE_IDS.len() {
+ return Err(format!(
+ "raw-source rebuild vector requires exactly {} direct executor cases; found {direct_count}",
+ RESULT_VECTOR_DIRECT_CASE_IDS.len()
+ ));
+ }
+ for authority in &vector.delegated_suite.authorities {
+ validate_executable_test(
+ workspace_root,
+ &authority.authority_path,
+ &authority.authority,
+ )?;
+ }
+ validate_executable_test(
+ workspace_root,
+ RESULT_VECTOR_EXECUTOR_RELATIVE,
+ RESULT_VECTOR_EXECUTOR_TEST,
+ )?;
+ validate_direct_executor_authority(workspace_root)?;
+ validate_delegated_suite_contract_lane(workspace_root)
+}
+
+fn validate_failpoint_result_vector_cases(vector: &RawSourceRebuildVector) -> Result<(), String> {
+ let expected_ids = REBUILD_FAILPOINTS
+ .iter()
+ .map(|failpoint| failpoint.rollback_case_id)
+ .collect::<BTreeSet<_>>();
+ let actual_cases = vector
+ .cases
+ .iter()
+ .filter(|case| {
+ case.authority == REBUILD_FAILPOINT_TEST || case.id.starts_with("rollback_after_")
+ })
+ .collect::<Vec<_>>();
+ let actual_ids = actual_cases
+ .iter()
+ .map(|case| case.id.as_str())
+ .collect::<BTreeSet<_>>();
+ if actual_ids != expected_ids {
+ return Err(format!(
+ "raw-source rebuild vector rollback failpoint case IDs drifted: expected {expected_ids:?}, found {actual_ids:?}"
+ ));
+ }
+ for failpoint in REBUILD_FAILPOINTS {
+ let matching = actual_cases
+ .iter()
+ .filter(|case| case.id == failpoint.rollback_case_id)
+ .copied()
+ .collect::<Vec<_>>();
+ let [case] = matching.as_slice() else {
+ return Err(format!(
+ "raw-source rebuild vector must bind rollback case `{}` exactly once",
+ failpoint.rollback_case_id
+ ));
+ };
+ if case.execution != "delegated_rust_test"
+ || case.authority != REBUILD_FAILPOINT_TEST
+ || case.authority_path != REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE
+ {
+ return Err(format!(
+ "raw-source rebuild rollback case `{}` must bind the governed failpoint test authority",
+ failpoint.rollback_case_id
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_delegated_suite_inventory(vector: &RawSourceRebuildVector) -> Result<(), String> {
+ let suite = &vector.delegated_suite;
+ if suite.id != RESULT_VECTOR_DELEGATED_SUITE_ID
+ || suite.lane != RESULT_VECTOR_DELEGATED_SUITE_LANE
+ || suite.package != RESULT_VECTOR_DELEGATED_SUITE_PACKAGE
+ {
+ return Err(format!(
+ "{RESULT_VECTOR_CANONICAL_RELATIVE} delegated suite identity, lane, or package drifted"
+ ));
+ }
+ if suite.authorities.is_empty() {
+ return Err(format!(
+ "{RESULT_VECTOR_CANONICAL_RELATIVE} delegated suite must contain exact test authorities"
+ ));
+ }
+ let suite_authorities = suite
+ .authorities
+ .iter()
+ .map(|authority| {
+ (
+ authority.authority_path.as_str(),
+ authority.authority.as_str(),
+ )
+ })
+ .collect::<Vec<_>>();
+ let suite_authority_keys = suite_authorities
+ .iter()
+ .map(|(path, authority)| format!("{path}::{authority}"))
+ .collect::<Vec<_>>();
+ validate_unique(
+ "raw-source rebuild delegated suite authorities",
+ suite_authority_keys.iter().map(String::as_str),
+ )?;
+ if suite_authorities
+ .iter()
+ .any(|(path, authority)| path.trim().is_empty() || authority.trim().is_empty())
+ {
+ return Err(
+ "raw-source rebuild delegated suite authorities must have nonempty paths and names"
+ .to_owned(),
+ );
+ }
+
+ let suite_authorities = suite_authorities.into_iter().collect::<BTreeSet<_>>();
+ let delegated_case_authorities = vector
+ .cases
+ .iter()
+ .filter(|case| case.execution == "delegated_rust_test")
+ .map(|case| (case.authority_path.as_str(), case.authority.as_str()))
+ .collect::<BTreeSet<_>>();
+ if suite_authorities != delegated_case_authorities {
+ let missing = delegated_case_authorities
+ .difference(&suite_authorities)
+ .map(|(path, authority)| format!("{path}::{authority}"))
+ .collect::<Vec<_>>();
+ let unrepresented = suite_authorities
+ .difference(&delegated_case_authorities)
+ .map(|(path, authority)| format!("{path}::{authority}"))
+ .collect::<Vec<_>>();
+ return Err(format!(
+ "raw-source rebuild delegated suite must exactly cover delegated vector cases; missing {missing:?}; unrepresented {unrepresented:?}"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_delegated_suite_contract_lane(workspace_root: &Path) -> Result<(), String> {
+ let flake = regular_utf8_source(workspace_root, FLAKE_SOURCE_RELATIVE)?;
+ let apps = regular_utf8_source(workspace_root, CONTRACT_APP_SOURCE_RELATIVE)?;
+ let common = regular_utf8_source(workspace_root, CONTRACT_LANE_SOURCE_RELATIVE)?;
+ let toolchains = regular_utf8_source(workspace_root, TOOLCHAIN_ROUTING_SOURCE_RELATIVE)?;
+ validate_delegated_suite_contract_lane_sources(&flake, &apps, &common, &toolchains)?;
+ validate_flake_lock_authority(workspace_root)?;
+ validate_delegated_suite_test_targets(workspace_root)
+}
+
+fn validate_delegated_suite_test_targets(workspace_root: &Path) -> Result<(), String> {
+ let cargo_relative = "crates/event_store/Cargo.toml";
+ let cargo = regular_utf8_source(workspace_root, cargo_relative)?;
+ let cargo: toml::Value =
+ toml::from_str(&cargo).map_err(|error| format!("parse {cargo_relative}: {error}"))?;
+ let package = cargo
+ .get("package")
+ .and_then(toml::Value::as_table)
+ .ok_or_else(|| format!("{cargo_relative} must define one package"))?;
+ if package.get("name").and_then(toml::Value::as_str)
+ != Some(RESULT_VECTOR_DELEGATED_SUITE_PACKAGE)
+ || package.get("autotests").and_then(toml::Value::as_bool) == Some(false)
+ || cargo
+ .get("lib")
+ .and_then(|lib| lib.get("test"))
+ .and_then(toml::Value::as_bool)
+ == Some(false)
+ {
+ return Err(format!(
+ "{cargo_relative} must leave the delegated library tests and direct integration executor enabled for `{RESULT_VECTOR_DELEGATED_SUITE_PACKAGE}`"
+ ));
+ }
+
+ for (relative, module_name, expected_attributes) in [
+ (
+ "crates/event_store/src/store.rs",
+ "raw_source_rebuild_v1_tests",
+ &["#[cfg(test)]"][..],
+ ),
+ (
+ "crates/event_store/src/nip09/reconciliation_v1.rs",
+ "visibility_oracle_v1",
+ &[][..],
+ ),
+ ] {
+ let file = rust_file(workspace_root, relative)?;
+ let modules = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Mod(module) if module.ident == module_name => Some(module),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [module] = modules.as_slice() else {
+ return Err(format!(
+ "{relative} must register delegated test module `{module_name}` exactly once; found {}",
+ modules.len()
+ ));
+ };
+ let attributes = module.attrs.iter().map(compact_tokens).collect::<Vec<_>>();
+ if !matches!(module.vis, syn::Visibility::Inherited)
+ || module.content.is_some()
+ || attributes != expected_attributes
+ {
+ return Err(format!(
+ "{relative} delegated test module `{module_name}` visibility, source routing, or attributes drifted"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_delegated_suite_contract_lane_sources(
+ flake: &str,
+ apps: &str,
+ common: &str,
+ toolchains: &str,
+) -> Result<(), String> {
+ let flake_toolchains =
+ nix_code_occurrences(flake, "toolchains = import ./build/nix/toolchains.nix {");
+ let all_flake_toolchains = nix_code_occurrences(flake, "toolchains =");
+ if flake_toolchains.len() != 1 || all_flake_toolchains != flake_toolchains {
+ return Err(format!(
+ "{FLAKE_SOURCE_RELATIVE} must bind exactly one toolchain authority from ./build/nix/toolchains.nix"
+ ));
+ }
+ let flake_common = nix_code_occurrences(flake, "common = import ./build/nix/common.nix {");
+ let all_flake_common = nix_code_occurrences(flake, "common =");
+ if flake_common.len() != 1 || all_flake_common != flake_common {
+ return Err(format!(
+ "{FLAKE_SOURCE_RELATIVE} must bind exactly one common authority from ./build/nix/common.nix"
+ ));
+ }
+ let flake_apps = nix_code_occurrences(flake, "apps = import ./build/nix/apps.nix {");
+ let all_flake_apps = nix_code_occurrences(flake, "apps =");
+ if flake_apps.len() != 1 || all_flake_apps != flake_apps {
+ return Err(format!(
+ "{FLAKE_SOURCE_RELATIVE} must export exactly one per-system apps authority from ./build/nix/apps.nix"
+ ));
+ }
+ let per_system = nix_code_occurrences(flake, "perSystem =");
+ if per_system.len() != 1
+ || per_system[0] >= flake_toolchains[0]
+ || flake_toolchains[0] >= flake_common[0]
+ || flake_common[0] >= flake_apps[0]
+ {
+ return Err(format!(
+ "{FLAKE_SOURCE_RELATIVE} must bind governed toolchains, common, and apps imports in order through perSystem"
+ ));
+ }
+ for (label, assignment_start, source, expected) in [
+ (
+ "toolchains",
+ flake_toolchains[0],
+ nix_balanced_slice(flake, flake_toolchains[0], b'{', b'}')?,
+ "{inheritpkgs;}",
+ ),
+ (
+ "common",
+ flake_common[0],
+ nix_balanced_slice(flake, flake_common[0], b'{', b'}')?,
+ "{crane=inputs.crane;inheritlibpkgstoolchains;}",
+ ),
+ (
+ "apps",
+ flake_apps[0],
+ nix_balanced_slice(flake, flake_apps[0], b'{', b'}')?,
+ "{inheritcommonconfiglibpkgstoolchains;}",
+ ),
+ ] {
+ let actual = source.split_whitespace().collect::<String>();
+ if actual != expected {
+ return Err(format!(
+ "{FLAKE_SOURCE_RELATIVE} `{label}` import arguments drifted from the exact delegated contract-lane authority"
+ ));
+ }
+ validate_nix_attrset_assignment_terminator(flake, assignment_start).map_err(|error| {
+ format!(
+ "{FLAKE_SOURCE_RELATIVE} `{label}` import must end immediately after its governed arguments: {error}"
+ )
+ })?;
+ }
+
+ let stable_assignments = nix_code_occurrences(toolchains, "stable =");
+ let stable_authority = nix_code_occurrences(
+ toolchains,
+ "stable = pkgs.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml;",
+ );
+ if stable_assignments.len() != 1 || stable_authority != stable_assignments {
+ return Err(format!(
+ "{TOOLCHAIN_ROUTING_SOURCE_RELATIVE} must route the stable toolchain exactly through ../../{RUST_TOOLCHAIN_RELATIVE}"
+ ));
+ }
+
+ let cargo_source_assignments =
+ nix_code_occurrences(common, "cargoSource = lib.fileset.toSource {");
+ if cargo_source_assignments.len() != 1 {
+ return Err(format!(
+ "{CONTRACT_LANE_SOURCE_RELATIVE} must define exactly one cargoSource fileset"
+ ));
+ }
+ let cargo_source = nix_balanced_slice(common, cargo_source_assignments[0], b'{', b'}')?;
+ for required in [
+ "../../Cargo.toml",
+ "../../Cargo.lock",
+ "../../flake.nix",
+ "../../flake.lock",
+ "../../build/nix/apps.nix",
+ "../../build/nix/common.nix",
+ "../../build/nix/toolchains.nix",
+ "../../rust-toolchain.toml",
+ "../../tools",
+ ] {
+ if nix_code_occurrences(cargo_source, required).len() != 1 {
+ return Err(format!(
+ "{CONTRACT_LANE_SOURCE_RELATIVE} cargoSource must include governed input `{required}` exactly once"
+ ));
+ }
+ }
+
+ let contract_apps = nix_code_occurrences(apps, "contract = mkRepoApp {");
+ if contract_apps.len() != 1 {
+ return Err(format!(
+ "{CONTRACT_APP_SOURCE_RELATIVE} must define exactly one executable contract app"
+ ));
+ }
+ let contract_app = nix_balanced_slice(apps, contract_apps[0], b'{', b'}')?;
+ let contract_app_authority = contract_app.split_whitespace().collect::<String>();
+ let expected_contract_app_authority = concat!(
+ "{",
+ "name=\"contract\";",
+ "description=\"Runthecore-librarycontractlane\";",
+ "runtimeInputs=common.runtimeInputs.stable;",
+ "command=common.contractCommand;",
+ "}"
+ );
+ if contract_app_authority != expected_contract_app_authority {
+ return Err(format!(
+ "{CONTRACT_APP_SOURCE_RELATIVE} contract app must bind the exact stable runtime, default path, environment, and command authority"
+ ));
+ }
+
+ let crate_list_assignments = nix_code_occurrences(common, "coreContractCrates = [");
+ if crate_list_assignments.len() != 1 {
+ return Err(format!(
+ "{CONTRACT_LANE_SOURCE_RELATIVE} must define one literal coreContractCrates list"
+ ));
+ }
+ let crate_list = nix_balanced_slice(common, crate_list_assignments[0], b'[', b']')?;
+ let crates = nix_literal_string_array(crate_list)?;
+ validate_unique(
+ "Nix core contract crates",
+ crates.iter().map(String::as_str),
+ )?;
+ if crates
+ .iter()
+ .filter(|package| package.as_str() == RESULT_VECTOR_DELEGATED_SUITE_PACKAGE)
+ .count()
+ != 1
+ {
+ return Err(format!(
+ "{CONTRACT_LANE_SOURCE_RELATIVE} coreContractCrates must contain `{RESULT_VECTOR_DELEGATED_SUITE_PACKAGE}` exactly once"
+ ));
+ }
+
+ let cargo_args = nix_assignment_through_semicolon(common, "coreContractCargoArgs")?;
+ let cargo_arg_lines = cargo_args
+ .lines()
+ .map(str::trim)
+ .filter(|line| !line.is_empty())
+ .collect::<Vec<_>>();
+ if cargo_arg_lines.len() != 3
+ || cargo_arg_lines[0] != "coreContractCargoArgs ="
+ || cargo_arg_lines[1]
+ != "lib.concatStringsSep \" \" (map (crate: \"-p ${crate}\") coreContractCrates)"
+ || cargo_arg_lines[2]
+ != "+ \" --features radroots_event_codec/serde_json,radroots_event_codec/nostr,radroots_nostr/blossom,radroots_nostr/client,radroots_nostr/codec,radroots_nostr/events\";"
+ {
+ return Err(format!(
+ "{CONTRACT_LANE_SOURCE_RELATIVE} coreContractCargoArgs must map every literal core contract crate to an unfiltered `-p` package selection"
+ ));
+ }
+
+ let contract_command = nix_indented_string_assignment(common, "contractCommand")?;
+ let contract_commands = contract_command
+ .lines()
+ .map(str::trim)
+ .filter(|line| !line.is_empty())
+ .collect::<Vec<_>>();
+ let expected_commands = [
+ "cargo run -q -p xtask -- hygiene forbidden-identifiers",
+ "cargo check -q ${coreContractCargoArgs}",
+ "cargo test -q ${coreContractCargoArgs}",
+ "cargo run -q -p xtask -- contract validate",
+ ];
+ if contract_commands != expected_commands {
+ return Err(format!(
+ "{CONTRACT_LANE_SOURCE_RELATIVE} contractCommand must run the exact unfiltered core package test lane before contract validation"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_flake_lock_authority(workspace_root: &Path) -> Result<(), String> {
+ let source = regular_utf8_source(workspace_root, FLAKE_LOCK_RELATIVE)?;
+ let lock: Value = serde_json::from_str(&source)
+ .map_err(|error| format!("parse {FLAKE_LOCK_RELATIVE}: {error}"))?;
+ if lock.get("version").and_then(Value::as_u64) != Some(7)
+ || lock.get("root").and_then(Value::as_str) != Some("root")
+ {
+ return Err(format!(
+ "{FLAKE_LOCK_RELATIVE} must remain a version-7 lock with the root node named `root`"
+ ));
+ }
+ let nodes = lock
+ .get("nodes")
+ .and_then(Value::as_object)
+ .ok_or_else(|| format!("{FLAKE_LOCK_RELATIVE} must define a nodes object"))?;
+ let root_inputs = nodes
+ .get("root")
+ .and_then(|root| root.get("inputs"))
+ .and_then(Value::as_object)
+ .ok_or_else(|| format!("{FLAKE_LOCK_RELATIVE} root node must define direct inputs"))?;
+ let expected_inputs = [
+ ("crane", "crane"),
+ ("flake-parts", "flake-parts"),
+ ("nixpkgs", "nixpkgs"),
+ ("rust-overlay", "rust-overlay"),
+ ("treefmt-nix", "treefmt-nix"),
+ ]
+ .into_iter()
+ .map(|(name, node)| (name.to_owned(), Value::String(node.to_owned())))
+ .collect::<serde_json::Map<_, _>>();
+ if root_inputs != &expected_inputs {
+ return Err(format!(
+ "{FLAKE_LOCK_RELATIVE} root inputs must exactly lock crane, flake-parts, nixpkgs, rust-overlay, and treefmt-nix"
+ ));
+ }
+ for node in expected_inputs.values().filter_map(Value::as_str) {
+ let locked = nodes
+ .get(node)
+ .and_then(|node| node.get("locked"))
+ .and_then(Value::as_object)
+ .ok_or_else(|| {
+ format!("{FLAKE_LOCK_RELATIVE} direct input node `{node}` must be locked")
+ })?;
+ for field in ["narHash", "rev"] {
+ if locked
+ .get(field)
+ .and_then(Value::as_str)
+ .is_none_or(str::is_empty)
+ {
+ return Err(format!(
+ "{FLAKE_LOCK_RELATIVE} direct input node `{node}` must bind nonempty `{field}`"
+ ));
+ }
+ }
+ }
+ Ok(())
+}
+
+fn regular_utf8_source(workspace_root: &Path, relative: &str) -> Result<String, String> {
+ let bytes = read_regular_file(workspace_root, relative)?;
+ String::from_utf8(bytes).map_err(|error| format!("{relative} must be UTF-8: {error}"))
+}
+
+fn nix_code_mask(source: &str) -> Vec<bool> {
+ #[derive(Clone, Copy)]
+ enum State {
+ Code,
+ LineComment,
+ BlockComment,
+ DoubleString,
+ IndentedString,
+ }
+
+ let bytes = source.as_bytes();
+ let mut mask = vec![false; bytes.len()];
+ let mut state = State::Code;
+ let mut index = 0_usize;
+ while index < bytes.len() {
+ match state {
+ State::Code => {
+ if bytes[index] == b'#' {
+ state = State::LineComment;
+ index += 1;
+ } else if bytes[index..].starts_with(b"/*") {
+ state = State::BlockComment;
+ index += 2;
+ } else if bytes[index] == b'"' {
+ mask[index] = true;
+ state = State::DoubleString;
+ index += 1;
+ } else if bytes[index..].starts_with(b"''") {
+ mask[index] = true;
+ state = State::IndentedString;
+ index += 2;
+ } else {
+ mask[index] = true;
+ index += 1;
+ }
+ }
+ State::LineComment => {
+ if bytes[index] == b'\n' {
+ state = State::Code;
+ } else {
+ index += 1;
+ }
+ }
+ State::BlockComment => {
+ if bytes[index..].starts_with(b"*/") {
+ state = State::Code;
+ index += 2;
+ } else {
+ index += 1;
+ }
+ }
+ State::DoubleString => {
+ if bytes[index] == b'\\' {
+ index = (index + 2).min(bytes.len());
+ } else if bytes[index] == b'"' {
+ state = State::Code;
+ index += 1;
+ } else {
+ index += 1;
+ }
+ }
+ State::IndentedString => {
+ if bytes[index..].starts_with(b"''") {
+ match bytes.get(index + 2) {
+ Some(b'$' | b'\'' | b'\\') => index += 3,
+ _ => {
+ state = State::Code;
+ index += 2;
+ }
+ }
+ } else {
+ index += 1;
+ }
+ }
+ }
+ }
+ mask
+}
+
+fn nix_code_occurrences(source: &str, needle: &str) -> Vec<usize> {
+ let mask = nix_code_mask(source);
+ source
+ .match_indices(needle)
+ .filter_map(|(index, _)| mask.get(index).copied().unwrap_or(false).then_some(index))
+ .collect()
+}
+
+fn nix_balanced_slice(
+ source: &str,
+ search_start: usize,
+ open: u8,
+ close: u8,
+) -> Result<&str, String> {
+ let mask = nix_code_mask(source);
+ let bytes = source.as_bytes();
+ let start = (search_start..bytes.len())
+ .find(|index| mask[*index] && bytes[*index] == open)
+ .ok_or_else(|| {
+ format!(
+ "governed Nix authority has no `{}` opener",
+ char::from(open)
+ )
+ })?;
+ let mut depth = 0_usize;
+ for index in start..bytes.len() {
+ if !mask[index] {
+ continue;
+ }
+ if bytes[index] == open {
+ depth += 1;
+ } else if bytes[index] == close {
+ depth = depth
+ .checked_sub(1)
+ .ok_or_else(|| "governed Nix authority has unbalanced delimiters".to_owned())?;
+ if depth == 0 {
+ return Ok(&source[start..=index]);
+ }
+ }
+ }
+ Err("governed Nix authority has an unterminated delimiter".to_owned())
+}
+
+fn validate_nix_attrset_assignment_terminator(
+ source: &str,
+ assignment_start: usize,
+) -> Result<(), String> {
+ let attrset = nix_balanced_slice(source, assignment_start, b'{', b'}')?;
+ let attrset_start = attrset.as_ptr() as usize - source.as_ptr() as usize;
+ let after_attrset = &source[attrset_start + attrset.len()..];
+ let first = after_attrset
+ .bytes()
+ .find(|byte| !byte.is_ascii_whitespace());
+ if first != Some(b';') {
+ return Err("postfix merge or expression detected before assignment terminator".to_owned());
+ }
+ Ok(())
+}
+
+fn nix_literal_string_array(source: &str) -> Result<Vec<String>, String> {
+ let mask = nix_code_mask(source);
+ let bytes = source.as_bytes();
+ let mut values = Vec::new();
+ let mut index = 0_usize;
+ while index < bytes.len() {
+ if !mask[index] {
+ index += 1;
+ continue;
+ }
+ match bytes[index] {
+ b'[' | b']' | b' ' | b'\t' | b'\r' | b'\n' => index += 1,
+ b'"' => {
+ let start = index + 1;
+ index = start;
+ while index < bytes.len() && bytes[index] != b'"' {
+ if bytes[index] == b'\\' || bytes[index..].starts_with(b"${") {
+ return Err(
+ "governed Nix package list must use plain literal strings".to_owned()
+ );
+ }
+ index += 1;
+ }
+ if index == bytes.len() {
+ return Err("governed Nix package list has an unterminated string".to_owned());
+ }
+ values.push(source[start..index].to_owned());
+ index += 1;
+ }
+ _ => {
+ return Err(
+ "governed Nix package list must contain only literal strings".to_owned(),
+ );
+ }
+ }
+ }
+ Ok(values)
+}
+
+fn nix_assignment_through_semicolon<'a>(source: &'a str, name: &str) -> Result<&'a str, String> {
+ let needle = format!("{name} =");
+ let starts = nix_code_occurrences(source, &needle);
+ let [start] = starts.as_slice() else {
+ return Err(format!(
+ "governed Nix source must define `{name}` exactly once; found {}",
+ starts.len()
+ ));
+ };
+ let mask = nix_code_mask(source);
+ let end = (*start..source.len())
+ .find(|index| mask[*index] && source.as_bytes()[*index] == b';')
+ .ok_or_else(|| format!("governed Nix assignment `{name}` has no terminator"))?;
+ Ok(&source[*start..=end])
+}
+
+fn nix_indented_string_assignment<'a>(source: &'a str, name: &str) -> Result<&'a str, String> {
+ let needle = format!("{name} = ''");
+ let starts = nix_code_occurrences(source, &needle);
+ let [start] = starts.as_slice() else {
+ return Err(format!(
+ "governed Nix source must define indented string `{name}` exactly once; found {}",
+ starts.len()
+ ));
+ };
+ let content_start = *start + needle.len();
+ let bytes = source.as_bytes();
+ let mut index = content_start;
+ while index < bytes.len() {
+ if bytes[index..].starts_with(b"''") {
+ match bytes.get(index + 2) {
+ Some(b'$' | b'\'' | b'\\') => index += 3,
+ _ => {
+ let content = &source[content_start..index];
+ index += 2;
+ while bytes.get(index).is_some_and(u8::is_ascii_whitespace) {
+ index += 1;
+ }
+ if bytes.get(index) != Some(&b';') {
+ return Err(format!(
+ "governed Nix indented string `{name}` must end with one semicolon"
+ ));
+ }
+ return Ok(content);
+ }
+ }
+ } else {
+ index += 1;
+ }
+ }
+ Err(format!(
+ "governed Nix indented string `{name}` is unterminated"
+ ))
+}
+
+fn validate_result_vector_identity(bytes: &[u8]) -> Result<(), String> {
+ if bytes.len() != RESULT_VECTOR_BYTE_LENGTH || sha256_hex(bytes) != RESULT_VECTOR_SHA256 {
+ return Err(format!(
+ "{RESULT_VECTOR_CANONICAL_RELATIVE} does not match the immutable executable case inventory"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_direct_executor_authority(workspace_root: &Path) -> Result<(), String> {
+ let file = rust_file(workspace_root, RESULT_VECTOR_EXECUTOR_RELATIVE)?;
+ let full_source = compact_tokens(&file);
+ if !full_source.contains("include_bytes!(\"fixtures/food_availability_projection.v1.json\")") {
+ return Err(
+ "direct raw-source rebuild vector executor must byte-bind the signed Food fixture"
+ .to_owned(),
+ );
+ }
+ let function = compact_tokens(exact_free_function(&file, RESULT_VECTOR_EXECUTOR_TEST)?);
+ for marker in [
+ "decode_digest(",
+ "expected_immutable_raw_digest.as_deref().expect(",
+ "expected_active_product_state_digest.as_deref().expect(",
+ "first.immutable_raw_digest().as_bytes(),&expected_immutable_raw_digest",
+ "first.active_product_state_digest().as_bytes(),&expected_active_product_state_digest",
+ "signed_food_fixture_ingest()",
+ "food_first.immutable_raw_digest().as_bytes(),&expected_food_raw_digest",
+ "food_first.active_product_state_digest().as_bytes(),&expected_food_product_digest",
+ "food_second.immutable_raw_digest(),food_first.immutable_raw_digest()",
+ "food_second.active_product_state_digest(),food_first.active_product_state_digest()",
+ ] {
+ if !function.contains(marker) {
+ return Err(format!(
+ "direct raw-source rebuild vector executor is missing exact digest authority `{marker}`"
+ ));
+ }
+ }
+ for case_id in RESULT_VECTOR_DIRECT_CASE_IDS {
+ if !function.contains(case_id) {
+ return Err(format!(
+ "direct raw-source rebuild vector executor does not execute case `{case_id}`"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_executable_test(
+ workspace_root: &Path,
+ relative: &str,
+ name: &str,
+) -> Result<(), String> {
+ let file = rust_file(workspace_root, relative)?;
+ #[derive(Clone)]
+ struct ModuleContext {
+ name: String,
+ attributes: Vec<String>,
+ private: bool,
+ }
+ struct Match<'a> {
+ function: &'a syn::ItemFn,
+ modules: Vec<ModuleContext>,
+ }
+ fn collect<'a>(
+ items: &'a [Item],
+ name: &str,
+ modules: &mut Vec<ModuleContext>,
+ matches: &mut Vec<Match<'a>>,
+ ) {
+ for item in items {
+ match item {
+ Item::Fn(function) if function.sig.ident == name => matches.push(Match {
+ function,
+ modules: modules.clone(),
+ }),
+ Item::Mod(module) => {
+ if let Some((_, items)) = &module.content {
+ modules.push(ModuleContext {
+ name: module.ident.to_string(),
+ attributes: module.attrs.iter().map(compact_tokens).collect(),
+ private: matches!(module.vis, syn::Visibility::Inherited),
+ });
+ collect(items, name, modules, matches);
+ modules.pop();
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ let mut matches = Vec::new();
+ collect(&file.items, name, &mut Vec::new(), &mut matches);
+ let [matched] = matches.as_slice() else {
+ return Err(format!(
+ "executable raw-source rebuild authority {relative}::{name} must exist exactly once; found {}",
+ matches.len()
+ ));
+ };
+ let expected_test_module = (relative
+ == "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs")
+ .then_some("tests");
+ match (expected_test_module, matched.modules.as_slice()) {
+ (None, []) => {}
+ (Some(expected), [module])
+ if module.name == expected
+ && module.private
+ && module.attributes == ["#[cfg(test)]"] => {}
+ _ => {
+ let actual = matched
+ .modules
+ .iter()
+ .map(|module| {
+ format!(
+ "{}:{:?}:private={}",
+ module.name, module.attributes, module.private
+ )
+ })
+ .collect::<Vec<_>>();
+ return Err(format!(
+ "executable raw-source rebuild authority {relative}::{name} has unsupported module ancestry {actual:?}"
+ ));
+ }
+ }
+ let function = matched.function;
+ let attrs = function
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .collect::<Vec<_>>();
+ if attrs.as_slice() != ["#[test]"] && attrs.as_slice() != ["#[tokio::test]"] {
+ return Err(format!(
+ "executable raw-source rebuild authority {relative}::{name} must have exactly one unconditional test attribute"
+ ));
+ }
+ struct ReturnCounter(usize);
+ impl<'ast> syn::visit::Visit<'ast> for ReturnCounter {
+ fn visit_expr_return(&mut self, expression: &'ast syn::ExprReturn) {
+ self.0 += 1;
+ syn::visit::visit_expr_return(self, expression);
+ }
+ }
+ use syn::visit::Visit;
+ let mut returns = ReturnCounter(0);
+ returns.visit_block(&function.block);
+ if returns.0 != 0 {
+ return Err(format!(
+ "executable raw-source rebuild authority {relative}::{name} must not contain early return control flow"
+ ));
+ }
+ Ok(())
+}
+
+fn generated_descriptor(
+ manifest: &RawSourceRebuildManifest,
+ manifest_bytes: &[u8],
+ manifest_sha256: &str,
+) -> String {
+ let manifest_json = std::str::from_utf8(manifest_bytes).expect("canonical manifest UTF-8");
+ format!(
+ "// @generated by `{WRITE_COMMAND}`; do not edit.\n\
+#![allow(dead_code)]\n\
+\n\
+pub(crate) const RAW_SOURCE_REBUILD_MANIFEST_JSON: &str = {manifest_json:?};\n\
+pub(crate) const RAW_SOURCE_REBUILD_MANIFEST_BYTE_LENGTH: usize = {};\n\
+pub(crate) const RAW_SOURCE_REBUILD_MANIFEST_SHA256: &str =\n \"{manifest_sha256}\";\n\
+pub(crate) const RAW_SOURCE_REBUILD_CONTRACT_ID: &str =\n \"{CONTRACT_ID}\";\n\
+pub(crate) const RAW_SOURCE_REBUILD_AUTHORITY_ID: &str = \"{AUTHORITY_ID}\";\n\
+pub(crate) const RAW_SOURCE_REBUILD_PREDECESSOR_MANIFEST_SHA256: &str =\n \"{PREDECESSOR_MANIFEST_SHA256}\";\n\
+pub(crate) const RAW_SOURCE_REBUILD_EVENT_STORE_SCHEMA_VERSION: u32 = {EVENT_STORE_SCHEMA_VERSION};\n\
+pub(crate) const RAW_SOURCE_REBUILD_EVENT_CONTRACT_REGISTRY_VERSION: u32 = {EVENT_CONTRACT_REGISTRY_VERSION};\n\
+pub(crate) const RAW_SOURCE_REBUILD_RESULT_VECTOR_SHA256: &str =\n \"{}\";\n\
+pub(crate) const RAW_SOURCE_REBUILD_RESULT_VECTOR_EXECUTOR_SHA256: &str =\n \"{}\";\n",
+ manifest_bytes.len(),
+ manifest.result_vector.sha256,
+ manifest.result_vector.executor_sha256,
+ )
+}
+
+fn manifest_schema() -> Value {
+ let path_pattern = "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$";
+ let file = json!({
+ "type": "object",
+ "required": ["path", "byte_length", "sha256", "hash_algorithm"],
+ "properties": {
+ "path": {"type": "string", "pattern": path_pattern},
+ "byte_length": {"type": "integer", "minimum": 1},
+ "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "hash_algorithm": {"const": HASH_ALGORITHM}
+ },
+ "additionalProperties": false
+ });
+ let string_array = json!({
+ "type": "array",
+ "items": {"type": "string", "minLength": 1},
+ "uniqueItems": true
+ });
+ let digest_field = json!({
+ "type": "object",
+ "required": ["name", "framing"],
+ "properties": {
+ "name": {"type": "string", "minLength": 1},
+ "framing": {"enum": ["i64", "boolean", "optional_i64", "text", "optional_text", "blob"]}
+ },
+ "additionalProperties": false
+ });
+ let digest_query = json!({
+ "type": "object",
+ "required": ["section", "sql", "fields"],
+ "properties": {
+ "section": {"type": "string", "minLength": 1},
+ "sql": {"type": "string", "minLength": 1},
+ "fields": {"type": "array", "minItems": 1, "items": digest_field}
+ },
+ "additionalProperties": false
+ });
+ let digest_framing = json!({
+ "type": "object",
+ "required": ["section", "row", "signed_i64", "boolean", "optional", "text", "blob"],
+ "properties": {
+ "section": {"const": "S_then_N_then_u64be_length_then_utf8_name"},
+ "row": {"const": "R"},
+ "signed_i64": {"const": "I_then_i64be"},
+ "boolean": {"const": "B_then_u8_0_or_1"},
+ "optional": {"const": "O_then_presence_u8_then_nested_value_when_present"},
+ "text": {"const": "T_then_u64be_length_then_utf8_bytes"},
+ "blob": {"const": "X_then_u64be_length_then_bytes"}
+ },
+ "additionalProperties": false
+ });
+ json!({
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://radroots.org/contracts/event-store/raw-source-rebuild-v1-manifest.schema.json",
+ "title": "Radroots event-store raw-source rebuild v1 manifest",
+ "type": "object",
+ "required": [
+ "schema_version", "contract_id", "authority_id", "manifest_schema", "predecessor",
+ "migration_inventory", "runtime", "entry_points", "source_files", "public_api",
+ "result_vector"
+ ],
+ "properties": {
+ "schema_version": {"const": SCHEMA_VERSION},
+ "contract_id": {"const": CONTRACT_ID},
+ "authority_id": {"const": AUTHORITY_ID},
+ "manifest_schema": file.clone(),
+ "predecessor": {
+ "type": "object",
+ "required": ["contract_id", "manifest"],
+ "properties": {
+ "contract_id": {"const": PREDECESSOR_CONTRACT_ID},
+ "manifest": file.clone()
+ },
+ "additionalProperties": false
+ },
+ "migration_inventory": {
+ "type": "array", "minItems": 8, "maxItems": 8, "items": file.clone()
+ },
+ "runtime": {
+ "type": "object",
+ "required": [
+ "event_store_schema_version", "event_contract_registry_version",
+ "transaction_mode", "projection_cursor_count_limit",
+ "projection_cursor_rejection_probe_limit", "caller_main_table_count_limit",
+ "caller_foreign_key_row_count_limit", "caller_inbound_foreign_key_policy",
+ "caller_inbound_foreign_key_parent_tables",
+ "cold_repair_mode",
+ "immutable_raw_digest", "active_product_state_digest",
+ "visibility_oracle", "scoped_integrity_mode", "scoped_integrity_tables", "sqlite_sequence_scope", "stages", "failpoints",
+ "preserved_authorities"
+ ],
+ "properties": {
+ "event_store_schema_version": {"const": EVENT_STORE_SCHEMA_VERSION},
+ "event_contract_registry_version": {"const": EVENT_CONTRACT_REGISTRY_VERSION},
+ "transaction_mode": {"const": TRANSACTION_MODE},
+ "projection_cursor_count_limit": {"const": PROJECTION_CURSOR_COUNT_LIMIT},
+ "projection_cursor_rejection_probe_limit": {"const": PROJECTION_CURSOR_REJECTION_PROBE_LIMIT},
+ "caller_main_table_count_limit": {"const": CALLER_MAIN_TABLE_COUNT_LIMIT},
+ "caller_foreign_key_row_count_limit": {"const": CALLER_FOREIGN_KEY_ROW_COUNT_LIMIT},
+ "caller_inbound_foreign_key_policy": {"const": CALLER_INBOUND_FOREIGN_KEY_POLICY},
+ "caller_inbound_foreign_key_parent_tables": string_array.clone(),
+ "cold_repair_mode": {"const": COLD_REPAIR_MODE},
+ "immutable_raw_digest": {
+ "type": "object",
+ "required": ["algorithm", "domain_utf8", "domain_terminator", "framing", "output_bytes", "source_queries"],
+ "properties": {
+ "algorithm": {"const": DIGEST_ALGORITHM},
+ "domain_utf8": {"const": RAW_DIGEST_DOMAIN_UTF8},
+ "domain_terminator": {"const": DIGEST_DOMAIN_TERMINATOR},
+ "framing": digest_framing.clone(),
+ "output_bytes": {"const": 32},
+ "source_queries": {"type": "array", "minItems": 2, "maxItems": 2, "items": digest_query.clone()}
+ },
+ "additionalProperties": false
+ },
+ "active_product_state_digest": {
+ "type": "object",
+ "required": ["algorithm", "domain_utf8", "domain_terminator", "framing", "output_bytes", "components", "exclusions", "component_queries"],
+ "properties": {
+ "algorithm": {"const": DIGEST_ALGORITHM},
+ "domain_utf8": {"const": PRODUCT_DIGEST_DOMAIN_UTF8},
+ "domain_terminator": {"const": DIGEST_DOMAIN_TERMINATOR},
+ "framing": digest_framing,
+ "output_bytes": {"const": 32},
+ "components": string_array.clone(),
+ "exclusions": string_array.clone(),
+ "component_queries": {"type": "array", "minItems": 13, "maxItems": 13, "items": digest_query.clone()}
+ },
+ "additionalProperties": false
+ },
+ "visibility_oracle": {"const": VISIBILITY_ORACLE},
+ "scoped_integrity_mode": {"const": SCOPED_INTEGRITY_MODE},
+ "scoped_integrity_tables": string_array.clone(),
+ "sqlite_sequence_scope": {"const": SQLITE_SEQUENCE_SCOPE},
+ "stages": string_array.clone(),
+ "failpoints": string_array.clone(),
+ "preserved_authorities": string_array.clone()
+ },
+ "additionalProperties": false
+ },
+ "entry_points": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["role", "rust_path"],
+ "properties": {
+ "role": {"type": "string", "minLength": 1},
+ "rust_path": {"type": "string", "minLength": 1}
+ },
+ "additionalProperties": false
+ }
+ },
+ "source_files": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["role", "path", "byte_length", "sha256", "hash_algorithm"],
+ "properties": {
+ "role": {"type": "string", "minLength": 1},
+ "path": {"type": "string", "pattern": path_pattern},
+ "byte_length": {"type": "integer", "minimum": 1},
+ "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "hash_algorithm": {"const": HASH_ALGORITHM}
+ },
+ "additionalProperties": false
+ }
+ },
+ "public_api": {
+ "type": "object",
+ "required": ["added_symbols", "methods", "error_variants", "drift_kinds"],
+ "properties": {
+ "added_symbols": string_array.clone(),
+ "methods": string_array.clone(),
+ "error_variants": string_array.clone(),
+ "drift_kinds": {
+ "type": "array",
+ "minItems": 6,
+ "maxItems": 6,
+ "items": {
+ "type": "object",
+ "required": ["variant", "code"],
+ "properties": {
+ "variant": {"type": "string", "minLength": 1},
+ "code": {"type": "string", "pattern": "^[a-z][a-z0-9_]*$"}
+ },
+ "additionalProperties": false
+ }
+ }
+ },
+ "additionalProperties": false
+ },
+ "result_vector": {
+ "type": "object",
+ "required": [
+ "canonical_path", "mirror_path", "byte_length", "sha256", "hash_algorithm",
+ "executor_id", "executor_path", "executor_test", "executor_byte_length",
+ "executor_sha256", "executor_hash_algorithm"
+ ],
+ "properties": {
+ "canonical_path": {"type": "string", "pattern": path_pattern},
+ "mirror_path": {"type": "string", "pattern": path_pattern},
+ "byte_length": {"type": "integer", "minimum": 1},
+ "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "hash_algorithm": {"const": HASH_ALGORITHM},
+ "executor_id": {"type": "string", "minLength": 1},
+ "executor_path": {"type": "string", "pattern": path_pattern},
+ "executor_test": {"type": "string", "minLength": 1},
+ "executor_byte_length": {"type": "integer", "minimum": 1},
+ "executor_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "executor_hash_algorithm": {"const": HASH_ALGORITHM}
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ })
+}
+
+#[derive(Clone, Debug)]
+struct PublicUseRoute {
+ segments: Vec<String>,
+ exported_name: String,
+ renamed: bool,
+ glob: bool,
+ absolute: bool,
+ attributes: Vec<String>,
+}
+
+fn collect_top_level_public_use_routes(file: &syn::File) -> Vec<PublicUseRoute> {
+ let mut routes = Vec::new();
+ for item in &file.items {
+ let Item::Use(item_use) = item else {
+ continue;
+ };
+ if !matches!(item_use.vis, syn::Visibility::Public(_)) {
+ continue;
+ }
+ let attributes = item_use
+ .attrs
+ .iter()
+ .map(compact_tokens)
+ .collect::<Vec<_>>();
+ flatten_public_use_tree(
+ &item_use.tree,
+ &mut Vec::new(),
+ item_use.leading_colon.is_some(),
+ &attributes,
+ &mut routes,
+ );
+ }
+ routes
+}
+
+fn flatten_public_use_tree(
+ tree: &UseTree,
+ prefix: &mut Vec<String>,
+ absolute: bool,
+ attributes: &[String],
+ routes: &mut Vec<PublicUseRoute>,
+) {
+ match tree {
+ UseTree::Path(path) => {
+ prefix.push(path.ident.to_string());
+ flatten_public_use_tree(&path.tree, prefix, absolute, attributes, routes);
+ prefix.pop();
+ }
+ UseTree::Name(name) => {
+ let mut segments = prefix.clone();
+ segments.push(name.ident.to_string());
+ routes.push(PublicUseRoute {
+ exported_name: name.ident.to_string(),
+ segments,
+ renamed: false,
+ glob: false,
+ absolute,
+ attributes: attributes.to_vec(),
+ });
+ }
+ UseTree::Rename(rename) => {
+ let mut segments = prefix.clone();
+ segments.push(rename.ident.to_string());
+ routes.push(PublicUseRoute {
+ exported_name: rename.rename.to_string(),
+ segments,
+ renamed: true,
+ glob: false,
+ absolute,
+ attributes: attributes.to_vec(),
+ });
+ }
+ UseTree::Glob(_) => routes.push(PublicUseRoute {
+ exported_name: "*".to_owned(),
+ segments: prefix.clone(),
+ renamed: false,
+ glob: true,
+ absolute,
+ attributes: attributes.to_vec(),
+ }),
+ UseTree::Group(group) => {
+ for item in &group.items {
+ flatten_public_use_tree(item, prefix, absolute, attributes, routes);
+ }
+ }
+ }
+}
+
+fn exact_struct<'a>(file: &'a syn::File, name: &str) -> Result<&'a syn::ItemStruct, String> {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Struct(item) if item.ident == name => Some(item),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [item] = matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define struct `{name}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ Ok(item)
+}
+
+fn exact_enum<'a>(file: &'a syn::File, name: &str) -> Result<&'a syn::ItemEnum, String> {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Enum(item) if item.ident == name => Some(item),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [item] = matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define enum `{name}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ Ok(item)
+}
+
+fn exact_byte_string_const(file: &syn::File, name: &str) -> Result<Vec<u8>, String> {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Const(item) if item.ident == name => Some(item),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [item] = matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define byte-string const `{name}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ let syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::ByteStr(value),
+ ..
+ }) = item.expr.as_ref()
+ else {
+ return Err(format!("`{name}` must be one literal byte string"));
+ };
+ Ok(value.value())
+}
+
+fn exact_string_const(file: &syn::File, name: &str) -> Result<String, String> {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Const(item) if item.ident == name => Some(item),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [item] = matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define string const `{name}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ let syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::Str(value),
+ ..
+ }) = item.expr.as_ref()
+ else {
+ return Err(format!("`{name}` must be one literal string"));
+ };
+ Ok(value.value())
+}
+
+fn exact_string_slice_const(file: &syn::File, name: &str) -> Result<Vec<String>, String> {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Const(item) if item.ident == name => Some(item),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [item] = matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define string-slice const `{name}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ let syn::Expr::Reference(reference) = item.expr.as_ref() else {
+ return Err(format!("`{name}` must be a reference to one literal array"));
+ };
+ let syn::Expr::Array(array) = reference.expr.as_ref() else {
+ return Err(format!("`{name}` must be a reference to one literal array"));
+ };
+ array
+ .elems
+ .iter()
+ .map(|element| match element {
+ syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::Str(value),
+ ..
+ }) => Ok(value.value()),
+ _ => Err(format!("`{name}` must contain only literal strings")),
+ })
+ .collect()
+}
+
+fn sqlx_query_literals(function: &syn::ItemFn) -> Vec<String> {
+ struct Collector(Vec<String>);
+ impl<'ast> syn::visit::Visit<'ast> for Collector {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ if compact_tokens(call.func.as_ref()) == "sqlx::query"
+ && let Some(syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::Str(value),
+ ..
+ })) = call.args.first()
+ {
+ self.0.push(value.value());
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+ }
+ use syn::visit::Visit;
+ let mut collector = Collector(Vec::new());
+ collector.visit_block(&function.block);
+ collector.0
+}
+
+fn sqlx_query_family_literals(function: &syn::ItemFn) -> Vec<String> {
+ struct Collector(Vec<String>);
+ impl<'ast> syn::visit::Visit<'ast> for Collector {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ let function = compact_tokens(call.func.as_ref());
+ if matches!(function.as_str(), "sqlx::query" | "sqlx::query_scalar")
+ && let Some(syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::Str(value),
+ ..
+ })) = call.args.first()
+ {
+ self.0.push(value.value());
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+ }
+ use syn::visit::Visit;
+ let mut collector = Collector(Vec::new());
+ collector.visit_block(&function.block);
+ collector.0
+}
+
+fn sqlx_query_literals_in_expr(expression: &syn::Expr) -> Vec<String> {
+ struct Collector(Vec<String>);
+ impl<'ast> syn::visit::Visit<'ast> for Collector {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ if compact_tokens(call.func.as_ref()) == "sqlx::query"
+ && let Some(syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::Str(value),
+ ..
+ })) = call.args.first()
+ {
+ self.0.push(value.value());
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+ }
+ use syn::visit::Visit;
+ let mut collector = Collector(Vec::new());
+ collector.visit_expr(expression);
+ collector.0
+}
+
+fn digest_section_literals(function: &syn::ItemFn) -> Vec<String> {
+ struct Collector(Vec<String>);
+ impl<'ast> syn::visit::Visit<'ast> for Collector {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ if compact_tokens(call.func.as_ref()) == "digest_section"
+ && let Some(syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::ByteStr(value),
+ ..
+ })) = call.args.iter().nth(1)
+ {
+ self.0
+ .push(String::from_utf8_lossy(&value.value()).into_owned());
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+ }
+ use syn::visit::Visit;
+ let mut collector = Collector(Vec::new());
+ collector.visit_block(&function.block);
+ collector.0
+}
+
+fn digest_field_witnesses(function: &syn::ItemFn) -> Result<Vec<(String, String)>, String> {
+ struct Collector {
+ fields: Vec<(String, String)>,
+ }
+ impl<'ast> syn::visit::Visit<'ast> for Collector {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ let function_name = compact_tokens(call.func.as_ref());
+ if let Some(framing) = digest_field_call_framing(&function_name) {
+ if let Some(syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::Str(field),
+ ..
+ })) = call.args.last()
+ {
+ self.fields.push((field.value(), framing.to_owned()));
+ }
+ } else if let Some(framing) = direct_digest_call_framing(&function_name)
+ && let Some(value) = call.args.iter().nth(1)
+ {
+ let literals = expression_string_literals(value);
+ if let [field] = literals.as_slice() {
+ self.fields.push((field.clone(), framing.to_owned()));
+ }
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+
+ fn visit_expr_for_loop(&mut self, expression: &'ast syn::ExprForLoop) {
+ let syn::Pat::Ident(binding) = expression.pat.as_ref() else {
+ syn::visit::visit_expr_for_loop(self, expression);
+ return;
+ };
+ let syn::Expr::Array(array) = expression.expr.as_ref() else {
+ syn::visit::visit_expr_for_loop(self, expression);
+ return;
+ };
+ let fields = array
+ .elems
+ .iter()
+ .filter_map(|element| match element {
+ syn::Expr::Lit(syn::ExprLit {
+ lit: syn::Lit::Str(value),
+ ..
+ }) => Some(value.value()),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ if fields.len() == array.elems.len()
+ && let Some(framing) =
+ loop_digest_field_framing(&expression.body, binding.ident.to_string().as_str())
+ {
+ self.fields
+ .extend(fields.into_iter().map(|field| (field, framing.to_owned())));
+ }
+ syn::visit::visit_expr_for_loop(self, expression);
+ }
+ }
+ use syn::visit::Visit;
+ let mut collector = Collector { fields: Vec::new() };
+ collector.visit_block(&function.block);
+ Ok(collector.fields)
+}
+
+fn digest_field_call_framing(function_name: &str) -> Option<&'static str> {
+ match function_name {
+ "digest_i64_field" => Some("i64"),
+ "digest_optional_i64_field" => Some("optional_i64"),
+ "digest_text_field" => Some("text"),
+ "digest_optional_text_field" => Some("optional_text"),
+ "digest_bool_field" => Some("boolean"),
+ "digest_blob_field" => Some("blob"),
+ _ => None,
+ }
+}
+
+fn direct_digest_call_framing(function_name: &str) -> Option<&'static str> {
+ match function_name {
+ "digest_i64" => Some("i64"),
+ "digest_text" => Some("text"),
+ "digest_optional_text" => Some("optional_text"),
+ _ => None,
+ }
+}
+
+fn expression_string_literals(expression: &syn::Expr) -> Vec<String> {
+ struct Collector(Vec<String>);
+ impl<'ast> syn::visit::Visit<'ast> for Collector {
+ fn visit_lit_str(&mut self, literal: &'ast syn::LitStr) {
+ self.0.push(literal.value());
+ }
+ }
+ use syn::visit::Visit;
+ let mut collector = Collector(Vec::new());
+ collector.visit_expr(expression);
+ collector.0
+}
+
+fn loop_digest_field_framing(block: &syn::Block, binding: &str) -> Option<&'static str> {
+ struct Collector<'a> {
+ binding: &'a str,
+ framings: Vec<&'static str>,
+ }
+ impl<'ast> syn::visit::Visit<'ast> for Collector<'_> {
+ fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) {
+ let function_name = compact_tokens(call.func.as_ref());
+ if let Some(framing) = digest_field_call_framing(&function_name)
+ && call
+ .args
+ .last()
+ .is_some_and(|argument| compact_tokens(argument) == self.binding)
+ {
+ self.framings.push(framing);
+ }
+ syn::visit::visit_expr_call(self, call);
+ }
+ }
+ use syn::visit::Visit;
+ let mut collector = Collector {
+ binding,
+ framings: Vec::new(),
+ };
+ collector.visit_block(block);
+ match collector.framings.as_slice() {
+ [framing] => Some(*framing),
+ _ => None,
+ }
+}
+
+fn exact_impl<'a>(file: &'a syn::File, name: &str) -> Result<&'a syn::ItemImpl, String> {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Impl(item)
+ if item.trait_.is_none() && compact_tokens(item.self_ty.as_ref()) == name =>
+ {
+ Some(item)
+ }
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [item] = matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define one inherent impl for `{name}`; found {}",
+ matches.len()
+ ));
+ };
+ Ok(item)
+}
+
+fn exact_associated_method<'a>(
+ file: &'a syn::File,
+ owner: &str,
+ method: &str,
+) -> Result<&'a syn::ImplItemFn, String> {
+ let mut matches = Vec::new();
+ for item in &file.items {
+ let Item::Impl(item) = item else {
+ continue;
+ };
+ if compact_tokens(item.self_ty.as_ref()) != owner {
+ continue;
+ }
+ for member in &item.items {
+ if let syn::ImplItem::Fn(function) = member
+ && function.sig.ident == method
+ {
+ matches.push(function);
+ }
+ }
+ }
+ let [function] = matches.as_slice() else {
+ return Err(format!(
+ "{owner} must define `{method}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ Ok(function)
+}
+
+struct FreeFunctionCollector<'name, 'ast> {
+ name: &'name str,
+ matches: Vec<&'ast syn::ItemFn>,
+}
+
+impl<'ast> syn::visit::Visit<'ast> for FreeFunctionCollector<'_, 'ast> {
+ fn visit_item_fn(&mut self, function: &'ast syn::ItemFn) {
+ if function.sig.ident == self.name {
+ self.matches.push(function);
+ }
+ syn::visit::visit_item_fn(self, function);
+ }
+}
+
+fn exact_free_function<'a>(file: &'a syn::File, name: &str) -> Result<&'a syn::ItemFn, String> {
+ use syn::visit::Visit;
+ let mut collector = FreeFunctionCollector {
+ name,
+ matches: Vec::new(),
+ };
+ collector.visit_file(file);
+ let [function] = collector.matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define free function `{name}` exactly once; found {}",
+ collector.matches.len()
+ ));
+ };
+ Ok(function)
+}
+
+fn exact_top_level_function<'a>(
+ file: &'a syn::File,
+ name: &str,
+) -> Result<&'a syn::ItemFn, String> {
+ let matches = file
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ Item::Fn(function) if function.sig.ident == name => Some(function),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+ let [function] = matches.as_slice() else {
+ return Err(format!(
+ "governed Rust source must define top-level function `{name}` exactly once; found {}",
+ matches.len()
+ ));
+ };
+ Ok(function)
+}
+
+fn compact_signature(source: &str) -> Result<String, String> {
+ let function = syn::parse_str::<syn::ImplItemFn>(&format!("{source} {{ unreachable!() }}"))
+ .map_err(|error| format!("parse authoritative signature `{source}`: {error}"))?;
+ Ok(compact_tokens(&function.sig))
+}
+
+fn strip_doc_attributes(attributes: &mut Vec<syn::Attribute>) {
+ attributes.retain(|attribute| !attribute.path().is_ident("doc"));
+}
+
+fn descriptor_for_file(workspace_root: &Path, relative: &str) -> Result<FileDescriptor, String> {
+ descriptor_for_bytes(relative, &read_regular_file(workspace_root, relative)?)
+}
+
+fn descriptor_for_bytes(relative: &str, bytes: &[u8]) -> Result<FileDescriptor, String> {
+ Ok(FileDescriptor {
+ path: relative.to_owned(),
+ byte_length: byte_length(relative, bytes)?,
+ sha256: sha256_hex(bytes),
+ hash_algorithm: HASH_ALGORITHM.to_owned(),
+ })
+}
+
+fn validate_file_descriptor(
+ path: &str,
+ byte_length: u64,
+ sha256: &str,
+ hash_algorithm: &str,
+) -> Result<(), String> {
+ if byte_length == 0 || hash_algorithm != HASH_ALGORITHM {
+ return Err(format!("file descriptor `{path}` is invalid"));
+ }
+ validate_sha256(path, sha256)
+}
+
+fn byte_length(relative: &str, bytes: &[u8]) -> Result<u64, String> {
+ u64::try_from(bytes.len()).map_err(|_| format!("{relative} byte length does not fit u64"))
+}
+
+fn rust_file(workspace_root: &Path, relative: &str) -> Result<syn::File, String> {
+ let source = rust_source(workspace_root, relative)?;
+ syn::parse_file(&source).map_err(|error| format!("parse {relative}: {error}"))
+}
+
+fn rust_source(workspace_root: &Path, relative: &str) -> Result<String, String> {
+ let bytes = read_regular_file(workspace_root, relative)?;
+ std::str::from_utf8(&bytes)
+ .map(str::to_owned)
+ .map_err(|error| format!("{relative} must be UTF-8 Rust: {error}"))
+}
+
+fn compact_tokens(tokens: &impl ToTokens) -> String {
+ tokens.to_token_stream().to_string().replace(' ', "")
+}
+
+fn owned(values: &[&str]) -> Vec<String> {
+ values.iter().map(|value| (*value).to_owned()).collect()
+}
+
+fn validate_unique<'a>(
+ label: &str,
+ values: impl IntoIterator<Item = &'a str>,
+) -> Result<(), String> {
+ let values = values.into_iter().collect::<Vec<_>>();
+ let unique = values.iter().copied().collect::<BTreeSet<_>>();
+ if unique.len() != values.len() {
+ return Err(format!("{label} must contain no duplicate values"));
+ }
+ Ok(())
+}
+
+fn canonical_json_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>, String> {
+ let mut bytes = serde_json::to_vec_pretty(value)
+ .map_err(|error| format!("serialize canonical JSON: {error}"))?;
+ bytes.push(b'\n');
+ Ok(bytes)
+}
+
+fn validate_canonical_json<T: Serialize>(
+ relative: &str,
+ bytes: &[u8],
+ value: &T,
+) -> Result<(), String> {
+ let expected = canonical_json_bytes(value)?;
+ if bytes != expected {
+ return Err(format!(
+ "{relative} must use canonical pretty JSON with one trailing newline"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_json_schema(schema: &Value, manifest: &Value) -> Result<(), String> {
+ let validator = jsonschema::validator_for(schema)
+ .map_err(|error| format!("compile {MANIFEST_SCHEMA_RELATIVE}: {error}"))?;
+ let errors = validator
+ .iter_errors(manifest)
+ .map(|error| error.to_string())
+ .collect::<Vec<_>>();
+ if errors.is_empty() {
+ Ok(())
+ } else {
+ Err(format!(
+ "{MANIFEST_RELATIVE} violates {MANIFEST_SCHEMA_RELATIVE}: {}",
+ errors.join("; ")
+ ))
+ }
+}
+
+fn validate_digest_sidecar(relative: &str, bytes: &[u8]) -> Result<(), String> {
+ let value =
+ std::str::from_utf8(bytes).map_err(|error| format!("{relative} must be UTF-8: {error}"))?;
+ let Some(digest) = value.strip_suffix('\n') else {
+ return Err(format!("{relative} must end with one newline"));
+ };
+ if digest.contains('\n') {
+ return Err(format!("{relative} must contain one digest line"));
+ }
+ validate_sha256(relative, digest)
+}
+
+fn validate_sha256(label: &str, value: &str) -> Result<(), String> {
+ if value.len() != 64
+ || !value
+ .as_bytes()
+ .iter()
+ .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(byte))
+ {
+ return Err(format!("{label} must be canonical lowercase SHA-256 hex"));
+ }
+ Ok(())
+}
+
+fn validate_vector_expected_digest(value: &str) -> Result<(), String> {
+ validate_sha256("vector expected digest", value)?;
+ if value.as_bytes().iter().all(|byte| *byte == b'0') {
+ return Err("vector expected digest must not be an all-zero bootstrap value".to_owned());
+ }
+ Ok(())
+}
+
+fn sha256_hex(bytes: &[u8]) -> String {
+ hex::encode(Sha256::digest(bytes))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn workspace_root() -> std::path::PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .and_then(Path::parent)
+ .expect("xtask lives under tools in the workspace")
+ .to_path_buf()
+ }
+
+ #[test]
+ fn source_inventory_is_unique_complete_and_excludes_generated_outputs() {
+ validate_source_inventory().expect("raw-source rebuild source inventory");
+ let mut missing_compiler_input = SOURCE_SPECS.to_vec();
+ missing_compiler_input.retain(|spec| spec.path != FLAKE_LOCK_RELATIVE);
+ let error = validate_source_inventory_specs(&missing_compiler_input)
+ .expect_err("delegated compiler input omission must fail closed");
+ assert!(error.contains("nix_input_lock_authority"), "{error}");
+ let root = workspace_root();
+ validate_complete_event_store_source_closure(&root)
+ .expect("complete event-store Rust source closure");
+ validate_successor_compiler_input_authority(&root)
+ .expect("complete event-store compiler-input authority");
+ validate_delegated_compiler_source_pins(&root).expect("delegated compiler source pins");
+ validate_xtask_manifest_authority(&root).expect("xtask compiler authority");
+ }
+
+ #[test]
+ fn delegated_compiler_sources_and_xtask_targets_fail_closed() {
+ let root = workspace_root();
+ let pinned_workspace = tempfile::tempdir().expect("compiler pin workspace");
+ for (relative, _) in DELEGATED_COMPILER_SOURCE_PINS {
+ let destination = pinned_workspace.path().join(relative);
+ fs::create_dir_all(destination.parent().expect("compiler pin parent"))
+ .expect("create compiler pin parent");
+ fs::copy(root.join(relative), destination).expect("copy compiler pin source");
+ }
+ validate_delegated_compiler_source_pins(pinned_workspace.path())
+ .expect("current compiler source pins");
+ fs::write(pinned_workspace.path().join(FLAKE_LOCK_RELATIVE), "{}\n")
+ .expect("mutate pinned flake lock");
+ validate_delegated_compiler_source_pins(pinned_workspace.path())
+ .expect_err("compiler source mutation must fail closed");
+
+ let manifest_workspace = tempfile::tempdir().expect("xtask manifest workspace");
+ let manifest_path = manifest_workspace.path().join(XTASK_MANIFEST_RELATIVE);
+ fs::create_dir_all(manifest_path.parent().expect("xtask manifest parent"))
+ .expect("create xtask manifest parent");
+ fs::copy(root.join(XTASK_MANIFEST_RELATIVE), &manifest_path).expect("copy xtask manifest");
+ validate_xtask_manifest_authority(manifest_workspace.path())
+ .expect("current xtask manifest authority");
+ fs::write(
+ manifest_workspace.path().join("tools/xtask/build.rs"),
+ "fn main() {}\n",
+ )
+ .expect("write injected build script");
+ validate_xtask_manifest_authority(manifest_workspace.path())
+ .expect_err("xtask build-script injection must fail closed");
+ }
+
+ #[test]
+ fn xtask_auto_target_flags_reject_omission_and_retargeting() {
+ let root = workspace_root();
+ let manifest =
+ regular_utf8_source(&root, XTASK_MANIFEST_RELATIVE).expect("current xtask manifest");
+
+ for flag in XTASK_REQUIRED_DISABLED_AUTO_TARGET_FLAGS {
+ let assignment = format!("{flag} = false\n");
+ let omitted = manifest.replacen(&assignment, "", 1);
+ assert_ne!(omitted, manifest, "{flag} omission fixture must mutate");
+ let retargeted = manifest.replacen(&assignment, &format!("{flag} = true\n"), 1);
+ assert_ne!(retargeted, manifest, "{flag} retarget fixture must mutate");
+
+ for (mutation, label) in [(omitted, "omission"), (retargeted, "retarget")] {
+ let workspace = tempfile::tempdir().expect("xtask flag fixture workspace");
+ let path = workspace.path().join(XTASK_MANIFEST_RELATIVE);
+ fs::create_dir_all(path.parent().expect("xtask manifest fixture parent"))
+ .expect("create xtask manifest fixture parent");
+ fs::write(&path, mutation).expect("write xtask manifest mutation");
+ let error = validate_xtask_manifest_authority(workspace.path())
+ .expect_err("xtask auto-target flag mutation must fail closed");
+ assert!(
+ error.contains(flag),
+ "{flag} {label} error must identify the exact flag: {error}"
+ );
+ }
+ }
+ }
+
+ #[test]
+ fn xtask_auto_target_source_paths_are_forbidden() {
+ let root = workspace_root();
+ for (forbidden, injected) in [
+ ("tools/xtask/build.rs", "tools/xtask/build.rs"),
+ ("tools/xtask/src/lib.rs", "tools/xtask/src/lib.rs"),
+ ("tools/xtask/src/bin.rs", "tools/xtask/src/bin.rs"),
+ ("tools/xtask/src/bin", "tools/xtask/src/bin/injected.rs"),
+ ("tools/xtask/tests", "tools/xtask/tests/injected.rs"),
+ ("tools/xtask/examples", "tools/xtask/examples/injected.rs"),
+ ("tools/xtask/benches", "tools/xtask/benches/injected.rs"),
+ ] {
+ let workspace = tempfile::tempdir().expect("xtask path fixture workspace");
+ let manifest_path = workspace.path().join(XTASK_MANIFEST_RELATIVE);
+ fs::create_dir_all(
+ manifest_path
+ .parent()
+ .expect("xtask manifest fixture parent"),
+ )
+ .expect("create xtask manifest fixture parent");
+ fs::copy(root.join(XTASK_MANIFEST_RELATIVE), &manifest_path)
+ .expect("copy xtask manifest fixture");
+
+ let injected_path = workspace.path().join(injected);
+ fs::create_dir_all(injected_path.parent().expect("injected auto-target parent"))
+ .expect("create injected auto-target parent");
+ fs::write(&injected_path, "fn main() {}\n").expect("write injected auto-target source");
+
+ let error = validate_xtask_manifest_authority(workspace.path())
+ .expect_err("xtask auto-target source path must fail closed");
+ assert!(
+ error.contains(forbidden),
+ "auto-target error must identify `{forbidden}`: {error}"
+ );
+ }
+ }
+
+ #[test]
+ fn rebuild_marker_token_is_non_cloneable_and_consumed_by_both_routes() {
+ let root = workspace_root();
+ let reconciliation_relative = "crates/event_store/src/nip09/reconciliation_v1.rs";
+ let reconciliation =
+ rust_source(&root, reconciliation_relative).expect("reconciliation marker authority");
+ let rebuild = rust_source(&root, REBUILD_RUNTIME_SOURCE_RELATIVE)
+ .expect("raw rebuild marker authority");
+ let reconciliation_file =
+ syn::parse_file(&reconciliation).expect("parse reconciliation marker authority");
+ let rebuild_file = syn::parse_file(&rebuild).expect("parse raw rebuild marker authority");
+ validate_rebuild_marker_token_authority(
+ &reconciliation_file,
+ reconciliation_relative,
+ &rebuild_file,
+ REBUILD_RUNTIME_SOURCE_RELATIVE,
+ )
+ .expect("current rebuild marker token authority");
+
+ let cloneable = reconciliation.replacen(
+ "struct SourceRebuildMarkerTokenV1 {",
+ "#[derive(Clone)]\nstruct SourceRebuildMarkerTokenV1 {",
+ 1,
+ );
+ assert_ne!(cloneable, reconciliation, "cloneable fixture must mutate");
+ let cloneable = syn::parse_file(&cloneable).expect("parse cloneable marker fixture");
+ validate_rebuild_marker_token_authority(
+ &cloneable,
+ reconciliation_relative,
+ &rebuild_file,
+ REBUILD_RUNTIME_SOURCE_RELATIVE,
+ )
+ .expect_err("cloneable marker token must fail closed");
+
+ let non_consuming = rebuild.replacen(
+ "close_source_rebuild_marker(connection, marker).await?;",
+ "close_source_rebuild_marker(connection, marker.clone()).await?;",
+ 1,
+ );
+ assert_ne!(
+ non_consuming, rebuild,
+ "non-consuming marker fixture must mutate"
+ );
+ let non_consuming =
+ syn::parse_file(&non_consuming).expect("parse non-consuming marker fixture");
+ validate_rebuild_marker_token_authority(
+ &reconciliation_file,
+ reconciliation_relative,
+ &non_consuming,
+ REBUILD_RUNTIME_SOURCE_RELATIVE,
+ )
+ .expect_err("cloned marker consumption must fail closed");
+
+ let forged = rebuild.replacen(
+ " close_source_rebuild_marker(connection, marker).await?;",
+ " let marker = super::SourceRebuildMarkerTokenV1 { generation: plan.generation };\n close_source_rebuild_marker(connection, marker).await?;",
+ 1,
+ );
+ assert_ne!(forged, rebuild, "forged marker fixture must mutate");
+ let forged = syn::parse_file(&forged).expect("parse forged marker fixture");
+ validate_rebuild_marker_token_authority(
+ &reconciliation_file,
+ reconciliation_relative,
+ &forged,
+ REBUILD_RUNTIME_SOURCE_RELATIVE,
+ )
+ .expect_err("forged marker shadow must fail closed");
+ }
+
+ #[test]
+ fn compiler_input_authority_rejects_unapproved_inputs_and_path_retargeting() {
+ for source in [
+ "#[path = \"escape.rs\"]\nmod escape;",
+ "#[cfg_attr(target_os = \"ios\", path = \"escape.rs\")]\nmod escape;",
+ ] {
+ let file = syn::parse_file(source).expect("path-retargeting probe parses");
+ let error = validate_exact_successor_compiler_inputs("probe.rs", &file, &[])
+ .expect_err("path retargeting must fail closed");
+ assert!(error.contains("no path retargeting"), "{error}");
+ }
+
+ let file = syn::parse_file("const ESCAPE: &str = include_str!(\"escape.rs\");")
+ .expect("compiler-input probe parses");
+ let error = validate_exact_successor_compiler_inputs("probe.rs", &file, &[])
+ .expect_err("unapproved compiler input must fail closed");
+ assert!(error.contains("include_str!"), "{error}");
+ }
+
+ #[test]
+ fn executable_authority_rejects_should_panic_and_extra_attributes() {
+ let workspace = tempfile::tempdir().expect("executable authority workspace");
+ let relative = "probe.rs";
+ fs::write(
+ workspace.path().join(relative),
+ "#[test]\n#[should_panic]\nfn governed_test() { panic!(\"bypass\"); }\n",
+ )
+ .expect("write should-panic probe");
+ let error = validate_executable_test(workspace.path(), relative, "governed_test")
+ .expect_err("should-panic authority must fail closed");
+ assert!(error.contains("exactly one unconditional test attribute"));
+ }
+
+ #[test]
+ fn command_reachability_rejects_string_literal_witnesses() {
+ let root = workspace_root();
+ let workspace = tempfile::tempdir().expect("command authority workspace");
+ for relative in [CONTRACT_COMMAND_SOURCE_RELATIVE, XTASK_MAIN_SOURCE_RELATIVE] {
+ let destination = workspace.path().join(relative);
+ fs::create_dir_all(destination.parent().expect("command source parent"))
+ .expect("create command source parent");
+ fs::copy(root.join(relative), destination).expect("copy command source");
+ }
+ let main_path = workspace.path().join(XTASK_MAIN_SOURCE_RELATIVE);
+ let main = fs::read_to_string(&main_path).expect("xtask main source");
+ let bypass = main.replacen(
+ "[] => contract::validate_raw_source_rebuild_manifest(&workspace_root()),",
+ "[] => { let _ = \"contract::validate_raw_source_rebuild_manifest(&workspace_root())\"; Ok(()) },",
+ 1,
+ );
+ assert_ne!(bypass, main, "command bypass fixture must mutate");
+ fs::write(main_path, bypass).expect("write command bypass");
+ let error = validate_command_reachability(workspace.path())
+ .expect_err("string literal must not satisfy command reachability");
+ assert!(error.contains("command arm drifted"), "{error}");
+ }
+
+ #[test]
+ fn delegated_suite_inventory_rejects_missing_and_unrepresented_authorities() {
+ let root = workspace_root();
+ let bytes = read_regular_file(&root, RESULT_VECTOR_CANONICAL_RELATIVE)
+ .expect("raw-source rebuild vector");
+ let vector: RawSourceRebuildVector =
+ serde_json::from_slice(&bytes).expect("typed raw-source rebuild vector");
+ validate_delegated_suite_inventory(&vector).expect("current delegated suite inventory");
+
+ let mut missing = vector.clone();
+ missing
+ .delegated_suite
+ .authorities
+ .pop()
+ .expect("nonempty suite");
+ let error = validate_delegated_suite_inventory(&missing)
+ .expect_err("missing delegated authority must fail closed");
+ assert!(error.contains("missing"), "{error}");
+
+ let mut unrepresented = vector.clone();
+ unrepresented
+ .delegated_suite
+ .authorities
+ .push(DelegatedAuthority {
+ authority: "unrepresented_test_v1".to_owned(),
+ authority_path: REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE.to_owned(),
+ });
+ let error = validate_delegated_suite_inventory(&unrepresented)
+ .expect_err("unrepresented delegated authority must fail closed");
+ assert!(error.contains("unrepresented"), "{error}");
+
+ let mut duplicate = vector.clone();
+ duplicate
+ .delegated_suite
+ .authorities
+ .push(duplicate.delegated_suite.authorities[0].clone());
+ let error = validate_delegated_suite_inventory(&duplicate)
+ .expect_err("duplicate delegated authority must fail closed");
+ assert!(error.contains("duplicate"), "{error}");
+ }
+
+ #[test]
+ fn delegated_suite_contract_lane_rejects_filters_and_lexical_decoys() {
+ let root = workspace_root();
+ let flake = regular_utf8_source(&root, FLAKE_SOURCE_RELATIVE).expect("flake authority");
+ let apps = regular_utf8_source(&root, CONTRACT_APP_SOURCE_RELATIVE)
+ .expect("contract app authority");
+ let common = regular_utf8_source(&root, CONTRACT_LANE_SOURCE_RELATIVE)
+ .expect("contract lane authority");
+ let toolchains = regular_utf8_source(&root, TOOLCHAIN_ROUTING_SOURCE_RELATIVE)
+ .expect("toolchain routing authority");
+ validate_delegated_suite_contract_lane_sources(&flake, &apps, &common, &toolchains)
+ .expect("current delegated suite contract lane");
+
+ let flake_bypass = flake
+ .replacen(
+ "apps = import ./build/nix/apps.nix {",
+ "apps = import ./build/nix/apps-bypass.nix {",
+ 1,
+ )
+ .replacen(
+ "description = \"Radroots Core Libraries\";",
+ "description = \"apps = import ./build/nix/apps.nix {\";",
+ 1,
+ );
+ assert_ne!(flake_bypass, flake, "flake bypass fixture must mutate");
+ validate_delegated_suite_contract_lane_sources(&flake_bypass, &apps, &common, &toolchains)
+ .expect_err("string decoy must not satisfy flake app routing");
+
+ let mut common_import_bypass = flake
+ .replacen(
+ "common = import ./build/nix/common.nix {",
+ "common = import ./build/nix/common-bypass.nix {",
+ 1,
+ )
+ .replacen(
+ "description = \"Radroots Core Libraries\";",
+ "description = \"common = import ./build/nix/common.nix {\";",
+ 1,
+ );
+ common_import_bypass.push_str("\n# common = import ./build/nix/common.nix {\n");
+ assert_ne!(
+ common_import_bypass, flake,
+ "common import bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &common_import_bypass,
+ &apps,
+ &common,
+ &toolchains,
+ )
+ .expect_err("string and comment decoys must not satisfy flake common routing");
+
+ let common_argument_bypass = flake.replacen(
+ " inherit lib pkgs toolchains;",
+ " inherit lib pkgs;\n toolchains = import ./build/nix/toolchains-bypass.nix { inherit pkgs; };",
+ 1,
+ );
+ assert_ne!(
+ common_argument_bypass, flake,
+ "common argument bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &common_argument_bypass,
+ &apps,
+ &common,
+ &toolchains,
+ )
+ .expect_err("common toolchain argument substitution must fail closed");
+
+ let toolchain_import_bypass = flake
+ .replacen(
+ "toolchains = import ./build/nix/toolchains.nix {",
+ "toolchains = import ./build/nix/toolchains-bypass.nix {",
+ 1,
+ )
+ .replacen(
+ "description = \"Radroots Core Libraries\";",
+ "description = \"toolchains = import ./build/nix/toolchains.nix {\";",
+ 1,
+ );
+ assert_ne!(
+ toolchain_import_bypass, flake,
+ "toolchain import bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &toolchain_import_bypass,
+ &apps,
+ &common,
+ &toolchains,
+ )
+ .expect_err("string decoy must not satisfy flake toolchain routing");
+
+ let toolchain_postfix_bypass = flake.replacen(
+ "toolchains = import ./build/nix/toolchains.nix { inherit pkgs; };",
+ "toolchains = import ./build/nix/toolchains.nix { inherit pkgs; } // { stable = null; };",
+ 1,
+ );
+ assert_ne!(
+ toolchain_postfix_bypass, flake,
+ "toolchain postfix bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &toolchain_postfix_bypass,
+ &apps,
+ &common,
+ &toolchains,
+ )
+ .expect_err("toolchain postfix override must fail closed");
+
+ let common_postfix_bypass = flake.replacen(
+ " inherit lib pkgs toolchains;\n };",
+ " inherit lib pkgs toolchains;\n } // { contractCommand = \"cargo test -q -p xtask\"; };",
+ 1,
+ );
+ assert_ne!(
+ common_postfix_bypass, flake,
+ "common postfix bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &common_postfix_bypass,
+ &apps,
+ &common,
+ &toolchains,
+ )
+ .expect_err("common postfix override must fail closed");
+
+ let apps_postfix_bypass = flake.replacen(
+ " ;\n };\n\n checks =",
+ " ;\n } // { contract = { type = \"app\"; program = \"/bin/false\"; }; };\n\n checks =",
+ 1,
+ );
+ assert_ne!(
+ apps_postfix_bypass, flake,
+ "apps postfix bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &apps_postfix_bypass,
+ &apps,
+ &common,
+ &toolchains,
+ )
+ .expect_err("apps postfix override must fail closed");
+
+ let apps_bypass = apps
+ .replacen(
+ "command = common.contractCommand;",
+ "command = \"cargo test -q -p xtask\";",
+ 1,
+ )
+ .replacen(
+ "description = \"Run the core-library contract lane\";",
+ "description = \"command = common.contractCommand;\";",
+ 1,
+ );
+ assert_ne!(apps_bypass, apps, "apps bypass fixture must mutate");
+ validate_delegated_suite_contract_lane_sources(&flake, &apps_bypass, &common, &toolchains)
+ .expect_err("string decoy must not satisfy contract app command routing");
+
+ let contract_path_bypass = apps.replacen(
+ " command = common.contractCommand;\n };",
+ " command = common.contractCommand;\n pathPrefix = \"\";\n };",
+ 1,
+ );
+ assert_ne!(
+ contract_path_bypass, apps,
+ "contract path bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &flake,
+ &contract_path_bypass,
+ &common,
+ &toolchains,
+ )
+ .expect_err("contract path override must fail closed");
+
+ let package_bypass = common.replacen(
+ " \"radroots_event_store\"\n",
+ " # \"radroots_event_store\"\n",
+ 1,
+ );
+ assert_ne!(package_bypass, common, "package bypass fixture must mutate");
+ validate_delegated_suite_contract_lane_sources(&flake, &apps, &package_bypass, &toolchains)
+ .expect_err("commented package must not satisfy literal package inventory");
+
+ let cargo_source_bypass = common.replacen(
+ " ../../build/nix/toolchains.nix\n",
+ " # ../../build/nix/toolchains.nix\n",
+ 1,
+ );
+ assert_ne!(
+ cargo_source_bypass, common,
+ "cargo source bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &flake,
+ &apps,
+ &cargo_source_bypass,
+ &toolchains,
+ )
+ .expect_err("commented source path must not satisfy cargoSource closure");
+
+ let toolchain_bypass = toolchains.replacen(
+ "stable = pkgs.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml;",
+ "stable = pkgs.rust-bin.fromRustupToolchainFile ../../rust-toolchain-bypass.toml;",
+ 1,
+ );
+ assert_ne!(
+ toolchain_bypass, toolchains,
+ "stable toolchain bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(&flake, &apps, &common, &toolchain_bypass)
+ .expect_err("stable toolchain bypass must fail closed");
+
+ let command_bypass = common.replacen(
+ "cargo test -q ${coreContractCargoArgs}",
+ "cargo test -q -p xtask\n # cargo test -q ${coreContractCargoArgs}",
+ 1,
+ );
+ assert_ne!(
+ command_bypass, common,
+ "contract command bypass fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(&flake, &apps, &command_bypass, &toolchains)
+ .expect_err("shell-comment decoy must not satisfy unfiltered package tests");
+
+ let selector_bypass = common.replacen(
+ "radroots_nostr/events\";",
+ "radroots_nostr/events --lib\";",
+ 1,
+ );
+ assert_ne!(
+ selector_bypass, common,
+ "cargo selector fixture must mutate"
+ );
+ validate_delegated_suite_contract_lane_sources(
+ &flake,
+ &apps,
+ &selector_bypass,
+ &toolchains,
+ )
+ .expect_err("Cargo target selector must not skip the integration executor");
+ }
+
+ #[test]
+ fn delegated_suite_flake_lock_rejects_unlocked_or_redirected_direct_inputs() {
+ let root = workspace_root();
+ validate_flake_lock_authority(&root).expect("current flake lock authority");
+ let workspace = tempfile::tempdir().expect("flake lock test workspace");
+ let lock_path = workspace.path().join(FLAKE_LOCK_RELATIVE);
+ let lock = regular_utf8_source(&root, FLAKE_LOCK_RELATIVE).expect("flake lock source");
+
+ fs::write(
+ &lock_path,
+ lock.replacen("\"crane\": \"crane\"", "\"crane\": \"nixpkgs\"", 1),
+ )
+ .expect("write redirected flake lock");
+ validate_flake_lock_authority(workspace.path())
+ .expect_err("redirected direct input must fail closed");
+
+ fs::write(
+ &lock_path,
+ lock.replacen("\"narHash\":", "\"untrustedNarHash\":", 1),
+ )
+ .expect("write unlocked flake lock");
+ validate_flake_lock_authority(workspace.path())
+ .expect_err("missing direct-input narHash must fail closed");
+ }
+
+ #[test]
+ fn delegated_suite_test_targets_reject_disabled_or_detached_tests() {
+ let root = workspace_root();
+ let workspace = tempfile::tempdir().expect("delegated test-target workspace");
+ for relative in [
+ "crates/event_store/Cargo.toml",
+ "crates/event_store/src/store.rs",
+ "crates/event_store/src/nip09/reconciliation_v1.rs",
+ ] {
+ let destination = workspace.path().join(relative);
+ fs::create_dir_all(destination.parent().expect("test-target source parent"))
+ .expect("create test-target source parent");
+ fs::copy(root.join(relative), destination).expect("copy test-target source");
+ }
+ validate_delegated_suite_test_targets(workspace.path())
+ .expect("current delegated suite test targets");
+
+ let cargo_path = workspace.path().join("crates/event_store/Cargo.toml");
+ let cargo = fs::read_to_string(&cargo_path).expect("event-store Cargo manifest");
+ fs::write(&cargo_path, format!("{cargo}\n[lib]\ntest = false\n"))
+ .expect("disable library tests");
+ validate_delegated_suite_test_targets(workspace.path())
+ .expect_err("disabled delegated library tests must fail closed");
+ fs::write(&cargo_path, cargo).expect("restore Cargo manifest");
+
+ let store_path = workspace.path().join("crates/event_store/src/store.rs");
+ let store = fs::read_to_string(&store_path).expect("event-store source");
+ let detached = store.replacen(
+ "#[cfg(test)]\nmod raw_source_rebuild_v1_tests;",
+ "#[cfg(any())]\nmod raw_source_rebuild_v1_tests;",
+ 1,
+ );
+ assert_ne!(detached, store, "detached test module fixture must mutate");
+ fs::write(store_path, detached).expect("detach delegated test module");
+ validate_delegated_suite_test_targets(workspace.path())
+ .expect_err("detached delegated test module must fail closed");
+ }
+
+ #[test]
+ fn drift_taxonomy_rejects_variant_and_code_retargeting() {
+ let root = workspace_root();
+ let source = rust_source(&root, "crates/event_store/src/error.rs")
+ .expect("event-store error source");
+ let baseline = syn::parse_file(&source).expect("event-store error AST");
+ validate_raw_source_rebuild_drift_taxonomy(&baseline)
+ .expect("current raw-source rebuild drift taxonomy");
+
+ for mutation in [
+ source.replacen(
+ " RebuildPostcondition,",
+ " RebuildPostconditionRetargeted,",
+ 1,
+ ),
+ source.replacen(
+ "\"source_generation_lineage\"",
+ "\"addressable_transition_authority\"",
+ 1,
+ ),
+ ] {
+ assert_ne!(mutation, source, "taxonomy fixture must mutate");
+ let file = syn::parse_file(&mutation).expect("mutated taxonomy AST");
+ validate_raw_source_rebuild_drift_taxonomy(&file)
+ .expect_err("taxonomy mutation must fail closed");
+ }
+ }
+
+ #[test]
+ fn failpoint_authority_rejects_mapping_retargeting_and_injection_drift() {
+ let root = workspace_root();
+ let relative = REBUILD_RUNTIME_SOURCE_RELATIVE;
+ let source = rust_source(&root, relative).expect("raw-source rebuild runtime");
+ let baseline = syn::parse_file(&source).expect("raw-source rebuild AST");
+ validate_failpoint_authority(&baseline, relative).expect("current failpoint authority");
+ validate_coordinator_authority(&baseline, relative).expect("current coordinator authority");
+
+ let retargeted = source.replacen(
+ "Self::AfterFoodAudit => \"after_food_audit\"",
+ "Self::AfterFoodAudit => \"after_food_reset_replay\"",
+ 1,
+ );
+ assert_ne!(retargeted, source, "retargeted fixture must mutate");
+ let retargeted = syn::parse_file(&retargeted).expect("retargeted failpoint AST");
+ validate_failpoint_authority(&retargeted, relative)
+ .expect_err("retargeted failpoint stage must fail closed");
+
+ let extra = source.replacen(
+ "append_source_generation(connection, &plan).await?;",
+ "inject_raw_source_rebuild_failpoint_v1(\n _failpoint,\n RawSourceRebuildFailpointV1::AfterMarkerOpen,\n )?;\n append_source_generation(connection, &plan).await?;",
+ 1,
+ );
+ assert_ne!(extra, source, "extra-injection fixture must mutate");
+ let extra = syn::parse_file(&extra).expect("extra-injection AST");
+ validate_failpoint_authority(&extra, relative)
+ .expect_err("extra rollback injection must fail closed");
+
+ let omitted = source.replacen(
+ " #[cfg(test)]\n inject_raw_source_rebuild_failpoint_v1(\n _failpoint,\n RawSourceRebuildFailpointV1::AfterFoodAudit,\n )?;\n",
+ "",
+ 1,
+ );
+ assert_ne!(omitted, source, "omitted-injection fixture must mutate");
+ let omitted = syn::parse_file(&omitted).expect("omitted-injection AST");
+ validate_failpoint_authority(&omitted, relative)
+ .expect_err("omitted rollback injection must fail closed");
+ }
+
+ #[test]
+ fn failpoint_test_array_rejects_member_omission() {
+ let root = workspace_root();
+ let source = rust_source(&root, REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE)
+ .expect("raw-source rebuild failpoint tests");
+ let baseline = syn::parse_file(&source).expect("raw-source rebuild failpoint test AST");
+ validate_failpoint_test_array_authority(&baseline, REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE)
+ .expect("current failpoint test array authority");
+
+ let omitted = source.replacen(
+ " RawSourceRebuildFailpointV1::AfterVisibilityAudit,\n",
+ "",
+ 1,
+ );
+ assert_ne!(omitted, source, "test-array omission fixture must mutate");
+ let omitted = syn::parse_file(&omitted).expect("omitted failpoint test array AST");
+ validate_failpoint_test_array_authority(&omitted, REBUILD_FAILPOINT_TEST_SOURCE_RELATIVE)
+ .expect_err("omitted failpoint test array member must fail closed");
+ }
+
+ #[test]
+ fn digest_streaming_authority_rejects_duplicate_and_missing_row_markers() {
+ let root = workspace_root();
+ let relative = REBUILD_RUNTIME_SOURCE_RELATIVE;
+ let source = rust_source(&root, relative).expect("raw-source rebuild runtime");
+ for mutation in [
+ source.replacen(
+ " digest_row_start(&mut digest);",
+ " digest_row_start(&mut digest);\n digest_row_start(&mut digest);",
+ 1,
+ ),
+ source.replacen(" digest_row_start(&mut digest);\n", "", 1),
+ ] {
+ assert_ne!(mutation, source, "row-marker fixture must mutate");
+ let file = syn::parse_file(&mutation).expect("mutated digest runtime AST");
+ let function = exact_free_function(&file, "immutable_raw_digest_v1")
+ .expect("immutable raw digest authority");
+ let error = validate_digest_streaming_authority(
+ function,
+ relative,
+ "immutable_raw_digest_v1",
+ RAW_DIGEST_QUERY_SPECS,
+ )
+ .expect_err("row-marker framing mutation must fail closed");
+ assert!(error.contains("exactly one top-level"), "{error}");
+ }
+ }
+
+ #[test]
+ fn migration_inventory_remains_runtime_only_v4() {
+ validate_migration_inventory(&workspace_root()).expect("exact migration inventory");
+ }
+
+ #[test]
+ fn source_maintenance_predecessor_identity_is_frozen() {
+ let root = workspace_root();
+ let bytes = read_regular_file(&root, PREDECESSOR_MANIFEST_RELATIVE)
+ .expect("SourceMaintenance predecessor manifest");
+ validate_predecessor_identity(&bytes).expect("frozen predecessor identity");
+ validate_predecessor_source_supersession(&root, &bytes)
+ .expect("changed predecessor sources are superseded");
+ }
+
+ #[test]
+ fn generated_bundle_render_is_deterministic() {
+ let root = workspace_root();
+ let first = expected_artifacts(&root)
+ .expect("first render")
+ .into_iter()
+ .map(|artifact| (artifact.relative, artifact.contents))
+ .collect::<Vec<_>>();
+ let second = expected_artifacts(&root)
+ .expect("second render")
+ .into_iter()
+ .map(|artifact| (artifact.relative, artifact.contents))
+ .collect::<Vec<_>>();
+ assert_eq!(first, second);
+ }
+
+ #[test]
+ fn direct_vector_digest_rejects_bootstrap_placeholder() {
+ let error = validate_vector_expected_digest(&"0".repeat(64))
+ .expect_err("all-zero bootstrap digest must fail closed");
+ assert!(error.contains("bootstrap"), "{error}");
+ }
+
+ #[test]
+ fn executable_vector_case_inventory_is_frozen() {
+ let root = workspace_root();
+ let bytes = read_regular_file(&root, RESULT_VECTOR_CANONICAL_RELATIVE)
+ .expect("raw-source rebuild vector");
+ validate_result_vector_identity(&bytes).expect("immutable vector identity");
+
+ let mut reduced = bytes;
+ reduced.pop();
+ let error = validate_result_vector_identity(&reduced)
+ .expect_err("reduced vector inventory must fail closed");
+ assert!(
+ error.contains("immutable executable case inventory"),
+ "{error}"
+ );
+ }
+
+ #[test]
+ fn rollback_vector_requires_exact_failpoint_case_ids() {
+ let root = workspace_root();
+ let bytes = read_regular_file(&root, RESULT_VECTOR_CANONICAL_RELATIVE)
+ .expect("raw-source rebuild vector");
+ let mut vector = serde_json::from_slice::<RawSourceRebuildVector>(&bytes)
+ .expect("raw-source rebuild vector schema");
+ validate_failpoint_result_vector_cases(&vector)
+ .expect("current rollback failpoint vector cases");
+
+ let case = vector
+ .cases
+ .iter_mut()
+ .find(|case| case.id == "rollback_after_marker_open")
+ .expect("marker-open rollback case");
+ case.id = "rollback_after_marker_open_retargeted".to_owned();
+ validate_failpoint_result_vector_cases(&vector)
+ .expect_err("retargeted rollback failpoint case ID must fail closed");
+ }
+
+ #[test]
+ fn public_error_runtime_and_command_authorities_are_active() {
+ let root = workspace_root();
+ validate_public_api_authority(&root).expect("public API authority");
+ validate_error_authority(&root).expect("typed error authority");
+ validate_runtime_authority(&root).expect("runtime authority");
+ validate_command_reachability(&root).expect("command and release reachability");
+ }
+
+ #[test]
+ fn caller_schema_dependency_authority_rejects_limits_narrowing_and_bypass() {
+ let root = workspace_root();
+ let relative = REBUILD_RUNTIME_SOURCE_RELATIVE;
+ let source = rust_source(&root, relative).expect("raw-source rebuild runtime");
+ let baseline = syn::parse_file(&source).expect("raw-source rebuild AST");
+ validate_caller_schema_dependency_authority(&baseline, relative)
+ .expect("current caller-schema dependency authority");
+ validate_scoped_integrity_authority(&baseline, relative)
+ .expect("current mutated-parent inventory authority");
+ validate_coordinator_authority(&baseline, relative)
+ .expect("current rebuild coordinator authority");
+
+ let missing_mutated_parent = source.replacen(" \"sqlite_sequence\",\n", "", 1);
+ assert_ne!(
+ missing_mutated_parent, source,
+ "mutated-parent omission fixture must mutate"
+ );
+ let missing_mutated_parent =
+ syn::parse_file(&missing_mutated_parent).expect("parse mutated-parent omission AST");
+ validate_scoped_integrity_authority(&missing_mutated_parent, relative)
+ .expect_err("mutated-parent omission must fail closed");
+
+ for (label, mutation) in [
+ (
+ "limit-retarget",
+ source.replacen(
+ "const RAW_SOURCE_REBUILD_CALLER_MAIN_TABLE_COUNT_LIMIT_V1: u32 = 4_096;",
+ "const RAW_SOURCE_REBUILD_CALLER_MAIN_TABLE_COUNT_LIMIT_V1: u32 = 4_097;",
+ 1,
+ ),
+ ),
+ (
+ "unqualified-foreign-key-inventory",
+ source.replacen(
+ "JOIN main.pragma_foreign_key_list(child.name, 'main') AS foreign_key",
+ "JOIN pragma_foreign_key_list(child.name) AS foreign_key",
+ 1,
+ ),
+ ),
+ (
+ "action-filter",
+ source.replacen(
+ r#"ON foreign_key.\"table\" COLLATE NOCASE = rebuild_parent.name"#,
+ r#"ON foreign_key.\"table\" COLLATE NOCASE = rebuild_parent.name
+ AND foreign_key.on_delete = 'CASCADE'"#,
+ 1,
+ ),
+ ),
+ (
+ "temporary-schema-redirection",
+ source.replacen(
+ "FROM main.sqlite_schema AS child",
+ "FROM temp.sqlite_schema AS child",
+ 1,
+ ),
+ ),
+ ] {
+ assert_ne!(mutation, source, "{label} fixture must mutate");
+ let mutation = syn::parse_file(&mutation)
+ .unwrap_or_else(|error| panic!("parse {label} caller-schema AST: {error}"));
+ assert!(
+ validate_caller_schema_dependency_authority(&mutation, relative).is_err(),
+ "{label} caller-schema bypass must fail closed"
+ );
+ }
+
+ let coordinator_bypass = source.replacen(
+ "preflight_caller_owned_schema_dependencies_v1(connection, caller_schema_limits).await?;",
+ "let _ = caller_schema_limits;",
+ 1,
+ );
+ assert_ne!(
+ coordinator_bypass, source,
+ "coordinator bypass fixture must mutate"
+ );
+ let coordinator_bypass =
+ syn::parse_file(&coordinator_bypass).expect("parse coordinator bypass AST");
+ validate_coordinator_authority(&coordinator_bypass, relative)
+ .expect_err("caller-schema preflight bypass must fail closed");
+ }
+
+ #[test]
+ fn cold_repair_authority_rejects_caller_mode_and_route_bypasses() {
+ let root = workspace_root();
+ let relative = "crates/event_store/src/store.rs";
+ let source = rust_source(&root, relative).expect("event-store source");
+ let baseline = syn::parse_file(&source).expect("event-store AST");
+ validate_public_entry_point_authority(&baseline, relative)
+ .expect("current cold-repair authority");
+
+ let caller_mode = source.replacen(
+ "pub async fn repair_file_from_raw_v1(\n path: impl AsRef<Path>,\n )",
+ "pub async fn repair_file_from_raw_v1(\n path: impl AsRef<Path>,\n pool: SqlitePool,\n )",
+ 1,
+ );
+ assert_ne!(caller_mode, source, "caller-mode fixture must mutate");
+ let caller_mode = syn::parse_file(&caller_mode).expect("caller-mode AST");
+ let error = validate_public_entry_point_authority(&caller_mode, relative)
+ .expect_err("caller-supplied backing mode must fail closed");
+ assert!(error.contains("signature drifted"), "{error}");
+
+ for (label, mutation) in [
+ (
+ "multi-connection",
+ source.replacen(
+ ".max_connections(1)\n .connect_with(options)\n .await?;\n pool.set_connect_options(raw_source_repair_connect_options_v1(&canonical_path));",
+ ".max_connections(2)\n .connect_with(options)\n .await?;\n pool.set_connect_options(raw_source_repair_connect_options_v1(&canonical_path));",
+ 1,
+ ),
+ ),
+ (
+ "lock-domain-bypass",
+ source.replacen(
+ "validate_raw_source_repair_canonical_lock_domain_v1(&canonical_path).await",
+ "Ok(())",
+ 1,
+ ),
+ ),
+ (
+ "identity-bypass",
+ source.replacen(
+ " if actual != canonical_path {",
+ " if false && actual != canonical_path {",
+ 1,
+ ),
+ ),
+ (
+ "unqualified-lock-probe",
+ source.replacen(
+ "UPDATE main.radroots_event_store_write_lock",
+ "UPDATE radroots_event_store_write_lock",
+ 1,
+ ),
+ ),
+ (
+ "create-missing-file",
+ source.replacen(".create_if_missing(false)", ".create_if_missing(true)", 1),
+ ),
+ ] {
+ assert_ne!(mutation, source, "{label} fixture must mutate");
+ let mutation = syn::parse_file(&mutation)
+ .unwrap_or_else(|error| panic!("parse {label} cold-repair bypass AST: {error}"));
+ assert!(
+ validate_public_entry_point_authority(&mutation, relative).is_err(),
+ "{label} cold-repair bypass must fail closed"
+ );
+ }
+ }
+
+ #[test]
+ fn schema_rejects_unknown_runtime_fields() {
+ let schema = manifest_schema();
+ let root = workspace_root();
+ let schema_bytes = canonical_json_bytes(&schema).expect("schema bytes");
+ let mut manifest = serde_json::to_value(
+ describe_manifest(&root, &schema_bytes).expect("current manifest"),
+ )
+ .expect("manifest value");
+ manifest
+ .pointer_mut("/runtime")
+ .and_then(Value::as_object_mut)
+ .expect("runtime object")
+ .insert("unbounded_scan".to_owned(), Value::Bool(true));
+ let error = validate_json_schema(&schema, &manifest)
+ .expect_err("unknown runtime field must fail closed");
+ assert!(error.contains("violates"), "{error}");
+ }
+}
diff --git a/tools/xtask/src/contract/source_maintenance.rs b/tools/xtask/src/contract/source_maintenance.rs
@@ -1,16 +1,13 @@
+// Frozen predecessor mutation fixtures intentionally retain non-runtime helpers.
#![allow(dead_code)]
-use super::artifact_bundle::{
- GeneratedArtifact, read_regular_file, with_artifact_bundle_transaction,
-};
-use super::food_availability_projection::{
- validate_food_availability_projection_manifest_under_lock,
- validate_food_availability_projection_predecessor_production_sources_under_lock,
-};
+use super::artifact_bundle::{read_regular_file, with_artifact_bundle_transaction};
use super::nip09_reconciliation::validate_current_event_store_successor_authority;
use quote::ToTokens;
use serde::{Deserialize, Serialize};
-use serde_json::{Value, json};
+use serde_json::Value;
+#[cfg(test)]
+use serde_json::json;
use sha2::{Digest, Sha256};
use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
@@ -37,8 +34,6 @@ const RAW_TAG_REJECTION_SCAN_BOUND: u64 = RAW_TAG_COUNT_LIMIT + 1;
const RETAINED_GENERATION_REJECTION_SCAN_BOUND: u32 = RETAINED_SOURCE_GENERATION_LIMIT + 1;
const SCHEMA_SHA256: &str = "d526d96ea02be12b4b0aed99e97cfdde17c4474ace67111506a7b900ee78b186";
const HASH_ALGORITHM: &str = "sha256_bytes_v1";
-const WRITE_COMMAND: &str = "cargo xtask contract source-maintenance-manifest --write";
-
const PREDECESSOR_HOOK_ID: &str = "food_availability_projection_v1";
const PREDECESSOR_MANIFEST_RELATIVE: &str =
"crates/event_store/contracts/food_availability_projection_v1.manifest.json";
@@ -73,10 +68,60 @@ const RESULT_VECTOR_EXECUTOR_RELATIVE: &str =
const RESULT_VECTOR_EXECUTOR_ID: &str =
"radroots_event_store.source_maintenance_v1.result_vector_executor.v1";
const RESULT_VECTOR_EXECUTOR_TEST: &str = "source_maintenance_v1_result_vector";
-const CONTRACT_COMMAND_SOURCE_RELATIVE: &str = "tools/xtask/src/contract.rs";
-const XTASK_MAIN_SOURCE_RELATIVE: &str = "tools/xtask/src/main.rs";
-const XTASK_MAIN_FULL_AST_SHA256: &str =
- "b48c71c7f40f45c89bd7c83935d48eac3a1a367c8f73f62262e8ee14404616b4";
+#[derive(Clone, Copy)]
+struct ImmutableArtifactSpec {
+ relative: &'static str,
+ byte_length: usize,
+ sha256: &'static str,
+}
+
+const IMMUTABLE_PREDECESSOR_ARTIFACTS: [ImmutableArtifactSpec; 9] = [
+ ImmutableArtifactSpec {
+ relative: MANIFEST_RELATIVE,
+ byte_length: 14_216,
+ sha256: "e8911e6e5710278969cbd15557a5b856b1575dfd11a655711403598370b41221",
+ },
+ ImmutableArtifactSpec {
+ relative: MANIFEST_SCHEMA_RELATIVE,
+ byte_length: 12_315,
+ sha256: "ad4a6c8ae9488fc8033792bc6952af04687f312901c1847d8c668a62913bb642",
+ },
+ ImmutableArtifactSpec {
+ relative: MANIFEST_SHA256_RELATIVE,
+ byte_length: 65,
+ sha256: "b6a6040932c092574f25caf0fa008a892ba9258f2848444edebbdbc3e441c633",
+ },
+ ImmutableArtifactSpec {
+ relative: GENERATED_DESCRIPTOR_RELATIVE,
+ byte_length: 18_723,
+ sha256: "5f988f800425cf36d4327c828b30943c2f79c1fa577ce80730dc13383a1466b1",
+ },
+ ImmutableArtifactSpec {
+ relative: RESULT_VECTOR_CANONICAL_RELATIVE,
+ byte_length: 16_253,
+ sha256: "997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f",
+ },
+ ImmutableArtifactSpec {
+ relative: RESULT_VECTOR_MIRROR_RELATIVE,
+ byte_length: 16_253,
+ sha256: "997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f",
+ },
+ ImmutableArtifactSpec {
+ relative: RESULT_VECTOR_EXECUTOR_RELATIVE,
+ byte_length: 23_510,
+ sha256: "a7487afdfe19fc5fc794811d0f0e6035203e1aabcf0a33a1d398f6b3555d38f3",
+ },
+ ImmutableArtifactSpec {
+ relative: MIGRATION_UP_RELATIVE,
+ byte_length: 19_841,
+ sha256: "425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d",
+ },
+ ImmutableArtifactSpec {
+ relative: MIGRATION_DOWN_RELATIVE,
+ byte_length: 5_172,
+ sha256: "fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860",
+ },
+];
const RAW_EVENT_COLUMNS: &[&str] = &[
"event_id",
@@ -313,18 +358,6 @@ pub(super) fn source_contract_fixture_source_paths() -> Vec<&'static str> {
SOURCE_SPECS.iter().map(|source| source.path).collect()
}
-const PREDECESSOR_SUPERSEDED_SOURCE_PATHS: &[&str] = &[
- "crates/event_store/src/error.rs",
- "crates/event_store/src/generated.rs",
- "crates/event_store/src/lib.rs",
- "crates/event_store/src/migrations.rs",
- "crates/event_store/src/model.rs",
- "crates/event_store/src/nip09/reconciliation_v1.rs",
- "crates/event_store/src/schema.rs",
- "crates/event_store/src/store.rs",
- "crates/event_store/src/store/protocol_reconciliation_v1.rs",
-];
-
const GENERATED_ARTIFACT_PATHS: &[&str] = &[
MANIFEST_RELATIVE,
MANIFEST_SCHEMA_RELATIVE,
@@ -515,9 +548,7 @@ struct VectorCase {
}
pub(crate) fn write_source_maintenance_manifest(workspace_root: &Path) -> Result<(), String> {
- with_artifact_bundle_transaction(workspace_root, |transaction| {
- let artifacts = expected_artifacts(workspace_root)?;
- transaction.write(artifacts)?;
+ with_artifact_bundle_transaction(workspace_root, |_| {
validate_source_maintenance_manifest_under_lock(workspace_root)
})
}
@@ -531,14 +562,6 @@ pub(crate) fn validate_source_maintenance_manifest(workspace_root: &Path) -> Res
pub(super) fn validate_source_maintenance_manifest_under_lock(
workspace_root: &Path,
) -> Result<(), String> {
- let expected = expected_artifacts(workspace_root)?;
- for artifact in expected {
- let actual = read_regular_file(workspace_root, artifact.relative)?;
- if actual != artifact.contents {
- return Err(stale_error(artifact.relative));
- }
- }
-
let manifest_bytes = read_regular_file(workspace_root, MANIFEST_RELATIVE)?;
let manifest_value: Value = serde_json::from_slice(&manifest_bytes)
.map_err(|error| format!("parse {MANIFEST_RELATIVE}: {error}"))?;
@@ -571,161 +594,28 @@ pub(super) fn validate_source_maintenance_manifest_under_lock(
let vector: SourceMaintenanceVector = serde_json::from_slice(&vector_bytes)
.map_err(|error| format!("parse {RESULT_VECTOR_CANONICAL_RELATIVE}: {error}"))?;
validate_canonical_json(RESULT_VECTOR_CANONICAL_RELATIVE, &vector_bytes, &vector)?;
- validate_result_vector(workspace_root, &vector)?;
- Ok(())
-}
-
-fn expected_artifacts(workspace_root: &Path) -> Result<Vec<GeneratedArtifact>, String> {
- let schema = manifest_schema();
- let schema_bytes = canonical_json_bytes(&schema)?;
- let manifest = describe_manifest(workspace_root, &schema_bytes)?;
- let manifest_bytes = canonical_json_bytes(&manifest)?;
- let manifest_sha256 = sha256_hex(&manifest_bytes);
- let descriptor = generated_descriptor(&manifest, &manifest_bytes, &manifest_sha256);
- let vector_bytes = read_regular_file(workspace_root, RESULT_VECTOR_CANONICAL_RELATIVE)?;
-
- Ok(vec![
- GeneratedArtifact {
- relative: MANIFEST_RELATIVE,
- contents: manifest_bytes,
- },
- GeneratedArtifact {
- relative: MANIFEST_SCHEMA_RELATIVE,
- contents: schema_bytes,
- },
- GeneratedArtifact {
- relative: MANIFEST_SHA256_RELATIVE,
- contents: format!("{manifest_sha256}\n").into_bytes(),
- },
- GeneratedArtifact {
- relative: GENERATED_DESCRIPTOR_RELATIVE,
- contents: descriptor.into_bytes(),
- },
- GeneratedArtifact {
- relative: RESULT_VECTOR_MIRROR_RELATIVE,
- contents: vector_bytes,
- },
- ])
-}
+ validate_immutable_result_vector(&vector)?;
-fn describe_manifest(
- workspace_root: &Path,
- schema_bytes: &[u8],
-) -> Result<SourceMaintenanceManifest, String> {
- validate_food_availability_projection_manifest_under_lock(workspace_root)?;
- validate_source_contract(workspace_root)?;
- validate_predecessor_production_source_coverage(workspace_root)?;
-
- let predecessor_bytes = read_regular_file(workspace_root, PREDECESSOR_MANIFEST_RELATIVE)?;
- if predecessor_bytes.len() != PREDECESSOR_MANIFEST_BYTE_LENGTH
- || sha256_hex(&predecessor_bytes) != PREDECESSOR_MANIFEST_SHA256
+ if manifest.migration.up.sha256 != IMMUTABLE_PREDECESSOR_ARTIFACTS[7].sha256
+ || manifest.migration.down.sha256 != IMMUTABLE_PREDECESSOR_ARTIFACTS[8].sha256
+ || manifest.result_vector.sha256 != IMMUTABLE_PREDECESSOR_ARTIFACTS[4].sha256
+ || manifest.result_vector.executor_sha256 != IMMUTABLE_PREDECESSOR_ARTIFACTS[6].sha256
{
return Err(format!(
- "{PREDECESSOR_MANIFEST_RELATIVE} does not match the immutable predecessor identity"
+ "{MANIFEST_RELATIVE} does not describe the immutable SourceMaintenance predecessor identity"
));
}
- validate_predecessor_public_api(&predecessor_bytes)?;
-
- let vector_bytes = read_regular_file(workspace_root, RESULT_VECTOR_CANONICAL_RELATIVE)?;
- let vector: SourceMaintenanceVector = serde_json::from_slice(&vector_bytes)
- .map_err(|error| format!("parse {RESULT_VECTOR_CANONICAL_RELATIVE}: {error}"))?;
- validate_canonical_json(RESULT_VECTOR_CANONICAL_RELATIVE, &vector_bytes, &vector)?;
- validate_result_vector(workspace_root, &vector)?;
- let migration_source = read_regular_file(workspace_root, MIGRATIONS_SOURCE_RELATIVE)?;
- let catalog = catalog_from_migration_source(&migration_source)?;
- validate_catalog(&catalog)?;
- let executor = descriptor_for_file(workspace_root, RESULT_VECTOR_EXECUTOR_RELATIVE)?;
- let migration_up = descriptor_for_file(workspace_root, MIGRATION_UP_RELATIVE)?;
- let migration_down = descriptor_for_file(workspace_root, MIGRATION_DOWN_RELATIVE)?;
- validate_migration_identity(&migration_up, &migration_down)?;
-
- let source_files = SOURCE_SPECS
- .iter()
- .map(|spec| {
- let bytes = if spec.path == MIGRATIONS_SOURCE_RELATIVE {
- migration_source.clone()
- } else {
- read_regular_file(workspace_root, spec.path)?
- };
- Ok(SourceFileDescriptor {
- role: spec.role.to_owned(),
- path: spec.path.to_owned(),
- byte_length: byte_length(spec.path, &bytes)?,
- sha256: sha256_hex(&bytes),
- hash_algorithm: HASH_ALGORITHM.to_owned(),
- })
- })
- .collect::<Result<Vec<_>, String>>()?;
-
- Ok(SourceMaintenanceManifest {
- schema_version: SCHEMA_VERSION,
- contract_id: CONTRACT_ID.to_owned(),
- hook_id: HOOK_ID.to_owned(),
- manifest_schema: descriptor_for_bytes(MANIFEST_SCHEMA_RELATIVE, schema_bytes)?,
- predecessor: PredecessorDescriptor {
- hook_id: PREDECESSOR_HOOK_ID.to_owned(),
- manifest: descriptor_for_bytes(PREDECESSOR_MANIFEST_RELATIVE, &predecessor_bytes)?,
- },
- migration: MigrationDescriptor {
- version: MIGRATION_VERSION,
- name: MIGRATION_NAME.to_owned(),
- up: migration_up,
- down: migration_down,
- schema_sha256: SCHEMA_SHA256.to_owned(),
- catalog,
- },
- source_maintenance: SourceMaintenanceDescriptor {
- version: CAPACITY_VERSION,
- event_contract_registry_version: EVENT_CONTRACT_REGISTRY_VERSION,
- capacity_authority_id: CAPACITY_AUTHORITY_ID.to_owned(),
- accounting: AccountingDescriptor {
- algorithm: ACCOUNTING_ALGORITHM.to_owned(),
- raw_event_columns: owned(RAW_EVENT_COLUMNS),
- raw_tag_columns: owned(RAW_TAG_COLUMNS),
- nullable_raw_tag_columns: owned(NULLABLE_RAW_TAG_COLUMNS),
- },
- limits: expected_limits(),
- reopen_validation: ReopenValidationDescriptor {
- mode: REOPEN_VALIDATION_MODE.to_owned(),
- raw_event_rejection_scan_bound: RAW_EVENT_REJECTION_SCAN_BOUND,
- raw_tag_rejection_scan_bound: RAW_TAG_REJECTION_SCAN_BOUND,
- generation_history_validation: GENERATION_HISTORY_VALIDATION.to_owned(),
- retained_generation_rejection_scan_bound: RETAINED_GENERATION_REJECTION_SCAN_BOUND,
- },
- rebuild_seal: RebuildSealDescriptor {
- nip09_hook_id: NIP09_HOOK_ID.to_owned(),
- nip09_manifest_sha256: NIP09_MANIFEST_SHA256.to_owned(),
- food_hook_id: PREDECESSOR_HOOK_ID.to_owned(),
- food_manifest_sha256: PREDECESSOR_MANIFEST_SHA256.to_owned(),
- food_scope_fingerprint_sha256: FOOD_SCOPE_FINGERPRINT_SHA256.to_owned(),
- active_generation_authority: ACTIVE_GENERATION_AUTHORITY.to_owned(),
- marker_close_authority: MARKER_CLOSE_AUTHORITY.to_owned(),
- },
- },
- entry_points: ENTRY_POINTS
- .iter()
- .map(|(role, rust_path)| EntryPointDescriptor {
- role: (*role).to_owned(),
- rust_path: (*rust_path).to_owned(),
- })
- .collect(),
- source_files,
- public_api: expected_public_api(),
- result_vector: ResultVectorDescriptor {
- canonical_path: RESULT_VECTOR_CANONICAL_RELATIVE.to_owned(),
- mirror_path: RESULT_VECTOR_MIRROR_RELATIVE.to_owned(),
- byte_length: byte_length(RESULT_VECTOR_CANONICAL_RELATIVE, &vector_bytes)?,
- sha256: sha256_hex(&vector_bytes),
- hash_algorithm: HASH_ALGORITHM.to_owned(),
- executor_id: RESULT_VECTOR_EXECUTOR_ID.to_owned(),
- executor_path: RESULT_VECTOR_EXECUTOR_RELATIVE.to_owned(),
- executor_test: RESULT_VECTOR_EXECUTOR_TEST.to_owned(),
- executor_byte_length: executor.byte_length,
- executor_sha256: executor.sha256,
- executor_hash_algorithm: HASH_ALGORITHM.to_owned(),
- },
- })
+ for artifact in IMMUTABLE_PREDECESSOR_ARTIFACTS {
+ let actual = read_regular_file(workspace_root, artifact.relative)?;
+ if actual.len() != artifact.byte_length || sha256_hex(&actual) != artifact.sha256 {
+ return Err(format!(
+ "immutable SourceMaintenance predecessor artifact {} does not match its authenticated byte identity",
+ artifact.relative
+ ));
+ }
+ }
+ Ok(())
}
fn expected_limits() -> LimitDescriptor {
@@ -759,62 +649,6 @@ fn owned(values: &[&str]) -> Vec<String> {
values.iter().map(|value| (*value).to_owned()).collect()
}
-fn validate_predecessor_production_source_coverage(workspace_root: &Path) -> Result<(), String> {
- let source_paths = SOURCE_SPECS
- .iter()
- .map(|source| source.path)
- .collect::<Vec<_>>();
- let unique_source_paths = source_paths.iter().copied().collect::<BTreeSet<_>>();
- if unique_source_paths.len() != source_paths.len() {
- return Err("SourceMaintenance SOURCE_SPECS paths must be unique".to_owned());
- }
- for path in PREDECESSOR_SUPERSEDED_SOURCE_PATHS {
- let count = source_paths
- .iter()
- .filter(|candidate| **candidate == *path)
- .count();
- if count != 1 {
- return Err(format!(
- "SourceMaintenance successor must current-byte-bind superseded predecessor path `{path}` exactly once; found {count}"
- ));
- }
- }
- let superseded = PREDECESSOR_SUPERSEDED_SOURCE_PATHS
- .iter()
- .copied()
- .collect::<BTreeSet<_>>();
- if superseded.len() != PREDECESSOR_SUPERSEDED_SOURCE_PATHS.len() {
- return Err("SourceMaintenance predecessor supersession paths must be unique".to_owned());
- }
- validate_food_availability_projection_predecessor_production_sources_under_lock(
- workspace_root,
- PREDECESSOR_SUPERSEDED_SOURCE_PATHS,
- )
-}
-
-fn validate_predecessor_public_api(predecessor_bytes: &[u8]) -> Result<(), String> {
- let predecessor: Value = serde_json::from_slice(predecessor_bytes)
- .map_err(|error| format!("parse {PREDECESSOR_MANIFEST_RELATIVE}: {error}"))?;
- let actual = predecessor
- .pointer("/public_api")
- .and_then(Value::as_array)
- .ok_or_else(|| format!("{PREDECESSOR_MANIFEST_RELATIVE} has no public_api array"))?
- .iter()
- .map(|value| {
- value.as_str().map(str::to_owned).ok_or_else(|| {
- format!("{PREDECESSOR_MANIFEST_RELATIVE} public_api values must be strings")
- })
- })
- .collect::<Result<Vec<_>, String>>()?;
- if actual != owned(INHERITED_PUBLIC_API) {
- return Err(
- "SourceMaintenance inherited public API must exactly equal the immutable FoodAvailability public API"
- .to_owned(),
- );
- }
- Ok(())
-}
-
fn descriptor_for_file(workspace_root: &Path, relative: &str) -> Result<FileDescriptor, String> {
descriptor_for_bytes(relative, &read_regular_file(workspace_root, relative)?)
}
@@ -959,84 +793,9 @@ pub(super) fn validate_source_contract(workspace_root: &Path) -> Result<(), Stri
validate_schema_capacity_authority(workspace_root)?;
validate_generation_rebuild_authority(workspace_root)?;
validate_sql_capacity_authority(workspace_root)?;
- validate_contract_command_reachability_authority(workspace_root)?;
validate_current_event_store_successor_authority(workspace_root)
}
-fn validate_contract_command_reachability_authority(workspace_root: &Path) -> Result<(), String> {
- let contract = rust_source(workspace_root, CONTRACT_COMMAND_SOURCE_RELATIVE)?;
- let main = rust_source(workspace_root, XTASK_MAIN_SOURCE_RELATIVE)?;
- validate_contract_command_reachability_sources(&contract, &main)
-}
-
-fn validate_contract_command_reachability_sources(
- contract_source: &str,
- main_source: &str,
-) -> Result<(), String> {
- let contract = syn::parse_file(contract_source)
- .map_err(|error| format!("parse {CONTRACT_COMMAND_SOURCE_RELATIVE}: {error}"))?;
- let main = syn::parse_file(main_source)
- .map_err(|error| format!("parse {XTASK_MAIN_SOURCE_RELATIVE}: {error}"))?;
- let main_ast_sha256 = sha256_hex(compact_tokens(&main).as_bytes());
- if main_ast_sha256 != XTASK_MAIN_FULL_AST_SHA256 {
- return Err(format!(
- "{XTASK_MAIN_SOURCE_RELATIVE} full dispatch AST authority drifted: expected {XTASK_MAIN_FULL_AST_SHA256}, found {main_ast_sha256}"
- ));
- }
- for (relative, file, name, expected) in [
- (
- CONTRACT_COMMAND_SOURCE_RELATIVE,
- &contract,
- "validate_artifact_contracts",
- r#"pub(crate) fn validate_artifact_contracts(
- workspace_root: &Path
- ) -> Result<(), String> {
- validate_event_contract_registry_v7_inventory(workspace_root)?;
- validate_nip09_reconciliation_manifest(workspace_root)?;
- validate_food_availability_projection_manifest(workspace_root)?;
- validate_source_maintenance_manifest(workspace_root)?;
- validate_knowledge_contract_manifest(workspace_root)
- }"#,
- ),
- (
- XTASK_MAIN_SOURCE_RELATIVE,
- &main,
- "validate_contract",
- r#"fn validate_contract() -> Result<(), String> {
- radroots_protocol_contract_v1::validate_protocol_contract_v1()
- .map_err(|error| error.to_string())?;
- let root = workspace_root();
- dto_roots::check(&root)?;
- contract::load_contract_bundle(&root)
- .and_then(|bundle| contract::validate_contract_bundle(&bundle))
- .and_then(|_| contract::validate_canonical_event_boundary(&root))
- .and_then(|_| contract::validate_artifact_contracts(&root))
- }"#,
- ),
- (
- XTASK_MAIN_SOURCE_RELATIVE,
- &main,
- "release_preflight_at",
- r#"fn release_preflight_at(root: &Path) -> Result<(), String> {
- dto_roots::check(root)?;
- contract::validate_artifact_contracts(root)?;
- contract::validate_release_preflight(root)
- }"#,
- ),
- ] {
- let actual = compact_tokens(exact_top_level_function(file, name)?);
- let expected_file = syn::parse_file(expected)
- .map_err(|error| format!("parse authoritative `{name}` function: {error}"))?;
- let expected = compact_tokens(exact_top_level_function(&expected_file, name)?);
- if actual != expected {
- return Err(format!(
- "{relative} `{name}` SourceMaintenance validation call-path authority drifted: expected `{expected}`, found `{actual}`"
- ));
- }
- }
- Ok(())
-}
-
fn validate_source_inventory() -> Result<(), String> {
validate_unique(
"SourceMaintenance source roles",
@@ -2089,27 +1848,6 @@ fn exact_free_function_tokens(file: &syn::File, name: &str) -> Result<String, St
Ok(compact_tokens(exact_free_function(file, name)?))
}
-fn exact_top_level_function<'a>(
- file: &'a syn::File,
- name: &str,
-) -> Result<&'a syn::ItemFn, String> {
- let matches = file
- .items
- .iter()
- .filter_map(|item| match item {
- Item::Fn(function) if function.sig.ident == name => Some(function),
- _ => None,
- })
- .collect::<Vec<_>>();
- let [function] = matches.as_slice() else {
- return Err(format!(
- "governed Rust source must define top-level function `{name}` exactly once; found {}",
- matches.len()
- ));
- };
- Ok(function)
-}
-
fn rust_source(workspace_root: &Path, relative: &str) -> Result<String, String> {
let bytes = read_regular_file(workspace_root, relative)?;
std::str::from_utf8(&bytes)
@@ -2296,78 +2034,7 @@ fn validate_manifest_shape(manifest: &SourceMaintenanceManifest) -> Result<(), S
Ok(())
}
-fn generated_descriptor(
- manifest: &SourceMaintenanceManifest,
- manifest_bytes: &[u8],
- manifest_sha256: &str,
-) -> String {
- let manifest_json = std::str::from_utf8(manifest_bytes).expect("canonical manifest is UTF-8");
- let manifest_literal = format!("{manifest_json:?}");
- format!(
- "// @generated by `cargo xtask contract source-maintenance-manifest --write`; do not edit.\n\
-pub(crate) const SOURCE_MAINTENANCE_MANIFEST_JSON: &str = {manifest_literal};\n\
-pub(crate) const SOURCE_MAINTENANCE_MANIFEST_BYTE_LENGTH: usize = {};\n\
-pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str =\n \"{manifest_sha256}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SCHEMA_VERSION: u32 = {SCHEMA_VERSION};\n\
-pub(crate) const SOURCE_MAINTENANCE_CONTRACT_ID: &str =\n \"{CONTRACT_ID}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_HOOK_ID: &str = \"{HOOK_ID}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_MIGRATION_VERSION: u32 = {MIGRATION_VERSION};\n\
-pub(crate) const SOURCE_MAINTENANCE_MIGRATION_NAME: &str = \"{MIGRATION_NAME}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_MIGRATION_UP_BYTE_LENGTH: usize = {};\n\
-pub(crate) const SOURCE_MAINTENANCE_MIGRATION_UP_SHA256: &str =\n \"{}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_MIGRATION_DOWN_BYTE_LENGTH: usize = {};\n\
-pub(crate) const SOURCE_MAINTENANCE_MIGRATION_DOWN_SHA256: &str =\n \"{}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_SCHEMA_SHA256: &str =\n \"{SCHEMA_SHA256}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_EVENT_CONTRACT_REGISTRY_VERSION: u32 = {EVENT_CONTRACT_REGISTRY_VERSION};\n\
-pub(crate) const SOURCE_MAINTENANCE_CAPACITY_VERSION: u32 = {CAPACITY_VERSION};\n\
-pub(crate) const SOURCE_MAINTENANCE_CAPACITY_AUTHORITY_ID: &str =\n \"{CAPACITY_AUTHORITY_ID}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_ACCOUNTING_ALGORITHM: &str = \"{ACCOUNTING_ALGORITHM}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_RAW_EVENT_COLUMNS: &[&str] = &{};\n\
-pub(crate) const SOURCE_MAINTENANCE_RAW_TAG_COLUMNS: &[&str] =\n &{};\n\
-pub(crate) const SOURCE_MAINTENANCE_NULLABLE_RAW_TAG_COLUMNS: &[&str] = &{};\n\
-pub(crate) const SOURCE_MAINTENANCE_REPLACED_OBJECT_NAMES: &[&str] = &{};\n\
-pub(crate) const SOURCE_MAINTENANCE_RAW_EVENT_COUNT_LIMIT: u64 = {RAW_EVENT_COUNT_LIMIT};\n\
-pub(crate) const SOURCE_MAINTENANCE_RAW_TAG_COUNT_LIMIT: u64 = {RAW_TAG_COUNT_LIMIT};\n\
-pub(crate) const SOURCE_MAINTENANCE_RAW_EVENT_TEXT_BYTES_LIMIT: u64 = {RAW_EVENT_TEXT_BYTES_LIMIT};\n\
-pub(crate) const SOURCE_MAINTENANCE_RAW_TAG_TEXT_BYTES_LIMIT: u64 = {RAW_TAG_TEXT_BYTES_LIMIT};\n\
-pub(crate) const SOURCE_MAINTENANCE_RETAINED_SOURCE_GENERATION_LIMIT: u32 = {RETAINED_SOURCE_GENERATION_LIMIT};\n\
-pub(crate) const SOURCE_MAINTENANCE_PREDECESSOR_HOOK_ID: &str = \"{PREDECESSOR_HOOK_ID}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_PREDECESSOR_MANIFEST_SHA256: &str =\n \"{PREDECESSOR_MANIFEST_SHA256}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_RESULT_VECTOR_SHA256: &str =\n \"{}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_RESULT_VECTOR_EXECUTOR_ID: &str =\n \"{RESULT_VECTOR_EXECUTOR_ID}\";\n\
-pub(crate) const SOURCE_MAINTENANCE_RESULT_VECTOR_EXECUTOR_SHA256: &str =\n \"{}\";\n",
- manifest_bytes.len(),
- usize::try_from(manifest.migration.up.byte_length).expect("up length fits usize"),
- manifest.migration.up.sha256,
- usize::try_from(manifest.migration.down.byte_length).expect("down length fits usize"),
- manifest.migration.down.sha256,
- rust_multiline_string_slice(RAW_EVENT_COLUMNS),
- rust_string_slice(RAW_TAG_COLUMNS),
- rust_string_slice(NULLABLE_RAW_TAG_COLUMNS),
- rust_multiline_string_slice(EXPECTED_REPLACED_CATALOG_OBJECTS),
- manifest.result_vector.sha256,
- manifest.result_vector.executor_sha256,
- )
-}
-
-fn rust_string_slice(values: &[&str]) -> String {
- let values = values
- .iter()
- .map(|value| format!("{value:?}"))
- .collect::<Vec<_>>()
- .join(", ");
- format!("[{values}]")
-}
-
-fn rust_multiline_string_slice(values: &[&str]) -> String {
- let values = values
- .iter()
- .map(|value| format!(" {value:?},"))
- .collect::<Vec<_>>()
- .join("\n");
- format!("[\n{values}\n]")
-}
-
+#[cfg(test)]
fn manifest_schema() -> Value {
let path_pattern = "^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$";
let file = json!({
@@ -2677,10 +2344,6 @@ fn sha256_hex(bytes: &[u8]) -> String {
hex::encode(Sha256::digest(bytes))
}
-fn stale_error(relative: &str) -> String {
- format!("{relative} is stale; run `{WRITE_COMMAND}`")
-}
-
#[derive(Clone, Copy)]
struct ExpectedVectorCase {
id: &'static str,
@@ -3079,10 +2742,7 @@ const EXPECTED_VECTOR_CASES: &[ExpectedVectorCase] = &[
},
];
-fn validate_result_vector(
- workspace_root: &Path,
- vector: &SourceMaintenanceVector,
-) -> Result<(), String> {
+fn validate_immutable_result_vector(vector: &SourceMaintenanceVector) -> Result<(), String> {
if vector.schema_version != SCHEMA_VERSION
|| vector.contract_id != CONTRACT_ID
|| vector.capacity_version != CAPACITY_VERSION
@@ -3140,632 +2800,13 @@ fn validate_result_vector(
}
}
}
- validate_delegated_test_authorities(workspace_root, vector)
-}
-
-#[derive(Clone, Copy)]
-struct DelegatedAuthoritySpec {
- path: &'static str,
- test: &'static str,
- ordered_markers: &'static [&'static str],
-}
-
-const EXECUTABLE_AUTHORITY_AST_SHA256: &str =
- "19c405fc91468997aa53f08ebbaed82c526bf4810b2175ad9034d95dc95b8597";
-const BOUND_AUTHORITY_SOURCE_AST_SHA256: &str =
- "ba44c729ebba14e658ec7b48f7ba395fd4e8fb3d597d306df5cfa7010a4f9bac";
-
-#[derive(Clone, Debug, Serialize)]
-struct ExecutableAuthorityIdentity {
- path: String,
- test: String,
- tokens: String,
-}
-
-#[derive(Clone, Debug, Serialize)]
-struct BoundAuthoritySourceIdentity {
- path: String,
- tokens: String,
-}
-
-const DELEGATED_AUTHORITIES: &[DelegatedAuthoritySpec] = &[
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "exact_capacity_boundary_allows_duplicate_observation_and_ephemeral_noop",
- ordered_markers: &[
- "RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1",
- "validate_source_capacity_authority_fast_v1",
- "duplicate.persistence.is_duplicate()",
- "SourceCapacityExceeded",
- "RadrootsEventPersistence::NotPersisted",
- "assert_eq!(ephemeral_observation_count,0)",
- "transaction.rollback().await",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "borrowed_ingest_savepoint_rolls_back_post_core_authority_forge",
- ordered_markers: &[
- "priorcallerwork",
- "capacity_after_prior",
- "expect_err(\"post-corerawauthoritymutationmustfail\")",
- "MigrationHookStateDrift",
- "capacity_after_rollback,capacity_after_prior",
- "callermaycommitpriorworkafterfailedingest",
- "raw_event(prior_event.id_str())",
- "raw_event(trigger_event.id_str())",
- "raw_event(forged_event.id_str())",
- "trade_mutation_count,0",
- "transition_count,0",
- "capacity_after_prior",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/source_maintenance_v1.rs",
- test: "every_prospective_capacity_dimension_accepts_exact_and_rejects_one_over",
- ordered_markers: &[
- "RadrootsEventStoreSourceCapacityResourceV1::RawEvents",
- "RadrootsEventStoreSourceCapacityResourceV1::RawTags",
- "RadrootsEventStoreSourceCapacityResourceV1::RawEventBytes",
- "RadrootsEventStoreSourceCapacityResourceV1::RawTagBytes",
- "capacity_with(resource,limit-1)",
- "delta_with(resource,1)",
- "capacity_with(resource,limit)",
- "SourceCapacityExceeded",
- "requested:1",
- "capacity_with(resource,limit+1)",
- "requested:0",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "v3_to_v4_under_limit_backfills_exact_capacity_and_preserves_source",
- ordered_markers: &[
- "&EVENT_STORE_MIGRATIONS[..3]",
- "event_envelopes",
- "active_generation",
- "RADROOTS_EVENT_STORE_SCHEMA_VERSION_CURRENT",
- "Managed{version:4}",
- "radroots_event_store_source_capacity_v1",
- "assert_eq!(capacity,(generation_before,1,0,event_bytes,0,1,8))",
- "preserved",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "v3_to_v4_rejects_prior_transition_drift_atomically",
- ordered_markers: &[
- "&EVENT_STORE_MIGRATIONS[..3]",
- "predecessor_trigger_sql",
- "corruption.commit().await",
- "expect_err(\"v4upgrademustnotrepaircorruptmanaged-v3hookstate\")",
- "MigrationHookStateDrift{hook_id:\"nip09_reconciliation_v1\"",
- "ledger_after,ledger_before",
- "v4_objects,0",
- "v4_ledger_rows,0",
- "schema_object_sql(&pool,name).await,*sql",
- "Managed{version:3}",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "source_capacity_is_rechecked_for_every_rebuild_bound_migration",
- ordered_markers: &[
- "raw_events:0",
- "UnledgeredBaseline",
- "&EVENT_STORE_MIGRATIONS[..3]",
- "expect_err(\"v4capacityexcessmustfail\")",
- "SourceCapacityExceeded",
- "Managed{version:3}",
- "radroots_event_store_source_capacity_v1",
- "assert_eq!(v4_object_count,0)",
- "assert_eq!(v4_ledger_count,0)",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "v4_rejects_persisted_legacy_ephemeral_rows_atomically",
- ordered_markers: &[
- "&EVENT_STORE_MIGRATIONS[..3]",
- "kind,tags_json,content",
- "20000",
- "begin_with(\"BEGINIMMEDIATE\")",
- "expect_err(\"persistedephemeralsourcemustrejectv4\")",
- "PersistedEphemeralRawEvent",
- "Managed{version:3}",
- "radroots_event_store_source_capacity_v1",
- "assert_eq!(v4_object_count,0)",
- "assert_eq!(v4_ledger_count,0)",
- "assert_eq!(raw_count,1)",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/source_maintenance_v1.rs",
- test: "reopen_full_measure_detects_every_persisted_capacity_dimension",
- ordered_markers: &[
- "raw_event_count=1",
- "raw_tag_count=1",
- "raw_event_bytes=1",
- "raw_tag_bytes=1",
- "RadrootsEventStore::open_file(&path).await",
- "SourceCapacityStateDrift",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/source_maintenance_v1.rs",
- test: "reopen_stops_at_the_first_raw_event_one_over_before_ephemeral_probe",
- ordered_markers: &[
- "value<25001",
- "CASEWHENvalue=25001THEN20000ELSE1END",
- "RadrootsEventStore::open_file(&path).await",
- "SourceCapacityExceeded",
- "current:25_000",
- "requested:1",
- "limit:25_000",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "ninth_current_v4_rebuild_is_typed_and_preflight_atomic",
- ordered_markers: &[
- "forordinalin2_u8..=8",
- "assert_eq!(capacity_before.retained_generation_count(),8)",
- "PanickingGeneration",
- "expect_err(\"ninthrebuildmustfailbeforeentropyormutation\")",
- "SourceGenerationHistoryLimitReached{current:8,limit:8,}",
- "assert_eq!(source_authority_snapshot(&store).await,source_before)",
- "assert_eq!(raw_authority_digest(&store).await,raw_before)",
- "assert_eq!(normalized_nip09_snapshot(&store).await,nip09_before)",
- "assert_eq!(food_after,food_before)",
- "assert_eq!(derived_after,(derived_before.0,derived_before.1,derived_before.2,0))",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/source_maintenance_v1.rs",
- test: "generation_sql_backstop_allows_exact_append_and_is_conflict_safe_one_over",
- ordered_markers: &[
- "retained_generation_count=retained_generation_limit-1",
- "exactgenerationboundarymustappend",
- "assert_eq!(retained_count,8)",
- "sourcegenerationalreadyexists",
- "uniquegenerationappendmusthittheSQLcapacitybackstop",
- "sqlx::Error::Database",
- "retainedsourcegenerationlimitreached",
- "transaction.rollback().await",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "current_v4_rebuild_rotates_capacity_and_food_authority_end_to_end",
- ordered_markers: &[
- "apply_reconciliation_hook",
- "after.retained_generation_count()",
- "before.retained_generation_count()+1",
- "assert_eq!(marker_count,0)",
- "audit_food_availability_projection_v1",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/source_maintenance_v1.rs",
- test: "marker_close_sql_backstop_rejects_each_required_seal_drift",
- ordered_markers: &[
- "rebuildmarkercannotclosebeforecapacity,NIP-09,andFoodAvailabilitysealsagree",
- "fordriftin[\"capacity\",\"nip09\",\"food\",\"fts\"]",
- "radroots_event_store_source_capacity_update_guard",
- "raw_event_bytes=raw_event_bytes+1",
- "radroots_event_store_addressable_feed_integrity_v1",
- "last_transition_seq=last_transition_seq+1",
- "radroots_event_store_food_availability_cursor_update_guard",
- "projected_row_count=projected_row_count+1",
- "radroots_event_store_food_availability_search_fts",
- "DELETEFROMradroots_event_store_source_rebuild_marker",
- "sqlx::Error::Database",
- "database.message()==MARKER_CLOSE_ERROR",
- "transaction.rollback().await",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "v4_marker_open_allows_repairing_prior_transition_high_water_drift",
- ordered_markers: &[
- "last_transition_seq=7",
- "validate_schema_fingerprint",
- "wrong_prior",
- "wrong_floor",
- "expect(\"derivedtransitiondriftisrepairableunderv4\")",
- "repaired.0.as_slice(),target_generation.as_slice()",
- "repaired.1,repaired.2",
- "repaired.1,0",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "v4_food_reset_requires_marker_rotation_and_preserves_target_rows",
- ordered_markers: &[
- "expect_err(\"marker-freeFoodresetmustfail\")",
- "expect_err(\"markeralonemustnotauthorizeFoodreset\")",
- "expect(\"rotatesourcestate\")",
- "expect(\"post-rotationhistoricalFoodreset\")",
- "expect_err(\"activetarget-generationFoodrowsmustremainguarded\")",
- "remaining,(1,1)",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "v4_down_restores_exact_predecessor_trigger_sql_and_fingerprint",
- ordered_markers: &[
- "&EVENT_STORE_MIGRATIONS[..3]",
- "predecessor_sql",
- "assert_ne!(schema_object_sql(&pool,name).await,predecessor_sql[*name])",
- "rollback_event_store_schema_with_registry",
- "assert_eq!(schema_object_sql(&pool,name).await,predecessor_sql[*name])",
- "Managed{version:3}",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "open_file_rejects_utf16_main_database_before_schema_or_journal_mutation",
- ordered_markers: &[
- "initialize_utf16le_database(&path).await",
- "RadrootsEventStore::open_file(&path).await",
- "SqliteMainDatabaseEncodingNotUtf8{actual}",
- "actual==\"UTF-16le\"",
- "assert_utf16le_database_was_not_mutated(&path).await",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "open_pool_rejects_utf16_main_database_before_schema_or_journal_mutation",
- ordered_markers: &[
- "initialize_utf16le_database(&path).await",
- "max_connections(2)",
- "RadrootsEventStore::open_pool(pool,true).await",
- "SqliteMainDatabaseEncodingNotUtf8{actual}",
- "actual==\"UTF-16le\"",
- "assert_utf16le_database_was_not_mutated(&path).await",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "utf8_file_reopen_preserves_non_ascii_and_nul_capacity_accounting",
- ordered_markers: &[
- "letexpected_tag_count=",
- "raw_source_text_bytes(&event)",
- "expect(\"non-ASCIIandNULingest\")",
- "before_reopen.raw_event_count(),1",
- "before_reopen.raw_tag_count(),expected_tag_count",
- "before_reopen.raw_event_text_bytes(),expected_event_bytes",
- "before_reopen.raw_tag_text_bytes(),expected_tag_bytes",
- "store.pool().close().await",
- "RadrootsEventStore::open_file(&path).await",
- "source_capacity_v1()",
- "before_reopen",
- "raw_event(&event_id)",
- "tags_for_event(&event_id)",
- "assert_eq!(tags.len(),2)",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "rollback_rejects_below_floor_ahead_unmanaged_and_generation_destructive_targets",
- ordered_markers: &[
- "lethistory_before:Vec<(Vec<u8>,i64)>=",
- "rollback_event_store_schema_offline(&managed,1).await",
- "RollbackWouldDiscardSourceGenerationHistory{current:RADROOTS_EVENT_STORE_SCHEMA_VERSION_CURRENT,target:1,floor:2,}",
- "inspect_event_store_schema_status(&managed).await",
- "Managed{version:RADROOTS_EVENT_STORE_SCHEMA_VERSION_CURRENT,}",
- "source-generationhistoryafterrejectedrollback",
- "history_before",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/schema.rs",
- test: "rollback_cannot_bypass_generation_history_guard_through_version_three",
- ordered_markers: &[
- "rollback_event_store_schema_offline(&managed,3).await",
- "lethistory_before:Vec<(Vec<u8>,i64)>=",
- "rollback_event_store_schema_offline(&managed,1).await",
- "RollbackWouldDiscardSourceGenerationHistory{current:3,target:1,floor:2,}",
- "inspect_event_store_schema_status(&managed).await",
- "Managed{version:3}",
- "v3source-generationhistoryafterrejectedbypass",
- "history_before",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "independent_file_pools_serialize_the_last_raw_event_byte_capacity_slot",
- ordered_markers: &[
- "RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1",
- "before_race.raw_event_text_bytes(),filler_target",
- "Barrier::new(3)",
- "tokio::spawn",
- "tokio::spawn",
- "tokio::join!",
- "(Ok(accepted),Err(rejected))|(Err(rejected),Ok(accepted))",
- "accepted.persistence.is_inserted()",
- "SourceCapacityExceeded{resource:crate::RadrootsEventStoreSourceCapacityResourceV1::RawEventBytes",
- "requested==contender_bytes",
- "cleanfullreopenafterlast-slotrace",
- "after_race.raw_event_count(),filler_count+1",
- "after_race.raw_event_text_bytes(),crate::RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1",
- "assert_eq!(retained_contenders,1)",
- ],
- },
-];
-
-const MANDATORY_BOUND_AUTHORITIES: &[DelegatedAuthoritySpec] = &[
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/nip09/reconciliation_v1.rs",
- test: "bounded_capacity_page_len_caps_gross_source_probe_at_one_over",
- ordered_markers: &[
- "forlimitin[25_000_u64,250_000_u64]",
- "bounded_capacity_page_len(current,limit)",
- "(1..=RECONCILIATION_SNAPSHOT_BATCH_LEN).contains(&fetched_len)",
- "assert_eq!(fetched,limit+1)",
- "bounded_capacity_page_len(24_576,25_000),(425,425)",
- "bounded_capacity_page_len(249_856,250_000),(145,145)",
- "bounded_capacity_page_len(25_000,25_000),(1,1)",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/source_maintenance_v1.rs",
- test: "generation_append_limit_returns_the_typed_current_and_limit",
- ordered_markers: &[
- "validate_source_generation_append_available_v1(7,8)",
- "validate_source_generation_append_available_v1(8,8)",
- "SourceGenerationHistoryLimitReached{current:8,limit:8,}",
- ],
- },
- DelegatedAuthoritySpec {
- path: "crates/event_store/src/source_maintenance_v1.rs",
- test: "retained_generation_nip09_logical_rows_have_an_audited_upper_bound",
- ordered_markers: &[
- "RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1",
- "RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1",
- "letper_generation=",
- "4*events+2*tags+2",
- "RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1",
- "4_800_016",
- "EVENT_STORE_MIGRATIONS",
- ],
- },
-];
-
-const MANDATORY_BOUND_HELPER_AUTHORITIES: &[DelegatedAuthoritySpec] = &[DelegatedAuthoritySpec {
- path: "crates/event_store/src/store.rs",
- test: "assert_utf16le_database_was_not_mutated",
- ordered_markers: &[
- "PRAGMAmain.encoding",
- "PRAGMAmain.journal_mode",
- "SELECTCOUNT(*)FROMmain.sqlite_schemaWHEREname='radroots_event_store_schema_migrations'ORname='event_envelopes'ORnameLIKE'radroots_event_store_%'",
- "assert_eq!(encoding,\"UTF-16le\")",
- "assert_eq!(journal_mode,\"delete\")",
- "assert_eq!(event_store_objects,0)",
- ],
-}];
-
-fn validate_delegated_test_authorities(
- workspace_root: &Path,
- vector: &SourceMaintenanceVector,
-) -> Result<(), String> {
- let delegated = vector
- .cases
- .iter()
- .filter(|case| case.execution != DIRECT_EXECUTOR)
- .map(|case| (case.authority_path.as_str(), case.authority.as_str()))
- .collect::<BTreeSet<_>>();
- let expected = DELEGATED_AUTHORITIES
- .iter()
- .map(|spec| (spec.path, spec.test))
- .collect::<BTreeSet<_>>();
- if delegated != expected {
- return Err(format!(
- "SourceMaintenance delegated-test inventory differs: expected {expected:?}, found {delegated:?}"
- ));
- }
- let mut executable_identities = Vec::new();
- for spec in DELEGATED_AUTHORITIES
- .iter()
- .chain(MANDATORY_BOUND_AUTHORITIES)
- {
- executable_identities.push(ExecutableAuthorityIdentity {
- path: spec.path.to_owned(),
- test: spec.test.to_owned(),
- tokens: validate_delegated_authority(workspace_root, *spec)?,
- });
- }
- for spec in MANDATORY_BOUND_HELPER_AUTHORITIES {
- executable_identities.push(ExecutableAuthorityIdentity {
- path: spec.path.to_owned(),
- test: spec.test.to_owned(),
- tokens: validate_bound_function_authority(workspace_root, *spec)?,
- });
- }
-
- let executor_source = rust_source(workspace_root, RESULT_VECTOR_EXECUTOR_RELATIVE)?;
- let executor = syn::parse_file(&executor_source)
- .map_err(|error| format!("parse {RESULT_VECTOR_EXECUTOR_RELATIVE}: {error}"))?;
- let executor = exact_free_function(&executor, RESULT_VECTOR_EXECUTOR_TEST)?;
- validate_executable_test_authority(
- RESULT_VECTOR_EXECUTOR_RELATIVE,
- RESULT_VECTOR_EXECUTOR_TEST,
- executor,
- )?;
- let executor = compact_tokens(executor);
- for case in vector
- .cases
- .iter()
- .filter(|case| case.execution == DIRECT_EXECUTOR)
- {
- require_marker(
- "SourceMaintenance direct result-vector executor",
- &executor,
- case.id.as_str(),
- )?;
- }
- require_marker(
- "SourceMaintenance direct result-vector executor",
- &executor,
- "assert_direct_cases_executed",
- )?;
- executable_identities.push(ExecutableAuthorityIdentity {
- path: RESULT_VECTOR_EXECUTOR_RELATIVE.to_owned(),
- test: RESULT_VECTOR_EXECUTOR_TEST.to_owned(),
- tokens: executor,
- });
- validate_executable_authority_identities(&executable_identities)?;
- let source_identities = bound_authority_source_identities(workspace_root)?;
- validate_bound_authority_source_identities(&source_identities)?;
- Ok(())
-}
-
-fn validate_delegated_authority(
- workspace_root: &Path,
- spec: DelegatedAuthoritySpec,
-) -> Result<String, String> {
- let source = rust_source(workspace_root, spec.path)?;
- let syntax = syn::parse_file(&source)
- .map_err(|error| format!("parse delegated authority {}: {error}", spec.path))?;
- let function = exact_free_function(&syntax, spec.test)?;
- validate_executable_test_authority(spec.path, spec.test, function)?;
- let function = compact_tokens(function);
- require_ordered_markers(
- &format!("delegated authority {}::{}", spec.path, spec.test),
- &function,
- spec.ordered_markers,
- )?;
- Ok(function)
-}
-
-fn validate_executable_authority_identities(
- identities: &[ExecutableAuthorityIdentity],
-) -> Result<(), String> {
- let bytes = canonical_json_bytes(&identities)?;
- let actual = sha256_hex(&bytes);
- if actual != EXECUTABLE_AUTHORITY_AST_SHA256 {
- return Err(format!(
- "SourceMaintenance executable direct/delegated authority AST identity drifted: expected {EXECUTABLE_AUTHORITY_AST_SHA256}, found {actual}"
- ));
- }
- Ok(())
-}
-
-fn bound_authority_source_identities(
- workspace_root: &Path,
-) -> Result<Vec<BoundAuthoritySourceIdentity>, String> {
- let paths = DELEGATED_AUTHORITIES
- .iter()
- .chain(MANDATORY_BOUND_AUTHORITIES)
- .chain(MANDATORY_BOUND_HELPER_AUTHORITIES)
- .map(|spec| spec.path)
- .chain([RESULT_VECTOR_EXECUTOR_RELATIVE])
- .collect::<BTreeSet<_>>();
- paths
- .into_iter()
- .map(|path| {
- let source = rust_source(workspace_root, path)?;
- let file = syn::parse_file(&source)
- .map_err(|error| format!("parse bound authority source {path}: {error}"))?;
- Ok(BoundAuthoritySourceIdentity {
- path: path.to_owned(),
- tokens: compact_tokens(&file),
- })
- })
- .collect()
-}
-
-fn validate_bound_authority_source_identities(
- identities: &[BoundAuthoritySourceIdentity],
-) -> Result<(), String> {
- let bytes = canonical_json_bytes(&identities)?;
- let actual = sha256_hex(&bytes);
- if actual != BOUND_AUTHORITY_SOURCE_AST_SHA256 {
- return Err(format!(
- "SourceMaintenance bound executor/test-module/helper source AST identity drifted: expected {BOUND_AUTHORITY_SOURCE_AST_SHA256}, found {actual}"
- ));
- }
- Ok(())
-}
-
-#[derive(Default)]
-struct EarlyReturnAudit {
- count: usize,
-}
-
-impl<'ast> syn::visit::Visit<'ast> for EarlyReturnAudit {
- fn visit_expr_return(&mut self, expression: &'ast syn::ExprReturn) {
- self.count += 1;
- syn::visit::visit_expr_return(self, expression);
- }
-}
-
-fn validate_executable_test_authority(
- relative: &str,
- name: &str,
- function: &syn::ItemFn,
-) -> Result<(), String> {
- let expected_attribute = if function.sig.asyncness.is_some() {
- "#[tokio::test]"
- } else {
- "#[test]"
- };
- let attributes = function
- .attrs
- .iter()
- .map(compact_tokens)
- .collect::<Vec<_>>();
- if attributes != [expected_attribute] {
- return Err(format!(
- "executable test authority {relative}::{name} must have exactly `{expected_attribute}` and no disabling or conditional attributes; found {attributes:?}"
- ));
- }
- if !matches!(function.vis, syn::Visibility::Inherited)
- || function.sig.constness.is_some()
- || function.sig.unsafety.is_some()
- || function.sig.abi.is_some()
- || !function.sig.inputs.is_empty()
- || !function.sig.generics.params.is_empty()
- || function.sig.generics.where_clause.is_some()
- || !matches!(function.sig.output, syn::ReturnType::Default)
- || function.sig.variadic.is_some()
- {
- return Err(format!(
- "executable test authority {relative}::{name} must remain a private zero-argument test with no generic, ABI, unsafe, variadic, or return-type escape"
- ));
- }
-
- use syn::visit::Visit;
- let mut returns = EarlyReturnAudit::default();
- returns.visit_block(&function.block);
- if returns.count != 0 {
- return Err(format!(
- "executable test authority {relative}::{name} must not contain early return control flow; found {} return expression(s)",
- returns.count
- ));
- }
Ok(())
}
-fn validate_bound_function_authority(
- workspace_root: &Path,
- spec: DelegatedAuthoritySpec,
-) -> Result<String, String> {
- let source = rust_source(workspace_root, spec.path)?;
- let syntax = syn::parse_file(&source)
- .map_err(|error| format!("parse bound authority {}: {error}", spec.path))?;
- let function = exact_free_function_tokens(&syntax, spec.test)?;
- require_ordered_markers(
- &format!("bound authority {}::{}", spec.path, spec.test),
- &function,
- spec.ordered_markers,
- )?;
- Ok(function)
-}
-
#[cfg(test)]
mod tests {
use super::*;
+ use std::fs;
use std::path::PathBuf;
fn workspace_root() -> PathBuf {
@@ -3788,11 +2829,8 @@ mod tests {
}
#[test]
- fn source_inventory_excludes_outputs_and_exactly_supersedes_predecessors() {
- let root = workspace_root();
+ fn source_inventory_excludes_generated_outputs() {
validate_source_inventory().expect("source inventory");
- validate_predecessor_production_source_coverage(&root)
- .expect("exact predecessor supersession");
let source_paths = SOURCE_SPECS
.iter()
@@ -3801,14 +2839,6 @@ mod tests {
for generated in GENERATED_ARTIFACT_PATHS {
assert!(!source_paths.contains(generated));
}
- assert_eq!(
- PREDECESSOR_SUPERSEDED_SOURCE_PATHS
- .iter()
- .copied()
- .collect::<BTreeSet<_>>()
- .len(),
- PREDECESSOR_SUPERSEDED_SOURCE_PATHS.len()
- );
}
#[test]
@@ -3866,308 +2896,45 @@ mod tests {
}
#[test]
- fn generated_bundle_render_is_rerunnable_without_byte_drift() {
+ fn immutable_bundle_write_path_is_validation_only() {
let root = workspace_root();
- let before = expected_artifacts(&root)
- .expect("first in-memory generated bundle render")
- .into_iter()
- .map(|artifact| (artifact.relative, artifact.contents))
- .collect::<Vec<_>>();
- let after = expected_artifacts(&root)
- .expect("second in-memory generated bundle render")
- .into_iter()
- .map(|artifact| (artifact.relative, artifact.contents))
- .collect::<Vec<_>>();
- assert_eq!(before, after);
- }
-
- fn current_executable_authority_identities(root: &Path) -> Vec<ExecutableAuthorityIdentity> {
- let mut identities = DELEGATED_AUTHORITIES
+ let before = IMMUTABLE_PREDECESSOR_ARTIFACTS
.iter()
- .chain(MANDATORY_BOUND_AUTHORITIES)
- .map(|spec| ExecutableAuthorityIdentity {
- path: spec.path.to_owned(),
- test: spec.test.to_owned(),
- tokens: validate_delegated_authority(root, *spec)
- .expect("current delegated executable authority"),
+ .map(|artifact| {
+ (
+ artifact.relative,
+ fs::read(root.join(artifact.relative)).expect("immutable artifact"),
+ )
})
.collect::<Vec<_>>();
- identities.extend(MANDATORY_BOUND_HELPER_AUTHORITIES.iter().map(|spec| {
- ExecutableAuthorityIdentity {
- path: spec.path.to_owned(),
- test: spec.test.to_owned(),
- tokens: validate_bound_function_authority(root, *spec)
- .expect("current bound helper authority"),
- }
- }));
- let source = rust_source(root, RESULT_VECTOR_EXECUTOR_RELATIVE)
- .expect("direct result-vector executor source");
- let file = syn::parse_file(&source).expect("direct result-vector executor AST");
- let function = exact_free_function(&file, RESULT_VECTOR_EXECUTOR_TEST)
- .expect("direct result-vector executor function");
- validate_executable_test_authority(
- RESULT_VECTOR_EXECUTOR_RELATIVE,
- RESULT_VECTOR_EXECUTOR_TEST,
- function,
- )
- .expect("current direct executable authority");
- identities.push(ExecutableAuthorityIdentity {
- path: RESULT_VECTOR_EXECUTOR_RELATIVE.to_owned(),
- test: RESULT_VECTOR_EXECUTOR_TEST.to_owned(),
- tokens: compact_tokens(function),
- });
- identities
- }
-
- fn assert_bound_source_mutation_rejected(
- baseline: &[BoundAuthoritySourceIdentity],
- path: &str,
- source: &str,
- label: &str,
- ) {
- let mut identities = baseline.to_vec();
- let identity = identities
- .iter_mut()
- .find(|identity| identity.path == path)
- .unwrap_or_else(|| panic!("missing bound source identity for {path}"));
- let file = syn::parse_file(source)
- .unwrap_or_else(|error| panic!("parse {label} mutation for {path}: {error}"));
- let tokens = compact_tokens(&file);
- assert_ne!(tokens, identity.tokens, "{label} fixture must mutate");
- identity.tokens = tokens;
- let error = validate_bound_authority_source_identities(&identities)
- .expect_err("bound authority source-context drift must fail closed");
- assert!(
- error.contains("bound executor/test-module/helper source AST identity drifted"),
- "unexpected {label} error: {error}"
- );
- }
-
- #[test]
- fn bound_executor_and_test_module_sources_are_exact_ast_authority() {
- let root = workspace_root();
- let baseline =
- bound_authority_source_identities(&root).expect("current bound source identities");
- validate_bound_authority_source_identities(&baseline)
- .expect("current bound source aggregate identity");
-
- let executor = rust_source(&root, RESULT_VECTOR_EXECUTOR_RELATIVE)
- .expect("direct result-vector executor source");
- let crate_disabled = executor.replacen(
- "#![forbid(unsafe_code)]",
- "#![forbid(unsafe_code)]\n#![cfg(any())]",
- 1,
- );
- assert_bound_source_mutation_rejected(
- &baseline,
- RESULT_VECTOR_EXECUTOR_RELATIVE,
- &crate_disabled,
- "crate-disabled direct executor",
- );
-
- let delegated_path = "crates/event_store/src/source_maintenance_v1.rs";
- let delegated =
- rust_source(&root, delegated_path).expect("delegated authority module source");
- let module_disabled = delegated.replacen(
- "#[cfg(test)]\nmod tests {",
- "#[cfg(all(test, any()))]\nmod tests {",
- 1,
- );
- assert_bound_source_mutation_rejected(
- &baseline,
- delegated_path,
- &module_disabled,
- "disabled delegated test module",
- );
-
- let macro_shadowed = executor.replacen(
- "#![forbid(unsafe_code)]",
- "#![forbid(unsafe_code)]\nmacro_rules! assert_eq { ($($tokens:tt)*) => {}; }",
- 1,
- );
- assert_bound_source_mutation_rejected(
- &baseline,
- RESULT_VECTOR_EXECUTOR_RELATIVE,
- ¯o_shadowed,
- "shadowing direct-executor assertion macro",
- );
- }
-
- #[test]
- fn executable_authority_rejects_disabled_missing_and_unreachable_tests() {
- let root = workspace_root();
- let source = rust_source(&root, RESULT_VECTOR_EXECUTOR_RELATIVE)
- .expect("direct result-vector executor source");
- for (label, mutation) in [
- (
- "ignored direct executor",
- source.replacen("#[tokio::test]", "#[ignore]\n#[tokio::test]", 1),
- ),
- (
- "missing direct executor attribute",
- source.replacen("#[tokio::test]\n", "", 1),
- ),
- (
- "early-returning direct executor",
- source.replacen(
- "async fn source_maintenance_v1_result_vector() {",
- "async fn source_maintenance_v1_result_vector() {\n return;",
- 1,
- ),
- ),
- ] {
- assert_ne!(mutation, source, "{label} fixture must mutate");
- let file = syn::parse_file(&mutation).expect("mutated direct executor AST");
- let function = exact_free_function(&file, RESULT_VECTOR_EXECUTOR_TEST)
- .expect("mutated direct executor function");
- let error = validate_executable_test_authority(
- RESULT_VECTOR_EXECUTOR_RELATIVE,
- RESULT_VECTOR_EXECUTOR_TEST,
- function,
- )
- .expect_err("disabled or early-returning direct executor must fail closed");
- assert!(
- error.contains("executable test authority"),
- "unexpected {label} error: {error}"
+ write_source_maintenance_manifest(&root)
+ .expect("valid frozen predecessor remains accepted");
+ for (relative, bytes) in &before {
+ assert_eq!(
+ fs::read(root.join(relative)).expect("immutable artifact after validation"),
+ *bytes,
+ "validation-only write path mutated {relative}"
);
}
- let mut identities = current_executable_authority_identities(&root);
- validate_executable_authority_identities(&identities)
- .expect("current aggregate executable identity");
- let direct = identities
- .last_mut()
- .expect("direct executable identity is terminal");
- let file = syn::parse_file(&source).expect("direct executor AST");
- let function = exact_free_function(&file, RESULT_VECTOR_EXECUTOR_TEST)
- .expect("direct executor function");
- let mut function = function.clone();
- let body = function.block.clone();
- *function.block = syn::parse_quote!({ if false #body; });
- direct.tokens = compact_tokens(&function);
- let error = validate_executable_authority_identities(&identities)
- .expect_err("non-returning unreachable test body must fail exact AST authority");
- assert!(error.contains("executable direct/delegated authority AST identity drifted"));
-
- let mut identities = current_executable_authority_identities(&root);
- let helper_spec = MANDATORY_BOUND_HELPER_AUTHORITIES[0];
- let helper_source = rust_source(&root, helper_spec.path).expect("bound helper source");
- let helper_file = syn::parse_file(&helper_source).expect("bound helper AST");
- let helper = exact_free_function(&helper_file, helper_spec.test).expect("bound helper");
- let mut bypass = helper.clone();
- let body = bypass.block.clone();
- *bypass.block = syn::parse_quote!({ if false #body; });
- let identity = identities
- .iter_mut()
- .find(|identity| identity.path == helper_spec.path && identity.test == helper_spec.test)
- .expect("bound helper identity");
- identity.tokens = compact_tokens(&bypass);
- validate_executable_authority_identities(&identities)
- .expect_err("unreachable bound helper body must fail exact AST authority");
- }
-
- #[test]
- fn command_and_release_validation_reachability_is_exact() {
- let root = workspace_root();
- let contract =
- rust_source(&root, CONTRACT_COMMAND_SOURCE_RELATIVE).expect("contract command source");
- let main = rust_source(&root, XTASK_MAIN_SOURCE_RELATIVE).expect("xtask main source");
- validate_contract_command_reachability_sources(&contract, &main)
- .expect("current aggregate and release validation reachability");
-
- let mutations = [
- (
- "aggregate removal",
- contract.replacen(
- " validate_source_maintenance_manifest(workspace_root)?;\n",
- "",
- 1,
- ),
- main.clone(),
- ),
- (
- "aggregate discarded result",
- contract.replacen(
- " validate_source_maintenance_manifest(workspace_root)?;",
- " let _ = validate_source_maintenance_manifest(workspace_root);",
- 1,
- ),
- main.clone(),
- ),
- (
- "aggregate reordering",
- contract.replacen(
- " validate_food_availability_projection_manifest(workspace_root)?;\n validate_source_maintenance_manifest(workspace_root)?;",
- " validate_source_maintenance_manifest(workspace_root)?;\n validate_food_availability_projection_manifest(workspace_root)?;",
- 1,
- ),
- main.clone(),
- ),
- (
- "contract validation removal",
- contract.clone(),
- main.replacen(
- " .and_then(|_| contract::validate_artifact_contracts(&root))",
- " .map(|_| ())",
- 1,
- ),
- ),
- (
- "contract validate dispatch bypass",
- contract.clone(),
- main.replacen(
- " Some(\"validate\") => validate_contract(),",
- " Some(\"validate\") => Ok(()),",
- 1,
- ),
- ),
- (
- "release preflight dispatch bypass",
- contract.clone(),
- main.replacen(
- " Some(\"preflight\") => release_preflight(),",
- " Some(\"preflight\") => Ok(()),",
- 1,
- ),
- ),
- (
- "release validation removal",
- contract.clone(),
- main.replacen(" contract::validate_artifact_contracts(root)?;\n", "", 1),
- ),
- (
- "release validation discarded result",
- contract.clone(),
- main.replacen(
- " contract::validate_artifact_contracts(root)?;",
- " let _ = contract::validate_artifact_contracts(root);",
- 1,
- ),
- ),
- (
- "release validation reordering",
- contract.clone(),
- main.replacen(
- " dto_roots::check(root)?;\n contract::validate_artifact_contracts(root)?;",
- " contract::validate_artifact_contracts(root)?;\n dto_roots::check(root)?;",
- 1,
- ),
- ),
- ];
- for (label, contract_mutation, main_mutation) in mutations {
- assert!(
- contract_mutation != contract || main_mutation != main,
- "{label} fixture must mutate"
- );
- let error =
- validate_contract_command_reachability_sources(&contract_mutation, &main_mutation)
- .expect_err("validation reachability drift must fail closed");
- assert!(
- error.contains("validation call-path authority drifted")
- || error.contains("full dispatch AST authority drifted"),
- "unexpected {label} error: {error}"
- );
+ let tampered = tempfile::tempdir().expect("tampered workspace");
+ for (relative, bytes) in &before {
+ let path = tampered.path().join(relative);
+ fs::create_dir_all(path.parent().expect("artifact parent"))
+ .expect("create artifact parent");
+ fs::write(path, bytes).expect("copy immutable artifact");
}
+ let manifest = tampered.path().join(MANIFEST_RELATIVE);
+ let mut tampered_manifest = fs::read(&manifest).expect("tampered manifest source");
+ tampered_manifest.push(b'\n');
+ fs::write(&manifest, &tampered_manifest).expect("tamper manifest");
+ write_source_maintenance_manifest(tampered.path())
+ .expect_err("tampered frozen predecessor must be rejected");
+ assert_eq!(
+ fs::read(manifest).expect("tampered manifest after validation"),
+ tampered_manifest,
+ "rejected validation-only write path must not rewrite a tampered bundle"
+ );
}
#[test]
diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs
@@ -21,6 +21,7 @@ fn usage() {
eprintln!(" cargo xtask contract nip09-reconciliation-manifest [--write]");
eprintln!(" cargo xtask contract food-availability-projection-manifest [--write]");
eprintln!(" cargo xtask contract source-maintenance-manifest [--write]");
+ eprintln!(" cargo xtask contract raw-source-rebuild-manifest [--write]");
eprintln!(" cargo xtask contract knowledge-manifest [--write]");
eprintln!(" cargo xtask dto-roots --check|--write");
eprintln!(" cargo xtask release preflight");
@@ -128,6 +129,15 @@ fn run_contract(args: &[String]) -> Result<(), String> {
"source-maintenance-manifest accepts no arguments or exactly --write".to_string(),
),
},
+ Some("raw-source-rebuild-manifest") => match &args[1..] {
+ [] => contract::validate_raw_source_rebuild_manifest(&workspace_root()),
+ [flag] if flag == "--write" => {
+ contract::write_raw_source_rebuild_manifest(&workspace_root())
+ }
+ _ => Err(
+ "raw-source-rebuild-manifest accepts no arguments or exactly --write".to_string(),
+ ),
+ },
Some("knowledge-manifest") => {
if args.get(1).map(String::as_str) == Some("--write") {
contract::write_knowledge_contract_manifest(&workspace_root())
@@ -248,6 +258,12 @@ mod tests {
])
.expect_err("invalid SourceMaintenance manifest mode");
assert!(invalid_source_maintenance.contains("exactly --write"));
+ let invalid_raw_source_rebuild = run_contract(&[
+ "raw-source-rebuild-manifest".to_string(),
+ "--invalid".to_string(),
+ ])
+ .expect_err("invalid raw-source rebuild manifest mode");
+ assert!(invalid_raw_source_rebuild.contains("exactly --write"));
let unknown_root = run(&["unknown".to_string()]).expect_err("unknown command");
assert!(unknown_root.contains("unknown command"));
@@ -347,6 +363,8 @@ mod tests {
.expect("contract FoodAvailability projection manifest");
run_contract(&["source-maintenance-manifest".to_string()])
.expect("contract SourceMaintenance manifest");
+ run_contract(&["raw-source-rebuild-manifest".to_string()])
+ .expect("contract raw-source rebuild manifest");
run_contract(&["knowledge-manifest".to_string()]).expect("contract knowledge manifest");
}
}