From e770fb9f215bb43fcc546b204f08c7ceb79ba3b6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 03:56:03 +0200 Subject: [PATCH] BP-2: move Huginn to CultLib 6251cbc and the RS-L leaf 4891f2e5 One pin move: huginn-mind pins cultcache-rs at 6251cbc and the leaf at 4891f2e5; huginn-daemon pins cultnet-rs at 6251cbc. cargo tree shows one cultcache-rs and one cultnet-rs per crate. Adopting RS-L, which the new leaf carries: - Campaign, cut-spec, question and ruling fixtures carry a Title; the question and ruling fixtures gain the new required title field. - The epoch literals in daemon and envelope tests move v1 -> v2. A store written at an earlier epoch is refused at open (ForeignEpoch); no key migration. - Derived stewardship, hand-off and resolution keys are now the lowercased OrgRepo::identity(); test key literals follow. - The two published wire schemas are regenerated (Title, OrgRepo bound and description); the byte-for-byte pin test passes. CultLib 6251cbc gives CultNetMessage::Error code and details; the four construction sites and one pattern in huginn-daemon take None and `..`. No behaviour change: None is skipped on the wire. mind 70 and daemon 18 pass. Co-Authored-By: Claude Sonnet 5 --- Cargo.lock | 8 ++-- crates/huginn-daemon/Cargo.toml | 2 +- crates/huginn-daemon/src/daemon.rs | 10 ++-- crates/huginn-daemon/src/envelope.rs | 4 +- crates/huginn-daemon/src/serve.rs | 8 ++-- crates/huginn-mind/Cargo.toml | 4 +- crates/huginn-mind/src/admission.rs | 42 ++++++++--------- crates/huginn-mind/src/fixtures.rs | 6 ++- crates/huginn-mind/src/query.rs | 6 +-- .../huginn.mind_request.v1.schema.json | 22 +++++++-- .../huginn.mind_response.v1.schema.json | 46 +++++++++++++++++-- 11 files changed, 108 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 438450c..b68e112 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,7 +175,7 @@ dependencies = [ [[package]] name = "cultcache-rs" version = "0.2.0" -source = "git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6#a0813c6eed24d30bf88073ef615b633c77ebfcd6" +source = "git+https://github.com/GameCult/CultLib.git?rev=6251cbcd37c12ef3f0c1cc50063a03369225b7ac#6251cbcd37c12ef3f0c1cc50063a03369225b7ac" dependencies = [ "anyhow", "chrono", @@ -193,7 +193,7 @@ dependencies = [ [[package]] name = "cultcache-rs-derive" version = "0.1.0" -source = "git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6#a0813c6eed24d30bf88073ef615b633c77ebfcd6" +source = "git+https://github.com/GameCult/CultLib.git?rev=6251cbcd37c12ef3f0c1cc50063a03369225b7ac#6251cbcd37c12ef3f0c1cc50063a03369225b7ac" dependencies = [ "proc-macro2", "quote", @@ -203,7 +203,7 @@ dependencies = [ [[package]] name = "cultnet-rs" version = "0.1.0" -source = "git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6#a0813c6eed24d30bf88073ef615b633c77ebfcd6" +source = "git+https://github.com/GameCult/CultLib.git?rev=6251cbcd37c12ef3f0c1cc50063a03369225b7ac#6251cbcd37c12ef3f0c1cc50063a03369225b7ac" dependencies = [ "aes-gcm", "anyhow", @@ -306,7 +306,7 @@ dependencies = [ [[package]] name = "epiphany-pipeline" version = "0.1.0" -source = "git+https://github.com/GameCult/Epiphany.git?rev=b3ae787b7dc13e3677bd6f7d6b3867542a32f5c5#b3ae787b7dc13e3677bd6f7d6b3867542a32f5c5" +source = "git+https://github.com/GameCult/Epiphany.git?rev=4891f2e59008efeb262197564eddaa75bc3c75ea#4891f2e59008efeb262197564eddaa75bc3c75ea" dependencies = [ "anyhow", "chrono", diff --git a/crates/huginn-daemon/Cargo.toml b/crates/huginn-daemon/Cargo.toml index 9b434e3..04dfd1a 100644 --- a/crates/huginn-daemon/Cargo.toml +++ b/crates/huginn-daemon/Cargo.toml @@ -21,7 +21,7 @@ path = "src/main.rs" anyhow = "1" base64 = "0.22" chrono = "0.4.44" -cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultnet-rs" } +cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "6251cbcd37c12ef3f0c1cc50063a03369225b7ac", package = "cultnet-rs" } huginn-mind = { path = "../huginn-mind" } rmp-serde = "1" schemars = "1" diff --git a/crates/huginn-daemon/src/daemon.rs b/crates/huginn-daemon/src/daemon.rs index 56b98db..9e2e04c 100644 --- a/crates/huginn-daemon/src/daemon.rs +++ b/crates/huginn-daemon/src/daemon.rs @@ -101,7 +101,7 @@ pub(crate) mod tests { AuthorityMap, CodeLocation, CutDelete, CutVerification, Date, DocRef, FileChange, Line, NegativeCheck, OrgRepo, PipelineCampaign, PipelineCutSpec, PipelineDocument, PipelineInstance, PipelineKind, PipelineQuestion, PipelineRuling, PipelineStewardship, QuestionOption, RulingAuthority, Sha, Short, - StructuralDelta, VerificationTest, + StructuralDelta, Title, VerificationTest, }; use huginn_mind::wire::MindStatus; use huginn_mind::{Faculty, PipelineAdmissionBatch, PipelineProvenance, PipelineQuery, PipelineStatus, SemanticQuery}; @@ -154,6 +154,7 @@ pub(crate) mod tests { let question = PipelineDocument::Question(PipelineQuestion { campaign: slug(CAMPAIGN), label: "Q1".into(), + title: Title("Who owns the state?".into()), question: "Who owns the state?".into(), options: vec![ QuestionOption { label: "A".into(), text: "the organ".into() }, @@ -167,6 +168,7 @@ pub(crate) mod tests { let ruling = PipelineDocument::Ruling(PipelineRuling { campaign: slug(CAMPAIGN), label: "R1".into(), + title: Title("An instance owns its mind".into()), answers: Some(Short(format!("{CAMPAIGN}:question:Q1"))), choice: Some("A".into()), ruling: "An instance owns its mind.".into(), @@ -213,7 +215,7 @@ pub(crate) mod tests { }), PipelineDocument::Campaign(PipelineCampaign { slug: slug(CAMPAIGN), - title: Short("Eureka pipeline state".into()), + title: Title("Eureka pipeline state".into()), repos: vec![OrgRepo(REPO.into())], working_branch: Short("codex/eureka-pipeline-state".into()), target_doc: doc_ref(), @@ -237,7 +239,7 @@ pub(crate) mod tests { campaign: slug(CAMPAIGN), cut: cut.into(), revision: 1, - title: short(0), + title: Title(short(0).0), repo: OrgRepo(REPO.into()), branch: short(1), base: sha(), @@ -366,7 +368,7 @@ pub(crate) mod tests { status(&mut daemon), MindStatus { instance: slug(INSTANCE), - schema_epoch: "epiphany.pipeline.epoch.v1".into(), + schema_epoch: "epiphany.pipeline.epoch.v2".into(), documents: 1, receipts: 1, } diff --git a/crates/huginn-daemon/src/envelope.rs b/crates/huginn-daemon/src/envelope.rs index 66b0563..b3b0275 100644 --- a/crates/huginn-daemon/src/envelope.rs +++ b/crates/huginn-daemon/src/envelope.rs @@ -180,7 +180,7 @@ mod tests { ( HuginnMindResponse::Whoami(MindStatus { instance: slug(INSTANCE), - schema_epoch: "epiphany.pipeline.epoch.v1".into(), + schema_epoch: "epiphany.pipeline.epoch.v2".into(), documents: 0, receipts: 0, }), @@ -217,7 +217,7 @@ mod tests { let message = encode_request("m-3", &request, Some("eureka-state".into())).unwrap(); assert_eq!(decode_request(&message).unwrap(), ("m-3".into(), request)); - let foreign = CultNetMessage::Error { error: "not for you".into() }; + let foreign = CultNetMessage::Error { error: "not for you".into(), code: None, details: None }; assert_eq!(decode_request(&foreign).unwrap_err().code, "not-an-operation-request"); } } diff --git a/crates/huginn-daemon/src/serve.rs b/crates/huginn-daemon/src/serve.rs index 40f068e..b097f02 100644 --- a/crates/huginn-daemon/src/serve.rs +++ b/crates/huginn-daemon/src/serve.rs @@ -183,12 +183,14 @@ pub fn answer>( }, CultNetMessage::SchemaCatalogRequest { .. } => match registry.create_catalog_response(&message) { Ok(response) => response, - Err(error) => CultNetMessage::Error { error: format!("{error:#}") }, + Err(error) => CultNetMessage::Error { error: format!("{error:#}"), code: None, details: None }, }, _ => CultNetMessage::Error { error: format!( "{MIND_SERVICE_ID} answers cultnet.operation_request.v0 and cultnet.schema_catalog_request.v0" ), + code: None, + details: None, }, } } @@ -395,8 +397,8 @@ mod tests { // The sixth code, `not-an-operation-request`, is the codec's and is // pinned in `envelope::tests`: `answer` never produces it, because a // message of another family is answered with `Error` instead. - let reply = answer(&mut daemon, ®istry, CultNetMessage::Error { error: "hello".into() }, now()); - let CultNetMessage::Error { error } = &reply else { panic!("expected an error, got {reply:?}") }; + let reply = answer(&mut daemon, ®istry, CultNetMessage::Error { error: "hello".into(), code: None, details: None }, now()); + let CultNetMessage::Error { error, .. } = &reply else { panic!("expected an error, got {reply:?}") }; assert!( !error.is_empty() && error.contains("cultnet.operation_request.v0") && error.contains("cultnet.schema_catalog_request.v0"), "{error}" diff --git a/crates/huginn-mind/Cargo.toml b/crates/huginn-mind/Cargo.toml index c8156a3..64fea12 100644 --- a/crates/huginn-mind/Cargo.toml +++ b/crates/huginn-mind/Cargo.toml @@ -13,8 +13,8 @@ publish.workspace = true [dependencies] anyhow = "1" chrono = "0.4.44" -cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultcache-rs" } -epiphany-pipeline = { git = "https://github.com/GameCult/Epiphany.git", rev = "b3ae787b7dc13e3677bd6f7d6b3867542a32f5c5", package = "epiphany-pipeline" } +cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "6251cbcd37c12ef3f0c1cc50063a03369225b7ac", package = "cultcache-rs" } +epiphany-pipeline = { git = "https://github.com/GameCult/Epiphany.git", rev = "4891f2e59008efeb262197564eddaa75bc3c75ea", package = "epiphany-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 7fed0b3..4734da2 100644 --- a/crates/huginn-mind/src/admission.rs +++ b/crates/huginn-mind/src/admission.rs @@ -758,7 +758,7 @@ mod tests { ); committed(admit(&mut yggdrasil, vec![instance(INSTANCE), stewardship(INSTANCE, REPO)])); committed(admit(&mut thought_cage, vec![instance(OTHER_INSTANCE)])); - let key = format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1"); + let key = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"); assert!(yggdrasil.envelope(K::Stewardship, &key).is_some()); assert!(thought_cage.envelope(K::Stewardship, &key).is_none()); assert_eq!(yggdrasil.envelopes().len(), 4); @@ -1199,7 +1199,7 @@ mod tests { #[test] fn a_repo_is_stewarded_once_at_a_time_and_again_after_a_transfer() { let mut mind = seeded(); - let first = format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1"); + let first = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"); assert_eq!( refusal(admit(&mut mind, vec![stewardship_n(INSTANCE, REPO, 2)])), MindRefusal::AlreadyStewarded { repo: REPO.into() } @@ -1215,10 +1215,10 @@ mod tests { // is a resolution of that record, sequence and all. The stewardship it // reads to derive that is an image document, so it is pinned. let (receipt_id, writes) = committed(admit(&mut mind, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[])])); - let away = format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.GameCult_-Epiphany.{}", date().0); + let away = format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-epiphany.{}", date().0); assert_eq!(writes, vec![ r(K::HandOff, &away), - r(K::Resolution, &format!("{INSTANCE}:resolution:stewardship.GameCult_-Epiphany.n1.n1")), + r(K::Resolution, &format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n1.n1")), ]); let receipt = mind.receipts().unwrap().into_iter().find(|receipt| receipt.receipt_id == receipt_id).unwrap(); let stewardship_envelope = mind.envelope(K::Stewardship, &first).unwrap().clone(); @@ -1242,9 +1242,9 @@ mod tests { let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() }; back.handed_on = epiphany_pipeline::Date("2026-09-17".into()); let (_, writes) = committed(admit(&mut mind, vec![D::HandOff(back)])); - let again = format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n2"); + let again = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n2"); assert_eq!(writes, vec![ - r(K::HandOff, &format!("{OTHER_INSTANCE}:hand_off:{INSTANCE}.GameCult_-Epiphany.2026-09-17")), + r(K::HandOff, &format!("{OTHER_INSTANCE}:hand_off:{INSTANCE}.gamecult_-epiphany.2026-09-17")), r(K::Stewardship, &again), ]); let Some(D::Stewardship(taken)) = mind.get(K::Stewardship, &again).unwrap() else { panic!() }; @@ -1264,9 +1264,9 @@ mod tests { let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() }; back.handed_on = epiphany_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"); - let withdrawal = format!("{INSTANCE}:resolution:stewardship.GameCult_-Epiphany.n1.n1"); + let first = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"); + let again = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n2"); + let withdrawal = format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n1.n1"); assert_eq!( refusal(admit(&mut mind, vec![resolution(r(K::Resolution, &withdrawal), withdrawn())])), MindRefusal::WouldReinstateOverLater { subject: first, later: again.clone() } @@ -1305,14 +1305,14 @@ mod tests { committed(admit(&mut mind, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[])])); // Nothing later stands over the first assignment, so withdrawing the // withdrawal puts it back in force and the repo is this mind's again. - let withdrawal = format!("{INSTANCE}:resolution:stewardship.GameCult_-Epiphany.n1.n1"); + 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()); 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")), - r(K::Resolution, &format!("{INSTANCE}:resolution:stewardship.GameCult_-Epiphany.n1.n2")), + r(K::HandOff, &format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-epiphany.2026-09-17")), + r(K::Resolution, &format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n1.n2")), ]); } @@ -1323,7 +1323,7 @@ mod tests { } fn stewardship_key(sequence: u32) -> String { - format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n{sequence}") + format!("{INSTANCE}:stewardship:gamecult_-epiphany.n{sequence}") } /// Which assignment a hand-off's derived withdrawal names is a question of @@ -1362,7 +1362,7 @@ mod tests { // carries a withdrawal -- of another hand-off. Sorting first, it is // what a match on the outcome alone would pick. committed(admit(&mut mind, vec![hand_off_on(INSTANCE, OTHER_INSTANCE, "2026-09-11")])); - let withdrawal = format!("{INSTANCE}:resolution:stewardship.GameCult_-Epiphany.n11.n1"); + let withdrawal = format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n11.n1"); committed(admit(&mut mind, vec![resolution(r(K::Resolution, &withdrawal), withdrawn())])); let (away, receipt_id) = &aways[1]; assert_eq!( @@ -1381,7 +1381,7 @@ mod tests { committed(admit(&mut mind, vec![hand_off_on(INSTANCE, OTHER_INSTANCE, "2026-09-16")])); committed(admit(&mut mind, vec![hand_off_on(OTHER_INSTANCE, INSTANCE, "2026-09-17")])); committed(admit(&mut mind, vec![hand_off_on(INSTANCE, OTHER_INSTANCE, "2026-09-18")])); - let withdrawal = format!("{INSTANCE}:resolution:stewardship.GameCult_-Epiphany.n1.n1"); + let withdrawal = format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n1.n1"); committed(admit(&mut mind, vec![resolution(r(K::Resolution, &withdrawal), withdrawn())])); let in_force = docs_of(&mind).stewardships_of(&slug(INSTANCE), &repo(REPO), None).iter().map(|(key, _)| key.to_string()).collect::>(); assert_eq!(in_force, vec![stewardship_key(1)]); @@ -1394,7 +1394,7 @@ mod tests { fn a_reinstatement_is_blocked_by_an_assignment_in_its_own_batch() { let mut mind = seeded(); committed(admit(&mut mind, vec![hand_off_on(INSTANCE, OTHER_INSTANCE, "2026-09-16")])); - let withdrawal = format!("{INSTANCE}:resolution:stewardship.GameCult_-Epiphany.n1.n1"); + let withdrawal = format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n1.n1"); assert_eq!( refusal(admit(&mut mind, vec![ hand_off_on(OTHER_INSTANCE, INSTANCE, "2026-09-17"), @@ -1464,12 +1464,12 @@ mod tests { let spec_ref = r(K::CutSpec, &id("cut_spec", "cut-1.r1")); let finding_ref = r(K::Finding, &id("finding", "cut-1.s1.F1")); let follow_up_ref = r(K::FollowUp, &id("follow_up", "FU-1")); - let stewardship_ref = r(K::Stewardship, &format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1")); + let stewardship_ref = r(K::Stewardship, &format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1")); let campaign_ref = r(K::Campaign, &id("campaign", "self")); let report_ref = r(K::CutReport, &id("cut_report", "cut-1.h1")); let verdict_ref = r(K::Verdict, &id("verdict", "cut-1.s1")); let instance_ref = r(K::Instance, &format!("{INSTANCE}:instance:self")); - let hand_off_ref = r(K::HandOff, &format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.GameCult_-Huginn.2026-09-16")); + let hand_off_ref = r(K::HandOff, &format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-huginn.2026-09-16")); // Accepted, one per resolvable kind. committed(admit(&mut world(), vec![target(2, &[INVARIANT]), resolution(target_ref.clone(), superseded(&[target_r2.clone()]))])); @@ -1900,9 +1900,9 @@ mod tests { #[test] fn a_hand_off_derives_this_minds_side_only() { - let key = format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.GameCult_-Epiphany.2026-09-16"); + let key = format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-epiphany.2026-09-16"); let campaign_key = id("campaign", "self"); - let stewardship_key = format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1"); + let stewardship_key = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"); let mut source = seeded(); assert_eq!( @@ -1923,7 +1923,7 @@ mod tests { let mut receiving = opened(MemoryStore::new(), OTHER_INSTANCE); committed(admit(&mut receiving, vec![instance(OTHER_INSTANCE)])); let (_, writes) = committed(admit(&mut receiving, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[&campaign_key])])); - let assigned = format!("{OTHER_INSTANCE}:stewardship:GameCult_-Epiphany.n1"); + let assigned = format!("{OTHER_INSTANCE}:stewardship:gamecult_-epiphany.n1"); assert_eq!(writes, vec![r(K::HandOff, &key), r(K::Stewardship, &assigned)]); let Some(D::Stewardship(derived)) = receiving.get(K::Stewardship, &assigned).unwrap() else { panic!() }; assert_eq!(derived.note, Line(key.clone())); diff --git a/crates/huginn-mind/src/fixtures.rs b/crates/huginn-mind/src/fixtures.rs index 56ff119..0f0f59f 100644 --- a/crates/huginn-mind/src/fixtures.rs +++ b/crates/huginn-mind/src/fixtures.rs @@ -138,7 +138,7 @@ pub(crate) fn stewardship_n(instance: &str, repo_name: &str, sequence: u32) -> P pub(crate) fn campaign(repos: &[&str]) -> PipelineDocument { PipelineDocument::Campaign(PipelineCampaign { slug: slug(CAMPAIGN), - title: s("Eureka pipeline state"), + title: "Eureka pipeline state".into(), repos: repos.iter().map(|name| repo(name)).collect(), working_branch: s("codex/eureka-pipeline-state"), target_doc: doc_ref(), @@ -163,6 +163,7 @@ pub(crate) fn question(label: &str, options: &[&str], recommended: &str) -> Pipe PipelineDocument::Question(PipelineQuestion { campaign: slug(CAMPAIGN), label: l(label), + title: "Who owns the state?".into(), question: "Who owns the state?".into(), options: options.iter().map(|option| QuestionOption { label: l(option), text: "an option".into() }).collect(), recommended: l(recommended), @@ -176,6 +177,7 @@ pub(crate) fn ruling(label: &str) -> PipelineRuling { PipelineRuling { campaign: slug(CAMPAIGN), label: l(label), + title: "An instance owns its mind".into(), answers: None, choice: None, ruling: "An instance owns its mind.".into(), @@ -191,7 +193,7 @@ pub(crate) fn cut_spec(cut: &str, revision: u32) -> PipelineCutSpec { campaign: slug(CAMPAIGN), cut: l(cut), revision, - title: s("A cut"), + title: "A cut".into(), repo: repo(REPO), branch: s("codex/eureka-pipeline-state"), base: sha(), diff --git a/crates/huginn-mind/src/query.rs b/crates/huginn-mind/src/query.rs index f9ada57..445b7a3 100644 --- a/crates/huginn-mind/src/query.rs +++ b/crates/huginn-mind/src/query.rs @@ -643,7 +643,7 @@ mod tests { assert!(!eureka.contains(&"other-campaign:question:Q1".to_string())); assert_eq!(matching(PipelineQuery { campaign: Some(slug(INSTANCE)), ..Default::default() }), vec![ format!("{INSTANCE}:instance:self"), - format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1"), + format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"), ]); // `repo`: a campaign lists its repos, five kinds name one, and a @@ -655,7 +655,7 @@ mod tests { id("cut_spec", "cut-9.r1"), id("cut_report", "cut-9.h1"), id("follow_up", "FU-1"), - format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1"), + format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"), supersession.clone(), ] { assert!(epiphany.contains(&expected), "{expected}"); @@ -714,7 +714,7 @@ mod tests { id("campaign", "self"), id("target", "r1"), format!("{INSTANCE}:instance:self"), - format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1"), + format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1"), ]); // 201 questions over four batches at rising `now`s, with Q1 -- the id diff --git a/schemas/cultnet/huginn.mind_request.v1.schema.json b/schemas/cultnet/huginn.mind_request.v1.schema.json index 67f08fa..44518f2 100644 --- a/schemas/cultnet/huginn.mind_request.v1.schema.json +++ b/schemas/cultnet/huginn.mind_request.v1.schema.json @@ -473,9 +473,9 @@ ] }, "OrgRepo": { - "description": "A GitHub repository as `Org/Repo`.", + "description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.", "type": "string", - "maxLength": 200 + "maxLength": 140 }, "Para": { "description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.", @@ -522,7 +522,7 @@ "$ref": "#/$defs/DocRef" }, "title": { - "$ref": "#/$defs/Short" + "$ref": "#/$defs/Title" }, "working_branch": { "$ref": "#/$defs/Short" @@ -730,7 +730,7 @@ "maxItems": 32 }, "title": { - "$ref": "#/$defs/Short" + "$ref": "#/$defs/Title" }, "verification": { "$ref": "#/$defs/CutVerification" @@ -1313,11 +1313,15 @@ }, "recommended": { "$ref": "#/$defs/Label" + }, + "title": { + "$ref": "#/$defs/Title" } }, "required": [ "campaign", "label", + "title", "question", "options", "recommended", @@ -1425,11 +1429,15 @@ }, "ruling": { "$ref": "#/$defs/Para" + }, + "title": { + "$ref": "#/$defs/Title" } }, "required": [ "campaign", "label", + "title", "ruling", "ruled_on", "precedents", @@ -1862,6 +1870,12 @@ "statement" ] }, + "Title": { + "description": "A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema\nlength counts characters. Holds at least one alphanumeric character,\nno control character, and no bidi control character (`U+061C`,\n`U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`).", + "type": "string", + "maxLength": 200, + "minLength": 1 + }, "VerdictClaim": { "description": "The promise this claim measured, and the report's mutations it ran. An\n`Unproven` claim with a promise is one Soul could not reach.", "type": "object", diff --git a/schemas/cultnet/huginn.mind_response.v1.schema.json b/schemas/cultnet/huginn.mind_response.v1.schema.json index e22e14b..aa12f38 100644 --- a/schemas/cultnet/huginn.mind_response.v1.schema.json +++ b/schemas/cultnet/huginn.mind_response.v1.schema.json @@ -1312,9 +1312,9 @@ ] }, "OrgRepo": { - "description": "A GitHub repository as `Org/Repo`.", + "description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.", "type": "string", - "maxLength": 200 + "maxLength": 140 }, "Para": { "description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.", @@ -1429,7 +1429,7 @@ "$ref": "#/$defs/DocRef" }, "title": { - "$ref": "#/$defs/Short" + "$ref": "#/$defs/Title" }, "working_branch": { "$ref": "#/$defs/Short" @@ -1637,7 +1637,7 @@ "maxItems": 32 }, "title": { - "$ref": "#/$defs/Short" + "$ref": "#/$defs/Title" }, "verification": { "$ref": "#/$defs/CutVerification" @@ -2182,11 +2182,15 @@ }, "recommended": { "$ref": "#/$defs/Label" + }, + "title": { + "$ref": "#/$defs/Title" } }, "required": [ "campaign", "label", + "title", "question", "options", "recommended", @@ -2297,6 +2301,30 @@ "InvalidIdentity" ] }, + { + "type": "object", + "properties": { + "DuplicateRepo": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "repo": { + "type": "string" + } + }, + "required": [ + "field", + "repo" + ] + } + }, + "additionalProperties": false, + "required": [ + "DuplicateRepo" + ] + }, { "type": "object", "properties": { @@ -2403,11 +2431,15 @@ }, "ruling": { "$ref": "#/$defs/Para" + }, + "title": { + "$ref": "#/$defs/Title" } }, "required": [ "campaign", "label", + "title", "ruling", "ruled_on", "precedents", @@ -2857,6 +2889,12 @@ "statement" ] }, + "Title": { + "description": "A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema\nlength counts characters. Holds at least one alphanumeric character,\nno control character, and no bidi control character (`U+061C`,\n`U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`).", + "type": "string", + "maxLength": 200, + "minLength": 1 + }, "VerdictClaim": { "description": "The promise this claim measured, and the report's mutations it ran. An\n`Unproven` claim with a promise is one Soul could not reach.", "type": "object",