Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Effect System

Overview

Aura uses algebraic effects to abstract system capabilities. Effect traits define abstract interfaces for cryptography, storage, networking, time, and randomness. Handlers implement these traits with concrete behavior. Context propagation ensures consistent execution across async boundaries.

This document covers effect trait design, handler patterns, and the context model. See Runtime for lifecycle management, service composition, and guard chain execution. See Ownership Model for the repo-wide Pure/MoveOwned/ActorOwned/Observed taxonomy.

The aura-agent runtime uses structured concurrency with explicit session ownership. Session-bound effects execute only under the current owner via canonical ingress. For complete details on async ownership, session ownership, typed runtime errors, and instrumentation policy, see crates/aura-agent/ARCHITECTURE.md.

The runtime contract is intentionally split:

  • actor services supervise long-lived runtime structure
  • move semantics govern session and endpoint ownership

Effect execution that touches session state belongs to the second category, not the first.

Typed Error Sources

Effect failures converted into AuraError retain their original concrete cause as the immediate standard std::error::Error::source. Cloning the error preserves that cause and its nested source chain. Shared storage of a cause does not add a wrapper node to the chain. Source objects are process-local: serialization keeps the error category and message and intentionally omits the source chain.

Native app/runtime boundaries preserve the same source contract. A RuntimeBridgeError carries a structural failure category and the actual underlying error; diagnostic wording cannot determine an idempotent success, retry, authorization result, or protocol confirmation. Foreign callback payloads explicitly omit process-local sources. Their diagnostic representation does not constitute native failure provenance and cannot reconstruct it.

Ownership At Effect Boundaries

Effect traits sit at an ownership boundary and should preserve the repo-wide ownership model rather than hide it.

  • Effect trait definitions in aura-core are primarily Pure.
  • Long-lived mutable async ownership belongs to ActorOwned runtime services, not to effect trait definitions or ad hoc handler-local state.
  • Exclusive authority transfer belongs to MoveOwned handles, owner tokens, or transfer records rather than shared mutable rewrites.
  • Effect handlers may implement capabilities, but parity-critical mutation and publication should remain capability-gated in the exposed API shape.

Practical implications:

  • define trait methods so callers can preserve typed ownership and typed failure
  • do not use effect handlers as a loophole for bypassing authority checks
  • do not let observation-facing layers gain semantic mutation power through convenience helpers
  • ensure long-running effect-driven flows report typed terminal outcomes rather than implicit success or silent hangs
  • prefer the canonical aura-core ownership vocabulary at effect-facing boundaries:
    • OperationContext for move-owned workflow ownership
    • exact progress/terminal publication wrappers plus consumed TerminalPublisher for lifecycle
    • OwnedTaskSpawner, OwnedShutdownToken, and BoundedActorIngress for actor-owned task and ingress boundaries

Effect Traits

Aura defines effect traits as abstract interfaces for system capabilities. Core traits expose essential functionality. Extended traits expose optional operations and coordinated behaviors. Each trait is independent and does not assume global state.

Core traits include CryptoCoreEffects, NetworkCoreEffects, StorageCoreEffects, time domain traits, RandomCoreEffects, and JournalEffects. Extended traits include CryptoExtendedEffects, NetworkExtendedEffects, StorageExtendedEffects, RandomExtendedEffects, and system-level traits such as SystemEffects and ChoreographicEffects.

#![allow(unused)]
fn main() {
#[async_trait]
pub trait CryptoCoreEffects: RandomCoreEffects + Send + Sync {
    async fn ed25519_sign(
        &self,
        message: &[u8],
        private_key: &[u8],
    ) -> Result<Vec<u8>, CryptoError>;

    async fn ed25519_verify(
        &self,
        message: &[u8],
        signature: &[u8],
        public_key: &[u8],
    ) -> Result<bool, CryptoError>;

    async fn kdf_derive(
        &self,
        ikm: &[u8],
        salt: &[u8],
        info: &[u8],
        output_len: u32,
    ) -> Result<Vec<u8>, CryptoError>;
}
}

This example shows a core effect trait for cryptographic operations. Traits contain async methods for compatibility with async runtimes. Extension traits add optional capabilities without forcing all handlers to implement them. The hash() function is intentionally pure in aura-core::hash rather than an effect because it is deterministic and side-effect-free.

