mind-self-run: cut the orphan sweep (the mind admits one Self run in force; launch closes a dead one); fix splice; closure tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
551a0340bf
commit
3c258bd9db
3 changed files with 34 additions and 47 deletions
|
|
@ -30,11 +30,9 @@
|
|||
//!
|
||||
//! The actuator admits only closures of Self runs, nothing else. Every exit of
|
||||
//! `carry` that leaves her run open resolves it Recorded exactly once (the
|
||||
//! child's result, or that it gave none, in the reason), and a run found open at
|
||||
//! the start besides the one carried is an orphan of a process that ended
|
||||
//! without closing it: one Self unit per Mind runs at a time (the systemd
|
||||
//! template instance), so the older runs are closed first. It reads neither the
|
||||
//! brake nor the environment and stops no unit: the gate and the brake hold own those. It never echoes an argument, an endpoint, a
|
||||
//! child's result, or that it gave none, in the reason). It reads neither the
|
||||
//! brake nor the environment and stops no unit: the gate and the brake hold own those.
|
||||
//! It never echoes an argument, an endpoint, a
|
||||
//! connector's refusal text or a stream line. Stream-json is Claude Code's wire, a
|
||||
//! foreign owner's JSON: it is parsed into `serde_json::Value` here and nothing
|
||||
//! parsed is stored.
|
||||
|
|
@ -288,23 +286,13 @@ impl fmt::Display for SelfRunOutcome {
|
|||
}
|
||||
}
|
||||
|
||||
/// Why a run found open beside the carried one was closed.
|
||||
const ORPHANED: &str = "orphaned: the run's process ended without closing it";
|
||||
|
||||
/// Her open Self run and its budget, read through the launch primitive's holder
|
||||
/// reader. The newest in force is the one this unit carries; any older one is an
|
||||
/// orphan (its process ended without closing it, and one Self unit per Mind runs
|
||||
/// at a time), closed here before anything is claimed. None when she has none.
|
||||
fn her_run(mind: &dyn MindPort, clock: &dyn Clock, out: &mut dyn Write) -> Result<Option<(PipelineRef, String)>> {
|
||||
/// Her one open Self run and its budget, read through the launch primitive's
|
||||
/// holder reader. Anything but exactly one run is none: the mind admits at most
|
||||
/// one Self run of hers in force, and the launch that opens the next closes a
|
||||
/// dead one.
|
||||
fn her_run(mind: &dyn MindPort) -> Result<Option<(PipelineRef, String)>> {
|
||||
let mut held = holders(mind, mind.instance(), RunTurn::SelfRun)?;
|
||||
held.sort_by(|a, b| (&a.admission.admitted_at, &a.id.id.0).cmp(&(&b.admission.admitted_at, &b.id.id.0)));
|
||||
held.dedup_by(|a, b| a.id == b.id);
|
||||
let Some(run) = held.pop() else { return Ok(None) };
|
||||
for orphan in held {
|
||||
admit_closure(mind, clock, &orphan.id, ResolutionOutcome::Recorded { reason: ORPHANED.into() }, "An earlier Self run's process ended without closing it; the next start closed it.")?;
|
||||
let _ = writeln!(out, "closed an orphaned run");
|
||||
}
|
||||
let one = Selection { keys: Some(vec![run.id.id.0.clone()]), projection: "document".into(), ..Selection::default() };
|
||||
let (Some(run), None) = (held.pop(), held.pop()) else { return Ok(None) };
|
||||
let PipelinePageItems::Documents(views) = mind.query(&one)?.items else { bail!("the mind answered headers to a document selection") };
|
||||
match views.into_iter().next().map(|view| view.document) {
|
||||
Some(PipelineDocument::Run(document)) => Ok(Some((run.id, document.budget_usd.0))),
|
||||
|
|
@ -421,7 +409,7 @@ fn admit_closure(mind: &dyn MindPort, clock: &dyn Clock, run: &PipelineRef, outc
|
|||
|
||||
/// Resolves `run` Recorded if it is still in force: the child may have closed it
|
||||
/// itself in its flush, and a refused lease was closed already. One try; a mind
|
||||
/// that cannot be reached leaves it to the next start's orphan sweep.
|
||||
/// that cannot be reached leaves it open: the launch that opens the next run closes it.
|
||||
fn close_if_open(mind: &dyn MindPort, clock: &dyn Clock, run: &PipelineRef, ran: &Result<SelfRunOutcome>, spent: &Spent) -> Result<()> {
|
||||
if !holders(mind, mind.instance(), RunTurn::SelfRun)?.iter().any(|held| held.id == *run) {
|
||||
return Ok(());
|
||||
|
|
@ -459,14 +447,14 @@ fn withdraw_misconfigured(mind: &dyn MindPort, clock: &dyn Clock, run: &Pipeline
|
|||
/// before `carry` returns: the child's result (or that it gave none) in the
|
||||
/// reason. A resolve that fails is logged and tried no more: the outcome (or the
|
||||
/// original error) stands, a failed close of a run that ended normally is an
|
||||
/// error, and the next start closes the run as an orphan.
|
||||
/// error, and the launch that opens the next run closes the run left open.
|
||||
pub fn carry(mind: &dyn MindPort, clock: &dyn Clock, lender: &dyn Lender, spawner: &dyn Spawner, run_cap_s: u64, out: &mut dyn Write) -> Result<SelfRunOutcome> {
|
||||
let Some((run, budget)) = her_run(mind, clock, out)? else { return Ok(SelfRunOutcome::NoRun) };
|
||||
let Some((run, budget)) = her_run(mind)? else { return Ok(SelfRunOutcome::NoRun) };
|
||||
let mut spent = Spent::default();
|
||||
let ran = lend_and_drive(mind, clock, lender, spawner, run_cap_s, out, (&run, &budget), &mut spent);
|
||||
let closed = close_if_open(mind, clock, &run, &ran, &spent);
|
||||
if closed.is_err() {
|
||||
let _ = writeln!(out, "the run could not be closed: the next start closes it as an orphan");
|
||||
let _ = writeln!(out, "the run could not be closed: the next launch closes it");
|
||||
}
|
||||
match (ran, closed) {
|
||||
(Err(error), _) => Err(error),
|
||||
|
|
@ -542,7 +530,6 @@ fn write_result(line: &str, out: &mut dyn Write) -> std::io::Result<()> {
|
|||
}
|
||||
writeln!(out, "{}", describe_result(&event, RESULT_TEXT_LIMIT))
|
||||
}
|
||||
}
|
||||
|
||||
/// The loop: facts in, one clock read per fact, actions out.
|
||||
fn drive(mind: &dyn MindPort, clock: &dyn Clock, mut child: Box<dyn Child>, run: &PipelineRef, spent: &mut Spent, out: &mut dyn Write) -> Result<SelfRunOutcome> {
|
||||
|
|
|
|||
|
|
@ -269,13 +269,14 @@ impl Drop for FakeLease {
|
|||
struct Seen<'a> {
|
||||
inner: &'a TestMind,
|
||||
instance: Slug,
|
||||
doubled: bool,
|
||||
admit_fails: bool,
|
||||
admits: Cell<usize>,
|
||||
}
|
||||
|
||||
impl<'a> Seen<'a> {
|
||||
fn of(inner: &'a TestMind) -> Self {
|
||||
Self { inner, instance: inner.instance().clone(), admit_fails: false, admits: Cell::new(0) }
|
||||
Self { inner, instance: inner.instance().clone(), doubled: false, admit_fails: false, admits: Cell::new(0) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +286,14 @@ impl MindPort for Seen<'_> {
|
|||
}
|
||||
|
||||
fn query(&self, selection: &Selection) -> Result<PipelineSelectionPage> {
|
||||
self.inner.query(selection)
|
||||
let mut page = self.inner.query(selection)?;
|
||||
if self.doubled
|
||||
&& let PipelinePageItems::Headers(items) = &mut page.items
|
||||
{
|
||||
let again = items.clone();
|
||||
items.extend(again);
|
||||
}
|
||||
Ok(page)
|
||||
}
|
||||
|
||||
fn admit(&self, agent: &str, session: &str, documents: Vec<PipelineDocument>) -> Result<PipelineAdmissionOutcome> {
|
||||
|
|
@ -402,7 +410,7 @@ fn flushes(ran: &Ran) -> usize {
|
|||
// ---- who is carried -----------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn without_an_open_self_run_of_hers_it_is_no_run_and_no_child_is_spawned() {
|
||||
fn without_exactly_one_open_self_run_of_hers_it_is_no_run_and_no_child_is_spawned() {
|
||||
let cases: Vec<(&str, Seed)> = vec![
|
||||
("no run at all", Box::new(|_| {})),
|
||||
("a Self run the operator operates", Box::new(|mind| mind.committed(vec![run("op", RunTurn::SelfRun, RunOperator::Operator, &[])]))),
|
||||
|
|
@ -428,6 +436,13 @@ fn without_an_open_self_run_of_hers_it_is_no_run_and_no_child_is_spawned() {
|
|||
let mut other = Seen::of(&mind);
|
||||
other.instance = Slug("another".into());
|
||||
assert_eq!(carry_with(&other, vec![], Some(0), false).outcome.unwrap(), SelfRunOutcome::NoRun);
|
||||
|
||||
// Two open runs are not one: nothing is spawned for either.
|
||||
let mut two = Seen::of(&mind);
|
||||
two.doubled = true;
|
||||
let ran = carry_with(&two, vec![], Some(0), false);
|
||||
assert_eq!(ran.outcome.unwrap(), SelfRunOutcome::NoRun);
|
||||
assert!(ran.spawns.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -956,9 +971,9 @@ fn the_clock_is_read_once_per_event_so_a_step_of_the_wall_clock_cannot_split_a_p
|
|||
let outcome = carry(&Seen::of(&mind), &Stepped(clock.clone()), &FakeLender::new(Answer::Grant), &fake, TEST_CAP_S, &mut Vec::new()).unwrap();
|
||||
let events = log.borrow().clone();
|
||||
let reads = events.iter().filter(|event| matches!(event, Event::Asked(_))).count() as u32;
|
||||
// One read for the lease's expiry, one before the first wait, then one per fact heard:
|
||||
// One read for the lease's expiry, one before the first wait, then one per fact heard, and one for the closure:
|
||||
// never a second read inside a pass.
|
||||
assert_eq!(clock.1.get(), 2 + reads, "{events:?}");
|
||||
assert_eq!(clock.1.get(), 3 + reads, "{events:?}");
|
||||
// The flush was written on the first request at time 0; the very next pass sees the stepped clock.
|
||||
// The flush pass itself has no interrupt, whatever the step: the interrupt is on a later event.
|
||||
let flush_at = events.iter().position(|event| matches!(event, Event::Wrote(line) if line.contains("context ceiling"))).unwrap();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
//! Every exit of `carry` resolves her run Recorded exactly once, and a run left
|
||||
//! open by a process that died is closed at the next start.
|
||||
//! Every exit of `carry` resolves her run Recorded exactly once.
|
||||
|
||||
use eureka_pipeline::ResolutionOutcome;
|
||||
|
||||
|
|
@ -101,20 +100,6 @@ fn a_run_the_child_closed_itself_is_not_closed_again() {
|
|||
assert_eq!(seen.admits.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_run_still_open_at_the_next_start_is_an_orphan_and_is_closed_before_the_newer_one_is_carried() {
|
||||
let mind = TestMind::seeded();
|
||||
mind.committed(vec![self_run("selfrun-old", "5")]);
|
||||
mind.committed(vec![self_run("selfrun-a", "5")]);
|
||||
let ran = carry_with(&Seen::of(&mind), vec![Out(result_line())], Some(0), false);
|
||||
assert!(ran.outcome.is_ok());
|
||||
assert_eq!(ran.spawns.len(), 1);
|
||||
assert!(ran.spawns[0].contains(&SESSION_OF_RUN_A.to_string()), "the newer run is the one carried");
|
||||
assert_eq!(reason_of(&mind, "selfrun-old"), "orphaned: the run's process ended without closing it");
|
||||
assert!(reason_of(&mind, "selfrun-a").contains("subtype=success"));
|
||||
assert_eq!(mind.ids(K::Resolution).len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_resolve_that_fails_is_logged_and_tried_once() {
|
||||
let mind = with_run();
|
||||
|
|
|
|||
Loading…
Reference in a new issue