From c2aef3bc58e65eb900245aa1c5dea323f894c05d Mon Sep 17 00:00:00 2001 From: Eureka Date: Wed, 11 Nov 2026 00:56:36 +0000 Subject: [PATCH] Desk door: DeskForkTooLarge becomes the not-stored refusal fork_too_large (list only), text names fork id and order; schemas regenerated Co-Authored-By: Claude Sonnet 5.5 --- crates/huginn-mind/src/desk_wire.rs | 9 +++- crates/huginn-mind/src/desk_wire/reads.rs | 12 ++--- crates/huginn-mind/src/refusal.rs | 5 --- .../huginn.desk_response.v1.schema.json | 44 ++----------------- .../huginn.mind_response.v1.schema.json | 39 ---------------- 5 files changed, 18 insertions(+), 91 deletions(-) diff --git a/crates/huginn-mind/src/desk_wire.rs b/crates/huginn-mind/src/desk_wire.rs index 3c2c956..94e2946 100644 --- a/crates/huginn-mind/src/desk_wire.rs +++ b/crates/huginn-mind/src/desk_wire.rs @@ -152,13 +152,14 @@ pub struct ForkDeskAnswer { pub request_id: String, } -/// The three requests Huginn refuses without storing, by code. +/// The refusals Huginn sends without storing, by code. `ForkTooLarge` is the list's alone. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ForkRefusalCode { ForkUnknown, BodyInvalid, RequestIdConflict, + ForkTooLarge, } /// `gamecult.fork.refusal.v0`. `error` is a fixed sentence, never an echo of what was sent; @@ -382,6 +383,12 @@ fn fork_unknown() -> ForkRefusal { fork_refusal(ForkRefusalCode::ForkUnknown, "fork_unknown: no such fork".into()) } +/// A list page whose first entry alone is past the page budget: names that fork and its `order`; a +/// client continues with the cursor `c` and has reached the end at order 0. +fn fork_too_large(fork: &str, order: u64) -> ForkRefusal { + fork_refusal(ForkRefusalCode::ForkTooLarge, format!("fork_too_large: fork {fork} at order {order} is past the page budget by itself; continue with cursor c{order}")) +} + fn body_invalid(field: &str) -> ForkRefusal { fork_refusal(ForkRefusalCode::BodyInvalid, format!("body_invalid: the body does not satisfy gamecult.fork.answer_request.v0 ({field})")) } diff --git a/crates/huginn-mind/src/desk_wire/reads.rs b/crates/huginn-mind/src/desk_wire/reads.rs index 2b8e3e1..34de0b1 100644 --- a/crates/huginn-mind/src/desk_wire/reads.rs +++ b/crates/huginn-mind/src/desk_wire/reads.rs @@ -326,7 +326,7 @@ impl Mind { /// A page holds at most `limit` entries (never more than 50) and stops short only when the next /// entry would push the encoded response past `DESK_PAGE_BUDGET` (the one `DESK_MAX_RESPONSE_BYTES` /// less the envelope margin); counts and stamp are unaffected. A page whose FIRST entry alone is past - /// the budget is `MindRefusal::DeskForkTooLarge`, naming that fork and its order: the caller goes on + /// the budget is the `fork_too_large` refusal, naming that fork and its order: the caller goes on /// with the cursor `c`, which returns the forks below it, so no older fork is unreachable. /// The fork itself is still its whole entry on `desk_fork`. Nothing is dropped silently. /// @@ -384,7 +384,7 @@ impl Mind { if bytes > DESK_PAGE_BUDGET { let list = held(&mut response); if list.forks.len() == 1 { - return Ok(DeskResponse::Refused(MindRefusal::DeskForkTooLarge { fork: key.to_string(), order: *order, bytes: bytes as u64, limit: DESK_PAGE_BUDGET as u64 })); + return Ok(DeskResponse::NotStored(fork_too_large(key, *order))); } list.forks.pop(); list.next_cursor = before; @@ -1061,9 +1061,11 @@ mod tests { assert_eq!(ids(&first), [fork(3)]); assert_eq!(first.next_cursor.map(ForkCursor::order), Some(2)); let response = mind.desk_forks(&list(&LIST), None, first.next_cursor, limit(50)); - let DeskResponse::Refused(MindRefusal::DeskForkTooLarge { fork: named, order, bytes, limit: bound }) = response else { panic!("{response:?}") }; - assert_eq!((named, order, bound), (fork(2), 1, DESK_PAGE_BUDGET as u64)); - assert!(bytes > bound); + let DeskResponse::NotStored(refusal) = response else { panic!("{response:?}") }; + assert_eq!(refusal.code, ForkRefusalCode::ForkTooLarge); + let order = 1; + assert_eq!(refusal.error, fork_too_large(&fork(2), order).error); + assert!(refusal.error.contains(&fork(2)) && refusal.error.contains("order 1"), "{refusal:?}"); // The order the refusal names is the order of the fork it names; its cursor goes on past it. assert_eq!(entry(mind.desk_fork(&list(&LIST), &fork(2))).0.card.order, order); let (rest, _) = page(mind.desk_forks(&list(&LIST), None, ForkCursor::new(order), limit(50))); diff --git a/crates/huginn-mind/src/refusal.rs b/crates/huginn-mind/src/refusal.rs index 047afd1..06dfb10 100644 --- a/crates/huginn-mind/src/refusal.rs +++ b/crates/huginn-mind/src/refusal.rs @@ -109,11 +109,6 @@ pub enum MindRefusal { /// truncated or paginated on its behalf. The daemon owns it and its /// deferral bound decides it; no rule of the mind is involved. ResponseTooLarge { bytes: u64, limit: u64 }, - /// A desk page whose first entry alone is past the page budget: `fork` is that fork, `order` its - /// rank (a caller goes on past it with the cursor `c`, which returns the forks below it), - /// `bytes` what the page holding it would encode to (without the envelope) and `limit` the page - /// budget it exceeds. Raised by the mind's page fill, which is why it is not `ResponseTooLarge`. - DeskForkTooLarge { fork: String, order: u64, bytes: u64, limit: u64 }, /// A selection the substrate or the organ's value door refused: `field` /// is the selection field the client sent, `value` the offending part. SelectionInvalid { field: String, value: Option, message: String }, diff --git a/schemas/cultnet/huginn.desk_response.v1.schema.json b/schemas/cultnet/huginn.desk_response.v1.schema.json index a6fbc80..92c6db1 100644 --- a/schemas/cultnet/huginn.desk_response.v1.schema.json +++ b/schemas/cultnet/huginn.desk_response.v1.schema.json @@ -471,12 +471,13 @@ ] }, "ForkRefusalCode": { - "description": "The three requests Huginn refuses without storing, by code.", + "description": "The refusals Huginn sends without storing, by code. `ForkTooLarge` is the list's alone.", "type": "string", "enum": [ "fork_unknown", "body_invalid", - "request_id_conflict" + "request_id_conflict", + "fork_too_large" ] }, "ListSchema": { @@ -1562,45 +1563,6 @@ "ResponseTooLarge" ] }, - { - "description": "A desk page whose first entry alone is past the page budget: `fork` is that fork, `order` its\nrank (a caller goes on past it with the cursor `c`, which returns the forks below it),\n`bytes` what the page holding it would encode to (without the envelope) and `limit` the page\nbudget it exceeds. Raised by the mind's page fill, which is why it is not `ResponseTooLarge`.", - "type": "object", - "properties": { - "DeskForkTooLarge": { - "type": "object", - "properties": { - "bytes": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "fork": { - "type": "string" - }, - "limit": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "order": { - "type": "integer", - "format": "uint64", - "minimum": 0 - } - }, - "required": [ - "fork", - "order", - "bytes", - "limit" - ] - } - }, - "additionalProperties": false, - "required": [ - "DeskForkTooLarge" - ] - }, { "description": "A selection the substrate or the organ's value door refused: `field`\nis the selection field the client sent, `value` the offending part.", "type": "object", diff --git a/schemas/cultnet/huginn.mind_response.v1.schema.json b/schemas/cultnet/huginn.mind_response.v1.schema.json index d514d50..c3d6e4e 100644 --- a/schemas/cultnet/huginn.mind_response.v1.schema.json +++ b/schemas/cultnet/huginn.mind_response.v1.schema.json @@ -2059,45 +2059,6 @@ "ResponseTooLarge" ] }, - { - "description": "A desk page whose first entry alone is past the page budget: `fork` is that fork, `order` its\nrank (a caller goes on past it with the cursor `c`, which returns the forks below it),\n`bytes` what the page holding it would encode to (without the envelope) and `limit` the page\nbudget it exceeds. Raised by the mind's page fill, which is why it is not `ResponseTooLarge`.", - "type": "object", - "properties": { - "DeskForkTooLarge": { - "type": "object", - "properties": { - "bytes": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "fork": { - "type": "string" - }, - "limit": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "order": { - "type": "integer", - "format": "uint64", - "minimum": 0 - } - }, - "required": [ - "fork", - "order", - "bytes", - "limit" - ] - } - }, - "additionalProperties": false, - "required": [ - "DeskForkTooLarge" - ] - }, { "description": "A selection the substrate or the organ's value door refused: `field`\nis the selection field the client sent, `value` the offending part.", "type": "object",