Mind: run admission with AlreadyClaimed, the run resolution row, the claims edge
A run's instance must be this mind. Every claim must name a document in force
(MissingReference, CitesResolvedDocument); a claim held by another run in force
is refused AlreadyClaimed { item, run }, so the claim is the one consumption
fact. A byte-identical replay of an admitted run owes its claims no fresh
freedom and answers AlreadyAdmitted. A run resolves Recorded or Withdrawn only.
Runs cite their claims under role `claims`, so cites and cited selections reach
them; the header facts carry label, operator, turn, start and claims.
The published schemas that embed PipelineKind (finding, follow_up, question,
resolution, ruling) and the two mind wire schemas are re-derived: the Run
variant, PipelineFacts::Run, AlreadyClaimed and the claims role. The leaf commit
before this one has those five stale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6c705f5f2a
commit
bb9e64dca1
15 changed files with 482 additions and 30 deletions
|
|
@ -221,13 +221,14 @@ fn stage(envelope: CultCacheEnvelope, mind: &Slug) -> Result<Staged, MindRefusal
|
|||
Ok(Staged { kind: document.kind(), key: envelope.key.clone(), document, envelope })
|
||||
}
|
||||
|
||||
/// A5: every document carrying an instance field names this mind; a hand-off
|
||||
/// names it on one side.
|
||||
/// A5: every document carrying an instance field names this mind (a run
|
||||
/// included); a hand-off names it on one side.
|
||||
fn refuse_foreign_instance(document: &PipelineDocument, mind: &Slug) -> Result<(), MindRefusal> {
|
||||
let foreign = |declared: &Slug| MindRefusal::ForeignInstance { declared: declared.0.clone(), mind: mind.0.clone() };
|
||||
match document {
|
||||
PipelineDocument::Instance(value) if value.instance != *mind => Err(foreign(&value.instance)),
|
||||
PipelineDocument::Stewardship(value) if value.instance != *mind => Err(foreign(&value.instance)),
|
||||
PipelineDocument::Run(value) if value.instance != *mind => Err(foreign(&value.instance)),
|
||||
PipelineDocument::HandOff(value) if value.from_instance != *mind && value.to_instance != *mind => {
|
||||
Err(foreign(&value.from_instance))
|
||||
}
|
||||
|
|
@ -517,6 +518,27 @@ fn check(docs: &Docs, staged: &Staged, mind: &Slug) -> Result<(), MindRefusal> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
// run-is-the-grant: the claim is the one consumption fact. A claim must
|
||||
// be in force, and no other run in force may hold it. A run already in
|
||||
// the image under this key, byte for byte, is a replay and owes its
|
||||
// claims no fresh freedom, so the replay still answers AlreadyAdmitted
|
||||
// after the work it claimed has moved on.
|
||||
D::Run(run) => {
|
||||
let replay = docs.in_image(K::Run, key) == Some(&staged.document);
|
||||
for claim in &run.claims {
|
||||
if !replay && !docs.in_force(claim.kind, &claim.id.0) {
|
||||
return Err(MindRefusal::CitesResolvedDocument { kind: claim.kind, id: claim.id.0.clone() });
|
||||
}
|
||||
let holder = docs.of_kind(K::Run).find_map(|(other_key, document)| match document {
|
||||
D::Run(other) if other_key != key && other.claims.contains(claim) && docs.in_force(K::Run, other_key) => Some(other_key),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(holder) = holder {
|
||||
return Err(MindRefusal::AlreadyClaimed { item: claim.id.0.clone(), run: holder.into() });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
D::FollowUp(_) | D::Instance(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
|
@ -593,6 +615,7 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool {
|
|||
(K::FollowUp, O::Withdrawn { .. }) => true,
|
||||
(K::Stewardship, O::Superseded { by }) => all(by, K::Stewardship),
|
||||
(K::Stewardship, O::Withdrawn { .. }) => true,
|
||||
(K::Run, O::Recorded { .. } | O::Withdrawn { .. }) => true,
|
||||
(K::Resolution, O::Withdrawn { .. }) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
|
@ -1925,6 +1948,126 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
fn spec_ref(cut: &str) -> PipelineRef {
|
||||
r(K::CutSpec, &id("cut_spec", &format!("cut-{cut}.r1")))
|
||||
}
|
||||
|
||||
fn run_key(label: &str) -> String {
|
||||
format!("{INSTANCE}:run:{label}")
|
||||
}
|
||||
|
||||
/// A seeded mind holding cut specs 1 and 2.
|
||||
fn with_specs() -> Mind<MemoryStore> {
|
||||
let mut mind = seeded();
|
||||
committed(admit(&mut mind, vec![D::CutSpec(cut_spec("1", 1)), D::CutSpec(cut_spec("2", 1))]));
|
||||
mind
|
||||
}
|
||||
|
||||
fn recorded(label: &str) -> PipelineDocument {
|
||||
resolution(r(K::Run, &run_key(label)), ResolutionOutcome::Recorded { reason: "finished".into() })
|
||||
}
|
||||
|
||||
/// run-is-the-grant: the claim is the only consumption fact. A second run
|
||||
/// on a claim a live run holds is AlreadyClaimed, naming the claim and the
|
||||
/// holder; another claim is free; once the holder is Recorded the claim is
|
||||
/// free, and withdrawing that record makes the holder live again.
|
||||
#[test]
|
||||
fn a_second_run_on_a_live_claim_is_already_claimed() {
|
||||
let mut mind = with_specs();
|
||||
committed(admit(&mut mind, vec![run("a", &[spec_ref("1")])]));
|
||||
let claimed = MindRefusal::AlreadyClaimed { item: spec_ref("1").id.0, run: run_key("a") };
|
||||
assert_eq!(refusal(admit(&mut mind, vec![run("b", &[spec_ref("1")])])), claimed);
|
||||
assert_eq!(refusal(admit(&mut mind, vec![run("b", &[spec_ref("2"), spec_ref("1")])])), claimed, "any one held claim refuses the run");
|
||||
committed(admit(&mut mind, vec![run("b", &[spec_ref("2")])]));
|
||||
committed(admit(&mut mind, vec![recorded("a")]));
|
||||
committed(admit(&mut mind, vec![run("c", &[spec_ref("1")])]));
|
||||
|
||||
let mut reopened = with_specs();
|
||||
committed(admit(&mut reopened, vec![run("a", &[spec_ref("1")])]));
|
||||
committed(admit(&mut reopened, vec![recorded("a")]));
|
||||
let closure = r(K::Resolution, &format!("{INSTANCE}:resolution:run.a.n1"));
|
||||
committed(admit(&mut reopened, vec![resolution(closure, withdrawn())]));
|
||||
assert_eq!(refusal(admit(&mut reopened, vec![run("d", &[spec_ref("1")])])), claimed);
|
||||
}
|
||||
|
||||
/// Two openers on one claim in one batch: the claim is held by neither, so
|
||||
/// the batch is refused whole and nothing is written. One opener at a time
|
||||
/// is `a_second_run_on_a_live_claim_is_already_claimed`.
|
||||
#[test]
|
||||
fn two_openers_in_one_batch_hold_nothing() {
|
||||
let mut mind = with_specs();
|
||||
let refused = refusal(admit(&mut mind, vec![run("a", &[spec_ref("1")]), run("b", &[spec_ref("1")])]));
|
||||
assert!(matches!(refused, MindRefusal::AlreadyClaimed { .. }), "{refused:?}");
|
||||
assert!(mind.envelope(K::Run, &run_key("a")).is_none() && mind.envelope(K::Run, &run_key("b")).is_none());
|
||||
}
|
||||
|
||||
/// Every claim names a document that exists and is in force.
|
||||
#[test]
|
||||
fn a_runs_claims_must_exist_and_be_in_force() {
|
||||
let mut mind = with_specs();
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![run("a", &[spec_ref("9")])])),
|
||||
MindRefusal::MissingReference { kind: K::CutSpec, id: spec_ref("9").id.0 }
|
||||
);
|
||||
committed(admit(&mut mind, vec![resolution(spec_ref("2"), withdrawn())]));
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![run("a", &[spec_ref("2")])])),
|
||||
MindRefusal::CitesResolvedDocument { kind: K::CutSpec, id: spec_ref("2").id.0 }
|
||||
);
|
||||
assert!(mind.envelope(K::Run, &run_key("a")).is_none());
|
||||
}
|
||||
|
||||
/// A replay of an admitted run is AlreadyAdmitted even when its claim has
|
||||
/// since been closed: a retry owes nothing to the work moving on. A
|
||||
/// different run on that closed claim is still refused.
|
||||
#[test]
|
||||
fn replaying_an_admitted_run_after_its_claim_closed_is_already_admitted() {
|
||||
let mut mind = with_specs();
|
||||
let (first, _) = committed(admit(&mut mind, vec![run("a", &[spec_ref("1")])]));
|
||||
committed(admit(&mut mind, vec![resolution(spec_ref("1"), withdrawn())]));
|
||||
assert_eq!(admit(&mut mind, vec![run("a", &[spec_ref("1")])]), PipelineAdmissionOutcome::AlreadyAdmitted { receipt_id: first });
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![run("b", &[spec_ref("1")])])),
|
||||
MindRefusal::CitesResolvedDocument { kind: K::CutSpec, id: spec_ref("1").id.0 }
|
||||
);
|
||||
}
|
||||
|
||||
/// A5 for runs: a run of another instance is a ForeignInstance.
|
||||
#[test]
|
||||
fn a_run_of_another_mind_is_a_foreign_instance() {
|
||||
let mut mind = with_specs();
|
||||
let D::Run(mut foreign) = run("a", &[]) else { panic!() };
|
||||
foreign.instance = slug(OTHER_INSTANCE);
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![D::Run(foreign)])),
|
||||
MindRefusal::ForeignInstance { declared: OTHER_INSTANCE.into(), mind: INSTANCE.into() }
|
||||
);
|
||||
}
|
||||
|
||||
/// A run ends: Recorded or Withdrawn, and no other outcome.
|
||||
#[test]
|
||||
fn a_run_resolves_recorded_or_withdrawn_and_nothing_else() {
|
||||
let incompatible = |outcome: &str| MindRefusal::IncompatibleResolution { subject_kind: K::Run, outcome: outcome.into() };
|
||||
let subject = || r(K::Run, &run_key("a"));
|
||||
let open = || {
|
||||
let mut mind = with_specs();
|
||||
committed(admit(&mut mind, vec![D::Ruling(ruling("R1")), run("a", &[spec_ref("1")]), run("z", &[])]));
|
||||
mind
|
||||
};
|
||||
committed(admit(&mut open(), vec![recorded("a")]));
|
||||
committed(admit(&mut open(), vec![resolution(subject(), withdrawn())]));
|
||||
let other_run = r(K::Run, &run_key("z"));
|
||||
let refused = [
|
||||
(superseded(&[other_run.clone()]), "Superseded"),
|
||||
(ResolutionOutcome::Answered { by: r(K::Ruling, &id("ruling", "R1")) }, "Answered"),
|
||||
(ResolutionOutcome::Fixed { commit: sha(), by: None }, "Fixed"),
|
||||
(ResolutionOutcome::Deferred { to: other_run }, "Deferred"),
|
||||
];
|
||||
for (outcome, name) in refused {
|
||||
assert_eq!(refusal(admit(&mut open(), vec![resolution(subject(), outcome)])), incompatible(name), "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depends_on_names_cuts() {
|
||||
let depending = |cut: &str, on: &[&str]| {
|
||||
|
|
|
|||
|
|
@ -286,10 +286,11 @@ pub enum CitationRole {
|
|||
ResolvedBy,
|
||||
DeferredTo,
|
||||
Documents,
|
||||
Claims,
|
||||
}
|
||||
|
||||
impl CitationRole {
|
||||
pub const ALL: [CitationRole; 15] = [
|
||||
pub const ALL: [CitationRole; 16] = [
|
||||
Self::RaisedIn,
|
||||
Self::Answers,
|
||||
Self::Rulings,
|
||||
|
|
@ -305,6 +306,7 @@ impl CitationRole {
|
|||
Self::ResolvedBy,
|
||||
Self::DeferredTo,
|
||||
Self::Documents,
|
||||
Self::Claims,
|
||||
];
|
||||
|
||||
/// The wire spelling, equal to the referring field's name.
|
||||
|
|
@ -325,6 +327,7 @@ impl CitationRole {
|
|||
Self::ResolvedBy => "resolved_by",
|
||||
Self::DeferredTo => "deferred_to",
|
||||
Self::Documents => "documents",
|
||||
Self::Claims => "claims",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -345,12 +348,13 @@ impl CitationRole {
|
|||
Self::Source => K::FollowUp,
|
||||
Self::Subject | Self::SupersededBy | Self::ResolvedBy | Self::DeferredTo => K::Resolution,
|
||||
Self::Documents => K::HandOff,
|
||||
Self::Claims => K::Run,
|
||||
}
|
||||
}
|
||||
|
||||
/// The kinds a referent of this field may be: the one kind the field is
|
||||
/// typed to, or every kind for the fields typed `PipelineRef` or a full
|
||||
/// id of any kind. "Any" is spelled as all thirteen, never as none.
|
||||
/// id of any kind. "Any" is spelled as every kind, never as none.
|
||||
pub fn target_kinds(self) -> Vec<PipelineKind> {
|
||||
use PipelineKind as K;
|
||||
match self {
|
||||
|
|
@ -360,6 +364,7 @@ impl CitationRole {
|
|||
Self::CutReport => vec![K::CutReport],
|
||||
Self::Findings => vec![K::Finding],
|
||||
Self::Verdict => vec![K::Verdict],
|
||||
Self::Claims => vec![K::CutSpec, K::Finding, K::FollowUp],
|
||||
Self::RaisedIn
|
||||
| Self::Source
|
||||
| Self::Subject
|
||||
|
|
@ -437,6 +442,7 @@ pub(crate) fn citations(document: &PipelineDocument) -> Vec<(CitationRole, Pipel
|
|||
D::HandOff(hand_off) => edges.extend(
|
||||
hand_off.documents.iter().filter_map(|to| kind_of_id(&to.0).map(|kind| (R::Documents, cited(kind, to)))),
|
||||
),
|
||||
D::Run(run) => edges.extend(run.claims.iter().map(|to| (R::Claims, to.clone()))),
|
||||
D::Campaign(_) | D::Target(_) | D::Instance(_) | D::Stewardship(_) => {}
|
||||
}
|
||||
edges
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use eureka_pipeline::{
|
|||
FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MutationRecord, OrgRepo, PipelineCampaign,
|
||||
PipelineCutReport, PipelineCutSpec, PipelineDocument, PipelineFinding, PipelineFollowUp, PipelineHandOff,
|
||||
PipelineInstance, PipelineKind, PipelineQuestion, PipelineRef, PipelineResolution, PipelineRuling,
|
||||
PipelineStewardship, PipelineTarget, PipelineVerdict, Promise, QuestionOption, ReportCommit, ResolutionOutcome,
|
||||
RulingAuthority, Sha, Short, Slug, StructuralDelta, TargetInvariant, VerdictClaim,
|
||||
PipelineRun, PipelineStewardship, PipelineTarget, PipelineVerdict, Promise, QuestionOption, ReportCommit,
|
||||
ResolutionOutcome, RulingAuthority, RunOperator, RunTurn, Sha, Short, Slug, StructuralDelta, TargetInvariant, VerdictClaim,
|
||||
};
|
||||
|
||||
use crate::admission::{PipelineAdmissionBatch, PipelineAdmissionOutcome};
|
||||
|
|
@ -327,6 +327,21 @@ pub(crate) fn hand_off(from: &str, to: &str, repo_name: &str, documents: &[&str]
|
|||
})
|
||||
}
|
||||
|
||||
/// A Self run of this mind that claims `claims`.
|
||||
pub(crate) fn run(label: &str, claims: &[PipelineRef]) -> PipelineDocument {
|
||||
PipelineDocument::Run(PipelineRun {
|
||||
instance: slug(INSTANCE),
|
||||
label: l(label),
|
||||
operated_by: RunOperator::Mind,
|
||||
turn: RunTurn::SelfRun,
|
||||
host: s("yggdrasil"),
|
||||
started_on: date(),
|
||||
budget_usd: s("5"),
|
||||
claims: claims.to_vec(),
|
||||
campaigns: vec![slug(CAMPAIGN)],
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn provenance(faculty: Faculty) -> PipelineProvenance {
|
||||
PipelineProvenance { faculty, agent: s("claude"), session: s("session-1"), tool: s("admit") }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ pub fn index_text(document: &PipelineDocument) -> Option<String> {
|
|||
}
|
||||
D::Resolution(resolution) => lines.push(&resolution.rationale.0),
|
||||
D::HandOff(hand_off) => lines.push(&hand_off.reason.0),
|
||||
D::Instance(_) | D::Stewardship(_) => {}
|
||||
D::Instance(_) | D::Stewardship(_) | D::Run(_) => {}
|
||||
}
|
||||
lines.retain(|line| !line.is_empty());
|
||||
if lines.is_empty() {
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ fn require_grammatical_slug(field: &str, declared: &Slug) -> Result<(), MindRefu
|
|||
})
|
||||
}
|
||||
|
||||
/// A cache that knows the fifteen types a mind's store may hold: the leaf's
|
||||
/// thirteen through its registrar, the epoch record and the commit receipt.
|
||||
/// A cache that knows the sixteen types a mind's store may hold: the leaf's
|
||||
/// fourteen through its registrar, the epoch record and the commit receipt.
|
||||
/// It is the cache every envelope is prepared against.
|
||||
pub(crate) fn schema_cache() -> Result<CultCache, MindRefusal> {
|
||||
let mut cache = CultCache::new();
|
||||
|
|
@ -168,7 +168,7 @@ impl<S: MindStore> Mind<S> {
|
|||
|
||||
/// Fail-closed, in this order, nothing attached until every step passes:
|
||||
/// pull the raw envelopes; if anything is stored, exactly one epoch
|
||||
/// record at the current epoch; every type is one of the fifteen; and
|
||||
/// record at the current epoch; every type is one of the sixteen; and
|
||||
/// exactly one `instance` document naming the declared instance; then
|
||||
/// register, attach and pull. The epoch gate runs first so a store
|
||||
/// written at a foreign epoch is refused as `ForeignEpoch`, never as
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ use cultnet_rs::{
|
|||
};
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, CommitRange, Date, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, OrgRepo,
|
||||
PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, ResolutionOutcome, RulingAuthority, Sha, Short,
|
||||
Slug, Title,
|
||||
PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, ResolutionOutcome, RulingAuthority, RunOperator, RunTurn, Sha,
|
||||
Short, Slug, Title,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -136,6 +136,7 @@ pub enum PipelineFacts {
|
|||
Instance { instance: Slug, display_name: Short, host: Short, created_at: Date },
|
||||
Stewardship { instance: Slug, repo: OrgRepo, sequence: u32, assigned_on: Date, note: Line },
|
||||
HandOff { from_instance: Slug, to_instance: Slug, repo: OrgRepo, handed_on: Date },
|
||||
Run { label: Label, operated_by: RunOperator, turn: RunTurn, started_on: Date, claims: Vec<PipelineRef> },
|
||||
}
|
||||
|
||||
impl PipelineFacts {
|
||||
|
|
@ -227,6 +228,13 @@ impl PipelineFacts {
|
|||
repo: value.repo.clone(),
|
||||
handed_on: value.handed_on.clone(),
|
||||
},
|
||||
D::Run(value) => Self::Run {
|
||||
label: value.label.clone(),
|
||||
operated_by: value.operated_by,
|
||||
turn: value.turn,
|
||||
started_on: value.started_on.clone(),
|
||||
claims: value.claims.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1263,6 +1271,33 @@ mod tests {
|
|||
assert_eq!(ids(&mind.query(&right).unwrap()), vec![id("follow_up", "FU-1")]);
|
||||
}
|
||||
|
||||
/// The claim edge is the waker's queue: a `cites` hop over the `claims`
|
||||
/// role returns exactly the runs that claim the spec, closed ones included
|
||||
/// with their status.
|
||||
#[test]
|
||||
fn a_claims_selection_returns_exactly_the_runs_claiming_a_spec() {
|
||||
let mut mind = seeded();
|
||||
let (one, two) = (id("cut_spec", "cut-1.r1"), id("cut_spec", "cut-2.r1"));
|
||||
committed(admit(&mut mind, vec![D::CutSpec(cut_spec("1", 1)), D::CutSpec(cut_spec("2", 1))]));
|
||||
committed(admit(&mut mind, vec![run("a", &[r(K::CutSpec, &one)]), run("b", &[r(K::CutSpec, &two)])]));
|
||||
let claiming = |mind: &Mind<MemoryStore>, spec: &str| {
|
||||
let selection = Selection { cites: Some(cites(K::CutSpec, spec, Some("claims"))), ..of_kinds(&[K::Run]) };
|
||||
ids(&mind.query(&selection).unwrap())
|
||||
};
|
||||
assert_eq!(claiming(&mind, &one), vec![format!("{INSTANCE}:run:a")]);
|
||||
assert_eq!(claiming(&mind, &two), vec![format!("{INSTANCE}:run:b")]);
|
||||
let done = ResolutionOutcome::Recorded { reason: "done".into() };
|
||||
committed(admit(&mut mind, vec![resolution(r(K::Run, &format!("{INSTANCE}:run:a")), done)]));
|
||||
assert_eq!(claiming(&mind, &one), vec![format!("{INSTANCE}:run:a")], "a closed run is still a citer");
|
||||
let page = mind.query(&of_kinds(&[K::Run])).unwrap();
|
||||
let facts = headers(&page).iter().map(|header| header.facts.clone()).collect::<Vec<_>>();
|
||||
assert!(facts.iter().any(|fact| matches!(
|
||||
fact,
|
||||
PipelineFacts::Run { label, turn: RunTurn::SelfRun, operated_by: RunOperator::Mind, claims, .. }
|
||||
if label.0 == "a" && claims.len() == 1
|
||||
)), "{facts:?}");
|
||||
}
|
||||
|
||||
/// The substrate's own refusals cross as themselves: an undeclared alias
|
||||
/// is a `SelectionInvalid` from the substrate's door, before the organ's.
|
||||
#[test]
|
||||
|
|
@ -1336,6 +1371,8 @@ mod tests {
|
|||
]));
|
||||
let (_, landed) = committed(admit(&mut mind, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[&q1])]));
|
||||
let handoff = landed.iter().find(|write| write.kind == K::HandOff).expect("the hand-off landed").id.0.clone();
|
||||
let (_, landed) = committed(admit(&mut mind, vec![run("w1", &[r(K::CutSpec, &id("cut_spec", "cut-1.r2")), r(K::FollowUp, &fu)])]));
|
||||
let run_id = landed[0].id.0.clone();
|
||||
|
||||
let (n1, f1_closed, f2_closed) = (
|
||||
id("resolution", "question.Q1.n1"),
|
||||
|
|
@ -1367,8 +1404,10 @@ mod tests {
|
|||
(CitationRole::ResolvedBy, &f1_closed, &report),
|
||||
(CitationRole::DeferredTo, &f2_closed, &fu),
|
||||
(CitationRole::Documents, &handoff, &q1),
|
||||
(CitationRole::Claims, &run_id, &spec_r2),
|
||||
(CitationRole::Claims, &run_id, &fu),
|
||||
];
|
||||
assert_eq!(CitationRole::ALL.len(), 15);
|
||||
assert_eq!(CitationRole::ALL.len(), 16);
|
||||
for role in CitationRole::ALL {
|
||||
let selection =
|
||||
Selection { cited: Some(Incoming { role: role.name().into(), exists: true }), ..Selection::default() };
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ pub enum MindRefusal {
|
|||
FindingWithoutEvidence,
|
||||
UnknownInvariant { label: String },
|
||||
NotStewarded { repo: String },
|
||||
/// A run claims work another run in force already holds: `item` is the
|
||||
/// claimed document's id and `run` the id of the run that holds it.
|
||||
AlreadyClaimed { item: String, run: String },
|
||||
/// The one refusal a mind never raises: the answer exceeds the largest
|
||||
/// body the organ will deliver by any plane, one send or a deferred body.
|
||||
/// It lives here because a refusal rides the response schema and there is
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ impl SelectionRow {
|
|||
D::FollowUp(follow_up) => vec![follow_up.repo.identity()],
|
||||
D::Stewardship(stewardship) => vec![stewardship.repo.identity()],
|
||||
D::HandOff(hand_off) => vec![hand_off.repo.identity()],
|
||||
D::Target(_) | D::Question(_) | D::Ruling(_) | D::Verdict(_) | D::Finding(_) | D::Instance(_) | D::Resolution(_) => {
|
||||
D::Target(_) | D::Question(_) | D::Ruling(_) | D::Verdict(_) | D::Finding(_) | D::Instance(_) | D::Resolution(_) | D::Run(_) => {
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
|
|
@ -240,7 +240,7 @@ impl Row for SelectionRow {
|
|||
}
|
||||
}
|
||||
|
||||
/// The organ's closed lists: thirteen schemas, eleven aliases, fifteen roles.
|
||||
/// The organ's closed lists: fourteen schemas, eleven aliases, sixteen roles.
|
||||
/// A schema's name is its id, so the substrate's alias matching resolves
|
||||
/// nothing beyond exact ids.
|
||||
pub(crate) struct Vocabulary;
|
||||
|
|
@ -286,7 +286,7 @@ fn invalid(field: impl Into<String>, value: &str, message: impl Into<String>) ->
|
|||
/// because `OrgRepo` identity is case-insensitive.
|
||||
///
|
||||
/// Refuses, naming the field the client sent: a `schemas` entry that is not
|
||||
/// one of the thirteen ids; a `keys` entry that is not a full id whose kind
|
||||
/// one of the fourteen ids; a `keys` entry that is not a full id whose kind
|
||||
/// segment reads and passes the leaf's ref grammar; an `any_of` value outside
|
||||
/// its alias's domain; and a `cites.target` whose key fails the ref grammar
|
||||
/// for its schema's kind (V24's rule, at this door). Whether the target
|
||||
|
|
@ -353,7 +353,7 @@ mod tests {
|
|||
/// refused at the substrate's door.
|
||||
#[test]
|
||||
fn each_kind_declares_exactly_its_aliases_and_none_is_numeric() {
|
||||
let table: [(K, &[&str]); 13] = [
|
||||
let table: [(K, &[&str]); 14] = [
|
||||
(K::Campaign, &["repo"]),
|
||||
(K::Target, &[]),
|
||||
(K::Question, &[]),
|
||||
|
|
@ -367,6 +367,7 @@ mod tests {
|
|||
(K::Instance, &[]),
|
||||
(K::Stewardship, &["repo"]),
|
||||
(K::HandOff, &["repo"]),
|
||||
(K::Run, &[]),
|
||||
];
|
||||
for (kind, extra) in table {
|
||||
let want = [&["root", "in_force", "faculty"][..], extra].concat();
|
||||
|
|
@ -379,10 +380,10 @@ mod tests {
|
|||
|
||||
/// Each role is declared by the one kind whose field carries it, and its
|
||||
/// targets are the kinds that field is typed to, "any" spelled as all
|
||||
/// thirteen.
|
||||
/// fourteen.
|
||||
#[test]
|
||||
fn each_kind_declares_the_roles_it_carries_and_their_targets() {
|
||||
let table: [(K, &[&str]); 13] = [
|
||||
let table: [(K, &[&str]); 14] = [
|
||||
(K::Campaign, &[]),
|
||||
(K::Target, &[]),
|
||||
(K::Question, &["raised_in"]),
|
||||
|
|
@ -396,13 +397,18 @@ mod tests {
|
|||
(K::Instance, &[]),
|
||||
(K::Stewardship, &[]),
|
||||
(K::HandOff, &["documents"]),
|
||||
(K::Run, &["claims"]),
|
||||
];
|
||||
for (kind, roles) in table {
|
||||
assert_eq!(sorted(Vocabulary.declared_roles(kind.type_id())), strings(roles), "{kind:?}");
|
||||
}
|
||||
assert_eq!(Vocabulary.target_leaves("answers"), vec![K::Question.type_id().to_string()]);
|
||||
assert_eq!(Vocabulary.target_leaves("rulings"), vec![K::Ruling.type_id().to_string()]);
|
||||
assert_eq!(Vocabulary.target_leaves("subject").len(), 13);
|
||||
assert_eq!(Vocabulary.target_leaves("subject").len(), 14);
|
||||
assert_eq!(
|
||||
sorted(Vocabulary.target_leaves("claims")),
|
||||
sorted([K::CutSpec, K::Finding, K::FollowUp].iter().map(|kind| kind.type_id().to_string()).collect())
|
||||
);
|
||||
assert!(Vocabulary.target_leaves("nonsense").is_empty());
|
||||
assert_eq!(Vocabulary.schema_name(K::Question.type_id()), Some(K::Question.type_id().to_string()));
|
||||
assert_eq!(Vocabulary.schema_name("epiphany.pipeline.nonsense.v2"), None);
|
||||
|
|
|
|||
|
|
@ -160,7 +160,8 @@
|
|||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
"HandOff",
|
||||
"Run"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@
|
|||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
"HandOff",
|
||||
"Run"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@
|
|||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
"HandOff",
|
||||
"Run"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
"HandOff",
|
||||
"Run"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@
|
|||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
"HandOff",
|
||||
"Run"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -767,7 +767,7 @@
|
|||
]
|
||||
},
|
||||
"PipelineDocument": {
|
||||
"description": "The envelope enum, adjacently tagged on the kind name: a document\non a wire is `{ kind, value }`, and `kind` is exactly what\n`PipelineKind::name()` returns, so a reader dispatches on the kind\nsegment it already knows from the key and needs no second registry\nmapping variant spellings to kinds. Adjacent rather than internal\nbecause a value is a named map of its own and an internal tag would\nhave to be merged into it; the tag stays beside the value instead.\nThis is not a published schema: `schemas/cultnet` publishes the\nthirteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"description": "The envelope enum, adjacently tagged on the kind name: a document\non a wire is `{ kind, value }`, and `kind` is exactly what\n`PipelineKind::name()` returns, so a reader dispatches on the kind\nsegment it already knows from the key and needs no second registry\nmapping variant spellings to kinds. Adjacent rather than internal\nbecause a value is a named map of its own and an internal tag would\nhave to be merged into it; the tag stays beside the value instead.\nThis is not a published schema: `schemas/cultnet` publishes the\nfourteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
@ -976,6 +976,22 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "run"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineRun"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -1172,7 +1188,8 @@
|
|||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
"HandOff",
|
||||
"Run"
|
||||
]
|
||||
},
|
||||
"PipelineProvenance": {
|
||||
|
|
@ -1371,6 +1388,58 @@
|
|||
"authority"
|
||||
]
|
||||
},
|
||||
"PipelineRun": {
|
||||
"description": "One launch of a Self or a Persona turn: the one record of what it took\non. `claims` are the work it holds, and a claim is live while its run\nhas no resolution in force. `budget_usd` is a canonical decimal string,\n`0` for an interactive run. Instance-rooted like stewardship: the key\nis `<instance>:run:<label>`. Whether a claim is free is admission's rule.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"budget_usd": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"campaigns": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"claims": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 64
|
||||
},
|
||||
"host": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"operated_by": {
|
||||
"$ref": "#/$defs/RunOperator"
|
||||
},
|
||||
"started_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"turn": {
|
||||
"$ref": "#/$defs/RunTurn"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"label",
|
||||
"operated_by",
|
||||
"turn",
|
||||
"host",
|
||||
"started_on",
|
||||
"budget_usd",
|
||||
"claims",
|
||||
"campaigns"
|
||||
]
|
||||
},
|
||||
"PipelineStewardship": {
|
||||
"description": "Stewardship over a repo, as an assignment recorded in a mind. One\ninstance may steward several repos, so the repo is part of the key.\n`sequence` is per `(instance, repo)`, set by the writer, `1` for the\nfirst, so a repo transferred away and back is two records under one\nprefix rather than one document overwriting the other. `assigned_on`\nis a field, not a key part. As on a resolution, whether the sequence\nis the previous plus one, and which assignment is in force, are\nadmission's rules and not this crate's.",
|
||||
"type": "object",
|
||||
|
|
@ -1678,6 +1747,20 @@
|
|||
"Mind"
|
||||
]
|
||||
},
|
||||
"RunOperator": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Mind"
|
||||
]
|
||||
},
|
||||
"RunTurn": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PersonaTurn",
|
||||
"SelfRun"
|
||||
]
|
||||
},
|
||||
"SemanticQuery": {
|
||||
"description": "A semantic query: the text to find documents near, and how many. The index\nowns which fields of each kind are text (`index_text`), so no substring\nfilter lives here to become a second answer to that question.",
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -184,7 +184,8 @@
|
|||
"superseded_by",
|
||||
"resolved_by",
|
||||
"deferred_to",
|
||||
"documents"
|
||||
"documents",
|
||||
"claims"
|
||||
]
|
||||
},
|
||||
"ClaimOutcome": {
|
||||
|
|
@ -1349,6 +1350,31 @@
|
|||
"NotStewarded"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A run claims work another run in force already holds: `item` is the\nclaimed document's id and `run` the id of the run that holds it.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"AlreadyClaimed": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"item": {
|
||||
"type": "string"
|
||||
},
|
||||
"run": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"item",
|
||||
"run"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"AlreadyClaimed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The one refusal a mind never raises: the answer exceeds the largest\nbody the organ will deliver by any plane, one send or a deferred body.\nIt lives here because a refusal rides the response schema and there is\none of those, so a client reads it the way it reads every other refusal\nrather than parsing a second vocabulary. `bytes` is the encoded answer\n(the payload, not its envelope) and `limit` is the largest body the\ndaemon defers, both so a caller can narrow its own request; nothing is\ntruncated or paginated on its behalf. The daemon owns it and its\ndeferral bound decides it; no rule of the mind is involved.",
|
||||
"type": "object",
|
||||
|
|
@ -1889,7 +1915,7 @@
|
|||
]
|
||||
},
|
||||
"PipelineDocument": {
|
||||
"description": "The envelope enum, adjacently tagged on the kind name: a document\non a wire is `{ kind, value }`, and `kind` is exactly what\n`PipelineKind::name()` returns, so a reader dispatches on the kind\nsegment it already knows from the key and needs no second registry\nmapping variant spellings to kinds. Adjacent rather than internal\nbecause a value is a named map of its own and an internal tag would\nhave to be merged into it; the tag stays beside the value instead.\nThis is not a published schema: `schemas/cultnet` publishes the\nthirteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"description": "The envelope enum, adjacently tagged on the kind name: a document\non a wire is `{ kind, value }`, and `kind` is exactly what\n`PipelineKind::name()` returns, so a reader dispatches on the kind\nsegment it already knows from the key and needs no second registry\nmapping variant spellings to kinds. Adjacent rather than internal\nbecause a value is a named map of its own and an internal tag would\nhave to be merged into it; the tag stays beside the value instead.\nThis is not a published schema: `schemas/cultnet` publishes the\nfourteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
@ -2098,6 +2124,22 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "run"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineRun"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -2727,6 +2769,49 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "run"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"claims": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"operated_by": {
|
||||
"$ref": "#/$defs/RunOperator"
|
||||
},
|
||||
"started_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"turn": {
|
||||
"$ref": "#/$defs/RunTurn"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"operated_by",
|
||||
"turn",
|
||||
"started_on",
|
||||
"claims"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -2923,7 +3008,8 @@
|
|||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
"HandOff",
|
||||
"Run"
|
||||
]
|
||||
},
|
||||
"PipelinePageItems": {
|
||||
|
|
@ -3290,6 +3376,58 @@
|
|||
"authority"
|
||||
]
|
||||
},
|
||||
"PipelineRun": {
|
||||
"description": "One launch of a Self or a Persona turn: the one record of what it took\non. `claims` are the work it holds, and a claim is live while its run\nhas no resolution in force. `budget_usd` is a canonical decimal string,\n`0` for an interactive run. Instance-rooted like stewardship: the key\nis `<instance>:run:<label>`. Whether a claim is free is admission's rule.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"budget_usd": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"campaigns": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"claims": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 64
|
||||
},
|
||||
"host": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"operated_by": {
|
||||
"$ref": "#/$defs/RunOperator"
|
||||
},
|
||||
"started_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"turn": {
|
||||
"$ref": "#/$defs/RunTurn"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"label",
|
||||
"operated_by",
|
||||
"turn",
|
||||
"host",
|
||||
"started_on",
|
||||
"budget_usd",
|
||||
"claims",
|
||||
"campaigns"
|
||||
]
|
||||
},
|
||||
"PipelineSelectionPage": {
|
||||
"description": "One page of a selection. `matched` is the count before paging, `as_of` the\nsnapshot the page and every cursor of its walk are exact as of, and `edges`\nis present exactly when the selection follows a hop.",
|
||||
"type": "object",
|
||||
|
|
@ -3706,6 +3844,20 @@
|
|||
"Mind"
|
||||
]
|
||||
},
|
||||
"RunOperator": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Mind"
|
||||
]
|
||||
},
|
||||
"RunTurn": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PersonaTurn",
|
||||
"SelfRun"
|
||||
]
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"type": "string",
|
||||
|
|
|
|||
Loading…
Reference in a new issue