diff --git a/crates/eureka-pipeline/golden/envelopes.txt b/crates/eureka-pipeline/golden/envelopes.txt index b88d25e..1519dcf 100644 --- a/crates/eureka-pipeline/golden/envelopes.txt +++ b/crates/eureka-pipeline/golden/envelopes.txt @@ -22,6 +22,7 @@ sample.resolution eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resol sample.instance yggdrasil:instance:self epiphany.pipeline.instance.v2 EpiphanyPipelineInstanceDocument 9184a8696e7374616e6365a979676764726173696cac646973706c61795f6e616d65ae59676764726173696c206d696e64aa637265617465645f6174aa323032362d30392d3135a4686f7374a979676764726173696c sample.stewardship yggdrasil:stewardship:gamecult_-epiphany.n1 epiphany.pipeline.stewardship.v2 EpiphanyPipelineStewardshipDocument 9185a8696e7374616e6365a979676764726173696ca47265706fb147616d6543756c742f4570697068616e79a873657175656e636501ab61737369676e65645f6f6eaa323032362d30392d3135a46e6f7465b954686520457572656b612063616d706169676e207265706f2e sample.hand_off yggdrasil:hand_off:thought-cage.gamecult_-epiphany.2026-09-15 epiphany.pipeline.hand_off.v2 EpiphanyPipelineHandOffDocument 9186ad66726f6d5f696e7374616e6365a979676764726173696cab746f5f696e7374616e6365ac74686f756768742d63616765a47265706fb147616d6543756c742f4570697068616e79a9646f63756d656e747392ac657572656b612d7374617465b6657572656b612d73746174653a72756c696e673a5238a6726561736f6ed92854686520776f726b73746174696f6e206d696e642074616b6573207468652063616d706169676e2ea968616e6465645f6f6eaa323032362d30392d3135 +sample.run yggdrasil:run:mind-2026-10-09-1 epiphany.pipeline.run.v2 EpiphanyPipelineRunDocument 9189a8696e7374616e6365a979676764726173696ca56c6162656cb16d696e642d323032362d31302d30392d31ab6f706572617465645f6279a44d696e64a47475726ea753656c6652756ea4686f7374a979676764726173696caa737461727465645f6f6eaa323032362d30392d3135aa6275646765745f757364a3372e35a6636c61696d739182a46b696e64a743757453706563a26964d925657572656b612d73746174653a6375745f737065633a6375742d72756e2d6b696e642e7232a963616d706169676e7391ac657572656b612d7374617465 resolution.superseded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581aa5375706572736564656481a262799282a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a523882a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 resolution.fixed-by-none eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a26279c0a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 resolution.fixed-by-some eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a2627982a46b696e64a94375745265706f7274a26964d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 diff --git a/crates/eureka-pipeline/src/lib.rs b/crates/eureka-pipeline/src/lib.rs index 6164288..abced06 100644 --- a/crates/eureka-pipeline/src/lib.rs +++ b/crates/eureka-pipeline/src/lib.rs @@ -472,6 +472,31 @@ fn campaign_repos_are_distinct(campaign: &PipelineCampaign, at: &str) -> Result< Ok(()) } +/// A run claims work, never runs, rulings or campaigns: its claims are cut +/// specs, findings and follow-ups, the three kinds a Self takes on. And its +/// budget is a canonical decimal (`0`, `12`, `7.5`: no sign, no leading or +/// trailing zeros, no exponent), so one spend has one spelling. +fn run_is_well_formed(run: &PipelineRun, at: &str) -> Result<(), PipelineRefusal> { + for (index, claim) in run.claims.iter().enumerate() { + if !matches!(claim.kind, PipelineKind::CutSpec | PipelineKind::Finding | PipelineKind::FollowUp) { + return Err(format_error(&format!("{at}.claims[{index}].kind"), claim.kind.name())); + } + if run.claims[..index].contains(claim) { + return Err(format_error(&format!("{at}.claims[{index}]"), &claim.id.0)); + } + } + let (whole, fraction) = run.budget_usd.0.split_once('.').map_or((run.budget_usd.0.as_str(), None), |(whole, fraction)| (whole, Some(fraction))); + let digits = |text: &str| !text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit()); + let canonical = digits(whole) + && (whole == "0" || !whole.starts_with('0')) + && fraction.is_none_or(|fraction| digits(fraction) && !fraction.ends_with('0')); + if canonical { + Ok(()) + } else { + Err(format_error(&format!("{at}.budget_usd"), &run.budget_usd.0)) + } +} + unit_enums! { EvidenceKind { Command, Test, Mutation, Probe, SourceRead, Capture } ClaimOutcome { Holds, Falsified, Unproven } @@ -479,6 +504,8 @@ unit_enums! { FindingSeverity { Blocker, High, Medium, Low } RulingAuthority { Operator, Standing, Defaulted, Mind } FindingOrigin { Introduced, PreExisting } + RunOperator { Operator, Mind } + RunTurn { PersonaTurn, SelfRun } } value_types! { @@ -595,6 +622,15 @@ value_types! { from_instance: Slug, to_instance: Slug, repo: OrgRepo, documents: Vec[256], reason: Para, handed_on: Date, } + /// One launch of a Self or a Persona turn: the one record of what it took + /// on. `claims` are the work it holds, and a claim is live while its run + /// has no resolution in force. `budget_usd` is a canonical decimal string, + /// `0` for an interactive run. Instance-rooted like stewardship: the key + /// is `:run: