From 66e6fe8d7d0324405301aacd9548209e5d857f43 Mon Sep 17 00:00:00 2001 From: Eureka Date: Wed, 11 Nov 2026 17:03:12 +0000 Subject: [PATCH 1/4] Desk feed door: list/read wire contract and golden fixtures for Bifrost docs/desk-feed-door.md states the list and read wire already on main (frame, CultNet envelope, named MessagePack payload), the per-Mind peer-checked socket, 503 for the addressed Mind only, the raw-answerer trust boundary (Bifrost masks), raised_at as ruled by CultLib (card bytes not pinned until the re-pinned card vectors land) and the planned listing gate (default not listed). crates/huginn-mind/tests/desk_feed_fixtures.rs writes the fixtures with Huginn's own encoders and checks them; HUGINN_WRITE_DESK_FIXTURES=1 rewrites. No card bytes in any fixture. Co-Authored-By: Claude Sonnet 5.5 --- .../huginn-mind/tests/desk_feed_fixtures.rs | 184 ++++++++++++++++++ .../tests/fixtures/desk-feed/README.md | 33 ++++ .../desk-feed/error.response.frame.bin | Bin 0 -> 119 bytes .../desk-feed/error.response.frame.hex | 1 + .../desk-feed/failure.response.frame.bin | Bin 0 -> 372 bytes .../desk-feed/failure.response.frame.hex | 1 + .../desk-feed/failure.response.payload.hex | 1 + .../failure.response.payload.msgpack | 1 + .../desk-feed/list-counts.request.frame.bin | Bin 0 -> 278 bytes .../desk-feed/list-counts.request.frame.hex | 1 + .../desk-feed/list-counts.request.payload.hex | 1 + .../list-counts.request.payload.msgpack | Bin 0 -> 72 bytes .../desk-feed/list-counts.response.frame.bin | Bin 0 -> 406 bytes .../desk-feed/list-counts.response.frame.hex | 1 + .../list-counts.response.payload.hex | 1 + .../list-counts.response.payload.msgpack | 1 + .../desk-feed/list-empty.response.frame.bin | Bin 0 -> 406 bytes .../desk-feed/list-empty.response.frame.hex | 1 + .../desk-feed/list-empty.response.payload.hex | 1 + .../list-empty.response.payload.msgpack | Bin 0 -> 132 bytes .../list-open-cursor.request.frame.bin | Bin 0 -> 282 bytes .../list-open-cursor.request.frame.hex | 1 + .../list-open-cursor.request.payload.hex | 1 + .../list-open-cursor.request.payload.msgpack | 1 + .../desk-feed/list-open.request.frame.bin | Bin 0 -> 278 bytes .../desk-feed/list-open.request.frame.hex | 1 + .../desk-feed/list-open.request.payload.hex | 1 + .../list-open.request.payload.msgpack | 1 + .../list-too-large.response.frame.bin | Bin 0 -> 519 bytes .../list-too-large.response.frame.hex | 1 + .../list-too-large.response.payload.hex | 1 + .../list-too-large.response.payload.msgpack | 1 + .../desk-feed/read-unknown.response.frame.bin | Bin 0 -> 365 bytes .../desk-feed/read-unknown.response.frame.hex | 1 + .../read-unknown.response.payload.hex | 1 + .../read-unknown.response.payload.msgpack | 1 + .../fixtures/desk-feed/read.request.frame.bin | Bin 0 -> 313 bytes .../fixtures/desk-feed/read.request.frame.hex | 1 + .../desk-feed/read.request.payload.hex | 1 + .../desk-feed/read.request.payload.msgpack | 1 + .../desk-feed/record-whole.payload.hex | 1 + .../desk-feed/record-whole.payload.msgpack | Bin 0 -> 1072 bytes .../desk-feed/unavailable.response.frame.bin | Bin 0 -> 314 bytes .../desk-feed/unavailable.response.frame.hex | 1 + .../unavailable.response.payload.hex | 1 + .../unavailable.response.payload.msgpack | 1 + docs/desk-feed-door.md | 145 ++++++++++++++ 47 files changed, 392 insertions(+) create mode 100644 crates/huginn-mind/tests/desk_feed_fixtures.rs create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/README.md create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/error.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/error.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/failure.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/failure.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/failure.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/failure.response.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open-cursor.request.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open-cursor.request.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open-cursor.request.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open-cursor.request.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open.request.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open.request.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open.request.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-open.request.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.msgpack create mode 100644 docs/desk-feed-door.md diff --git a/crates/huginn-mind/tests/desk_feed_fixtures.rs b/crates/huginn-mind/tests/desk_feed_fixtures.rs new file mode 100644 index 0000000..e366af9 --- /dev/null +++ b/crates/huginn-mind/tests/desk_feed_fixtures.rs @@ -0,0 +1,184 @@ +//! The desk feed's golden fixtures: the bytes Bifrost writes to a Mind's desk socket for a list or +//! a read, and the bytes Huginn writes back, built here with Huginn's own encoders so the files +//! are what the door speaks. `docs/desk-feed-door.md` is the contract they pin; the README in the +//! fixtures directory lists them. +//! +//! The card (`gamecult.fork.card.v0`) is in no fixture: it waits on CultLib's re-pinned card +//! vectors for `raised_at`. Every response pinned here carries no entry. +//! +//! `cargo test -p huginn-mind --test desk_feed_fixtures` compares every file with what the +//! encoders produce now. `HUGINN_WRITE_DESK_FIXTURES=1` rewrites them instead. + +use std::path::PathBuf; + +use cultnet_rs::{CultNetMessage, CultNetWireContract, encode_cultnet_message_to_vec, encode_frame}; +use huginn_mind::MindRefusal; +use huginn_mind::desk_wire::{ + AnswerSchema, DeskRequest, DeskResponse, ForkCursor, ForkDeskAnswer, ForkLimit, ForkList, ForkRecord, ForkRefusal, ForkRefusalCode, + ListSchema, ListStatus, RecordSchema, +}; +use huginn_mind::envelope::{OperationFailure, encode_desk_failure, encode_desk_request, encode_desk_response}; +use huginn_mind::eureka_pipeline::{DeskAuthority, Short, Slug}; + +const RUNTIME: &str = "huginn-eureka"; +const FORK: &str = "eureka-substrate:question:desk-fork-audience"; + +fn s(text: &str) -> Short { + Short(text.into()) +} + +fn forks(status: Option, cursor: Option, limit: u8) -> DeskRequest { + DeskRequest::Forks { + mind: Slug("eureka".into()), + subject: s("7"), + deciders: vec![s("7"), s("12")], + status, + cursor: cursor.and_then(ForkCursor::new), + limit: ForkLimit::new(limit).unwrap(), + } +} + +fn read() -> DeskRequest { + DeskRequest::Fork { mind: Slug("eureka".into()), subject: s("7"), deciders: vec![s("7"), s("12")], fork_id: FORK.into() } +} + +fn list(open_count: u64, recorded_count: u64) -> DeskResponse { + DeskResponse::Forks { + list: ForkList { + schema_version: ListSchema::V0, + mind: "eureka".into(), + stamp: "s10190".into(), + open_count, + recorded_count, + forks: Vec::new(), + next_cursor: None, + }, + as_of: 10190, + } +} + +fn refusal(code: ForkRefusalCode, error: &str) -> DeskResponse { + DeskResponse::NotStored(ForkRefusal { schema_version: huginn_mind::desk_wire::RefusalSchema::V0, code, error: error.into() }) +} + +fn answer(id: &str, outcome: &str, of: Option<&str>, answerer: &str, note: &str, received_at: &str) -> ForkDeskAnswer { + ForkDeskAnswer { + schema_version: AnswerSchema::V0, + answer_id: id.into(), + fork_id: FORK.into(), + outcome: outcome.into(), + of: of.map(str::to_string), + retracts: None, + code: None, + options_hash: "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48".into(), + choice: Some("campaign-listing".into()), + note: Some(note.into()), + answered_at: received_at.into(), + received_at: received_at.into(), + claim_in_future: false, + answerer: answerer.into(), + authority: DeskAuthority::Member, + request_id: format!("00000000-0000-4000-8000-00000000000{}", &id[1..]), + } +} + +/// A fork's record with a counted answer and a decider's revision, answerer ids raw: the door sends +/// them raw and Bifrost masks them for a reader who is not a decider. +fn record() -> ForkRecord { + ForkRecord { + schema_version: RecordSchema::V0, + fork_id: FORK.into(), + options_hash: "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48".into(), + counted_answer_id: Some("n1".into()), + answers: vec![ + answer("n1", "Counted", None, "7", "List by campaign.", "2026-11-11T17:00:00Z"), + answer("n2", "Revision", Some("n1"), "12", "Agreed, campaign grain.", "2026-11-11T17:05:00Z"), + ], + } +} + +/// The named MessagePack payload a CultNet operation message carries (base64 in the envelope). +fn payload(message: &CultNetMessage) -> Vec { + use base64::Engine; + let (CultNetMessage::OperationRequest { payload, .. } | CultNetMessage::OperationResponse { payload, .. }) = message else { + panic!("an operation message") + }; + base64::engine::general_purpose::STANDARD.decode(payload).unwrap() +} + +fn frame(message: &CultNetMessage) -> Vec { + encode_frame(&encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0).unwrap()).unwrap() +} + +fn hex(bytes: &[u8]) -> String { + let mut text: String = bytes.iter().map(|byte| format!("{byte:02x}")).collect(); + text.push('\n'); + text +} + +/// Every fixture: its file stem and its bytes. A message gives `.frame.bin`, `.frame.hex` and, +/// for an operation message, `.payload.msgpack` and `.payload.hex`; a bare payload gives the last two. +fn fixtures() -> Vec<(String, Vec)> { + let mut files = Vec::new(); + let mut message = |name: &str, message: CultNetMessage| { + let framed = frame(&message); + files.push((format!("{name}.frame.bin"), framed.clone())); + files.push((format!("{name}.frame.hex"), hex(&framed).into_bytes())); + if matches!(message, CultNetMessage::OperationRequest { .. } | CultNetMessage::OperationResponse { .. }) { + let inner = payload(&message); + files.push((format!("{name}.payload.msgpack"), inner.clone())); + files.push((format!("{name}.payload.hex"), hex(&inner).into_bytes())); + } + }; + message("list-open.request", encode_desk_request("m-1", &forks(Some(ListStatus::Open), None, 50), None).unwrap()); + message("list-open-cursor.request", encode_desk_request("m-2", &forks(Some(ListStatus::Open), Some(42), 50), None).unwrap()); + message("list-counts.request", encode_desk_request("m-3", &forks(Some(ListStatus::Open), None, 0), None).unwrap()); + message("read.request", encode_desk_request("m-4", &read(), None).unwrap()); + message("list-empty.response", encode_desk_response("m-1", "forks", &list(0, 0), RUNTIME).unwrap()); + message("list-counts.response", encode_desk_response("m-3", "forks", &list(3, 5), RUNTIME).unwrap()); + message( + "read-unknown.response", + encode_desk_response("m-4", "fork", &refusal(ForkRefusalCode::ForkUnknown, "fork_unknown: no such fork"), RUNTIME).unwrap(), + ); + message( + "list-too-large.response", + encode_desk_response( + "m-1", + "forks", + &refusal(ForkRefusalCode::ForkTooLarge, &format!("fork_too_large: fork {FORK} at order 41 is past the page budget by itself; continue with cursor c41")), + RUNTIME, + ) + .unwrap(), + ); + message( + "unavailable.response", + encode_desk_response("m-1", "forks", &DeskResponse::Refused(MindRefusal::Unavailable { detail: "the mind store is not readable".into() }), RUNTIME).unwrap(), + ); + message( + "failure.response", + encode_desk_failure("m-1", "forks", &OperationFailure { code: "payload-not-a-request".into(), message: "field Forks.limit: out-of-range".into() }, RUNTIME), + ); + message("error.response", CultNetMessage::Error { error: "huginn.desk refuses a message_id over 256 bytes".into(), code: None, details: None }); + let whole = rmp_serde::to_vec_named(&record()).unwrap(); + files.push(("record-whole.payload.msgpack".into(), whole.clone())); + files.push(("record-whole.payload.hex".into(), hex(&whole).into_bytes())); + files +} + +#[test] +fn desk_feed_fixtures_are_what_the_encoders_write() { + let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/desk-feed"); + let write = std::env::var_os("HUGINN_WRITE_DESK_FIXTURES").is_some(); + if write { + std::fs::create_dir_all(&dir).unwrap(); + } + for (name, bytes) in fixtures() { + let path = dir.join(&name); + if write { + std::fs::write(&path, &bytes).unwrap(); + } else { + let held = std::fs::read(&path).unwrap_or_else(|error| panic!("{}: {error}", path.display())); + assert!(held == bytes, "{name} differs from what the encoders write now"); + } + } +} diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/README.md b/crates/huginn-mind/tests/fixtures/desk-feed/README.md new file mode 100644 index 0000000..ff8dca9 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/README.md @@ -0,0 +1,33 @@ +# Desk feed golden fixtures + +The bytes on a Mind's desk socket for a list or a read. The contract is `docs/desk-feed-door.md`. +`crates/huginn-mind/tests/desk_feed_fixtures.rs` writes these files with Huginn's own encoders and +checks them on every `cargo test -p huginn-mind --test desk_feed_fixtures`. Set +`HUGINN_WRITE_DESK_FIXTURES=1` to rewrite them. Do not edit them by hand. + +Each case has up to four files: + +- `.frame.bin` and `.frame.hex`: the whole frame on the socket (u32 big-endian length, + then the CultNet message). +- `.payload.msgpack` and `.payload.hex`: the base64-decoded `payload` of an operation + message (named MessagePack). The `record-*` cases are bare payload fragments with no frame. + +Hex is lowercase with one trailing newline. + +| case | direction | content | +|---|---|---| +| `list-open.request` | Bifrost to Huginn | `Forks`, status open, no cursor, limit 50 | +| `list-open-cursor.request` | Bifrost to Huginn | `Forks`, status open, cursor `c42`, limit 50 | +| `list-counts.request` | Bifrost to Huginn | `Forks`, status open, limit 0 (counts and stamp only) | +| `read.request` | Bifrost to Huginn | `Fork` for one fork id | +| `list-empty.response` | Huginn to Bifrost | an empty Mind, or nothing listed to this reader: counts 0, `forks: []` | +| `list-counts.response` | Huginn to Bifrost | the limit-0 answer: open 3, recorded 5, `forks: []` | +| `read-unknown.response` | Huginn to Bifrost | `NotStored` `fork_unknown` (Bifrost: 404) | +| `list-too-large.response` | Huginn to Bifrost | `NotStored` `fork_too_large` naming the cursor to continue (Bifrost: 422) | +| `unavailable.response` | Huginn to Bifrost | `Refused` `Unavailable` (Bifrost: 503 for this Mind) | +| `failure.response` | Huginn to Bifrost | operation failure, `payloadSchema` `gamecult.cultnet.operation_failure.v1` (Bifrost: 503) | +| `error.response` | Huginn to Bifrost | CultNet `Error` message, no payload (Bifrost: 503) | +| `record-whole` | fragment | `gamecult.fork.record.v0` with a counted answer and a revision, answerer ids and notes raw, as the door sends them (Bifrost masks for a reader who is not a decider) | + +No case carries a card (`gamecult.fork.card.v0`). The card's bytes wait on CultLib's re-pinned card +vectors for `raised_at`, so no entry-bearing list page or read is pinned yet. diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/error.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/error.response.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..e3f3cb2d117e33000059756e20c01ffc1c540fa3 GIT binary patch literal 119 zcmW;8!3hE}5CzaJWh#gwXbo22RbukZ4(?81X0l)bcA|I?!4lGj-3Y$Z8-~GdZ%ZTQ zcz_yL`J^_vzynmPhGqTu=ertPVUdtw7gC&?!6?!UvsiHCOm%_MX1k-))nPBK%~{Hx P#rr+gjQ-5<$dmOS=glw< literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/error.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/error.response.frame.hex new file mode 100644 index 0000000..fffbf7c --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/error.response.frame.hex @@ -0,0 +1 @@ +0000007385ad736368656d6156657273696f6eb063756c746e65742e6572726f722e7630a56572726f72d92f687567696e6e2e6465736b20726566757365732061206d6573736167655f6964206f76657220323536206279746573ab726f7574696e6748696e74c0a4636f6465c0a764657461696c73c0 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/failure.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/failure.response.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..3937de00bbc4ece45f4e7c915dba35f8cb34f797 GIT binary patch literal 372 zcmZXQ%}T>S6ou=uMHXQL0Y5qj*4EO==NO*5X`HGQC*OVo_94 zQYLYo4+lxb6|G0AF4MT^UdnzYrrsOPyzk5<(aq_uO&Mrzgw)x4+o@zc4^oD3eAKka zHD7>Y`@@VW;I5$X`)Hu>ulU`Z$--F(t6qaY^aL#Wk3RpS7O*rLJ=@(N*h+H0!*<~^ ksdXp-^Bo#zIgFhqsl3+wY619woV_41a`F!f)75qk?zJK z!TNkY&N<&?a&h@QC~^)ex?wMB8Vsf;3oSSLbz`h>Z#C?t0d{<>AdAW%wzvRM-pj67 z6_Vw#gn6Vf$T+BM$5vs`M5$cQr6}_FNi<<#cHzF($!wjcJ}%Q9qpYh{;-gM_bl0+s zWDEK2)yKFLYBY@N>T~>61Bz)0j+TqVh}_|p6nt&FifipLa^k^K(~)bv-aokCG$ZRy a7T?&(R#WT!d7aVO;K=qIPwMagx_$uhiEPRM literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.frame.hex new file mode 100644 index 0000000..d0554ad --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.frame.hex @@ -0,0 +1 @@ +0000011287a96d6573736167654964a36d2d33a96f7065726174696f6ea5666f726b73a77061796c6f6164d9606761564762334a726334616b62576c755a4b5a6c64584a6c6132476e63335669616d566a644b45337147526c59326c6b5a584a7a6b7145336f6a4579706e4e30595852316336527663475675706d4e31636e4e766373436c62476c7461585141af7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b6687567696e6e2e6465736b5f726571756573742e7631ad736368656d6156657273696f6ebc63756c746e65742e6f7065726174696f6e5f726571756573742e7630a9736572766963654964ab687567696e6e2e6465736b diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.payload.hex new file mode 100644 index 0000000..60cb15a --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.payload.hex @@ -0,0 +1 @@ +81a5466f726b7386a46d696e64a6657572656b61a77375626a656374a137a8646563696465727392a137a23132a6737461747573a46f70656ea6637572736f72c0a56c696d697400 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.request.payload.msgpack new file mode 100644 index 0000000000000000000000000000000000000000..931ad8d5551e6dfba06304c62e3f992180624569 GIT binary patch literal 72 zcmZo%>Xu)WUEH=LH#09~S!!ufYIfrC;?kt7)Z~(d<||TClQUCNi;5>LG+$(Bw5+%! cv81$kNq#|U-m>J9b150x2U_l<_1+ewf8f+#Fg}PO7$=VW5dX%C z#4tO_+S%D>RVpVB_Ztbu;5l^JqpaGSBn6GLI8jftM04|!;+12i_ysc=$Vm1Fb{F!3Ps{|W3ZEg>d35`JF&nTbAO4S$`ext#p zi84@IHnwJu5ol9t`J%bV6lAr3QAgSO#(-YNw3PXF)V4E8t|^RjmBq4T+do!?Tm#3? PCc{~7UNNPRX25;`>583n literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.frame.hex new file mode 100644 index 0000000..26d91d8 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.frame.hex @@ -0,0 +1 @@ +0000019289a96d6573736167654964a36d2d33a96f7065726174696f6ea5666f726b73a77061796c6f6164d9b06761564762334a7263344b6b62476c7a6449657463324e6f5a573168566d567963326c766272566e5957316c593356736443356d62334a724c6d787063335175646a436b62576c755a4b5a6c64584a6c6132476c633352686258436d637a45774d546b77716d39775a57356659323931626e5144726e4a6c593239795a47566b58324e76645735304261566d62334a7263354372626d56346446396a64584a7a62334c417057467a5832396d7a53664faf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a86163636570746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.hex new file mode 100644 index 0000000..47af395 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.hex @@ -0,0 +1 @@ +81a5466f726b7382a46c69737487ad736368656d6156657273696f6eb567616d6563756c742e666f726b2e6c6973742e7630a46d696e64a6657572656b61a57374616d70a6733130313930aa6f70656e5f636f756e7403ae7265636f726465645f636f756e7405a5666f726b7390ab6e6578745f637572736f72c0a561735f6f66cd27ce diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.msgpack new file mode 100644 index 0000000..a040d33 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-counts.response.payload.msgpack @@ -0,0 +1 @@ +�¥Forks‚¤list‡­schemaVersionµgamecult.fork.list.v0¤mind¦eureka¥stamp¦s10190ªopen_count®recorded_count¥forks�«next_cursorÀ¥as_ofÍ'Î \ No newline at end of file diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..5184d4255b4ec855e752251b1ac5c6162f698341 GIT binary patch literal 406 zcmYk2%TB^T6o&PIAhe|>U6KYGE@A*PE!~*89BApxw9d3e`UY-&1>=Pnf^p-@2k~vJ zNDRA^e1HD@=Q}$)f4JWY&>AnH&mKj?*rpjMoX4qrTBIt`FB#tO6tnO5B}N{x28yqbaJCW zXQ|ZCERCH$U<69nTE1w(a|wC#U(`vqwbh`?nAS4iPTKZ5Efj@mA@f+&Z1=|+P$)?7 Pv(9l|=vPcBWI3=O&wrg* literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.frame.hex new file mode 100644 index 0000000..df49d00 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.frame.hex @@ -0,0 +1 @@ +0000019289a96d6573736167654964a36d2d31a96f7065726174696f6ea5666f726b73a77061796c6f6164d9b06761564762334a7263344b6b62476c7a6449657463324e6f5a573168566d567963326c766272566e5957316c593356736443356d62334a724c6d787063335175646a436b62576c755a4b5a6c64584a6c6132476c633352686258436d637a45774d546b77716d39775a57356659323931626e5141726e4a6c593239795a47566b58324e7664573530414b566d62334a7263354372626d56346446396a64584a7a62334c417057467a5832396d7a53664faf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a86163636570746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.payload.hex new file mode 100644 index 0000000..3a4ef1e --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.payload.hex @@ -0,0 +1 @@ +81a5466f726b7382a46c69737487ad736368656d6156657273696f6eb567616d6563756c742e666f726b2e6c6973742e7630a46d696e64a6657572656b61a57374616d70a6733130313930aa6f70656e5f636f756e7400ae7265636f726465645f636f756e7400a5666f726b7390ab6e6578745f637572736f72c0a561735f6f66cd27ce diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/list-empty.response.payload.msgpack new file mode 100644 index 0000000000000000000000000000000000000000..d6dd07be262108c9fdaa52ba0f1fa2e152d7b6c4 GIT binary patch literal 132 zcmZo%>Xu)WUEH)JC$qSueQj}aMrv+iSZYymW`5q*^u*lMX2J&+2$GJ_?# znRzM8QcH_cvlEvVmn7yEEGsrNFtjvSm0ysW7oVJ8npeWGt|&D*zbGX&1b%s7lKAA(qT>9b14|Q&1}oi zsjkn*IpHndIRqDn^Wk~+DH zWf{sI@|)>>Sql_u##QyPd}{&4x&%wR?QubF@jwlH9)^l5=tu!jfqW(h5|1;F~aLSR!{zDe^3O6ipbILpanrUhJ~O$92*pOS@XdKI*tfcO?rZ zTgbM<`?M5lG`!N~$MmZP6!QWctyjkhxxqat_}q78rM1V&i91V8N3QjH|LA_xl&m{h Zd|@YBO|AFm^^Yz_N4BrNnoe;*5u+rH0l!qnc7R|TWE&dTLDy0BC#s^6`qHOv zq^l`26IJterE_%er~BR9yngffEV(fx=K=d7C+@j!xF)u&)z?Fi~7KZ~{ zg$0BAIZcqD-ge9|n`4Lu(z{RoI`&-*g~((i0_Z!zV|Ix8;BIpYpcsPHvY_53XRr{D zGY0!SRj9%Mjc#~5It~TM;G@Y?uxJ?+EsOU#1HvKimPeL^L*@l}I&4l4*H^t)v}UEs ze_bEj5L!(#V)%S^xhgwaa7lg_ZC)xhW!zLBH8)MI44-!H#XMoewHbT(Lt9}Lx6}X4 vyl|bf;kqm7*xt_<+a65aYRb2*vZWl^<(W-*t9eDf8B1(yerSH=)N=L@34Xy# literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.hex new file mode 100644 index 0000000..60505ec --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.hex @@ -0,0 +1 @@ +0000020389a96d6573736167654964a36d2d31a96f7065726174696f6ea5666f726b73a77061796c6f6164da012067616c4f62335254644739795a57534472584e6a6147567459565a6c636e4e70623236345a3246745a574e31624851755a6d3979617935795a575a31633246734c6e597770474e765a4757755a6d39796131393062323966624746795a32576c5a584a7962334c5a69575a76636d74666447397658327868636d646c4f69426d62334a7249475631636d56725953317a64574a7a64484a68644755366358566c63335270623234365a47567a6179316d62334a724c5746315a476c6c626d4e6c49474630494739795a4756794944517849476c7a49484268633351676447686c494842685a325567596e566b5a32563049474a3549476c30633256735a6a73675932397564476c756457556764326c306143426a64584a7a62334967597a5178af7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.hex new file mode 100644 index 0000000..5b6bc62 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.hex @@ -0,0 +1 @@ +81a94e6f7453746f72656483ad736368656d6156657273696f6eb867616d6563756c742e666f726b2e7265667573616c2e7630a4636f6465ae666f726b5f746f6f5f6c61726765a56572726f72d989666f726b5f746f6f5f6c617267653a20666f726b20657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365206174206f7264657220343120697320706173742074686520706167652062756467657420627920697473656c663b20636f6e74696e7565207769746820637572736f7220633431 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.msgpack new file mode 100644 index 0000000..f294566 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.payload.msgpack @@ -0,0 +1 @@ +�©NotStoredƒ­schemaVersion¸gamecult.fork.refusal.v0¤code®fork_too_large¥errorÙ‰fork_too_large: fork eureka-substrate:question:desk-fork-audience at order 41 is past the page budget by itself; continue with cursor c41 \ No newline at end of file diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..40186373edea6957be586ae23ccf0b9eb0da45b5 GIT binary patch literal 365 zcmYk2TS~(~7=`PCT1?Xx^XjEhk%+A%c3xyIf74uMCd@w*$qsZKidrNTeDTSJxEn() z(&z8vaL(!V4zj1U1Voy`g?N_z(OOqvsLix`S!z?fRkSHJ6+e$DEw7nByA@&3kYs*p z?xLKAv0cQZ) literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.frame.hex new file mode 100644 index 0000000..4299eb0 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.frame.hex @@ -0,0 +1 @@ +0000016989a96d6573736167654964a36d2d34a96f7065726174696f6ea4666f726ba77061796c6f6164d98867616c4f62335254644739795a57534472584e6a6147567459565a6c636e4e70623236345a3246745a574e31624851755a6d3979617935795a575a31633246734c6e597770474e765a4757735a6d397961313931626d74756233647570575679636d3979756d5a76636d746664573572626d3933626a6f67626d38676333566a6143426d62334a72af7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.hex new file mode 100644 index 0000000..2634d13 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.hex @@ -0,0 +1 @@ +81a94e6f7453746f72656483ad736368656d6156657273696f6eb867616d6563756c742e666f726b2e7265667573616c2e7630a4636f6465ac666f726b5f756e6b6e6f776ea56572726f72ba666f726b5f756e6b6e6f776e3a206e6f207375636820666f726b diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.msgpack new file mode 100644 index 0000000..fbd6335 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read-unknown.response.payload.msgpack @@ -0,0 +1 @@ +�©NotStoredƒ­schemaVersion¸gamecult.fork.refusal.v0¤code¬fork_unknown¥errorºfork_unknown: no such fork \ No newline at end of file diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..8860f1035707f150f8a9ba6d0520e2c1af36c7bf GIT binary patch literal 313 zcmYk2%}T>S9K`DbH6+>!J$Te$TO+h_H`$B)ySpaoWS38rVk2T_nBMeyLs7x8T@ z64c``!!W-Y435T+JIS4cf?u*HIUMb@;TC+Q)$^UU;>AE$YhYgw1uWv#$<&s!g$Fzq zMxuyHWOHC!hNl~XlTfEzlNhZ5FKVi;mj_*4r87n3q=RT;NFp4`P)D#Y=UEcvDha8) z2uW)08Ae+c*X?R;1j!OR$E2mw`8-1cFlzGSo3pom&AFnQRjPRJryHPRxB|ydNBcgq zq$QW|Q8q=TRKU0sx0Y`j?tIWjd-vlaZruU&lQy+i+y}qtf6;K~xNR$Xc<1$B*EflA BdQ<=a literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex new file mode 100644 index 0000000..cfb4b1f --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex @@ -0,0 +1 @@ +0000013587a96d6573736167654964a36d2d34a96f7065726174696f6ea4666f726ba77061796c6f6164d9846761524762334a72684b52746157356b706d5631636d56725961647a64574a715a574e306f54656f5a47566a6157526c636e4f536f5465694d544b6e5a6d3979613139705a4e6b735a5856795a5774684c584e31596e4e30636d46305a5470786457567a64476c76626a706b5a584e724c575a76636d73745958566b615756755932553daf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b6687567696e6e2e6465736b5f726571756573742e7631ad736368656d6156657273696f6ebc63756c746e65742e6f7065726174696f6e5f726571756573742e7630a9736572766963654964ab687567696e6e2e6465736b diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex new file mode 100644 index 0000000..8785434 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex @@ -0,0 +1 @@ +81a4466f726b84a46d696e64a6657572656b61a77375626a656374a137a8646563696465727392a137a23132a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack new file mode 100644 index 0000000..38683f4 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack @@ -0,0 +1 @@ +�¤Fork„¤mind¦eureka§subject¡7¨deciders’¡7¢12§fork_idÙ,eureka-substrate:question:desk-fork-audience \ No newline at end of file diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex new file mode 100644 index 0000000..619d820 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex @@ -0,0 +1 @@ +85ad736368656d6156657273696f6eb767616d6563756c742e666f726b2e7265636f72642e7630a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438b1636f756e7465645f616e737765725f6964a26e31a7616e737765727392de0010ad736368656d6156657273696f6eb767616d6563756c742e6465736b5f616e737765722e7630a9616e737765725f6964a26e31a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365a76f7574636f6d65a7436f756e746564a26f66c0a87265747261637473c0a4636f6465c0ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438a663686f696365b063616d706169676e2d6c697374696e67a46e6f7465b14c6973742062792063616d706169676e2eab616e7377657265645f6174b4323032362d31312d31315431373a30303a30305aab72656365697665645f6174b4323032362d31312d31315431373a30303a30305aaf636c61696d5f696e5f667574757265c2a8616e737765726572a137a9617574686f72697479a64d656d626572aa726571756573745f6964d92430303030303030302d303030302d343030302d383030302d303030303030303030303031de0010ad736368656d6156657273696f6eb767616d6563756c742e6465736b5f616e737765722e7630a9616e737765725f6964a26e32a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365a76f7574636f6d65a85265766973696f6ea26f66a26e31a87265747261637473c0a4636f6465c0ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438a663686f696365b063616d706169676e2d6c697374696e67a46e6f7465b74167726565642c2063616d706169676e20677261696e2eab616e7377657265645f6174b4323032362d31312d31315431373a30353a30305aab72656365697665645f6174b4323032362d31312d31315431373a30353a30305aaf636c61696d5f696e5f667574757265c2a8616e737765726572a23132a9617574686f72697479a64d656d626572aa726571756573745f6964d92430303030303030302d303030302d343030302d383030302d303030303030303030303032 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.msgpack new file mode 100644 index 0000000000000000000000000000000000000000..cc2e81a58f4c8fcda26be2c409c514c8f89d9873 GIT binary patch literal 1072 zcmc(dO-chX6vsV+N9e+(Qzn_O+Lhu~HzF?F44LH3OlT+3d{jJu7w`ravGofT1h>Uo zxbZ9|whAt)Pz6Isc;vnC{_pocxz)6WhU|gTT#BcPG(c_Nv`eXaZ3R>+)*gf-ALg9B ztb$e0BZ0PsHi{VN9$L`Gm3A5EUf}ZrVi^ZP;YJ?#7oFEc*DvcCTT4pX$ygHM7>6lB zCB!J9h(U=n62)MXk?*3?pQb9}K4O#$jk!MhKX7F+TYw)cej*oFxInj3xtGaQJ@ literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..bbb82db82832887cbfab45966737a99a39f386b6 GIT binary patch literal 314 zcmYk1O-{ow6okux5+#Lp*G&-(=>mxcC5ku5u^(x0Y)5`}q;dn!f%u6~REZ5M4#I5+ zB|=?}zM0X?WO6cp>?9!45@P-&y|B{_7-}o6p6|2~_}b82tttP$DJf4#MnX1CMM-U1 z$kjB@YRt1Wl01?mN`qhBs*NVe($Z`u;>)X`I9=CqR6oSgtmWs+9m!VWkhxwg_G9~n zVwzW~{1~@1lnJk(2=lXkv?Oc`l74PoSt;dnKyhmT8?6xhHtg|$Q!qG8|6;DT3atNs s=Exg#2&S#raQOYGZI9X+1{+sFMJunOW2XCKzk^lez literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.frame.hex new file mode 100644 index 0000000..c2e1375 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.frame.hex @@ -0,0 +1 @@ +0000013689a96d6573736167654964a36d2d31a96f7065726174696f6ea5666f726b73a77061796c6f6164d954676164535a575a316332566b67617456626d463259576c7359574a735a59476d5a47563059576c73766e526f5a5342746157356b49484e3062334a6c49476c7a49473576644342795a57466b59574a735a513d3daf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.hex new file mode 100644 index 0000000..a1e1be5 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.hex @@ -0,0 +1 @@ +81a75265667573656481ab556e617661696c61626c6581a664657461696cbe746865206d696e642073746f7265206973206e6f74207265616461626c65 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.msgpack new file mode 100644 index 0000000..cbfe1bc --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/unavailable.response.payload.msgpack @@ -0,0 +1 @@ +�§Refused�«Unavailable�¦detail¾the mind store is not readable \ No newline at end of file diff --git a/docs/desk-feed-door.md b/docs/desk-feed-door.md new file mode 100644 index 0000000..98ad947 --- /dev/null +++ b/docs/desk-feed-door.md @@ -0,0 +1,145 @@ +# Desk feed door: the list and read wire between Bifrost and a Mind's Huginn + +Status, 2026-11-11: the list and read wire described here is on `main` (`DeskRequest::Forks`, +`DeskRequest::Fork`, `DeskResponse::Forks`, `DeskResponse::Fork`; `crates/huginn-mind/src/desk_wire.rs` +and `desk_wire/reads.rs`; the socket is `crates/huginn-daemon/src/desk_door.rs`). No running daemon +binds a desk socket yet (follow-up `eureka-substrate:follow_up:desk-door-ops-root-run`). Two parts +are planned and not built: `raised_at` on the card, and the listing gate that decides which forks a +reader sees. They are cut specs `cut-desk-feed-raised-at` and `cut-desk-feed-listing` in the +`eureka-substrate` campaign. + +This page is the wire contract for the consumer, Bifrost's `UnixSocketDeskDoor` (Bifrost PR #67, +`docs/desk-feed-design.md`). The golden bytes are in `crates/huginn-mind/tests/fixtures/desk-feed/`. +Huginn's own encoders write them (`crates/huginn-mind/tests/desk_feed_fixtures.rs`), so a decoder +that reads them reads the door. The document shapes inside the payload (`gamecult.fork.list.v0`, +`gamecult.fork.entry.v0`, `gamecult.fork.card.v0`, `gamecult.fork.record.v0`, +`gamecult.desk_answer.v0`, `gamecult.fork.refusal.v0`) are CultLib's +(`contracts/cultnet/governance/fork-card.md`) and are not restated here. + +## One socket per Mind + +- Each Mind's Huginn daemon binds its own socket, given by `--desk-socket ` and + `--desk-peer-uid ` (both or neither). The ops plan names `/run/huginn-desk/.sock`, mode + 0660, group `huginn-desk`. +- The kernel's peer credential is checked before a byte is read. Only the configured uid (Bifrost's) + is answered; any other peer is closed with nothing read or written. That check is the whole + authentication: Bifrost vouches for `subject` and `deciders`, and Huginn never sees the member's + token. +- Each connection carries one request frame and gets one reply frame, all within 1 second. A + retry is a new connection. Huginn answers each read from its live image at one snapshot. It keeps + no cache, and Bifrost needs none: the list's `stamp` (`s`) is the change signal. +- Huginn serves only its own Mind. A request whose `mind` is another instance is refused + (`Refused(...)`). + +## Framing and envelope + +``` +frame = u32 big-endian length || message +message = MessagePack map, CultNet schema v0 (cultnet-rs encode_cultnet_message_to_vec) +payload = message["payload"], standard base64 of named MessagePack (rmp_serde::to_vec_named) +``` + +- Request message: `schemaVersion` `cultnet.operation_request.v0`, `serviceId` `huginn.desk`, + `operation` `forks` (list) or `fork` (read), `payloadSchema` `huginn.desk_request.v1`, + `payloadEncoding` `messagepack-base64`, `messageId` (at most 256 bytes, echoed back). +- Response message: `cultnet.operation_response.v0`, the same `serviceId`, `operation` and + `messageId`, `status` `accepted` or `rejected`, and `sourceRuntimeId` (informational: do not + compare it). The `payloadSchema` is `huginn.desk_response.v1`, or + `gamecult.cultnet.operation_failure.v1` when the envelope or payload did not decode. +- Envelope keys are written in sorted order and absent optionals are omitted. Decode by key, not + by position. +- Frame bound: 65536 bytes each way. A reply past it is refused by name, never truncated. + +## Payloads + +Rust's externally tagged enums: a one-key map from the variant name to its fields. Every field is +written; `null` where absent. + +Request (`huginn.desk_request.v1`): + +| operation | payload | +|---|---| +| `forks` | `{"Forks": {mind, subject, deciders, status, cursor, limit}}`. `status` is `"open"`, `"recorded"` or nil (both); `cursor` is `"c"` (N from 1) or nil; `limit` 0..50, where 0 returns counts and stamp only. | +| `fork` | `{"Fork": {mind, subject, deciders, fork_id}}` | + +`deciders` is the Mind's admin-set list for this call: 1 to 64 distinct ids, sent on every call. +Huginn keeps no copy. `subject` is the verified Bifrost account id of the reader. The reads carry +no `authority`, and a payload that names it is refused whole. + +Response (`huginn.desk_response.v1`): + +| variant | meaning | Bifrost answers | +|---|---|---| +| `{"Forks": {list, as_of}}` | one page, `list` is `gamecult.fork.list.v0` | 200 | +| `{"Fork": {entry, as_of}}` | one fork, `entry` is `gamecult.fork.entry.v0` | 200 | +| `{"NotStored": refusal}` | `gamecult.fork.refusal.v0`: `fork_unknown`, or `fork_too_large` with the cursor to continue | 404 / 422 | +| `{"Refused": {"": {...}}}` | a Mind refusal (store fault, wrong instance, a response too large) | 503 `unavailable` | + +## Failure means 503 for that Mind only + +Each of these gives the request 503 `unavailable` with `Retry-After`. Only the Mind addressed is +affected, because each Mind has its own socket and the client asks per Mind: + +- the socket is absent, or refuses the connection (`ENOENT`, `ECONNREFUSED`), or the peer closes it + without a reply frame; +- no whole reply frame arrives within the deadline; +- the reply is a CultNet `Error` message (`cultnet.error.v0`, fixture `error.response`), an + operation failure (fixture `failure.response`), or a `Refused` payload (fixture + `unavailable.response`). + +An empty list is never served in place of a failure. `open_count: 0` means zero open forks. + +## Card fields (pending CultLib's re-pinned card vectors) + +`raised_at` is ruled by CultLib's steward (2026-11-11). It is a required card key whose value is an +RFC3339 string or nil, which takes the card from 16 to 17 keys in closed map-mode. Nil means unknown. +A string is the question's first-admission time, which never moves. It is outside `options_hash`, +which keeps its five members, so the hash vectors do not change. Huginn derives it from the +question's admission: the `committed_at` of the receipt that first admitted the question, or for an +imported question the source mind's `committed_at` (`imported_from.committed_at`). + +**No fixture here carries card bytes.** The card's key order and hex wait on the CultLib commit that +re-pins the card vectors with `raised_at`. Until then the card is opaque to these fixtures, and +every pinned response carries no entry. The entry-bearing fixtures (an open list page and a read +with `raised_at` as a string and as nil) are added by `cut-desk-feed-raised-at` against that +CultLib commit. + +## Trust boundary: what Huginn sends raw, what Bifrost does + +- Huginn sends every record whole over the peer-checked socket: every answer's `answerer` id and + every note, unmasked (fixture `record-whole`). The peer-uid check means only Bifrost's uid reads + them. A second reader of the socket would leak answerer ids and notes, so the socket must belong + to Bifrost's uid alone. +- Bifrost owns three things: the per-request membership check, the `desk.read` token, and + masking on what the door returns. It masks answerer ids for a reader who is not a decider (X3, + Heimdall 2026-11-11) and strips notes (ruling `desk-read-visibility-whole-record`, `DeskNotes`). + Huginn masks nothing. + +## Listing: which forks a reader sees (Huginn's; planned) + +Huginn owns audience and visibility, at its door. The card has no audience key (CultLib), and +Bifrost's feed applies no fork filter. + +- **Default: not listed.** A fork (a showable question) appears in a list or read for a reader who + is not on the call's `deciders` only while a `desk_listing` document for it is in force. Otherwise + the list omits it, it counts in neither `open_count` nor `recorded_count`, and a read of it + answers `fork_unknown`, the same bytes as a fork that does not exist. The counts and the cursor + are computed over the visible set, which is why the gate is Huginn's: a filter applied after + paging would make both wrong. +- **Deciders** see every showable fork whether or not it is listed (recommended in question + `eureka-substrate:question:desk-unlisted-to-deciders`), because they must see a fork to answer it. + The door compares the call's `subject` with its `deciders` to tell. The wire does not change. +- **The listing record** is planned kind `desk_listing`, key `:desk_listing:`, + fields `fork` (the question's id) and `reason` (one line: why members may read it). It is + admitted on the Mind's own door, so its receipt records who listed it and when. Unlisting is a + `Withdrawn` resolution on it, and relisting withdraws that resolution, which reopens it. Each + flip is a document with its own receipt, so the history is `huginn history `. No flag + is stored anywhere else. +- **Who may list or unlist** is question `eureka-substrate:question:desk-listing-authority`. The + recommended answer is the asking Mind herself, at her Self. +- **Open for Heimdall:** if Bifrost wants any part of the listing (an operator override at the + desk, a per-Mind default, a reason shown to members), that is his decision to raise. Nothing in + this plan puts listing state in Bifrost. + +The fixture `list-empty.response` is also what a reader who is not a decider gets from a Mind +with nothing listed. -- 2.47.3 From 650e5e262e5315c3c1b5ee5b300ee826a0af7880 Mon Sep 17 00:00:00 2001 From: Eureka Date: Wed, 11 Nov 2026 17:14:27 +0000 Subject: [PATCH 2/4] Desk feed door doc: read-form fork_too_large, Refused shapes, fork source, gate not live Soul pass on 66e6fe8: document and fixture the read form of fork_too_large (live on main; CultLib limitation 13 is stale), the ResponseTooLarge 503 and a unit-variant Refused; say that only operation messages have sorted keys, what failure diagnostics and the schema catalog reply are, where forks come from, and that the listing gate is not live (no receiver on a Mind with private questions before cut-desk-feed-listing). Fixtures use a held fork id, synthetic record. .gitattributes keeps .bin/.msgpack binary and .hex LF. Co-Authored-By: Claude Sonnet 5.5 --- .../huginn-mind/tests/desk_feed_fixtures.rs | 18 +++- .../tests/fixtures/desk-feed/.gitattributes | 3 + .../tests/fixtures/desk-feed/README.md | 10 +- ...list-response-too-large.response.frame.bin | Bin 0 -> 298 bytes ...list-response-too-large.response.frame.hex | 1 + ...st-response-too-large.response.payload.hex | 1 + ...esponse-too-large.response.payload.msgpack | Bin 0 -> 50 bytes .../list-too-large.response.frame.bin | Bin 519 -> 527 bytes .../list-too-large.response.frame.hex | 2 +- .../list-too-large.response.payload.hex | 2 +- .../list-too-large.response.payload.msgpack | 2 +- .../read-too-large.response.frame.bin | Bin 0 -> 477 bytes .../read-too-large.response.frame.hex | 1 + .../read-too-large.response.payload.hex | 1 + .../read-too-large.response.payload.msgpack | 1 + .../fixtures/desk-feed/read.request.frame.bin | Bin 313 -> 317 bytes .../fixtures/desk-feed/read.request.frame.hex | 2 +- .../desk-feed/read.request.payload.hex | 2 +- .../desk-feed/read.request.payload.msgpack | 2 +- .../desk-feed/record-whole.payload.hex | 2 +- .../desk-feed/record-whole.payload.msgpack | Bin 1072 -> 1071 bytes .../desk-feed/refused-unit.response.frame.bin | Bin 0 -> 266 bytes .../desk-feed/refused-unit.response.frame.hex | 1 + .../refused-unit.response.payload.hex | 1 + .../refused-unit.response.payload.msgpack | 1 + docs/desk-feed-door.md | 95 +++++++++++++++--- 26 files changed, 121 insertions(+), 27 deletions(-) create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/.gitattributes create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.msgpack create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.bin create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.hex create mode 100644 crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.msgpack diff --git a/crates/huginn-mind/tests/desk_feed_fixtures.rs b/crates/huginn-mind/tests/desk_feed_fixtures.rs index e366af9..9c6b64a 100644 --- a/crates/huginn-mind/tests/desk_feed_fixtures.rs +++ b/crates/huginn-mind/tests/desk_feed_fixtures.rs @@ -14,14 +14,16 @@ use std::path::PathBuf; use cultnet_rs::{CultNetMessage, CultNetWireContract, encode_cultnet_message_to_vec, encode_frame}; use huginn_mind::MindRefusal; use huginn_mind::desk_wire::{ - AnswerSchema, DeskRequest, DeskResponse, ForkCursor, ForkDeskAnswer, ForkLimit, ForkList, ForkRecord, ForkRefusal, ForkRefusalCode, + fork_too_large_read, AnswerSchema, DeskRequest, DeskResponse, ForkCursor, ForkDeskAnswer, ForkLimit, ForkList, ForkRecord, ForkRefusal, ForkRefusalCode, ListSchema, ListStatus, RecordSchema, }; use huginn_mind::envelope::{OperationFailure, encode_desk_failure, encode_desk_request, encode_desk_response}; use huginn_mind::eureka_pipeline::{DeskAuthority, Short, Slug}; const RUNTIME: &str = "huginn-eureka"; -const FORK: &str = "eureka-substrate:question:desk-fork-audience"; +/// A question Eureka's mind holds (asked 2026-11-11). The answers, notes and hash in `record()` are +/// synthetic: the hash is CultLib's sample digest, not this question's. +const FORK: &str = "eureka-substrate:question:desk-listing-authority"; fn s(text: &str) -> Short { Short(text.into()) @@ -71,7 +73,7 @@ fn answer(id: &str, outcome: &str, of: Option<&str>, answerer: &str, note: &str, retracts: None, code: None, options_hash: "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48".into(), - choice: Some("campaign-listing".into()), + choice: Some("asking-mind".into()), note: Some(note.into()), answered_at: received_at.into(), received_at: received_at.into(), @@ -91,8 +93,8 @@ fn record() -> ForkRecord { options_hash: "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48".into(), counted_answer_id: Some("n1".into()), answers: vec![ - answer("n1", "Counted", None, "7", "List by campaign.", "2026-11-11T17:00:00Z"), - answer("n2", "Revision", Some("n1"), "12", "Agreed, campaign grain.", "2026-11-11T17:05:00Z"), + answer("n1", "Counted", None, "7", "The asker lists her own forks.", "2026-11-11T17:00:00Z"), + answer("n2", "Revision", Some("n1"), "12", "Agreed.", "2026-11-11T17:05:00Z"), ], } } @@ -150,6 +152,12 @@ fn fixtures() -> Vec<(String, Vec)> { ) .unwrap(), ); + message("read-too-large.response", encode_desk_response("m-4", "fork", &DeskResponse::NotStored(fork_too_large_read(FORK)), RUNTIME).unwrap()); + message( + "list-response-too-large.response", + encode_desk_response("m-1", "forks", &DeskResponse::Refused(MindRefusal::ResponseTooLarge { bytes: 70000, limit: 65536 }), RUNTIME).unwrap(), + ); + message("refused-unit.response", encode_desk_response("m-1", "forks", &DeskResponse::Refused(MindRefusal::MissingIdentity), RUNTIME).unwrap()); message( "unavailable.response", encode_desk_response("m-1", "forks", &DeskResponse::Refused(MindRefusal::Unavailable { detail: "the mind store is not readable".into() }), RUNTIME).unwrap(), diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/.gitattributes b/crates/huginn-mind/tests/fixtures/desk-feed/.gitattributes new file mode 100644 index 0000000..0f9afd5 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/.gitattributes @@ -0,0 +1,3 @@ +*.bin binary +*.msgpack binary +*.hex text eol=lf diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/README.md b/crates/huginn-mind/tests/fixtures/desk-feed/README.md index ff8dca9..b018191 100644 --- a/crates/huginn-mind/tests/fixtures/desk-feed/README.md +++ b/crates/huginn-mind/tests/fixtures/desk-feed/README.md @@ -12,7 +12,12 @@ Each case has up to four files: - `.payload.msgpack` and `.payload.hex`: the base64-decoded `payload` of an operation message (named MessagePack). The `record-*` cases are bare payload fragments with no frame. -Hex is lowercase with one trailing newline. +Hex is lowercase with one trailing newline. The fork id is a question Eureka's mind holds. The +record's answers, notes and hash are synthetic. + +**The listing gate is not live.** On `main` every showable question goes to every reader. Do not +put a Mind that holds private questions behind a live receiver before `cut-desk-feed-listing` +lands (see `docs/desk-feed-door.md`). | case | direction | content | |---|---|---| @@ -24,6 +29,9 @@ Hex is lowercase with one trailing newline. | `list-counts.response` | Huginn to Bifrost | the limit-0 answer: open 3, recorded 5, `forks: []` | | `read-unknown.response` | Huginn to Bifrost | `NotStored` `fork_unknown` (Bifrost: 404) | | `list-too-large.response` | Huginn to Bifrost | `NotStored` `fork_too_large` naming the cursor to continue (Bifrost: 422) | +| `read-too-large.response` | Huginn to Bifrost | `NotStored` `fork_too_large`, read form: names the fork, no order or cursor (Bifrost: 422) | +| `list-response-too-large.response` | Huginn to Bifrost | `Refused` `ResponseTooLarge` (Bifrost: 503) | +| `refused-unit.response` | Huginn to Bifrost | `Refused` with a unit variant: a bare string `"MissingIdentity"` (shape only; Bifrost: 503) | | `unavailable.response` | Huginn to Bifrost | `Refused` `Unavailable` (Bifrost: 503 for this Mind) | | `failure.response` | Huginn to Bifrost | operation failure, `payloadSchema` `gamecult.cultnet.operation_failure.v1` (Bifrost: 503) | | `error.response` | Huginn to Bifrost | CultNet `Error` message, no payload (Bifrost: 503) | diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..fec42753c7b3a324eb479afbd5bb33db2e39adad GIT binary patch literal 298 zcmYk1Jx;?g7>3Jb*b*gGK`ace0EvYmk&-G4a_kSsB(aS@J1xBd=Ro{KD5}KLiGy$( zB8X7u=h6E!n;jiL^(`P$0oVMw4dPxKu+&MdUhcH5@y*c3MpOQMAt+CayolL6t3|m? zi`C7#oS$rqEa}QyASAh3n|x!+yot7gED0g!yD9BbG0mkCpHsJ? ztPVKC1n+~bYFQPl?pjveQN=YRtRAi5BQ5SusBZrV!m+-T=+l!z>RtY+etPa j{5VM4q4t(R;+2!_$c8@_fVWW7S9H{Qe7A59%mM!az7cmj literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.hex new file mode 100644 index 0000000..2f7bf26 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.hex @@ -0,0 +1 @@ +0000012689a96d6573736167654964a36d2d31a96f7065726174696f6ea5666f726b73a77061796c6f6164d944676164535a575a316332566b676242535a584e776232357a5a56527662307868636d646c677156696558526c63383441415246777057787062576c307a6741424141413daf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.hex new file mode 100644 index 0000000..a69cabb --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.hex @@ -0,0 +1 @@ +81a75265667573656481b0526573706f6e7365546f6f4c6172676582a56279746573ce00011170a56c696d6974ce00010000 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.msgpack new file mode 100644 index 0000000000000000000000000000000000000000..26ac65e1da3564ea44689df43ad5d041dd39c28d GIT binary patch literal 50 zcmZo%9+a9^TAZ5FxFIODxFA2TI5i|c-zTvsJ+*0RQe{bM@i_)Y!GfhZnYo!IKqdnN E03+EG#Q*>R literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.bin index 623b1157c6da38ac4974a17398226d99a731be10..e3ca7d59ae2928dab7b6da815f21c0ace603a889 100644 GIT binary patch delta 154 zcmZo?>1SbKU|`~&$fC)pF;OprF>B&*3%;>KPni=4o}JPboVlha!<(ts?7xIDM<0~%Sm#N zH1~A(D@)1?DoDx;%klI`DM)coPmeSXt4ubstn_sEHSi6zwOwD3SecVQIfQXD0GAgs A=Kufz delta 154 zcmeBYX=h~T{xBA>9W3M8ga5uv!_ne%hT)!MocQ*r1cgxBs z_pnM&m%s{7_nazE52uV|%r85T?>Ee)q*9L_DylYlNhc393FaTA}#0So75t;$1-k{+Y{ zg$1(_4QE@5<_?Fsz{SeZXpM=)J(G6p_w+^hwPKQs>pGS+TInUq0wyzErooZtt0P~m zDvmx+S&<1AZTt=6DMs0mxWJGi8IgRV*7-c(sPdg+1Zmtd7avN`mWxe;)7ar6InZ!& z^m9$4ti$i(`YFd3iJ+)_dv`JLL9vF{YWL@&t0indpv1s*e7;0dR)Hjct8P~-HRNFS zUo@DuQ3i&6a5m3418uzBf2;8PI*r{7-(rYP&N{rx|QIWozl#`GpOj T)37H$j3u@+zckzcv%nt#$``eN literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.frame.hex new file mode 100644 index 0000000..57ca2f8 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.frame.hex @@ -0,0 +1 @@ +000001d989a96d6573736167654964a36d2d34a96f7065726174696f6ea4666f726ba77061796c6f6164d9f867616c4f62335254644739795a57534472584e6a6147567459565a6c636e4e70623236345a3246745a574e31624851755a6d3979617935795a575a31633246734c6e597770474e765a4757755a6d39796131393062323966624746795a32576c5a584a7962334c5a616d5a76636d74666447397658327868636d646c4f69426d62334a7249475631636d56725953317a64574a7a64484a68644755366358566c63335270623234365a47567a6179317361584e306157356e4c57463164476876636d6c30655342706379427759584e304948526f5a5342795a584e776232357a5a534269623356755a4342696553427064484e6c6247593daf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.hex new file mode 100644 index 0000000..660a3fb --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.hex @@ -0,0 +1 @@ +81a94e6f7453746f72656483ad736368656d6156657273696f6eb867616d6563756c742e666f726b2e7265667573616c2e7630a4636f6465ae666f726b5f746f6f5f6c61726765a56572726f72d96a666f726b5f746f6f5f6c617267653a20666f726b20657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d6c697374696e672d617574686f7269747920697320706173742074686520726573706f6e736520626f756e6420627920697473656c66 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.msgpack new file mode 100644 index 0000000..a31a344 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.payload.msgpack @@ -0,0 +1 @@ +�©NotStoredƒ­schemaVersion¸gamecult.fork.refusal.v0¤code®fork_too_large¥errorÙjfork_too_large: fork eureka-substrate:question:desk-listing-authority is past the response bound by itself \ No newline at end of file diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.bin index 8860f1035707f150f8a9ba6d0520e2c1af36c7bf..9b114db76c59855abedb6ebb62de897f6d8a8adb 100644 GIT binary patch delta 48 zcmdnVw3ms6fq~I-B8xg>$3z`>#`1|JT4EIi$;LqiNx8`-kr81AiSCw_i4j4j6E}1M E07>o-KmY&$ delta 44 zcmdnXw3CU2fq~I%S0V_#^Q-3T0&7}$+^WPkr83piQ!?Tkw&4m6Ss5%025XY A761SM diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex index cfb4b1f..64c88a6 100644 --- a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.hex @@ -1 +1 @@ -0000013587a96d6573736167654964a36d2d34a96f7065726174696f6ea4666f726ba77061796c6f6164d9846761524762334a72684b52746157356b706d5631636d56725961647a64574a715a574e306f54656f5a47566a6157526c636e4f536f5465694d544b6e5a6d3979613139705a4e6b735a5856795a5774684c584e31596e4e30636d46305a5470786457567a64476c76626a706b5a584e724c575a76636d73745958566b615756755932553daf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b6687567696e6e2e6465736b5f726571756573742e7631ad736368656d6156657273696f6ebc63756c746e65742e6f7065726174696f6e5f726571756573742e7630a9736572766963654964ab687567696e6e2e6465736b +0000013987a96d6573736167654964a36d2d34a96f7065726174696f6ea4666f726ba77061796c6f6164d9886761524762334a72684b52746157356b706d5631636d56725961647a64574a715a574e306f54656f5a47566a6157526c636e4f536f5465694d544b6e5a6d3979613139705a4e6b775a5856795a5774684c584e31596e4e30636d46305a5470786457567a64476c76626a706b5a584e724c57787063335270626d6374595856306147397961585235af7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b6687567696e6e2e6465736b5f726571756573742e7631ad736368656d6156657273696f6ebc63756c746e65742e6f7065726174696f6e5f726571756573742e7630a9736572766963654964ab687567696e6e2e6465736b diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex index 8785434..68e7538 100644 --- a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.hex @@ -1 +1 @@ -81a4466f726b84a46d696e64a6657572656b61a77375626a656374a137a8646563696465727392a137a23132a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365 +81a4466f726b84a46d696e64a6657572656b61a77375626a656374a137a8646563696465727392a137a23132a7666f726b5f6964d930657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d6c697374696e672d617574686f72697479 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack index 38683f4..bda8f77 100644 --- a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack +++ b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.payload.msgpack @@ -1 +1 @@ -�¤Fork„¤mind¦eureka§subject¡7¨deciders’¡7¢12§fork_idÙ,eureka-substrate:question:desk-fork-audience \ No newline at end of file +�¤Fork„¤mind¦eureka§subject¡7¨deciders’¡7¢12§fork_idÙ0eureka-substrate:question:desk-listing-authority \ No newline at end of file diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex index 619d820..ecf6f3f 100644 --- a/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex +++ b/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.hex @@ -1 +1 @@ -85ad736368656d6156657273696f6eb767616d6563756c742e666f726b2e7265636f72642e7630a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438b1636f756e7465645f616e737765725f6964a26e31a7616e737765727392de0010ad736368656d6156657273696f6eb767616d6563756c742e6465736b5f616e737765722e7630a9616e737765725f6964a26e31a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365a76f7574636f6d65a7436f756e746564a26f66c0a87265747261637473c0a4636f6465c0ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438a663686f696365b063616d706169676e2d6c697374696e67a46e6f7465b14c6973742062792063616d706169676e2eab616e7377657265645f6174b4323032362d31312d31315431373a30303a30305aab72656365697665645f6174b4323032362d31312d31315431373a30303a30305aaf636c61696d5f696e5f667574757265c2a8616e737765726572a137a9617574686f72697479a64d656d626572aa726571756573745f6964d92430303030303030302d303030302d343030302d383030302d303030303030303030303031de0010ad736368656d6156657273696f6eb767616d6563756c742e6465736b5f616e737765722e7630a9616e737765725f6964a26e32a7666f726b5f6964d92c657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365a76f7574636f6d65a85265766973696f6ea26f66a26e31a87265747261637473c0a4636f6465c0ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438a663686f696365b063616d706169676e2d6c697374696e67a46e6f7465b74167726565642c2063616d706169676e20677261696e2eab616e7377657265645f6174b4323032362d31312d31315431373a30353a30305aab72656365697665645f6174b4323032362d31312d31315431373a30353a30305aaf636c61696d5f696e5f667574757265c2a8616e737765726572a23132a9617574686f72697479a64d656d626572aa726571756573745f6964d92430303030303030302d303030302d343030302d383030302d303030303030303030303032 +85ad736368656d6156657273696f6eb767616d6563756c742e666f726b2e7265636f72642e7630a7666f726b5f6964d930657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d6c697374696e672d617574686f72697479ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438b1636f756e7465645f616e737765725f6964a26e31a7616e737765727392de0010ad736368656d6156657273696f6eb767616d6563756c742e6465736b5f616e737765722e7630a9616e737765725f6964a26e31a7666f726b5f6964d930657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d6c697374696e672d617574686f72697479a76f7574636f6d65a7436f756e746564a26f66c0a87265747261637473c0a4636f6465c0ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438a663686f696365ab61736b696e672d6d696e64a46e6f7465be5468652061736b6572206c6973747320686572206f776e20666f726b732eab616e7377657265645f6174b4323032362d31312d31315431373a30303a30305aab72656365697665645f6174b4323032362d31312d31315431373a30303a30305aaf636c61696d5f696e5f667574757265c2a8616e737765726572a137a9617574686f72697479a64d656d626572aa726571756573745f6964d92430303030303030302d303030302d343030302d383030302d303030303030303030303031de0010ad736368656d6156657273696f6eb767616d6563756c742e6465736b5f616e737765722e7630a9616e737765725f6964a26e32a7666f726b5f6964d930657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d6c697374696e672d617574686f72697479a76f7574636f6d65a85265766973696f6ea26f66a26e31a87265747261637473c0a4636f6465c0ac6f7074696f6e735f68617368d94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438a663686f696365ab61736b696e672d6d696e64a46e6f7465a74167726565642eab616e7377657265645f6174b4323032362d31312d31315431373a30353a30305aab72656365697665645f6174b4323032362d31312d31315431373a30353a30305aaf636c61696d5f696e5f667574757265c2a8616e737765726572a23132a9617574686f72697479a64d656d626572aa726571756573745f6964d92430303030303030302d303030302d343030302d383030302d303030303030303030303032 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.msgpack index cc2e81a58f4c8fcda26be2c409c514c8f89d9873..2405433338a707aaea3318ac8d0f73ff49ce584c 100644 GIT binary patch delta 171 zcmdnMv7Tds0i(f0LwUiR%;J*FymZ~f(vpn)qRf)YiLq;N$V@h4tkPedSey+~pPQMN zvLr9RBz0d%Mydi(FttblWLB|429T9so~MwOUzA-uxsO?P@)yR#xQu+lR4ajDiHU2lh)%X+tWw{QoS0jXn3S8x$yJLD$YHErO+!%%QqQuNR Hpka#uKSe;t diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.bin new file mode 100644 index 0000000000000000000000000000000000000000..92b9314f2dfc77329f5c43f334e233569eec9266 GIT binary patch literal 266 zcmYk1%}T>S6h_;PPvSNqvBh1NQgk8MLWFGO_RcjOC-cL-GX)>P=MaB_gn}DaK8SB) zutB;$hmZ43CexFr&XNc;bg7L^m4tyM~?ZqcXS|t~?9aw*086_qIuM#09d1rbc HjHG@6kJ@W? literal 0 HcmV?d00001 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.hex b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.hex new file mode 100644 index 0000000..3fdedc6 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.hex @@ -0,0 +1 @@ +0000010689a96d6573736167654964a36d2d31a96f7065726174696f6ea5666f726b73a77061796c6f6164d924676164535a575a316332566b7230317063334e70626d644a5a47567564476c3065513d3daf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.hex b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.hex new file mode 100644 index 0000000..bdb0ed9 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.hex @@ -0,0 +1 @@ +81a752656675736564af4d697373696e674964656e74697479 diff --git a/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.msgpack b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.msgpack new file mode 100644 index 0000000..7a83e60 --- /dev/null +++ b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.payload.msgpack @@ -0,0 +1 @@ +�§Refused¯MissingIdentity \ No newline at end of file diff --git a/docs/desk-feed-door.md b/docs/desk-feed-door.md index 98ad947..59cb803 100644 --- a/docs/desk-feed-door.md +++ b/docs/desk-feed-door.md @@ -8,6 +8,13 @@ are planned and not built: `raised_at` on the card, and the listing gate that de reader sees. They are cut specs `cut-desk-feed-raised-at` and `cut-desk-feed-listing` in the `eureka-substrate` campaign. +**Not live: the listing gate.** On `main` today every showable question is returned to every +reader, whatever the call's `deciders` say (pinned by +`the_whole_record_comes_back_whoever_the_deciders_list_leaves_out` in `desk_wire/reads.rs`). +The default of not listed, described under Listing below, holds only once `cut-desk-feed-listing` +lands. Until then, neither Bifrost's receiver nor the ops root-run (`desk-door-ops-root-run`) may +go live on a Mind that holds private questions. + This page is the wire contract for the consumer, Bifrost's `UnixSocketDeskDoor` (Bifrost PR #67, `docs/desk-feed-design.md`). The golden bytes are in `crates/huginn-mind/tests/fixtures/desk-feed/`. Huginn's own encoders write them (`crates/huginn-mind/tests/desk_feed_fixtures.rs`), so a decoder @@ -31,6 +38,20 @@ that reads them reads the door. The document shapes inside the payload (`gamecul - Huginn serves only its own Mind. A request whose `mind` is another instance is refused (`Refused(...)`). +## Where forks come from + +A fork is any question the Mind holds, in any of its campaigns, that is showable. Showable +(`showable`, `desk_wire.rs`) means at least two options, no blank option text or `depends` line, +and at least one decider on the call. An in-force question is `open`. A question with any +resolution in force is `recorded` (a closed card, `ruling` set when an in-force ruling answers it). +That set includes the questions holding a spec (`huginn blocked`) and the open questions of every +campaign (`huginn open`). Nothing else in the mind is a fork: follow-ups, findings, research and +Persona state never reach the desk. + +The fixtures use the fork id `eureka-substrate:question:desk-listing-authority`, a question Eureka's +mind holds. The record's answers, notes and `options_hash` are synthetic: the hash is CultLib's +sample digest. + ## Framing and envelope ``` @@ -46,8 +67,15 @@ payload = message["payload"], standard base64 of named MessagePack (rmp_serde:: `messageId`, `status` `accepted` or `rejected`, and `sourceRuntimeId` (informational: do not compare it). The `payloadSchema` is `huginn.desk_response.v1`, or `gamecult.cultnet.operation_failure.v1` when the envelope or payload did not decode. -- Envelope keys are written in sorted order and absent optionals are omitted. Decode by key, not - by position. +- Operation messages (requests, responses, failures) have their keys written in sorted order, + with absent optionals omitted. The CultNet `Error` message does not follow that: its keys come in + CultNet's raw order (`schemaVersion`, `error`, `routingHint`, `code`, `details`), and absent + values are written as nil (fixture `error.response`). Decode by key, never by position. +- An operation failure carries `diagnostics`, a list holding the failure code (fixture + `failure.response`). It is absent on every other response. +- The door also answers `cultnet.schema_catalog_request.v0` with the catalog of the two desk + schemas (`huginn.desk_request.v1`, `huginn.desk_response.v1`, with their JSON Schemas). No + fixture pins that reply, because it embeds the schema text. - Frame bound: 65536 bytes each way. A reply past it is refused by name, never truncated. ## Payloads @@ -72,8 +100,31 @@ Response (`huginn.desk_response.v1`): |---|---|---| | `{"Forks": {list, as_of}}` | one page, `list` is `gamecult.fork.list.v0` | 200 | | `{"Fork": {entry, as_of}}` | one fork, `entry` is `gamecult.fork.entry.v0` | 200 | -| `{"NotStored": refusal}` | `gamecult.fork.refusal.v0`: `fork_unknown`, or `fork_too_large` with the cursor to continue | 404 / 422 | -| `{"Refused": {"": {...}}}` | a Mind refusal (store fault, wrong instance, a response too large) | 503 `unavailable` | +| `{"NotStored": refusal}` | `gamecult.fork.refusal.v0`: `fork_unknown`, or `fork_too_large` | 404 / 422 | +| `{"Refused": }` | a Mind refusal (store fault, wrong instance, a list reply over the cap) | 503 `unavailable` | + +The `Refused` value is opaque to Bifrost: any `Refused` is a 503. Its shape depends on the +variant: a variant with fields is a one-key map (`{"Unavailable": {"detail": ...}}`, +`{"ResponseTooLarge": {"bytes": ..., "limit": ...}}`), and a unit variant is a bare string +(`"MissingIdentity"`, fixture `refused-unit.response`). + +**`fork_too_large` has two forms, and both are live on `main`.** + +- The list form comes from `Mind::desk_forks` when the first entry of a page is alone past the + page budget: `fork_too_large: fork at order is past the page budget by itself; + continue with cursor c`, or `...; this is the end of the list` at order 0 (fixture + `list-too-large.response`). +- The read form comes from the door (`respond` in `desk_door.rs`, `fork_too_large_read`) when a + read's whole entry is past the 65536-byte reply cap: `fork_too_large: fork is past the + response bound by itself`, with no order and no cursor (fixture `read-too-large.response`). + CultLib `fork-card.md` limitation 13 still says that an over-cap read reaches Bifrost as + `Refused(ResponseTooLarge)`. That text is stale: Huginn main answers the read form. The refusal + carries no typed order or fork id. A `ForkTooLarge(ForkId[, Order])` in Bifrost must parse them + out of the informational `error` text (CultLib calls the text informational), or use the route's + `fork_id` and the request's cursor instead. The second is the safer choice. +- Any other reply over the cap, which in practice means a list, is `Refused(ResponseTooLarge)`, a + 503 (fixture `list-response-too-large.response`). A list should not reach it, because pages are + budgeted below the cap. ## Failure means 503 for that Mind only @@ -84,8 +135,8 @@ affected, because each Mind has its own socket and the client asks per Mind: without a reply frame; - no whole reply frame arrives within the deadline; - the reply is a CultNet `Error` message (`cultnet.error.v0`, fixture `error.response`), an - operation failure (fixture `failure.response`), or a `Refused` payload (fixture - `unavailable.response`). + operation failure (fixture `failure.response`), or a `Refused` payload (fixtures + `unavailable.response`, `list-response-too-large.response`, `refused-unit.response`). An empty list is never served in place of a failure. `open_count: 0` means zero open forks. @@ -96,7 +147,10 @@ RFC3339 string or nil, which takes the card from 16 to 17 keys in closed map-mod A string is the question's first-admission time, which never moves. It is outside `options_hash`, which keeps its five members, so the hash vectors do not change. Huginn derives it from the question's admission: the `committed_at` of the receipt that first admitted the question, or for an -imported question the source mind's `committed_at` (`imported_from.committed_at`). +imported question the source mind's `committed_at` (`imported_from.committed_at`). A value that does not +parse as RFC3339 is served as nil. One known limit: after a campaign has moved twice (A to B to C), +C sees B's admission time, because an import quotes its source's receipt and B's receipt is B's +own (follow-up `desk-raised-at-import-chain`). **No fixture here carries card bytes.** The card's key order and hex wait on the CultLib commit that re-pins the card vectors with `raised_at`. Until then the card is opaque to these fixtures, and @@ -115,7 +169,7 @@ CultLib commit. Heimdall 2026-11-11) and strips notes (ruling `desk-read-visibility-whole-record`, `DeskNotes`). Huginn masks nothing. -## Listing: which forks a reader sees (Huginn's; planned) +## Listing: which forks a reader sees (Huginn's; NOT live, see the status above) Huginn owns audience and visibility, at its door. The card has no audience key (CultLib), and Bifrost's feed applies no fork filter. @@ -126,17 +180,30 @@ Bifrost's feed applies no fork filter. answers `fork_unknown`, the same bytes as a fork that does not exist. The counts and the cursor are computed over the visible set, which is why the gate is Huginn's: a filter applied after paging would make both wrong. -- **Deciders** see every showable fork whether or not it is listed (recommended in question - `eureka-substrate:question:desk-unlisted-to-deciders`), because they must see a fork to answer it. - The door compares the call's `subject` with its `deciders` to tell. The wire does not change. +- **Deciders** see every showable fork whether or not it is listed (ruling + `eureka-substrate:ruling:desk-deciders-see-all`), because they must see a fork to answer it. The + door compares the call's `subject` with its `deciders` to tell. The wire does not change. An + operator or steward who is not on `deciders` sees only listed forks: reads carry no authority, so + anyone who must see unlisted forks is put on the Mind's decider list in Bifrost's admin. +- **What a non-decider can still infer.** `order` is the fork's rank among all the Mind's + questions, and `stamp` is the Mind's head. So a non-decider sees gaps in `order` (some unlisted or + unshowable questions exist between two listed ones) and sees `stamp` move on any admission. The + gate accepts this leak: no fork's text, id or count leaves the door. Ranking over the visible set + only would make `order` depend on the reader and move whenever a fork is listed, which breaks + CultLib's rule that an order never changes and breaks every held cursor. - **The listing record** is planned kind `desk_listing`, key `:desk_listing:`, - fields `fork` (the question's id) and `reason` (one line: why members may read it). It is + fields `campaign`, `fork` (the question's id, in the same campaign) and `reason` (one line: why + members may read it). It is admitted on the Mind's own door, so its receipt records who listed it and when. Unlisting is a `Withdrawn` resolution on it, and relisting withdraws that resolution, which reopens it. Each flip is a document with its own receipt, so the history is `huginn history `. No flag is stored anywhere else. -- **Who may list or unlist** is question `eureka-substrate:question:desk-listing-authority`. The - recommended answer is the asking Mind herself, at her Self. +- **Who may list or unlist:** the asking Mind herself (ruling + `eureka-substrate:ruling:desk-listing-asking-mind`). A fork that names a person's matter is listed + only with that person's say. +- **CultLib's text disagrees.** `fork-card.md` (Read and List) says no fork is hidden by + `fork_unknown` and every `desk.read` holder sees every card. That is Libby's to amend for the + listing gate (follow-up `cultlib-fork-card-listing`). - **Open for Heimdall:** if Bifrost wants any part of the listing (an operator override at the desk, a per-Mind default, a reason shown to members), that is his decision to raise. Nothing in this plan puts listing state in Bifrost. -- 2.47.3 From 85b92f7f96c8749ad306bae6fb99f0a3ffd87c26 Mon Sep 17 00:00:00 2001 From: Eureka Date: Wed, 11 Nov 2026 17:15:25 +0000 Subject: [PATCH 3/4] Desk feed door doc: the listing's question field is listed (cut-desk-feed-listing.r2) Co-Authored-By: Claude Sonnet 5.5 --- docs/desk-feed-door.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/desk-feed-door.md b/docs/desk-feed-door.md index 59cb803..092d6b5 100644 --- a/docs/desk-feed-door.md +++ b/docs/desk-feed-door.md @@ -192,7 +192,7 @@ Bifrost's feed applies no fork filter. only would make `order` depend on the reader and move whenever a fork is listed, which breaks CultLib's rule that an order never changes and breaks every held cursor. - **The listing record** is planned kind `desk_listing`, key `:desk_listing:`, - fields `campaign`, `fork` (the question's id, in the same campaign) and `reason` (one line: why + fields `campaign`, `listed` (the question's id, in the same campaign) and `reason` (one line: why members may read it). It is admitted on the Mind's own door, so its receipt records who listed it and when. Unlisting is a `Withdrawn` resolution on it, and relisting withdraws that resolution, which reopens it. Each -- 2.47.3 From 86372ae57bbe7418eb6effdebcda8a882cd827f3 Mon Sep 17 00:00:00 2001 From: Eureka Date: Wed, 11 Nov 2026 17:17:58 +0000 Subject: [PATCH 4/4] Desk feed door doc: cite desk-read-visibility-listed for note stripping Co-Authored-By: Claude Sonnet 5.5 --- docs/desk-feed-door.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/desk-feed-door.md b/docs/desk-feed-door.md index 092d6b5..8f233ec 100644 --- a/docs/desk-feed-door.md +++ b/docs/desk-feed-door.md @@ -166,7 +166,7 @@ CultLib commit. to Bifrost's uid alone. - Bifrost owns three things: the per-request membership check, the `desk.read` token, and masking on what the door returns. It masks answerer ids for a reader who is not a decider (X3, - Heimdall 2026-11-11) and strips notes (ruling `desk-read-visibility-whole-record`, `DeskNotes`). + Heimdall 2026-11-11) and strips notes (ruling `desk-read-visibility-listed`, `DeskNotes`). Huginn masks nothing. ## Listing: which forks a reader sees (Huginn's; NOT live, see the status above) -- 2.47.3