Time Traits

The legacy monolithic TimeEffects trait is replaced by domain-specific traits. PhysicalTimeEffects returns wall-clock time with uncertainty and provides sleep operations. LogicalClockEffects advances and reads causal vector clocks and Lamport scalars. OrderClockEffects produces opaque total order tokens without temporal meaning.

Callers select the domain appropriate to their semantics. Guards and transport use physical time. CRDT operations use logical clocks. Privacy-preserving ordering uses order tokens.

Cross-domain comparisons are explicit via TimeStamp::compare(policy). Total ordering across domains must use OrderTime or consensus sequencing. Direct SystemTime::now() or chrono usage is forbidden outside effect implementations.

Time Domain System

The unified TimeStamp type provides five domains for different use cases.

DomainEffect TraitPrimary Use
PhysicalClockPhysicalTimeEffectsWall time, cooldowns, receipts, liveness
LogicalClockLogicalClockEffectsCausal ordering, CRDT merge, happens-before
OrderClockOrderClockEffectsDeterministic ordering without timing leakage
RangePhysicalTimeEffects + policyValidity windows with bounded skew
ProvenancedTimeTimeComparisonAttested timestamps for consensus

Application code accesses time exclusively through effect traits. Direct SystemTime::now() calls are forbidden outside effect implementations. Cross-domain comparisons require explicit TimeStamp::compare(policy). OrderClock must not leak timing information.

Timeout And Backoff Contract

Timeout and backoff behavior is part of the ownership model. Each parity-critical async path must identify a deadline owner, retry policy owner, and terminal consequence. See Effects and Handlers Guide for timeout patterns and Ownership Model for timeout ownership rules.

Threshold Signing

Aura provides a unified ThresholdSigningEffects trait in aura-core/src/effects/threshold.rs for all threshold signing scenarios. The trait supports multi-device personal signing, guardian recovery approvals, and group operation approvals.

The trait uses a unified SigningContext that pairs a SignableOperation with an ApprovalContext. This design allows the same FROST signing machinery to handle all scenarios with proper audit context. The ThresholdSigningService in aura-agent provides the production implementation.

Key components include ThresholdSigningEffects for async signing operations, lifecycle traits for provisional and consensus modes, and AppCore.sign_tree_op() for high-level signing. See Cryptography for detailed threshold signature architecture.

When to Create Effect Traits

New effect traits are warranted when multiple crates need the same async capability and the operation involves OS integration or external state. Convenience wrappers belong in composite extension traits rather than new base traits. See Effects and Handlers Guide for trait design guidance.

Database Effects

Database operations use existing StorageEffects, JournalEffects, and AuthorizationEffects rather than a dedicated DatabaseEffects trait. See Database Architecture for query patterns.

Handler Design

Handlers are stateless per-request processors. They do not store global state. The handler taxonomy includes stateless infrastructure handlers, application handlers, typed composite handlers, and type-erased composite handlers. See Effects and Handlers Guide for implementation patterns.

Unified Encrypted Storage

Aura uses StorageEffects as the single persistence interface in application code. The production runtime wires StorageEffects through a unified encryption-at-rest wrapper. FilesystemStorageHandler provides raw bytes persistence. RealSecureStorageHandler uses Keychain or TPM for master-key persistence.

EncryptedStorage implements StorageEffects by encrypting and decrypting transparently. It generates or loads the master key on first use. Runtime assembly remains synchronous.

#![allow(unused)]
fn main() {
use aura_effects::{
    EncryptedStorage, EncryptedStorageConfig, FilesystemStorageHandler,
    RealCryptoHandler, RealSecureStorageHandler,
};
use std::sync::Arc;

let secure = Arc::new(RealSecureStorageHandler::with_base_path(base_path.clone()));
let storage = EncryptedStorage::new(
    FilesystemStorageHandler::from_path(base_path.clone()),
    Arc::new(RealCryptoHandler::new()),
    secure,
    EncryptedStorageConfig::default(),
);
}

This example shows the encryption wrapper assembly. RealCryptoHandler lives in aura-effects and implements CryptoCoreEffects. Storage configuration controls encryption enablement and opaque naming. Application code uses StorageEffects without knowledge of encryption details.

Context Model

The effect system propagates an EffectContext through async tasks. The context carries authority identity, context scope, session identification, execution mode, and metadata. No ambient state exists.

