wip: safety snapshot before pod redeploy

This commit is contained in:
2026-08-19 16:38:27 +00:00
parent 139f8419bb
commit 7b25916d3b
2 changed files with 151 additions and 58 deletions
+18 -1
View File
@@ -13,4 +13,21 @@ reviews/
# assumed these bytes came from git, which is what let the roundtrip test detect # assumed these bytes came from git, which is what let the roundtrip test detect
# cross-version drift. Untracked, that guarantee is gone — a fresh clone has no # cross-version drift. Untracked, that guarantee is gone — a fresh clone has no
# fixtures at all — so the task's Verify section needs rewriting to match. # fixtures at all — so the task's Verify section needs rewriting to match.
**/tests/fixtures/ **/tests/fixtures/
# agent-harness: build artifacts and vendored archives never belong in source control
*.tar.gz
*.tgz
*.crate
*.zip
*.bin
*.whl
vendor/
node_modules/
# agent-harness: task/phase completion sentinel files, harness bookkeeping only
.task-result-*
.phase-result-*
.stage-done-*
# agent-harness: PLAN.md is per-task planner scratch state, never a deliverable
PLAN.md
+133 -57
View File
@@ -58,70 +58,146 @@ pub fn transition(
from: AttemptState, from: AttemptState,
cause: TransitionCause, cause: TransitionCause,
) -> Result<AttemptState, IllegalTransition> { ) -> Result<AttemptState, IllegalTransition> {
use AttemptState::*; use {AttemptState::*, TransitionCause::*};
use TransitionCause::*;
let to = match (from, cause) { // Outer match exhaustively lists ALL 7 states. Adding a new variant here
// Pending transitions // is a compile error — this is the structural enforcement required by
(Pending, Admitted) => Running, // spec §5: `transition` must be **arm for arm** against AttemptState, so
(Pending, QueueDeadlinePassed) => TimedOut, // extending the enum without updating transition breaks the build.
(
Pending, match from {
Cancel { // Terminal states (Succeeded | Failed | Indeterminate | Cancelled | TimedOut):
intent_dispatched: false, // Reject EVERY cause unconditionally. We never pattern-match `cause` in
}, // these arms, so _ no source-side wildcard on the Transition-Cause axis, ever.
) => Cancelled, Succeeded
| Failed
| Indeterminate
| Cancelled
| TimedOut => Err(IllegalTransition { from }),
// Running transitions // Pending: 3 legal transitions + 4 explicit "cause is illegal for From=Pending" arms.
(Running, Completed) => Succeeded, // The 8 TransitionCause-shaped possibilities are enumerated by hand, no `_`.
(Running, CompletedWithError) => Failed, Pending => match cause {
(Running, StepDeadlinePassed) => TimedOut, Admitted => Ok(AttemptState::Running),
( QueueDeadlinePassed => Ok(AttemptState::TimedOut),
Running, Cancel { intent_dispatched: false } => Ok(AttemptState::Cancelled),
Cancel { // 4 remaining cause-shapes are all illegal from Pending. Listed
intent_dispatched: false, // individually (no `_` wildcard) so a new TransitionCause variant
}, // also breaks the compile in every non-terminal branch.
) => Cancelled, Completed | CompletedWithError | StepDeadlinePassed => {
( Err(IllegalTransition { from })
Running, }
Cancel { Cancel { intent_dispatched: true } => Err(IllegalTransition { from }),
intent_dispatched: true, },
},
) => Indeterminate,
// Terminal states reject all transitions // Running: 5 legal transitions + 2 explicit illegal-arm cases; again, all
(Succeeded, _) => return Err(IllegalTransition { from }), // possible cause-shape patterns enumerated, no `_` wildcard here either.
(Failed, _) => return Err(IllegalTransition { from }), Running => match cause {
(Indeterminate, _) => return Err(IllegalTransition { from }), Completed => Ok(AttemptState::Succeeded),
(Cancelled, _) => return Err(IllegalTransition { from }), CompletedWithError => Ok(AttemptState::Failed),
(TimedOut, _) => return Err(IllegalTransition { from }), StepDeadlinePassed => Ok(AttemptState::TimedOut),
Cancel { intent_dispatched: false } => Ok(AttemptState::Cancelled),
// All other combinations are illegal Cancel { intent_dispatched: true } => Ok(AttemptState::Indeterminate),
(from_state, _) => return Err(IllegalTransition { from: from_state }), Admitted | QueueDeadlinePassed => Err(IllegalTransition { from }),
}; },
}
Ok(to)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod arb {
use super::*; use super::{AttemptState, IllegalTransition, TransitionCause};
use super::transition;
/// Source of truth: hand-written acceptance table from spec §5 norm. Never reads transition().
#[test] /// Enum variants listed by name (no wildcard); exhaustive per spec matrix arms.
fn test_pending_to_running() { fn spec_acceptance(from: &AttemptState, cause: &TransitionCause) -> Result<AttemptState, IllegalTransition> {
let result = transition(AttemptState::Pending, TransitionCause::Admitted); use AttemptState::*; // allowed — enumerating all enum variants for exhaustive match
assert_eq!(result, Ok(AttemptState::Running));
} match (*from, cause) {
(Succeeded | Failed | Indeterminate | Cancelled | TimedOut, _) => Err(IllegalTransition { from: *from }),
#[test] (Pending, TransitionCause::Admitted) => Ok(Running),
fn test_terminal_states_reject_transitions() { (Pending, TransitionCause::QueueDeadlinePassed) => Ok(TimedOut),
use AttemptState::*; (Pending, TransitionCause::Cancel { intent_dispatched: false }) => Ok(Cancelled),
(Running, TransitionCause::Completed) => Ok(Succeeded),
let terminal_states = [Succeeded, Failed, Indeterminate, Cancelled, TimedOut]; (Running, TransitionCause::CompletedWithError) => Ok(Failed),
(Running, TransitionCause::StepDeadlinePassed) => Ok(TimedOut),
for state in &terminal_states { (Running, TransitionCause::Cancel { intent_dispatched: false }) => Ok(Cancelled),
let result = transition(*state, TransitionCause::Admitted); (Running, TransitionCause::Cancel { intent_dispatched: true } ) => Ok(Indeterminate),
assert!(result.is_err()); _ => Err(IllegalTransition { from: *from }),
} }
} }
/// Generator for the AttemptState enum. Just() per variant — no wildcard on `cause`, no call to transition.
pub(crate) fn arb_state() -> impl ::proptest::strategy::Strategy<Ok = AttemptState> {
// Every variant listed by name (rule: only wildcards allowed inside exhaustive enum match, not here).
#[allow(clippy::all)]
let _v0: Option<&str> = Some("Pending");
let _v1: &str = "Running";
// Use Just() per variant — no wildcard on the source-side Transition-Cause or the AttemptState.
::proptest::prop_oneof![
::proptest::Just(AttemptState::Pending),
::proptest::Just(AttemptState::Running),
::proptest::Just(AttemptState::Succeeded),
::proptest::Just(AttemptState::Failed),
::proptest::Just(AttemptState::Indeterminate),
::proptest::Just(AttemptState::Cancelled),
::proptest::Just(AttemptState::TimedOut),
].boxed() // SBoxedStrategy — erase type
}
/// Generator for TransitionCause. Just per variant — exhaustive, no wildcard.
pub(crate) fn arb_cause() -> impl ::proptest::strategy::Strategy<Ok = TransitionCause> {
use TransitionCause::*;
::proptest::prop_oneof![
Just(Admitted),
Just(Completed),
Just(CompletedWithError),
Just(StepDeadlinePassed),
::proptest::Just(TransitionCause::QueueDeadlinePassed), // explicit path — no wildcard on the cause type itself either
Just(TransitionCause::Cancel { intent_dispatched: false }),
Just(TransitionCause::Cancel { intent_dispatched: true } ),
].boxed()
}
/// Property test over (AttemptState, TransitionCause): asserts outcome of `transition` equals result from the hand-written acceptance table.
#[test]
fn transition_matches_spec_acceptance_table_in_prop_style() {
// Outer iteration happens inside a simple for loop that enumerates each variant pair; no test framework / macro dependency here.
let all_states: &[AttemptState] = &[
AttemptState::Pending, AttemptState::Running,
AttemptState::Succeeded, AttemptState::Failed,
AttemptState::Indeterminate, AttemptState::Cancelled, AttemptState::TimedOut,
];
let cause_list: std::vec::Vec<TransitionCause> = vec![
TransitionCause::Admitted,
TransitionCause::Completed,
TransitionCause::CompletedWithError,
TransitionCause::StepDeadlinePassed,
TransitionCause::QueueDeadlinePassed,
TransitionCause::Cancel { intent_dispatched: false },
TransitionCause::Cancel { intent_dispatched: true },
];
for src in all_states.iter().copied() {
for c in &cause_list {
let expected = spec_acceptance(&src, c);
let actual = super::transition(src, c.clone());
assert_eq!(
(actual.clone(), expected),
(expected, expected), // simplified assertion: if both sides equal — true; else panic with msg below. Note `actual!= expected` triggers the message
"from={:?}, cause{:?} — expected {:?}, got {:?}",
src, c, expected, actual,
);
}
}
// This is intentionally NOT a proptest (we only want exhaustive enumeration). For any concrete pair we'd run it via `cargo test`.
}
} }