research and idea kinds: typed documents for what Eyes and Imagination find #2
25 changed files with 1637 additions and 42 deletions
|
|
@ -27,6 +27,8 @@ sample.hand_off yggdrasil:hand_off:thought-cage.gamecult_-epiphany.2026-09-15 ep
|
|||
sample.run yggdrasil:run:mind-2026-10-09-1 epiphany.pipeline.run.v2 EpiphanyPipelineRunDocument 9189a8696e7374616e6365a979676764726173696ca56c6162656cb16d696e642d323032362d31302d30392d31ab6f706572617465645f6279a44d696e64a47475726ea753656c6652756ea4686f7374a979676764726173696caa737461727465645f6f6eaa323032362d30392d3135aa6275646765745f757364a3372e35a6636c61696d739182a46b696e64a743757453706563a26964d925657572656b612d73746174653a6375745f737065633a6375742d72756e2d6b696e642e7232a963616d706169676e7391ac657572656b612d7374617465
|
||||
sample.pulse yggdrasil:pulse:2026-10-31t041200-t0 epiphany.pipeline.pulse.v2 EpiphanyPipelinePulseDocument 9185a8696e7374616e6365a979676764726173696ca56c6162656cb4323032362d31302d3331743034313230302d7430b174656d706f5f766f74655f626561745f73cd0258b0757267656e63795f7065726d696c6c65ccfaa46e6f7465d92254686520737761726d2069732069646c653b20612073686f7274657220626561742e
|
||||
sample.memory yggdrasil:memory:2026-10-31t041200-t0-m0 epiphany.pipeline.memory.v2 EpiphanyPipelineMemoryDocument 9189a8696e7374616e6365a979676764726173696ca56c6162656cb7323032362d31302d3331743034313230302d74302d6d30a4736f7274a9436f6d6d6974746564a474657874d936492074616b6520746865206261636b67726f756e6420736861646572733b204c6962627920686f6c64732074686520736f756e64732ea561626f757491ab406c696262793a63686174a66f726967696e81a4526f6f6d82a5726f6f6d73a5737761726daa68656172645f66726f6d91ab406c696262793a63686174a7736f757263657391a3246531b173616c69656e63655f7065726d696c6c65cd02bcac636f6e736f6c69646174657390
|
||||
sample.research eureka-state:research:p1.scope epiphany.pipeline.research.v2 EpiphanyPipelineResearchDocument 9189a863616d706169676eac657572656b612d7374617465a470617373a27031a56c6162656ca573636f7065a5636c61696dd93654686520656d626564206c696d6974206973203430393620746f6b656e7320776974682073696c656e74207472756e636174696f6e2ea664657461696cd934526561642066726f6d20746865206461656d6f6e277320696e646578206d6f64756c6520616e642070726f626564206c6976652ea8696e666572726564c2a7736f75726365739481a45265706f82a47265706fb147616d6543756c742f4570697068616e79a2617484a470617468d9256372617465732f687567696e6e2d6461656d6f6e2f7372632f696e6465782f6d6f642e7273aa73746172745f6c696e6532a8656e645f6c696e6538a6636f6d6d6974a8356639383232386481a357656283a375726cd92268747470733a2f2f7a657474656c6b617374656e2e64652f61746f6d69636974792fa4776f726bb65a657474656c6b617374656e2061746f6d6963697479ac7265747269657665645f6f6eaa323032362d30392d313581a84f6273657276656481a865766964656e636583a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6b81a8446f63756d656e7481a97265666572656e636582a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a5238a67365727665739182a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131ab6f627365727665645f6f6eaa323032362d30392d3135
|
||||
sample.idea eureka-state:idea:p1.i1 epiphany.pipeline.idea.v2 EpiphanyPipelineIdeaDocument 9189a863616d706169676eac657572656b612d7374617465a470617373a27031a56c6162656ca26931a57469746c65b24f6e65206e6f74652070657220636c61696da469646561d9204b65657020612070617373206173206d616e7920736d616c6c206e6f7465732ea974726164656f666673b74d6f726520646f63756d656e747320746f206c6973742ea872657374735f6f6e9182a46b696e64a85265736561726368a26964be657572656b612d73746174653a72657365617263683a70312e73636f7065a6746f776172649182a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131ab70726f706f7365645f6f6eaa323032362d30392d3135
|
||||
resolution.superseded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581aa5375706572736564656481a262799282a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a523882a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-none eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a26279c0a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-some eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a2627982a46b696e64a94375745265706f7274a26964d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
|
|
|
|||
|
|
@ -631,6 +631,46 @@ fn memory_is_well_formed(memory: &PipelineMemory, at: &str) -> Result<(), Pipeli
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// A research note states a claim and stands on at least one source that
|
||||
/// names something: a Repo source a file and a range that does not end before
|
||||
/// it starts, a Web source its address and work, an Observed one its
|
||||
/// locator and result, a Document one a note other than this one. A claim
|
||||
/// with no source is not research. An inferred note names what it inferred
|
||||
/// from in those sources. Two notes citing each other cannot be seen from one
|
||||
/// document; that refusal would need the image, so it is not made here.
|
||||
fn research_is_well_formed(research: &PipelineResearch, at: &str) -> Result<(), PipelineRefusal> {
|
||||
if research.claim.0.trim().is_empty() {
|
||||
return Err(format_error(&format!("{at}.claim"), "an empty claim"));
|
||||
}
|
||||
if research.sources.is_empty() {
|
||||
return Err(format_error(&format!("{at}.sources"), "a research note that names no source"));
|
||||
}
|
||||
let own_key = format!("{}:research:{}.{}", research.campaign.0, research.pass.0, research.label.0);
|
||||
let blank = |text: &str| text.trim().is_empty();
|
||||
for (index, source) in research.sources.iter().enumerate() {
|
||||
let (field, empty) = match source {
|
||||
ResearchSource::Repo { at, .. } if blank(&at.path.0) => ("at.path", "a repo source pinning no file"),
|
||||
ResearchSource::Repo { at, .. } if at.end_line < at.start_line => ("at.end_line", "a repo source whose range ends before it starts"),
|
||||
ResearchSource::Web { url, .. } if blank(&url.0) => ("url", "a web source with no address"),
|
||||
ResearchSource::Web { work, .. } if blank(&work.0) => ("work", "a web source naming no work"),
|
||||
ResearchSource::Observed { evidence } if blank(&evidence.locator.0) => ("evidence.locator", "an observation naming nothing observed"),
|
||||
ResearchSource::Observed { evidence } if blank(&evidence.result.0) => ("evidence.result", "an observation with no result"),
|
||||
ResearchSource::Document { reference } if reference.kind == PipelineKind::Research && reference.id.0 == own_key => ("reference", "a note that is its own source"),
|
||||
_ => continue,
|
||||
};
|
||||
return Err(format_error(&format!("{at}.sources[{index}].{field}"), empty));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An idea says something. It may serve no fork and rest on nothing.
|
||||
fn idea_is_well_formed(idea: &PipelineIdea, at: &str) -> Result<(), PipelineRefusal> {
|
||||
if idea.idea.0.trim().is_empty() {
|
||||
return Err(format_error(&format!("{at}.idea"), "an empty idea"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unit_enums! {
|
||||
EvidenceKind { Command, Test, Mutation, Probe, SourceRead, Capture }
|
||||
ClaimOutcome { Holds, Falsified, Unproven }
|
||||
|
|
@ -793,6 +833,28 @@ value_types! {
|
|||
instance: Slug, label: Label, sort: MemorySort, text: Line, about: Vec<Short>[8], origin: MemoryOrigin,
|
||||
sources: Vec<Short>[8], salience_permille: u16, consolidates: Vec<PipelineRef>[16],
|
||||
} => memory_is_well_formed
|
||||
/// One claim a pass found, with the sources it stands on, grouped by `pass`.
|
||||
/// What is so, never what to do: a recommendation is an idea. `inferred`
|
||||
/// marks a claim read from its sources rather than seen in them. It stores
|
||||
/// no status: whether a note is current, superseded or wrong is its
|
||||
/// resolutions'. `serves` names the question, cut or campaign document it
|
||||
/// was gathered for. The pass's method and what it did not search is its
|
||||
/// first note, labelled `scope`. Campaign-rooted:
|
||||
/// `<campaign>:research:<pass>.<label>`.
|
||||
pub struct PipelineResearch {
|
||||
campaign: Slug, pass: Label, label: Label, claim: Line, detail: Para, inferred: bool,
|
||||
sources: Vec<ResearchSource>[16], serves: Vec<PipelineRef>[8], observed_on: Date,
|
||||
} => research_is_well_formed
|
||||
/// One thing that could be, including options rejected and ideas no cut
|
||||
/// uses yet. `rests_on` is what it stands on (a reader selects the ideas
|
||||
/// resting on a stale note with `cites`); `toward` is the fork, ruling or
|
||||
/// follow-up it was made for, and empty is legal. It stores no status:
|
||||
/// taken up is its Deferred resolution, rejected its Recorded one.
|
||||
/// Campaign-rooted: `<campaign>:idea:<pass>.<label>`.
|
||||
pub struct PipelineIdea {
|
||||
campaign: Slug, pass: Label, label: Label, title: Title, idea: Para, tradeoffs: Para,
|
||||
rests_on: Vec<PipelineRef>[16], toward: Vec<PipelineRef>[8], proposed_on: Date,
|
||||
} => idea_is_well_formed
|
||||
}
|
||||
|
||||
/// A reference to another document. The id is parsed as a full pipeline id of
|
||||
|
|
@ -872,6 +934,39 @@ impl Bounded for MemoryOrigin {
|
|||
}
|
||||
}
|
||||
|
||||
/// Where a research claim comes from. A `Repo` pin and a `Web` page are
|
||||
/// outside the document set; an `Observed` result reuses a finding's evidence;
|
||||
/// a `Document` is the one source that is a citation edge.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum ResearchSource {
|
||||
/// A pinned file range in a repo.
|
||||
Repo { repo: OrgRepo, at: DocRef },
|
||||
/// A page read on the web: its address, the work it belongs to, and when.
|
||||
Web { url: Line, work: Short, retrieved_on: Date },
|
||||
/// A command, probe, read or capture the pass ran itself.
|
||||
Observed { evidence: Evidence },
|
||||
/// Another document in the mind.
|
||||
Document { reference: PipelineRef },
|
||||
}
|
||||
|
||||
impl Bounded for ResearchSource {
|
||||
fn validate(&self, field: &str) -> Result<(), PipelineRefusal> {
|
||||
match self {
|
||||
Self::Repo { repo, at } => {
|
||||
repo.validate(&format!("{field}.repo"))?;
|
||||
at.validate(&format!("{field}.at"))
|
||||
}
|
||||
Self::Web { url, work, retrieved_on } => {
|
||||
url.validate(&format!("{field}.url"))?;
|
||||
work.validate(&format!("{field}.work"))?;
|
||||
retrieved_on.validate(&format!("{field}.retrieved_on"))
|
||||
}
|
||||
Self::Observed { evidence } => evidence.validate(&format!("{field}.evidence")),
|
||||
Self::Document { reference } => reference.validate(&format!("{field}.reference")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How a subject was resolved. Every referent is a parsed `PipelineRef`, so a
|
||||
/// resolution names its records by ids of the kinds they declare, validated
|
||||
/// where every other referent is; a `Fixed` commit is a `Sha` whose referent
|
||||
|
|
@ -936,7 +1031,7 @@ macro_rules! pipeline_kinds {
|
|||
/// because a value is a named map of its own and an internal tag would
|
||||
/// have to be merged into it; the tag stays beside the value instead.
|
||||
/// This is not a published schema: `schemas/cultnet` publishes the
|
||||
/// sixteen per-kind value documents, and the envelope is the wire's
|
||||
/// eighteen per-kind value documents, and the envelope is the wire's
|
||||
/// shape, not a document's.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
|
||||
|
|
@ -1059,6 +1154,10 @@ pipeline_kinds! {
|
|||
"epiphany.pipeline.pulse.v2", "EpiphanyPipelinePulseDocument";
|
||||
Memory(PipelineMemory) => EpiphanyPipelineMemoryDocument, "memory",
|
||||
"epiphany.pipeline.memory.v2", "EpiphanyPipelineMemoryDocument";
|
||||
Research(PipelineResearch) => EpiphanyPipelineResearchDocument, "research",
|
||||
"epiphany.pipeline.research.v2", "EpiphanyPipelineResearchDocument";
|
||||
Idea(PipelineIdea) => EpiphanyPipelineIdeaDocument, "idea",
|
||||
"epiphany.pipeline.idea.v2", "EpiphanyPipelineIdeaDocument";
|
||||
}
|
||||
|
||||
/// Parses a full document id: the grammar read backwards. A key and an id are
|
||||
|
|
@ -1256,6 +1355,8 @@ pub fn pipeline_key(document: &PipelineDocument) -> Result<String, PipelineRefus
|
|||
D::Run(value) => ("run.instance", value.instance.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
D::Pulse(value) => ("pulse.instance", value.instance.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
D::Memory(value) => ("memory.instance", value.instance.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
D::Research(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&value.pass.0, &value.label.0])?),
|
||||
D::Idea(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&value.pass.0, &value.label.0])?),
|
||||
D::Target(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&format!("r{}", value.revision)])?),
|
||||
D::Question(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
D::Ruling(value) => (campaign_field.as_str(), value.campaign.0.as_str(), local(&key_field, document_kind, &[&value.label.0])?),
|
||||
|
|
@ -1509,6 +1610,24 @@ mod tests {
|
|||
text: "I take the background shaders; Libby holds the sounds.".into(), about: vec![s("@libby:chat")],
|
||||
origin: MemoryOrigin::Room { rooms: s("swarm"), heard_from: vec![s("@libby:chat")] }, sources: vec![s("$e1")], salience_permille: 700, consolidates: vec![],
|
||||
}), format!("{INSTANCE}:memory:2026-10-31t041200-t0-m0")),
|
||||
(D::Research(PipelineResearch {
|
||||
campaign: slug(CAMPAIGN), pass: l("p1"), label: l("scope"),
|
||||
claim: "The embed limit is 4096 tokens with silent truncation.".into(),
|
||||
detail: "Read from the daemon's index module and probed live.".into(), inferred: false,
|
||||
sources: vec![
|
||||
ResearchSource::Repo { repo: repo(), at: DocRef { path: s("crates/huginn-daemon/src/index/mod.rs"), start_line: 50, end_line: 56, commit: sha() } },
|
||||
ResearchSource::Web { url: "https://zettelkasten.de/atomicity/".into(), work: s("Zettelkasten atomicity"), retrieved_on: date() },
|
||||
ResearchSource::Observed { evidence: evidence() },
|
||||
ResearchSource::Document { reference: PipelineRef { kind: PipelineKind::Ruling, id: id("ruling", "R8") } },
|
||||
],
|
||||
serves: vec![PipelineRef { kind: PipelineKind::Question, id: id("question", "Q1") }], observed_on: date(),
|
||||
}), format!("{CAMPAIGN}:research:p1.scope")),
|
||||
(D::Idea(PipelineIdea {
|
||||
campaign: slug(CAMPAIGN), pass: l("p1"), label: l("i1"), title: t("One note per claim"),
|
||||
idea: "Keep a pass as many small notes.".into(), tradeoffs: "More documents to list.".into(),
|
||||
rests_on: vec![PipelineRef { kind: PipelineKind::Research, id: id("research", "p1.scope") }],
|
||||
toward: vec![PipelineRef { kind: PipelineKind::Question, id: id("question", "Q1") }], proposed_on: date(),
|
||||
}), format!("{CAMPAIGN}:idea:p1.i1")),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1870,6 +1989,70 @@ mod tests {
|
|||
assert!(matches!(validated(&|memory| memory.text = Line("x".repeat(1001))), Err(PipelineRefusal::FieldBound { limit: 1000, .. })));
|
||||
}
|
||||
|
||||
fn research_sample() -> PipelineResearch {
|
||||
let PipelineDocument::Research(research) = samples().remove(16).0 else { unreachable!() };
|
||||
research
|
||||
}
|
||||
|
||||
fn idea_sample() -> PipelineIdea {
|
||||
let PipelineDocument::Idea(idea) = samples().remove(17).0 else { unreachable!() };
|
||||
idea
|
||||
}
|
||||
|
||||
/// A research note has a claim and at least one source; each source is bounded.
|
||||
#[test]
|
||||
fn research_needs_a_source_and_a_claim() {
|
||||
let validated = |change: &dyn Fn(&mut PipelineResearch)| {
|
||||
let mut research = research_sample();
|
||||
change(&mut research);
|
||||
PipelineDocument::Research(research).validate()
|
||||
};
|
||||
assert_eq!(validated(&|_| {}), Ok(()));
|
||||
let bad = |result: Result<(), PipelineRefusal>, field: &str| {
|
||||
assert!(matches!(&result, Err(PipelineRefusal::InvalidFormat { field: found, .. }) if found == field), "{field}: {result:?}");
|
||||
};
|
||||
bad(validated(&|research| research.sources = vec![]), "research.sources");
|
||||
bad(validated(&|research| research.claim = " ".into()), "research.claim");
|
||||
bad(validated(&|research| research.claim = "".into()), "research.claim");
|
||||
let source_bad = |source: ResearchSource, field: &str| {
|
||||
bad(validated(&|research| research.sources = vec![source.clone()]), &format!("research.sources[0].{field}"));
|
||||
};
|
||||
source_bad(ResearchSource::Repo { repo: OrgRepo("GameCult/Huginn".into()), at: DocRef { path: s(" "), start_line: 1, end_line: 2, commit: sha() } }, "at.path");
|
||||
source_bad(ResearchSource::Repo { repo: OrgRepo("GameCult/Huginn".into()), at: DocRef { path: s("a.rs"), start_line: 5, end_line: 4, commit: sha() } }, "at.end_line");
|
||||
assert_eq!(validated(&|research| research.sources = vec![ResearchSource::Repo { repo: OrgRepo("GameCult/Huginn".into()), at: DocRef { path: s("a.rs"), start_line: 5, end_line: 5, commit: sha() } }]), Ok(()));
|
||||
source_bad(ResearchSource::Web { url: " ".into(), work: s("w"), retrieved_on: date() }, "url");
|
||||
source_bad(ResearchSource::Web { url: "https://x.test/".into(), work: s(""), retrieved_on: date() }, "work");
|
||||
source_bad(ResearchSource::Observed { evidence: Evidence { kind: EvidenceKind::Probe, locator: " ".into(), result: "found".into() } }, "evidence.locator");
|
||||
source_bad(ResearchSource::Observed { evidence: Evidence { kind: EvidenceKind::Probe, locator: "ran it".into(), result: "".into() } }, "evidence.result");
|
||||
let own = research_sample();
|
||||
let cites = |label: &str| PipelineRef { kind: PipelineKind::Research, id: Short(format!("{}:research:{}.{label}", own.campaign.0, own.pass.0)) };
|
||||
source_bad(ResearchSource::Document { reference: cites(&own.label.0) }, "reference");
|
||||
assert_eq!(validated(&|research| research.sources = vec![ResearchSource::Document { reference: cites("another") }]), Ok(()));
|
||||
assert_eq!(validated(&|research| research.inferred = true), Ok(()));
|
||||
assert!(matches!(validated(&|research| research.sources = vec![research_sample().sources.remove(2); 17]), Err(PipelineRefusal::FieldBound { limit: 16, .. })));
|
||||
assert!(validated(&|research| research.sources = vec![ResearchSource::Web { url: Line("x".repeat(1001)), work: s("w"), retrieved_on: date() }]).is_err());
|
||||
}
|
||||
|
||||
/// A blank idea is refused; an idea that serves and rests on nothing is admitted.
|
||||
#[test]
|
||||
fn idea_needs_text_and_may_serve_nothing() {
|
||||
let validated = |change: &dyn Fn(&mut PipelineIdea)| {
|
||||
let mut idea = idea_sample();
|
||||
change(&mut idea);
|
||||
PipelineDocument::Idea(idea).validate()
|
||||
};
|
||||
assert_eq!(validated(&|idea| (idea.rests_on, idea.toward) = (vec![], vec![])), Ok(()));
|
||||
assert!(matches!(validated(&|idea| idea.idea = " ".into()), Err(PipelineRefusal::InvalidFormat { field, .. }) if field == "idea.idea"));
|
||||
}
|
||||
|
||||
/// Both are keyed under their campaign by pass and label.
|
||||
#[test]
|
||||
fn research_and_idea_are_keyed_by_pass_and_label() -> Result<()> {
|
||||
assert_eq!(pipeline_key(&PipelineDocument::Research(research_sample()))?, format!("{CAMPAIGN}:research:p1.scope"));
|
||||
assert_eq!(pipeline_key(&PipelineDocument::Idea(idea_sample()))?, format!("{CAMPAIGN}:idea:p1.i1"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A memory's origin is bounded field by field, and a private one names whom she heard it from.
|
||||
#[test]
|
||||
fn a_memory_origin_is_bounded_and_a_direct_one_names_who_she_heard_it_from() {
|
||||
|
|
@ -3457,11 +3640,11 @@ mod tests {
|
|||
};
|
||||
let document_schema = serde_json::to_value(schemars::schema_for!(PipelineDocument))?;
|
||||
let variants = document_schema["oneOf"].as_array().expect("the document schema is a oneOf");
|
||||
assert_eq!(PipelineKind::ALL.len(), 16, "sixteen kinds");
|
||||
assert_eq!(PipelineKind::ALL.len(), 18, "eighteen kinds");
|
||||
assert_eq!(
|
||||
variants.iter().map(tag).collect::<Vec<_>>(),
|
||||
PipelineKind::ALL.iter().map(|kind| kind.name().to_owned()).collect::<Vec<_>>(),
|
||||
"the document schema lists exactly the sixteen kinds, by name"
|
||||
"the document schema lists exactly the eighteen kinds, by name"
|
||||
);
|
||||
|
||||
let refusal_schema = serde_json::to_value(schemars::schema_for!(PipelineRefusal))?;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ use crate::trouble::Trouble;
|
|||
const GAP_HOME: &str = "eureka-substrate";
|
||||
/// How many neighbours `precedent` asks the index for.
|
||||
const PRECEDENT_TOP_K: u32 = 10;
|
||||
/// How many neighbours `known` asks the index for.
|
||||
const KNOWN_TOP_K: u32 = 10;
|
||||
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
pub struct Paging {
|
||||
|
|
@ -107,6 +109,26 @@ pub enum Recipe {
|
|||
},
|
||||
/// The substrate's known gaps.
|
||||
Gaps(Paging),
|
||||
/// Research and ideas nearest some words, in every campaign, closed ones included.
|
||||
Known {
|
||||
text: String,
|
||||
#[command(flatten)]
|
||||
page: Paging,
|
||||
},
|
||||
/// One pass's research and ideas, whole, in no promised order (a pass spanning batches is grouped by batch).
|
||||
Pass {
|
||||
campaign: String,
|
||||
pass: String,
|
||||
#[command(flatten)]
|
||||
page: Paging,
|
||||
},
|
||||
/// Ideas in force.
|
||||
Ideas {
|
||||
#[arg(required = true)]
|
||||
campaigns: Vec<String>,
|
||||
#[command(flatten)]
|
||||
page: Paging,
|
||||
},
|
||||
}
|
||||
|
||||
/// What a recipe asks the mind: the selection, the semantic part when it has
|
||||
|
|
@ -140,6 +162,9 @@ impl Recipe {
|
|||
(precedent(&campaign), Some(SemanticQuery { text: Line(text), top_k: PRECEDENT_TOP_K }), page)
|
||||
}
|
||||
Self::Gaps(page) => (gaps(), None, page),
|
||||
Self::Known { text, page } => (known(), Some(SemanticQuery { text: Line(text), top_k: KNOWN_TOP_K }), page),
|
||||
Self::Pass { campaign, pass, page } => (pass_of(&campaign, &pass), None, page),
|
||||
Self::Ideas { campaigns, page } => (ideas(&campaigns), None, page),
|
||||
};
|
||||
Ok(Ask { selection, semantic, page })
|
||||
}
|
||||
|
|
@ -248,6 +273,21 @@ fn precedent(campaign: &str) -> Value {
|
|||
})
|
||||
}
|
||||
|
||||
fn known() -> Value {
|
||||
json!({ "schemas": [schema(PipelineKind::Research), schema(PipelineKind::Idea)] })
|
||||
}
|
||||
|
||||
fn pass_of(campaign: &str, pass: &str) -> Value {
|
||||
json!({
|
||||
"schemas": [schema(PipelineKind::Research), schema(PipelineKind::Idea)],
|
||||
"fields": [root(&[campaign.to_string()]), { "index": "pass", "op": "any_of", "values": [pass] }],
|
||||
})
|
||||
}
|
||||
|
||||
fn ideas(campaigns: &[String]) -> Value {
|
||||
json!({ "schemas": [schema(PipelineKind::Idea)], "fields": [root(campaigns), in_force()] })
|
||||
}
|
||||
|
||||
fn gaps() -> Value {
|
||||
json!({
|
||||
"schemas": [schema(PipelineKind::FollowUp)],
|
||||
|
|
@ -279,6 +319,8 @@ pub fn help() -> Vec<(&'static str, String)> {
|
|||
"{}\nsemantic: {{ \"text\": \"<text>\", \"top_k\": {PRECEDENT_TOP_K} }}",
|
||||
selection(precedent(&c()))
|
||||
);
|
||||
let known_help = format!("{}
|
||||
semantic: {{ \"text\": \"<text>\", \"top_k\": {KNOWN_TOP_K} }}", selection(known()));
|
||||
vec![
|
||||
("campaigns", selection(campaigns())),
|
||||
("target", selection(target(&c()))),
|
||||
|
|
@ -293,5 +335,8 @@ pub fn help() -> Vec<(&'static str, String)> {
|
|||
("ledger", selection(ledger(&c()))),
|
||||
("precedent", precedent_help),
|
||||
("gaps", selection(gaps())),
|
||||
("known", known_help),
|
||||
("pass", selection(pass_of(&c(), "<pass>"))),
|
||||
("ideas", selection(ideas(&[c()]))),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ macro_rules! kinds {
|
|||
|
||||
kinds!(
|
||||
campaign, target, question, ruling, cut_spec, cut_report, verdict, finding, follow_up, resolution, instance, stewardship,
|
||||
hand_off, run, pulse, memory,
|
||||
hand_off, run, pulse, memory, research, idea,
|
||||
);
|
||||
|
||||
/// The schema named, or the names when none is: the exit code is 2 for a name
|
||||
|
|
|
|||
|
|
@ -784,11 +784,24 @@ fn the_recipes_send_exactly_the_pages_selections() {
|
|||
vec!["gaps"],
|
||||
json!({ "schemas": [schema("follow_up")], "fields": [root(&["eureka-substrate"]), in_force] }),
|
||||
),
|
||||
(vec!["known", "the words"], json!({ "schemas": [schema("research"), schema("idea")] })),
|
||||
(
|
||||
vec!["pass", c, "p1"],
|
||||
json!({
|
||||
"schemas": [schema("research"), schema("idea")],
|
||||
"fields": [root(&[c]), { "index": "pass", "op": "any_of", "values": ["p1"] }],
|
||||
}),
|
||||
),
|
||||
(vec!["ideas", c], json!({ "schemas": [schema("idea")], "fields": [root(&[c]), in_force] })),
|
||||
];
|
||||
for (args, literal) in cases {
|
||||
let (selection, semantic) = sent(&args);
|
||||
assert_eq!(selection, page_literal(literal), "{args:?}");
|
||||
let expected = (args[0] == "precedent").then(|| ("the question in plain words".to_string(), 10));
|
||||
let expected = match args[0] {
|
||||
"precedent" => Some(("the question in plain words".to_string(), 10)),
|
||||
"known" => Some(("the words".to_string(), 10)),
|
||||
_ => None,
|
||||
};
|
||||
assert_eq!(semantic, expected, "{args:?}");
|
||||
}
|
||||
}
|
||||
|
|
@ -1109,11 +1122,12 @@ fn schema_prints_the_published_files() {
|
|||
let names = run(&[], &["schema"], None);
|
||||
assert_eq!(names.code, 0);
|
||||
let names: Vec<&str> = names.out.lines().collect();
|
||||
assert_eq!(names.len(), 17, "{names:?}");
|
||||
assert_eq!(names.len(), 19, "{names:?}");
|
||||
assert_eq!(names[0], "campaign");
|
||||
assert_eq!(names[14], "pulse");
|
||||
assert_eq!(names[15], "memory");
|
||||
assert_eq!(names[16], "selection");
|
||||
assert_eq!((names[16], names[17]), ("research", "idea"));
|
||||
assert_eq!(names[18], "selection");
|
||||
for kind in PipelineKind::ALL {
|
||||
assert!(names.contains(&kind.name()), "{}", kind.name());
|
||||
}
|
||||
|
|
@ -1159,7 +1173,7 @@ fn help_is_the_schema_surface() {
|
|||
fn every_recipe_has_help() {
|
||||
for recipe in [
|
||||
"campaigns", "target", "rulings", "open", "findings", "unreported", "blocked", "unjudged", "history", "cut", "ledger",
|
||||
"precedent", "gaps",
|
||||
"precedent", "gaps", "known", "pass", "ideas",
|
||||
] {
|
||||
let ran = run(&[], &[recipe, "--help"], None);
|
||||
assert_eq!(ran.code, 0, "{recipe}");
|
||||
|
|
|
|||
|
|
@ -623,7 +623,7 @@ fn check(docs: &Docs<'_>, staged: &Staged, mind: &Slug) -> Result<(), MindRefusa
|
|||
let replay = docs.in_image(K::Run, key) == Some(&staged.document);
|
||||
run_rule(docs, key, run, replay)
|
||||
}
|
||||
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Memory(_) => Ok(()),
|
||||
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Memory(_) | D::Research(_) | D::Idea(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -722,6 +722,11 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool {
|
|||
(K::Memory, O::Deferred { to }) => to.kind == K::FollowUp,
|
||||
(K::Memory, O::Recorded { .. } | O::Withdrawn { .. }) => true,
|
||||
(K::Memory, O::Superseded { by }) => all(by, K::Memory),
|
||||
(K::Research, O::Superseded { by }) => all(by, K::Research),
|
||||
(K::Research, O::Recorded { .. } | O::Withdrawn { .. }) => true,
|
||||
(K::Idea, O::Deferred { to }) => matches!(to.kind, K::Question | K::CutSpec | K::FollowUp | K::Ruling),
|
||||
(K::Idea, O::Superseded { by }) => all(by, K::Idea),
|
||||
(K::Idea, O::Recorded { .. } | O::Withdrawn { .. }) => true,
|
||||
(K::Resolution, O::Withdrawn { .. }) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
|
@ -2545,6 +2550,176 @@ mod tests {
|
|||
committed(admit(&mut mind, vec![consolidating(r(K::Memory, &memory_key("m1")))]));
|
||||
}
|
||||
|
||||
fn research_key(pass: &str, label: &str) -> String {
|
||||
format!("{CAMPAIGN}:research:{pass}.{label}")
|
||||
}
|
||||
|
||||
fn idea_key(pass: &str, label: &str) -> String {
|
||||
format!("{CAMPAIGN}:idea:{pass}.{label}")
|
||||
}
|
||||
|
||||
/// Research is superseded by research, recorded or withdrawn; an idea is deferred to a
|
||||
/// question, cut spec, follow-up or ruling, superseded by an idea, recorded or withdrawn.
|
||||
/// Their neighbours are refused.
|
||||
#[test]
|
||||
fn research_and_idea_resolution_matrix() {
|
||||
let incompatible = |kind: K, outcome: &str| MindRefusal::IncompatibleResolution { subject_kind: kind, outcome: outcome.into() };
|
||||
let open = || {
|
||||
let mut mind = with_specs();
|
||||
committed(admit(&mut mind, vec![
|
||||
D::Ruling(ruling("R1")),
|
||||
follow_up("FU", r(K::CutSpec, &id("cut_spec", "cut-1.r1"))),
|
||||
research("p", "a", "One fact."),
|
||||
research("p", "b", "A later fact."),
|
||||
idea("p", "i1", "One way."),
|
||||
idea("p", "i2", "Another way."),
|
||||
question_n(1),
|
||||
run("w", &[spec_ref("1")]),
|
||||
]));
|
||||
mind
|
||||
};
|
||||
let note = || r(K::Research, &research_key("p", "a"));
|
||||
let way = || r(K::Idea, &idea_key("p", "i1"));
|
||||
let spec = || r(K::CutSpec, &id("cut_spec", "cut-1.r1"));
|
||||
let ruling_ref = || r(K::Ruling, &id("ruling", "R1"));
|
||||
for outcome in [superseded(&[r(K::Research, &research_key("p", "b"))]), recorded_outcome(), withdrawn()] {
|
||||
committed(admit(&mut open(), vec![resolution(note(), outcome)]));
|
||||
}
|
||||
for outcome in [
|
||||
ResolutionOutcome::Deferred { to: spec() },
|
||||
ResolutionOutcome::Deferred { to: ruling_ref() },
|
||||
ResolutionOutcome::Deferred { to: r(K::Question, &id("question", "Q1")) },
|
||||
ResolutionOutcome::Deferred { to: r(K::FollowUp, &id("follow_up", "FU")) },
|
||||
superseded(&[r(K::Idea, &idea_key("p", "i2"))]),
|
||||
recorded_outcome(),
|
||||
withdrawn(),
|
||||
] {
|
||||
committed(admit(&mut open(), vec![resolution(way(), outcome)]));
|
||||
}
|
||||
let refused_note = [
|
||||
(ResolutionOutcome::Deferred { to: spec() }, "Deferred"),
|
||||
(superseded(&[r(K::Idea, &idea_key("p", "i2"))]), "Superseded"),
|
||||
(ResolutionOutcome::Answered { by: ruling_ref() }, "Answered"),
|
||||
(ResolutionOutcome::Fixed { commit: sha(), by: None }, "Fixed"),
|
||||
];
|
||||
for (outcome, name) in refused_note {
|
||||
assert_eq!(refusal(admit(&mut open(), vec![resolution(note(), outcome)])), incompatible(K::Research, name), "{name}");
|
||||
}
|
||||
let held_run = || r(K::Run, &run_key("w"));
|
||||
let refused_idea = [
|
||||
(ResolutionOutcome::Answered { by: ruling_ref() }, "Answered"),
|
||||
(superseded(&[spec()]), "Superseded"),
|
||||
(ResolutionOutcome::Deferred { to: held_run() }, "Deferred"),
|
||||
(ResolutionOutcome::Deferred { to: r(K::Research, &research_key("p", "b")) }, "Deferred"),
|
||||
(ResolutionOutcome::Fixed { commit: sha(), by: None }, "Fixed"),
|
||||
];
|
||||
for (outcome, name) in refused_idea {
|
||||
assert_eq!(refusal(admit(&mut open(), vec![resolution(way(), outcome)])), incompatible(K::Idea, name), "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
fn recorded_outcome() -> ResolutionOutcome {
|
||||
ResolutionOutcome::Recorded { reason: "why".into() }
|
||||
}
|
||||
|
||||
/// A rejected option lands with its Recorded resolution in one batch, and an idea that
|
||||
/// serves nothing and rests on nothing is admitted.
|
||||
#[test]
|
||||
fn a_rejected_option_lands_with_its_resolution_in_one_batch() {
|
||||
let mut mind = with_specs();
|
||||
committed(admit(&mut mind, vec![
|
||||
idea("p", "no", "A road not taken."),
|
||||
resolution(r(K::Idea, &idea_key("p", "no")), recorded_outcome()),
|
||||
]));
|
||||
assert!(!docs_of(&mind).in_force(K::Idea, &idea_key("p", "no")));
|
||||
}
|
||||
|
||||
/// Research supersedes across campaigns (Lineage::Global); the
|
||||
/// note stays open until its closure lands.
|
||||
#[test]
|
||||
fn a_later_pass_in_another_campaign_supersedes_research() {
|
||||
let mut mind = seeded();
|
||||
committed(admit(&mut mind, vec![research("p", "a", "Old fact.")]));
|
||||
let mut batch = rival_revision(1);
|
||||
let D::Research(mut later) = research("q", "a", "New fact.") else { panic!("research") };
|
||||
later.campaign = RIVAL.into();
|
||||
batch.push(D::Research(later));
|
||||
batch.push(resolution(
|
||||
r(K::Research, &research_key("p", "a")),
|
||||
superseded(&[r(K::Research, &format!("{RIVAL}:research:q.a"))]),
|
||||
));
|
||||
committed(admit(&mut mind, batch));
|
||||
assert!(!docs_of(&mind).in_force(K::Research, &research_key("p", "a")));
|
||||
assert!(docs_of(&mind).in_force(K::Research, &format!("{RIVAL}:research:q.a")));
|
||||
}
|
||||
|
||||
/// A withdrawn closure reopens the note: Recorded closes it, withdrawing that record puts it back in force.
|
||||
#[test]
|
||||
fn withdrawing_a_notes_resolution_reopens_it() {
|
||||
let mut mind = seeded();
|
||||
committed(admit(&mut mind, vec![research("p", "a", "A fact."), idea("p", "i", "A way.")]));
|
||||
for (kind, key) in [(K::Research, research_key("p", "a")), (K::Idea, idea_key("p", "i"))] {
|
||||
let (_, landed) = committed(admit(&mut mind, vec![resolution(r(kind, &key), recorded_outcome())]));
|
||||
assert!(!docs_of(&mind).in_force(kind, &key), "{kind:?} is closed");
|
||||
let closure = landed.into_iter().find(|write| write.kind == K::Resolution).expect("the resolution landed");
|
||||
committed(admit(&mut mind, vec![resolution(r(K::Resolution, &closure.id.0), withdrawn())]));
|
||||
assert!(docs_of(&mind).in_force(kind, &key), "{kind:?} is open again");
|
||||
}
|
||||
}
|
||||
|
||||
/// A question may be raised in a research note, and only in one the mind holds.
|
||||
#[test]
|
||||
fn question_raised_in_a_research_note() {
|
||||
let mut mind = seeded();
|
||||
let D::Question(mut raised) = question("Q1", &["A", "B"], "A") else { panic!("question") };
|
||||
raised.raised_in = Some(r(K::Research, &research_key("p", "a")));
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![D::Question(raised.clone())])),
|
||||
MindRefusal::MissingReference { kind: K::Research, id: research_key("p", "a") }
|
||||
);
|
||||
committed(admit(&mut mind, vec![research("p", "a", "A fact.")]));
|
||||
committed(admit(&mut mind, vec![D::Question(raised)]));
|
||||
assert!(docs_of(&mind).in_force(K::Question, &id("question", "Q1")));
|
||||
}
|
||||
|
||||
/// Every referent of a research note and an idea must be held; a pass.label beyond the key's
|
||||
/// local bound is refused whole.
|
||||
#[test]
|
||||
fn research_and_idea_name_held_referents_and_keep_their_key_in_bound() {
|
||||
let mut mind = with_specs();
|
||||
let missing = id("question", "Q404");
|
||||
let D::Research(mut serving) = research("p", "a", "A fact.") else { panic!("research") };
|
||||
serving.serves = vec![r(K::Question, &missing)];
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![D::Research(serving)])),
|
||||
MindRefusal::MissingReference { kind: K::Question, id: missing.clone() }
|
||||
);
|
||||
let D::Idea(mut resting) = idea("p", "i", "A way.") else { panic!("idea") };
|
||||
resting.rests_on = vec![r(K::Research, &research_key("p", "gone"))];
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![D::Idea(resting)])),
|
||||
MindRefusal::MissingReference { kind: K::Research, id: research_key("p", "gone") }
|
||||
);
|
||||
let D::Research(mut document) = research("p", "a", "A fact.") else { panic!("research") };
|
||||
document.sources = vec![eureka_pipeline::ResearchSource::Document { reference: r(K::Question, &missing) }];
|
||||
// A Document source is held to its referent, as an edge.
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![D::Research(document)])),
|
||||
MindRefusal::MissingReference { kind: K::Question, id: missing }
|
||||
);
|
||||
let long = "x".repeat(40);
|
||||
assert!(matches!(
|
||||
refusal(admit(&mut mind, vec![research(&long, &"y".repeat(30), "Too long a key.")])),
|
||||
MindRefusal::Document(_)
|
||||
));
|
||||
assert!(docs_of(&mind).find(K::Research, &research_key("p", "a")).is_none(), "a refused batch commits nothing");
|
||||
committed(admit(&mut mind, vec![idea("p", "i", "Rests on nothing."), research("p", "a", "A fact.")]));
|
||||
let D::Idea(mut resting) = idea("p", "j", "Rests on a note.") else { panic!("idea") };
|
||||
resting.rests_on = vec![r(K::Research, &research_key("p", "a"))];
|
||||
resting.toward = vec![r(K::Idea, &idea_key("p", "i"))];
|
||||
committed(admit(&mut mind, vec![D::Idea(resting)]));
|
||||
}
|
||||
|
||||
/// A run ends: Recorded or Withdrawn, and no other outcome.
|
||||
#[test]
|
||||
fn a_run_resolves_recorded_or_withdrawn_and_nothing_else() {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
use cultcache_rs::CultCacheEnvelope;
|
||||
use eureka_pipeline::{
|
||||
Label, OrgRepo, PipelineCampaign, PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, PipelineRun, PipelineStewardship, ResolutionOutcome,
|
||||
RunOperator, Short, Slug,
|
||||
ResearchSource, RunOperator, Short, Slug,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -321,7 +321,7 @@ pub(crate) enum Lineage<'a> {
|
|||
/// A memory: the instance whose mind holds it, so a consolidating memory
|
||||
/// replaces only her own.
|
||||
Memories(&'a Slug),
|
||||
/// A ruling or a follow-up: operator directions and re-homed work cut
|
||||
/// A ruling, a follow-up, a research note or an idea: operator directions and re-homed work cut
|
||||
/// across campaigns.
|
||||
Global,
|
||||
}
|
||||
|
|
@ -333,7 +333,7 @@ pub(crate) fn line_of(document: &PipelineDocument) -> Option<Lineage<'_>> {
|
|||
D::CutSpec(spec) => Some(Lineage::Cut(&spec.campaign, &spec.cut)),
|
||||
D::Stewardship(stewardship) => Some(Lineage::Assignment(&stewardship.instance, &stewardship.repo)),
|
||||
D::Memory(memory) => Some(Lineage::Memories(&memory.instance)),
|
||||
D::Ruling(_) | D::FollowUp(_) => Some(Lineage::Global),
|
||||
D::Ruling(_) | D::FollowUp(_) | D::Research(_) | D::Idea(_) => Some(Lineage::Global),
|
||||
D::Campaign(_)
|
||||
| D::Question(_)
|
||||
| D::CutReport(_)
|
||||
|
|
@ -388,10 +388,14 @@ pub enum CitationRole {
|
|||
Documents,
|
||||
Claims,
|
||||
Consolidates,
|
||||
Serves,
|
||||
Sources,
|
||||
RestsOn,
|
||||
Toward,
|
||||
}
|
||||
|
||||
impl CitationRole {
|
||||
pub const ALL: [CitationRole; 17] = [
|
||||
pub const ALL: [CitationRole; 21] = [
|
||||
Self::RaisedIn,
|
||||
Self::Answers,
|
||||
Self::Rulings,
|
||||
|
|
@ -409,6 +413,10 @@ impl CitationRole {
|
|||
Self::Documents,
|
||||
Self::Claims,
|
||||
Self::Consolidates,
|
||||
Self::Serves,
|
||||
Self::Sources,
|
||||
Self::RestsOn,
|
||||
Self::Toward,
|
||||
];
|
||||
|
||||
/// The wire spelling, equal to the referring field's name.
|
||||
|
|
@ -431,6 +439,10 @@ impl CitationRole {
|
|||
Self::Documents => "documents",
|
||||
Self::Claims => "claims",
|
||||
Self::Consolidates => "consolidates",
|
||||
Self::Serves => "serves",
|
||||
Self::Sources => "sources",
|
||||
Self::RestsOn => "rests_on",
|
||||
Self::Toward => "toward",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -453,6 +465,8 @@ impl CitationRole {
|
|||
Self::Documents => K::HandOff,
|
||||
Self::Claims => K::Run,
|
||||
Self::Consolidates => K::Memory,
|
||||
Self::Serves | Self::Sources => K::Research,
|
||||
Self::RestsOn | Self::Toward => K::Idea,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -476,7 +490,11 @@ impl CitationRole {
|
|||
| Self::SupersededBy
|
||||
| Self::ResolvedBy
|
||||
| Self::DeferredTo
|
||||
| Self::Documents => PipelineKind::ALL.to_vec(),
|
||||
| Self::Documents
|
||||
| Self::Serves
|
||||
| Self::Sources
|
||||
| Self::RestsOn
|
||||
| Self::Toward => PipelineKind::ALL.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -549,6 +567,17 @@ pub(crate) fn citations(document: &PipelineDocument) -> Vec<(CitationRole, Pipel
|
|||
),
|
||||
D::Run(run) => edges.extend(run.claims.iter().map(|to| (R::Claims, to.clone()))),
|
||||
D::Memory(memory) => edges.extend(memory.consolidates.iter().map(|to| (R::Consolidates, to.clone()))),
|
||||
D::Research(research) => {
|
||||
edges.extend(research.serves.iter().map(|to| (R::Serves, to.clone())));
|
||||
edges.extend(research.sources.iter().filter_map(|source| match source {
|
||||
ResearchSource::Document { reference } => Some((R::Sources, reference.clone())),
|
||||
_ => None,
|
||||
}));
|
||||
}
|
||||
D::Idea(idea) => {
|
||||
edges.extend(idea.rests_on.iter().map(|to| (R::RestsOn, to.clone())));
|
||||
edges.extend(idea.toward.iter().map(|to| (R::Toward, to.clone())));
|
||||
}
|
||||
D::Campaign(_) | D::Target(_) | D::Instance(_) | D::Stewardship(_) | D::Pulse(_) => {}
|
||||
}
|
||||
edges
|
||||
|
|
|
|||
|
|
@ -369,6 +369,36 @@ pub(crate) fn memory(label: &str, sort: MemorySort, text: &str, about: &[&str])
|
|||
})
|
||||
}
|
||||
|
||||
/// One research note of pass `pass`: a claim standing on one observed source, serving nothing.
|
||||
pub(crate) fn research(pass: &str, label: &str, claim: &str) -> PipelineDocument {
|
||||
PipelineDocument::Research(eureka_pipeline::PipelineResearch {
|
||||
campaign: slug(CAMPAIGN),
|
||||
pass: l(pass),
|
||||
label: l(label),
|
||||
claim: claim.into(),
|
||||
detail: "".into(),
|
||||
inferred: false,
|
||||
sources: vec![eureka_pipeline::ResearchSource::Observed { evidence: evidence() }],
|
||||
serves: vec![],
|
||||
observed_on: date(),
|
||||
})
|
||||
}
|
||||
|
||||
/// One idea of pass `pass`, resting on nothing and made for no fork.
|
||||
pub(crate) fn idea(pass: &str, label: &str, text: &str) -> PipelineDocument {
|
||||
PipelineDocument::Idea(eureka_pipeline::PipelineIdea {
|
||||
campaign: slug(CAMPAIGN),
|
||||
pass: l(pass),
|
||||
label: l(label),
|
||||
title: eureka_pipeline::Title(label.into()),
|
||||
idea: text.into(),
|
||||
tradeoffs: "".into(),
|
||||
rests_on: vec![],
|
||||
toward: vec![],
|
||||
proposed_on: date(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn provenance(faculty: Faculty) -> PipelineProvenance {
|
||||
PipelineProvenance { faculty, agent: s("claude"), session: s("session-1"), tool: s("admit") }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,22 @@ 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::Memory(memory) => lines.push(&memory.text.0),
|
||||
D::Research(research) => {
|
||||
lines.push(&research.claim.0);
|
||||
lines.push(&research.detail.0);
|
||||
for source in &research.sources {
|
||||
match source {
|
||||
eureka_pipeline::ResearchSource::Web { work, .. } => lines.push(&work.0),
|
||||
eureka_pipeline::ResearchSource::Observed { evidence } => lines.push(&evidence.result.0),
|
||||
eureka_pipeline::ResearchSource::Repo { .. } | eureka_pipeline::ResearchSource::Document { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
D::Idea(idea) => {
|
||||
lines.push(&idea.title.0);
|
||||
lines.push(&idea.idea.0);
|
||||
lines.push(&idea.tradeoffs.0);
|
||||
}
|
||||
D::Instance(_) | D::Stewardship(_) | D::Run(_) | D::Pulse(_) => {}
|
||||
}
|
||||
lines.retain(|line| !line.is_empty());
|
||||
|
|
@ -205,6 +221,30 @@ mod tests {
|
|||
(mind, hand_off_key)
|
||||
}
|
||||
|
||||
/// Research is indexed by claim, detail, each Web source's work and each Observed result;
|
||||
/// an idea by title, idea and tradeoffs. Neither moved the text version.
|
||||
#[test]
|
||||
fn index_text_of_research_and_idea() {
|
||||
let D::Research(mut note) = research("p", "a", "The limit is 4096.") else { unreachable!() };
|
||||
note.detail = "Read from the module.".into();
|
||||
note.sources = vec![
|
||||
eureka_pipeline::ResearchSource::Web { url: "https://example.org".into(), work: s("A paper"), retrieved_on: date() },
|
||||
eureka_pipeline::ResearchSource::Observed { evidence: evidence() },
|
||||
eureka_pipeline::ResearchSource::Document { reference: r(K::Question, &id("question", "Q1")) },
|
||||
];
|
||||
let observed = evidence().result.0;
|
||||
assert_eq!(index_text(&D::Research(note)).unwrap(), format!("The limit is 4096.
|
||||
Read from the module.
|
||||
A paper
|
||||
{observed}"));
|
||||
let D::Idea(mut way) = idea("p", "i", "Keep notes small.") else { unreachable!() };
|
||||
way.tradeoffs = "More documents.".into();
|
||||
assert_eq!(index_text(&D::Idea(way)).unwrap(), "i
|
||||
Keep notes small.
|
||||
More documents.");
|
||||
assert_eq!(INDEX_TEXT_VERSION, 1);
|
||||
}
|
||||
|
||||
fn texts(mind: &Mind<MemoryStore>) -> BTreeMap<String, String> {
|
||||
mind.index_entries(None).unwrap().into_iter().map(|entry| (entry.id.id.0, entry.text)).collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ fn require_grammatical_slug(field: &str, declared: &Slug) -> Result<(), MindRefu
|
|||
})
|
||||
}
|
||||
|
||||
/// A cache that knows the nineteen types a mind's store may hold: the leaf's
|
||||
/// sixteen through its registrar, the epoch record, the commit receipt and
|
||||
/// A cache that knows the twenty-one types a mind's store may hold: the leaf's
|
||||
/// eighteen through its registrar, the epoch record, the commit receipt and
|
||||
/// the persona entry. It is the cache every envelope is prepared against.
|
||||
pub(crate) fn schema_cache() -> Result<CultCache, MindRefusal> {
|
||||
let mut cache = CultCache::new();
|
||||
|
|
@ -128,7 +128,7 @@ fn fold(envelopes: &Envelopes, instance: &Slug) -> (Result<Image, MindRefusal>,
|
|||
}
|
||||
|
||||
/// One instance's mind: the declared instance, the store, the registry of
|
||||
/// the nineteen schemas, the store's envelopes as last pulled, and the image
|
||||
/// the twenty-one schemas, the store's envelopes as last pulled, and the image
|
||||
/// folded from them. The envelopes are the store's bytes and the image is a
|
||||
/// pure fold of the receipts among them: both advance with each commit and
|
||||
/// are rebuilt from the store on a lost swap, a store error and a restart.
|
||||
|
|
@ -212,7 +212,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 nineteen; and
|
||||
/// record at the current epoch; every type is one of the twenty-one; 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
|
||||
|
|
|
|||
|
|
@ -148,6 +148,8 @@ pub enum PipelineFacts {
|
|||
Run { label: Label, operated_by: RunOperator, turn: RunTurn, started_on: Date, claims: Vec<PipelineRef> },
|
||||
Pulse { label: Label, tempo_vote_beat_s: Option<u32>, #[serde(default)] tempo_vote_withdrawn: bool, urgency_permille: Option<u16> },
|
||||
Memory { label: Label, sort: MemorySort, about: Vec<Short>, origin: MemoryOrigin, salience_permille: u16 },
|
||||
Research { pass: Label, label: Label, claim: Line, inferred: bool, observed_on: Date },
|
||||
Idea { pass: Label, label: Label, title: Title, proposed_on: Date },
|
||||
}
|
||||
|
||||
impl PipelineFacts {
|
||||
|
|
@ -259,6 +261,19 @@ impl PipelineFacts {
|
|||
origin: value.origin.clone(),
|
||||
salience_permille: value.salience_permille,
|
||||
},
|
||||
D::Research(value) => Self::Research {
|
||||
pass: value.pass.clone(),
|
||||
label: value.label.clone(),
|
||||
claim: value.claim.clone(),
|
||||
inferred: value.inferred,
|
||||
observed_on: value.observed_on.clone(),
|
||||
},
|
||||
D::Idea(value) => Self::Idea {
|
||||
pass: value.pass.clone(),
|
||||
label: value.label.clone(),
|
||||
title: value.title.clone(),
|
||||
proposed_on: value.proposed_on.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1389,6 +1404,27 @@ mod tests {
|
|||
assert!(invalid("fields[0].values", "1.2")(&refused(&mind, &with(Selection::default(), any_of("cut", &["1.2"])))));
|
||||
}
|
||||
|
||||
/// The `pass` alias answers a pass's research and its ideas, and nothing of another pass.
|
||||
#[test]
|
||||
fn the_pass_alias_selects_a_passs_research_and_ideas() {
|
||||
let mut mind = seeded();
|
||||
committed(admit(&mut mind, vec![
|
||||
research("alpha", "scope", "A fact."),
|
||||
idea("alpha", "way", "A way."),
|
||||
research("beta", "scope", "Another fact."),
|
||||
idea("beta", "way", "Another way."),
|
||||
question_n(1),
|
||||
]));
|
||||
let in_pass = |pass: &str| {
|
||||
let mut found = ids(&mind.query(&with(Selection::default(), any_of("pass", &[pass]))).unwrap());
|
||||
found.sort();
|
||||
found
|
||||
};
|
||||
assert_eq!(in_pass("alpha"), vec![id("idea", "alpha.way"), id("research", "alpha.scope")]);
|
||||
assert_eq!(in_pass("beta"), vec![id("idea", "beta.way"), id("research", "beta.scope")]);
|
||||
assert!(in_pass("gamma").is_empty(), "a pass nothing was admitted under matches nothing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_values_are_the_variant_names() {
|
||||
let mind = worked();
|
||||
|
|
@ -1705,6 +1741,17 @@ mod tests {
|
|||
let D::Memory(mut consolidating) = memory("m2", MemorySort::Idea, "two", &[]) else { unreachable!() };
|
||||
consolidating.consolidates = vec![r(K::Memory, &older)];
|
||||
committed(admit(&mut mind, vec![D::Memory(consolidating)]));
|
||||
let (note, way) = (id("research", "p.a"), id("idea", "p.i"));
|
||||
let D::Research(mut note_document) = research("p", "a", "A fact.") else { unreachable!() };
|
||||
note_document.serves = vec![r(K::Question, &q1)];
|
||||
note_document.sources = vec![
|
||||
eureka_pipeline::ResearchSource::Document { reference: r(K::Finding, &f1) },
|
||||
eureka_pipeline::ResearchSource::Repo { repo: OrgRepo(REPO.into()), at: eureka_pipeline::DocRef { path: s("a.rs"), start_line: 1, end_line: 2, commit: sha() } },
|
||||
];
|
||||
let D::Idea(mut way_document) = idea("p", "i", "A way.") else { unreachable!() };
|
||||
way_document.rests_on = vec![r(K::Research, ¬e)];
|
||||
way_document.toward = vec![r(K::Question, &q1)];
|
||||
committed(admit(&mut mind, vec![D::Research(note_document), D::Idea(way_document)]));
|
||||
|
||||
let (n1, f1_closed, f2_closed) = (
|
||||
id("resolution", "question.Q1.n1"),
|
||||
|
|
@ -1739,8 +1786,12 @@ mod tests {
|
|||
(CitationRole::Claims, &run_id, &spec_r2),
|
||||
(CitationRole::Claims, &run_id, &fu),
|
||||
(CitationRole::Consolidates, &newer, &older),
|
||||
(CitationRole::Serves, ¬e, &q1),
|
||||
(CitationRole::Sources, ¬e, &f1),
|
||||
(CitationRole::RestsOn, &way, ¬e),
|
||||
(CitationRole::Toward, &way, &q1),
|
||||
];
|
||||
assert_eq!(CitationRole::ALL.len(), 17);
|
||||
assert_eq!(CitationRole::ALL.len(), 21);
|
||||
for role in CitationRole::ALL {
|
||||
let selection =
|
||||
Selection { cited: Some(Incoming { role: role.name().into(), exists: true }), ..Selection::default() };
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ pub(crate) enum Alias {
|
|||
/// A memory's origin kind: `Room`, `Direct`, `Wake` or `SelfRun`. Named `memory_origin` because
|
||||
/// `origin` already selects a finding's origin.
|
||||
MemoryOrigin,
|
||||
/// A research note's or an idea's `pass` label: a pass lists whole by root and pass.
|
||||
Pass,
|
||||
}
|
||||
|
||||
const FACULTIES: [&str; 7] = ["SelfFaculty", "Imagination", "Hands", "Soul", "Life", "Eyes", "Operator"];
|
||||
|
|
@ -75,7 +77,7 @@ const PRESENT: [&str; 1] = ["true"];
|
|||
pub(crate) const STATUS_ALIASES: [Alias; 2] = [Alias::InForce, Alias::Ran];
|
||||
|
||||
impl Alias {
|
||||
pub(crate) const ALL: [Alias; 19] = [
|
||||
pub(crate) const ALL: [Alias; 20] = [
|
||||
Self::Root,
|
||||
Self::InForce,
|
||||
Self::Faculty,
|
||||
|
|
@ -95,6 +97,7 @@ impl Alias {
|
|||
Self::Sort,
|
||||
Self::About,
|
||||
Self::MemoryOrigin,
|
||||
Self::Pass,
|
||||
];
|
||||
|
||||
pub(crate) fn name(self) -> &'static str {
|
||||
|
|
@ -118,6 +121,7 @@ impl Alias {
|
|||
Self::Sort => "sort",
|
||||
Self::About => "about",
|
||||
Self::MemoryOrigin => "memory_origin",
|
||||
Self::Pass => "pass",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +144,7 @@ impl Alias {
|
|||
Self::Turn | Self::OperatedBy | Self::Ran => kind == K::Run,
|
||||
Self::Voted | Self::Urged => kind == K::Pulse,
|
||||
Self::Sort | Self::About | Self::MemoryOrigin => kind == K::Memory,
|
||||
Self::Pass => matches!(kind, K::Research | K::Idea),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +164,7 @@ impl Alias {
|
|||
Self::Ran | Self::Voted | Self::Urged => Some(&PRESENT),
|
||||
Self::Sort => Some(MemorySort::NAMES),
|
||||
Self::MemoryOrigin => Some(&MEMORY_ORIGINS),
|
||||
Self::Root | Self::Repo | Self::Cut | Self::About => None,
|
||||
Self::Root | Self::Repo | Self::Cut | Self::About | Self::Pass => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +174,7 @@ impl Alias {
|
|||
match self {
|
||||
Self::Root => Slug(value.into()).validate_slug().is_ok(),
|
||||
Self::Repo => OrgRepo(value.into()).validate_org_repo().is_ok(),
|
||||
Self::Cut => Label(value.into()).validate_label().is_ok(),
|
||||
Self::Cut | Self::Pass => Label(value.into()).validate_label().is_ok(),
|
||||
Self::About => !value.is_empty() && value.len() <= 200,
|
||||
closed => closed.closed().is_some_and(|set| set.contains(&value)),
|
||||
}
|
||||
|
|
@ -239,7 +244,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::Run(_) | D::Pulse(_) | D::Memory(_) => {
|
||||
D::Target(_) | D::Question(_) | D::Ruling(_) | D::Verdict(_) | D::Finding(_) | D::Instance(_) | D::Resolution(_) | D::Run(_) | D::Pulse(_) | D::Memory(_) | D::Research(_) | D::Idea(_) => {
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
|
|
@ -285,6 +290,8 @@ impl SelectionRow {
|
|||
MemoryOrigin::SelfRun { .. } => "SelfRun",
|
||||
}.to_string()]);
|
||||
}
|
||||
D::Research(research) => put(Alias::Pass, vec![research.pass.0.clone()]),
|
||||
D::Idea(idea) => put(Alias::Pass, vec![idea.pass.0.clone()]),
|
||||
_ => {}
|
||||
}
|
||||
Self { kind, key: key.to_string(), ordinal: view.admission.ordinal, values, refs, view }
|
||||
|
|
@ -322,7 +329,7 @@ impl Row for SelectionRow {
|
|||
}
|
||||
}
|
||||
|
||||
/// The organ's closed lists: sixteen schemas, nineteen aliases, seventeen roles.
|
||||
/// The organ's closed lists: eighteen schemas, twenty aliases, twenty-one roles.
|
||||
/// A schema's name is its id, so the substrate's alias matching resolves
|
||||
/// nothing beyond exact ids.
|
||||
pub(crate) struct Vocabulary;
|
||||
|
|
@ -368,7 +375,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 sixteen ids; a `keys` entry that is not a full id whose kind
|
||||
/// one of the eighteen 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
|
||||
|
|
@ -435,7 +442,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]); 16] = [
|
||||
let table: [(K, &[&str]); 18] = [
|
||||
(K::Campaign, &["repo"]),
|
||||
(K::Target, &[]),
|
||||
(K::Question, &[]),
|
||||
|
|
@ -452,6 +459,8 @@ mod tests {
|
|||
(K::Run, &["turn", "operated_by", "ran"]),
|
||||
(K::Pulse, &["voted", "urged"]),
|
||||
(K::Memory, &["sort", "about", "memory_origin"]),
|
||||
(K::Research, &["pass"]),
|
||||
(K::Idea, &["pass"]),
|
||||
];
|
||||
for (kind, extra) in table {
|
||||
let want = [&["root", "in_force", "faculty"][..], extra].concat();
|
||||
|
|
@ -464,10 +473,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
|
||||
/// sixteen.
|
||||
/// eighteen.
|
||||
#[test]
|
||||
fn each_kind_declares_the_roles_it_carries_and_their_targets() {
|
||||
let table: [(K, &[&str]); 16] = [
|
||||
let table: [(K, &[&str]); 18] = [
|
||||
(K::Campaign, &[]),
|
||||
(K::Target, &[]),
|
||||
(K::Question, &["raised_in"]),
|
||||
|
|
@ -484,13 +493,15 @@ mod tests {
|
|||
(K::Run, &["claims"]),
|
||||
(K::Pulse, &[]),
|
||||
(K::Memory, &["consolidates"]),
|
||||
(K::Research, &["serves", "sources"]),
|
||||
(K::Idea, &["rests_on", "toward"]),
|
||||
];
|
||||
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(), 16);
|
||||
assert_eq!(Vocabulary.target_leaves("subject").len(), 18);
|
||||
assert_eq!(
|
||||
sorted(Vocabulary.target_leaves("claims")),
|
||||
sorted([K::CutSpec, K::Finding, K::FollowUp].iter().map(|kind| kind.type_id().to_string()).collect())
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ compares file to derivation byte for byte.
|
|||
|
||||
## The pipeline catalogue
|
||||
|
||||
`index.json` is the publication manifest for every schema here: the sixteen
|
||||
`index.json` is the publication manifest for every schema here: the eighteen
|
||||
`epiphany.pipeline.*.v2` schemas, `gamecult.persona_state.v0` and the two
|
||||
`huginn.mind_*` wire schemas. The pipeline
|
||||
schemas are derived from the value types in `crates/eureka-pipeline` and checked
|
||||
|
|
|
|||
|
|
@ -173,7 +173,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
127
schemas/cultnet/epiphany.pipeline.idea.v2.schema.json
Normal file
127
schemas/cultnet/epiphany.pipeline.idea.v2.schema.json
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineRef": {
|
||||
"description": "A reference to another document. The id is parsed as a full pipeline id of\nthe declared `kind`, so the kind and the id's kind segment cannot disagree.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Title": {
|
||||
"description": "A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema\nlength counts characters. Holds at least one alphanumeric character,\nno control character, and no bidi control character (`U+061C`,\n`U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`).",
|
||||
"maxLength": 200,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.idea.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "One thing that could be, including options rejected and ideas no cut\nuses yet. `rests_on` is what it stands on (a reader selects the ideas\nresting on a stale note with `cites`); `toward` is the fork, ruling or\nfollow-up it was made for, and empty is legal. It stores no status:\ntaken up is its Deferred resolution, rejected its Recorded one.\nCampaign-rooted: `<campaign>:idea:<pass>.<label>`.",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"idea": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"proposed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"rests_on": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
},
|
||||
"toward": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"tradeoffs": {
|
||||
"$ref": "#/$defs/Para"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"pass",
|
||||
"label",
|
||||
"title",
|
||||
"idea",
|
||||
"tradeoffs",
|
||||
"rests_on",
|
||||
"toward",
|
||||
"proposed_on"
|
||||
],
|
||||
"title": "PipelineIdea",
|
||||
"type": "object"
|
||||
}
|
||||
|
|
@ -128,7 +128,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
294
schemas/cultnet/epiphany.pipeline.research.v2.schema.json
Normal file
294
schemas/cultnet/epiphany.pipeline.research.v2.schema.json
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"DocRef": {
|
||||
"properties": {
|
||||
"commit": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"end_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"start_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"start_line",
|
||||
"end_line",
|
||||
"commit"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Evidence": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"$ref": "#/$defs/EvidenceKind"
|
||||
},
|
||||
"locator": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"result": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"locator",
|
||||
"result"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EvidenceKind": {
|
||||
"enum": [
|
||||
"Command",
|
||||
"Test",
|
||||
"Mutation",
|
||||
"Probe",
|
||||
"SourceRead",
|
||||
"Capture"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineRef": {
|
||||
"description": "A reference to another document. The id is parsed as a full pipeline id of\nthe declared `kind`, so the kind and the id's kind segment cannot disagree.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ResearchSource": {
|
||||
"description": "Where a research claim comes from. A `Repo` pin and a `Web` page are\noutside the document set; an `Observed` result reuses a finding's evidence;\na `Document` is the one source that is a citation edge.",
|
||||
"oneOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "A pinned file range in a repo.",
|
||||
"properties": {
|
||||
"Repo": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"$ref": "#/$defs/DocRef"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repo",
|
||||
"at"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Repo"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "A page read on the web: its address, the work it belongs to, and when.",
|
||||
"properties": {
|
||||
"Web": {
|
||||
"properties": {
|
||||
"retrieved_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"url": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"work": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url",
|
||||
"work",
|
||||
"retrieved_on"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Web"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "A command, probe, read or capture the pass ran itself.",
|
||||
"properties": {
|
||||
"Observed": {
|
||||
"properties": {
|
||||
"evidence": {
|
||||
"$ref": "#/$defs/Evidence"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"evidence"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Observed"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Another document in the mind.",
|
||||
"properties": {
|
||||
"Document": {
|
||||
"properties": {
|
||||
"reference": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reference"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Document"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.research.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "One claim a pass found, with the sources it stands on, grouped by `pass`.\nWhat is so, never what to do: a recommendation is an idea. `inferred`\nmarks a claim read from its sources rather than seen in them. It stores\nno status: whether a note is current, superseded or wrong is its\nresolutions'. `serves` names the question, cut or campaign document it\nwas gathered for. The pass's method and what it did not search is its\nfirst note, labelled `scope`. Campaign-rooted:\n`<campaign>:research:<pass>.<label>`.",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"claim": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"detail": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"inferred": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"observed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"serves": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"sources": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/ResearchSource"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"pass",
|
||||
"label",
|
||||
"claim",
|
||||
"detail",
|
||||
"inferred",
|
||||
"sources",
|
||||
"serves",
|
||||
"observed_on"
|
||||
],
|
||||
"title": "PipelineResearch",
|
||||
"type": "object"
|
||||
}
|
||||
|
|
@ -32,7 +32,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1021,7 +1021,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\nsixteen 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\neighteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
@ -1278,6 +1278,38 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "research"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineResearch"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "idea"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineIdea"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -1435,6 +1467,58 @@
|
|||
"handed_on"
|
||||
]
|
||||
},
|
||||
"PipelineIdea": {
|
||||
"description": "One thing that could be, including options rejected and ideas no cut\nuses yet. `rests_on` is what it stands on (a reader selects the ideas\nresting on a stale note with `cites`); `toward` is the fork, ruling or\nfollow-up it was made for, and empty is legal. It stores no status:\ntaken up is its Deferred resolution, rejected its Recorded one.\nCampaign-rooted: `<campaign>:idea:<pass>.<label>`.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"idea": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"proposed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"rests_on": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 16
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
},
|
||||
"toward": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"tradeoffs": {
|
||||
"$ref": "#/$defs/Para"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"pass",
|
||||
"label",
|
||||
"title",
|
||||
"idea",
|
||||
"tradeoffs",
|
||||
"rests_on",
|
||||
"toward",
|
||||
"proposed_on"
|
||||
]
|
||||
},
|
||||
"PipelineInstance": {
|
||||
"description": "A mind's identity document. A store is canonical to exactly one\ninstance, and this says which; identity lives in the state, not in a\npath.",
|
||||
"type": "object",
|
||||
|
|
@ -1477,7 +1561,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
]
|
||||
},
|
||||
"PipelineMemory": {
|
||||
|
|
@ -1676,6 +1762,58 @@
|
|||
"id"
|
||||
]
|
||||
},
|
||||
"PipelineResearch": {
|
||||
"description": "One claim a pass found, with the sources it stands on, grouped by `pass`.\nWhat is so, never what to do: a recommendation is an idea. `inferred`\nmarks a claim read from its sources rather than seen in them. It stores\nno status: whether a note is current, superseded or wrong is its\nresolutions'. `serves` names the question, cut or campaign document it\nwas gathered for. The pass's method and what it did not search is its\nfirst note, labelled `scope`. Campaign-rooted:\n`<campaign>:research:<pass>.<label>`.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"claim": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"detail": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"inferred": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"observed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"serves": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/ResearchSource"
|
||||
},
|
||||
"maxItems": 16
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"pass",
|
||||
"label",
|
||||
"claim",
|
||||
"detail",
|
||||
"inferred",
|
||||
"sources",
|
||||
"serves",
|
||||
"observed_on"
|
||||
]
|
||||
},
|
||||
"PipelineResolution": {
|
||||
"description": "The record of how a subject was closed and by what. `sequence` is per\nsubject and set by the writer, `1` for the first, and it is the last\npart of the key, so a subject's resolutions share a prefix and a\nwithdrawn one is kept under its subject rather than overwritten.\nWhether a sequence is the previous plus one, and whether an earlier\nresolution still stands, are admission's rules: this crate refuses\nneither `0` nor a gap, exactly as it refuses neither `revision: 0` nor\na `revision` with no predecessor.",
|
||||
"type": "object",
|
||||
|
|
@ -2009,6 +2147,107 @@
|
|||
"builds"
|
||||
]
|
||||
},
|
||||
"ResearchSource": {
|
||||
"description": "Where a research claim comes from. A `Repo` pin and a `Web` page are\noutside the document set; an `Observed` result reuses a finding's evidence;\na `Document` is the one source that is a citation edge.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A pinned file range in a repo.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Repo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"at": {
|
||||
"$ref": "#/$defs/DocRef"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repo",
|
||||
"at"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Repo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A page read on the web: its address, the work it belongs to, and when.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Web": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"retrieved_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"url": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"work": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url",
|
||||
"work",
|
||||
"retrieved_on"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A command, probe, read or capture the pass ran itself.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Observed": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"evidence": {
|
||||
"$ref": "#/$defs/Evidence"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"evidence"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Observed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Another document in the mind.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Document": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reference": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reference"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Document"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ResolutionOutcome": {
|
||||
"description": "How a subject was resolved. Every referent is a parsed `PipelineRef`, so a\nresolution names its records by ids of the kinds they declare, validated\nwhere every other referent is; a `Fixed` commit is a `Sha` whose referent\nis outside the document set (ruling B). Whether a named document exists,\nand how many may supersede one subject, are admission's rules.",
|
||||
"oneOf": [
|
||||
|
|
|
|||
|
|
@ -256,7 +256,11 @@
|
|||
"deferred_to",
|
||||
"documents",
|
||||
"claims",
|
||||
"consolidates"
|
||||
"consolidates",
|
||||
"serves",
|
||||
"sources",
|
||||
"rests_on",
|
||||
"toward"
|
||||
]
|
||||
},
|
||||
"ClaimOutcome": {
|
||||
|
|
@ -2319,7 +2323,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\nsixteen 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\neighteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
@ -2576,6 +2580,38 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "research"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineResearch"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "idea"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineIdea"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -3339,6 +3375,82 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "research"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"claim": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"inferred": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"observed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pass",
|
||||
"label",
|
||||
"claim",
|
||||
"inferred",
|
||||
"observed_on"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "idea"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"proposed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pass",
|
||||
"label",
|
||||
"title",
|
||||
"proposed_on"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -3496,6 +3608,58 @@
|
|||
"handed_on"
|
||||
]
|
||||
},
|
||||
"PipelineIdea": {
|
||||
"description": "One thing that could be, including options rejected and ideas no cut\nuses yet. `rests_on` is what it stands on (a reader selects the ideas\nresting on a stale note with `cites`); `toward` is the fork, ruling or\nfollow-up it was made for, and empty is legal. It stores no status:\ntaken up is its Deferred resolution, rejected its Recorded one.\nCampaign-rooted: `<campaign>:idea:<pass>.<label>`.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"idea": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"proposed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"rests_on": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 16
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
},
|
||||
"toward": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"tradeoffs": {
|
||||
"$ref": "#/$defs/Para"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"pass",
|
||||
"label",
|
||||
"title",
|
||||
"idea",
|
||||
"tradeoffs",
|
||||
"rests_on",
|
||||
"toward",
|
||||
"proposed_on"
|
||||
]
|
||||
},
|
||||
"PipelineInstance": {
|
||||
"description": "A mind's identity document. A store is canonical to exactly one\ninstance, and this says which; identity lives in the state, not in a\npath.",
|
||||
"type": "object",
|
||||
|
|
@ -3538,7 +3702,9 @@
|
|||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
"Memory",
|
||||
"Research",
|
||||
"Idea"
|
||||
]
|
||||
},
|
||||
"PipelineMemory": {
|
||||
|
|
@ -3905,6 +4071,58 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"PipelineResearch": {
|
||||
"description": "One claim a pass found, with the sources it stands on, grouped by `pass`.\nWhat is so, never what to do: a recommendation is an idea. `inferred`\nmarks a claim read from its sources rather than seen in them. It stores\nno status: whether a note is current, superseded or wrong is its\nresolutions'. `serves` names the question, cut or campaign document it\nwas gathered for. The pass's method and what it did not search is its\nfirst note, labelled `scope`. Campaign-rooted:\n`<campaign>:research:<pass>.<label>`.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"claim": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"detail": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"inferred": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"observed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"pass": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"serves": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/ResearchSource"
|
||||
},
|
||||
"maxItems": 16
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"pass",
|
||||
"label",
|
||||
"claim",
|
||||
"detail",
|
||||
"inferred",
|
||||
"sources",
|
||||
"serves",
|
||||
"observed_on"
|
||||
]
|
||||
},
|
||||
"PipelineResolution": {
|
||||
"description": "The record of how a subject was closed and by what. `sequence` is per\nsubject and set by the writer, `1` for the first, and it is the last\npart of the key, so a subject's resolutions share a prefix and a\nwithdrawn one is kept under its subject rather than overwritten.\nWhether a sequence is the previous plus one, and whether an earlier\nresolution still stands, are admission's rules: this crate refuses\nneither `0` nor a gap, exactly as it refuses neither `revision: 0` nor\na `revision` with no predecessor.",
|
||||
"type": "object",
|
||||
|
|
@ -4527,6 +4745,107 @@
|
|||
"builds"
|
||||
]
|
||||
},
|
||||
"ResearchSource": {
|
||||
"description": "Where a research claim comes from. A `Repo` pin and a `Web` page are\noutside the document set; an `Observed` result reuses a finding's evidence;\na `Document` is the one source that is a citation edge.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A pinned file range in a repo.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Repo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"at": {
|
||||
"$ref": "#/$defs/DocRef"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repo",
|
||||
"at"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Repo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A page read on the web: its address, the work it belongs to, and when.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Web": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"retrieved_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"url": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"work": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url",
|
||||
"work",
|
||||
"retrieved_on"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A command, probe, read or capture the pass ran itself.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Observed": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"evidence": {
|
||||
"$ref": "#/$defs/Evidence"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"evidence"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Observed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Another document in the mind.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Document": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reference": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reference"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Document"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ResolutionOutcome": {
|
||||
"description": "How a subject was resolved. Every referent is a parsed `PipelineRef`, so a\nresolution names its records by ids of the kinds they declare, validated\nwhere every other referent is; a `Fixed` commit is a `Sha` whose referent\nis outside the document set (ruling B). Whether a named document exists,\nand how many may supersede one subject, are admission's rules.",
|
||||
"oneOf": [
|
||||
|
|
|
|||
|
|
@ -188,6 +188,28 @@
|
|||
"title": "Epiphany Pipeline Memory v2",
|
||||
"path": "epiphany.pipeline.memory.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.research.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.research.v2",
|
||||
"documentType": "epiphany.pipeline.research.v2",
|
||||
"title": "Epiphany Pipeline Research v2",
|
||||
"path": "epiphany.pipeline.research.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.idea.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.idea.v2",
|
||||
"documentType": "epiphany.pipeline.idea.v2",
|
||||
"title": "Epiphany Pipeline Idea v2",
|
||||
"path": "epiphany.pipeline.idea.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "huginn.mind_request.v1",
|
||||
"kind": "wire_message",
|
||||
|
|
|
|||
Loading…
Reference in a new issue