#![allow(unused)]
fn main() {
pub struct EffectContext {
    authority_id: AuthorityId,
    context_id: ContextId,
    session_id: SessionId,
    execution_mode: ExecutionMode,
    metadata: HashMap<String, String>,
}
}

This structure defines the operation-scoped effect context. The context flows through all effect calls and identifies which authority, context, and session the operation belongs to. The execution_mode controls handler selection for production versus test environments.

Context propagation uses scoped execution. A task local stores the current context. Nested tasks inherit the context. This ensures consistent behavior across async boundaries.

ReactiveEffects Trait

The ReactiveEffects trait provides type-safe signal-based state management. Signals are phantom-typed identifiers that reference reactive state. The phantom type ensures compile-time type safety.

#![allow(unused)]
fn main() {
pub struct Signal<T> {
    id: SignalId,
    _phantom: PhantomData<T>,
}

#[async_trait]
pub trait ReactiveEffects: Send + Sync {
    async fn read<T>(&self, signal: &Signal<T>) -> Result<T, ReactiveError>
    where T: Clone + Send + Sync + 'static;

    async fn emit<T>(&self, signal: &Signal<T>, value: T) -> Result<(), ReactiveError>
    where T: Clone + Send + Sync + 'static;

    fn subscribe<T>(&self, signal: &Signal<T>) -> Result<SignalStream<T>, ReactiveError>
    where T: Clone + Send + Sync + 'static;

    async fn register<T>(&self, signal: &Signal<T>, initial: T) -> Result<(), ReactiveError>
    where T: Clone + Send + Sync + 'static;

    async fn ensure_registered<T>(&self, signal: &Signal<T>, initial: T) -> Result<(), ReactiveError>
    where T: Clone + Send + Sync + 'static;
}
}

The read method returns the current value. The emit method updates the value. The subscribe method returns a stream of changes. The register method creates a new signal and rejects a duplicate ID. ensure_registered creates a missing signal while preserving the value and subscriptions of a matching existing signal; a different value type under the same ID is an error. See Runtime for reactive scheduling implementation. Subscribing an unregistered signal fails fast. Aura does not permit "dead stream" subscription success for missing registrations.

QueryEffects Trait

The QueryEffects trait provides typed Datalog queries with capability-based authorization. Queries implement the Query trait which defines typed access to journal facts.

#![allow(unused)]
fn main() {
pub trait Query: Send + Sync + Clone + 'static {
    type Result: Clone + Send + Sync + Default + 'static;

    fn to_datalog(&self) -> DatalogProgram;
    fn required_capabilities(&self) -> Vec<QueryCapability>;
    fn dependencies(&self) -> Vec<FactPredicate>;
    fn parse(bindings: DatalogBindings) -> Result<Self::Result, QueryParseError>;
    fn query_id(&self) -> String;
}

#[async_trait]
pub trait QueryEffects: Send + Sync {
    async fn query<Q: Query>(&self, query: &Q) -> Result<Q::Result, QueryError>;
    async fn query_raw(&self, program: &DatalogProgram) -> Result<DatalogBindings, QueryError>;
    fn subscribe<Q: Query>(&self, query: &Q) -> QuerySubscription<Q::Result>;
    async fn check_capabilities(&self, caps: &[QueryCapability]) -> Result<(), QueryError>;
    async fn invalidate(&self, predicate: &FactPredicate);
}
}

The Query trait converts queries to Datalog programs and defines capability requirements. The QueryEffects trait executes queries and manages subscriptions. Query isolation levels control consistency requirements. See Database Architecture for complete query system documentation.

A query-bound signal is active only after its initial query and signal emission succeed. Failed binding leaves no partial query subscription; retry keeps any existing signal value and subscribers and installs the binding once the query succeeds.

Determinism Rules

Effect boundaries determine native and WASM conformance parity. Protocol code must follow these rules to ensure deterministic execution.

The pure transition core requires identical outputs given the same input stream. No hidden state may affect observable behavior. All state must flow through explicit effect calls. Non-determinism is permitted only through explicit algebraic effects. Time comes from time traits. Randomness comes from RandomEffects. Storage comes from StorageEffects.

Conformance lanes compare logical steps rather than wall-clock timing. Time-dependent behavior uses simulated time through effect handlers. Conformance artifacts use canonical encoding with deterministic field ordering. See Testing Guide for conformance testing patterns.

