Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

### Added

- `warp-core` trusted runtime hosts now configure runtime WAL through
`TrustedRuntimeWalConfig`, with `TrustedRuntimeWalStoreKind` exposing the
configured adapter kind as host-owned read-only evidence while
`TrustedRuntimeApp` remains limited to submit and observe surfaces.
- Added an Echo 1.0 release contract that records the four binary release gates,
compatibility policy, evidence requirements, and GitHub Project boundary
without carrying live roadmap state in the repository.
Expand Down
2 changes: 1 addition & 1 deletion crates/warp-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ pub use tick_patch::{
#[cfg(all(feature = "native_rule_bootstrap", feature = "trusted_runtime"))]
pub use trusted_runtime_host::{
TrustedRuntimeApp, TrustedRuntimeHost, TrustedRuntimeHostError, TrustedRuntimeHostRunReport,
TrustedRuntimeWal, TrustedRuntimeWalError,
TrustedRuntimeWal, TrustedRuntimeWalConfig, TrustedRuntimeWalError, TrustedRuntimeWalStoreKind,
};
pub use tx::TxId;
pub use warp_state::{WarpInstance, WarpState};
Expand Down
206 changes: 198 additions & 8 deletions crates/warp-core/src/trusted_runtime_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use thiserror::Error;
use crate::{
causal_wal::{
build_recovery_certificate, build_submission_acceptance_transaction,
build_tick_transaction, recover_in_memory_store, recover_receipt_index,
build_tick_transaction, recover_from_frames_and_commits, recover_receipt_index,
recover_submission_index, recovered_submission_receipt_index_root, AffectedFrontier,
AffectedFrontierKind, InMemoryWalStore, Lsn, PayloadCodecId, PayloadSchemaId,
RecoveredReceiptIndex, RecoveredSubmissionIndex, RecoveryAccessMode, RecoveryCertificate,
Expand Down Expand Up @@ -118,6 +118,45 @@ pub struct TrustedRuntimeWalRecovery {
pub receipts: RecoveredReceiptIndex,
}

/// Store kind configured for the trusted runtime WAL adapter.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TrustedRuntimeWalStoreKind {
/// Deterministic process-local store used by fast tests.
InMemory,
}

/// Host-owned runtime WAL adapter configuration.
#[derive(Clone, Debug)]
pub struct TrustedRuntimeWalConfig {
store: TrustedRuntimeWalStore,
next_lsn: Lsn,
}

impl TrustedRuntimeWalConfig {
/// Builds the deterministic in-memory runtime WAL configuration.
#[must_use]
pub fn in_memory() -> Self {
Self {
store: TrustedRuntimeWalStore::in_memory(),
next_lsn: Lsn::from_raw(0),
}
}

/// Returns the configured store kind as read-only evidence.
#[must_use]
pub fn store_kind(&self) -> TrustedRuntimeWalStoreKind {
self.store.kind()
}

/// Returns this configuration with a caller-supplied next LSN.
#[must_use]
pub fn with_next_lsn(mut self, next_lsn: Lsn) -> Self {
self.next_lsn = next_lsn;
self
}
}

/// Local trusted runtime host for the app-safe contract-host path.
///
/// Application code should receive [`TrustedRuntimeApp`], not this type. This
Expand Down Expand Up @@ -195,7 +234,20 @@ impl TrustedRuntimeHost {
///
/// Returns a WAL error when the writer epoch cannot be acquired.
pub fn enable_in_memory_runtime_wal(&mut self) -> Result<(), TrustedRuntimeHostError> {
self.runtime_wal = Some(TrustedRuntimeWal::new_in_memory()?);
self.enable_runtime_wal(TrustedRuntimeWalConfig::in_memory())
}

/// Enables a host-owned runtime WAL adapter configuration.
///
/// # Errors
///
/// Returns a WAL error when the configured store cannot acquire the runtime
/// writer epoch.
pub fn enable_runtime_wal(
&mut self,
config: TrustedRuntimeWalConfig,
) -> Result<(), TrustedRuntimeHostError> {
self.runtime_wal = Some(TrustedRuntimeWal::from_config(config)?);
Ok(())
}

Expand Down Expand Up @@ -357,7 +409,7 @@ impl TrustedRuntimeHost {
/// Minimal trusted-runtime WAL adapter for ACK-boundary integration tests.
#[derive(Clone, Debug)]
pub struct TrustedRuntimeWal {
store: InMemoryWalStore,
store: TrustedRuntimeWalStore,
writer_epoch: WriterEpochId,
segment_id: WalSegmentId,
next_lsn: Lsn,
Expand All @@ -375,11 +427,19 @@ pub struct TrustedRuntimeWal {
impl TrustedRuntimeWal {
/// Builds a WAL adapter backed by an in-memory store.
pub fn new_in_memory() -> Result<Self, TrustedRuntimeWalError> {
Self::new_in_memory_at_lsn(Lsn::from_raw(0))
Self::from_config(TrustedRuntimeWalConfig::in_memory())
}

fn new_in_memory_at_lsn(next_lsn: Lsn) -> Result<Self, TrustedRuntimeWalError> {
let mut store = InMemoryWalStore::new();
Self::from_config(TrustedRuntimeWalConfig::in_memory().with_next_lsn(next_lsn))
}

/// Builds a WAL adapter from host-owned configuration.
pub fn from_config(config: TrustedRuntimeWalConfig) -> Result<Self, TrustedRuntimeWalError> {
let TrustedRuntimeWalConfig {
mut store,
next_lsn,
} = config;
let writer_epoch = WriterEpochId::from_hash(trusted_runtime_wal_digest("writer-epoch"));
store.acquire_writer_epoch(WriterEpochRequest {
epoch_id: writer_epoch,
Expand Down Expand Up @@ -414,6 +474,12 @@ impl TrustedRuntimeWal {
})
}

/// Returns the configured store kind as read-only evidence.
#[must_use]
pub fn store_kind(&self) -> TrustedRuntimeWalStoreKind {
self.store.kind()
}

/// Returns committed WAL markers recorded by the adapter.
#[must_use]
pub fn commits(&self) -> Vec<WalTransactionCommit> {
Expand All @@ -429,14 +495,13 @@ impl TrustedRuntimeWal {
/// Returns a clone of the underlying in-memory store for recovery tests.
#[must_use]
pub fn cloned_store(&self) -> InMemoryWalStore {
self.store.clone()
self.store.cloned_in_memory_store()
}

/// Recovers submission and receipt indexes from committed WAL transactions
/// without scheduler callbacks.
pub fn recover_read_only(&self) -> Result<TrustedRuntimeWalRecovery, TrustedRuntimeWalError> {
let mut store = self.cloned_store();
let report = recover_in_memory_store(&mut store, RecoveryAccessMode::ReadOnly)?;
let report = recover_runtime_wal_store_read_only(&self.store)?;
let submissions = recover_submission_index(&report).map_err(WalRecoveryError::from)?;
let receipts = recover_receipt_index(&report).map_err(WalRecoveryError::from)?;
Ok(TrustedRuntimeWalRecovery {
Expand Down Expand Up @@ -616,6 +681,131 @@ impl TrustedRuntimeWal {
}
}

#[derive(Clone, Debug)]
enum TrustedRuntimeWalStore {
InMemory(InMemoryWalStore),
}

impl TrustedRuntimeWalStore {
fn in_memory() -> Self {
Self::InMemory(InMemoryWalStore::new())
}

fn kind(&self) -> TrustedRuntimeWalStoreKind {
match self {
Self::InMemory(_) => TrustedRuntimeWalStoreKind::InMemory,
}
}

fn append_transaction(
&mut self,
transaction: WalCommittedTransaction,
) -> Result<(), WalStoreError> {
match self {
Self::InMemory(store) => store.append_transaction(transaction),
}
}

fn append_uncommitted_frame(
&mut self,
epoch_id: WriterEpochId,
frame: crate::causal_wal::WalFrame,
) -> Result<(), WalStoreError> {
match self {
Self::InMemory(store) => store.append_uncommitted_frame(epoch_id, frame),
}
}

fn cloned_in_memory_store(&self) -> InMemoryWalStore {
match self {
Self::InMemory(store) => store.clone(),
}
}
}

impl WalStorePort for TrustedRuntimeWalStore {
fn acquire_writer_epoch(
&mut self,
request: WriterEpochRequest,
) -> Result<crate::causal_wal::WriterEpoch, WalStoreError> {
match self {
Self::InMemory(store) => store.acquire_writer_epoch(request),
}
}

fn append_frame(
&mut self,
epoch_id: WriterEpochId,
frame: crate::causal_wal::WalFrame,
) -> Result<(), WalStoreError> {
match self {
Self::InMemory(store) => store.append_frame(epoch_id, frame),
}
}

fn flush_commit(
&mut self,
epoch_id: WriterEpochId,
commit: WalTransactionCommit,
) -> Result<(), WalStoreError> {
match self {
Self::InMemory(store) => store.flush_commit(epoch_id, commit),
}
}

fn read_frames(&self) -> Vec<crate::causal_wal::WalFrame> {
match self {
Self::InMemory(store) => store.read_frames(),
}
}

fn read_commits(&self) -> Vec<WalTransactionCommit> {
match self {
Self::InMemory(store) => store.read_commits(),
}
}

fn seal_segment(
&mut self,
epoch_id: WriterEpochId,
segment_id: WalSegmentId,
) -> Result<crate::causal_wal::WalSegmentSeal, WalStoreError> {
match self {
Self::InMemory(store) => store.seal_segment(epoch_id, segment_id),
}
}

fn truncate_tail_after(&mut self, after_lsn: Lsn) -> Result<(), WalStoreError> {
match self {
Self::InMemory(store) => store.truncate_tail_after(after_lsn),
}
}

fn publish_manifest(
&mut self,
epoch_id: WriterEpochId,
manifest: crate::causal_wal::WalManifest,
) -> Result<(), WalStoreError> {
match self {
Self::InMemory(store) => store.publish_manifest(epoch_id, manifest),
}
}

fn close_epoch(&mut self, epoch_id: WriterEpochId) -> Result<(), WalStoreError> {
match self {
Self::InMemory(store) => store.close_epoch(epoch_id),
}
}
}

fn recover_runtime_wal_store_read_only(
store: &impl WalStorePort,
) -> Result<RecoveryScanReport, WalRecoveryError> {
let frames = store.read_frames();
let commits = store.read_commits();
recover_from_frames_and_commits(&frames, &commits, RecoveryAccessMode::ReadOnly)
}

#[cfg(any(test, feature = "host_test"))]
impl TrustedRuntimeWal {
/// Builds an in-memory WAL at a caller-supplied LSN for overflow tests.
Expand Down
40 changes: 37 additions & 3 deletions crates/warp-core/tests/trusted_runtime_host_loop_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ use warp_core::{
ObservationCoordinate, ObservationFrame, ObservationPayload, ObservationProjection,
ObservationReadBudget, ObservationRequest, ObserverPlanId, OpticAdmissionTicket,
OpticArtifactHandle, PatternGraph, PlaybackMode, SchedulerKind, TickDelta, TrustedRuntimeHost,
TrustedRuntimeHostError, TrustedRuntimeWal, TrustedRuntimeWalError, WarpOp, WorldlineId,
WorldlineRuntime, WorldlineState, WriterHead, WriterHeadKey, OPTIC_ADMISSION_TICKET_KIND,
OPTIC_ARTIFACT_HANDLE_KIND,
TrustedRuntimeHostError, TrustedRuntimeWal, TrustedRuntimeWalConfig, TrustedRuntimeWalError,
TrustedRuntimeWalStoreKind, WarpOp, WorldlineId, WorldlineRuntime, WorldlineState, WriterHead,
WriterHeadKey, OPTIC_ADMISSION_TICKET_KIND, OPTIC_ARTIFACT_HANDLE_KIND,
};

const SCHEMA_SHA256_HEX: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
Expand Down Expand Up @@ -427,6 +427,40 @@ fn runtime_wal_ack_submit_commits_acceptance_before_returning_handle() {
assert_eq!(entry.posture, RecoveredSubmissionPosture::AcceptedPending);
}

#[test]
fn runtime_wal_ack_adapter_is_configured_by_trusted_host_boundary() {
let (runtime, worldline_id) = runtime();
let mut host =
TrustedRuntimeHost::new(runtime, empty_engine()).expect("trusted host should initialize");

host.enable_runtime_wal(TrustedRuntimeWalConfig::in_memory())
.expect("host should own runtime WAL adapter configuration");
assert_eq!(
host.runtime_wal()
.expect("runtime WAL should be configured")
.store_kind(),
TrustedRuntimeWalStoreKind::InMemory
);

let submission = {
let mut app = host.app();
app.submit_intent_with_runtime_wal_ack(eint_envelope(worldline_id))
.expect("app should only see ACK submission surface")
};

assert_eq!(
host.runtime_wal()
.expect("runtime WAL should remain host-owned")
.recover_read_only()
.expect("runtime WAL should recover through adapter boundary")
.submissions
.get(&submission.submission_id)
.expect("submission should recover")
.posture,
RecoveredSubmissionPosture::AcceptedPending
);
}

#[test]
fn runtime_wal_ack_duplicate_submit_does_not_append_second_acceptance() {
let (runtime, worldline_id) = runtime();
Expand Down
2 changes: 2 additions & 0 deletions docs/design/causal-wal-hardening-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -910,11 +910,13 @@ boundary without giving the application append authority.
Acceptance criteria:

- `TrustedRuntimeHost` owns the runtime WAL adapter.
- `TrustedRuntimeHost` configures the adapter through `TrustedRuntimeWalConfig`.
- `TrustedRuntimeApp` exposes no WAL append or tick authority.
- The adapter can be inspected by host tests as read-only evidence.

Test plan:

- `runtime_wal_ack_adapter_is_configured_by_trusted_host_boundary`
- `runtime_wal_ack_submit_commits_acceptance_before_returning_handle`

## Slice 87: Submission Acceptance Transaction Wiring
Expand Down
11 changes: 11 additions & 0 deletions docs/design/reference-trusted-runtime-host-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ staging, no `super_tick`, no scheduler pass, and no fault recovery authority.
- `TrustedRuntimeHost::stage_installed_contract_submission(...)`;
- `TrustedRuntimeHost::tick_once(...)`;
- `TrustedRuntimeHost::run_until_idle(...)`;
- `TrustedRuntimeWalConfig`, which the trusted host uses to configure the
runtime WAL adapter before app-facing ACK submission;
- `TrustedRuntimeWalStoreKind`, which exposes the configured store kind as
host-owned read-only evidence;
- `TrustedRuntimeHostRunReport`, which records scheduler passes and committed
step count.

Expand All @@ -54,6 +58,7 @@ application
-> witnessed submission handle

trusted runtime host
-> configures runtime WAL adapter
-> registers package
-> stages ticketed ingress
-> runs scheduler-owned ticks
Expand All @@ -64,6 +69,11 @@ The host loop does not make application dispatch synchronous. A submitted
intent remains pending until trusted runtime-owned ingress staging and
scheduler-owned tick execution decide it.

The runtime WAL adapter is configured through the trusted host, not through
`TrustedRuntimeApp`. The app-facing handle can request the WAL-backed ACK path,
but it never receives WAL append, flush, truncate, manifest, tick, or recovery
authority.

## Non-Goals

- No production daemon.
Expand All @@ -76,6 +86,7 @@ scheduler-owned tick execution decide it.
## Evidence

- `cargo test -p warp-core --features "native_rule_bootstrap trusted_runtime" --test trusted_runtime_host_loop_tests`
- `cargo test -p warp-core --features "native_rule_bootstrap trusted_runtime host_test" --test trusted_runtime_host_loop_tests runtime_wal_ack`

The witness registers a generated-style package, submits through the app handle,
stages ticketed ingress through the host, runs until idle, observes an applied
Expand Down
Loading