open lists: order by origin time (an import is as old as it was in the giver), clip each line's item text, name the dropped follow-ups' campaigns

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-11-09 11:57:48 +01:00
commit 94f235e7ce
4 changed files with 251 additions and 30 deletions

View file

@ -170,7 +170,8 @@ pub(crate) const SHORT_MAX_BYTES: usize = 200;
/// The most bytes each open-item list of the responsibilities section carries, outside `GOALS_MAX_BYTES`.
const OPEN_COMMITTED_MAX_BYTES: usize = 4 * 1024;
const OPEN_ASKED_MAX_BYTES: usize = 4 * 1024;
/// The most open items one list fetches; the rest are counted.
/// The page size of an open-item fetch. It bounds the cost of one read, not what shows: every page is read, because the
/// order is the item's origin time and a page of admission order is not that order.
const OPEN_FETCH: u32 = 64;
/// The most event ids a Persona memory cites as its sources.
pub(crate) const SOURCES_MAX: usize = 8;
@ -2319,6 +2320,22 @@ fn ruling_ids_in(text: &str) -> Vec<String> {
.collect()
}
/// The campaigns whose dropped follow-ups the note names; the rest are counted together.
const DROPPED_CAMPAIGNS_NAMED: usize = 6;
/// The note that counts the open follow-ups the bound left out and says where they are, the biggest campaigns first.
fn dropped_note(dropped: &BTreeMap<String, usize>) -> String {
let mut by_campaign: Vec<(&String, &usize)> = dropped.iter().collect();
by_campaign.sort_by(|a, b| b.1.cmp(a.1).then(a.0.cmp(b.0)));
let total: usize = dropped.values().sum();
let mut where_: Vec<String> = by_campaign.iter().take(DROPPED_CAMPAIGNS_NAMED).map(|(campaign, count)| format!("{campaign} {count}")).collect();
if by_campaign.len() > DROPPED_CAMPAIGNS_NAMED {
let rest: usize = by_campaign[DROPPED_CAMPAIGNS_NAMED..].iter().map(|(_, count)| **count).sum();
where_.push(format!("{} more campaigns {rest}", by_campaign.len() - DROPPED_CAMPAIGNS_NAMED));
}
format!("({total} more open follow-ups not shown: {}; `huginn open <campaign>` lists them.)\n", where_.join(", "))
}
/// What she is responsible for, from her own mind: her stewardships in force,
/// always whole, with each record's note; her open commitments, then her open asks, each oldest first with
/// its id and its own bound; her follow-ups in force, oldest
@ -2370,15 +2387,24 @@ pub(crate) fn render_responsibilities(mind: &dyn MindPort, instance: &Slug, name
(MemorySort::Committed, "You committed to, oldest first:", "commitments", OPEN_COMMITTED_MAX_BYTES),
(MemorySort::Asked, "Asked of you, not yet taken on, oldest first:", "asks", OPEN_ASKED_MAX_BYTES),
] {
// Ascending, which is admission order: the oldest open items first, the newest left to the count.
let selection = Selection {
let mut asking = Selection {
limit: Some(OPEN_FETCH),
projection: "document".into(),
..any_of(in_force(of_kind(PipelineKind::Memory)), "sort", &[format!("{sort:?}").as_str()])
};
let page = mind.query(&selection)?;
let matched = page.matched as usize;
let PipelinePageItems::Documents(views) = page.items else { bail!("the mind answered headers to a document selection") };
let mut views = Vec::new();
loop {
let page = mind.query(&asking)?;
let PipelinePageItems::Documents(items) = page.items else { bail!("the mind answered headers to a document selection") };
views.extend(items);
match page.next {
Some(cursor) => asking.cursor = Some(cursor),
None => break,
}
}
// Oldest first by origin: an imported item is as old as it was in the giver, not as its import.
views.sort_by(|a, b| recall::origin_order(&a.admission).cmp(&recall::origin_order(&b.admission)));
let matched = views.len();
let (mut section, mut shown) = (String::new(), 0usize);
for view in &views {
let Some(line) = recall::memory_line(view, names, true) else { continue };
@ -2403,10 +2429,11 @@ pub(crate) fn render_responsibilities(mind: &dyn MindPort, instance: &Slug, name
}
// The open_ids items are outside the follow-ups' and rulings' bound.
let open_len = out.len() - open_start;
// Ascending, which is admission order: oldest first is the reminder of work she has carried longest, and the bound drops the newest.
let open = in_force(of_kind(PipelineKind::FollowUp));
let follow_ups = headers(mind, &open)?;
let mut dropped = 0;
// Oldest first by origin: an imported follow-up is as old as it was in the giver, not as its import. Work that has waited
// longest leads, and the bound drops the newest.
let mut follow_ups = headers(mind, &in_force(of_kind(PipelineKind::FollowUp)))?;
follow_ups.sort_by(|a, b| recall::origin_order(&a.admission).cmp(&recall::origin_order(&b.admission)));
let mut dropped: BTreeMap<String, usize> = BTreeMap::new();
// Every open follow-up counts as open work for its campaign, whether or not the bound below lets its line show.
let mut open_roots: Vec<String> = Vec::new();
for follow_up in &follow_ups {
@ -2423,9 +2450,9 @@ pub(crate) fn render_responsibilities(mind: &dyn MindPort, instance: &Slug, name
let asked = origin.as_ref().map_or_else(String::new, |origin| format!(", {}", recall::origin_phrase(origin, names)));
let campaign = follow_up.id.id.0.split_once(":follow_up:").map_or("", |(campaign, _)| campaign);
let held = if claimed.contains(&follow_up.id) { " [claimed by a run]" } else { "" };
let line = format!("- {campaign}/{} ({}{asked}){held}: {}\n", label.0, repo.0, item.0.lines().next().unwrap_or(""));
let line = format!("- {campaign}/{} ({}{asked}){held}: {}\n", label.0, repo.0, recall::clipped(item.0.lines().next().unwrap_or("")));
if out.len() - open_len + line.len() > GOALS_MAX_BYTES - GOALS_RULINGS_RESERVE {
dropped += 1;
*dropped.entry(campaign.to_owned()).or_default() += 1;
continue;
}
if source.kind == PipelineKind::Ruling {
@ -2438,8 +2465,8 @@ pub(crate) fn render_responsibilities(mind: &dyn MindPort, instance: &Slug, name
if follow_ups.is_empty() {
out.push_str("- no follow-up is open in your mind.\n");
}
if dropped > 0 {
out.push_str(&format!("({dropped} more open follow-ups not shown: `huginn open <campaign>` lists them all.)\n"));
if !dropped.is_empty() {
out.push_str(&dropped_note(&dropped));
}
let mut dropped_rulings = cited.len().saturating_sub(GOALS_RULINGS_MAX);
cited.truncate(GOALS_RULINGS_MAX);

View file

@ -2289,13 +2289,13 @@ fn a_long_sender_id_stays_out_of_the_schema_and_never_poisons_a_turn() {
assert!(input_of(&rig, 0).contains("hello there"));
}
/// Twelve open asks of about 900 bytes, heavy memories about the sender and matching the words, and a few short deeds.
/// Twenty open asks of about 900 bytes (each listed clipped), heavy memories about the sender and matching the words, and a few short deeds.
#[test]
fn caps_count_what_they_leave_out() {
let (libby, op) = ("@libby:chat", "@op:chat");
let heavy = |tag: &str| format!("{tag} hello {}", "x".repeat(880));
let rig = Rig::new(vec![delivery(1, "swarm", op, "hello")], &[says(&[("swarm", "hi")])]);
for index in 0..12 {
for index in 0..20 {
rig.mind.committed(vec![crate::testkit::memory(&format!("ask-{index:02}"), MemorySort::Asked, &heavy(&format!("ASK{index:02}")), &[libby])]);
}
for index in 0..6 {
@ -2312,10 +2312,10 @@ fn caps_count_what_they_leave_out() {
// The asks: shown with ids, the rest counted exactly, and the settle enum is exactly what was shown.
let asked = under(responsibilities_of(&input), "Asked of you, not yet taken on, oldest first:");
let shown_ids: Vec<String> = asked.lines().filter(|line| line.starts_with("- ") && line.contains(":memory:")).map(|line| line[2..].split(' ').next().unwrap().to_string()).collect();
assert!(!shown_ids.is_empty() && shown_ids.len() < 12, "{} shown: {asked}", shown_ids.len());
assert!(!shown_ids.is_empty() && shown_ids.len() < 20, "{} shown: {asked}", shown_ids.len());
let expected_oldest: Vec<String> = (0..shown_ids.len()).map(|index| format!("yggdrasil:memory:ask-{index:02}")).collect();
assert_eq!(shown_ids, expected_oldest, "the oldest asks show, in admission order, and the newest are counted");
assert!(asked.contains(&format!("({} more asks not shown", 12 - shown_ids.len())), "{} shown, ids {shown_ids:?}", shown_ids.len());
assert!(asked.contains(&format!("({} more asks not shown", 20 - shown_ids.len())), "{} shown, ids {shown_ids:?}", shown_ids.len());
let mut enum_ids = settle_enum(&rig, 0);
enum_ids.sort();
let mut expected = shown_ids.clone();
@ -2367,22 +2367,25 @@ fn each_open_list_holds_its_own_bound_exactly_and_counts_what_it_drops() {
(MemorySort::Committed, "You committed to, oldest first:", "commitments", OPEN_COMMITTED_MAX_BYTES),
(MemorySort::Asked, "Asked of you, not yet taken on, oldest first:", "asks", OPEN_ASKED_MAX_BYTES),
] {
let render = |adjustable: usize| {
// Short fillers make the lines fine-grained; the oldest item's text (under the clip) tunes the total to the byte.
let render = |fillers: usize, adjustable: usize| {
let mind = TestMind::seeded();
mind.committed(vec![crate::testkit::memory("m0", sort, &"w".repeat(adjustable), &[])]);
for index in 1..4 {
mind.committed(vec![crate::testkit::memory(&format!("m{index}"), sort, &"f".repeat(1000), &[])]);
for index in 1..=fillers {
mind.committed(vec![crate::testkit::memory(&format!("m{index:02}"), sort, "f", &[])]);
}
render_responsibilities(&mind, &slug, &names).unwrap().text
};
let used = |text: &str| under(text, heading).lines().filter(|line| line.starts_with("- ") && line.contains(":memory:")).map(|line| line.len() + 1).sum::<usize>();
let lines = |text: &str| under(text, heading).lines().filter(|line| line.starts_with("- ") && line.contains(":memory:")).count();
let fit = 100 + bound - used(&render(100));
let fits = render(fit);
assert_eq!((used(&fits), lines(&fits)), (bound, 4), "an exact fit is shown: {fits}");
let fillers = (1..100).take_while(|count| lines(&render(*count, 0)) == count + 1).last().unwrap();
let fit = bound - used(&render(fillers, 0));
assert!(fit < 150, "{fit}");
let fits = render(fillers, fit);
assert_eq!((used(&fits), lines(&fits)), (bound, fillers + 1), "an exact fit is shown: {fits}");
assert!(!fits.contains("not shown"), "{fits}");
let over = render(fit + 1);
assert_eq!(lines(&over), 3, "{over}");
let over = render(fillers, fit + 1);
assert_eq!(lines(&over), fillers, "{over}");
assert!(over.contains(&format!("(1 more {noun} not shown: settle what is finished and they come back into view.)")), "{over}");
}
// Both lists full and a follow-up still has its room: the open lists are outside the goals bound.
@ -7355,3 +7358,129 @@ fn a_sleep_holder_opens_one_process_with_the_night_schema_and_her_tools_never_a_
assert_eq!(rig.chat.pending_calls.get(), 0);
assert!(rig.model.requests.borrow()[0].input.contains("Tonight you consolidate your memory."));
}
/// A follow-up of `campaign` (whose spec `src` and campaign the caller has admitted), with its item text.
fn follow_up_in(campaign: &str, label: &str, item: &str) -> PipelineDocument {
let PipelineDocument::FollowUp(mut follow_up) = follow_up(label, &reference(K::CutSpec, &format!("{campaign}:cut_spec:cut-src.r1"))) else { unreachable!("a follow-up") };
follow_up.campaign = Slug(campaign.into());
follow_up.item = Line(item.into());
PipelineDocument::FollowUp(follow_up)
}
/// The labels of the follow-up lines `text` lists, in order.
fn listed_follow_ups(text: &str) -> Vec<String> {
text.lines()
.filter_map(|line| line.strip_prefix("- "))
.filter(|line| line.contains(" (GameCult/"))
.filter_map(|line| line.split(' ').next())
.map(|id| id.rsplit('/').next().unwrap().to_owned())
.collect()
}
/// An imported item is as old as it was in the giver: it sorts by that time, not by when it was imported.
#[test]
fn an_imported_item_sorts_by_its_origin_time_not_its_import() {
let mind = TestMind::seeded();
mind.committed(chain(CAMPAIGN, "src", REPO));
mind.committed(vec![follow_up_in(CAMPAIGN, "native", "Native, admitted first of the two.")]);
mind.committed_imported("2020-01-01T00:00:00Z", vec![follow_up_in(CAMPAIGN, "imported-old", "Imported, born in the giver long ago.")]);
mind.committed_imported("2999-01-01T00:00:00Z", vec![follow_up_in(CAMPAIGN, "imported-new", "Imported, born in the giver later than anything.")]);
mind.committed(vec![crate::testkit::memory("m-native", MemorySort::Asked, "native ask", &[])]);
mind.committed_imported("2020-01-01T00:00:00Z", vec![crate::testkit::memory("m-imported", MemorySort::Asked, "imported ask", &[])]);
let text = render_responsibilities(&mind, &Slug(INSTANCE.into()), &BTreeMap::new()).unwrap().text;
assert_eq!(listed_follow_ups(&text), ["imported-old", "native", "imported-new"], "{text}");
let at = |needle: &str| text.find(needle).unwrap_or_else(|| panic!("{needle} in {text}"));
assert!(at("m-imported") < at("m-native"), "{text}");
assert!(text.contains("(2020-01-01, "), "an imported memory shows the date it began: {text}");
}
/// A listed line carries about 160 bytes of the item's text, cut on a character boundary with an ellipsis; the whole stays one `huginn view` away.
#[test]
fn a_listed_line_clips_the_item_text() {
assert_eq!(recall::clipped(&"w".repeat(160)), "w".repeat(160));
assert_eq!(recall::clipped(&"w".repeat(161)), format!("{}…", "w".repeat(160)));
let accents = "é".repeat(100);
assert_eq!(recall::clipped(&accents), format!("{}…", "é".repeat(80)), "a cut inside a character backs up to its start");
let mind = TestMind::seeded();
mind.committed(chain(CAMPAIGN, "src", REPO));
mind.committed(vec![follow_up_in(CAMPAIGN, "long", &"w".repeat(500))]);
mind.committed(vec![crate::testkit::memory("m-long", MemorySort::Committed, &"v".repeat(500), &[])]);
let text = render_responsibilities(&mind, &Slug(INSTANCE.into()), &BTreeMap::new()).unwrap().text;
assert!(text.contains(&format!("{}…", "w".repeat(160))) && !text.contains(&"w".repeat(161)), "{text}");
assert!(text.contains(&format!("{}…", "v".repeat(160))) && !text.contains(&"v".repeat(161)), "{text}");
let whole = recall::memory_line(&mind.view(K::Memory, &format!("{INSTANCE}:memory:m-long")).unwrap(), &BTreeMap::new(), false).unwrap();
assert!(whole.contains(&"v".repeat(500)), "recall shows a memory whole: {whole}");
}
/// A line that does not fit is skipped and a later short one may still show, in the follow-ups and in the open memories.
#[test]
fn a_line_that_does_not_fit_is_skipped_and_a_later_short_one_shows() {
// Follow-ups: fill the bound, then an older long-labelled line that cannot fit, then a newer short one.
let build = |fillers: usize, with_short: bool| {
let mind = TestMind::seeded();
mind.committed(chain(CAMPAIGN, "src", REPO));
for index in 0..fillers {
mind.committed(vec![follow_up_in(CAMPAIGN, &format!("fill{index:03}"), "filler")]);
}
mind.committed(vec![follow_up_in(CAMPAIGN, &format!("big{}", "g".repeat(190)), &"b".repeat(160))]);
if with_short {
mind.committed(vec![follow_up_in(CAMPAIGN, "short", "s")]);
}
render_responsibilities(&mind, &Slug(INSTANCE.into()), &BTreeMap::new()).unwrap().text
};
let fillers = (1..200).find(|count| !build(*count, false).contains("big")).expect("the bound is reached");
let text = build(fillers, true);
assert!(!text.contains("big") && listed_follow_ups(&text).last().map(String::as_str) == Some("short"), "{text}");
assert!(text.contains("(1 more open follow-ups not shown: eureka-body 1;"), "{text}");
// Open memories: the same.
for sort in [MemorySort::Committed, MemorySort::Asked] {
let build = |fillers: usize, with_short: bool| {
let mind = TestMind::seeded();
for index in 0..fillers {
mind.committed(vec![crate::testkit::memory(&format!("fill{index:03}"), sort, "filler", &[])]);
}
let about: Vec<String> = (0..8).map(|index| format!("{index}{}", "p".repeat(190))).collect();
let about: Vec<&str> = about.iter().map(String::as_str).collect();
mind.committed(vec![crate::testkit::memory("big", sort, &"b".repeat(160), &about)]);
if with_short {
mind.committed(vec![crate::testkit::memory("short", sort, "s", &[])]);
}
render_responsibilities(&mind, &Slug(INSTANCE.into()), &BTreeMap::new()).unwrap().text
};
let fillers = (1..200).find(|count| !build(*count, false).contains(":memory:big")).expect("the bound is reached");
let text = build(fillers, true);
assert!(!text.contains(":memory:big") && text.contains(":memory:short"), "{sort:?}: {text}");
}
}
/// The note that counts the dropped follow-ups says where they are, the biggest campaign first, and stays bounded.
#[test]
fn the_dropped_follow_ups_note_names_their_campaigns() {
let mind = TestMind::seeded();
for campaign in [CAMPAIGN, "hidden-in", "third"] {
if campaign != CAMPAIGN {
mind.committed(campaign_on(campaign, &[REPO]));
}
mind.committed(chain(campaign, "src", REPO));
}
let long = "w".repeat(400);
for index in 0..30 {
mind.committed(vec![follow_up_in(CAMPAIGN, &format!("fu-{index:02}"), &long)]);
}
for index in 0..5 {
mind.committed(vec![follow_up_in("hidden-in", &format!("h{index}"), &long)]);
}
for index in 0..7 {
mind.committed(vec![follow_up_in("third", &format!("t{index}"), &long)]);
}
let text = render_responsibilities(&mind, &Slug(INSTANCE.into()), &BTreeMap::new()).unwrap().text;
let listed = listed_follow_ups(&text).len();
let note = text.lines().find(|line| line.contains("more open follow-ups not shown")).unwrap_or_else(|| panic!("{text}"));
let native_dropped = 30 - listed;
assert_eq!(note, format!("({} more open follow-ups not shown: eureka-body {native_dropped}, third 7, hidden-in 5; `huginn open <campaign>` lists them.)", native_dropped + 12), "{text}");
let many: BTreeMap<String, usize> = (0..9).map(|index| (format!("c{index}"), 10 - index)).collect();
assert_eq!(
dropped_note(&many),
"(54 more open follow-ups not shown: c0 10, c1 9, c2 8, c3 7, c4 6, c5 5, 3 more campaigns 9; `huginn open <campaign>` lists them.)\n"
);
}

View file

@ -29,7 +29,7 @@ use anyhow::{Result, bail};
use chrono::{DateTime, Utc};
use cultnet_rs::Selection;
use eureka_pipeline::{MemoryOrigin, MemorySort, PipelineDocument, PipelineKind};
use huginn_mind::{PipelineDocumentView, PipelinePageItems};
use huginn_mind::{AdmissionFacts, PipelineDocumentView, PipelinePageItems};
use crate::persona::quoted;
use crate::queue::{MindPort, any_of, in_force, of_kind};
@ -215,8 +215,10 @@ pub(crate) fn origin_phrase(origin: &MemoryOrigin, names: &BTreeMap<&str, &str>)
/// recall and for the open items the responsibilities section shows, which pass
/// `with_id` to lead with the id she settles it by. `about` is never the place.
pub(crate) fn memory_line(view: &PipelineDocumentView, names: &BTreeMap<&str, &str>, with_id: bool) -> Option<String> {
// An open item of the responsibilities (the one caller that leads with the id) shows a clipped text; recall shows it whole.
let PipelineDocument::Memory(memory) = &view.document else { return None };
let date = view.admission.admitted_at.get(..10).unwrap_or(&view.admission.admitted_at);
let when = origin_time(&view.admission);
let date = when.get(..10).unwrap_or(when);
let origin = origin_phrase(&memory.origin, names);
let about = who(memory.about.iter().map(|id| id.0.as_str()), names);
let sort = match (memory.sort, memory.about.is_empty()) {
@ -227,7 +229,30 @@ pub(crate) fn memory_line(view: &PipelineDocumentView, names: &BTreeMap<&str, &s
(sort, false) => format!("{sort:?} about {about}"),
};
let lead = if with_id { format!("{} ", view.id.id.0) } else { String::new() };
Some(format!("- {lead}({date}, {origin}, {sort}): {}\n", quoted(&memory.text.0)))
Some(format!("- {lead}({date}, {origin}, {sort}): {}\n", quoted(&if with_id { clipped(&memory.text.0) } else { memory.text.0.clone() })))
}
/// The most bytes of an item's text a listed line carries; the rest is one `huginn view` away.
pub(crate) const LINE_TEXT_MAX_BYTES: usize = 160;
/// `text` cut to about `LINE_TEXT_MAX_BYTES` on a character boundary, with an ellipsis where it was cut.
pub(crate) fn clipped(text: &str) -> String {
if text.len() <= LINE_TEXT_MAX_BYTES {
return text.to_owned();
}
let cut = (0..=LINE_TEXT_MAX_BYTES).rev().find(|at| text.is_char_boundary(*at)).unwrap_or(0);
format!("{}…", &text[..cut])
}
/// When a document first stood, as the time it was admitted where it began: an imported document carries the source
/// receipt's time, any other its own admission.
pub(crate) fn origin_time(admission: &AdmissionFacts) -> &str {
admission.provenance.imported_from.as_ref().map_or(admission.admitted_at.as_str(), |source| source.committed_at.0.as_str())
}
/// The sort key of oldest first: origin time, then the mind's own admission ordinal for equal times.
pub(crate) fn origin_order(admission: &AdmissionFacts) -> (&str, u64) {
(origin_time(admission), admission.ordinal)
}
/// One recalled document as its line, or None for a kind recall does not show.

View file

@ -18,7 +18,7 @@ use eureka_pipeline::{
PipelineTarget, QuestionOption, ResolutionOutcome, RulingAuthority, RunOperator, RunTurn, Sha, Slug, StructuralDelta, TargetInvariant,
};
use huginn_mind::{
Faculty, HuginnMindRequest, HuginnMindResponse, Mind, OwnedRedbMessagePackBackingStore, PipelineAdmissionBatch, PipelineAdmissionOutcome,
Faculty, HuginnMindRequest, ImportSource, ImportSource, HuginnMindResponse, Mind, OwnedRedbMessagePackBackingStore, PipelineAdmissionBatch, PipelineAdmissionOutcome,
PipelineDocumentView, PipelineProvenance, PipelineSelectionPage,
};
use tempfile::TempDir;
@ -155,6 +155,46 @@ impl TestMind {
self.mind.borrow_mut().admit(batch, self.now.get())
}
/// Admits as an import of a giver's receipt committed at `committed_at`, one second after the last admission here.
pub(crate) fn committed_imported(&self, committed_at: &str, documents: Vec<PipelineDocument>) {
self.now.set(self.now.get() + Duration::seconds(1));
let source = ImportSource {
instance: Slug("giver".into()),
receipt_id: format!("giver-{committed_at}").as_str().into(),
ordinal: 1,
committed_at: committed_at.into(),
faculty: Faculty::Hands,
agent: "test".into(),
session: "test".into(),
tool: "test".into(),
external: vec![],
};
let provenance = PipelineProvenance { faculty: Faculty::Hands, agent: "test".into(), session: "test".into(), tool: "test".into(), imported_from: Some(source) };
let batch = PipelineAdmissionBatch { instance: self.instance.clone(), provenance, documents };
let outcome = self.mind.borrow_mut().admit(batch, self.now.get());
assert!(matches!(outcome, PipelineAdmissionOutcome::Committed { .. }), "{outcome:?}");
}
/// Admits as an import of a giver's receipt committed at `committed_at`, one second after the last admission here.
pub(crate) fn committed_imported(&self, committed_at: &str, documents: Vec<PipelineDocument>) {
self.now.set(self.now.get() + Duration::seconds(1));
let source = ImportSource {
instance: Slug("giver".into()),
receipt_id: format!("giver-{committed_at}").as_str().into(),
ordinal: 1,
committed_at: committed_at.into(),
faculty: Faculty::Hands,
agent: "test".into(),
session: "test".into(),
tool: "test".into(),
external: vec![],
};
let provenance = PipelineProvenance { faculty: Faculty::Hands, agent: "test".into(), session: "test".into(), tool: "test".into(), imported_from: Some(source) };
let batch = PipelineAdmissionBatch { instance: self.instance.clone(), provenance, documents };
let outcome = self.mind.borrow_mut().admit(batch, self.now.get());
assert!(matches!(outcome, PipelineAdmissionOutcome::Committed { .. }), "{outcome:?}");
}
pub(crate) fn committed(&self, documents: Vec<PipelineDocument>) {
let outcome = self.put(documents);
assert!(matches!(outcome, PipelineAdmissionOutcome::Committed { .. }), "{outcome:?}");