Session-Local VM Bridge Effects

Production choreography execution uses a narrow synchronous bridge trait at the Aura and Telltale boundary. VmBridgeEffects in aura-core exposes only immediate session-local queue and snapshot operations. It does not expose async transport, storage, or journal methods.

This split exists because Telltale host callbacks are synchronous. The callback path may enqueue outbound payloads, record blocked receive edges, consume branch choices, and snapshot scheduler signals. It must not perform network I/O or journal work directly.

Async host work resumes outside the VM step boundary in Layer 6 runtime services. vm_host_bridge observes VmBridgeEffects state, performs transport and guard-chain work, and injects completed results back into the VM. This preserves deterministic VM progression while keeping Aura's runtime async.

aura-agent runtime code preserves this boundary through canonical ingress and explicit session ownership. Network callbacks, timers, and background tasks route typed session-ingress messages to the current local owner. Each active session has exactly one owner at any time.

That owner may be hosted by an actor, but the effect-routing rule is still ownership-based: session-bound effects execute because the caller is the current owner, not merely because it runs inside a service actor.

The runtime must also distinguish owner identity from owner capability:

  • owner identity identifies the current fragment/session owner
  • owner capability authorizes specific session-bound effects within fragment scope

Both checks matter for effect routing, especially across delegation boundaries.

See System Internals Guide for VM bridge implementation patterns and crates/aura-agent/ARCHITECTURE.md for the complete ownership model.

Layer Placement

The effect system spans several crates with strict dependency boundaries. aura-core defines effect traits, identifiers, and core data structures. It contains no implementations.

aura-effects contains stateless and single-party effect handlers. It provides default implementations for cryptography, storage, networking, and randomness. aura-protocol contains orchestrated and multi-party behavior. It bridges session types to effect calls.

aura-agent assembles handlers into runnable systems. It configures effect pipelines for production environments. aura-simulator provides deterministic execution with simulated time, networking, and controlled failure injection.

Performance

Aura includes several performance optimizations. Parallel initialization reduces startup time. Caching handlers reduce repeated computation. Buffer pools reduce memory allocation. The effect system avoids OS threads for WASM compatibility.

#![allow(unused)]
fn main() {
let builder = EffectSystemBuilder::new()
    .with_handler(Arc::new(RealCryptoHandler))
    .with_parallel_init();
}

This snippet shows parallel initialization of handlers. The builder pattern allows flexible handler composition. Lazy initialization creates handlers on first use. Async tasks and cooperative scheduling provide efficient execution.

Testing Support

The effect system supports deterministic testing through mock handlers. A simulated runtime provides control over time and network behavior. The simulator exposes primitives to inject delays or failures.

#![allow(unused)]
fn main() {
let system = TestRuntime::new()
    .with_mock_crypto()
    .with_deterministic_time()
    .build();
}

This snippet creates a test runtime with mock handlers for all effects. It provides deterministic time and network control. Tests use in-memory storage and mock networking to execute protocols without side effects. See Test Infrastructure Reference for test patterns.

Local timeout failure identity

Local budget failures distinguish policy rejection, clock unavailability, deadline expiry, and attempt exhaustion. A required clock failure remains observable through the standard error source chain and cannot be treated as deadline evidence. Native clock causes are omitted from serialized diagnostic errors. The local timeout result retains operation failures as distinct typed causes.

Storage profile exclusivity and immutable publication

Profile storage ownership is a move-owned infrastructure lease covering one physical profile lifetime. It does not authorize domain actions. Cooperative persistent writers require the same profile owner; unsupported exclusivity is a typed failure. Resource release on cancellation or process death permits a new owner, whose durable recovery still requires authenticated domain evidence.

Immutable secure publication has two outcomes: Created acknowledges complete encrypted and durable publication; AlreadyExists identifies an existing record without proving equality or trust. Publication does not replace existing bytes. A failure after publication can leave a complete record, so recovery validates the original stored record rather than assuming rollback. Backends without these transactional guarantees report unsupported semantics explicitly. Multi-record profile handoff requires a separately owned durable transaction and recovery contract; a runtime mutex or several atomic file replacements do not establish that contract.

