memory: a persistent kind for what a Mind keeps (eureka-pipeline, huginn-mind)
epiphany.pipeline.memory.v2, instance-rooted `<instance>:memory:<label>`: PipelineMemory with seven sorts (Asked, Decided, Committed, Learned, Did, Idea, Rapport), her own text, who it is about, where it was learned, whether directly, what it rests on, a salience that only ranks, and the memories a consolidating one replaces. Admission refuses a foreign instance and resolves a memory only Deferred to a follow-up (an ask taken on), Recorded, Withdrawn or Superseded by a memory. Memories have their own lineage so supersession stays within one mind. The read side selects by sort, about (multi-valued) and direct; the index carries the text; `consolidates` is a citation role. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ffb379d18d
commit
152e315e5d
23 changed files with 693 additions and 41 deletions
|
|
@ -26,6 +26,7 @@ sample.stewardship yggdrasil:stewardship:gamecult_-epiphany.n1 epiphany.pipeline
|
|||
sample.hand_off yggdrasil:hand_off:thought-cage.gamecult_-epiphany.2026-09-15 epiphany.pipeline.hand_off.v2 EpiphanyPipelineHandOffDocument 9186ad66726f6d5f696e7374616e6365a979676764726173696cab746f5f696e7374616e6365ac74686f756768742d63616765a47265706fb147616d6543756c742f4570697068616e79a9646f63756d656e747392ac657572656b612d7374617465b6657572656b612d73746174653a72756c696e673a5238a6726561736f6ed92854686520776f726b73746174696f6e206d696e642074616b6573207468652063616d706169676e2ea968616e6465645f6f6eaa323032362d30392d3135
|
||||
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 918aa8696e7374616e6365a979676764726173696ca56c6162656cb7323032362d31302d3331743034313230302d74302d6d30a4736f7274a9436f6d6d6974746564a474657874d936492074616b6520746865206261636b67726f756e6420736861646572733b204c6962627920686f6c64732074686520736f756e64732ea561626f757491ab406c696262793a63686174aa6c6561726e65645f696ea5737761726da6646972656374c2a7736f757263657391a3246531b173616c69656e63655f7065726d696c6c65cd02bcac636f6e736f6c69646174657390
|
||||
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
|
||||
|
|
|
|||
|
|
@ -606,6 +606,26 @@ fn pulse_is_well_formed(pulse: &PipelinePulse, at: &str) -> Result<(), PipelineR
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// The most thousandths a memory's salience may carry.
|
||||
pub const SALIENCE_MAX_PERMILLE: u16 = 1000;
|
||||
|
||||
/// A memory says something (its text is not empty), its salience is in
|
||||
/// thousandths, and it consolidates only memories.
|
||||
fn memory_is_well_formed(memory: &PipelineMemory, at: &str) -> Result<(), PipelineRefusal> {
|
||||
if memory.text.0.trim().is_empty() {
|
||||
return Err(format_error(&format!("{at}.text"), "an empty memory"));
|
||||
}
|
||||
if memory.salience_permille > SALIENCE_MAX_PERMILLE {
|
||||
return Err(format_error(&format!("{at}.salience_permille"), &memory.salience_permille.to_string()));
|
||||
}
|
||||
for (index, entry) in memory.consolidates.iter().enumerate() {
|
||||
if entry.kind != PipelineKind::Memory {
|
||||
return Err(format_error(&format!("{at}.consolidates[{index}].kind"), entry.kind.name()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unit_enums! {
|
||||
EvidenceKind { Command, Test, Mutation, Probe, SourceRead, Capture }
|
||||
ClaimOutcome { Holds, Falsified, Unproven }
|
||||
|
|
@ -615,6 +635,7 @@ unit_enums! {
|
|||
FindingOrigin { Introduced, PreExisting }
|
||||
RunOperator { Operator, Mind }
|
||||
RunTurn { PersonaTurn, SelfRun }
|
||||
MemorySort { Asked, Decided, Committed, Learned, Did, Idea, Rapport }
|
||||
}
|
||||
|
||||
value_types! {
|
||||
|
|
@ -754,6 +775,20 @@ value_types! {
|
|||
pub struct PipelinePulse {
|
||||
instance: Slug, label: Label, tempo_vote_beat_s: Option<u32>, tempo_vote_withdrawn: bool = absent, urgency_permille: Option<u16>, note: Line,
|
||||
} => pulse_is_well_formed
|
||||
/// One thing a Mind keeps for herself, in her own mind: what she was asked,
|
||||
/// decided, committed to, learned or did, an idea, or the rapport between
|
||||
/// her and another Mind or person. `text` is her own words. `about` names
|
||||
/// the Matrix users it concerns (Minds included), `learned_in` where she
|
||||
/// learned it (a room alias, a DM's room id, or `self run <label>`),
|
||||
/// `direct` whether it came in a direct message, and `sources` the event
|
||||
/// ids, commits or documents it rests on; the organ fills the last three,
|
||||
/// not the model. `salience_permille` ranks it and decides nothing.
|
||||
/// `consolidates` names the memories a consolidating memory replaces.
|
||||
/// Instance-rooted: the key is `<instance>:memory:<label>`.
|
||||
pub struct PipelineMemory {
|
||||
instance: Slug, label: Label, sort: MemorySort, text: Line, about: Vec<Short>[8], learned_in: Short, direct: bool,
|
||||
sources: Vec<Short>[8], salience_permille: u16, consolidates: Vec<PipelineRef>[16],
|
||||
} => memory_is_well_formed
|
||||
}
|
||||
|
||||
/// A reference to another document. The id is parsed as a full pipeline id of
|
||||
|
|
@ -856,7 +891,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
|
||||
/// fifteen per-kind value documents, and the envelope is the wire's
|
||||
/// sixteen 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")]
|
||||
|
|
@ -977,6 +1012,8 @@ pipeline_kinds! {
|
|||
"epiphany.pipeline.run.v2", "EpiphanyPipelineRunDocument";
|
||||
Pulse(PipelinePulse) => EpiphanyPipelinePulseDocument, "pulse",
|
||||
"epiphany.pipeline.pulse.v2", "EpiphanyPipelinePulseDocument";
|
||||
Memory(PipelineMemory) => EpiphanyPipelineMemoryDocument, "memory",
|
||||
"epiphany.pipeline.memory.v2", "EpiphanyPipelineMemoryDocument";
|
||||
}
|
||||
|
||||
/// Parses a full document id: the grammar read backwards. A key and an id are
|
||||
|
|
@ -1173,6 +1210,7 @@ pub fn pipeline_key(document: &PipelineDocument) -> Result<String, PipelineRefus
|
|||
// A run hangs off its instance too: its label is its whole local.
|
||||
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::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])?),
|
||||
|
|
@ -1421,6 +1459,11 @@ mod tests {
|
|||
instance: slug(INSTANCE), label: l("2026-10-31t041200-t0"), tempo_vote_beat_s: Some(600), tempo_vote_withdrawn: false, urgency_permille: Some(250),
|
||||
note: "The swarm is idle; a shorter beat.".into(),
|
||||
}), format!("{INSTANCE}:pulse:2026-10-31t041200-t0")),
|
||||
(D::Memory(PipelineMemory {
|
||||
instance: slug(INSTANCE), label: l("2026-10-31t041200-t0-m0"), sort: MemorySort::Committed,
|
||||
text: "I take the background shaders; Libby holds the sounds.".into(), about: vec![s("@libby:chat")],
|
||||
learned_in: s("swarm"), direct: false, sources: vec![s("$e1")], salience_permille: 700, consolidates: vec![],
|
||||
}), format!("{INSTANCE}:memory:2026-10-31t041200-t0-m0")),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1753,6 +1796,42 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn memory_sample() -> PipelineMemory {
|
||||
let PipelineDocument::Memory(memory) = samples().remove(15).0 else { unreachable!() };
|
||||
memory
|
||||
}
|
||||
|
||||
/// A memory says something, its salience is in thousandths, and it consolidates only memories.
|
||||
#[test]
|
||||
fn a_memory_says_something_in_bounds_and_consolidates_only_memories() {
|
||||
let validated = |change: &dyn Fn(&mut PipelineMemory)| {
|
||||
let mut memory = memory_sample();
|
||||
change(&mut memory);
|
||||
PipelineDocument::Memory(memory).validate()
|
||||
};
|
||||
assert_eq!(validated(&|_| {}), Ok(()));
|
||||
assert_eq!(validated(&|memory| memory.salience_permille = 1000), Ok(()));
|
||||
let bad = |outcome: Result<(), PipelineRefusal>, expected: &str| {
|
||||
assert!(matches!(outcome, Err(PipelineRefusal::InvalidFormat { field, .. }) if field == expected), "{expected}");
|
||||
};
|
||||
bad(validated(&|memory| memory.text = " ".into()), "memory.text");
|
||||
bad(validated(&|memory| memory.text = "".into()), "memory.text");
|
||||
bad(validated(&|memory| memory.salience_permille = 1001), "memory.salience_permille");
|
||||
bad(validated(&|memory| memory.consolidates = vec![PipelineRef { kind: PipelineKind::Ruling, id: id("ruling", "R8") }]), "memory.consolidates[0].kind");
|
||||
let own = PipelineRef { kind: PipelineKind::Memory, id: Short(format!("{INSTANCE}:memory:older")) };
|
||||
assert_eq!(validated(&|memory| memory.consolidates = vec![own.clone()]), Ok(()));
|
||||
assert!(matches!(validated(&|memory| memory.about = vec![s("@a:chat"); 9]), Err(PipelineRefusal::FieldBound { limit: 8, .. })));
|
||||
assert!(matches!(validated(&|memory| memory.sources = vec![s("$e"); 9]), Err(PipelineRefusal::FieldBound { limit: 8, .. })));
|
||||
assert!(matches!(validated(&|memory| memory.text = Line("x".repeat(1001))), Err(PipelineRefusal::FieldBound { limit: 1000, .. })));
|
||||
}
|
||||
|
||||
/// A memory is instance-rooted and its label is its whole local.
|
||||
#[test]
|
||||
fn a_memory_is_keyed_under_its_instance_by_its_label() -> Result<()> {
|
||||
assert_eq!(pipeline_key(&PipelineDocument::Memory(memory_sample()))?, format!("{INSTANCE}:memory:2026-10-31t041200-t0-m0"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bounds: a run's campaigns and claims stop at their maximums.
|
||||
#[test]
|
||||
fn a_runs_lists_are_bounded() {
|
||||
|
|
@ -3307,11 +3386,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(), 15, "fifteen kinds");
|
||||
assert_eq!(PipelineKind::ALL.len(), 16, "sixteen 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 fifteen kinds, by name"
|
||||
"the document schema lists exactly the sixteen kinds, by name"
|
||||
);
|
||||
|
||||
let refusal_schema = serde_json::to_value(schemars::schema_for!(PipelineRefusal))?;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
hand_off, run, pulse, memory,
|
||||
);
|
||||
|
||||
/// The schema named, or the names when none is: the exit code is 2 for a name
|
||||
|
|
|
|||
|
|
@ -974,10 +974,11 @@ 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(), 16, "{names:?}");
|
||||
assert_eq!(names.len(), 17, "{names:?}");
|
||||
assert_eq!(names[0], "campaign");
|
||||
assert_eq!(names[14], "pulse");
|
||||
assert_eq!(names[15], "selection");
|
||||
assert_eq!(names[15], "memory");
|
||||
assert_eq!(names[16], "selection");
|
||||
for kind in PipelineKind::ALL {
|
||||
assert!(names.contains(&kind.name()), "{}", kind.name());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,6 +312,7 @@ fn refuse_foreign_instance(document: &PipelineDocument, mind: &Slug) -> Result<(
|
|||
PipelineDocument::Stewardship(value) if value.instance != *mind => Err(foreign(&value.instance)),
|
||||
PipelineDocument::Run(value) if value.instance != *mind => Err(foreign(&value.instance)),
|
||||
PipelineDocument::Pulse(value) if value.instance != *mind => Err(foreign(&value.instance)),
|
||||
PipelineDocument::Memory(value) if value.instance != *mind => Err(foreign(&value.instance)),
|
||||
PipelineDocument::HandOff(value) if value.from_instance != *mind && value.to_instance != *mind => {
|
||||
Err(foreign(&value.from_instance))
|
||||
}
|
||||
|
|
@ -621,7 +622,7 @@ fn check(docs: &Docs, staged: &Staged, mind: &Slug) -> Result<(), MindRefusal> {
|
|||
let replay = docs.in_image(K::Run, key) == Some(&staged.document);
|
||||
run_rule(docs, key, run, replay)
|
||||
}
|
||||
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) => Ok(()),
|
||||
D::FollowUp(_) | D::Instance(_) | D::Pulse(_) | D::Memory(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -716,6 +717,9 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool {
|
|||
(K::Stewardship, O::Superseded { by }) => all(by, K::Stewardship),
|
||||
(K::Stewardship, O::Withdrawn { .. }) => true,
|
||||
(K::Run, O::Recorded { .. } | O::Withdrawn { .. }) => true,
|
||||
(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::Resolution, O::Withdrawn { .. }) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
|
@ -843,7 +847,7 @@ mod tests {
|
|||
use crate::receipt::{DocumentVersion, Faculty, HuginnCommitReceipt};
|
||||
use crate::store::test_stores::{MemoryStore, RefusingStore, SwapCommand};
|
||||
use cultcache_rs::DatabaseEntry;
|
||||
use eureka_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument as D, PipelineKind as K, RunTurn};
|
||||
use eureka_pipeline::{MemorySort, PIPELINE_SCHEMA_EPOCH, PipelineDocument as D, PipelineKind as K, RunTurn};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
fn receipt_count<S: MindStore>(mind: &Mind<S>) -> usize {
|
||||
|
|
@ -2462,6 +2466,82 @@ mod tests {
|
|||
committed(admit(&mut mind, vec![pulse(INSTANCE)]));
|
||||
}
|
||||
|
||||
fn memory_key(label: &str) -> String {
|
||||
format!("{INSTANCE}:memory:{label}")
|
||||
}
|
||||
|
||||
/// A memory is her own: one of another mind is a ForeignInstance, and her own is admitted.
|
||||
#[test]
|
||||
fn a_memory_of_another_mind_is_a_foreign_instance_and_her_own_is_admitted() {
|
||||
let mut mind = with_specs();
|
||||
let D::Memory(mut foreign) = memory("m1", MemorySort::Asked, "Prioritise the fork.", &["@op:chat"]) else { panic!() };
|
||||
foreign.instance = slug(OTHER_INSTANCE);
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![D::Memory(foreign)])),
|
||||
MindRefusal::ForeignInstance { declared: OTHER_INSTANCE.into(), mind: INSTANCE.into() }
|
||||
);
|
||||
committed(admit(&mut mind, vec![memory("m1", MemorySort::Asked, "Prioritise the fork.", &["@op:chat"])]));
|
||||
}
|
||||
|
||||
/// A memory is taken on (Deferred to a follow-up), recorded, withdrawn or superseded by a
|
||||
/// memory; Fixed, Answered, and Deferred or Superseded to anything else are refused.
|
||||
#[test]
|
||||
fn a_memory_resolves_deferred_to_a_follow_up_recorded_withdrawn_or_superseded_by_a_memory_only() {
|
||||
let incompatible = |outcome: &str| MindRefusal::IncompatibleResolution { subject_kind: K::Memory, outcome: outcome.into() };
|
||||
let subject = || r(K::Memory, &memory_key("m1"));
|
||||
let follow = || r(K::FollowUp, &id("follow_up", "FU"));
|
||||
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"))),
|
||||
memory("m1", MemorySort::Asked, "Prioritise the fork.", &[]),
|
||||
memory("m2", MemorySort::Idea, "A newer way.", &[]),
|
||||
]));
|
||||
mind
|
||||
};
|
||||
for outcome in [
|
||||
ResolutionOutcome::Deferred { to: follow() },
|
||||
ResolutionOutcome::Recorded { reason: "done".into() },
|
||||
withdrawn(),
|
||||
superseded(&[r(K::Memory, &memory_key("m2"))]),
|
||||
] {
|
||||
committed(admit(&mut open(), vec![resolution(subject(), outcome)]));
|
||||
}
|
||||
let refused = [
|
||||
(ResolutionOutcome::Deferred { to: r(K::CutSpec, &id("cut_spec", "cut-1.r1")) }, "Deferred"),
|
||||
(ResolutionOutcome::Fixed { commit: sha(), by: None }, "Fixed"),
|
||||
(ResolutionOutcome::Answered { by: r(K::Ruling, &id("ruling", "R1")) }, "Answered"),
|
||||
(superseded(&[r(K::Ruling, &id("ruling", "R1"))]), "Superseded"),
|
||||
];
|
||||
for (outcome, name) in refused {
|
||||
assert_eq!(refusal(admit(&mut open(), vec![resolution(subject(), outcome)])), incompatible(name), "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A memory consolidates memories of this mind: a ruling is refused by the leaf, another mind's
|
||||
/// memory is a missing reference, her own is cited.
|
||||
#[test]
|
||||
fn a_memory_consolidates_only_memories_in_this_mind() {
|
||||
let mut mind = with_specs();
|
||||
committed(admit(&mut mind, vec![D::Ruling(ruling("R1")), memory("m1", MemorySort::Idea, "One.", &[])]));
|
||||
let consolidating = |cites: PipelineRef| {
|
||||
let D::Memory(mut memory) = memory("m2", MemorySort::Idea, "One and more.", &[]) else { panic!() };
|
||||
memory.consolidates = vec![cites];
|
||||
D::Memory(memory)
|
||||
};
|
||||
assert!(matches!(
|
||||
refusal(admit(&mut mind, vec![consolidating(r(K::Ruling, &id("ruling", "R1")))])),
|
||||
MindRefusal::Document(PipelineRefusal::InvalidFormat { .. })
|
||||
));
|
||||
let elsewhere = format!("{OTHER_INSTANCE}:memory:m1");
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![consolidating(r(K::Memory, &elsewhere))])),
|
||||
MindRefusal::MissingReference { kind: K::Memory, id: elsewhere }
|
||||
);
|
||||
committed(admit(&mut mind, vec![consolidating(r(K::Memory, &memory_key("m1")))]));
|
||||
}
|
||||
|
||||
/// A run ends: Recorded or Withdrawn, and no other outcome.
|
||||
#[test]
|
||||
fn a_run_resolves_recorded_or_withdrawn_and_nothing_else() {
|
||||
|
|
|
|||
|
|
@ -317,6 +317,9 @@ pub(crate) enum Lineage<'a> {
|
|||
Cut(&'a Slug, &'a Label),
|
||||
/// A stewardship: the instance and the repo.
|
||||
Assignment(&'a Slug, &'a OrgRepo),
|
||||
/// 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
|
||||
/// across campaigns.
|
||||
Global,
|
||||
|
|
@ -328,6 +331,7 @@ pub(crate) fn line_of(document: &PipelineDocument) -> Option<Lineage<'_>> {
|
|||
D::Target(target) => Some(Lineage::Campaign(&target.campaign)),
|
||||
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::Campaign(_)
|
||||
| D::Question(_)
|
||||
|
|
@ -382,10 +386,11 @@ pub enum CitationRole {
|
|||
DeferredTo,
|
||||
Documents,
|
||||
Claims,
|
||||
Consolidates,
|
||||
}
|
||||
|
||||
impl CitationRole {
|
||||
pub const ALL: [CitationRole; 16] = [
|
||||
pub const ALL: [CitationRole; 17] = [
|
||||
Self::RaisedIn,
|
||||
Self::Answers,
|
||||
Self::Rulings,
|
||||
|
|
@ -402,6 +407,7 @@ impl CitationRole {
|
|||
Self::DeferredTo,
|
||||
Self::Documents,
|
||||
Self::Claims,
|
||||
Self::Consolidates,
|
||||
];
|
||||
|
||||
/// The wire spelling, equal to the referring field's name.
|
||||
|
|
@ -423,6 +429,7 @@ impl CitationRole {
|
|||
Self::DeferredTo => "deferred_to",
|
||||
Self::Documents => "documents",
|
||||
Self::Claims => "claims",
|
||||
Self::Consolidates => "consolidates",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -444,6 +451,7 @@ impl CitationRole {
|
|||
Self::Subject | Self::SupersededBy | Self::ResolvedBy | Self::DeferredTo => K::Resolution,
|
||||
Self::Documents => K::HandOff,
|
||||
Self::Claims => K::Run,
|
||||
Self::Consolidates => K::Memory,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -460,6 +468,7 @@ impl CitationRole {
|
|||
Self::Findings => vec![K::Finding],
|
||||
Self::Verdict => vec![K::Verdict],
|
||||
Self::Claims => vec![K::CutSpec, K::Finding, K::FollowUp],
|
||||
Self::Consolidates => vec![K::Memory],
|
||||
Self::RaisedIn
|
||||
| Self::Source
|
||||
| Self::Subject
|
||||
|
|
@ -538,6 +547,7 @@ pub(crate) fn citations(document: &PipelineDocument) -> Vec<(CitationRole, Pipel
|
|||
hand_off.documents.iter().filter_map(|to| kind_of_id(&to.0).map(|kind| (R::Documents, cited(kind, to)))),
|
||||
),
|
||||
D::Run(run) => edges.extend(run.claims.iter().map(|to| (R::Claims, to.clone()))),
|
||||
D::Memory(memory) => edges.extend(memory.consolidates.iter().map(|to| (R::Consolidates, to.clone()))),
|
||||
D::Campaign(_) | D::Target(_) | D::Instance(_) | D::Stewardship(_) | D::Pulse(_) => {}
|
||||
}
|
||||
edges
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ use chrono::{DateTime, TimeZone, Utc};
|
|||
use cultcache_rs::CultCacheEnvelope;
|
||||
use eureka_pipeline::{
|
||||
AuthorityMap, ClaimOutcome, CodeLocation, CommitRange, CutVerification, Date, DocRef, Evidence, EvidenceKind,
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MutationRecord, OrgRepo, PipelineCampaign,
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MemorySort, MutationRecord, OrgRepo, PipelineCampaign,
|
||||
PipelineCutReport, PipelineCutSpec, PipelineDocument, PipelineFinding, PipelineFollowUp, PipelineHandOff,
|
||||
PipelineInstance, PipelineKind, PipelineQuestion, PipelineRef, PipelineResolution, PipelineRuling,
|
||||
PipelineInstance, PipelineKind, PipelineMemory, PipelineQuestion, PipelineRef, PipelineResolution, PipelineRuling,
|
||||
PipelineRun, PipelineStewardship, PipelineTarget, PipelineVerdict, Promise, QuestionOption, ReportCommit,
|
||||
ResolutionOutcome, RulingAuthority, RunOperator, RunTurn, Sha, Short, Slug, StructuralDelta, TargetInvariant, VerdictClaim,
|
||||
};
|
||||
|
|
@ -354,6 +354,22 @@ fn run_of(label: &str, operated_by: RunOperator, turn: RunTurn, claims: &[Pipeli
|
|||
})
|
||||
}
|
||||
|
||||
/// One of her memories: the sort and the text, about `about`, learned in a room, not direct.
|
||||
pub(crate) fn memory(label: &str, sort: MemorySort, text: &str, about: &[&str]) -> PipelineDocument {
|
||||
PipelineDocument::Memory(PipelineMemory {
|
||||
instance: slug(INSTANCE),
|
||||
label: l(label),
|
||||
sort,
|
||||
text: text.into(),
|
||||
about: about.iter().map(|who| s(who)).collect(),
|
||||
learned_in: s("swarm"),
|
||||
direct: false,
|
||||
sources: vec![],
|
||||
salience_permille: 500,
|
||||
consolidates: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn provenance(faculty: Faculty) -> PipelineProvenance {
|
||||
PipelineProvenance { faculty, agent: s("claude"), session: s("session-1"), tool: s("admit") }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ pub fn index_text(document: &PipelineDocument) -> Option<String> {
|
|||
}
|
||||
D::Resolution(resolution) => lines.push(&resolution.rationale.0),
|
||||
D::HandOff(hand_off) => lines.push(&hand_off.reason.0),
|
||||
D::Memory(memory) => lines.push(&memory.text.0),
|
||||
D::Instance(_) | D::Stewardship(_) | D::Run(_) | D::Pulse(_) => {}
|
||||
}
|
||||
lines.retain(|line| !line.is_empty());
|
||||
|
|
@ -155,7 +156,7 @@ mod tests {
|
|||
use crate::receipt::Faculty;
|
||||
use crate::store::test_stores::MemoryStore;
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, Deviation, FindingConfidence, PipelineDocument as D, PipelineKind as K, RulingAuthority,
|
||||
ClaimOutcome, Deviation, FindingConfidence, MemorySort, PipelineDocument as D, PipelineKind as K, RulingAuthority,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
|
|
@ -201,6 +202,7 @@ mod tests {
|
|||
D::Finding(finding("1", 1, "F1", FindingConfidence::Confirmed)),
|
||||
],
|
||||
));
|
||||
committed(admit(&mut mind, vec![memory("M1", MemorySort::Idea, "A shared idea.", &[])]));
|
||||
let (_, writes) = committed(admit(&mut mind, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[])]));
|
||||
let hand_off_key = writes.iter().find(|write| write.kind == K::HandOff).unwrap().id.0.clone();
|
||||
(mind, hand_off_key)
|
||||
|
|
@ -231,6 +233,7 @@ mod tests {
|
|||
// The withdrawn closure of Q1, admitted by hand.
|
||||
(id("resolution", "resolution.question.Q1.n1.n1"), "Resolved."),
|
||||
(hand_off_key, "The workstation mind takes the campaign."),
|
||||
(format!("{INSTANCE}:memory:M1"), "A shared idea."),
|
||||
];
|
||||
let held = texts(&mind);
|
||||
for (key, text) in &expected {
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ fn require_grammatical_slug(field: &str, declared: &Slug) -> Result<(), MindRefu
|
|||
})
|
||||
}
|
||||
|
||||
/// A cache that knows the seventeen types a mind's store may hold: the leaf's
|
||||
/// fifteen through its registrar, the epoch record, the commit receipt and
|
||||
/// 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
|
||||
/// 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();
|
||||
|
|
@ -171,7 +171,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 seventeen; and
|
||||
/// record at the current epoch; every type is one of the nineteen; 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
|
||||
|
|
|
|||
|
|
@ -595,7 +595,7 @@ mod tests {
|
|||
Mind::open_with(planted(with_one), &yggdrasil).err(),
|
||||
Some(MindRefusal::ForeignStore { r#type: HuginnPersonaEntry::TYPE.into() })
|
||||
);
|
||||
// The opener still refuses a type that is not one of the seventeen.
|
||||
// The opener still refuses a type that is not one of the nineteen.
|
||||
let mut stranger = base();
|
||||
let mut foreign = entry(INSTANCE, persona(INSTANCE, T1));
|
||||
foreign.r#type = "gamecult.persona_state.v1".into();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use cultnet_rs::{
|
|||
Cursor, EdgeAnchor, EdgeMatch, Evaluation, LIMIT_MAX, LIMIT_MIN, PROJECTION_DOCUMENT, Row, Selection, select, validate,
|
||||
};
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, CommitRange, Date, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, OrgRepo,
|
||||
ClaimOutcome, CommitRange, Date, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MemorySort, OrgRepo,
|
||||
PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, ResolutionOutcome, RulingAuthority, RunOperator, RunTurn, Sha,
|
||||
Short, Slug, Title, pipeline_key,
|
||||
};
|
||||
|
|
@ -142,6 +142,7 @@ pub enum PipelineFacts {
|
|||
HandOff { from_instance: Slug, to_instance: Slug, repo: OrgRepo, handed_on: Date },
|
||||
Run { label: Label, operated_by: RunOperator, turn: RunTurn, started_on: Date, claims: Vec<PipelineRef> },
|
||||
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>, direct: bool, learned_in: Short, salience_permille: u16 },
|
||||
}
|
||||
|
||||
impl PipelineFacts {
|
||||
|
|
@ -246,6 +247,14 @@ impl PipelineFacts {
|
|||
tempo_vote_withdrawn: value.tempo_vote_withdrawn,
|
||||
urgency_permille: value.urgency_permille,
|
||||
},
|
||||
D::Memory(value) => Self::Memory {
|
||||
label: value.label.clone(),
|
||||
sort: value.sort,
|
||||
about: value.about.clone(),
|
||||
direct: value.direct,
|
||||
learned_in: value.learned_in.clone(),
|
||||
salience_permille: value.salience_permille,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -749,7 +758,7 @@ mod tests {
|
|||
use crate::store::test_stores::MemoryStore;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, FindingConfidence, PipelineDocument as D, PipelineKind as K, PipelineRefusal, ResolutionOutcome,
|
||||
ClaimOutcome, FindingConfidence, MemorySort, PipelineDocument as D, PipelineKind as K, PipelineRefusal, ResolutionOutcome,
|
||||
};
|
||||
|
||||
fn view(mind: &Mind<MemoryStore>, kind: K, key: &str) -> PipelineDocumentView {
|
||||
|
|
@ -1486,6 +1495,41 @@ mod tests {
|
|||
assert!(invalid("fields[0].values", "false")(&refused(&mind, &outside)));
|
||||
}
|
||||
|
||||
/// A memory is selected by its sort, by each user it is about (a memory about two users answers
|
||||
/// for both) and by whether it was learned in a direct message; a value outside the domain is
|
||||
/// refused, and its header carries the triage facts but no text.
|
||||
#[test]
|
||||
fn memories_are_selected_by_sort_about_and_direct() {
|
||||
let mut mind = seeded();
|
||||
let D::Memory(mut direct) = memory("dm", MemorySort::Asked, "Prioritise the fork.", &["@op:chat"]) else { panic!() };
|
||||
direct.direct = true;
|
||||
committed(admit(&mut mind, vec![
|
||||
D::Memory(direct),
|
||||
memory("plan", MemorySort::Committed, "I take the shaders.", &["@aqua:chat", "@libby:chat"]),
|
||||
memory("bond", MemorySort::Rapport, "Libby asks sharp questions.", &["@libby:chat"]),
|
||||
]));
|
||||
let key = |label: &str| format!("{INSTANCE}:memory:{label}");
|
||||
let asking = |alias: &str, values: &[&str]| {
|
||||
let mut found = ids(&mind.query(&with(of_kinds(&[K::Memory]), any_of(alias, values))).unwrap());
|
||||
found.sort();
|
||||
found
|
||||
};
|
||||
assert_eq!(asking("sort", &["Asked"]), vec![key("dm")]);
|
||||
assert_eq!(asking("sort", &["Committed", "Rapport"]), vec![key("bond"), key("plan")]);
|
||||
assert_eq!(asking("about", &["@libby:chat"]), vec![key("bond"), key("plan")]);
|
||||
assert_eq!(asking("about", &["@aqua:chat", "@op:chat"]), vec![key("dm"), key("plan")]);
|
||||
assert_eq!(asking("direct", &["true"]), vec![key("dm")]);
|
||||
assert_eq!(asking("direct", &["false"]), vec![key("bond"), key("plan")]);
|
||||
for sort in MemorySort::NAMES {
|
||||
assert!(mind.query(&with(of_kinds(&[K::Memory]), any_of("sort", &[sort]))).is_ok(), "{sort}");
|
||||
}
|
||||
assert!(invalid("fields[0].values", "Opinion")(&refused(&mind, &with(of_kinds(&[K::Memory]), any_of("sort", &["Opinion"])))));
|
||||
assert!(invalid("fields[0].values", "")(&refused(&mind, &with(of_kinds(&[K::Memory]), any_of("about", &[""])))));
|
||||
let page = mind.query(&with(of_kinds(&[K::Memory]), any_of("sort", &["Rapport"]))).unwrap();
|
||||
let PipelinePageItems::Headers(found) = page.items else { panic!("headers") };
|
||||
assert!(matches!(&found[0].facts, PipelineFacts::Memory { sort: MemorySort::Rapport, direct: false, salience_permille: 500, .. }), "{:?}", found[0].facts);
|
||||
}
|
||||
|
||||
/// A withdrawal pulse admitted into the mind is read back as a withdrawal: its
|
||||
/// facts say so, `voted` selects it, and a later plain vote is not one.
|
||||
#[test]
|
||||
|
|
@ -1592,6 +1636,11 @@ mod tests {
|
|||
let handoff = landed.iter().find(|write| write.kind == K::HandOff).expect("the hand-off landed").id.0.clone();
|
||||
let (_, landed) = committed(admit(&mut mind, vec![run("w1", &[r(K::CutSpec, &id("cut_spec", "cut-1.r2")), r(K::FollowUp, &fu)])]));
|
||||
let run_id = landed[0].id.0.clone();
|
||||
let (older, newer) = (format!("{INSTANCE}:memory:m1"), format!("{INSTANCE}:memory:m2"));
|
||||
committed(admit(&mut mind, vec![memory("m1", MemorySort::Idea, "one", &[])]));
|
||||
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 (n1, f1_closed, f2_closed) = (
|
||||
id("resolution", "question.Q1.n1"),
|
||||
|
|
@ -1625,8 +1674,9 @@ mod tests {
|
|||
(CitationRole::Documents, &handoff, &q1),
|
||||
(CitationRole::Claims, &run_id, &spec_r2),
|
||||
(CitationRole::Claims, &run_id, &fu),
|
||||
(CitationRole::Consolidates, &newer, &older),
|
||||
];
|
||||
assert_eq!(CitationRole::ALL.len(), 16);
|
||||
assert_eq!(CitationRole::ALL.len(), 17);
|
||||
for role in CitationRole::ALL {
|
||||
let selection =
|
||||
Selection { cited: Some(Incoming { role: role.name().into(), exists: true }), ..Selection::default() };
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use std::collections::BTreeMap;
|
|||
|
||||
use cultnet_rs::{Row, RowSet, Selection};
|
||||
use eureka_pipeline::{
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, OrgRepo, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome,
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, MemorySort, OrgRepo, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome,
|
||||
RulingAuthority, RunOperator, RunTurn, Slug,
|
||||
};
|
||||
|
||||
|
|
@ -52,6 +52,12 @@ pub(crate) enum Alias {
|
|||
Voted,
|
||||
/// `true` on a pulse that declares an urgency.
|
||||
Urged,
|
||||
/// A memory's `sort`, from its fields.
|
||||
Sort,
|
||||
/// The Matrix users a memory is about, one value each.
|
||||
About,
|
||||
/// `true` or `false` on a memory: whether it was learned in a direct message.
|
||||
Direct,
|
||||
}
|
||||
|
||||
const FACULTIES: [&str; 7] = ["SelfFaculty", "Imagination", "Hands", "Soul", "Life", "Eyes", "Operator"];
|
||||
|
|
@ -61,7 +67,7 @@ const BOOLEANS: [&str; 2] = ["true", "false"];
|
|||
const PRESENT: [&str; 1] = ["true"];
|
||||
|
||||
impl Alias {
|
||||
const ALL: [Alias; 16] = [
|
||||
const ALL: [Alias; 19] = [
|
||||
Self::Root,
|
||||
Self::InForce,
|
||||
Self::Faculty,
|
||||
|
|
@ -78,6 +84,9 @@ impl Alias {
|
|||
Self::Ran,
|
||||
Self::Voted,
|
||||
Self::Urged,
|
||||
Self::Sort,
|
||||
Self::About,
|
||||
Self::Direct,
|
||||
];
|
||||
|
||||
fn name(self) -> &'static str {
|
||||
|
|
@ -98,6 +107,9 @@ impl Alias {
|
|||
Self::Ran => "ran",
|
||||
Self::Voted => "voted",
|
||||
Self::Urged => "urged",
|
||||
Self::Sort => "sort",
|
||||
Self::About => "about",
|
||||
Self::Direct => "direct",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +131,7 @@ impl Alias {
|
|||
Self::Outcome => kind == K::Resolution,
|
||||
Self::Turn | Self::OperatedBy | Self::Ran => kind == K::Run,
|
||||
Self::Voted | Self::Urged => kind == K::Pulse,
|
||||
Self::Sort | Self::About | Self::Direct => kind == K::Memory,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +149,9 @@ impl Alias {
|
|||
Self::Turn => Some(RunTurn::NAMES),
|
||||
Self::OperatedBy => Some(RunOperator::NAMES),
|
||||
Self::Ran | Self::Voted | Self::Urged => Some(&PRESENT),
|
||||
Self::Root | Self::Repo | Self::Cut => None,
|
||||
Self::Sort => Some(MemorySort::NAMES),
|
||||
Self::Direct => Some(&BOOLEANS),
|
||||
Self::Root | Self::Repo | Self::Cut | Self::About => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,6 +162,7 @@ impl Alias {
|
|||
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::About => !value.is_empty() && value.len() <= 200,
|
||||
closed => closed.closed().is_some_and(|set| set.contains(&value)),
|
||||
}
|
||||
}
|
||||
|
|
@ -211,7 +227,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::Target(_) | D::Question(_) | D::Ruling(_) | D::Verdict(_) | D::Finding(_) | D::Instance(_) | D::Resolution(_) | D::Run(_) | D::Pulse(_) | D::Memory(_) => {
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
|
|
@ -247,6 +263,11 @@ impl SelectionRow {
|
|||
put(Alias::Voted, if pulse.tempo_vote_beat_s.is_some() || pulse.tempo_vote_withdrawn { vec!["true".into()] } else { Vec::new() });
|
||||
put(Alias::Urged, if pulse.urgency_permille.is_some() { vec!["true".into()] } else { Vec::new() });
|
||||
}
|
||||
D::Memory(memory) => {
|
||||
put(Alias::Sort, vec![format!("{:?}", memory.sort)]);
|
||||
put(Alias::About, memory.about.iter().map(|who| who.0.clone()).collect());
|
||||
put(Alias::Direct, vec![memory.direct.to_string()]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Self { kind: held.kind, key: held.key.clone(), ordinal: view.admission.ordinal, values, refs, view }
|
||||
|
|
@ -279,7 +300,7 @@ impl Row for SelectionRow {
|
|||
}
|
||||
}
|
||||
|
||||
/// The organ's closed lists: fifteen schemas, sixteen aliases, sixteen roles.
|
||||
/// The organ's closed lists: sixteen schemas, nineteen aliases, seventeen roles.
|
||||
/// A schema's name is its id, so the substrate's alias matching resolves
|
||||
/// nothing beyond exact ids.
|
||||
pub(crate) struct Vocabulary;
|
||||
|
|
@ -325,7 +346,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 fifteen ids; a `keys` entry that is not a full id whose kind
|
||||
/// one of the sixteen 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
|
||||
|
|
@ -392,7 +413,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]); 15] = [
|
||||
let table: [(K, &[&str]); 16] = [
|
||||
(K::Campaign, &["repo"]),
|
||||
(K::Target, &[]),
|
||||
(K::Question, &[]),
|
||||
|
|
@ -408,6 +429,7 @@ mod tests {
|
|||
(K::HandOff, &["repo"]),
|
||||
(K::Run, &["turn", "operated_by", "ran"]),
|
||||
(K::Pulse, &["voted", "urged"]),
|
||||
(K::Memory, &["sort", "about", "direct"]),
|
||||
];
|
||||
for (kind, extra) in table {
|
||||
let want = [&["root", "in_force", "faculty"][..], extra].concat();
|
||||
|
|
@ -420,10 +442,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
|
||||
/// fifteen.
|
||||
/// sixteen.
|
||||
#[test]
|
||||
fn each_kind_declares_the_roles_it_carries_and_their_targets() {
|
||||
let table: [(K, &[&str]); 15] = [
|
||||
let table: [(K, &[&str]); 16] = [
|
||||
(K::Campaign, &[]),
|
||||
(K::Target, &[]),
|
||||
(K::Question, &["raised_in"]),
|
||||
|
|
@ -439,13 +461,14 @@ mod tests {
|
|||
(K::HandOff, &["documents"]),
|
||||
(K::Run, &["claims"]),
|
||||
(K::Pulse, &[]),
|
||||
(K::Memory, &["consolidates"]),
|
||||
];
|
||||
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(), 15);
|
||||
assert_eq!(Vocabulary.target_leaves("subject").len(), 16);
|
||||
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 fourteen
|
||||
`index.json` is the publication manifest for every schema here: the sixteen
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -172,7 +172,8 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
137
schemas/cultnet/epiphany.pipeline.memory.v2.schema.json
Normal file
137
schemas/cultnet/epiphany.pipeline.memory.v2.schema.json
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"$defs": {
|
||||
"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"
|
||||
},
|
||||
"MemorySort": {
|
||||
"enum": [
|
||||
"Asked",
|
||||
"Decided",
|
||||
"Committed",
|
||||
"Learned",
|
||||
"Did",
|
||||
"Idea",
|
||||
"Rapport"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse",
|
||||
"Memory"
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.memory.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "One thing a Mind keeps for herself, in her own mind: what she was asked,\ndecided, committed to, learned or did, an idea, or the rapport between\nher and another Mind or person. `text` is her own words. `about` names\nthe Matrix users it concerns (Minds included), `learned_in` where she\nlearned it (a room alias, a DM's room id, or `self run <label>`),\n`direct` whether it came in a direct message, and `sources` the event\nids, commits or documents it rests on; the organ fills the last three,\nnot the model. `salience_permille` ranks it and decides nothing.\n`consolidates` names the memories a consolidating memory replaces.\nInstance-rooted: the key is `<instance>:memory:<label>`.",
|
||||
"properties": {
|
||||
"about": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"consolidates": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"learned_in": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"salience_permille": {
|
||||
"format": "uint16",
|
||||
"maximum": 65535,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"sort": {
|
||||
"$ref": "#/$defs/MemorySort"
|
||||
},
|
||||
"sources": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"text": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"label",
|
||||
"sort",
|
||||
"text",
|
||||
"about",
|
||||
"learned_in",
|
||||
"direct",
|
||||
"sources",
|
||||
"salience_permille",
|
||||
"consolidates"
|
||||
],
|
||||
"title": "PipelineMemory",
|
||||
"type": "object"
|
||||
}
|
||||
|
|
@ -36,7 +36,8 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -520,6 +520,18 @@
|
|||
"type": "string",
|
||||
"maxLength": 1000
|
||||
},
|
||||
"MemorySort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Asked",
|
||||
"Decided",
|
||||
"Committed",
|
||||
"Learned",
|
||||
"Did",
|
||||
"Idea",
|
||||
"Rapport"
|
||||
]
|
||||
},
|
||||
"MutationRecord": {
|
||||
"description": "One mutation a report ran: a key-safe label a verdict claim can name,\nthe exact edit, and the tree it was applied to.",
|
||||
"type": "object",
|
||||
|
|
@ -863,7 +875,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\nfifteen 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\nsixteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
@ -1104,6 +1116,22 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "memory"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineMemory"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -1302,7 +1330,71 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
]
|
||||
},
|
||||
"PipelineMemory": {
|
||||
"description": "One thing a Mind keeps for herself, in her own mind: what she was asked,\ndecided, committed to, learned or did, an idea, or the rapport between\nher and another Mind or person. `text` is her own words. `about` names\nthe Matrix users it concerns (Minds included), `learned_in` where she\nlearned it (a room alias, a DM's room id, or `self run <label>`),\n`direct` whether it came in a direct message, and `sources` the event\nids, commits or documents it rests on; the organ fills the last three,\nnot the model. `salience_permille` ranks it and decides nothing.\n`consolidates` names the memories a consolidating memory replaces.\nInstance-rooted: the key is `<instance>:memory:<label>`.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"about": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"consolidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 16
|
||||
},
|
||||
"direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"learned_in": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"salience_permille": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"maximum": 65535,
|
||||
"minimum": 0
|
||||
},
|
||||
"sort": {
|
||||
"$ref": "#/$defs/MemorySort"
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"text": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"label",
|
||||
"sort",
|
||||
"text",
|
||||
"about",
|
||||
"learned_in",
|
||||
"direct",
|
||||
"sources",
|
||||
"salience_permille",
|
||||
"consolidates"
|
||||
]
|
||||
},
|
||||
"PipelineProvenance": {
|
||||
|
|
|
|||
|
|
@ -228,7 +228,8 @@
|
|||
"resolved_by",
|
||||
"deferred_to",
|
||||
"documents",
|
||||
"claims"
|
||||
"claims",
|
||||
"consolidates"
|
||||
]
|
||||
},
|
||||
"ClaimOutcome": {
|
||||
|
|
@ -671,6 +672,18 @@
|
|||
"type": "string",
|
||||
"maxLength": 1000
|
||||
},
|
||||
"MemorySort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Asked",
|
||||
"Decided",
|
||||
"Committed",
|
||||
"Learned",
|
||||
"Did",
|
||||
"Idea",
|
||||
"Rapport"
|
||||
]
|
||||
},
|
||||
"MindRefusal": {
|
||||
"description": "Every way a mind refuses. `Document` wraps the leaf's four (bounds,\nformats, key identity, foreign type); the rest are the organ's: store\nidentity, epoch, ownership, batch shape, references, and every cross-field\nand cross-document rule of admission.\n\nThe two sequenced kinds refuse in the same pair of shapes: an\n`OutOfSequence` when the writer's `sequence` is not the previous plus one,\nand `AlreadyResolved`/`AlreadyStewarded` when a record of that scope is\nstill in force.",
|
||||
"oneOf": [
|
||||
|
|
@ -2161,7 +2174,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\nfifteen 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\nsixteen per-kind value documents, and the envelope is the wire's\nshape, not a document's.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
@ -2402,6 +2415,22 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "memory"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/$defs/PipelineMemory"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -3119,6 +3148,56 @@
|
|||
"kind",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"const": "memory"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"about": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"learned_in": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"salience_permille": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"maximum": 65535,
|
||||
"minimum": 0
|
||||
},
|
||||
"sort": {
|
||||
"$ref": "#/$defs/MemorySort"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"sort",
|
||||
"about",
|
||||
"direct",
|
||||
"learned_in",
|
||||
"salience_permille"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -3317,7 +3396,71 @@
|
|||
"Stewardship",
|
||||
"HandOff",
|
||||
"Run",
|
||||
"Pulse"
|
||||
"Pulse",
|
||||
"Memory"
|
||||
]
|
||||
},
|
||||
"PipelineMemory": {
|
||||
"description": "One thing a Mind keeps for herself, in her own mind: what she was asked,\ndecided, committed to, learned or did, an idea, or the rapport between\nher and another Mind or person. `text` is her own words. `about` names\nthe Matrix users it concerns (Minds included), `learned_in` where she\nlearned it (a room alias, a DM's room id, or `self run <label>`),\n`direct` whether it came in a direct message, and `sources` the event\nids, commits or documents it rests on; the organ fills the last three,\nnot the model. `salience_permille` ranks it and decides nothing.\n`consolidates` names the memories a consolidating memory replaces.\nInstance-rooted: the key is `<instance>:memory:<label>`.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"about": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"consolidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 16
|
||||
},
|
||||
"direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"learned_in": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"salience_permille": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"maximum": 65535,
|
||||
"minimum": 0
|
||||
},
|
||||
"sort": {
|
||||
"$ref": "#/$defs/MemorySort"
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8
|
||||
},
|
||||
"text": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"label",
|
||||
"sort",
|
||||
"text",
|
||||
"about",
|
||||
"learned_in",
|
||||
"direct",
|
||||
"sources",
|
||||
"salience_permille",
|
||||
"consolidates"
|
||||
]
|
||||
},
|
||||
"PipelinePageItems": {
|
||||
|
|
|
|||
|
|
@ -177,6 +177,17 @@
|
|||
"title": "Epiphany Pipeline Pulse v2",
|
||||
"path": "epiphany.pipeline.pulse.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.memory.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.memory.v2",
|
||||
"documentType": "epiphany.pipeline.memory.v2",
|
||||
"title": "Epiphany Pipeline Memory v2",
|
||||
"path": "epiphany.pipeline.memory.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "huginn.mind_request.v1",
|
||||
"kind": "wire_message",
|
||||
|
|
|
|||
Loading…
Reference in a new issue