From 1767dd759de8b300e43de38da4ea1518964c2a9c Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 18 Oct 2026 23:02:18 -0700 Subject: [PATCH 1/3] M2 slice 1: opt-in application-root commitment profile (experimental) Behind application_root_profile (implies application_root_shadow), a worldline registered with CommitmentProfile::ApplicationRadixV0 commits H("echo.state-root.application-radix.v0" || application_root) as its state_root: the engine advances the worldline trie from the commit write set instead of walking the graph. The profile travels with the trie through rollback, replay bases, forks and the commit guard. Replay, earlier readings, imports, playback, initial boundaries and checkpoint restores compute the worldline own recipe; a history replayed or recovered under the other profile fails. Legacy worldlines are the default and unchanged. F5 tier M: append 5,630 -> 272 ms, replay 5,552 -> 99 ms. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 6 + CHANGELOG.md | 9 ++ crates/warp-core/Cargo.toml | 3 + crates/warp-core/benches/scene_fixtures.rs | 29 +++- .../benches/support/fixtures/think_append.rs | 20 ++- crates/warp-core/src/application_root.rs | 45 ++++++ crates/warp-core/src/coordinator.rs | 132 ++++++++++++++++-- crates/warp-core/src/echo_operation.rs | 2 +- crates/warp-core/src/engine_impl.rs | 82 +++++++++-- crates/warp-core/src/playback.rs | 3 +- crates/warp-core/src/provenance_store.rs | 20 ++- crates/warp-core/src/settlement.rs | 7 +- crates/warp-core/src/worldline_state.rs | 131 ++++++++++++++++- .../tests/application_root_profile_tests.rs | 75 ++++++++++ .../tests/application_root_shadow_tests.rs | 25 +++- .../executable_operation_pipeline_tests.rs | 81 ++++++++++- .../tests/trusted_runtime_host_loop_tests.rs | 86 ++++++++++++ docs/studies/m2-commitment-profile.md | 104 ++++++++++++++ docs/studies/m2-commitment-profile/bench.txt | 28 ++++ .../full-suite-delta-validate.txt | 109 +++++++++++++++ .../m2-commitment-profile/targeted-tests.txt | 8 ++ 21 files changed, 947 insertions(+), 58 deletions(-) create mode 100644 crates/warp-core/tests/application_root_profile_tests.rs create mode 100644 docs/studies/m2-commitment-profile.md create mode 100644 docs/studies/m2-commitment-profile/bench.txt create mode 100644 docs/studies/m2-commitment-profile/full-suite-delta-validate.txt create mode 100644 docs/studies/m2-commitment-profile/targeted-tests.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d40e59aa7..d3b484e98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -196,6 +196,8 @@ jobs: run: cargo clippy -p warp-core --features native_rule_bootstrap,trusted_runtime --test scene_fixture_tests --bench scene_fixtures -- -D warnings -D missing_docs - name: cargo clippy (experimental application root, M1) run: cargo clippy -p warp-core --features native_rule_bootstrap,trusted_runtime,host_test,application_root_shadow --lib --tests -- -D warnings + - name: cargo clippy (experimental application-root commitment profile, M2) + run: cargo clippy -p warp-core --features native_rule_bootstrap,trusted_runtime,host_test,application_root_profile --lib --tests --benches -- -D warnings - name: cargo clippy (compiler-produced pure evaluation) run: cargo clippy -p warp-core --features trusted_runtime --lib --test edict_pure_evaluation_tests --test edict_pure_predicates_tests --test edict_pure_unsigned_subtraction_tests --test edict_pure_byte_length_tests --test edict_byte_equality_tests --test edict_byte_slice_tests --test edict_node_read_tests --test edict_byte_concat_tests --test edict_byte_literal_tests --test edict_source_functions_tests -- -D warnings -D missing_docs @@ -390,6 +392,10 @@ jobs: run: | cargo test -p warp-core --features native_rule_bootstrap,trusted_runtime,host_test,application_root_shadow --lib application_root cargo test -p warp-core --features native_rule_bootstrap,trusted_runtime,application_root_shadow,delta_validate --test application_root_shadow_tests + - name: cargo test (experimental application-root commitment profile, M2) + run: | + cargo test -p warp-core --features native_rule_bootstrap,trusted_runtime,host_test,application_root_profile --lib application_ + cargo test -p warp-core --features native_rule_bootstrap,trusted_runtime,host_test,application_root_profile,delta_validate --test application_root_profile_tests --test trusted_runtime_host_loop_tests --test executable_operation_pipeline_tests - name: cargo test (compiler-produced pure evaluation) run: | cargo test -p warp-core --features trusted_runtime --test edict_pure_evaluation_tests --test edict_pure_predicates_tests --test edict_pure_unsigned_subtraction_tests --test edict_pure_byte_length_tests --test edict_byte_equality_tests --test edict_byte_slice_tests --test edict_node_read_tests --test edict_byte_concat_tests --test edict_byte_literal_tests --test edict_source_functions_tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 042144a96..c31c6b975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ ### Added +- Experimental feature `application_root_profile` (M2, off by default, implies + `application_root_shadow`): a worldline registered with + `WorldlineState::with_commitment_profile(CommitmentProfile::ApplicationRadixV0)` + commits `H("echo.state-root.application-radix.v0" || application_root)` as + its `state_root` instead of walking the graph. Replay, readings at earlier + coordinates, forks, initial boundaries, imports and WAL recovery verify + under the worldline's profile; a history replayed or recovered under the + other profile fails. Worldlines default to `LegacyReachableV1`, whose roots, + commit hashes and receipts are unchanged. - Experimental feature `application_root_shadow` (M1, off by default): an incremental application root over every node, edge and attachment record of a worldline state, kept beside the legacy state root, which it neither diff --git a/crates/warp-core/Cargo.toml b/crates/warp-core/Cargo.toml index 65c390527..2fd8e65ac 100644 --- a/crates/warp-core/Cargo.toml +++ b/crates/warp-core/Cargo.toml @@ -48,6 +48,9 @@ host_test = [] # EXPERIMENTAL (M1): keep an incremental application root beside the legacy # state root, in shadow mode. It changes no root, receipt or wire format. application_root_shadow = [] +# EXPERIMENTAL (M2): let a worldline opt into committing the application root +# as its state root. Legacy worldlines and histories are unchanged. +application_root_profile = ["application_root_shadow"] # TRUSTED HOST ONLY: exposes runtime-owner authority constructors for host # adapters that own scheduler/runtime control. Application crates must not # enable this feature. diff --git a/crates/warp-core/benches/scene_fixtures.rs b/crates/warp-core/benches/scene_fixtures.rs index 041900d28..bf5297a1a 100644 --- a/crates/warp-core/benches/scene_fixtures.rs +++ b/crates/warp-core/benches/scene_fixtures.rs @@ -60,18 +60,35 @@ fn main() { ); for tier in [think_append::S, think_append::M] { - run_f5(tier, SEED, ROUNDS); + run_f5(tier, SEED, ROUNDS, "legacy-reachable-v1", think_append::run); + } + // The experimental application-root commitment profile (M2). + #[cfg(feature = "application_root_profile")] + for tier in [think_append::S, think_append::M] { + run_f5(tier, SEED, ROUNDS, "application-radix-v0", |tier, seed| { + think_append::run_with_profile( + tier, + seed, + warp_core::application_root::CommitmentProfile::ApplicationRadixV0, + ) + }); } } -fn run_f5(tier: think_append::Tier, seed: u64, rounds: usize) { +fn run_f5( + tier: think_append::Tier, + seed: u64, + rounds: usize, + profile: &str, + execute: fn(think_append::Tier, u64) -> think_append::Observation, +) { let spec = think_append::workload(tier, seed); let script = think_append::script(tier, seed); let digest = hex32(&think_append::script_digest(&script)); let expect = think_append::expectation(tier, Some(&digest)); let workload_id = spec.workload_id(); println!( - "F5 tier={} seed={seed} workload_id={} expectation_id={} script_digest={digest}", + "F5 tier={} profile={profile} seed={seed} workload_id={} expectation_id={} script_digest={digest}", tier.name, hex32(&workload_id), hex32(&expect.expectation_id(&workload_id)) @@ -96,7 +113,7 @@ fn run_f5(tier: think_append::Tier, seed: u64, rounds: usize) { let mut per_append = Vec::new(); let mut comparable = true; for round in 1..=rounds { - let obs = think_append::run(tier, seed); + let obs = execute(tier, seed); let class = think_append::validate(tier, &script, &expect, &obs); let a = &obs.accounting; println!( @@ -128,7 +145,7 @@ fn run_f5(tier: think_append::Tier, seed: u64, rounds: usize) { let committed = tier.appends as f64; let append_p50 = median_ms(appends); println!( - "F5 tier={} summary valid_rounds={rounds} append_p50_ms={append_p50:.2} \ + "F5 tier={} profile={profile} summary valid_rounds={rounds} append_p50_ms={append_p50:.2} \ per_append_p50_ms={:.4} useful_throughput_appends_per_s={:.0} replay_p50_ms={:.2}", tier.name, median_ms(per_append), @@ -137,7 +154,7 @@ fn run_f5(tier: think_append::Tier, seed: u64, rounds: usize) { ); } else { println!( - "F5 tier={} summary withheld: not every round was valid", + "F5 tier={} profile={profile} summary withheld: not every round was valid", tier.name ); } diff --git a/crates/warp-core/benches/support/fixtures/think_append.rs b/crates/warp-core/benches/support/fixtures/think_append.rs index 8ea4b9fe2..fe4dc5789 100644 --- a/crates/warp-core/benches/support/fixtures/think_append.rs +++ b/crates/warp-core/benches/support/fixtures/think_append.rs @@ -435,8 +435,26 @@ pub struct Timings { /// Executes the fixture through the trusted host. pub fn run(tier: Tier, seed: u64) -> Observation { + run_from(tier, seed, initial_state(tier)) +} + +/// Executes the fixture on a worldline with commitment profile `profile` +/// (M2, experimental). +#[cfg(feature = "application_root_profile")] +pub fn run_with_profile( + tier: Tier, + seed: u64, + profile: warp_core::application_root::CommitmentProfile, +) -> Observation { + run_from( + tier, + seed, + initial_state(tier).with_commitment_profile(profile), + ) +} + +fn run_from(tier: Tier, seed: u64, initial: WorldlineState) -> Observation { let script = script(tier, seed); - let initial = initial_state(tier); let base = initial.clone(); let worldline_id = WorldlineId::from_bytes(WORLDLINE); let mut runtime = WorldlineRuntime::new(); diff --git a/crates/warp-core/src/application_root.rs b/crates/warp-core/src/application_root.rs index 180f04efc..5bdb2b15a 100644 --- a/crates/warp-core/src/application_root.rs +++ b/crates/warp-core/src/application_root.rs @@ -338,6 +338,51 @@ fn update(subtree: Option<&Arc>, ops: &[(Key, Option)]) -> Optio } } +/// How a worldline's committed `state_root` is computed (M2, experimental). +/// +/// The profile is part of a worldline's registration, like its initial state: +/// it is chosen once, inherited by forks and replay bases, and never inferred +/// from a root's bytes. A history is only verifiable under the profile it was +/// committed with; replaying it under another fails with a root mismatch. +#[cfg(feature = "application_root_profile")] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub enum CommitmentProfile { + /// The legacy reachable-state root. Every existing history uses it, and + /// its recipe is unchanged. + #[default] + LegacyReachableV1, + /// The experimental application root: every record, reachable or not, + /// in the `echo.application-root.radix.v0` trie, tagged by + /// [`profile_tagged_state_root`] so it never equals a legacy root. + ApplicationRadixV0, +} + +#[cfg(feature = "application_root_profile")] +impl CommitmentProfile { + /// A stable identifier for diagnostics and descriptors. + #[must_use] + pub const fn id(self) -> &'static str { + match self { + Self::LegacyReachableV1 => "echo.state-root.legacy-reachable.v1", + Self::ApplicationRadixV0 => "echo.state-root.application-radix.v0", + } + } +} + +/// The committed `state_root` of an application-profile worldline: +/// `H("echo.state-root.application-radix.v0" || application_root)`. +/// +/// The tag keeps the value domain-separated from every legacy root, so a +/// root is never readable under the wrong profile. +#[cfg(feature = "application_root_profile")] +#[must_use] +pub fn profile_tagged_state_root(application_root: &Hash) -> Hash { + hash_parts(&[ + CommitmentProfile::ApplicationRadixV0.id().as_bytes(), + application_root, + ]) +} + /// A persistent version of the application reading. #[derive(Clone, Debug, Default)] pub(crate) struct ApplicationTrie { diff --git a/crates/warp-core/src/coordinator.rs b/crates/warp-core/src/coordinator.rs index 6958caff0..9a769feb5 100644 --- a/crates/warp-core/src/coordinator.rs +++ b/crates/warp-core/src/coordinator.rs @@ -5917,12 +5917,15 @@ impl SchedulerCoordinator { &admitted_submission_ids(*key, &admitted), ) .map_err(RuntimeError::from)?; + // An application-profile commit already advanced the trie. #[cfg(feature = "application_root_shadow")] - frontier.state_mut().advance_application_root( - application_root_was_current, - outcome.patch.ops(), - outcome.patch.out_slots(), - ); + if !frontier.state().application_root_is_current() { + frontier.state_mut().advance_application_root( + application_root_was_current, + outcome.patch.ops(), + outcome.patch.out_slots(), + ); + } runtime_before .state_journal_mut(key.worldline_id)? .push_touches(touches); @@ -5953,12 +5956,15 @@ impl SchedulerCoordinator { &admitted_submission_ids(*key, &admitted), ) .map_err(RuntimeError::from)?; + // An application-profile commit already advanced the trie. #[cfg(feature = "application_root_shadow")] - frontier.state_mut().advance_application_root( - application_root_was_current, - outcome.patch.ops(), - outcome.patch.out_slots(), - ); + if !frontier.state().application_root_is_current() { + frontier.state_mut().advance_application_root( + application_root_was_current, + outcome.patch.ops(), + outcome.patch.out_slots(), + ); + } runtime_before .state_journal_mut(key.worldline_id)? .push_touches(touches); @@ -8711,6 +8717,112 @@ mod tests { assert_eq!(provenance.len(worldline_id).unwrap(), 1); } + #[cfg(feature = "application_root_profile")] + #[test] + fn application_profile_commits_the_tagged_application_root_and_verifies_only_under_it() { + use crate::application_root::{profile_tagged_state_root, CommitmentProfile}; + + let worldline_id = wl(1); + let base = + WorldlineState::empty().with_commitment_profile(CommitmentProfile::ApplicationRadixV0); + let mut runtime = WorldlineRuntime::new(); + let mut engine = empty_engine(); + engine.register_rule(toy_increment_contract_rule()).unwrap(); + runtime + .register_worldline(worldline_id, base.clone()) + .unwrap(); + register_head( + &mut runtime, + worldline_id, + "default", + None, + true, + InboxPolicy::AcceptAll, + ); + let mut provenance = mirrored_provenance(&runtime); + assert_eq!( + provenance.initial_boundary_hash(worldline_id).unwrap(), + profile_tagged_state_root(&base.application_root()), + "the initial boundary is committed under the profile too" + ); + for index in 0..4_u8 { + runtime + .ingest(IngressEnvelope::local_intent( + IngressTarget::DefaultWriter { worldline_id }, + make_intent_kind("echo.intent/eint-v1"), + toy_increment_intent(&[index]), + )) + .unwrap(); + let records = + SchedulerCoordinator::super_tick(&mut runtime, &mut provenance, &mut engine) + .unwrap(); + let state = runtime.worldlines().get(&worldline_id).unwrap().state(); + let tagged = profile_tagged_state_root(&state.application_root_rebuilt()); + assert_eq!(records[0].state_root, tagged, "commit {index}"); + assert_eq!(state.state_root(), tagged, "commit {index}"); + assert_ne!( + tagged, + crate::snapshot::compute_state_root_for_warp_state( + state.warp_state(), + state.root() + ), + "the profile never reinterprets a legacy root" + ); + } + + let live = runtime.worldlines().get(&worldline_id).unwrap().state(); + let replayed = provenance + .replay_worldline_state(worldline_id, &base) + .unwrap(); + assert_eq!(replayed.state_root(), live.state_root()); + assert_eq!( + replayed.commitment_profile(), + CommitmentProfile::ApplicationRadixV0 + ); + let legacy_base = base.with_commitment_profile(CommitmentProfile::LegacyReachableV1); + assert!( + provenance + .replay_worldline_state(worldline_id, &legacy_base) + .is_err(), + "an application-profile history does not verify under the legacy profile" + ); + + let child_worldline_id = wl(43); + runtime + .fork_strand( + &mut provenance, + ForkStrandRequest { + strand_id: make_strand_id("application-profile-fork"), + source_lane_id: worldline_id, + fork_tick: wt(3), + child_worldline_id, + writer_heads: vec![WriterHead::with_routing( + WriterHeadKey { + worldline_id: child_worldline_id, + head_id: make_head_id("application-profile-child"), + }, + PlaybackMode::Play, + InboxPolicy::AcceptAll, + None, + true, + )], + retention_posture: test_retention_posture(10), + }, + ) + .unwrap(); + let source = runtime.worldlines().get(&worldline_id).unwrap().state(); + let child = runtime + .worldlines() + .get(&child_worldline_id) + .unwrap() + .state(); + assert_eq!( + child.commitment_profile(), + CommitmentProfile::ApplicationRadixV0 + ); + assert_eq!(child.state_root(), source.state_root()); + } + #[cfg(feature = "application_root_shadow")] #[test] fn application_root_tracks_runtime_commits_forks_and_replay() { diff --git a/crates/warp-core/src/echo_operation.rs b/crates/warp-core/src/echo_operation.rs index e733a725f..f0e382d08 100644 --- a/crates/warp-core/src/echo_operation.rs +++ b/crates/warp-core/src/echo_operation.rs @@ -6457,7 +6457,7 @@ pub(crate) fn commit_scheduler_action_batch_to_state_v1( } debug_assert_eq!( state_root_before, - crate::snapshot::compute_state_root_for_warp_state(state.warp_state(), state.root()), + state.recomputed_state_root(), "the caller's state root must be the root of the state being committed" ); let tx_raw = state diff --git a/crates/warp-core/src/engine_impl.rs b/crates/warp-core/src/engine_impl.rs index 886ab953b..107b0ffb5 100644 --- a/crates/warp-core/src/engine_impl.rs +++ b/crates/warp-core/src/engine_impl.rs @@ -502,6 +502,13 @@ pub struct Engine { /// outside a runtime batch always starts a fresh log, so a log left behind /// by an aborted commit never leaks into a later patch. runtime_batch_touch_log: bool, + /// During a runtime batch for an application-profile worldline (M2): the + /// worldline's current application trie, to be advanced by the commit. + #[cfg(feature = "application_root_profile")] + runtime_application_trie: Option, + /// The trie the commit advanced, handed back to the worldline state. + #[cfg(feature = "application_root_profile")] + runtime_application_trie_after: Option, /// Working memory for the state root, kept across commits so hashing the /// reachable graph allocates nothing once it has grown to fit. state_root_scratch: crate::snapshot::StateRootScratch, @@ -586,6 +593,11 @@ impl<'a> RuntimeCommitStateGuard<'a> { fn enter(engine: &'a mut Engine, state: &'a mut WorldlineState) -> Self { // Taken before the graph is borrowed mutably, so it keeps whether it // described the pre-commit graph. + #[cfg(feature = "application_root_profile")] + { + engine.runtime_application_trie = state.committing_application_trie(); + engine.runtime_application_trie_after = None; + } #[cfg(feature = "application_root_shadow")] let saved_application_root = state.take_application_root(); let saved_engine_state = @@ -661,6 +673,24 @@ impl<'a> RuntimeCommitStateGuard<'a> { .as_ref("runtime commit guard missing pre-commit state") } + /// The worldline's application trie after a successful commit: the trie + /// the commit advanced for an application-profile worldline, otherwise + /// the saved trie marked stale for the caller to advance. + #[cfg(feature = "application_root_shadow")] + fn application_root_after_success(&mut self) -> crate::worldline_state::ApplicationRootShadow { + let saved = self + .saved_application_root + .take("runtime commit guard missing application root"); + #[cfg(feature = "application_root_profile")] + { + self.engine.runtime_application_trie = None; + if let Some(trie) = self.engine.runtime_application_trie_after.take() { + return saved.advanced_to(trie); + } + } + saved.stale() + } + fn finish_success(&mut self) -> crate::warp_state::WarpStateTouches { // The touch log is the tick's undo record: hand it to the caller. self.engine.runtime_batch_touch_log = false; @@ -709,11 +739,7 @@ impl<'a> RuntimeCommitStateGuard<'a> { // The graph changed: the caller advances the trie from the // commit's write set. #[cfg(feature = "application_root_shadow")] - application_root: Some( - self.saved_application_root - .take("runtime commit guard missing application root") - .stale(), - ), + application_root: Some(self.application_root_after_success()), }); self.engine.current_root = self.saved_root; self.engine.tx_counter = self.saved_tx_counter; @@ -788,10 +814,15 @@ impl<'a> RuntimeCommitStateGuard<'a> { // The graph is restored exactly, so the trie keeps whatever it // described before the commit. #[cfg(feature = "application_root_shadow")] - application_root: Some( + application_root: Some({ + #[cfg(feature = "application_root_profile")] + { + self.engine.runtime_application_trie = None; + self.engine.runtime_application_trie_after = None; + } self.saved_application_root - .take("runtime commit guard missing application root"), - ), + .take("runtime commit guard missing application root") + }), }); self.engine.state = self .saved_engine_state @@ -1006,6 +1037,10 @@ impl Engine { last_execution_footprints: std::sync::Arc::default(), last_aborted_execution_footprints: std::sync::Arc::default(), runtime_batch_touch_log: false, + #[cfg(feature = "application_root_profile")] + runtime_application_trie: None, + #[cfg(feature = "application_root_profile")] + runtime_application_trie_after: None, state_root_scratch: crate::snapshot::StateRootScratch::default(), intent_log: Vec::new(), initial_state, @@ -1209,6 +1244,10 @@ impl Engine { last_execution_footprints: std::sync::Arc::default(), last_aborted_execution_footprints: std::sync::Arc::default(), runtime_batch_touch_log: false, + #[cfg(feature = "application_root_profile")] + runtime_application_trie: None, + #[cfg(feature = "application_root_profile")] + runtime_application_trie_after: None, state_root_scratch: crate::snapshot::StateRootScratch::default(), intent_log: Vec::new(), initial_state, @@ -2126,6 +2165,31 @@ impl Engine { ); let patch_digest = patch.digest(); + // An application-profile worldline (M2) commits its advanced + // application trie, and does not walk the graph. + #[cfg(feature = "application_root_profile")] + let application_state_root = self.runtime_application_trie.take().map(|trie| { + let advanced = trie + .advanced(&self.state, patch.ops(), patch.out_slots()) + .unwrap_or_else(|| crate::application_root::ApplicationTrie::rebuild(&self.state)); + let root = crate::application_root::profile_tagged_state_root(&advanced.root()); + self.runtime_application_trie_after = Some(advanced); + root + }); + #[cfg(all(feature = "delta_validate", feature = "application_root_profile"))] + let legacy_profile = application_state_root.is_none(); + #[cfg(all(feature = "delta_validate", not(feature = "application_root_profile")))] + let legacy_profile = true; + #[cfg(feature = "application_root_profile")] + let state_root = match application_state_root { + Some(root) => root, + None => crate::snapshot::compute_state_root_with( + &self.state, + &self.current_root, + &mut self.state_root_scratch, + ), + }; + #[cfg(not(feature = "application_root_profile"))] let state_root = crate::snapshot::compute_state_root_with( &self.state, &self.current_root, @@ -2133,7 +2197,7 @@ impl Engine { ); #[cfg(feature = "delta_validate")] - { + if legacy_profile { use crate::snapshot_accum::SnapshotAccumulator; let mut accumulator = SnapshotAccumulator::from_warp_state(&state_before); diff --git a/crates/warp-core/src/playback.rs b/crates/warp-core/src/playback.rs index 2dd39621a..c2b07cbd4 100644 --- a/crates/warp-core/src/playback.rs +++ b/crates/warp-core/src/playback.rs @@ -35,7 +35,6 @@ use crate::provenance_store::{ advance_replay_state, replay_worldline_state_at_from_provenance, validate_replay_base, ProvenanceStore, ReplayError, }; -use crate::snapshot::compute_state_root_for_warp_state; use crate::worldline::WorldlineId; use crate::worldline_state::WorldlineState; @@ -448,7 +447,7 @@ impl PlaybackCursor { ); assert_eq!( initial_state.state_root(), - compute_state_root_for_warp_state(initial_state.initial_state(), initial_state.root()), + initial_state.state_root_of(initial_state.initial_state()), "playback cursor initial_state must materialize the canonical U0 replay base" ); Self { diff --git a/crates/warp-core/src/provenance_store.rs b/crates/warp-core/src/provenance_store.rs index 1a5562592..88a336b00 100644 --- a/crates/warp-core/src/provenance_store.rs +++ b/crates/warp-core/src/provenance_store.rs @@ -31,7 +31,9 @@ use crate::ident::{Hash, NodeKey, WarpId}; use crate::materialization::FinalizedChannel; use crate::receipt::TickReceipt; use crate::revelation::CausalPosture; -use crate::snapshot::{compute_state_root_for_warp_state, Snapshot}; +#[cfg(test)] +use crate::snapshot::compute_state_root_for_warp_state; +use crate::snapshot::Snapshot; use crate::tick_patch::{TickCommitStatus, WarpTickPatchV1}; use crate::tx::TxId; use crate::worldline_state::WorldlineState; @@ -983,8 +985,7 @@ pub(crate) fn validate_replay_base( } let expected_initial_boundary = provenance.initial_boundary_hash(worldline_id)?; - let actual_initial_boundary = - compute_state_root_for_warp_state(base_state.initial_state(), base_state.root()); + let actual_initial_boundary = base_state.state_root_of(base_state.initial_state()); if actual_initial_boundary != expected_initial_boundary { return Err(ReplayError::InitialBoundaryHashMismatch { expected: expected_initial_boundary, @@ -1059,8 +1060,7 @@ pub(crate) fn advance_replay_state( .apply_to_worldline_state(replayed) .map_err(|source| ReplayError::Apply { tick, source })?; - let actual_state_root = - compute_state_root_for_warp_state(replayed.warp_state(), replayed.root()); + let actual_state_root = replayed.recomputed_state_root(); if actual_state_root != entry.expected.state_root { return Err(ReplayError::StateRootMismatch { tick, @@ -1188,10 +1188,9 @@ fn validate_checkpoint_for_history( }); } - let actual_initial_boundary = compute_state_root_for_warp_state( - checkpoint.state.initial_state(), - checkpoint.state.root(), - ); + let actual_initial_boundary = checkpoint + .state + .state_root_of(checkpoint.state.initial_state()); if actual_initial_boundary != history.initial_boundary_hash { return Err(HistoryError::CheckpointInitialBoundaryHashMismatch { worldline_id, @@ -2071,8 +2070,7 @@ impl ProvenanceService { worldline_id: WorldlineId, state: &WorldlineState, ) -> Result<(), HistoryError> { - let initial_boundary_hash = - compute_state_root_for_warp_state(state.initial_state(), state.root()); + let initial_boundary_hash = state.state_root_of(state.initial_state()); self.store.register_worldline_with_boundary( worldline_id, state.root().warp_id, diff --git a/crates/warp-core/src/settlement.rs b/crates/warp-core/src/settlement.rs index a7860aea4..18a967fa1 100644 --- a/crates/warp-core/src/settlement.rs +++ b/crates/warp-core/src/settlement.rs @@ -19,7 +19,7 @@ use crate::provenance_store::{ }; use crate::record::{EdgeRecord, NodeRecord}; use crate::revelation::{CausalPosture, RetentionPosture, SourceDisclosurePolicy}; -use crate::snapshot::{compute_commit_hash_v2, compute_state_root_for_warp_state}; +use crate::snapshot::compute_commit_hash_v2; use crate::strand::{ StrandBasisReport, StrandError, StrandId, StrandOverlapRevalidation, StrandRegistry, StrandRevalidationState, @@ -882,10 +882,7 @@ impl SettlementService { continue; } - let actual_state_root = compute_state_root_for_warp_state( - candidate_simulated.warp_state(), - candidate_simulated.root(), - ); + let actual_state_root = candidate_simulated.recomputed_state_root(); if parent_at_anchor && actual_state_root != source_entry.expected.state_root { blocked_reason = Some(ConflictReason::UnsupportedImport); decisions.push(SettlementDecision::ConflictArtifact(conflict_draft( diff --git a/crates/warp-core/src/worldline_state.rs b/crates/warp-core/src/worldline_state.rs index 6746c713e..9b012fc8e 100644 --- a/crates/warp-core/src/worldline_state.rs +++ b/crates/warp-core/src/worldline_state.rs @@ -191,6 +191,10 @@ impl std::fmt::Debug for TestCallCount { pub(crate) struct ApplicationRootShadow { trie: crate::application_root::ApplicationTrie, current: bool, + /// The worldline's commitment profile (M2), carried with the trie so + /// every path that carries the trie carries the profile. + #[cfg(feature = "application_root_profile")] + profile: crate::application_root::CommitmentProfile, } #[cfg(feature = "application_root_shadow")] @@ -216,8 +220,42 @@ impl ApplicationRootShadow { Self { trie: crate::application_root::ApplicationTrie::rebuild(warp_state), current: true, + #[cfg(feature = "application_root_profile")] + profile: crate::application_root::CommitmentProfile::default(), } } + + /// A rebuild of `warp_state` that keeps this shadow's profile. + #[cfg(feature = "application_root_profile")] + fn rebuilt(&self, warp_state: &WarpState) -> Self { + Self { + profile: self.profile, + ..Self::of(warp_state) + } + } + + /// A rebuild of `warp_state`. + #[cfg(not(feature = "application_root_profile"))] + #[allow(clippy::unused_self)] + fn rebuilt(&self, warp_state: &WarpState) -> Self { + Self::of(warp_state) + } + + /// An advanced trie, current, keeping this shadow's profile. + #[cfg_attr(not(feature = "application_root_profile"), allow(clippy::unused_self))] + pub(crate) fn advanced_to(&self, trie: crate::application_root::ApplicationTrie) -> Self { + Self { + trie, + current: true, + #[cfg(feature = "application_root_profile")] + profile: self.profile, + } + } + + #[cfg(feature = "application_root_profile")] + pub(crate) fn current_trie(&self) -> Option<&crate::application_root::ApplicationTrie> { + self.current.then_some(&self.trie) + } } impl Default for WorldlineState { @@ -382,6 +420,12 @@ impl WorldlineState { /// retained root. #[must_use] pub fn state_root(&self) -> Hash { + #[cfg(feature = "application_root_profile")] + if self.application_root.profile + == crate::application_root::CommitmentProfile::ApplicationRadixV0 + { + return crate::application_root::profile_tagged_state_root(&self.application_root()); + } if let Some((root, state_root)) = self.retained_state_root { if root == self.root { debug_assert_eq!( @@ -426,6 +470,84 @@ impl WorldlineState { std::mem::take(&mut self.application_root) } + /// Returns this state with commitment profile `profile` (M2, + /// experimental). Choose it before registering the worldline: it decides + /// what every committed `state_root` of the worldline means. + #[cfg(feature = "application_root_profile")] + #[must_use] + pub fn with_commitment_profile( + mut self, + profile: crate::application_root::CommitmentProfile, + ) -> Self { + self.application_root.profile = profile; + self + } + + /// The worldline's commitment profile (M2, experimental). + #[cfg(feature = "application_root_profile")] + #[must_use] + pub fn commitment_profile(&self) -> crate::application_root::CommitmentProfile { + self.application_root.profile + } + + /// The application trie, when the worldline commits it as its state root + /// and it is current, for the runtime commit guard. + #[cfg(feature = "application_root_profile")] + pub(crate) fn committing_application_trie( + &self, + ) -> Option { + (self.application_root.profile + == crate::application_root::CommitmentProfile::ApplicationRadixV0) + .then(|| { + self.application_root + .current_trie() + .cloned() + .unwrap_or_else(|| { + crate::application_root::ApplicationTrie::rebuild(&self.warp_state) + }) + }) + } + + /// The committed-root recipe of this worldline applied to `warp_state`, + /// for a graph that is not this state's own (an initial boundary or an + /// import candidate). + #[must_use] + pub(crate) fn state_root_of(&self, warp_state: &WarpState) -> Hash { + #[cfg(feature = "application_root_profile")] + if self.application_root.profile + == crate::application_root::CommitmentProfile::ApplicationRadixV0 + { + return crate::application_root::profile_tagged_state_root( + &crate::application_root::ApplicationTrie::rebuild(warp_state).root(), + ); + } + crate::snapshot::compute_state_root_for_warp_state(warp_state, &self.root) + } + + /// This state's committed root, recomputed from its graph without any + /// retained value: the legacy reachable root, or for an application + /// profile the tagged application root. + #[must_use] + pub(crate) fn recomputed_state_root(&self) -> Hash { + self.state_root_of_own_graph() + } + + #[cfg(not(feature = "application_root_profile"))] + fn state_root_of_own_graph(&self) -> Hash { + crate::snapshot::compute_state_root_for_warp_state(&self.warp_state, &self.root) + } + + #[cfg(feature = "application_root_profile")] + fn state_root_of_own_graph(&self) -> Hash { + if self.application_root.profile + == crate::application_root::CommitmentProfile::ApplicationRadixV0 + { + crate::application_root::profile_tagged_state_root(&self.application_root()) + } else { + crate::snapshot::compute_state_root_for_warp_state(&self.warp_state, &self.root) + } + } + /// Whether the application trie is known to describe the current graph. #[cfg(feature = "application_root_shadow")] pub(crate) fn application_root_is_current(&self) -> bool { @@ -453,11 +575,8 @@ impl WorldlineState { None }; self.application_root = advanced.map_or_else( - || ApplicationRootShadow::of(&self.warp_state), - |trie| ApplicationRootShadow { - trie, - current: true, - }, + || self.application_root.rebuilt(&self.warp_state), + |trie| self.application_root.advanced_to(trie), ); #[cfg(feature = "delta_validate")] assert_eq!( @@ -713,7 +832,7 @@ impl WorldlineState { pub(crate) fn replay_base_from_initial(&self) -> Self { let initial_state = Arc::clone(&self.initial_state); #[cfg(feature = "application_root_shadow")] - let application_root = ApplicationRootShadow::of(&initial_state); + let application_root = self.application_root.rebuilt(&initial_state); Self { warp_state: (*initial_state).clone(), root: self.root, diff --git a/crates/warp-core/tests/application_root_profile_tests.rs b/crates/warp-core/tests/application_root_profile_tests.rs new file mode 100644 index 000000000..643addcfc --- /dev/null +++ b/crates/warp-core/tests/application_root_profile_tests.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +#![allow(clippy::expect_used, clippy::panic)] +//! The experimental application-root commitment profile (M2) through the F5 +//! scene fixture: the same workload validates under both profiles, the +//! application profile commits a different, tagged root, and replay and +//! readings at earlier coordinates verify under it. + +#[cfg(all( + feature = "application_root_profile", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] +#[path = "../benches/support/action_scene.rs"] +#[allow(dead_code)] +mod action_scene; +#[cfg(all( + feature = "application_root_profile", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] +#[path = "../benches/support/fixtures/mod.rs"] +#[allow(dead_code)] +mod fixtures; + +#[cfg(all( + feature = "application_root_profile", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] +use fixtures::{manifest::hex32, think_append}; +#[cfg(all( + feature = "application_root_profile", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] +use warp_core::application_root::CommitmentProfile; + +#[cfg(all( + feature = "application_root_profile", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] +#[test] +fn f5_validates_under_the_application_profile() { + let tiers: &[think_append::Tier] = if cfg!(debug_assertions) { + &[think_append::S] + } else { + &[think_append::S, think_append::M] + }; + for &tier in tiers { + let script = think_append::script(tier, 7); + let digest = hex32(&think_append::script_digest(&script)); + let expect = think_append::expectation(tier, Some(&digest)); + let legacy = think_append::run(tier, 7); + let application = + think_append::run_with_profile(tier, 7, CommitmentProfile::ApplicationRadixV0); + for (label, obs) in [("legacy", &legacy), ("application", &application)] { + let class = think_append::validate(tier, &script, &expect, obs); + assert!(class.comparable(), "tier {} {label}: {class:?}", tier.name); + assert_eq!( + obs.replay_root, + Some(obs.live_root), + "tier {} {label}", + tier.name + ); + } + assert_ne!( + legacy.live_root, application.live_root, + "tier {}: the profiles commit different roots for the same graph", + tier.name + ); + assert_eq!(application.application_root, Some((true, true))); + } +} diff --git a/crates/warp-core/tests/application_root_shadow_tests.rs b/crates/warp-core/tests/application_root_shadow_tests.rs index 37a4962c2..9dbb31680 100644 --- a/crates/warp-core/tests/application_root_shadow_tests.rs +++ b/crates/warp-core/tests/application_root_shadow_tests.rs @@ -1,24 +1,39 @@ // SPDX-License-Identifier: Apache-2.0 // © James Ross Ω FLYING•ROBOTS -#![cfg(all( - feature = "application_root_shadow", - feature = "native_rule_bootstrap", - feature = "trusted_runtime" -))] #![allow(clippy::expect_used, clippy::panic)] //! The experimental incremental application root (M1) through the F5 scene //! fixture: the live root equals its full rebuild after every append, replay //! reproduces it, and the legacy state root is unaffected. +#[cfg(all( + feature = "application_root_shadow", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] #[path = "../benches/support/action_scene.rs"] #[allow(dead_code)] mod action_scene; +#[cfg(all( + feature = "application_root_shadow", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] #[path = "../benches/support/fixtures/mod.rs"] #[allow(dead_code)] mod fixtures; +#[cfg(all( + feature = "application_root_shadow", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] use fixtures::think_append; +#[cfg(all( + feature = "application_root_shadow", + feature = "native_rule_bootstrap", + feature = "trusted_runtime" +))] #[test] fn f5_application_root_is_incremental_exact_and_replayable() { // Tier M replays 5,000 appends several times; it is for optimised diff --git a/crates/warp-core/tests/executable_operation_pipeline_tests.rs b/crates/warp-core/tests/executable_operation_pipeline_tests.rs index a7523d3da..b9fa09f55 100644 --- a/crates/warp-core/tests/executable_operation_pipeline_tests.rs +++ b/crates/warp-core/tests/executable_operation_pipeline_tests.rs @@ -117,6 +117,13 @@ fn fixture_host() -> (TrustedRuntimeHost, WriterHeadKey, NodeKey) { fn fixture_host_with_independent_nodes( node_count: usize, +) -> (TrustedRuntimeHost, WriterHeadKey, Vec) { + fixture_host_with_independent_nodes_and_state(node_count, |state| state) +} + +fn fixture_host_with_independent_nodes_and_state( + node_count: usize, + configure: impl FnOnce(WorldlineState) -> WorldlineState, ) -> (TrustedRuntimeHost, WriterHeadKey, Vec) { assert!(node_count > 0, "the fixture must retain one root node"); let warp_id = make_warp_id("operation-fixture"); @@ -144,8 +151,10 @@ fn fixture_host_with_independent_nodes( ))), ); } - let state = WorldlineState::from_root_store(store, root_id) - .expect("the fixture state has one lawful root"); + let state = configure( + WorldlineState::from_root_store(store, root_id) + .expect("the fixture state has one lawful root"), + ); let worldline_id = WorldlineId::from_bytes(digest("operation-fixture-worldline")); let head_key = WriterHeadKey { worldline_id, @@ -4934,3 +4943,71 @@ fn an_action_with_a_forged_origin_keeps_the_whole_frontier_rule() { "an origin that history does not hold cannot earn scoped validity" ); } + +/// M2: on an application-profile worldline, Actions commit the tagged +/// application root, the stale-basis exception still corroborates origins +/// (including the genesis boundary), and recovery agrees. +#[cfg(feature = "application_root_profile")] +#[test] +fn application_profile_actions_commit_and_recover() { + use warp_core::application_root::{profile_tagged_state_root, CommitmentProfile}; + + const ACTIONS: usize = 70; + let profiled = |nodes: usize| { + fixture_host_with_independent_nodes_and_state(nodes, |state| { + state.with_commitment_profile(CommitmentProfile::ApplicationRadixV0) + }) + }; + let wal_dir = TempWalDir::new(); + let submissions; + let committed_root; + { + let (mut host, head_key, nodes) = profiled(ACTIONS); + host.enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(wal_dir.path())) + .expect("the WAL opens"); + let installed = install_fixture_operation(&mut host); + host.install_echo_operation_action_admission_policy_v1(invocation_policy()); + // Every Action is built at the genesis boundary; those decided after + // the first tick need the scoped exception to commit. + let invocations = nodes + .iter() + .map(|node| action_invocation(&host, &installed, head_key, *node, b"before", b"after")) + .collect::>(); + submissions = invocations + .into_iter() + .map(|invocation| submit_action(&mut host, head_key, invocation)) + .collect::>(); + for _ in 0..ACTIONS.div_ceil(64) { + host.tick_once().expect("the scheduler decides a batch"); + } + assert!(submissions.iter().all(|id| outcome_is_committed(&host, id))); + let state = host + .runtime() + .worldlines() + .get(&head_key.worldline_id) + .expect("the fixture worldline exists") + .state(); + committed_root = state.state_root(); + assert_eq!( + committed_root, + profile_tagged_state_root(&state.application_root_rebuilt()) + ); + } + let (mut recovered, head_key, _) = profiled(ACTIONS); + recovered + .enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(wal_dir.path())) + .expect("recovery recomputes validity under the profile and agrees"); + assert!(submissions + .iter() + .all(|id| outcome_is_committed(&recovered, id))); + assert_eq!( + recovered + .runtime() + .worldlines() + .get(&head_key.worldline_id) + .expect("the fixture worldline exists") + .state() + .state_root(), + committed_root + ); +} diff --git a/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs b/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs index 208a407c9..d2734b373 100644 --- a/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs +++ b/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs @@ -4424,3 +4424,89 @@ fn a_legacy_resolved_resend_records_the_retained_envelope() { drop(restarted); fs::remove_dir_all(&fresh_root).expect("WAL cleanup"); } + +/// M2: an application-profile worldline recovers from the filesystem WAL when +/// the host registers it under the same profile, and refuses to recover under +/// the legacy profile. +#[cfg(feature = "application_root_profile")] +#[test] +fn application_profile_history_recovers_only_under_its_profile() { + use warp_core::application_root::CommitmentProfile; + + let profiled_runtime = |profile: CommitmentProfile| { + let mut runtime = WorldlineRuntime::new(); + let worldline_id = WorldlineId::from_bytes([1; 32]); + runtime + .register_worldline( + worldline_id, + WorldlineState::empty().with_commitment_profile(profile), + ) + .expect("worldline should register"); + runtime + .register_writer_head(WriterHead::with_routing( + WriterHeadKey { + worldline_id, + head_id: make_head_id("default"), + }, + PlaybackMode::Play, + InboxPolicy::AcceptAll, + None, + true, + )) + .expect("writer head should register"); + (runtime, worldline_id) + }; + let wal_root = temp_runtime_wal_dir("application-profile-recovery"); + let (initial_runtime, worldline_id) = profiled_runtime(CommitmentProfile::ApplicationRadixV0); + let mut host = TrustedRuntimeHost::new(initial_runtime, empty_engine()) + .expect("trusted host should initialize"); + host.enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(&wal_root)) + .expect("host should configure filesystem runtime WAL adapter"); + host.register_contract_package(package()) + .expect("host should install package"); + let submission = host + .app() + .submit_intent_with_runtime_wal_ack(eint_envelope(worldline_id)) + .expect("submission should cross the durable ACK boundary"); + host.stage_installed_contract_submission(submission.submission_id, &admission_ticket(62)) + .expect("trusted host should stage the submission"); + host.run_until_idle(4).expect("the mutation commits"); + let committed_root = host + .runtime() + .worldlines() + .get(&worldline_id) + .expect("worldline exists") + .state() + .state_root(); + drop(host); + + let (recovered_runtime, _) = profiled_runtime(CommitmentProfile::ApplicationRadixV0); + let mut recovered = TrustedRuntimeHost::new(recovered_runtime, empty_engine()) + .expect("reconstructed host should initialize"); + recovered + .enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(&wal_root)) + .expect("the history recovers under its own profile"); + assert_eq!( + recovered + .runtime() + .worldlines() + .get(&worldline_id) + .expect("worldline exists") + .state() + .state_root(), + committed_root + ); + drop(recovered); + + let (legacy_runtime, _) = profiled_runtime(CommitmentProfile::LegacyReachableV1); + let mut legacy = TrustedRuntimeHost::new(legacy_runtime, empty_engine()) + .expect("reconstructed host should initialize"); + assert!( + legacy + .enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(&wal_root)) + .is_err(), + "an application-profile history does not recover under the legacy profile" + ); + drop(legacy); + fs::remove_dir_all(&wal_root).expect("WAL fixture cleanup succeeds"); +} diff --git a/docs/studies/m2-commitment-profile.md b/docs/studies/m2-commitment-profile.md new file mode 100644 index 000000000..590147806 --- /dev/null +++ b/docs/studies/m2-commitment-profile.md @@ -0,0 +1,104 @@ + + + +# Study: M2, slice 1 — an opt-in application-root commitment profile + +**Status:** experimental, off by default (`application_root_profile`, which +implies M1's `application_root_shadow`). Worldlines default to the legacy +profile, whose roots, commit hashes and receipts are unchanged. This is a +first slice of M2, not all of it; see *What M2 still needs*. Owner adoption +of the profile, its domain strings and any durable descriptor is not +established. +**Author:** Claude (Anthropic), for James Ross. +**Source:** milestone M2 and §§8.5, 9 and 15.1 of *Echo: Causal Commitments +and Authenticated WARP Readings*, revision 2. +**Builds on:** M1 (#922), which builds on #918 and #919. + +## Result + +On F5 tier M (5,000 appends into a growing graph), the same workload under +the application profile: + +| Tier | Profile | Append p50 | Per append | Replay p50 | +| --- | --- | --- | --- | --- | +| S | legacy-reachable-v1 | 16.56 ms | 0.0906 ms | 10.34 ms | +| S | application-radix-v0 | 9.34 ms | 0.0441 ms | 2.71 ms | +| M | legacy-reachable-v1 | 5,630.16 ms | 1.0993 ms | 5,551.82 ms | +| M | application-radix-v0 | 272.04 ms | 0.0542 ms | 99.17 ms | + +Appending is 20.7 times faster and replay 56 times faster in tier M; both +rounds validate under the fixture's own outcome checks. Same host, same +branch, same build, five rounds each +([output](m2-commitment-profile/bench.txt)). + +## What the slice does + +- **An explicit profile per worldline.** `CommitmentProfile` is + `LegacyReachableV1` (default) or `ApplicationRadixV0`, chosen with + `WorldlineState::with_commitment_profile` before registration. It travels + with the M1 trie, so every path that carries the trie carries the profile: + rollback markers, replay checkpoints and bases, forks, and the runtime + commit guard. Nothing infers a profile from a root's bytes (revision 2, + §15.1). +- **A tagged root.** An application-profile worldline's `state_root` is + `H("echo.state-root.application-radix.v0" || application_root)`. The tag + keeps it domain-separated from every legacy root, so no historical + `state_root` field is ever reinterpreted. Commit hash recipes v2 and v3 are + unchanged; they bind whichever root the profile commits. +- **No graph walk.** The engine's runtime commit advances the worldline's + trie from the commit's complete write set and commits its root, instead of + `compute_state_root_with`. The executable-operation (Action) path already + reads `WorldlineState::state_root`, which is profile-aware. +- **Verification under the profile.** Replay, readings at earlier + coordinates, import-candidate checks, playback cursors, provenance initial + boundaries and checkpoint restores compute the worldline's own recipe + (`recomputed_state_root`, `state_root_of`) where they used the legacy + function. With the feature off, each of these is the legacy function. + +## Evidence + +| Test | Shows | +| --- | --- | +| `coordinator::…::application_profile_commits_the_tagged_application_root_and_verifies_only_under_it` | Four contract commits: each committed root is the tagged application root and never the legacy root; the initial boundary is committed under the profile; replay verifies under the profile and fails under the legacy one; a fork inherits the profile and the root | +| `application_root_profile_tests::f5_validates_under_the_application_profile` | F5 validates under both profiles with the fixture's own outcome, replay and earlier-coordinate checks; the two profiles commit different roots for the same graph | +| `trusted_runtime_host_loop_tests::application_profile_history_recovers_only_under_its_profile` | A filesystem-WAL history recovers when the host registers the same profile and refuses to recover under the legacy profile | +| `executable_operation_pipeline_tests::application_profile_actions_commit_and_recover` | 70 Actions built at the genesis boundary commit across two ticks (the scoped stale-basis exception corroborates the genesis origin under the profile), and filesystem-WAL recovery agrees | +| Full suite with `application_root_profile,delta_validate` | Every legacy test still passes with the feature compiled in; see Results | + +## What M2 still needs + +These are gates the revision assigns to M2 that this slice does not meet, +and decisions that are not mine to make: + +1. **A durable profile descriptor.** The profile is part of the host's + worldline registration, like the initial state; it is not written to the + WAL, provenance codecs or WSC. A mismatch is detected (replay and recovery + fail), but the failure is a root mismatch, not a typed "unknown or + mismatched profile" error. A descriptor needs a codec version and owner + approval of the domain strings. +2. **The boundary seal.** Revision 2 §8.5 specifies a seal binding the + application and history roots, receipt core, parents, coordinates and + profiles, with a cross-reading validation rule. This slice binds only the + application reading, through the existing v2/v3 commit hashes. The + history reading is M3/M4 work. +3. **Ports and warp-instance headers** remain outside the application + reading. +4. **Migration.** There is no path from a legacy history to the new profile; + §15.1's migration-boundary rules apply when one is wanted. +5. **Exchange.** Import of application-profile histories across hosts uses + the same checks, but no cross-host exchange test exists. + +## Results + +| Check | Result | +| --- | --- | +| Full `warp-core` suite with `native_rule_bootstrap,trusted_runtime,host_test,application_root_profile,delta_validate` | 2,019 passed; the only failure is the long-`TMPDIR` socket test that also fails on `main` ([results](m2-commitment-profile/full-suite-delta-validate.txt)). Legacy worldlines are the default, so this is the legacy suite with the feature compiled in, plus the new tests. | +| `executable_operation_pipeline_tests`, `application_root_profile_tests`, `trusted_runtime_host_loop_tests` with `delta_validate` (after adding the Action test) | 49, 1 and 70 passed ([results](m2-commitment-profile/targeted-tests.txt)) | +| `application_root_profile_tests` in release (tiers S and M) | Passes | +| Clippy `-D warnings`: profile feature (`--lib --tests --benches`), legacy `--all-targets` with `delta_validate`, default library | Pass | +| `cargo fmt --all -- --check` | Pass | + +Finding along the way: test files that put `#![cfg(feature = …)]` on the +whole crate lose their crate docs when the feature is off, and CI's +`--all-targets` clippy (`-D missing_docs`) rejects them. Both new test files +gate their items instead; the M1 branch received the same fix. diff --git a/docs/studies/m2-commitment-profile/bench.txt b/docs/studies/m2-commitment-profile/bench.txt new file mode 100644 index 000000000..976c1b7a5 --- /dev/null +++ b/docs/studies/m2-commitment-profile/bench.txt @@ -0,0 +1,28 @@ +F5 tier=S profile=legacy-reachable-v1 seed=7 workload_id=2d073ea67df118cbddab23932a0bc2a57af9781b6a789e666ea99d9e231e4f47 expectation_id=05c0c4d06bf64926770f845c84182f5fcda153e7c54948cdc6f9cb336242b715 script_digest=efc633360e2e237b9ed238e1cb0f57b8c12e64c779a52bba6e223c9bdfd33d4d +round 1 class=valid append_ms=24.82 replay_ms=12.91 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 2 class=valid append_ms=17.17 replay_ms=10.38 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 3 class=valid append_ms=16.35 replay_ms=10.34 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 4 class=valid append_ms=16.56 replay_ms=10.27 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 5 class=valid append_ms=16.34 replay_ms=10.30 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +F5 tier=S profile=legacy-reachable-v1 summary valid_rounds=5 append_p50_ms=16.56 per_append_p50_ms=0.0906 useful_throughput_appends_per_s=12076 replay_p50_ms=10.34 +F5 tier=M profile=legacy-reachable-v1 seed=7 workload_id=26ef9606d45b13960809fe90a2e4dced659aea3e80b5d20fb682ee3873026061 expectation_id=40bbecfbdcf065dc62c21c5704750e10cdc5a06028bbf95275679d1a33618273 script_digest=ac18dd3b37286646ff3a475b288d51eeb52323406a3d88fbc5ed4595d76e2017 +round 1 class=valid append_ms=5637.62 replay_ms=5566.09 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 2 class=valid append_ms=5632.78 replay_ms=5551.82 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 3 class=valid append_ms=5619.24 replay_ms=5549.62 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 4 class=valid append_ms=5629.88 replay_ms=5540.67 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 5 class=valid append_ms=5630.16 replay_ms=5553.57 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +F5 tier=M profile=legacy-reachable-v1 summary valid_rounds=5 append_p50_ms=5630.16 per_append_p50_ms=1.0993 useful_throughput_appends_per_s=888 replay_p50_ms=5551.82 +F5 tier=S profile=application-radix-v0 seed=7 workload_id=2d073ea67df118cbddab23932a0bc2a57af9781b6a789e666ea99d9e231e4f47 expectation_id=05c0c4d06bf64926770f845c84182f5fcda153e7c54948cdc6f9cb336242b715 script_digest=efc633360e2e237b9ed238e1cb0f57b8c12e64c779a52bba6e223c9bdfd33d4d +round 1 class=valid append_ms=9.50 replay_ms=2.71 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 2 class=valid append_ms=8.90 replay_ms=2.68 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 3 class=valid append_ms=9.35 replay_ms=2.72 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 4 class=valid append_ms=8.73 replay_ms=2.66 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +round 5 class=valid append_ms=9.34 replay_ms=2.71 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 +F5 tier=S profile=application-radix-v0 summary valid_rounds=5 append_p50_ms=9.34 per_append_p50_ms=0.0441 useful_throughput_appends_per_s=21424 replay_p50_ms=2.71 +F5 tier=M profile=application-radix-v0 seed=7 workload_id=26ef9606d45b13960809fe90a2e4dced659aea3e80b5d20fb682ee3873026061 expectation_id=40bbecfbdcf065dc62c21c5704750e10cdc5a06028bbf95275679d1a33618273 script_digest=ac18dd3b37286646ff3a475b288d51eeb52323406a3d88fbc5ed4595d76e2017 +round 1 class=valid append_ms=272.04 replay_ms=100.84 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 2 class=valid append_ms=272.69 replay_ms=99.22 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 3 class=valid append_ms=268.49 replay_ms=99.04 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 4 class=valid append_ms=272.65 replay_ms=98.90 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +round 5 class=valid append_ms=271.63 replay_ms=99.17 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 +F5 tier=M profile=application-radix-v0 summary valid_rounds=5 append_p50_ms=272.04 per_append_p50_ms=0.0542 useful_throughput_appends_per_s=18380 replay_p50_ms=99.17 diff --git a/docs/studies/m2-commitment-profile/full-suite-delta-validate.txt b/docs/studies/m2-commitment-profile/full-suite-delta-validate.txt new file mode 100644 index 000000000..960698090 --- /dev/null +++ b/docs/studies/m2-commitment-profile/full-suite-delta-validate.txt @@ -0,0 +1,109 @@ +test result: ok. 899 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.75s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.48s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.11s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: FAILED. 26 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s +test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.86s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: test filesystem_writer_lease_refuses_overlap_before_takeover ... okok +test result: +test result: ok. 134 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.40s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s +test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s +test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s +test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 48 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.22s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.04s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.02s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.02s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.00s +test result: ok. 31 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.14s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s +test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 22 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s +test result: ok. 4 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.42s +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.42s +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s +test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.06s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s +test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s +test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.07s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 42.36s +test result: ok. 31 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.80s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s +test result: ok. 28 passed; 0 failed; 6 ignored; 0 measured; 0 filtered out; finished in 0.68s +test special_files_are_rejected_before_a_read_attempt ... FAILED +---- special_files_are_rejected_before_a_read_attempt stdout ---- +thread 'special_files_are_rejected_before_a_read_attempt' (12129778) panicked at crates/warp-core/tests/bounded_workspace_observation_tests.rs:48:23: +expected Ok(..), got Error { kind: InvalidInput, message: "path must be shorter than SUN_LEN" } + special_files_are_rejected_before_a_read_attempt diff --git a/docs/studies/m2-commitment-profile/targeted-tests.txt b/docs/studies/m2-commitment-profile/targeted-tests.txt new file mode 100644 index 000000000..1ddc2926e --- /dev/null +++ b/docs/studies/m2-commitment-profile/targeted-tests.txt @@ -0,0 +1,8 @@ +m2-rel.log:test f5_validates_under_the_application_profile ... ok +m2-rel.log:test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 33.53s +m2-action.log:test f5_validates_under_the_application_profile ... ok +m2-action.log:test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.23s +m2-action.log:test application_profile_actions_commit_and_recover ... ok +m2-action.log:test result: ok. 49 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.36s +m2-action.log:test application_profile_history_recovers_only_under_its_profile ... ok +m2-action.log:test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.77s From b27b6b8b5cf528e37d9bc43734b934205a9f0534 Mon Sep 17 00:00:00 2001 From: James Ross Date: Tue, 20 Oct 2026 01:24:37 -0700 Subject: [PATCH 2/3] Move the commitment-profile contract into its canonical docs Document the experimental application_root_profile and its relation to the legacy LegacyReachableV1 recipe in docs/topics/ApplicationRoot.md, and note in docs/spec/merkle-commit.md that Decision 1 is the default profile. Remove the checked-in docs/studies M2 narrative and run reports; AGENTS.md keeps change-local results in the pull request. Co-Authored-By: Claude Opus 5.5 --- docs/spec/merkle-commit.md | 2 + docs/studies/m2-commitment-profile.md | 104 ----------------- docs/studies/m2-commitment-profile/bench.txt | 28 ----- .../full-suite-delta-validate.txt | 109 ------------------ .../m2-commitment-profile/targeted-tests.txt | 8 -- docs/topics/ApplicationRoot.md | 54 ++++++++- 6 files changed, 53 insertions(+), 252 deletions(-) delete mode 100644 docs/studies/m2-commitment-profile.md delete mode 100644 docs/studies/m2-commitment-profile/bench.txt delete mode 100644 docs/studies/m2-commitment-profile/full-suite-delta-validate.txt delete mode 100644 docs/studies/m2-commitment-profile/targeted-tests.txt diff --git a/docs/spec/merkle-commit.md b/docs/spec/merkle-commit.md index 2f2dda617..b3003eaa1 100644 --- a/docs/spec/merkle-commit.md +++ b/docs/spec/merkle-commit.md @@ -29,6 +29,8 @@ The state root is BLAKE3 over canonical encoding of reachable WARP state from a Both canonical snapshots and incremental snapshot accumulation prepend the existing `echo:state_root:v1\0` domain separator before the root binding. Incremental accumulation must produce the same state root as canonical snapshot hashing for the same reachable state. Previously emitted accumulator roots that omitted this prefix do not satisfy that law; this correction does not migrate retained artifacts. +This is the `LegacyReachableV1` commitment profile, which every worldline uses by default. The experimental, off-by-default `application_root_profile` feature lets a worldline be registered with `ApplicationRadixV0`, whose `state_root` is the domain-tagged application root instead; see [Experimental commitment profile](../topics/ApplicationRoot.md#experimental-commitment-profile). Commit hash recipes are the same under either profile. + ## Decision 2: `commit_id` v2 commits to replay, not narration Commit header v2 commits to version, parents, state root, patch digest, and policy id. `patch_digest` is the digest of the replayable tick patch. diff --git a/docs/studies/m2-commitment-profile.md b/docs/studies/m2-commitment-profile.md deleted file mode 100644 index 590147806..000000000 --- a/docs/studies/m2-commitment-profile.md +++ /dev/null @@ -1,104 +0,0 @@ - - - -# Study: M2, slice 1 — an opt-in application-root commitment profile - -**Status:** experimental, off by default (`application_root_profile`, which -implies M1's `application_root_shadow`). Worldlines default to the legacy -profile, whose roots, commit hashes and receipts are unchanged. This is a -first slice of M2, not all of it; see *What M2 still needs*. Owner adoption -of the profile, its domain strings and any durable descriptor is not -established. -**Author:** Claude (Anthropic), for James Ross. -**Source:** milestone M2 and §§8.5, 9 and 15.1 of *Echo: Causal Commitments -and Authenticated WARP Readings*, revision 2. -**Builds on:** M1 (#922), which builds on #918 and #919. - -## Result - -On F5 tier M (5,000 appends into a growing graph), the same workload under -the application profile: - -| Tier | Profile | Append p50 | Per append | Replay p50 | -| --- | --- | --- | --- | --- | -| S | legacy-reachable-v1 | 16.56 ms | 0.0906 ms | 10.34 ms | -| S | application-radix-v0 | 9.34 ms | 0.0441 ms | 2.71 ms | -| M | legacy-reachable-v1 | 5,630.16 ms | 1.0993 ms | 5,551.82 ms | -| M | application-radix-v0 | 272.04 ms | 0.0542 ms | 99.17 ms | - -Appending is 20.7 times faster and replay 56 times faster in tier M; both -rounds validate under the fixture's own outcome checks. Same host, same -branch, same build, five rounds each -([output](m2-commitment-profile/bench.txt)). - -## What the slice does - -- **An explicit profile per worldline.** `CommitmentProfile` is - `LegacyReachableV1` (default) or `ApplicationRadixV0`, chosen with - `WorldlineState::with_commitment_profile` before registration. It travels - with the M1 trie, so every path that carries the trie carries the profile: - rollback markers, replay checkpoints and bases, forks, and the runtime - commit guard. Nothing infers a profile from a root's bytes (revision 2, - §15.1). -- **A tagged root.** An application-profile worldline's `state_root` is - `H("echo.state-root.application-radix.v0" || application_root)`. The tag - keeps it domain-separated from every legacy root, so no historical - `state_root` field is ever reinterpreted. Commit hash recipes v2 and v3 are - unchanged; they bind whichever root the profile commits. -- **No graph walk.** The engine's runtime commit advances the worldline's - trie from the commit's complete write set and commits its root, instead of - `compute_state_root_with`. The executable-operation (Action) path already - reads `WorldlineState::state_root`, which is profile-aware. -- **Verification under the profile.** Replay, readings at earlier - coordinates, import-candidate checks, playback cursors, provenance initial - boundaries and checkpoint restores compute the worldline's own recipe - (`recomputed_state_root`, `state_root_of`) where they used the legacy - function. With the feature off, each of these is the legacy function. - -## Evidence - -| Test | Shows | -| --- | --- | -| `coordinator::…::application_profile_commits_the_tagged_application_root_and_verifies_only_under_it` | Four contract commits: each committed root is the tagged application root and never the legacy root; the initial boundary is committed under the profile; replay verifies under the profile and fails under the legacy one; a fork inherits the profile and the root | -| `application_root_profile_tests::f5_validates_under_the_application_profile` | F5 validates under both profiles with the fixture's own outcome, replay and earlier-coordinate checks; the two profiles commit different roots for the same graph | -| `trusted_runtime_host_loop_tests::application_profile_history_recovers_only_under_its_profile` | A filesystem-WAL history recovers when the host registers the same profile and refuses to recover under the legacy profile | -| `executable_operation_pipeline_tests::application_profile_actions_commit_and_recover` | 70 Actions built at the genesis boundary commit across two ticks (the scoped stale-basis exception corroborates the genesis origin under the profile), and filesystem-WAL recovery agrees | -| Full suite with `application_root_profile,delta_validate` | Every legacy test still passes with the feature compiled in; see Results | - -## What M2 still needs - -These are gates the revision assigns to M2 that this slice does not meet, -and decisions that are not mine to make: - -1. **A durable profile descriptor.** The profile is part of the host's - worldline registration, like the initial state; it is not written to the - WAL, provenance codecs or WSC. A mismatch is detected (replay and recovery - fail), but the failure is a root mismatch, not a typed "unknown or - mismatched profile" error. A descriptor needs a codec version and owner - approval of the domain strings. -2. **The boundary seal.** Revision 2 §8.5 specifies a seal binding the - application and history roots, receipt core, parents, coordinates and - profiles, with a cross-reading validation rule. This slice binds only the - application reading, through the existing v2/v3 commit hashes. The - history reading is M3/M4 work. -3. **Ports and warp-instance headers** remain outside the application - reading. -4. **Migration.** There is no path from a legacy history to the new profile; - §15.1's migration-boundary rules apply when one is wanted. -5. **Exchange.** Import of application-profile histories across hosts uses - the same checks, but no cross-host exchange test exists. - -## Results - -| Check | Result | -| --- | --- | -| Full `warp-core` suite with `native_rule_bootstrap,trusted_runtime,host_test,application_root_profile,delta_validate` | 2,019 passed; the only failure is the long-`TMPDIR` socket test that also fails on `main` ([results](m2-commitment-profile/full-suite-delta-validate.txt)). Legacy worldlines are the default, so this is the legacy suite with the feature compiled in, plus the new tests. | -| `executable_operation_pipeline_tests`, `application_root_profile_tests`, `trusted_runtime_host_loop_tests` with `delta_validate` (after adding the Action test) | 49, 1 and 70 passed ([results](m2-commitment-profile/targeted-tests.txt)) | -| `application_root_profile_tests` in release (tiers S and M) | Passes | -| Clippy `-D warnings`: profile feature (`--lib --tests --benches`), legacy `--all-targets` with `delta_validate`, default library | Pass | -| `cargo fmt --all -- --check` | Pass | - -Finding along the way: test files that put `#![cfg(feature = …)]` on the -whole crate lose their crate docs when the feature is off, and CI's -`--all-targets` clippy (`-D missing_docs`) rejects them. Both new test files -gate their items instead; the M1 branch received the same fix. diff --git a/docs/studies/m2-commitment-profile/bench.txt b/docs/studies/m2-commitment-profile/bench.txt deleted file mode 100644 index 976c1b7a5..000000000 --- a/docs/studies/m2-commitment-profile/bench.txt +++ /dev/null @@ -1,28 +0,0 @@ -F5 tier=S profile=legacy-reachable-v1 seed=7 workload_id=2d073ea67df118cbddab23932a0bc2a57af9781b6a789e666ea99d9e231e4f47 expectation_id=05c0c4d06bf64926770f845c84182f5fcda153e7c54948cdc6f9cb336242b715 script_digest=efc633360e2e237b9ed238e1cb0f57b8c12e64c779a52bba6e223c9bdfd33d4d -round 1 class=valid append_ms=24.82 replay_ms=12.91 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 2 class=valid append_ms=17.17 replay_ms=10.38 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 3 class=valid append_ms=16.35 replay_ms=10.34 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 4 class=valid append_ms=16.56 replay_ms=10.27 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 5 class=valid append_ms=16.34 replay_ms=10.30 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -F5 tier=S profile=legacy-reachable-v1 summary valid_rounds=5 append_p50_ms=16.56 per_append_p50_ms=0.0906 useful_throughput_appends_per_s=12076 replay_p50_ms=10.34 -F5 tier=M profile=legacy-reachable-v1 seed=7 workload_id=26ef9606d45b13960809fe90a2e4dced659aea3e80b5d20fb682ee3873026061 expectation_id=40bbecfbdcf065dc62c21c5704750e10cdc5a06028bbf95275679d1a33618273 script_digest=ac18dd3b37286646ff3a475b288d51eeb52323406a3d88fbc5ed4595d76e2017 -round 1 class=valid append_ms=5637.62 replay_ms=5566.09 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 2 class=valid append_ms=5632.78 replay_ms=5551.82 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 3 class=valid append_ms=5619.24 replay_ms=5549.62 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 4 class=valid append_ms=5629.88 replay_ms=5540.67 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 5 class=valid append_ms=5630.16 replay_ms=5553.57 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -F5 tier=M profile=legacy-reachable-v1 summary valid_rounds=5 append_p50_ms=5630.16 per_append_p50_ms=1.0993 useful_throughput_appends_per_s=888 replay_p50_ms=5551.82 -F5 tier=S profile=application-radix-v0 seed=7 workload_id=2d073ea67df118cbddab23932a0bc2a57af9781b6a789e666ea99d9e231e4f47 expectation_id=05c0c4d06bf64926770f845c84182f5fcda153e7c54948cdc6f9cb336242b715 script_digest=efc633360e2e237b9ed238e1cb0f57b8c12e64c779a52bba6e223c9bdfd33d4d -round 1 class=valid append_ms=9.50 replay_ms=2.71 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 2 class=valid append_ms=8.90 replay_ms=2.68 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 3 class=valid append_ms=9.35 replay_ms=2.72 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 4 class=valid append_ms=8.73 replay_ms=2.66 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -round 5 class=valid append_ms=9.34 replay_ms=2.71 occurrences=200 delivery_attempts=200 submitted=200 admitted=200 committed=200 refused=0 unfinished=0 idle_passes=0 -F5 tier=S profile=application-radix-v0 summary valid_rounds=5 append_p50_ms=9.34 per_append_p50_ms=0.0441 useful_throughput_appends_per_s=21424 replay_p50_ms=2.71 -F5 tier=M profile=application-radix-v0 seed=7 workload_id=26ef9606d45b13960809fe90a2e4dced659aea3e80b5d20fb682ee3873026061 expectation_id=40bbecfbdcf065dc62c21c5704750e10cdc5a06028bbf95275679d1a33618273 script_digest=ac18dd3b37286646ff3a475b288d51eeb52323406a3d88fbc5ed4595d76e2017 -round 1 class=valid append_ms=272.04 replay_ms=100.84 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 2 class=valid append_ms=272.69 replay_ms=99.22 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 3 class=valid append_ms=268.49 replay_ms=99.04 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 4 class=valid append_ms=272.65 replay_ms=98.90 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -round 5 class=valid append_ms=271.63 replay_ms=99.17 occurrences=5000 delivery_attempts=5000 submitted=5000 admitted=5000 committed=5000 refused=0 unfinished=0 idle_passes=0 -F5 tier=M profile=application-radix-v0 summary valid_rounds=5 append_p50_ms=272.04 per_append_p50_ms=0.0542 useful_throughput_appends_per_s=18380 replay_p50_ms=99.17 diff --git a/docs/studies/m2-commitment-profile/full-suite-delta-validate.txt b/docs/studies/m2-commitment-profile/full-suite-delta-validate.txt deleted file mode 100644 index 960698090..000000000 --- a/docs/studies/m2-commitment-profile/full-suite-delta-validate.txt +++ /dev/null @@ -1,109 +0,0 @@ -test result: ok. 899 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.75s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.48s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.11s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: FAILED. 26 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s -test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.86s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: test filesystem_writer_lease_refuses_overlap_before_takeover ... okok -test result: -test result: ok. 134 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.40s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s -test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s -test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s -test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s -test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s -test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s -test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s -test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s -test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 48 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.22s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.04s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.02s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.02s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.00s -test result: ok. 31 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.14s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s -test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s -test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 22 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s -test result: ok. 4 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.42s -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s -test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s -test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.42s -test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s -test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.06s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s -test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s -test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.07s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 42.36s -test result: ok. 31 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.80s -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s -test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s -test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s -test result: ok. 28 passed; 0 failed; 6 ignored; 0 measured; 0 filtered out; finished in 0.68s -test special_files_are_rejected_before_a_read_attempt ... FAILED ----- special_files_are_rejected_before_a_read_attempt stdout ---- -thread 'special_files_are_rejected_before_a_read_attempt' (12129778) panicked at crates/warp-core/tests/bounded_workspace_observation_tests.rs:48:23: -expected Ok(..), got Error { kind: InvalidInput, message: "path must be shorter than SUN_LEN" } - special_files_are_rejected_before_a_read_attempt diff --git a/docs/studies/m2-commitment-profile/targeted-tests.txt b/docs/studies/m2-commitment-profile/targeted-tests.txt deleted file mode 100644 index 1ddc2926e..000000000 --- a/docs/studies/m2-commitment-profile/targeted-tests.txt +++ /dev/null @@ -1,8 +0,0 @@ -m2-rel.log:test f5_validates_under_the_application_profile ... ok -m2-rel.log:test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 33.53s -m2-action.log:test f5_validates_under_the_application_profile ... ok -m2-action.log:test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.23s -m2-action.log:test application_profile_actions_commit_and_recover ... ok -m2-action.log:test result: ok. 49 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.36s -m2-action.log:test application_profile_history_recovers_only_under_its_profile ... ok -m2-action.log:test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.77s diff --git a/docs/topics/ApplicationRoot.md b/docs/topics/ApplicationRoot.md index 400d20dce..5a15f9d3a 100644 --- a/docs/topics/ApplicationRoot.md +++ b/docs/topics/ApplicationRoot.md @@ -6,7 +6,9 @@ The `application_root_shadow` feature keeps an incremental authenticated application reading beside the legacy state root. It is experimental and off by default. Enabling it changes no state root, receipt, patch, WAL or wire -format. It does not select a new commitment profile for admitted histories. +format. It does not select a new commitment profile for admitted histories; +the separate `application_root_profile` feature does, per worldline, as +described in [Experimental commitment profile](#experimental-commitment-profile). The legacy commitment contract remains in [merkle-commit.md](../spec/merkle-commit.md). @@ -71,5 +73,51 @@ as a valid timing. - `scene_fixture_tests` includes canaries for missing or failed parity and is scheduled with the shadow feature without `delta_validate`. -These witnesses do not establish a production commitment format or recovery -of a new-profile history. WAL and wire formats retain their existing meaning. +These shadow witnesses do not establish a production commitment format. +WAL and wire formats retain their existing meaning. + +## Experimental commitment profile + +The `application_root_profile` feature (experimental, off by default, implies +`application_root_shadow`) lets a worldline commit its application reading +instead of the legacy reachable-state root. `CommitmentProfile` is either +`LegacyReachableV1` (`echo.state-root.legacy-reachable.v1`, the default) or +`ApplicationRadixV0` (`echo.state-root.application-radix.v0`). The profile is +chosen with `WorldlineState::with_commitment_profile` before the worldline is +registered. Nothing infers a profile from a root's bytes. + +An `ApplicationRadixV0` worldline commits +`H("echo.state-root.application-radix.v0" || application_root)` as its +`state_root`. The tag separates this value from every legacy root, so no +historical `state_root` is reinterpreted. Commit hash v2 and v3 recipes are +unchanged; they bind whichever root the profile commits. A +`LegacyReachableV1` worldline's roots, commit hashes and receipts are +unchanged, and with the feature off every path below uses the legacy recipe. + +The profile travels with the retained trie, so rollback markers, replay bases +and checkpoints, forks and the runtime commit guard carry it. The runtime +commit advances the trie from the commit's complete write set and commits its +tagged root without walking the graph. Replay, readings at earlier +coordinates, import candidates, playback cursors, provenance initial +boundaries and checkpoint restores verify under the worldline's own recipe +(`recomputed_state_root`, `state_root_of`). A history replayed or recovered +under the other profile fails as a root mismatch. + +Limits of this profile: + +- It is part of the host's worldline registration and is not written to the + WAL, provenance codecs or WSC. A mismatch is a root mismatch, not a typed + profile error; a durable descriptor would need a codec version and owner + approval of the domain strings. +- It binds only the application reading. It is not a boundary seal over the + history root, and ports and warp-instance headers remain outside the + reading. +- There is no migration from a legacy history and no cross-host exchange + witness. + +Witnesses: the coordinator test that commits, replays and forks under the +profile; `application_root_profile_tests` (F5 validates under both profiles, +which commit different roots); `trusted_runtime_host_loop_tests` (WAL recovery +succeeds only under the registering profile); and +`executable_operation_pipeline_tests` (Actions commit and recover under the +profile, including stale-basis corroboration of the genesis origin). From dde9e3ecb351545d950507de0f975cd1bb072e17 Mon Sep 17 00:00:00 2001 From: James Ross Date: Tue, 20 Oct 2026 01:46:14 -0700 Subject: [PATCH 3/3] Address M2 profile review findings - Engine::snapshot_for_state uses the worldline's own root recipe, so a synthesized frontier snapshot of an application-profile worldline advertises the tagged application root, not the legacy root. - Under delta_validate, the runtime commit asserts that the advanced application trie equals its full rebuild before committing its root. - Document that recomputed_state_root reads the current application trie (replay stays incremental) while state_root_of rebuilds. - Assert the exact refusal in the legacy-profile replay and WAL recovery tests. Co-Authored-By: Claude Opus 5.5 --- crates/warp-core/src/coordinator.rs | 7 ++++--- crates/warp-core/src/engine_impl.rs | 10 +++++++++- crates/warp-core/src/worldline_state.rs | 11 ++++++++--- .../tests/trusted_runtime_host_loop_tests.rs | 12 ++++++++---- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/warp-core/src/coordinator.rs b/crates/warp-core/src/coordinator.rs index 9a769feb5..3a38d02b7 100644 --- a/crates/warp-core/src/coordinator.rs +++ b/crates/warp-core/src/coordinator.rs @@ -8781,9 +8781,10 @@ mod tests { ); let legacy_base = base.with_commitment_profile(CommitmentProfile::LegacyReachableV1); assert!( - provenance - .replay_worldline_state(worldline_id, &legacy_base) - .is_err(), + matches!( + provenance.replay_worldline_state(worldline_id, &legacy_base), + Err(crate::provenance_store::ReplayError::InitialBoundaryHashMismatch { .. }) + ), "an application-profile history does not verify under the legacy profile" ); diff --git a/crates/warp-core/src/engine_impl.rs b/crates/warp-core/src/engine_impl.rs index afcec5603..53717d853 100644 --- a/crates/warp-core/src/engine_impl.rs +++ b/crates/warp-core/src/engine_impl.rs @@ -2176,6 +2176,12 @@ impl Engine { let advanced = trie .advanced(&self.state, patch.ops(), patch.out_slots()) .unwrap_or_else(|| crate::application_root::ApplicationTrie::rebuild(&self.state)); + #[cfg(feature = "delta_validate")] + assert_eq!( + advanced.root(), + crate::application_root::ApplicationTrie::rebuild(&self.state).root(), + "the incremental application root must equal its full rebuild" + ); let root = crate::application_root::profile_tagged_state_root(&advanced.root()); self.runtime_application_trie_after = Some(advanced); root @@ -2550,7 +2556,9 @@ impl Engine { /// Returns a snapshot view over an external worldline frontier state. #[must_use] pub fn snapshot_for_state(&self, state: &WorldlineState) -> Snapshot { - let state_root = compute_state_root(state.warp_state(), &state.root); + // The worldline's own recipe: the legacy reachable root, or under an + // application profile (M2) the tagged application root. + let state_root = state.recomputed_state_root(); let parents: Vec = state .last_snapshot .as_ref() diff --git a/crates/warp-core/src/worldline_state.rs b/crates/warp-core/src/worldline_state.rs index 9b012fc8e..39e6b9586 100644 --- a/crates/warp-core/src/worldline_state.rs +++ b/crates/warp-core/src/worldline_state.rs @@ -524,9 +524,14 @@ impl WorldlineState { crate::snapshot::compute_state_root_for_warp_state(warp_state, &self.root) } - /// This state's committed root, recomputed from its graph without any - /// retained value: the legacy reachable root, or for an application - /// profile the tagged application root. + /// This state's committed root, recomputed without the retained legacy + /// state root: the legacy reachable root walked from the graph, or for an + /// application profile the tagged root of the current application trie. + /// + /// The application trie is not rebuilt here when it is current, so replay + /// verifies incrementally; `delta_validate` checks each advance against a + /// full rebuild, and checkpoint and initial-boundary checks use + /// [`Self::state_root_of`], which rebuilds. #[must_use] pub(crate) fn recomputed_state_root(&self) -> Hash { self.state_root_of_own_graph() diff --git a/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs b/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs index d2734b373..b128f8032 100644 --- a/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs +++ b/crates/warp-core/tests/trusted_runtime_host_loop_tests.rs @@ -4501,11 +4501,15 @@ fn application_profile_history_recovers_only_under_its_profile() { let (legacy_runtime, _) = profiled_runtime(CommitmentProfile::LegacyReachableV1); let mut legacy = TrustedRuntimeHost::new(legacy_runtime, empty_engine()) .expect("reconstructed host should initialize"); + let refusal = legacy + .enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(&wal_root)) + .expect_err("an application-profile history does not recover under the legacy profile"); + // Recovery registers the initial boundary under the host's own profile, + // so the mismatch surfaces as the first replayed tick's root. assert!( - legacy - .enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(&wal_root)) - .is_err(), - "an application-profile history does not recover under the legacy profile" + format!("{refusal:?}") + .starts_with("Runtime(Replay(StateRootMismatch { tick: WorldlineTick(0),"), + "the legacy profile is refused at the first replayed root: {refusal:?}" ); drop(legacy); fs::remove_dir_all(&wal_root).expect("WAL fixture cleanup succeeds");