Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
75a7c45
test(core): define BAP task lifecycle contract
seonghobae Aug 20, 2026
eeed48c
test(core): format BAP lifecycle regression
seonghobae Aug 20, 2026
4cea204
feat(bap): add lifecycle crate manifest
seonghobae Aug 20, 2026
681a17b
feat(bap): implement deterministic task lifecycle kernel
seonghobae Aug 20, 2026
eca7fc5
test(bap): exercise lifecycle transitions
seonghobae Aug 20, 2026
3dffbfd
feat(bap): register lifecycle crate
seonghobae Aug 20, 2026
5864189
test(bap): move lifecycle contract to owning crate
seonghobae Aug 20, 2026
beabc15
test(repo): register reusable BAP kernel
seonghobae Aug 20, 2026
606286f
build: lock the BAP workspace member
seonghobae Aug 20, 2026
28a53fc
fix(bap): satisfy lifecycle default contract
seonghobae Aug 20, 2026
91ef8c1
test(bap): require resumable overflow-safe lifecycle recovery
seonghobae Aug 20, 2026
34b0fbe
feat(bap): support bounded lifecycle recovery
seonghobae Aug 20, 2026
0915eed
test(bap): reject impossible lifecycle recovery snapshots
seonghobae Aug 20, 2026
4be8376
fix(bap): validate recovered lifecycle snapshots
seonghobae Aug 20, 2026
d0111fd
test(bap): require standard lifecycle error contracts
seonghobae Aug 20, 2026
0e7ee30
style(bap): apply canonical lifecycle test formatting
seonghobae Aug 20, 2026
5d6b8fd
fix(bap): implement standard lifecycle errors
seonghobae Aug 20, 2026
c3b6e1a
style(bap): apply canonical rustfmt layout
seonghobae Aug 20, 2026
80de947
test(bap): require explicit reconciliation and dead-letter states
seonghobae Aug 22, 2026
dff029a
feat(bap): add fail-closed reconciliation lifecycle states
seonghobae Aug 22, 2026
133825d
test(bap): cover direct dead-letter admission bounds
seonghobae Aug 22, 2026
416a5e5
test(bap): cover reconciliation and dead-letter recovery snapshots
seonghobae Aug 22, 2026
7d3bb60
style(bap): apply canonical rustfmt to reconciliation states
seonghobae Aug 22, 2026
1e900ae
test(bap): require lifecycle architecture decision
seonghobae Aug 22, 2026
852b362
docs(bap): record task lifecycle state authority
seonghobae Aug 22, 2026
4b61fce
docs(bap): index task lifecycle ADR
seonghobae Aug 22, 2026
f2649a9
docs(bap): register lifecycle ADR in canonical index
seonghobae Aug 22, 2026
2feb778
test(bap): use typed lifecycle setup states
seonghobae Aug 23, 2026
b88e2fb
docs(adr): define post-integration provenance handling
seonghobae Aug 23, 2026
d71b05f
docs: synchronize ADR 0016 provenance lifecycle
seonghobae Aug 23, 2026
85cc477
docs: repair direct socket design link
seonghobae Aug 23, 2026
e41d3be
Merge branch 'main' into feat/bap-task-lifecycle
opencode-agent[bot] Aug 24, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement.
- Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge.
- Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation.
- Resumable BAP lifecycle restoration with monotonic sequence recovery and fail-closed sequence exhaustion.
- Authoritative product documentation graph spanning PRD, TRD, ADR lifecycle/index, product-wide UML, conceptual ERD, requirement/decision traceability, threat modeling, product-wide test strategy, operability, API/protocol, release/rollback, and current primary-source standards doctoring, with machine-checkable repository contracts that keep conversation-derived future work distinct from protected-main implementation claims.
- Purpose-bound data-governance and privacy baseline that rejects both blanket masking and ambient raw-value propagation, defines field-scoped just-in-time disclosure, opaque-handle/trusted-broker boundaries, model/provider/region policy, retention/deletion/residency/break-glass controls, truthful CSAP/SOC 2 readiness language, and machine-checkable documentation contracts without inventing an OriginWeave-owned production database.
- Proposed product-wide target-architecture ADRs for the Rust control plane, isolated execution modes, typed actions, semantic observation/stale-node authority, prompt-injection and secret separation, resource-governor priority, provenance evidence, browser/protocol adapters, crawler policy, and hourly automation operational closure; these remain Proposed rather than shipped claims until protected review and merge.
Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/originweave-core",
"crates/originweave-bap",
"crates/originweave-policy",
"crates/originweave-resource",
"crates/originweave-evidence",
Expand Down
12 changes: 12 additions & 0 deletions crates/originweave-bap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "originweave-bap"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true

[lints]
workspace = true
329 changes: 329 additions & 0 deletions crates/originweave-bap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
//! Stable internal Browser Agent Protocol lifecycle contracts.
//!
//! This crate intentionally owns no transport, browser, network, model, secret,
//! approval, or persistence authority. External protocol adapters may project
//! these states, but protocol metadata cannot mint or change OriginWeave task
//! authority.

