From 650e5e262e5315c3c1b5ee5b300ee826a0af7880 Mon Sep 17 00:00:00 2001 From: Eureka Date: Wed, 11 Nov 2026 17:14:27 +0000 Subject: [PATCH] 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.