The selected production provider's private concrete lease, rather than an arbitrary core trait implementation, establishes physical profile ownership. Persistent writer clones retain that resource owner. Browser ownership is scoped to the actual origin and storage namespace through Web Locks; immutable records require strict-durability IndexedDB transaction completion. Historical plaintext secret namespaces cannot authorize fresh signing-state creation. Shared platform credential namespaces need their own supported ownership contract; filesystem-profile ownership does not substitute for it.

Local timeout observation contract

A local timeout owner preserves its original deadline and the greatest required physical-clock observation across awaited operations, timer completion, retries, and child deadlines. Physical-clock rollback terminates that owner as a required service failure; it does not extend its allowance or imply deadline expiration. Observation contention is an ownership failure. Successful timer completion latches exhaustion. Persisted timeout state must include and validate the original window, observation, rollback, and exhaustion state; missing state cannot reconstruct an active allowance.

Window coordinate separation

Physical timeout intervals and receipt-generation intervals share checked half-open arithmetic with distinct sealed coordinate types. A coordinate cannot cross domains. A valid interval alone grants no observation, admission or persistence authority. Physical execution windows have positive millisecond extent and a fixed representable deadline; empty generation allowances admit no receipts. Restored owner state must preserve its original bound interval and observation history.

Required enhanced-time scheduling propagates actual provider query and sleep failures. A failed timer does not constitute deadline expiration or successful wake-up. Native source traversal retains the concrete time-effect error through runtime scheduling and effect-system forwarding.

VM bridge delivery custody

Synchronous VM callbacks enqueue frames; asynchronous transport delivery requires an exclusive VmBridgeSendLease. Queue snapshots are observations and do not remove frames. The lease retains the oldest frame through the awaited send and removes it only after acknowledgment. Concurrent enqueue preserves FIFO order, and a second delivery owner fails explicitly. Providers without this contract reject owned delivery instead of emulating it by draining a queue.

Dropping an in-flight lease retains the frame with an unknown delivery outcome. Unknown delivery blocks automatic replay; it requires protocol reconciliation or terminal session teardown. A native definitely-unsent result may release the frame for an owned retry. Display text cannot establish that result. These states are local delivery custody, not peer consensus or durable receipt proof.

Required choreography clock and timeout effects preserve their native source through ChoreographyError::RequiredTime. Failed clock reads do not provide timestamps or expiry evidence. Session cleanup remains required when its clock observation fails; the runtime retains both primary and secondary typed failures.

Secure storage distinguishes atomic initial publication of mutable owner state (secure_create_mutable) from lifetime-immutable evidence (secure_store_immutable). Both preserve an existing record and acknowledge a new publication; an interrupted call requires rereading the actual original. Atomic creation alone does not authorize later mutation or generation renewal. Providers without the atomic-create contract return a typed unsupported failure.

Secure publication classes

Secure storage distinguishes atomic initial mutable publication from lifetime immutable publication. Both preserve an existing original. Mutable records permit subsequent updates under their domain owner. Immutable records carry authenticated provider protection in the same atomic publication and reject generic replacement, deletion, and key-generation writes. Legacy sealing preserves the exact original bytes. A capability list or caller-supplied retirement label cannot override lifetime protection. Unsupported providers fail with a typed error. Interrupted writes have an uncertain publication outcome; successful retries validate and acknowledge the original record. Ordinary storage has no authority over the secure provider's physical subtree.

An enrollment mailbox record may acquire a retirement birth only in its first immutable publication. The birth must be authorized by the original approved issuer registration under its independently retained baseline and setup proof, and bound to the actual selected profile provider, exact record location and bytes, and the registration's signed expiry. A later registration, raw signed bytes, self-certified signer package, or caller-selected expiry cannot amend an existing immutable record. Issuer credential and setup records published before the registration is signed require separate original birth authority; the registration cannot retroactively authorize their deletion.

Required secure retrieval reports an absent record through a typed logical absence cause with the exact requested location. This differs from a provider I/O failure, whose concrete native cause remains intact. Observational existence checks may report false; required absence cannot authorize replacement keys, default signing policy or a weaker record selection.

The crypto extended effect exposes individual participant-key proof signing separately from authority threshold signing. A handler that lacks this primitive returns a typed unsupported cause; it cannot fabricate a proof or substitute a group signature. The stateless handler signs the supplied actual package, while the runtime owner supplies current-device/epoch/ceremony authorization.

