Drive open_items with faults inside the requested campaign
F3: the only existing fault for the open-items dispatch arm (an orphaned instance document) sits outside the campaign a request asks about, so two loosenings survive unnoticed: one that swallows an Unavailable naming a document in the requested campaign, and one that swallows every Unavailable except a missing receipt. Add open_items_refuses_on_a_fault_inside_the_requested_campaign, driving a missing receipt, an undecodable stored document (docs.rs:46) and a document written by two receipts (query.rs:145), each on a cut spec rooted in the requested campaign. Add mutation entries S1 and S1d against Mind::open_items; both die against the new test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
4ecbf53056
commit
d2b74f49af
2 changed files with 174 additions and 4 deletions
|
|
@ -622,6 +622,126 @@ pub(crate) mod tests {
|
|||
assert_eq!(daemon.handle(items, now()), HuginnMindResponse::Refused(orphaned));
|
||||
}
|
||||
|
||||
/// The fault the test above drives sits outside the campaign asked about:
|
||||
/// the orphan is the mind's own identity document, rooted at the instance,
|
||||
/// not at `CAMPAIGN`. A loosening that only refuses a fault *outside* the
|
||||
/// requested campaign, and one that swallows every fault except a missing
|
||||
/// receipt, both survive it. This drives the same arm with a fault on a
|
||||
/// document rooted inside the requested campaign, of each kind Soul named:
|
||||
/// a missing receipt, a document that does not decode, and a document two
|
||||
/// receipts both claim to have written.
|
||||
#[test]
|
||||
fn open_items_refuses_on_a_fault_inside_the_requested_campaign() {
|
||||
use huginn_mind::HuginnCommitReceipt;
|
||||
use huginn_mind::store::{CacheBackingStore, DatabaseEntry};
|
||||
|
||||
// A campaign-rooted document (a cut spec) admitted alone, so its own
|
||||
// receipt writes nothing else and can be found and broken without
|
||||
// touching the identity or stewardship receipt from the seed batch.
|
||||
fn campaign_rooted_cut_spec(
|
||||
root: &std::path::Path,
|
||||
cut: &str,
|
||||
) -> (String, OwnedRedbMessagePackBackingStore) {
|
||||
let mut daemon = Daemon::open(root, &slug(INSTANCE)).unwrap();
|
||||
let seeded = daemon.handle(HuginnMindRequest::Admit(batch(INSTANCE, campaign_seed())), now());
|
||||
assert!(matches!(seeded, HuginnMindResponse::Admit(PipelineAdmissionOutcome::Committed { .. })));
|
||||
let outcome = daemon.handle(HuginnMindRequest::Admit(batch(INSTANCE, vec![cut_spec(cut, 1)])), now());
|
||||
let HuginnMindResponse::Admit(PipelineAdmissionOutcome::Committed { writes, .. }) = outcome else {
|
||||
panic!("expected a commit");
|
||||
};
|
||||
assert_eq!(writes.len(), 1, "a cut spec alone derives nothing");
|
||||
let cut_spec_id = writes[0].id.0.clone();
|
||||
drop(daemon);
|
||||
(cut_spec_id, OwnedRedbMessagePackBackingStore::new(&Mind::<OwnedRedbMessagePackBackingStore>::path_for(root, &slug(INSTANCE))).unwrap())
|
||||
}
|
||||
|
||||
let cut_spec_type = PipelineKind::CutSpec.type_id();
|
||||
|
||||
// A missing receipt, this time on a document rooted in `CAMPAIGN`.
|
||||
{
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let (cut_spec_id, mut store) = campaign_rooted_cut_spec(root.path(), "orphan-in-campaign");
|
||||
let rows = store.pull_all().unwrap();
|
||||
let receipt_row = rows
|
||||
.iter()
|
||||
.find(|row| {
|
||||
row.r#type == HuginnCommitReceipt::TYPE
|
||||
&& rmp_serde::from_slice::<HuginnCommitReceipt>(&row.payload)
|
||||
.map(|receipt| receipt.writes.iter().any(|write| write.document_key == cut_spec_id))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.expect("the cut spec's own receipt")
|
||||
.clone();
|
||||
store.delete(&receipt_row).unwrap();
|
||||
drop(store);
|
||||
|
||||
let mut daemon = Daemon::open(root.path(), &slug(INSTANCE)).expect("an orphaned image still opens");
|
||||
let expected = MindRefusal::Unavailable {
|
||||
detail: format!("document {cut_spec_type}/{cut_spec_id} has no commit receipt"),
|
||||
};
|
||||
let items = HuginnMindRequest::OpenItems { instance: slug(INSTANCE), campaign: slug(CAMPAIGN) };
|
||||
assert_eq!(daemon.handle(items, now()), HuginnMindResponse::Refused(expected));
|
||||
}
|
||||
|
||||
// An undecodable stored document, rooted in `CAMPAIGN`.
|
||||
{
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let (cut_spec_id, mut store) = campaign_rooted_cut_spec(root.path(), "garbled-in-campaign");
|
||||
let rows = store.pull_all().unwrap();
|
||||
let mut doc_row =
|
||||
rows.iter().find(|row| row.r#type == cut_spec_type && row.key == cut_spec_id).unwrap().clone();
|
||||
store.delete(&doc_row).unwrap();
|
||||
doc_row.payload = b"not messagepack".to_vec();
|
||||
store.push(&doc_row).unwrap();
|
||||
drop(store);
|
||||
|
||||
let mut daemon = Daemon::open(root.path(), &slug(INSTANCE)).expect("a garbled document still opens");
|
||||
let items = HuginnMindRequest::OpenItems { instance: slug(INSTANCE), campaign: slug(CAMPAIGN) };
|
||||
let HuginnMindResponse::Refused(MindRefusal::Unavailable { detail }) = daemon.handle(items, now())
|
||||
else {
|
||||
panic!("a document that does not decode must refuse, not answer");
|
||||
};
|
||||
assert!(
|
||||
detail.starts_with(&format!("stored document {cut_spec_id} does not decode")),
|
||||
"unexpected detail: {detail}"
|
||||
);
|
||||
}
|
||||
|
||||
// A document two receipts both claim to have written, rooted in
|
||||
// `CAMPAIGN`: the cut spec's own receipt, cloned under a new key and
|
||||
// id, so the same identity is now landed twice.
|
||||
{
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let (cut_spec_id, mut store) = campaign_rooted_cut_spec(root.path(), "doubled-in-campaign");
|
||||
let rows = store.pull_all().unwrap();
|
||||
let receipt_row = rows
|
||||
.iter()
|
||||
.find(|row| {
|
||||
row.r#type == HuginnCommitReceipt::TYPE
|
||||
&& rmp_serde::from_slice::<HuginnCommitReceipt>(&row.payload)
|
||||
.map(|receipt| receipt.writes.iter().any(|write| write.document_key == cut_spec_id))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.expect("the cut spec's own receipt")
|
||||
.clone();
|
||||
let mut receipt: HuginnCommitReceipt = rmp_serde::from_slice(&receipt_row.payload).unwrap();
|
||||
receipt.receipt_id = format!("{}-forged", receipt.receipt_id);
|
||||
let mut forged_row = receipt_row.clone();
|
||||
forged_row.key = receipt.receipt_id.clone();
|
||||
forged_row.payload = rmp_serde::to_vec_named(&receipt).unwrap();
|
||||
store.push(&forged_row).unwrap();
|
||||
drop(store);
|
||||
|
||||
let mut daemon =
|
||||
Daemon::open(root.path(), &slug(INSTANCE)).expect("two receipts for one document still opens");
|
||||
let expected = MindRefusal::Unavailable {
|
||||
detail: format!("document {cut_spec_type}/{cut_spec_id} is written by two receipts"),
|
||||
};
|
||||
let items = HuginnMindRequest::OpenItems { instance: slug(INSTANCE), campaign: slug(CAMPAIGN) };
|
||||
assert_eq!(daemon.handle(items, now()), HuginnMindResponse::Refused(expected));
|
||||
}
|
||||
}
|
||||
|
||||
/// Cut 11's seam: the index is handed every landed write, derived ones
|
||||
/// included, and can read each; an index that fails changes nothing.
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# Cut 10 mutations, D1-D22: the CultNet surface's rules. Each rule has a
|
||||
# revert, `Dn`, which removes the rule, and a loosening, `DnL`, which weakens
|
||||
# it rather than removing it, because a plain revert is the easy target. Every
|
||||
# entry names the test that must fail while it is applied. Run through
|
||||
# Epiphany's harness from this repo:
|
||||
# entry names the test that must fail while it is applied. Run through the
|
||||
# Eureka skill's harness from this repo:
|
||||
#
|
||||
# $env:CARGO_TARGET_DIR = 'C:\Users\Meta\.cargo-target-codex'
|
||||
# powershell -File F:\Projects\Epiphany\tools\eureka-mutations.ps1 -Repo F:\Projects\Huginn `
|
||||
# powershell -File C:\Users\Meta\.claude\skills\eureka\tools\eureka-mutations.ps1 -Repo F:\Projects\Huginn `
|
||||
# -Entries tools/eureka-cut10-mutations.psd1 `
|
||||
# -Target crates/huginn-mind/src/mind.rs,crates/huginn-mind/src/wire.rs,crates/huginn-daemon/src/daemon.rs,crates/huginn-daemon/src/envelope.rs,crates/huginn-daemon/src/serve.rs,schemas/cultnet/huginn.mind_request.v1.schema.json,schemas/cultnet/huginn.mind_response.v1.schema.json `
|
||||
# -Target crates/huginn-mind/src/mind.rs,crates/huginn-mind/src/query.rs,crates/huginn-mind/src/wire.rs,crates/huginn-daemon/src/daemon.rs,crates/huginn-daemon/src/envelope.rs,crates/huginn-daemon/src/serve.rs,schemas/cultnet/huginn.mind_request.v1.schema.json,schemas/cultnet/huginn.mind_response.v1.schema.json `
|
||||
# -Test 'cargo test -p huginn-daemon --lib'
|
||||
#
|
||||
# Entries whose test lives in `huginn-mind` carry their own `Command`. `D1` and
|
||||
|
|
@ -540,6 +540,56 @@ pub fn require_grammatical_instance(declared: &Slug) -> Result<(), MindRefusal>
|
|||
specs_without_report: vec![],
|
||||
reports_without_verdict: vec![],
|
||||
}),
|
||||
'@
|
||||
}
|
||||
@{
|
||||
Id = 'S1'
|
||||
Rule = 'open_items refuses on a fault anywhere in the image it must read, not only on one outside the requested campaign.'
|
||||
Test = 'daemon::tests::open_items_refuses_on_a_fault_inside_the_requested_campaign'
|
||||
Command = 'cargo test -p huginn-daemon --lib'
|
||||
File = 'crates/huginn-mind/src/query.rs'
|
||||
Old = @'
|
||||
let reader = Reader::new(self)?;
|
||||
let views = reader
|
||||
.views()?
|
||||
.into_iter()
|
||||
.filter(|view| root_and_local(&view.id.id.0).0 == campaign.0)
|
||||
.collect::<Vec<_>>();
|
||||
'@
|
||||
New = @'
|
||||
let views = match Reader::new(self).and_then(|reader| reader.views()) {
|
||||
Ok(views) => views,
|
||||
Err(MindRefusal::Unavailable { detail }) if detail.contains(campaign.0.as_str()) => Vec::new(),
|
||||
Err(other) => return Err(other),
|
||||
}
|
||||
.into_iter()
|
||||
.filter(|view| root_and_local(&view.id.id.0).0 == campaign.0)
|
||||
.collect::<Vec<_>>();
|
||||
'@
|
||||
}
|
||||
@{
|
||||
Id = 'S1d'
|
||||
Rule = 'It refuses on every kind of fault a document can carry, not only a missing receipt.'
|
||||
Test = 'daemon::tests::open_items_refuses_on_a_fault_inside_the_requested_campaign'
|
||||
Command = 'cargo test -p huginn-daemon --lib'
|
||||
File = 'crates/huginn-mind/src/query.rs'
|
||||
Old = @'
|
||||
let reader = Reader::new(self)?;
|
||||
let views = reader
|
||||
.views()?
|
||||
.into_iter()
|
||||
.filter(|view| root_and_local(&view.id.id.0).0 == campaign.0)
|
||||
.collect::<Vec<_>>();
|
||||
'@
|
||||
New = @'
|
||||
let views = match Reader::new(self).and_then(|reader| reader.views()) {
|
||||
Ok(views) => views,
|
||||
Err(MindRefusal::Unavailable { detail }) if !detail.contains("has no commit receipt") => Vec::new(),
|
||||
Err(other) => return Err(other),
|
||||
}
|
||||
.into_iter()
|
||||
.filter(|view| root_and_local(&view.id.id.0).0 == campaign.0)
|
||||
.collect::<Vec<_>>();
|
||||
'@
|
||||
}
|
||||
@{
|
||||
|
|
|
|||
Loading…
Reference in a new issue