admission: skip the status check only for a citation into another campaign; drop the unreachable run-claims skip; a test for each live site
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6beb52e809
commit
f4f857a7da
2 changed files with 102 additions and 9 deletions
|
|
@ -538,8 +538,9 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug, import: Option<&ImportSo
|
|||
use PipelineKind as K;
|
||||
let key = staged.key.as_str();
|
||||
let external: &[PipelineRef] = import.map_or(&[], |source| source.external.as_slice());
|
||||
// An import replays a batch its source admitted at its own ordinal: a referent's status here may
|
||||
// have moved on since (another campaign's import carried the resolution), so no rule judges it.
|
||||
// An import replays the giver's order within a campaign, so a same-campaign referent has its status at
|
||||
// the source ordinal. Only a citation into another campaign drifts (that campaign's import may have
|
||||
// carried a later resolution), so only a cross-campaign citation is not judged by current status.
|
||||
let historic = import.is_some();
|
||||
if let D::Resolution(resolution) = &staged.document
|
||||
&& matches!(resolution.outcome, ResolutionOutcome::Withdrawn { .. })
|
||||
|
|
@ -630,7 +631,7 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug, import: Option<&ImportSo
|
|||
return Err(MindRefusal::RepoNotInCampaign { repo: spec.repo.0.clone() });
|
||||
}
|
||||
for ruling in &spec.rulings {
|
||||
if !historic && !docs.in_force(K::Ruling, &ruling.0) {
|
||||
if !(historic && root_and_local(&ruling.0).0 != root) && !docs.in_force(K::Ruling, &ruling.0) {
|
||||
return Err(MindRefusal::CitesResolvedDocument { kind: K::Ruling, id: ruling.0.clone() });
|
||||
}
|
||||
}
|
||||
|
|
@ -656,7 +657,7 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug, import: Option<&ImportSo
|
|||
}
|
||||
return Err(MindRefusal::CutReportWithoutSpec { report: key.into() });
|
||||
};
|
||||
if !historic && !docs.in_force(K::CutSpec, &report.cut_spec.0) {
|
||||
if !(historic && root_and_local(&report.cut_spec.0).0 != root) && !docs.in_force(K::CutSpec, &report.cut_spec.0) {
|
||||
return Err(MindRefusal::CitesResolvedDocument { kind: K::CutSpec, id: report.cut_spec.0.clone() });
|
||||
}
|
||||
if report.repo != spec.repo {
|
||||
|
|
@ -765,7 +766,7 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug, import: Option<&ImportSo
|
|||
// rule.
|
||||
D::Run(run) => {
|
||||
let replay = docs.in_image(K::Run, key) == Some(&staged.document);
|
||||
run_rule(docs, key, run, replay, historic)
|
||||
run_rule(docs, key, run, replay)
|
||||
}
|
||||
D::Memory(memory) => consolidation_rule(docs, key, memory),
|
||||
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Reply(_) | D::Thought(_) | D::Research(_) | D::Idea(_) => Ok(()),
|
||||
|
|
@ -775,9 +776,9 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug, import: Option<&ImportSo
|
|||
/// The one rule for a run opening or reinstated by withdrawing its close: every
|
||||
/// claim in force (a replay of an admitted run owes none) and held by no other
|
||||
/// live run, then at most one live run of hers per instance and turn.
|
||||
fn run_rule(docs: &Docs<'_>, run_key: &str, run: &PipelineRun, replay: bool, historic: bool) -> Result<(), MindRefusal> {
|
||||
fn run_rule(docs: &Docs<'_>, run_key: &str, run: &PipelineRun, replay: bool) -> Result<(), MindRefusal> {
|
||||
for claim in &run.claims {
|
||||
if !replay && !historic && !docs.in_force(claim.kind, &claim.id.0) {
|
||||
if !replay && !docs.in_force(claim.kind, &claim.id.0) {
|
||||
return Err(MindRefusal::CitesResolvedDocument { kind: claim.kind, id: claim.id.0.clone() });
|
||||
}
|
||||
if let Some(holder) = docs.claim_holder(run_key, claim) {
|
||||
|
|
@ -976,11 +977,11 @@ fn resolution_rule(docs: &Docs<'_>, resolution: &PipelineResolution, historic: b
|
|||
&& reinstating.subject.kind == K::Run
|
||||
&& let Some(D::Run(run)) = docs.find(K::Run, &reinstating.subject.id.0)
|
||||
{
|
||||
run_rule(docs, &reinstating.subject.id.0, run, false, historic)?;
|
||||
run_rule(docs, &reinstating.subject.id.0, run, false)?;
|
||||
}
|
||||
for (role, referent) in outcome_citations(&resolution.outcome) {
|
||||
// The last memory a night was shown may have been consumed by that night's own batches.
|
||||
if role != CitationRole::DayThrough && !historic && !docs.in_force(referent.kind, &referent.id.0) {
|
||||
if role != CitationRole::DayThrough && !(historic && root_and_local(&referent.id.0).0 != root_and_local(subject_id).0) && !docs.in_force(referent.kind, &referent.id.0) {
|
||||
return Err(MindRefusal::CitesResolvedDocument { kind: referent.kind, id: referent.id.0 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -737,3 +737,95 @@ fn replay_root(giver: &Mind<MemoryStore>, receiver: &mut Mind<MemoryStore>, root
|
|||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn ruling_of(root: &str, label: &str) -> D {
|
||||
let mut value = ruling(label);
|
||||
value.campaign = slug(root);
|
||||
D::Ruling(value)
|
||||
}
|
||||
|
||||
fn ruling_ref(root: &str, label: &str) -> PipelineRef {
|
||||
r(K::Ruling, &format!("{root}:ruling:{label}"))
|
||||
}
|
||||
|
||||
fn spec_ref(root: &str, revision: u32) -> PipelineRef {
|
||||
r(K::CutSpec, &format!("{root}:cut_spec:cut-1.r{revision}"))
|
||||
}
|
||||
|
||||
fn spec_of(root: &str, revision: u32) -> D {
|
||||
let mut value = cut_spec("1", revision);
|
||||
value.campaign = slug(root);
|
||||
D::CutSpec(value)
|
||||
}
|
||||
|
||||
fn report_citing(spec: &PipelineRef) -> D {
|
||||
let mut value = cut_report("1", 1);
|
||||
value.cut_spec = spec.id.clone();
|
||||
D::CutReport(value)
|
||||
}
|
||||
|
||||
fn spec_citing(ruling: &PipelineRef) -> D {
|
||||
let mut value = cut_spec("2", 1);
|
||||
value.rulings = vec![ruling.id.clone()];
|
||||
D::CutSpec(value)
|
||||
}
|
||||
|
||||
/// A receiver holding `root` whole, in which `old` is superseded by `new` (both of that root).
|
||||
fn receiver_with_superseded(root: &str, opening: Vec<D>, old: PipelineRef, new: PipelineRef) -> Mind<MemoryStore> {
|
||||
let mut mind = receiver();
|
||||
let mut first = vec![if root == CAMPAIGN { campaign(&[REPO]) } else { other_campaign() }];
|
||||
first.extend(opening);
|
||||
committed(import(&mut mind, first, vec![]));
|
||||
committed(import(&mut mind, vec![resolution(old, superseded(&[new]))], vec![]));
|
||||
mind
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_imported_report_may_cite_another_campaigns_spec_superseded_since() {
|
||||
let (old, new) = (spec_ref(OTHER_ROOT, 1), spec_ref(OTHER_ROOT, 2));
|
||||
let mut mind = receiver_with_superseded(OTHER_ROOT, vec![spec_of(OTHER_ROOT, 1), spec_of(OTHER_ROOT, 2)], old.clone(), new);
|
||||
committed(import(&mut mind, vec![campaign(&[REPO])], vec![]));
|
||||
let (_, writes) = committed(import(&mut mind, vec![report_citing(&old)], vec![]));
|
||||
assert_eq!(writes.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_imported_report_citing_its_own_campaigns_superseded_spec_is_refused() {
|
||||
let (old, new) = (spec_ref(CAMPAIGN, 1), spec_ref(CAMPAIGN, 2));
|
||||
let mut mind = receiver_with_superseded(CAMPAIGN, vec![spec_of(CAMPAIGN, 1), spec_of(CAMPAIGN, 2)], old.clone(), new);
|
||||
assert_eq!(
|
||||
refusal(import(&mut mind, vec![report_citing(&old)], vec![])),
|
||||
MindRefusal::CitesResolvedDocument { kind: K::CutSpec, id: old.id.0 }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_imported_spec_may_cite_another_campaigns_ruling_superseded_since() {
|
||||
let (old, new) = (ruling_ref(OTHER_ROOT, "R1"), ruling_ref(OTHER_ROOT, "R2"));
|
||||
let mut mind = receiver_with_superseded(OTHER_ROOT, vec![ruling_of(OTHER_ROOT, "R1"), ruling_of(OTHER_ROOT, "R2")], old.clone(), new);
|
||||
committed(import(&mut mind, vec![campaign(&[REPO])], vec![]));
|
||||
let (_, writes) = committed(import(&mut mind, vec![spec_citing(&old)], vec![]));
|
||||
assert_eq!(writes.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_imported_spec_citing_its_own_campaigns_superseded_ruling_is_refused() {
|
||||
let (old, new) = (ruling_ref(CAMPAIGN, "R1"), ruling_ref(CAMPAIGN, "R2"));
|
||||
let mut mind = receiver_with_superseded(CAMPAIGN, vec![ruling_of(CAMPAIGN, "R1"), ruling_of(CAMPAIGN, "R2")], old.clone(), new);
|
||||
assert_eq!(
|
||||
refusal(import(&mut mind, vec![spec_citing(&old)], vec![])),
|
||||
MindRefusal::CitesResolvedDocument { kind: K::Ruling, id: old.id.0 }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_imported_follow_up_may_not_be_superseded_by_its_own_campaigns_closed_follow_up() {
|
||||
let q1 = r(K::Question, &id("question", "Q1"));
|
||||
let mut mind = receiver();
|
||||
committed(import(&mut mind, vec![campaign(&[REPO]), question("Q1", &["A", "B"], "A"), follow_up("FU-A", q1.clone()), follow_up("FU-B", q1)], vec![]));
|
||||
committed(import(&mut mind, vec![resolution(fu(CAMPAIGN, "FU-A"), ResolutionOutcome::Recorded { reason: "noted".into() })], vec![]));
|
||||
assert_eq!(
|
||||
refusal(import(&mut mind, vec![resolution(fu(CAMPAIGN, "FU-B"), superseded(&[fu(CAMPAIGN, "FU-A")]))], vec![])),
|
||||
MindRefusal::CitesResolvedDocument { kind: K::FollowUp, id: fu(CAMPAIGN, "FU-A").id.0 }
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue