diff --git a/crates/eureka-pipeline/golden/envelopes.txt b/crates/eureka-pipeline/golden/envelopes.txt index 01bb38b..3f8d54c 100644 --- a/crates/eureka-pipeline/golden/envelopes.txt +++ b/crates/eureka-pipeline/golden/envelopes.txt @@ -32,7 +32,7 @@ sample.research eureka-state:research:p1.scope epiphany.pipeline.research.v2 Epi sample.idea eureka-state:idea:p1.i1 epiphany.pipeline.idea.v2 EpiphanyPipelineIdeaDocument 9189a863616d706169676eac657572656b612d7374617465a470617373a27031a56c6162656ca26931a57469746c65b24f6e65206e6f74652070657220636c61696da469646561d9204b65657020612070617373206173206d616e7920736d616c6c206e6f7465732ea974726164656f666673b74d6f726520646f63756d656e747320746f206c6973742ea872657374735f6f6e9182a46b696e64a85265736561726368a26964be657572656b612d73746174653a72657365617263683a70312e73636f7065a6746f776172649182a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131ab70726f706f7365645f6f6eaa323032362d30392d3135 sample.reply yggdrasil:reply:mind-2026-10-09-1-r1 epiphany.pipeline.reply.v2 EpiphanyPipelineReplyDocument 9185a8696e7374616e6365a979676764726173696ca56c6162656cb46d696e642d323032362d31302d30392d312d7231a372756e82a46b696e64a352756ea26964bf79676764726173696c3a72756e3a6d696e642d323032362d31302d30392d31a7616e73776572739182a46b696e64a8466f6c6c6f775570a26964d923657572656b612d73746174653a666f6c6c6f775f75703a61736b2d7468652d73656c66a474657874d93054686520736861646572207061737320697320646f6e653b2074686520736f756e647320617265204c6962627927732e sample.thought yggdrasil:thought:t1793000000 epiphany.pipeline.thought.v2 EpiphanyPipelineThoughtDocument 91de0014a8696e7374616e6365a979676764726173696ca56c6162656cab7431373933303030303030a474657874d93d54686520736f756e647320617265204c6962627927733b2074686520736861646572732077657265206e6576657220746865206861726420706172742ea572656164739182a46b696e64a64d656d6f7279a26964d92879676764726173696c3a6d656d6f72793a323032362d31302d3331743034313230302d74302d6d30af706572736f6e615f72656365697074b06d696e642d636f6d6d69742d30613162a86964656e74697479be457572656b612c207368652f6865722c20706c61696e2d73706f6b656e2ea7636f6e74657874d92056616c7565733a20636f686572656e6365206f7665722076656c6f636974792ea87374696d756c7573bd4c696262792061736b65642061626f75742074686520736f756e64732eaf70726f6a6563746f725f6d6f64656cb070726f6a6563746f723a6c6174657374aa70726f6a656374696f6ed92949206b656570207475726e696e67206f7665722077686f20686f6c64732074686520736f756e64732eaa626173655f6d6f64656cab626173653a6c6174657374b474656d70657261747572655f7065726d696c6c65cd03e8ae6d696e5f705f7065726d696c6c6532aa6d61785f746f6b656e73cca0aa63616e6469646174657392d93d54686520736f756e647320617265204c6962627927733b2074686520736861646572732077657265206e6576657220746865206861726420706172742eaf41207365636f6e642064726966742eb06e6561726573745f7065726d696c6c6592cd0136cd0366a663686f73656e00ac70726f6a6563746f725f6d73cd1068ac636f6e74696e7565725f6d73cd238cae70726f6d70745f76657273696f6ebd67686f73746c696768743d633433656235343b20776f7264733d313830 -sample.desk_answer eureka-state:desk_answer:Q1.n1 epiphany.pipeline.desk_answer.v2 EpiphanyPipelineDeskAnswerDocument 918aa863616d706169676eac657572656b612d7374617465a4666f726bb8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501ac6f7074696f6e735f68617368d94063636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363a663686f696365a161a46e6f7465b853686172652069742c207769746820746865206d61726b2eab616e7377657265645f6174b4323032362d31312d31305431353a30303a30305aa8616e737765726572a6757365722d37a9617574686f72697479a744656369646572a76f7574636f6d6581a85265766973696f6e81a26f66be657572656b612d73746174653a6465736b5f616e737765723a51312e6e31 +sample.desk_answer eureka-state:desk_answer:Q1.n1 epiphany.pipeline.desk_answer.v2 EpiphanyPipelineDeskAnswerDocument 918ea863616d706169676eac657572656b612d7374617465a4666f726bb8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501ac6f7074696f6e735f68617368d94063636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363a663686f696365a161a46e6f7465b853686172652069742c207769746820746865206d61726b2eab616e7377657265645f6174b4323032362d31312d31305431353a30303a30305aab72656365697665645f6174b4323032362d31312d31305431353a30303a30335aaf636c61696d5f696e5f667574757265c2aa726571756573745f6964d92436663163306135322d336237652d346431612d396335382d306532643761346239663133ab626f64795f736861323536d94064646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464a8616e737765726572a6757365722d37a9617574686f72697479a64d656d626572a76f7574636f6d6581a85265766973696f6e81a26f66be657572656b612d73746174653a6465736b5f616e737765723a51312e6e31 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 f14c1ef..072bc37 100644 --- a/crates/eureka-pipeline/src/lib.rs +++ b/crates/eureka-pipeline/src/lib.rs @@ -272,6 +272,27 @@ fn symbol_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { within(128)(field, value) } +/// A lowercase D-format UUID: 8-4-4-4-12 lowercase hex digits, any version. +fn uuid_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + let shaped = value.len() == 36 + && value.bytes().enumerate().all(|(at, byte)| match at { + 8 | 13 | 18 | 23 => byte == b'-', + _ => matches!(byte, b'0'..=b'9' | b'a'..=b'f'), + }); + if !shaped { + return Err(format_error(field, value)); + } + Ok(()) +} + +/// A person's words: 1 to 4000 UTF-8 bytes and not blank (`str::trim` leaves something). +fn words_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + if value.trim().is_empty() { + return Err(format_error(field, value)); + } + within(4000)(field, value) +} + bounded_text! { /// Text of at most 200 UTF-8 bytes. Short = 200, |field, value| within(200)(field, value); @@ -290,6 +311,11 @@ bounded_text! { Symbol = 128, symbol_text; /// Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`. Para = 4000, |field, value| within(4000)(field, value); + /// A person's words, verbatim: 1 to 4,000 UTF-8 bytes, not blank (CultLib PR #72, `answer.note`). + #[schemars(extend("minLength" = 1))] + Words = 4000, words_text; + /// A client's retry token: a lowercase D-format UUID. + RequestId = 36, uuid_text; /// A key label: `[A-Za-z0-9_-]{1,64}`. Label = 64, label_text; /// A campaign slug or key segment: label parts joined by `.`. @@ -738,8 +764,9 @@ unit_enums! { RunOperator { Operator, Mind } RunTurn { PersonaTurn, SelfRun, Sleep } MemorySort { Asked, Decided, Committed, Learned, Did, Idea, Rapport } - // What a desk answerer is, as the receiver stamped it (never a client's claim). - DeskAuthority { Operator, Decider } + // The role the verified account holds, an attribution copied from Bifrost's state (CultLib PR #72, + // fork-card.md). It confers nothing: whether an answerer may decide is the decider list's, never this. + DeskAuthority { Operator, Steward, Member } // Why a desk answer was refused and stored: binding spec 1.3 steps 4-7 and R1. DeskRefusal { StaleFork, NotADecider, ForkClosed, ChoiceInvalid, BadRetraction, AlreadyRetracted } } @@ -843,13 +870,20 @@ value_types! { promises: Vec[64], } /// One answer the fork desk's receiver passed to the Mind, refused ones included. - /// `answerer` and `authority` are stamped by the Mind from the verified caller and - /// are absent from the request type; `answered_at` is the client's claim kept as - /// written, display only: order is `sequence`, the receiver's order. `sequence` is - /// per question and is the last part of the key (the answer id). + /// `answerer` and `authority` (an attribution, never a decision) are stamped by the + /// Mind from the verified caller and are absent from the request type; `answered_at` + /// is the client's claim kept as written, display only: order is `sequence`, the + /// receiver's order. `received_at` is the Mind's admission time (UTC) and + /// `claim_in_future` is set when `answered_at` is more than five minutes after it. + /// `request_id` is the client's retry token and `body_sha256` the digest of the + /// request it came with: the pair that makes a retry the same answer, never a new + /// one; the key is `(answerer, request_id)`. A record holds no card: a replayed + /// `stale_fork` is shown the current card. `sequence` is per question and is the + /// last part of the key (the answer id). pub struct PipelineDeskAnswer { - campaign: Slug, fork: Short, sequence: u32, options_hash: Sha256Hex, choice: Option