Watch
1
0
Fork
You've already forked souveraine
0

record a delivered thought before removing it

The removal committed first, so when the sent-write hit the ledger's own
character ceiling the thought left the inbox and was recorded nowhere —
and the caller only logged a warning.

sent.md grew forever against that ceiling, reached at about 47 entries.
It rolls at 30 now; every write is a commit, so nothing is lost.
This commit is contained in:
Fimeg 2026-08-13 22:08:30 -04:00
commit 63ef8e5b55

View file

@ -34,6 +34,22 @@ use crate::core::memory::{parse_memory_file, MemoryRepo};
const PENDING: &str = "subconscious/pending.md";
const INTRUSIVE: &str = "subconscious/intrusive.md";
const SENT: &str = "subconscious/sent.md";
/// How many delivered thoughts the ledger keeps.
///
/// `sent.md` is an audit record — nothing reads it to decide anything, and a
/// thought is removed from `pending`/`intrusive` when it is delivered, so the
/// ledger is not what stops re-delivery. Its value is entirely recent.
///
/// It used to grow forever against the 20 000-character ceiling in its own
/// frontmatter, which real entries reach at about forty-seven items — days,
/// not months. After that every `mark_delivered` failed and the channel was
/// dead with only a warning to show for it.
///
/// Rolling the oldest off loses nothing: every write to a memory file is a
/// git commit, so what leaves the working file is still in the history. What
/// contracts is what she carries.
const SENT_LEDGER_MAX: usize = 30;
const INNER_VOICE: &str = "system/metacognition/subconscious.md";
/// Urgency determines surfacing timing (Constitution Article II.2).
@ -205,25 +221,41 @@ impl SubconsciousInbox {
/// Move an item from its current box to `sent.md`.
pub async fn mark_delivered(&self, id: &str) -> Result<()> {
let mut sent = self.read_items(SENT).await?;
let mut delivered: Option<InboxItem> = None;
let mut intrusive = self.read_items(INTRUSIVE).await?;
if let Some(pos) = intrusive.iter().position(|i| i.id == id) {
delivered = Some(intrusive.remove(pos));
let from_intrusive = intrusive.iter().position(|i| i.id == id);
let mut pending = self.read_items(PENDING).await?;
let from_pending = if from_intrusive.is_none() {
pending.iter().position(|i| i.id == id)
} else {
None
};
let Some(item) = from_intrusive
.map(|p| intrusive[p].clone())
.or_else(|| from_pending.map(|p| pending[p].clone()))
else {
return Ok(());
};
// Record before removing. The other order lost items: the removal
// committed, then the sent-write failed against the box's own
// character ceiling, and the caller only logged a warning — so the
// thought left the inbox and was never recorded anywhere. Failing
// here instead leaves it in place to be delivered again.
sent.push(item);
if sent.len() > SENT_LEDGER_MAX {
sent.drain(..sent.len() - SENT_LEDGER_MAX);
}
self.write_items(SENT, &sent).await?;
if let Some(pos) = from_intrusive {
intrusive.remove(pos);
self.write_items(INTRUSIVE, &intrusive).await?;
}
if delivered.is_none() {
let mut pending = self.read_items(PENDING).await?;
if let Some(pos) = pending.iter().position(|i| i.id == id) {
delivered = Some(pending.remove(pos));
self.write_items(PENDING, &pending).await?;
}
}
if let Some(item) = delivered {
sent.push(item);
self.write_items(SENT, &sent).await?;
} else if let Some(pos) = from_pending {
pending.remove(pos);
self.write_items(PENDING, &pending).await?;
}
Ok(())
}
@ -396,6 +428,67 @@ mod tests {
assert_eq!(item.content, "in intrusive");
}
#[tokio::test]
async fn the_sent_ledger_rolls_instead_of_filling_up() {
let (_d, repo) = make_repo();
repo.init().await.unwrap();
let inbox = SubconsciousInbox::new(repo);
inbox.init().await.unwrap();
for n in 0..(SENT_LEDGER_MAX + 5) {
let item = InboxItem::new("verify", Urgency::High, format!("thought {n}"));
let id = item.id.clone();
inbox.queue(item).await.unwrap();
inbox.mark_delivered(&id).await.unwrap();
}
let sent = inbox.read_items(SENT).await.unwrap();
assert_eq!(sent.len(), SENT_LEDGER_MAX, "the ledger must stay bounded");
// The newest survive; the oldest rolled off into git history.
assert_eq!(sent.last().unwrap().content, "thought 34");
assert!(
!sent.iter().any(|i| i.content == "thought 0"),
"the oldest should have rolled off, not the newest"
);
}
/// A thought must not vanish because the ledger refused to record it.
/// The removal used to commit first: the item left `intrusive`, the
/// sent-write then failed against the box's character ceiling, and the
/// caller only logged a warning.
#[tokio::test]
async fn a_ledger_that_refuses_leaves_the_thought_where_it_was() {
let (_d, repo) = make_repo();
repo.init().await.unwrap();
let inbox = SubconsciousInbox::new(repo.clone());
inbox.init().await.unwrap();
let item = InboxItem::new("verify", Urgency::High, "do not lose me");
let id = item.id.clone();
inbox.queue(item).await.unwrap();
// Give the ledger a ceiling no record can fit under. Written to disk
// directly: this is a hostile starting state, not an exercise of the
// write API.
tokio::fs::write(
repo.root().join(SENT),
"---\ndescription: delivery ledger\nlimit: 1\n---\n[]\n",
)
.await
.unwrap();
assert!(
inbox.mark_delivered(&id).await.is_err(),
"a failed record must be reported, not swallowed"
);
let intrusive = inbox.read_items(INTRUSIVE).await.unwrap();
assert!(
intrusive.iter().any(|i| i.id == id),
"the thought must still be there to deliver again"
);
}
#[tokio::test]
async fn mark_delivered_moves_to_sent() {
let (_d, repo) = make_repo();