diff --git a/AGENTS.md b/AGENTS.md index bfa7ee0..e217d3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,15 +11,17 @@ Studio's job, not Huginn's. - Project root: `F:\Projects\Huginn` - Upstream: `https://github.com/GameCult/Huginn.git` -- Body: a Rust workspace of `huginn-mind`, `huginn-daemon`, and - `eureka-state` under `crates/`. Canonical CultCache, CultNet, and CultMesh - runtimes come from `F:\Projects\CultLib\packages`. `huginn-mind` is live: - it persists, admits and reads back an instance's mind, queries and derived - status included, over `cultcache-rs` and `epiphany-pipeline` pinned by git - rev. `huginn-daemon` serves one mind over CultNet RUDP through `cultnet-rs` - at the same rev, and reaches the leaf and the store type through - `huginn-mind` so one crate pins each revision. `eureka-state` is the client - core: one call to a daemon over CultNet, holding no state. +- Body: a Rust workspace of `eureka-pipeline`, `huginn-mind`, `huginn-daemon`, + and `eureka-state` under `crates/`. Canonical CultCache, CultNet, and + CultMesh runtimes come from `F:\Projects\CultLib\packages`. + `eureka-pipeline` is the pipeline leaf: document shapes, bounds, formats, + keys and the schemas published under `schemas/cultnet`. `huginn-mind` is + live: it persists, admits and reads back an instance's mind, queries and + derived status included, over `cultcache-rs` and `eureka-pipeline`. + `huginn-daemon` serves one mind over CultNet RUDP through `cultnet-rs` at + the same rev, and reaches the leaf and the store type through `huginn-mind` + so one crate pins each revision. `eureka-state` is the client core: one call + to a daemon over CultNet, holding no state. - Owned: an instance's memory documents, their admission and the read side that derives their status, in `huginn-mind`, over a redb CultCache store at `/minds//mind.redb`; diff --git a/Cargo.lock b/Cargo.lock index 25c9d81..4fcc88d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,19 +372,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "epiphany-pipeline" -version = "0.1.0" -source = "git+https://github.com/GameCult/Epiphany.git?rev=ef9568655e75bb6cb362a038e2739a5ac5e777ee#ef9568655e75bb6cb362a038e2739a5ac5e777ee" -dependencies = [ - "anyhow", - "chrono", - "cultcache-rs", - "rmp-serde", - "schemars", - "serde", -] - [[package]] name = "errno" version = "0.3.14" @@ -395,6 +382,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "eureka-pipeline" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "cultcache-rs", + "rmp-serde", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "eureka-state" version = "0.1.0" @@ -644,7 +644,7 @@ dependencies = [ "chrono", "cultcache-rs", "cultnet-rs", - "epiphany-pipeline", + "eureka-pipeline", "rmp-serde", "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9522717..1f717e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/eureka-pipeline", "crates/huginn-mind", "crates/huginn-daemon", "crates/eureka-state", diff --git a/README.md b/README.md index b6a4bd6..9110adc 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ It reads them back the same way: a document with the facts of its admission joined from that receipt and its status derived at read time, and typed queries over one mind. Status is never stored, and the views derive it through the same rules admission does. Document shape and keys come from -`epiphany-pipeline`; the store is CultLib's Rust CultCache. +`eureka-pipeline` (`crates/eureka-pipeline`); the store is CultLib's Rust CultCache. `huginn-daemon` opens one mind, binds one UDP socket in that order, and answers every frame on the session it arrived on: one operation per `Mind` diff --git a/crates/eureka-pipeline/Cargo.toml b/crates/eureka-pipeline/Cargo.toml index 48d2fdd..1b077a7 100644 --- a/crates/eureka-pipeline/Cargo.toml +++ b/crates/eureka-pipeline/Cargo.toml @@ -1,13 +1,12 @@ [package] -name = "epiphany-pipeline" +name = "eureka-pipeline" version = "0.1.0" edition = "2024" publish = false autobins = false -license-file = "../LICENSE" [lib] -name = "epiphany_pipeline" +name = "eureka_pipeline" path = "src/lib.rs" # A leaf type library. Every dependency here is one the document types or the diff --git a/crates/eureka-pipeline/src/lib.rs b/crates/eureka-pipeline/src/lib.rs index c29146e..7bd75f3 100644 --- a/crates/eureka-pipeline/src/lib.rs +++ b/crates/eureka-pipeline/src/lib.rs @@ -1094,7 +1094,7 @@ mod tests { } fn location() -> CodeLocation { - CodeLocation { path: s("epiphany-pipeline/src/lib.rs"), line: 1, end_line: Some(9) } + CodeLocation { path: s("crates/eureka-pipeline/src/lib.rs"), line: 1, end_line: Some(9) } } fn evidence() -> Evidence { @@ -1187,7 +1187,7 @@ mod tests { dependencies_removed: vec![], formats_added: vec![s("epiphany.pipeline.*.v1")], formats_removed: vec![], targets_added: vec![], targets_removed: vec![], }, - landed_names: vec![LandedName { name: s("PipelineDocument"), path: s("epiphany-pipeline/src/lib.rs") }], + landed_names: vec![LandedName { name: s("PipelineDocument"), path: s("crates/eureka-pipeline/src/lib.rs") }], undone: vec!["admission".into()], promises: vec![Promise { label: l("P1"), text: "One derived key per document.".into() }], }), format!("{CAMPAIGN}:cut_report:cut-3a.h1")), @@ -2722,7 +2722,7 @@ mod tests { #[test] fn pipeline_published_schemas_match_derivation() -> Result<()> { - let published = Path::new(env!("CARGO_MANIFEST_DIR")).join("../schemas/cultnet"); + let published = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../schemas/cultnet"); let index: serde_json::Value = serde_json::from_slice(&std::fs::read(published.join("index.json"))?)?; // A directory this run alone writes, so everything in it is what this // run derived. A previous run's leftovers -- a mutation run's @@ -2739,7 +2739,7 @@ mod tests { // they outlive the run that wrote them. Whoever reads the failure is // the one who deletes them. let derived_dir = std::env::temp_dir().join(format!( - "epiphany-pipeline-schemas-{}-{}", + "eureka-pipeline-schemas-{}-{}", std::process::id(), std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_nanos() )); @@ -2772,6 +2772,36 @@ mod tests { Ok(()) } + /// The catalogue is exactly the files beside it: every entry's `path` is a + /// schema file in the directory, and every schema file in the directory + /// has an entry, so a schema removed from the catalogue (or a file left + /// behind by a move) fails here instead of publishing a dangling or + /// unlisted contract. Schemas whose owner is another crate (`huginn.*`) + /// are published by that crate, not listed here. + #[test] + fn the_catalogue_lists_exactly_the_schema_files_it_publishes() -> Result<()> { + let published = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../schemas/cultnet"); + let index: serde_json::Value = serde_json::from_slice(&std::fs::read(published.join("index.json"))?)?; + let listed = index["schemas"] + .as_array() + .expect("index.json carries a schemas array") + .iter() + .map(|entry| entry["path"].as_str().expect("every entry names its file").to_owned()) + .collect::>(); + for path in &listed { + assert!(published.join(path).is_file(), "index.json lists {path}, which is not in the directory"); + } + let mut files = std::collections::BTreeSet::new(); + for entry in std::fs::read_dir(&published)? { + let name = entry?.file_name().to_string_lossy().into_owned(); + if name.ends_with(".schema.json") && !name.starts_with("huginn.") { + files.insert(name); + } + } + assert_eq!(listed, files, "the catalogue and the schema files beside it disagree"); + Ok(()) + } + /// The epoch is the version every kind is published at: its version /// segment is read from the constant, not restated, and every type id /// carries the same one, so a bumped epoch fails here until the kinds diff --git a/crates/eureka-state/src/lib.rs b/crates/eureka-state/src/lib.rs index b6208e8..5d0d502 100644 --- a/crates/eureka-state/src/lib.rs +++ b/crates/eureka-state/src/lib.rs @@ -26,7 +26,7 @@ use cultnet_rs::{ CultNetRudpSocketTransportConnection, CultNetWireContract, encode_cultnet_message_to_vec, fetch_content, }; use huginn_mind::envelope::{OperationFailure, decode_response, encode_request}; -use huginn_mind::epiphany_pipeline::Slug; +use huginn_mind::eureka_pipeline::Slug; use huginn_mind::{HuginnMindRequest, HuginnMindResponse, MAX_DEFERRED_BODY_BYTES}; /// The correlation key of the one request a session carries. diff --git a/crates/eureka-state/src/main.rs b/crates/eureka-state/src/main.rs index d368a23..f48b8a0 100644 --- a/crates/eureka-state/src/main.rs +++ b/crates/eureka-state/src/main.rs @@ -22,7 +22,7 @@ use std::time::Duration; use cultnet_rs::Selection; use eureka_state::{ClientError, HuginnClient}; -use huginn_mind::epiphany_pipeline::{PipelineDocument, PipelineRef, Short, Slug}; +use huginn_mind::eureka_pipeline::{PipelineDocument, PipelineRef, Short, Slug}; use huginn_mind::{ Faculty, HuginnMindRequest, HuginnMindResponse, PipelineAdmissionBatch, PipelineProvenance, SemanticQuery, }; diff --git a/crates/eureka-state/tests/client.rs b/crates/eureka-state/tests/client.rs index 3f34512..ce9decb 100644 --- a/crates/eureka-state/tests/client.rs +++ b/crates/eureka-state/tests/client.rs @@ -14,7 +14,7 @@ use cultnet_rs::{ use eureka_state::{ClientError, HuginnClient, MAX_REQUEST_BYTES}; use huginn_daemon::serve::MAX_RESPONSE_BYTES; use huginn_mind::envelope::{OperationFailure, encode_failure, encode_request, encode_response}; -use huginn_mind::epiphany_pipeline::{ +use huginn_mind::eureka_pipeline::{ AuthorityMap, CodeLocation, CutDelete, CutVerification, FileChange, Line, NegativeCheck, OrgRepo, PipelineCutSpec, PipelineDocument, PipelineKind, PipelineRef, Short, StructuralDelta, Title, VerificationTest, diff --git a/crates/eureka-state/tests/common/mod.rs b/crates/eureka-state/tests/common/mod.rs index 27a0705..ca0e19b 100644 --- a/crates/eureka-state/tests/common/mod.rs +++ b/crates/eureka-state/tests/common/mod.rs @@ -13,7 +13,7 @@ use chrono::Utc; use cultnet_rs::{CultNetMessage, CultNetRudpServerEvent, CultNetWireContract, decode_cultnet_message_from_slice}; use huginn_daemon::serve::{bind, run, schema_registry}; use huginn_daemon::{Daemon, Handled, Hits, IndexSink, SearchTicket, ServeOptions}; -use huginn_mind::epiphany_pipeline::{ +use huginn_mind::eureka_pipeline::{ Date, DocRef, OrgRepo, PipelineCampaign, PipelineDocument, PipelineInstance, PipelineRef, PipelineStewardship, Sha, Short, Slug, Title, }; diff --git a/crates/eureka-state/tests/mcp.rs b/crates/eureka-state/tests/mcp.rs index 969ad55..adfc2f8 100644 --- a/crates/eureka-state/tests/mcp.rs +++ b/crates/eureka-state/tests/mcp.rs @@ -11,7 +11,7 @@ use std::time::Duration; use huginn_mind::envelope::{OperationFailure, encode_failure, encode_response}; use huginn_mind::{HuginnMindRequest, HuginnMindResponse}; -use huginn_mind::epiphany_pipeline::{Date, PipelineDocument, PipelineKind, PipelineRef, Short}; +use huginn_mind::eureka_pipeline::{Date, PipelineDocument, PipelineKind, PipelineRef, Short}; use serde_json::{Value, json}; mod common; @@ -536,7 +536,7 @@ fn initialize_advertises_tools() { /// value `pipeline_key` computes for that document, never a re-built one. #[test] fn admit_carries_the_leaf_key_refusal_with_its_field_and_value() { - use huginn_mind::epiphany_pipeline::{PipelineResolution, ResolutionOutcome, pipeline_key}; + use huginn_mind::eureka_pipeline::{PipelineResolution, ResolutionOutcome, pipeline_key}; let (root, daemon) = mind(vec![vec![stewardship()]]); let server = serve(root, daemon); diff --git a/crates/huginn-daemon/src/daemon.rs b/crates/huginn-daemon/src/daemon.rs index ffbf813..8e54d34 100644 --- a/crates/huginn-daemon/src/daemon.rs +++ b/crates/huginn-daemon/src/daemon.rs @@ -4,7 +4,7 @@ use chrono::{DateTime, Utc}; use cultnet_rs::Selection; -use huginn_mind::epiphany_pipeline::{PipelineRef, Slug}; +use huginn_mind::eureka_pipeline::{PipelineRef, Slug}; use huginn_mind::wire::{HuginnMindRequest, HuginnMindResponse, IndexStatus}; use huginn_mind::{Mind, MindRefusal, MindStore, PipelineAdmissionOutcome, SemanticQuery}; @@ -217,7 +217,7 @@ impl> Daemon { pub(crate) mod tests { use super::*; use chrono::TimeZone; - use huginn_mind::epiphany_pipeline::{ + use huginn_mind::eureka_pipeline::{ AuthorityMap, CodeLocation, CutDelete, CutVerification, Date, DocRef, FileChange, Line, NegativeCheck, OrgRepo, PipelineCampaign, PipelineCutSpec, PipelineDocument, PipelineInstance, PipelineKind, PipelineQuestion, PipelineRuling, PipelineStewardship, QuestionOption, RulingAuthority, Sha, Short, @@ -685,7 +685,7 @@ pub(crate) mod tests { assert_ne!(fullwidth, INSTANCE, "not the mind's own bytes"); assert!(!fullwidth.is_ascii(), "outside the grammar: a Slug is dot-joined ASCII labels"); - let expected = MindRefusal::Document(huginn_mind::epiphany_pipeline::PipelineRefusal::InvalidFormat { + let expected = MindRefusal::Document(huginn_mind::eureka_pipeline::PipelineRefusal::InvalidFormat { field: "declared".into(), value: fullwidth.into(), }); @@ -715,7 +715,7 @@ pub(crate) mod tests { let (_root, mut daemon) = seeded(); let fullwidth = "\u{FF59}ggdrasil"; - let expected = MindRefusal::Document(huginn_mind::epiphany_pipeline::PipelineRefusal::InvalidFormat { + let expected = MindRefusal::Document(huginn_mind::eureka_pipeline::PipelineRefusal::InvalidFormat { field: "declared".into(), value: fullwidth.into(), }); @@ -744,7 +744,7 @@ pub(crate) mod tests { // A reference whose kind and id disagree names no document any writer // could have composed; the leaf's own refusal says so. let invalid = PipelineRef { kind: PipelineKind::Question, id: "not a reference".into() }; - let malformed = MindRefusal::Document(huginn_mind::epiphany_pipeline::PipelineRefusal::InvalidFormat { + let malformed = MindRefusal::Document(huginn_mind::eureka_pipeline::PipelineRefusal::InvalidFormat { field: "ref.id".into(), value: "not a reference".into(), }); diff --git a/crates/huginn-daemon/src/index/mod.rs b/crates/huginn-daemon/src/index/mod.rs index 30a7b4c..fef9ebb 100644 --- a/crates/huginn-daemon/src/index/mod.rs +++ b/crates/huginn-daemon/src/index/mod.rs @@ -99,7 +99,7 @@ use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use std::time::{Duration, Instant}; use anyhow::{Context, Result, bail, ensure}; -use huginn_mind::epiphany_pipeline::{PipelineKind, PipelineRef, Short, Slug}; +use huginn_mind::eureka_pipeline::{PipelineKind, PipelineRef, Short, Slug}; use huginn_mind::wire::IndexStatus; use huginn_mind::{INDEX_TEXT_VERSION, IndexEntry, Mind, MindStore}; use sha2::{Digest, Sha256}; diff --git a/crates/huginn-daemon/src/semantic_tests.rs b/crates/huginn-daemon/src/semantic_tests.rs index f72c5b4..11da3f5 100644 --- a/crates/huginn-daemon/src/semantic_tests.rs +++ b/crates/huginn-daemon/src/semantic_tests.rs @@ -10,7 +10,7 @@ use std::time::Duration; use anyhow::Result; use cultnet_rs::{CULTNET_OPERATION_CONNECTION_ID, CultMesh, CultMeshRudpSocketOptions, FieldPredicate, Selection}; -use huginn_mind::epiphany_pipeline::{ +use huginn_mind::eureka_pipeline::{ Date, PipelineDocument, PipelineKind, PipelineQuestion, PipelineResolution, PipelineRef, PipelineRuling, QuestionOption, ResolutionOutcome, RulingAuthority, Short, Title, }; diff --git a/crates/huginn-daemon/src/serve.rs b/crates/huginn-daemon/src/serve.rs index bb9fb8e..561ad1b 100644 --- a/crates/huginn-daemon/src/serve.rs +++ b/crates/huginn-daemon/src/serve.rs @@ -68,7 +68,7 @@ use cultnet_rs::{ CultNetWireContract, answer_content_chunk_request, decode_cultnet_message_from_slice, encode_cultnet_message_to_vec, pack_content, }; -use huginn_mind::epiphany_pipeline::Slug; +use huginn_mind::eureka_pipeline::Slug; use huginn_mind::wire::{ DeferredAnswer, HuginnMindResponse, MIND_REQUEST_SCHEMA, MIND_REQUEST_SCHEMA_JSON, MIND_RESPONSE_SCHEMA, MIND_RESPONSE_SCHEMA_JSON, MIND_SERVICE_ID, @@ -548,7 +548,7 @@ mod tests { FITTING_CHANGES, FITTING_CUT, INSTANCE, NoIndex, OTHER, WIDE_CHANGES, WIDE_CUT, batch, identity, now, open_unindexed, seeded_wide, slug, }; - use huginn_mind::epiphany_pipeline::{PipelineKind, PipelineRef}; + use huginn_mind::eureka_pipeline::{PipelineKind, PipelineRef}; use huginn_mind::envelope::{FAILURE_SCHEMA, decode_response, encode_request}; use crate::index::Backoff; use base64::Engine; diff --git a/crates/huginn-mind/Cargo.toml b/crates/huginn-mind/Cargo.toml index c5f3953..999a7b2 100644 --- a/crates/huginn-mind/Cargo.toml +++ b/crates/huginn-mind/Cargo.toml @@ -6,9 +6,10 @@ license.workspace = true publish.workspace = true # The organ's rule engine: storage, identity and admission of one instance's -# mind. Document shape, bounds, formats and keys come from `epiphany-pipeline`; -# the store comes from CultLib's Rust CultCache at the same rev Epiphany pins, -# so the leaf's wrappers and this crate's cache are one `cultcache-rs`. The +# mind. Document shape, bounds, formats and keys come from this workspace's +# `eureka-pipeline` (crates/eureka-pipeline, copied once from Epiphany ef956865); +# the store comes from CultLib's Rust CultCache at the rev `eureka-pipeline` +# also uses, so the leaf's wrappers and this crate's cache are one `cultcache-rs`. The # read side selects through CultNet's typed selection (`cultnet-rs`, the same # rev the daemon pins): the selection's vocabulary and evaluator are the # substrate's, this crate supplies the rows. No socket, no index: those are the @@ -19,7 +20,7 @@ base64 = "0.22" chrono = "0.4.44" cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultcache-rs" } cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultnet-rs" } -epiphany-pipeline = { git = "https://github.com/GameCult/Epiphany.git", rev = "ef9568655e75bb6cb362a038e2739a5ac5e777ee", package = "epiphany-pipeline" } +eureka-pipeline = { path = "../eureka-pipeline" } rmp-serde = "1" schemars = "1" serde = { version = "1", features = ["derive"] } diff --git a/crates/huginn-mind/src/admission.rs b/crates/huginn-mind/src/admission.rs index 62380a7..fddb88d 100644 --- a/crates/huginn-mind/src/admission.rs +++ b/crates/huginn-mind/src/admission.rs @@ -30,7 +30,7 @@ use std::collections::BTreeSet; use chrono::{DateTime, Utc}; use cultcache_rs::{CultCache, CultCacheEnvelope}; -use epiphany_pipeline::{ +use eureka_pipeline::{ ClaimOutcome, FindingConfidence, Line, PipelineDocument, PipelineKind, PipelineRef, PipelineRefusal, PipelineResolution, PipelineStewardship, ResolutionOutcome, RulingAuthority, Short, Slug, pipeline_key, validate_pipeline_write_envelope, @@ -180,7 +180,7 @@ impl Mind { } } -/// The bound `epiphany_pipeline::Short` declares, in UTF-8 bytes. The leaf +/// The bound `eureka_pipeline::Short` declares, in UTF-8 bytes. The leaf /// exposes no validator for it, so `short_bound_is_the_leafs_own` pins this to /// the `maxLength` its schema publishes. const SHORT_MAX: usize = 200; @@ -371,7 +371,7 @@ fn check(docs: &Docs, staged: &Staged, mind: &Slug) -> Result<(), MindRefusal> { Ok(()) } D::Target(target) => { - let predecessor = D::Target(epiphany_pipeline::PipelineTarget { revision: target.revision.wrapping_sub(1), ..target.clone() }); + let predecessor = D::Target(eureka_pipeline::PipelineTarget { revision: target.revision.wrapping_sub(1), ..target.clone() }); revision_rule(docs, K::Target, key, target.revision, &predecessor)?; unique_labels("target.invariants", target.invariants.iter().map(|invariant| invariant.label.0.as_str())) } @@ -429,7 +429,7 @@ fn check(docs: &Docs, staged: &Staged, mind: &Slug) -> Result<(), MindRefusal> { return Err(MindRefusal::UnknownDependency { cut: dependency.0.clone() }); } } - let predecessor = D::CutSpec(epiphany_pipeline::PipelineCutSpec { revision: spec.revision.wrapping_sub(1), ..spec.clone() }); + let predecessor = D::CutSpec(eureka_pipeline::PipelineCutSpec { revision: spec.revision.wrapping_sub(1), ..spec.clone() }); revision_rule(docs, K::CutSpec, key, spec.revision, &predecessor) } D::CutReport(report) => { @@ -704,7 +704,7 @@ mod tests { use crate::receipt::{DocumentVersion, Faculty, HuginnCommitReceipt}; use crate::store::test_stores::{MemoryStore, RefusingStore, SwapCommand}; use cultcache_rs::DatabaseEntry; - use epiphany_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument as D, PipelineKind as K}; + use eureka_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument as D, PipelineKind as K}; use sha2::{Digest, Sha256}; fn receipt_count(mind: &Mind) -> usize { @@ -833,7 +833,7 @@ mod tests { fn provenance_text_is_the_leafs_title_rule() { let leaf_accepts = |text: &str| { let D::Campaign(mut campaign) = campaign(&[REPO]) else { unreachable!() }; - campaign.title = epiphany_pipeline::Title(text.into()); + campaign.title = eureka_pipeline::Title(text.into()); D::Campaign(campaign).validate().is_ok() }; let mine_accepts = |text: &str| { @@ -1280,7 +1280,7 @@ mod tests { let mut steward = seeded(); committed(admit(&mut steward, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[])])); let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() }; - back.handed_on = epiphany_pipeline::Date("2026-09-17".into()); + back.handed_on = eureka_pipeline::Date("2026-09-17".into()); committed(admit(&mut steward, vec![D::HandOff(back)])); assert_eq!( refusal(admit(&mut steward, vec![stewardship_n(INSTANCE, REPO, 4)])), @@ -1366,7 +1366,7 @@ mod tests { // Handed back, the mind stewards it again, as `n2`. No field names a // return: this is the same hand-off shape with the instances swapped. let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() }; - back.handed_on = epiphany_pipeline::Date("2026-09-17".into()); + back.handed_on = eureka_pipeline::Date("2026-09-17".into()); let (_, writes) = committed(admit(&mut mind, vec![D::HandOff(back)])); let again = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n2"); assert_eq!(writes, vec![ @@ -1375,7 +1375,7 @@ mod tests { ]); let Some(D::Stewardship(taken)) = mind.get(K::Stewardship, &again).unwrap() else { panic!() }; assert_eq!(taken.sequence, 2); - assert_eq!(taken.assigned_on, epiphany_pipeline::Date("2026-09-17".into())); + assert_eq!(taken.assigned_on, eureka_pipeline::Date("2026-09-17".into())); assert!(mind.envelope(K::Stewardship, &first).is_some(), "the first assignment is still readable by key"); } @@ -1388,7 +1388,7 @@ mod tests { let mut mind = seeded(); committed(admit(&mut mind, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[])])); let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() }; - back.handed_on = epiphany_pipeline::Date("2026-09-17".into()); + back.handed_on = eureka_pipeline::Date("2026-09-17".into()); committed(admit(&mut mind, vec![D::HandOff(back)])); let first = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"); let again = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n2"); @@ -1434,7 +1434,7 @@ mod tests { let withdrawal = format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n1.n1"); committed(admit(&mut mind, vec![resolution(r(K::Resolution, &withdrawal), withdrawn())])); let D::HandOff(mut again) = hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[]) else { panic!() }; - again.handed_on = epiphany_pipeline::Date("2026-09-17".into()); + again.handed_on = eureka_pipeline::Date("2026-09-17".into()); let (_, writes) = committed(admit(&mut mind, vec![D::HandOff(again)])); assert_eq!(writes, vec![ r(K::HandOff, &format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-epiphany.2026-09-17")), @@ -1444,7 +1444,7 @@ mod tests { fn hand_off_on(from: &str, to: &str, day: &str) -> D { let D::HandOff(mut hand_off) = hand_off(from, to, REPO, &[]) else { panic!() }; - hand_off.handed_on = epiphany_pipeline::Date(day.into()); + hand_off.handed_on = eureka_pipeline::Date(day.into()); D::HandOff(hand_off) } @@ -1905,7 +1905,7 @@ mod tests { report.repo = repo(OTHER_REPO); assert_eq!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::SpecMismatch { field: "repo".into() }); let mut report = cut_report("1", 1); - report.range.head = epiphany_pipeline::Sha("abcdef0".into()); + report.range.head = eureka_pipeline::Sha("abcdef0".into()); assert_eq!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { head: "abcdef0".into() }); committed(admit(&mut mind, vec![resolution(r(K::CutSpec, &id("cut_spec", "cut-1.r1")), withdrawn())])); assert_eq!( @@ -2008,18 +2008,18 @@ mod tests { committed(admit(&mut mind, vec![D::CutSpec(cut_spec("1", 1))])); // The commit is spelled short and the head full, and the reverse. let mut report = cut_report("1", 1); - report.range.head = epiphany_pipeline::Sha(full.into()); + report.range.head = eureka_pipeline::Sha(full.into()); committed(admit(&mut mind, vec![D::CutReport(report)])); let mut report = cut_report("1", 2); - report.commits[0].sha = epiphany_pipeline::Sha(full.into()); + report.commits[0].sha = eureka_pipeline::Sha(full.into()); committed(admit(&mut mind, vec![D::CutReport(report)])); // A head that is no commit's prefix, long or short, is outside. let mut report = cut_report("1", 3); - report.range.head = epiphany_pipeline::Sha("5f98229d0123456789abcdef0123456789abcdef".into()); + report.range.head = eureka_pipeline::Sha("5f98229d0123456789abcdef0123456789abcdef".into()); assert!(matches!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { .. })); let mut report = cut_report("1", 3); - report.commits[0].sha = epiphany_pipeline::Sha(full.into()); - report.range.head = epiphany_pipeline::Sha("5f98229".into()); + report.commits[0].sha = eureka_pipeline::Sha(full.into()); + report.range.head = eureka_pipeline::Sha("5f98229".into()); assert!(matches!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { .. })); } @@ -2115,16 +2115,16 @@ mod tests { struct Rangeless { campaign: Slug, verdict: Short, - label: epiphany_pipeline::Label, + label: eureka_pipeline::Label, confidence: FindingConfidence, - severity: epiphany_pipeline::FindingSeverity, + severity: eureka_pipeline::FindingSeverity, claim: Line, - invariants: Vec, - locations: Vec, - failure_scenario: epiphany_pipeline::Para, - evidence: Vec, - precedents: Vec, - origin: epiphany_pipeline::FindingOrigin, + invariants: Vec, + locations: Vec, + failure_scenario: eureka_pipeline::Para, + evidence: Vec, + precedents: Vec, + origin: eureka_pipeline::FindingOrigin, } let whole = finding("1", 1, "F1", FindingConfidence::Plausible); let rangeless = Rangeless { diff --git a/crates/huginn-mind/src/docs.rs b/crates/huginn-mind/src/docs.rs index ef4d723..e338154 100644 --- a/crates/huginn-mind/src/docs.rs +++ b/crates/huginn-mind/src/docs.rs @@ -6,7 +6,7 @@ //! `stored_at`. use cultcache_rs::CultCacheEnvelope; -use epiphany_pipeline::{ +use eureka_pipeline::{ OrgRepo, PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, PipelineStewardship, ResolutionOutcome, Short, Slug, }; diff --git a/crates/huginn-mind/src/fixtures.rs b/crates/huginn-mind/src/fixtures.rs index 0f0f59f..34e44c3 100644 --- a/crates/huginn-mind/src/fixtures.rs +++ b/crates/huginn-mind/src/fixtures.rs @@ -4,7 +4,7 @@ use chrono::{DateTime, TimeZone, Utc}; use cultcache_rs::CultCacheEnvelope; -use epiphany_pipeline::{ +use eureka_pipeline::{ AuthorityMap, ClaimOutcome, CodeLocation, CommitRange, CutVerification, Date, DocRef, Evidence, EvidenceKind, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MutationRecord, OrgRepo, PipelineCampaign, PipelineCutReport, PipelineCutSpec, PipelineDocument, PipelineFinding, PipelineFollowUp, PipelineHandOff, @@ -32,7 +32,7 @@ pub(crate) const NEAR_INSTANCE: &str = "yggdrasix"; pub(crate) const PREFIXED_INSTANCE: &str = "yggdrasil-two"; /// A foreign instance differing from `INSTANCE` in case alone. A `Slug` is /// dot-joined labels, each `[A-Za-z0-9_-]{1,64}`, bounded to 64 bytes whole -/// (`epiphany_pipeline::dotted_text`), so this is a name a client may declare +/// (`eureka_pipeline::dotted_text`), so this is a name a client may declare /// and a mind may hold: two minds whose names differ only in case are two /// minds, and a comparison that folds case answers one of them for the other. pub(crate) const CASED_INSTANCE: &str = "Yggdrasil"; diff --git a/crates/huginn-mind/src/index.rs b/crates/huginn-mind/src/index.rs index 7ac4869..5719684 100644 --- a/crates/huginn-mind/src/index.rs +++ b/crates/huginn-mind/src/index.rs @@ -7,7 +7,7 @@ //! how a change to it makes every projection rebuild. No network, no JSON, no //! store handle: the daemon owns the vector store and the embedder. -use epiphany_pipeline::{PipelineDocument, PipelineKind, PipelineRef}; +use eureka_pipeline::{PipelineDocument, PipelineKind, PipelineRef}; use sha2::{Digest, Sha256}; use crate::docs::Docs; @@ -154,7 +154,7 @@ mod tests { use crate::fixtures::*; use crate::receipt::Faculty; use crate::store::test_stores::MemoryStore; - use epiphany_pipeline::{ + use eureka_pipeline::{ ClaimOutcome, Deviation, FindingConfidence, PipelineDocument as D, PipelineKind as K, RulingAuthority, }; use std::collections::BTreeMap; diff --git a/crates/huginn-mind/src/lib.rs b/crates/huginn-mind/src/lib.rs index 29da386..5aa59f7 100644 --- a/crates/huginn-mind/src/lib.rs +++ b/crates/huginn-mind/src/lib.rs @@ -18,7 +18,7 @@ //! so the daemon and the client share one set //! of types. //! -//! Document shape, bounds, formats and keys are `epiphany-pipeline`'s; the +//! Document shape, bounds, formats and keys are `eureka-pipeline`'s; the //! organ registers, prepares, decodes and validates through the leaf's four //! doors and never re-derives a key. The store is CultLib's owned redb //! CultCache, whose lifetime-long exclusive lock is the single-writer @@ -59,4 +59,4 @@ pub use wire::{ /// The leaf, whole, through the one crate that pins its rev: the daemon and /// the client name `Slug`, `PipelineRef` and the document types from here, so /// one `[dependencies]` entry decides which revision the workspace speaks. -pub use epiphany_pipeline; +pub use eureka_pipeline; diff --git a/crates/huginn-mind/src/mind.rs b/crates/huginn-mind/src/mind.rs index 619471e..a557ff4 100644 --- a/crates/huginn-mind/src/mind.rs +++ b/crates/huginn-mind/src/mind.rs @@ -13,7 +13,7 @@ use std::path::{Path, PathBuf}; use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry, OwnedRedbMessagePackBackingStore}; use cultnet_rs::CursorKey; -use epiphany_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument, PipelineKind, Slug, register_pipeline_document_types}; +use eureka_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument, PipelineKind, Slug, register_pipeline_document_types}; use crate::receipt::HuginnCommitReceipt; use crate::refusal::MindRefusal; @@ -48,7 +48,7 @@ pub(crate) fn unavailable(error: anyhow::Error) -> MindRefusal { } /// A declared name's grammar, checked before its bytes are compared to -/// anything or reach the filesystem. `Slug` is `epiphany_pipeline`'s, +/// anything or reach the filesystem. `Slug` is `eureka_pipeline`'s, /// dot-joined ASCII labels, and the leaf now opens a public door onto its own /// check, `Slug::validate_slug`, on the pattern of `PipelineRef::validate_ref` /// (Self's ruling on the F6 fork): the grammar is the leaf's, never @@ -64,7 +64,7 @@ pub(crate) fn unavailable(error: anyhow::Error) -> MindRefusal { /// old wrapper's field, a client never sent) and not a label fragment. fn require_grammatical_slug(field: &str, declared: &Slug) -> Result<(), MindRefusal> { declared.validate_slug().map_err(|_| { - MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat { + MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat { field: field.into(), value: declared.0.clone(), }) @@ -712,7 +712,7 @@ mod tests { let escaping = Slug("..\\..\\escaped".into()); assert_eq!( Mind::open(&inner, &escaping).err(), - Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat { + Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat { field: "instance".into(), value: escaping.0.clone(), })) @@ -734,7 +734,7 @@ mod tests { let bad = Slug("not a slug!".into()); assert_eq!( Mind::open_with(store.clone(), &bad).err(), - Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat { + Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat { field: "instance".into(), value: bad.0.clone(), })) @@ -753,7 +753,7 @@ mod tests { let bad = Slug("not a slug!".into()); assert_eq!( mind.require_instance(&bad).err(), - Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat { + Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat { field: "declared".into(), value: bad.0.clone(), })) @@ -773,7 +773,7 @@ mod tests { assert!(!declared.0.is_ascii(), "U+2010 is not ASCII, unlike plain '-'"); assert_eq!( Mind::open_with(MemoryStore::new(), &declared).err(), - Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat { + Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat { field: "instance".into(), value: declared.0.clone(), })) @@ -789,7 +789,7 @@ mod tests { #[ignore = "reads and writes the scratch copy of a live state root named by HUGINN_MIND_SNAPSHOT"] fn snapshot_admits_and_reads_back() { use crate::fixtures::{admit, r, resolution, withdrawn}; - use epiphany_pipeline::{PipelineRef, ResolutionOutcome, Short, pipeline_key}; + use eureka_pipeline::{PipelineRef, ResolutionOutcome, Short, pipeline_key}; let root = std::env::var("HUGINN_MIND_SNAPSHOT").expect("HUGINN_MIND_SNAPSHOT names a scratch copy of the state root"); let mut mind = Mind::open(Path::new(&root), &slug("eureka")).unwrap(); @@ -816,7 +816,7 @@ mod tests { .unwrap_or_else(|| panic!("{} does not view", envelope.key)); assert_eq!(view.document, stored, "{}", envelope.key); if let PipelineDocument::Resolution(held) = &stored - && !matches!(held.outcome, epiphany_pipeline::ResolutionOutcome::Withdrawn { .. }) + && !matches!(held.outcome, eureka_pipeline::ResolutionOutcome::Withdrawn { .. }) && !envelope.key.contains(":resolution:resolution.") { resolutions.push(envelope.key.clone()); diff --git a/crates/huginn-mind/src/query.rs b/crates/huginn-mind/src/query.rs index 7f9d4ca..c71afd8 100644 --- a/crates/huginn-mind/src/query.rs +++ b/crates/huginn-mind/src/query.rs @@ -25,7 +25,7 @@ use std::collections::BTreeMap; use cultnet_rs::{ Cursor, EdgeAnchor, EdgeMatch, Evaluation, LIMIT_MAX, LIMIT_MIN, PROJECTION_DOCUMENT, Row, Selection, select, validate, }; -use epiphany_pipeline::{ +use eureka_pipeline::{ ClaimOutcome, CommitRange, Date, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, OrgRepo, PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, ResolutionOutcome, RulingAuthority, Sha, Short, Slug, Title, @@ -621,7 +621,7 @@ mod tests { use crate::mind::schema_cache; use crate::store::test_stores::MemoryStore; use chrono::{TimeZone, Utc}; - use epiphany_pipeline::{ + use eureka_pipeline::{ ClaimOutcome, FindingConfidence, PipelineDocument as D, PipelineKind as K, PipelineRefusal, ResolutionOutcome, }; diff --git a/crates/huginn-mind/src/receipt.rs b/crates/huginn-mind/src/receipt.rs index 3abbf16..37b042a 100644 --- a/crates/huginn-mind/src/receipt.rs +++ b/crates/huginn-mind/src/receipt.rs @@ -16,7 +16,7 @@ use chrono::{DateTime, SecondsFormat, Utc}; use cultcache_rs::{CultCacheEnvelope, DatabaseEntry}; -use epiphany_pipeline::{PipelineKind, PipelineRef, Short, Slug}; +use eureka_pipeline::{PipelineKind, PipelineRef, Short, Slug}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -47,9 +47,9 @@ pub enum Faculty { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct PipelineProvenance { pub faculty: Faculty, - pub agent: epiphany_pipeline::Short, - pub session: epiphany_pipeline::Short, - pub tool: epiphany_pipeline::Short, + pub agent: eureka_pipeline::Short, + pub session: eureka_pipeline::Short, + pub tool: eureka_pipeline::Short, } /// The exact bytes of one stored document, as read or as written. @@ -311,7 +311,7 @@ mod tests { use crate::mind::schema_cache; use crate::store::test_stores::MemoryStore; use cultcache_rs::{CacheBackingStore, PushAllOptions}; - use epiphany_pipeline::{PipelineDocument, PipelineKind, PipelineRefusal}; + use eureka_pipeline::{PipelineDocument, PipelineKind, PipelineRefusal}; /// A memory store that keeps the replacement set of every swap it was /// asked for, so a test can count the commit's calls as well as read diff --git a/crates/huginn-mind/src/refusal.rs b/crates/huginn-mind/src/refusal.rs index 1a33ac7..7236184 100644 --- a/crates/huginn-mind/src/refusal.rs +++ b/crates/huginn-mind/src/refusal.rs @@ -3,7 +3,7 @@ //! serialises it, the tools (Cut 13) show it. use cultnet_rs::SelectionRefusal; -use epiphany_pipeline::{PipelineKind, PipelineRefusal}; +use eureka_pipeline::{PipelineKind, PipelineRefusal}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/crates/huginn-mind/src/rows.rs b/crates/huginn-mind/src/rows.rs index 108cc9c..e34623f 100644 --- a/crates/huginn-mind/src/rows.rs +++ b/crates/huginn-mind/src/rows.rs @@ -12,7 +12,7 @@ use std::collections::BTreeMap; use cultnet_rs::{Row, RowSet, Selection}; -use epiphany_pipeline::{OrgRepo, Label, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome, Slug}; +use eureka_pipeline::{OrgRepo, Label, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome, Slug}; use crate::docs::{CitationRole, Held, kind_of_id, kind_of_type}; use crate::query::PipelineDocumentView; diff --git a/crates/huginn-mind/src/wire.rs b/crates/huginn-mind/src/wire.rs index 54bce78..a8df61f 100644 --- a/crates/huginn-mind/src/wire.rs +++ b/crates/huginn-mind/src/wire.rs @@ -6,7 +6,7 @@ use cultcache_rs::DatabaseEntry; use cultnet_rs::{CultMeshCdnArtifactManifest, Selection}; -use epiphany_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineKind, PipelineRef, Slug}; +use eureka_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineKind, PipelineRef, Slug}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/schemas/README.md b/schemas/README.md index eac141b..b871e43 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -1,7 +1,7 @@ # Schemas -This folder is the canonical paperwork shrine for Epiphany's shared state -contracts. +This folder is the canonical paperwork shrine for the state contracts Huginn +publishes and the pipeline leaf derives. If a Persona field or Mind document matters enough to steer the machine, it should have a receipt here instead of living @@ -10,15 +10,13 @@ only in one Rust struct or one developer's damp recollection. ## Canonical surfaces - [cultnet/gamecult.persona_state.v0.schema.json](./cultnet/gamecult.persona_state.v0.schema.json): - portable Persona state contract for Epiphany Persona, VoidBot repo Personas, and + portable Persona state contract for Eureka's Persona, VoidBot repo Personas, and Ghostlight characters. It carries explicit provenance, public presentation metadata, typed `candidateActions`, and a non-authoritative extension bag for source-specific fields; social bonds, status reads, and doctrine stances are typed affect records rather than generic thought blobs. Timestamps use JSON Schema `date-time`, `presentation` is required, and `custom` enum values have companion custom-label fields. -- [cultnet/epiphany.work_organ_state.v0.schema.json](./cultnet/epiphany.work_organ_state.v0.schema.json): - light function-shaped state for Epiphany internal work organs. - [cultnet/README.md](./cultnet/README.md): JSON Schema publication artifacts for typed CultNet boundaries. Live providers own their schema-catalog responses. @@ -27,7 +25,7 @@ only in one Rust struct or one developer's damp recollection. The living implementation is in code: -- [mind_documents.rs](/F:/Projects/Epiphany/epiphany-core/src/mind_documents.rs) +- [eureka-pipeline](../crates/eureka-pipeline/src/lib.rs): the pipeline document types and their derived schemas The rule is simple: diff --git a/schemas/cultnet/README.md b/schemas/cultnet/README.md index f6a34a5..3a83839 100644 --- a/schemas/cultnet/README.md +++ b/schemas/cultnet/README.md @@ -9,3 +9,15 @@ type of the same name in `crates/huginn-mind/src/wire.rs`. Regenerate by hand and the test refuses it: `cargo test -p huginn-mind --lib -- --exact wire::tests::published_wire_schemas_match_derivation` compares file to derivation byte for byte. + +## The pipeline catalogue + +`index.json` is the publication manifest for the thirteen +`epiphany.pipeline.*.v2` schemas and `gamecult.persona_state.v0`. The pipeline +schemas are derived from the value types in `crates/eureka-pipeline` and checked +byte for byte by its `pipeline_published_schemas_match_derivation` test. Each +document payload is `[value]`, a one-element MessagePack array whose element is +the named map the schema describes. Text bounds are UTF-8 bytes and enforced by +admission, not by the schema. A new named field with a serde default keeps +`epiphany.pipeline.epoch.v2`; a breaking change bumps the epoch, and the new +binary refuses the old store.