Required runtime construction adapters preserve native initialization causes in both production and testing/simulation presets. Returning-profile authorization failure cannot be converted into a flat runtime diagnostic or treated as fresh credential absence. Serialized presentation remains separate from the native source chain used to diagnose the failing provider or verification boundary.

Fixed window attenuation and restore

A physical timeout window may be narrowed to an earlier authenticated deadline while preserving its original start and clock observation owner. Attenuation does not observe a synthetic timestamp, allocate a new allowance, or modify the original interval. A child retains its own sticky exhaustion; parent and child share high-water and rollback evidence. Restoring a child requires the exact attenuated bounds and matching acknowledged clock history. It must preserve exhaustion and cannot enlarge either deadline.

These pure continuity checks do not establish checkpoint provenance or runtime admission. The domain owner must authenticate the policy, retain actual custody, and acknowledge the original and attenuated snapshots through its required checkpoint path. Generation/epoch windows do not authorize physical expiry.

Selected-provider allocation custody

Allocation references are observations, not authority. A selected physical profile owner transfers opaque whole-inventory custody before runtime sharing. Provider transitions dispatch through the retained original backend and preserve actual storage, codec and cryptographic failures. Recovery may complete an acknowledged pre-live birth using its original retained evidence. Missing once-live allocation or checkpoint evidence fails closed. Unsupported selected providers fail structurally; provider substitution does not repair custody.

Selected provider migration contract

A legacy selected profile may adopt allocation lifetime custody only through its original exclusive physical provider owner and original retained decryption material. Required bounded inventory authentication precedes immutable root birth acknowledgment. Existing permanent secret policy and ciphertext are preserved. The independently acknowledged completed-handoff evidence prevents missing live checkpoint data from being treated as a new or unmigrated profile. Recovery retains original allocation identity and first-decision history; a serialized locator grants no recovery authority.

A provider acknowledgment confirms its transactional persistence contract. It does not attest physical media erasure, external backup deletion, or absence of a complete rollback of all local evidence.

Selected provider continuity

An explicitly configured effect provider remains the source of its required operations after shared runtime ownership transfer. Shared crypto handles preserve extended operations as well as core operations; shared ownership cannot substitute optional defaults for the selected implementation. Required provider faults retain their original cause across persistence, journal and network adapter boundaries. Such faults do not authorize implicit selection of a default provider. Ordinary storage customization preserves encrypted storage and the independent selected secure profile's lifetime custody.

Required invitation transcript encoding and Guardian cryptographic calls retain process-local native error sources. A verified invalid signature is a boolean rejection; codec failure or selected cryptographic provider failure is a typed error and cannot be converted into absence or a successful response. Native sources are local diagnostic evidence, not serialized protocol authority.

Newly composed diagnostics and retained causes

A diagnostic category and message contain no causal provenance or mutation authority. Attaching a native failure to a new diagnostic preserves the original concrete standard error and its existing source chain. Existing cause-bearing errors and terminal outcomes are not converted to source-free diagnostics or overwritten by a later cause. Cloning a cause-bearing error preserves its original native source identity.

Deterministic entropy admission

A production assembly rejects an explicit deterministic entropy seed before acquiring profile custody or invoking configured providers. Simulated cryptographic handlers and deterministic random streams require a private seed capability admitted from the actual nonproduction execution mode. Selecting a custom cryptographic handler does not bypass this admission decision.

Absolute physical deadline observations

PhysicalTimeEffects::wait_until_physical_deadline accepts a WindowPosition<PhysicalMillis> for an existing fixed endpoint. Its successful result is an actual selected-provider observation at or beyond that endpoint. It neither allocates a new window nor proves domain completion. Registration must not depend on an awaited caller clock read or a caller's cached timestamp. The selected provider owns timer cancellation and preserves native clock/timer failures. A provider lacking this contract returns AbsoluteDeadlineUnsupported; it cannot silently substitute a relative sleep. Native error sources are process-local and are omitted from serialized diagnostics.

Terminal resource acknowledgment requires the original observation lease, required current time, window validation and checkpoint to complete within the same absolute endpoint. Expiry or provider failure cannot publish success. A fixed-endpoint witness can latch expiry without a second awaited clock read. Domain owners retain responsibility for the actual manager/task/disposal proof.