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 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 03:56:03 +02:00
commit e770fb9f21
11 changed files with 108 additions and 50 deletions

8
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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,
}

View file

@ -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");
}
}

View file

@ -183,12 +183,14 @@ pub fn answer<S: MindStore, I: IndexSink<S>>(
},
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, &registry, CultNetMessage::Error { error: "hello".into() }, now());
let CultNetMessage::Error { error } = &reply else { panic!("expected an error, got {reply:?}") };
let reply = answer(&mut daemon, &registry, 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}"

View file

@ -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"] }

View file

@ -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::<Vec<_>>();
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()));

View file

@ -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(),

View file

@ -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

View file

@ -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",

View file

@ -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",