diff --git a/crates/eureka-pipeline/golden/envelopes.txt b/crates/eureka-pipeline/golden/envelopes.txt index 84966a2..916532e 100644 --- a/crates/eureka-pipeline/golden/envelopes.txt +++ b/crates/eureka-pipeline/golden/envelopes.txt @@ -53,3 +53,4 @@ cut_spec.reads-and-symbol eureka-state:cut_spec:cut-3a.r1 epiphany.pipeline.cut_ finding.location-symbol eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9436f6e6669726d6564a87365766572697479a448696768a5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739184a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509a673796d626f6cab2631313533303930343938b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6baa707265636564656e747390a66f726967696eaa496e74726f6475636564 target.repos eureka-state:target:r2 epiphany.pipeline.target.v2 EpiphanyPipelineTargetDocument 9187a863616d706169676eac657572656b612d7374617465a87265766973696f6e02aa696e76617269616e74739182a56c6162656cab6d696e642d61646d697473a973746174656d656e74b64f6e6c792061646d697373696f6e207772697465732eac6e6f745f696e5f73636f706591ac4576652062726f7773696e67b963616e6f6e6963616c5f696d706c656d656e746174696f6e7391a743756c744c6962a57265706f7392b147616d6543756c742f4570697068616e79af47616d6543756c742f487567696e6ea3646f6384a470617468d9256e6f7465732f657572656b612d706970656c696e652d73746174652d7461726765742e6d64aa73746172745f6c696e6501a8656e645f6c696e6509a6636f6d6d6974a83566393832323864 stewardship.names yggdrasil:stewardship:gamecult_-epiphany.n1 epiphany.pipeline.stewardship.v2 EpiphanyPipelineStewardshipDocument 9186a8696e7374616e6365a979676764726173696ca47265706fb147616d6543756c742f4570697068616e79a873657175656e636501ab61737369676e65645f6f6eaa323032362d30392d3135a46e6f7465b954686520457572656b612063616d706169676e207265706f2ea56e616d657392a3424453b1426164204461792053696d756c61746f72 +resolution.moved eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a54d6f76656481a2746fa86865696d64616c6ca9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 diff --git a/crates/eureka-pipeline/src/lib.rs b/crates/eureka-pipeline/src/lib.rs index 1aec6dc..5b85395 100644 --- a/crates/eureka-pipeline/src/lib.rs +++ b/crates/eureka-pipeline/src/lib.rs @@ -1099,6 +1099,9 @@ pub enum ResolutionOutcome { /// How a Sleep run ended when the ledger took her reply: `line` is her one line, `day_through` the last memory /// of her day she was shown (none when the day was empty). It is the one place a night's cut line is kept. Slept { line: Line, day_through: Option }, + /// A campaign's history left this mind for `to`, the mind that now keeps it. Final: the campaign and every + /// document under its root read resolved by this and take no further writes here. + Moved { to: Slug }, } impl Bounded for ResolutionOutcome { @@ -1116,6 +1119,7 @@ impl Bounded for ResolutionOutcome { line.validate(&format!("{field}.line"))?; day_through.validate(&format!("{field}.day_through")) } + Self::Moved { to } => to.validate(&format!("{field}.to")), } } } @@ -1948,6 +1952,7 @@ mod tests { let mut stewardship = stewardship_sample(); stewardship.names = vec![s("BDS"), s("Bad Day Simulator")]; out.push(("stewardship.names".into(), D::Stewardship(stewardship))); + out.push(("resolution.moved".into(), resolution(ResolutionOutcome::Moved { to: Slug("heimdall".into()) }))); out } @@ -3087,6 +3092,13 @@ mod tests { assert_eq!(slept("ok".into(), None), Ok(())); assert_eq!(slept(Line("a".repeat(1001)), None), Err(PipelineRefusal::FieldBound { field: "resolution.outcome.line".into(), limit: 1000, actual: 1001 })); refused("resolution.outcome.day_through.id", slept("ok".into(), Some(PipelineRef { kind: PipelineKind::Memory, id: id("ruling", "R8") }))); + + // A move names the receiving mind as any other slug is named, and refuses one that is not a slug. + let campaign = PipelineRef { kind: PipelineKind::Campaign, id: id("campaign", "self") }; + let moved = |to: &str| resolved(campaign.clone(), ResolutionOutcome::Moved { to: Slug(to.into()) }); + assert_eq!(moved("heimdall"), Ok(())); + refused("resolution.outcome.to", moved("Not A Slug!")); + refused("resolution.outcome.to", moved("")); } /// Ruling B: a fix names the tree where the finding stopped being true, diff --git a/schemas/cultnet/epiphany.pipeline.resolution.v2.schema.json b/schemas/cultnet/epiphany.pipeline.resolution.v2.schema.json index 3baa334..71984aa 100644 --- a/schemas/cultnet/epiphany.pipeline.resolution.v2.schema.json +++ b/schemas/cultnet/epiphany.pipeline.resolution.v2.schema.json @@ -224,6 +224,27 @@ "Slept" ], "type": "object" + }, + { + "additionalProperties": false, + "description": "A campaign's history left this mind for `to`, the mind that now keeps it. Final: the campaign and every\ndocument under its root read resolved by this and take no further writes here.", + "properties": { + "Moved": { + "properties": { + "to": { + "$ref": "#/$defs/Slug" + } + }, + "required": [ + "to" + ], + "type": "object" + } + }, + "required": [ + "Moved" + ], + "type": "object" } ] }, @@ -236,6 +257,11 @@ "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.resolution.v2.schema.json",