#![forbid(unsafe_code)]
#![deny(missing_docs)]

/// Durable logical state of one governed BAP task.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BapTaskState {
/// The task record exists but has not entered admission control.
Created,
/// Admission control accepted the task but execution has not started.
Admitted,
/// The task is actively executing governed work.
Running,
/// Execution is suspended until an approval decision is available.
WaitingForApproval,
/// Execution is suspended until required external input is available.
WaitingForExternalInput,
/// Execution is suspended at a compatible recoverable checkpoint.
Checkpointed,
/// Execution is suspended until an explicit reconciliation decision is recorded.
///
/// The lifecycle state does not itself persist or authenticate reconciliation
/// evidence. A durable owner must preserve the complete evidence that caused
/// the task to enter this state before resolution is considered.
ReconciliationRequired,
/// The declared post-condition completed successfully.
Succeeded,
/// The task reached a terminal execution failure.
Failed,
/// Cancellation completed and the task cannot resume.
Cancelled,
/// The task exceeded its allowed lifetime and cannot resume.
Expired,
/// The task was terminally removed from automatic execution after governed handling.
///
/// Durable dead-letter evidence remains the responsibility of the persistence
/// boundary; this in-memory marker must not be treated as the evidence itself.
DeadLettered,
}

impl BapTaskState {
/// Return whether this state is final and must never transition again.
#[must_use]
pub const fn is_terminal(self) -> bool {
matches!(
self,
Self::Succeeded | Self::Failed | Self::Cancelled | Self::Expired | Self::DeadLettered
)
}
}

/// One requested task-lifecycle event.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BapTaskEvent {
/// Admit a newly created task.
Admit,
/// Start an admitted task.
Start,
/// Suspend a running task until approval is available.
WaitForApproval,
/// Suspend a running task until external input is available.
WaitForExternalInput,
/// Suspend a running task at a recoverable checkpoint.
Checkpoint,
/// Resume a normal suspended task into governed execution.
Resume,
/// Suspend a running task because its external outcome requires reconciliation.
RequireReconciliation,
/// Explicitly resolve a reconciliation hold and return the task to governed execution.
ResolveReconciliation,
/// Terminally remove a running or reconciliation-held task from automatic execution.
DeadLetter,
/// Record successful completion after the declared post-condition is verified.
Succeed,
/// Record terminal task failure.
Fail,
/// Record terminal cancellation.
Cancel,
/// Record terminal expiry.
Expire,
}

/// A fail-closed lifecycle transition failure.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BapTaskTransitionError {
/// The requested event is not valid from the current non-terminal state.
InvalidTransition {
/// Current state that rejected the event.
from: BapTaskState,
/// Event that was rejected.
event: BapTaskEvent,
},
/// The lifecycle sequence reached its maximum representable value.
SequenceExhausted,
/// A terminal task cannot be reopened or mutated by lifecycle events.
TerminalState {
/// Final state that rejected all further events.
state: BapTaskState,
},
}

impl std::fmt::Display for BapTaskTransitionError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidTransition { from, event } => {
write!(
formatter,
"BAP task event {event:?} is invalid from state {from:?}"
)
}
Self::SequenceExhausted => {
write!(formatter, "BAP task transition sequence is exhausted")
}
Self::TerminalState { state } => {
write!(formatter, "BAP task state {state:?} is terminal")
}
}
}
}

impl std::error::Error for BapTaskTransitionError {}

/// A fail-closed lifecycle recovery failure.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BapTaskRestoreError {
/// The supplied state and transition sequence cannot arise from this state machine.
InvalidSnapshot {
/// Logical state supplied by the durable recovery boundary.
state: BapTaskState,
/// Last accepted transition sequence supplied by the durable recovery boundary.
transition_sequence: u64,
},
}

impl std::fmt::Display for BapTaskRestoreError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidSnapshot {
state,
transition_sequence,
} => write!(
formatter,
"BAP task snapshot state {state:?} with transition sequence {transition_sequence} is unreachable"
),
}
}
}

impl std::error::Error for BapTaskRestoreError {}

/// Immutable receipt for one accepted in-memory lifecycle transition.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BapTaskTransition {
previous_state: BapTaskState,
current_state: BapTaskState,
sequence: u64,
}

impl BapTaskTransition {
/// Return the state before the accepted transition.
#[must_use]
pub const fn previous_state(self) -> BapTaskState {
self.previous_state
}

/// Return the state after the accepted transition.
#[must_use]
pub const fn current_state(self) -> BapTaskState {
self.current_state
}

/// Return the monotonic transition sequence for this lifecycle instance.
#[must_use]
pub const fn sequence(self) -> u64 {
self.sequence
}
}

