huginn-mind: a persona re-put no longer poisons reads; the admission index covers pipeline documents only
The persona document is replaced by design (compare-and-swap on updatedAt), so its second put is a second receipt naming one identity. AdmissionIndex::build read that as a doubled A10 write and refused every view and query. The persona type has no admission facts there now; Mind::persona reads its own receipt. A pipeline document written by two receipts still refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
21827a736f
commit
577cae23a7
2 changed files with 74 additions and 3 deletions
|
|
@ -164,6 +164,7 @@ mod tests {
|
|||
use crate::fixtures::{INSTANCE, OTHER_INSTANCE, epoch, instance, opened, prepare, provenance, seed, slug};
|
||||
use crate::receipt::Faculty;
|
||||
use crate::store::test_stores::{MemoryStore, RefusingStore, SwapCommand};
|
||||
use cultnet_rs::Selection;
|
||||
use eureka_pipeline::PipelineKind;
|
||||
use serde_json::json;
|
||||
|
||||
|
|
@ -306,6 +307,70 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// The persona is the one identity replaced by design: a second put is a
|
||||
/// second receipt naming it, and every pipeline read still answers.
|
||||
#[test]
|
||||
fn reads_answer_after_a_second_persona_put() {
|
||||
let store = MemoryStore::new();
|
||||
let mut mind = seeded_over(&store);
|
||||
committed(put(&mut mind, persona(INSTANCE, T1), None));
|
||||
let second = committed(put(&mut mind, persona(INSTANCE, T2), Some(T1)));
|
||||
committed(put(&mut mind, persona(INSTANCE, T3), Some(T2)));
|
||||
assert_eq!(mind.persona().unwrap().unwrap().updated_at, T3);
|
||||
assert_reads_answer(&mind);
|
||||
assert_ne!(mind.persona().unwrap().unwrap().receipt_id, second);
|
||||
assert_reads_answer(&opened(store.clone(), INSTANCE));
|
||||
}
|
||||
|
||||
/// The same, over a real store file closed and reopened.
|
||||
#[test]
|
||||
fn a_reopened_disk_store_reads_after_two_persona_puts() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut mind = Mind::open(root.path(), &slug(INSTANCE)).unwrap();
|
||||
seed(&mut mind);
|
||||
committed(put(&mut mind, persona(INSTANCE, T1), None));
|
||||
committed(put(&mut mind, persona(INSTANCE, T2), Some(T1)));
|
||||
}
|
||||
let mind = Mind::open(root.path(), &slug(INSTANCE)).unwrap();
|
||||
assert_eq!(mind.persona().unwrap().unwrap().updated_at, T2);
|
||||
assert_reads_answer(&mind);
|
||||
}
|
||||
|
||||
/// The exemption is the persona's alone: a pipeline document written by
|
||||
/// two receipts still refuses, persona re-puts or not.
|
||||
#[test]
|
||||
fn a_doubled_pipeline_write_still_refuses_beside_persona_reputs() {
|
||||
let store = MemoryStore::new();
|
||||
let mut mind = seeded_over(&store);
|
||||
committed(put(&mut mind, persona(INSTANCE, T1), None));
|
||||
committed(put(&mut mind, persona(INSTANCE, T2), Some(T1)));
|
||||
let doubled = MemoryStore::new();
|
||||
for row in store.rows() {
|
||||
doubled.plant(row);
|
||||
}
|
||||
let head = crate::receipt::head(&mind).unwrap();
|
||||
let again = crate::fixtures::prepare(&crate::fixtures::instance(INSTANCE));
|
||||
let forged = crate::receipt::candidate(&slug(INSTANCE), provenance(Faculty::Soul), &[], &[again], head + 1, crate::fixtures::now())
|
||||
.unwrap();
|
||||
doubled.plant(crate::mind::schema_cache().unwrap().prepare_entry_named(&forged.receipt_id, &forged).unwrap().0);
|
||||
let refusal = opened(doubled, INSTANCE).query(&Selection::default()).err();
|
||||
assert!(
|
||||
matches!(&refusal, Some(MindRefusal::Unavailable { detail }) if detail.ends_with("is written by two receipts")),
|
||||
"{refusal:?}"
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_reads_answer(mind: &Mind<impl MindStore>) {
|
||||
let page = mind.query(&Selection::default()).unwrap();
|
||||
assert!(page.matched > 0);
|
||||
let first = match &page.items {
|
||||
crate::query::PipelinePageItems::Headers(headers) => headers[0].id.clone(),
|
||||
crate::query::PipelinePageItems::Documents(views) => views[0].id.clone(),
|
||||
};
|
||||
assert!(mind.view(&first).unwrap().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_persona_document_is_validated_at_the_door() {
|
||||
let store = MemoryStore::new();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use cultcache_rs::DatabaseEntry;
|
||||
use cultnet_rs::{
|
||||
Cursor, EdgeAnchor, EdgeMatch, Evaluation, LIMIT_MAX, LIMIT_MIN, PROJECTION_DOCUMENT, Row, Selection, select, validate,
|
||||
};
|
||||
|
|
@ -35,6 +36,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::docs::{CitationRole, Docs, Held, citations, kind_of_type};
|
||||
use crate::mind::{Mind, schema_cache};
|
||||
use crate::persona::HuginnPersonaEntry;
|
||||
use crate::receipt::{self, PipelineProvenance};
|
||||
use crate::refusal::MindRefusal;
|
||||
use crate::rows::{SelectionRow, Vocabulary, refuse_values};
|
||||
|
|
@ -310,8 +312,12 @@ pub struct Ranked {
|
|||
|
||||
/// The admission facts of every stored document, built once per read call from
|
||||
/// the receipts. `writes` only: a document a later batch cited as a strong read
|
||||
/// keeps the receipt that wrote it. A10 admits one write per identity, so two
|
||||
/// receipts naming one document is an integrity fault, not a choice to make.
|
||||
/// keeps the receipt that wrote it. A10 admits one write per pipeline identity,
|
||||
/// so two receipts naming one pipeline document is an integrity fault, not a
|
||||
/// choice to make. The persona document is the one identity replaced by design
|
||||
/// (`put_persona` is a compare-and-swap on `updatedAt`) and is not a pipeline
|
||||
/// document: it has no admission facts here, and `Mind::persona` reads its own
|
||||
/// receipt.
|
||||
pub(crate) struct AdmissionIndex(BTreeMap<(String, String), AdmissionFacts>);
|
||||
|
||||
impl AdmissionIndex {
|
||||
|
|
@ -323,7 +329,7 @@ impl AdmissionIndex {
|
|||
receipt::head(mind)?;
|
||||
let mut facts = BTreeMap::new();
|
||||
for receipt in mind.receipts()? {
|
||||
for write in receipt.writes.iter() {
|
||||
for write in receipt.writes.iter().filter(|write| write.document_type != HuginnPersonaEntry::TYPE) {
|
||||
let identity = (write.document_type.clone(), write.document_key.clone());
|
||||
let landed = AdmissionFacts {
|
||||
receipt_id: receipt.receipt_id.clone(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue