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 0000000..fec4275 Binary files /dev/null and b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.frame.bin differ 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 0000000..26ac65e Binary files /dev/null and b/crates/huginn-mind/tests/fixtures/desk-feed/list-response-too-large.response.payload.msgpack differ 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 623b115..e3ca7d5 100644 Binary files a/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.bin and b/crates/huginn-mind/tests/fixtures/desk-feed/list-too-large.response.frame.bin differ 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 index 60505ec..d2fc906 100644 --- 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 @@ -1 +1 @@ -0000020389a96d6573736167654964a36d2d31a96f7065726174696f6ea5666f726b73a77061796c6f6164da012067616c4f62335254644739795a57534472584e6a6147567459565a6c636e4e70623236345a3246745a574e31624851755a6d3979617935795a575a31633246734c6e597770474e765a4757755a6d39796131393062323966624746795a32576c5a584a7962334c5a69575a76636d74666447397658327868636d646c4f69426d62334a7249475631636d56725953317a64574a7a64484a68644755366358566c63335270623234365a47567a6179316d62334a724c5746315a476c6c626d4e6c49474630494739795a4756794944517849476c7a49484268633351676447686c494842685a325567596e566b5a32563049474a3549476c30633256735a6a73675932397564476c756457556764326c306143426a64584a7a62334967597a5178af7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 +0000020b89a96d6573736167654964a36d2d31a96f7065726174696f6ea5666f726b73a77061796c6f6164da012867616c4f62335254644739795a57534472584e6a6147567459565a6c636e4e70623236345a3246745a574e31624851755a6d3979617935795a575a31633246734c6e597770474e765a4757755a6d39796131393062323966624746795a32576c5a584a7962334c5a6a575a76636d74666447397658327868636d646c4f69426d62334a7249475631636d56725953317a64574a7a64484a68644755366358566c63335270623234365a47567a6179317361584e306157356e4c57463164476876636d6c306553426864434276636d526c636941304d5342706379427759584e304948526f5a5342775957646c49474a315a47646c644342696553427064484e6c6247593749474e76626e5270626e566c4948647064476767593356796332397949474d304d513d3daf7061796c6f6164456e636f64696e67b26d6573736167657061636b2d626173653634ad7061796c6f6164536368656d61b7687567696e6e2e6465736b5f726573706f6e73652e7631ad736368656d6156657273696f6ebd63756c746e65742e6f7065726174696f6e5f726573706f6e73652e7630a9736572766963654964ab687567696e6e2e6465736baf736f7572636552756e74696d654964ad687567696e6e2d657572656b61a6737461747573a872656a6563746564 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 index 5b6bc62..3a34206 100644 --- 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 @@ -1 +1 @@ -81a94e6f7453746f72656483ad736368656d6156657273696f6eb867616d6563756c742e666f726b2e7265667573616c2e7630a4636f6465ae666f726b5f746f6f5f6c61726765a56572726f72d989666f726b5f746f6f5f6c617267653a20666f726b20657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d666f726b2d61756469656e6365206174206f7264657220343120697320706173742074686520706167652062756467657420627920697473656c663b20636f6e74696e7565207769746820637572736f7220633431 +81a94e6f7453746f72656483ad736368656d6156657273696f6eb867616d6563756c742e666f726b2e7265667573616c2e7630a4636f6465ae666f726b5f746f6f5f6c61726765a56572726f72d98d666f726b5f746f6f5f6c617267653a20666f726b20657572656b612d7375627374726174653a7175657374696f6e3a6465736b2d6c697374696e672d617574686f72697479206174206f7264657220343120697320706173742074686520706167652062756467657420627920697473656c663b20636f6e74696e7565207769746820637572736f7220633431 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 index f294566..7964e1f 100644 --- 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 @@ -1 +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 +�©NotStoredƒ­schemaVersion¸gamecult.fork.refusal.v0¤code®fork_too_large¥errorÙ�fork_too_large: fork eureka-substrate:question:desk-listing-authority 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-too-large.response.frame.bin b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.frame.bin new file mode 100644 index 0000000..cdb47aa Binary files /dev/null and b/crates/huginn-mind/tests/fixtures/desk-feed/read-too-large.response.frame.bin differ 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 8860f10..9b114db 100644 Binary files a/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.bin and b/crates/huginn-mind/tests/fixtures/desk-feed/read.request.frame.bin differ 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 cc2e81a..2405433 100644 Binary files a/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.msgpack and b/crates/huginn-mind/tests/fixtures/desk-feed/record-whole.payload.msgpack differ 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 0000000..92b9314 Binary files /dev/null and b/crates/huginn-mind/tests/fixtures/desk-feed/refused-unit.response.frame.bin differ 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.