/// Deterministic fail-closed BAP task-lifecycle kernel.
///
/// This value is intentionally an in-memory state-transition primitive. A
/// durable repository must persist accepted transitions and impose its own
/// bounded sequence/retention contract before commercial task recovery can be
/// claimed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BapTaskLifecycle {
state: BapTaskState,
transition_sequence: u64,
}

impl Default for BapTaskLifecycle {
fn default() -> Self {
Self::new()
}
}

impl BapTaskLifecycle {
/// Create one lifecycle in the `created` state with no accepted transitions.
#[must_use]
pub const fn new() -> Self {
Self {
state: BapTaskState::Created,
transition_sequence: 0,
}
}

/// Restore a lifecycle state and its last accepted transition sequence.
///
/// Recovery accepts only state/sequence pairs that are reachable through
/// this exact state machine. This prevents corrupt or stale durable metadata
/// from manufacturing an impossible execution state.
pub const fn restore(
state: BapTaskState,
transition_sequence: u64,
) -> Result<Self, BapTaskRestoreError> {
if !reachable_snapshot(state, transition_sequence) {
return Err(BapTaskRestoreError::InvalidSnapshot {
state,
transition_sequence,
});
}
Ok(Self {
state,
transition_sequence,
})
}

/// Return the current logical task state.
#[must_use]
pub const fn state(self) -> BapTaskState {
self.state
}

/// Return the number of accepted lifecycle transitions.
#[must_use]
pub const fn transition_sequence(self) -> u64 {
self.transition_sequence
}

/// Apply one reviewed lifecycle event without granting execution authority.
///
/// Rejected events leave both state and sequence unchanged. Terminal states
/// reject every later event before evaluating any normal transition rule.
/// Reconciliation cannot use the generic `Resume` event: it requires the
/// explicit `ResolveReconciliation` event so ambiguous external outcomes
/// cannot silently re-enter execution.
pub fn apply(
&mut self,
event: BapTaskEvent,
) -> Result<BapTaskTransition, BapTaskTransitionError> {
if self.state.is_terminal() {
return Err(BapTaskTransitionError::TerminalState { state: self.state });
}

let next_state = match (self.state, event) {
(BapTaskState::Created, BapTaskEvent::Admit) => BapTaskState::Admitted,
(BapTaskState::Admitted, BapTaskEvent::Start) => BapTaskState::Running,
(BapTaskState::Running, BapTaskEvent::WaitForApproval) => {
BapTaskState::WaitingForApproval
}
(BapTaskState::Running, BapTaskEvent::WaitForExternalInput) => {
BapTaskState::WaitingForExternalInput
}
(BapTaskState::Running, BapTaskEvent::Checkpoint) => BapTaskState::Checkpointed,
(
BapTaskState::WaitingForApproval
| BapTaskState::WaitingForExternalInput
| BapTaskState::Checkpointed,
BapTaskEvent::Resume,
) => BapTaskState::Running,
(BapTaskState::Running, BapTaskEvent::RequireReconciliation) => {
BapTaskState::ReconciliationRequired
}
(BapTaskState::ReconciliationRequired, BapTaskEvent::ResolveReconciliation) => {
BapTaskState::Running
}
(
BapTaskState::Running | BapTaskState::ReconciliationRequired,
BapTaskEvent::DeadLetter,
) => BapTaskState::DeadLettered,
(BapTaskState::Running, BapTaskEvent::Succeed) => BapTaskState::Succeeded,
(_, BapTaskEvent::Fail) => BapTaskState::Failed,
(_, BapTaskEvent::Cancel) => BapTaskState::Cancelled,
(_, BapTaskEvent::Expire) => BapTaskState::Expired,
(from, event) => {
return Err(BapTaskTransitionError::InvalidTransition { from, event });
}
};

let Some(sequence) = self.transition_sequence.checked_add(1) else {
return Err(BapTaskTransitionError::SequenceExhausted);
Comment thread
seonghobae marked this conversation as resolved.
};
let previous_state = self.state;
self.state = next_state;
self.transition_sequence = sequence;
Ok(BapTaskTransition {
previous_state,
current_state: next_state,
sequence,
})
}
}

const fn reachable_snapshot(state: BapTaskState, transition_sequence: u64) -> bool {
match state {
BapTaskState::Created => transition_sequence == 0,
BapTaskState::Admitted => transition_sequence == 1,
BapTaskState::Running => transition_sequence >= 2 && transition_sequence.is_multiple_of(2),
BapTaskState::WaitingForApproval
| BapTaskState::WaitingForExternalInput
| BapTaskState::Checkpointed
| BapTaskState::ReconciliationRequired => {
transition_sequence >= 3 && !transition_sequence.is_multiple_of(2)
}
BapTaskState::Succeeded => {
transition_sequence >= 3 && !transition_sequence.is_multiple_of(2)
}
BapTaskState::Failed | BapTaskState::Cancelled | BapTaskState::Expired => {
transition_sequence >= 1
}
BapTaskState::DeadLettered => transition_sequence >= 3,
}
}
Loading
Loading