B2 rework: pin the resume wait's clock-step guard; correct the actuator doc
cargo-mutants-style probe C26 (the resume backoff read without its longest wait) survived: the clock-step test called is_waiting directly, not the resume site. An Engine-level test now sets a real half-minute backoff, which holds, and a day-ahead one, which is attempted. docs/host-actuator.md said continuity gives up after three attempts; it is six restarts inside an hour. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b55bb97f46
commit
ef6cfb177f
2 changed files with 40 additions and 2 deletions
|
|
@ -82,8 +82,8 @@ running a script, and a dead `muninn serve` is seen the tick it dies.
|
|||
2. No path on yggdrasil can start, stop or observe a host workload except
|
||||
through the actuator session bound to that host's anchor.
|
||||
3. A host with no attached actuator is a failed observation, which
|
||||
continuity counts and gives up on after three attempts, exactly like a
|
||||
unit that will not start.
|
||||
continuity counts and gives up on after six restarts inside one hour,
|
||||
exactly like a unit that will not start.
|
||||
4. The actuator cannot be told to run a program the recipe did not declare;
|
||||
`allowed_programs` on the host-native runner is enforced on the host.
|
||||
|
||||
|
|
|
|||
|
|
@ -13689,6 +13689,44 @@ mod tests {
|
|||
assert!(!is_waiting(4, 10, 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_resume_backoff_set_by_a_stepped_back_clock_is_due_and_a_real_one_waits() -> Result<()> {
|
||||
let world = EngineFixture::with_workload(Arc::new(StillWorkload))?;
|
||||
let id = transaction_at(&world, DeploymentPhase::Warming)?.transaction_id;
|
||||
let not_before = |world: &EngineFixture| {
|
||||
world
|
||||
.engine
|
||||
.resume_backoff
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&id)
|
||||
.map(|backoff| backoff.not_before_unix_millis)
|
||||
};
|
||||
let set = |world: &EngineFixture, not_before_unix_millis: u64| {
|
||||
world.engine.resume_backoff.lock().unwrap().insert(
|
||||
id.clone(),
|
||||
ResumeBackoff {
|
||||
failures: 3,
|
||||
not_before_unix_millis,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Half a minute ahead is a wait the backoff can set: the step is left alone.
|
||||
let waiting = now_millis()? + 30_000;
|
||||
set(&world, waiting);
|
||||
let _ = world.engine.resume_candidate(&resident(&world)?);
|
||||
assert_eq!(not_before(&world), Some(waiting));
|
||||
|
||||
// A day ahead is a clock that stepped back: the step is attempted, and
|
||||
// whatever it does, it replaces the backoff.
|
||||
let ahead = now_millis()? + 86_400_000;
|
||||
set(&world, ahead);
|
||||
let _ = world.engine.resume_candidate(&resident(&world)?);
|
||||
assert!(not_before(&world).is_none_or(|due| due < ahead));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_persistent_fault_is_reported_once_and_again_only_after_recovery() -> Result<()> {
|
||||
let workload = Arc::new(ChangingErrorWorkload(
|
||||
|
|
|
|||
Loading…
Reference in a new issue