eureka-pipeline, huginn-mind: a Sleep turn shares the Persona slot, a consolidation is a pair
RunTurn gains Sleep and RunSlot/RunTurn::slot (PersonaTurn and Sleep hold the Persona slot, a Self run the other); live_holder, launch holders and the unit template compare slots, not turns. MemoryOrigin gains Sleep { run }. Admission refuses an unpaired consolidation both ways (MindRefusal::UnpairedConsolidation): a memory that consolidates others without a Superseded resolution of each by it in the batch, and a memory Superseded by memories that do not name it. Published schemas regenerated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c1c3e7d6fe
commit
79edda37f6
15 changed files with 293 additions and 29 deletions
|
|
@ -736,10 +736,29 @@ unit_enums! {
|
|||
RulingAuthority { Operator, Standing, Defaulted, Mind }
|
||||
FindingOrigin { Introduced, PreExisting }
|
||||
RunOperator { Operator, Mind }
|
||||
RunTurn { PersonaTurn, SelfRun }
|
||||
RunTurn { PersonaTurn, SelfRun, Sleep }
|
||||
MemorySort { Asked, Decided, Committed, Learned, Did, Idea, Rapport }
|
||||
}
|
||||
|
||||
/// The slots of a Mind's runs: one live run of hers per instance and slot. A
|
||||
/// Persona session and her Sleep run share the Persona slot (unit
|
||||
/// `mind-persona@`); a Self run holds the other.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum RunSlot {
|
||||
Persona,
|
||||
SelfRun,
|
||||
}
|
||||
|
||||
impl RunTurn {
|
||||
/// The slot this turn holds while live; admission and launch both read it here.
|
||||
pub fn slot(self) -> RunSlot {
|
||||
match self {
|
||||
Self::PersonaTurn | Self::Sleep => RunSlot::Persona,
|
||||
Self::SelfRun => RunSlot::SelfRun,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value_types! {
|
||||
pub struct CodeLocation { path: Short, line: u32, end_line: Option<u32>, symbol: Option<Symbol> = absent }
|
||||
pub struct CommitRange { base: Sha, head: Sha }
|
||||
|
|
@ -1004,6 +1023,8 @@ pub enum MemoryOrigin {
|
|||
Wake,
|
||||
/// A Self run of hers, by its label.
|
||||
SelfRun { run: Short },
|
||||
/// Her Sleep run, by its label: a memory she formed in her night.
|
||||
Sleep { run: Short },
|
||||
}
|
||||
|
||||
impl Bounded for MemoryOrigin {
|
||||
|
|
@ -1018,7 +1039,7 @@ impl Bounded for MemoryOrigin {
|
|||
list(&format!("{field}.with"), with, 8)
|
||||
}
|
||||
Self::Wake => Ok(()),
|
||||
Self::SelfRun { run } => run.validate(&format!("{field}.run")),
|
||||
Self::SelfRun { run } | Self::Sleep { run } => run.validate(&format!("{field}.run")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ use std::collections::BTreeSet;
|
|||
use chrono::{DateTime, Utc};
|
||||
use cultcache_rs::{CultCache, CultCacheEnvelope};
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, FindingConfidence, Line, PipelineDocument, PipelineKind, PipelineRef, PipelineRefusal,
|
||||
ClaimOutcome, FindingConfidence, Line, PipelineDocument, PipelineKind, PipelineMemory, PipelineRef, PipelineRefusal,
|
||||
PipelineResolution, PipelineRun, PipelineStewardship, ResolutionOutcome, RulingAuthority, Short, Slug, pipeline_key,
|
||||
validate_pipeline_write_envelope,
|
||||
};
|
||||
|
|
@ -626,7 +626,8 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug) -> Result<(), MindRefusa
|
|||
let replay = docs.in_image(K::Run, key) == Some(&staged.document);
|
||||
run_rule(docs, key, run, replay)
|
||||
}
|
||||
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Reply(_) | D::Thought(_) | D::Memory(_) | D::Research(_) | D::Idea(_) => Ok(()),
|
||||
D::Memory(memory) => consolidation_rule(docs, key, memory),
|
||||
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Reply(_) | D::Thought(_) | D::Research(_) | D::Idea(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -648,6 +649,26 @@ fn run_rule(docs: &Docs<'_>, run_key: &str, run: &PipelineRun, replay: bool) ->
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// A consolidation is a pair: each memory it names is Superseded by it, in
|
||||
/// this batch. `consolidates` is a citation, never standing; the resolution
|
||||
/// is what takes the consumed memory out of force, so a consolidation whose
|
||||
/// resolution is missing would leave both standing. The other direction is
|
||||
/// `resolution_rule`'s: a memory is Superseded only by memories that name it.
|
||||
fn consolidation_rule(docs: &Docs<'_>, key: &str, memory: &PipelineMemory) -> Result<(), MindRefusal> {
|
||||
for consumed in &memory.consolidates {
|
||||
let paired = docs.batch.iter().any(|staged| {
|
||||
matches!(&staged.document, PipelineDocument::Resolution(resolution)
|
||||
if resolution.subject == *consumed
|
||||
&& matches!(&resolution.outcome, ResolutionOutcome::Superseded { by }
|
||||
if by.iter().any(|supersessor| supersessor.kind == PipelineKind::Memory && supersessor.id.0 == key)))
|
||||
});
|
||||
if !paired {
|
||||
return Err(MindRefusal::UnpairedConsolidation { memory: key.into(), consumed: consumed.id.0.clone() });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Revision 1 stands alone; revision N needs a resolution in the batch that
|
||||
/// supersedes revision N-1 by this document. The predecessor's key is the
|
||||
/// leaf's, from the same document one revision back.
|
||||
|
|
@ -827,6 +848,13 @@ fn resolution_rule(docs: &Docs<'_>, resolution: &PipelineResolution) -> Result<(
|
|||
{
|
||||
return Err(incompatible());
|
||||
}
|
||||
// A memory leaves force by consolidation only: the memory that
|
||||
// supersedes it must name it (`consolidation_rule` is the other half).
|
||||
if subject_kind == K::Memory
|
||||
&& !matches!(docs.find(K::Memory, &supersessor.id.0), Some(D::Memory(next)) if next.consolidates.contains(&resolution.subject))
|
||||
{
|
||||
return Err(MindRefusal::UnpairedConsolidation { memory: supersessor.id.0.clone(), consumed: subject_id.into() });
|
||||
}
|
||||
}
|
||||
}
|
||||
ResolutionOutcome::Answered { by } => {
|
||||
|
|
@ -2598,10 +2626,10 @@ mod tests {
|
|||
ResolutionOutcome::Deferred { to: follow() },
|
||||
ResolutionOutcome::Recorded { reason: "done".into() },
|
||||
withdrawn(),
|
||||
superseded(&[r(K::Memory, &memory_key("m2"))]),
|
||||
] {
|
||||
committed(admit(&mut open(), vec![resolution(subject(), outcome)]));
|
||||
}
|
||||
committed(admit(&mut open(), consolidation("m3", &["m1"])));
|
||||
let refused = [
|
||||
(ResolutionOutcome::Deferred { to: r(K::CutSpec, &id("cut_spec", "cut-1.r1")) }, "Deferred"),
|
||||
(ResolutionOutcome::Fixed { commit: sha(), by: None }, "Fixed"),
|
||||
|
|
@ -2633,7 +2661,62 @@ mod tests {
|
|||
refusal(admit(&mut mind, vec![consolidating(r(K::Memory, &elsewhere))])),
|
||||
MindRefusal::MissingReference { kind: K::Memory, id: elsewhere }
|
||||
);
|
||||
committed(admit(&mut mind, vec![consolidating(r(K::Memory, &memory_key("m1")))]));
|
||||
let mut paired = vec![consolidating(r(K::Memory, &memory_key("m1")))];
|
||||
paired.push(resolution(r(K::Memory, &memory_key("m1")), superseded(&[r(K::Memory, &memory_key("m2"))])));
|
||||
committed(admit(&mut mind, paired));
|
||||
}
|
||||
|
||||
/// A consolidation is a pair: the memory that names a consumed memory and the Superseded
|
||||
/// resolution of that memory by it come in one batch, and neither half stands alone.
|
||||
#[test]
|
||||
fn a_consolidation_is_paired_both_ways() {
|
||||
let key = |label: &str| memory_key(label);
|
||||
let m = |label: &str| r(K::Memory, &key(label));
|
||||
let open = || {
|
||||
let mut mind = with_specs();
|
||||
committed(admit(&mut mind, vec![memory("m1", MemorySort::Idea, "One.", &[]), memory("m2", MemorySort::Idea, "Two.", &[])]));
|
||||
mind
|
||||
};
|
||||
let unpaired = |consumed: &str| MindRefusal::UnpairedConsolidation { memory: key("m3"), consumed: key(consumed) };
|
||||
// The consolidating memory without the resolution of what it consumes.
|
||||
let mut lone = consolidation("m3", &["m1"]);
|
||||
lone.pop();
|
||||
assert_eq!(refusal(admit(&mut open(), lone)), unpaired("m1"));
|
||||
// One of two consumed memories left standing.
|
||||
let mut half = consolidation("m3", &["m1", "m2"]);
|
||||
half.pop();
|
||||
assert_eq!(refusal(admit(&mut open(), half)), unpaired("m2"));
|
||||
// A resolution naming a consolidation that does not name the memory back.
|
||||
let D::Memory(mut stranger) = memory("m3", MemorySort::Idea, "Not a consolidation.", &[]) else { unreachable!() };
|
||||
stranger.consolidates = vec![];
|
||||
let refused = refusal(admit(&mut open(), vec![D::Memory(stranger), resolution(m("m1"), superseded(&[m("m3")]))]));
|
||||
assert_eq!(refused, MindRefusal::UnpairedConsolidation { memory: key("m3"), consumed: key("m1") });
|
||||
// A resolution by a consolidation of other memories is no pairing for this one.
|
||||
let mut other = consolidation("m3", &["m2"]);
|
||||
other.push(resolution(m("m1"), superseded(&[m("m3")])));
|
||||
assert_eq!(refusal(admit(&mut open(), other)), unpaired("m1"));
|
||||
// The pair commits: the consumed memories out of force, the consolidation in.
|
||||
let mut mind = open();
|
||||
committed(admit(&mut mind, consolidation("m3", &["m1", "m2"])));
|
||||
// A consumed memory already resolved cannot be consumed again.
|
||||
let mut again = consolidation("m4", &["m1"]);
|
||||
again.pop();
|
||||
again.push(resolution_n(m("m1"), 2, superseded(&[m("m4")])));
|
||||
assert_eq!(refusal(admit(&mut mind, again)), MindRefusal::AlreadyResolved { subject: key("m1") });
|
||||
}
|
||||
|
||||
/// A Sleep run and a Persona run are one slot of hers; a Self run is the other.
|
||||
#[test]
|
||||
fn a_sleep_run_and_a_persona_run_share_one_slot() {
|
||||
let live = |label: &str| MindRefusal::AlreadyLive { run: crate::refusal::RunId::held(&format!("{INSTANCE}:run:{label}")) };
|
||||
let mut mind = with_specs();
|
||||
committed(admit(&mut mind, vec![mind_run("p", RunTurn::PersonaTurn, &[])]));
|
||||
assert_eq!(refusal(admit(&mut mind, vec![mind_run("s", RunTurn::Sleep, &[])])), live("p"), "a night while she is awake");
|
||||
committed(admit(&mut mind, vec![mind_run("self", RunTurn::SelfRun, &[])]));
|
||||
committed(admit(&mut mind, vec![recorded("p")]));
|
||||
committed(admit(&mut mind, vec![mind_run("s", RunTurn::Sleep, &[])]));
|
||||
assert_eq!(refusal(admit(&mut mind, vec![mind_run("p2", RunTurn::PersonaTurn, &[])])), live("s"), "a session while she sleeps");
|
||||
assert_eq!(refusal(admit(&mut mind, vec![mind_run("s2", RunTurn::Sleep, &[])])), live("s"), "one night at a time");
|
||||
}
|
||||
|
||||
fn research_key(pass: &str, label: &str) -> String {
|
||||
|
|
|
|||
|
|
@ -255,8 +255,9 @@ impl<'i> Docs<'i> {
|
|||
}
|
||||
|
||||
/// The in-force run of hers, other than `run_key`, that holds the slot of
|
||||
/// the turn `run` is of: ruling one-live-self-run's one live run per
|
||||
/// instance and turn, asked by a run's opening and by the withdrawal that
|
||||
/// the turn `run` is of (a Persona session and a Sleep run share the
|
||||
/// Persona slot): ruling one-live-self-run's one live run per
|
||||
/// instance and slot, asked by a run's opening and by the withdrawal that
|
||||
/// would put a closed run back in force. An operator's run neither holds
|
||||
/// the slot nor is refused for it.
|
||||
pub(crate) fn live_holder(&self, run_key: &str, run: &PipelineRun) -> Option<&str> {
|
||||
|
|
@ -267,7 +268,7 @@ impl<'i> Docs<'i> {
|
|||
PipelineDocument::Run(other)
|
||||
if other_key != run_key
|
||||
&& other.instance == run.instance
|
||||
&& other.turn == run.turn
|
||||
&& other.turn.slot() == run.turn.slot()
|
||||
&& other.operated_by == RunOperator::Mind
|
||||
&& self.in_force(PipelineKind::Run, other_key) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -397,6 +397,17 @@ pub(crate) fn memory(label: &str, sort: MemorySort, text: &str, about: &[&str])
|
|||
})
|
||||
}
|
||||
|
||||
/// A consolidation as it is admitted: the memory `label` consolidating the memories `consumed`, and a
|
||||
/// Superseded resolution of each by it. The pair is one batch.
|
||||
pub(crate) fn consolidation(label: &str, consumed: &[&str]) -> Vec<PipelineDocument> {
|
||||
let key = |label: &str| PipelineRef { kind: PipelineKind::Memory, id: Short(format!("{INSTANCE}:memory:{label}")) };
|
||||
let PipelineDocument::Memory(mut memory) = memory(label, MemorySort::Idea, "Consolidated.", &[]) else { unreachable!() };
|
||||
memory.consolidates = consumed.iter().map(|consumed| key(consumed)).collect();
|
||||
std::iter::once(PipelineDocument::Memory(memory))
|
||||
.chain(consumed.iter().map(|consumed| resolution(key(consumed), superseded(&[key(label)]))))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// One research note of pass `pass`: a claim standing on one observed source, serving nothing.
|
||||
pub(crate) fn research(pass: &str, label: &str, claim: &str) -> PipelineDocument {
|
||||
PipelineDocument::Research(eureka_pipeline::PipelineResearch {
|
||||
|
|
|
|||
|
|
@ -1795,9 +1795,7 @@ mod tests {
|
|||
committed(admit(&mut mind, vec![crate::fixtures::thought("t1", "A drift.", &[r(K::Finding, &f1), r(K::Question, &q1)])]));
|
||||
let (older, newer) = (format!("{INSTANCE}:memory:m1"), format!("{INSTANCE}:memory:m2"));
|
||||
committed(admit(&mut mind, vec![memory("m1", MemorySort::Idea, "one", &[])]));
|
||||
let D::Memory(mut consolidating) = memory("m2", MemorySort::Idea, "two", &[]) else { unreachable!() };
|
||||
consolidating.consolidates = vec![r(K::Memory, &older)];
|
||||
committed(admit(&mut mind, vec![D::Memory(consolidating)]));
|
||||
committed(admit(&mut mind, crate::fixtures::consolidation("m2", &["m1"])));
|
||||
let (note, way) = (id("research", "p.a"), id("idea", "p.i"));
|
||||
let D::Research(mut note_document) = research("p", "a", "A fact.") else { unreachable!() };
|
||||
note_document.serves = vec![r(K::Question, &q1)];
|
||||
|
|
@ -1816,6 +1814,7 @@ mod tests {
|
|||
id("resolution", &format!("finding.{}.n1", f2.rsplit(':').next().unwrap())),
|
||||
);
|
||||
let spec_closed = id("resolution", "cut_spec.cut-1.r1.n1");
|
||||
let older_closed = format!("{INSTANCE}:resolution:memory.m1.n1");
|
||||
let spec_r2 = id("cut_spec", "cut-1.r2");
|
||||
let r1 = id("ruling", "R1");
|
||||
let expected: Vec<(CitationRole, &str, &str)> = vec![
|
||||
|
|
@ -1835,7 +1834,9 @@ mod tests {
|
|||
(CitationRole::Subject, &f1_closed, &f1),
|
||||
(CitationRole::Subject, &f2_closed, &f2),
|
||||
(CitationRole::Subject, &spec_closed, &spec),
|
||||
(CitationRole::Subject, &older_closed, &older),
|
||||
(CitationRole::SupersededBy, &spec_closed, &spec_r2),
|
||||
(CitationRole::SupersededBy, &older_closed, &newer),
|
||||
(CitationRole::ResolvedBy, &n1, &r1),
|
||||
(CitationRole::ResolvedBy, &f1_closed, &report),
|
||||
(CitationRole::DeferredTo, &f2_closed, &fu),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ pub enum MindRefusal {
|
|||
CitesResolvedDocument { kind: PipelineKind, id: String },
|
||||
EmptySupersession,
|
||||
UnknownSupersessor { id: String },
|
||||
/// A consolidation and the supersession of the memory it consumes come together: `memory` consolidates `consumed`, or is named as superseding it, without the other half in the batch.
|
||||
UnpairedConsolidation { memory: String, consumed: String },
|
||||
RevisionWithoutSupersession { kind: PipelineKind, revision: u32 },
|
||||
DuplicateLabel { field: String, label: String },
|
||||
InvalidOptions { question: String },
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ pub(crate) enum Alias {
|
|||
const FACULTIES: [&str; 7] = ["SelfFaculty", "Imagination", "Hands", "Soul", "Life", "Eyes", "Operator"];
|
||||
const OUTCOMES: [&str; 6] = ["Superseded", "Answered", "Fixed", "Deferred", "Recorded", "Withdrawn"];
|
||||
const BOOLEANS: [&str; 2] = ["true", "false"];
|
||||
const MEMORY_ORIGINS: [&str; 4] = ["Room", "Direct", "Wake", "SelfRun"];
|
||||
const MEMORY_ORIGINS: [&str; 5] = ["Room", "Direct", "Wake", "SelfRun", "Sleep"];
|
||||
/// The one value a presence alias answers with; a row without it answers nothing.
|
||||
const PRESENT: [&str; 1] = ["true"];
|
||||
|
||||
|
|
@ -288,6 +288,7 @@ impl SelectionRow {
|
|||
MemoryOrigin::Direct { .. } => "Direct",
|
||||
MemoryOrigin::Wake => "Wake",
|
||||
MemoryOrigin::SelfRun { .. } => "SelfRun",
|
||||
MemoryOrigin::Sleep { .. } => "Sleep",
|
||||
}.to_string()]);
|
||||
}
|
||||
D::Research(research) => put(Alias::Pass, vec![research.pass.0.clone()]),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use chrono::{DateTime, Duration, Utc};
|
|||
use cultnet_rs::Selection;
|
||||
use eureka_pipeline::{
|
||||
Date, Label, PipelineDocument, PipelineKind, PipelineQuestion, PipelineRef, PipelineResolution, PipelineRun, QuestionOption, ResolutionOutcome,
|
||||
RunOperator, RunTurn, Slug, pipeline_key,
|
||||
RunOperator, RunSlot, RunTurn, Slug, pipeline_key,
|
||||
};
|
||||
use huginn_mind::{
|
||||
MindRefusal, PipelineAdmissionOutcome, PipelineDocumentSummary, PipelineFacts,
|
||||
|
|
@ -115,12 +115,12 @@ impl Default for SystemdLauncher {
|
|||
}
|
||||
}
|
||||
|
||||
/// The unit template of one turn, named only through `unit()`, which start,
|
||||
/// The unit template of one slot, named only through `unit()`, which start,
|
||||
/// alive and stop all call.
|
||||
fn template(turn: RunTurn) -> &'static str {
|
||||
match turn {
|
||||
RunTurn::PersonaTurn => "mind-persona",
|
||||
RunTurn::SelfRun => "mind-self",
|
||||
match turn.slot() {
|
||||
RunSlot::Persona => "mind-persona",
|
||||
RunSlot::SelfRun => "mind-self",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -340,7 +340,7 @@ pub(crate) fn holders(mind: &dyn MindPort, instance: &Slug, turn: RunTurn) -> Re
|
|||
let runs = headers(mind, &in_force(any_of(of_kind(PipelineKind::Run), "root", &[&instance.0])))?;
|
||||
Ok(runs
|
||||
.into_iter()
|
||||
.filter(|run| matches!(run.facts, PipelineFacts::Run { turn: held, operated_by: RunOperator::Mind, .. } if held == turn))
|
||||
.filter(|run| matches!(run.facts, PipelineFacts::Run { turn: held, operated_by: RunOperator::Mind, .. } if held.slot() == turn.slot()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +384,7 @@ pub fn open_and_launch(ports: &Ports, request: LaunchRequest) -> Result<LaunchOu
|
|||
if grant.instance() != &instance {
|
||||
return Ok(LaunchOutcome::Refused(Declined::GrantForOtherInstance));
|
||||
}
|
||||
if turn == RunTurn::PersonaTurn && !claims.is_empty() {
|
||||
if turn != RunTurn::SelfRun && !claims.is_empty() {
|
||||
return Ok(LaunchOutcome::Refused(Declined::PersonaRunTakesNoClaims));
|
||||
}
|
||||
let now = ports.clock.now();
|
||||
|
|
@ -631,6 +631,32 @@ mod tests {
|
|||
assert_eq!(*rig.launcher.started.borrow(), vec![(Slug(INSTANCE.into()), RunTurn::SelfRun)]);
|
||||
}
|
||||
|
||||
/// A Sleep run and a Persona session are one slot of hers: one unit, one live holder, in either order.
|
||||
#[test]
|
||||
fn a_sleep_run_and_a_persona_run_share_one_slot() {
|
||||
let rig = Rig::new();
|
||||
let LaunchOutcome::Launched { run } = rig.launch(RunTurn::Sleep, &[]) else { panic!("not launched") };
|
||||
rig.at(1);
|
||||
assert_eq!(rig.launch(RunTurn::PersonaTurn, &[]), LaunchOutcome::Busy { run: run.clone() }, "a live Sleep holds the Persona slot");
|
||||
assert_eq!(rig.launch(RunTurn::Sleep, &[]), LaunchOutcome::Busy { run: run.clone() });
|
||||
assert!(matches!(rig.launch(RunTurn::SelfRun, &[]), LaunchOutcome::Launched { .. }), "a Self run is the other slot");
|
||||
let ports = Ports { mind: &rig.mind, launcher: &rig.launcher, clock: &rig.clock, host: "yggdrasil-host" };
|
||||
assert!(slot(&ports, rig.mind.instance(), RunTurn::PersonaTurn, rig.clock.now()).unwrap().live.is_some());
|
||||
assert_eq!(unit(&eureka(), RunTurn::Sleep).unwrap(), "mind-persona@eureka.service", "the night runs in the Persona unit");
|
||||
rig.mind.committed(vec![close(run, recorded())]);
|
||||
rig.at(2);
|
||||
let LaunchOutcome::Launched { run: persona } = rig.launch(RunTurn::PersonaTurn, &[]) else { panic!("not launched") };
|
||||
rig.at(3);
|
||||
assert_eq!(rig.launch(RunTurn::Sleep, &[]), LaunchOutcome::Busy { run: persona }, "a live Persona session holds the slot a night needs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sleep_run_takes_no_claims() {
|
||||
let rig = Rig::new();
|
||||
assert_eq!(rig.launch(RunTurn::Sleep, &[spec_ref("x")]), LaunchOutcome::Refused(Declined::PersonaRunTakesNoClaims));
|
||||
assert_eq!(rig.runs(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_unit_started_names_the_minds_instance() {
|
||||
let rig = Rig::new();
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ pub(crate) fn people_of(memory: &eureka_pipeline::PipelineMemory) -> Vec<String>
|
|||
let origin: &[eureka_pipeline::Short] = match &memory.origin {
|
||||
MemoryOrigin::Room { heard_from, .. } => heard_from,
|
||||
MemoryOrigin::Direct { with, .. } => with,
|
||||
MemoryOrigin::Wake | MemoryOrigin::SelfRun { .. } => &[],
|
||||
MemoryOrigin::Wake | MemoryOrigin::SelfRun { .. } | MemoryOrigin::Sleep { .. } => &[],
|
||||
};
|
||||
memory.about.iter().chain(origin).map(|id| id.0.clone()).collect()
|
||||
}
|
||||
|
|
@ -206,6 +206,7 @@ pub(crate) fn origin_phrase(origin: &MemoryOrigin, names: &BTreeMap<&str, &str>)
|
|||
MemoryOrigin::Direct { with, .. } => format!("privately, with {}", who(with.iter().map(|id| id.0.as_str()), names)),
|
||||
MemoryOrigin::Wake => "on a wake".to_string(),
|
||||
MemoryOrigin::SelfRun { run } => format!("in your Self run {}", run.0),
|
||||
MemoryOrigin::Sleep { run } => format!("in your sleep {}", run.0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,12 +55,17 @@ pub(crate) struct Recording {
|
|||
pub(crate) fail: Cell<bool>,
|
||||
/// One named instance whose unit never starts.
|
||||
pub(crate) fail_for: RefCell<Option<Slug>>,
|
||||
running: RefCell<Vec<(Slug, RunTurn)>>,
|
||||
running: RefCell<Vec<(Slug, eureka_pipeline::RunSlot)>>,
|
||||
}
|
||||
|
||||
impl Recording {
|
||||
pub(crate) fn kill(&self, turn: RunTurn) {
|
||||
self.running.borrow_mut().retain(|unit| *unit != (Slug(INSTANCE.into()), turn));
|
||||
self.running.borrow_mut().retain(|unit| *unit != (Slug(INSTANCE.into()), turn.slot()));
|
||||
}
|
||||
|
||||
/// `kill` for a named instance.
|
||||
pub(crate) fn kill_as(&self, instance: &Slug, turn: RunTurn) {
|
||||
self.running.borrow_mut().retain(|unit| *unit != (instance.clone(), turn.slot()));
|
||||
}
|
||||
|
||||
pub(crate) fn revive(&self, turn: RunTurn) {
|
||||
|
|
@ -69,7 +74,7 @@ impl Recording {
|
|||
|
||||
/// `revive` for a named instance.
|
||||
pub(crate) fn revive_as(&self, instance: &Slug, turn: RunTurn) {
|
||||
self.running.borrow_mut().push((instance.clone(), turn));
|
||||
self.running.borrow_mut().push((instance.clone(), turn.slot()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +84,12 @@ impl Launcher for Recording {
|
|||
return Err(anyhow!("refused"));
|
||||
}
|
||||
self.started.borrow_mut().push((instance.clone(), turn));
|
||||
self.running.borrow_mut().push((instance.clone(), turn));
|
||||
self.running.borrow_mut().push((instance.clone(), turn.slot()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn alive(&self, instance: &Slug, turn: RunTurn) -> bool {
|
||||
self.running.borrow().contains(&(instance.clone(), turn))
|
||||
self.running.borrow().contains(&(instance.clone(), turn.slot()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,27 @@
|
|||
"SelfRun"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Her Sleep run, by its label: a memory she formed in her night.",
|
||||
"properties": {
|
||||
"Sleep": {
|
||||
"properties": {
|
||||
"run": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"run"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Sleep"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -96,6 +96,27 @@
|
|||
"SelfRun"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Her Sleep run, by its label: a memory she formed in her night.",
|
||||
"properties": {
|
||||
"Sleep": {
|
||||
"properties": {
|
||||
"run": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"run"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Sleep"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@
|
|||
"RunTurn": {
|
||||
"enum": [
|
||||
"PersonaTurn",
|
||||
"SelfRun"
|
||||
"SelfRun",
|
||||
"Sleep"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -663,6 +663,27 @@
|
|||
"required": [
|
||||
"SelfRun"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Her Sleep run, by its label: a memory she formed in her night.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Sleep": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"run": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"run"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Sleep"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -2611,7 +2632,8 @@
|
|||
"type": "string",
|
||||
"enum": [
|
||||
"PersonaTurn",
|
||||
"SelfRun"
|
||||
"SelfRun",
|
||||
"Sleep"
|
||||
]
|
||||
},
|
||||
"SemanticQuery": {
|
||||
|
|
|
|||
|
|
@ -791,6 +791,27 @@
|
|||
"required": [
|
||||
"SelfRun"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Her Sleep run, by its label: a memory she formed in her night.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Sleep": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"run": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"run"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Sleep"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -1184,6 +1205,31 @@
|
|||
"UnknownSupersessor"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A consolidation and the supersession of the memory it consumes come together: `memory` consolidates `consumed`, or is named as superseding it, without the other half in the batch.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"UnpairedConsolidation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"consumed": {
|
||||
"type": "string"
|
||||
},
|
||||
"memory": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"memory",
|
||||
"consumed"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"UnpairedConsolidation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5335,7 +5381,8 @@
|
|||
"type": "string",
|
||||
"enum": [
|
||||
"PersonaTurn",
|
||||
"SelfRun"
|
||||
"SelfRun",
|
||||
"Sleep"
|
||||
]
|
||||
},
|
||||
"Sha": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue