-
Notifications
You must be signed in to change notification settings - Fork 0
feat(bap): define resumable task lifecycle kernel #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+886
−1
Merged
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 eeed48c
test(core): format BAP lifecycle regression
seonghobae 4cea204
feat(bap): add lifecycle crate manifest
seonghobae 681a17b
feat(bap): implement deterministic task lifecycle kernel
seonghobae eca7fc5
test(bap): exercise lifecycle transitions
seonghobae 3dffbfd
feat(bap): register lifecycle crate
seonghobae 5864189
test(bap): move lifecycle contract to owning crate
seonghobae beabc15
test(repo): register reusable BAP kernel
seonghobae 606286f
build: lock the BAP workspace member
seonghobae 28a53fc
fix(bap): satisfy lifecycle default contract
seonghobae 91ef8c1
test(bap): require resumable overflow-safe lifecycle recovery
seonghobae 34b0fbe
feat(bap): support bounded lifecycle recovery
seonghobae 0915eed
test(bap): reject impossible lifecycle recovery snapshots
seonghobae 4be8376
fix(bap): validate recovered lifecycle snapshots
seonghobae d0111fd
test(bap): require standard lifecycle error contracts
seonghobae 0e7ee30
style(bap): apply canonical lifecycle test formatting
seonghobae 5d6b8fd
fix(bap): implement standard lifecycle errors
seonghobae c3b6e1a
style(bap): apply canonical rustfmt layout
seonghobae 80de947
test(bap): require explicit reconciliation and dead-letter states
seonghobae dff029a
feat(bap): add fail-closed reconciliation lifecycle states
seonghobae 133825d
test(bap): cover direct dead-letter admission bounds
seonghobae 416a5e5
test(bap): cover reconciliation and dead-letter recovery snapshots
seonghobae 7d3bb60
style(bap): apply canonical rustfmt to reconciliation states
seonghobae 1e900ae
test(bap): require lifecycle architecture decision
seonghobae 852b362
docs(bap): record task lifecycle state authority
seonghobae 4b61fce
docs(bap): index task lifecycle ADR
seonghobae f2649a9
docs(bap): register lifecycle ADR in canonical index
seonghobae 2feb778
test(bap): use typed lifecycle setup states
seonghobae b88e2fb
docs(adr): define post-integration provenance handling
seonghobae d71b05f
docs: synchronize ADR 0016 provenance lifecycle
seonghobae 85cc477
docs: repair direct socket design link
seonghobae e41d3be
Merge branch 'main' into feat/bap-task-lifecycle
opencode-agent[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| }; | ||
| 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, | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.