Desk reads: Mind::desk_forks and desk_fork project card, record and entry at one snapshot

Newest first by admission ordinal, cursor strictly below, counts over the showable set, limit 0 is
counts and stamp. Whole record to the peer-checked socket; the trust boundary is pinned in the
desk_door module doc and the DeskRequest/DeskResponse docs. Published schemas regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Eureka 2026-11-10 23:57:27 +00:00
commit 707642bd54
6 changed files with 469 additions and 11 deletions

View file

@ -9,6 +9,17 @@
//! read and write spends what is left of it, so a peer that trickles bytes is cut off at the
//! deadline like one that is silent), a reply past `DESK_MAX_RESPONSE_BYTES` refused by name.
//!
//! The trust boundary of the reads (ruling `desk-read-visibility-whole-record`). The peer-uid check
//! IS the whole authentication: Huginn must be reachable only by Bifrost's peer-checked socket, so
//! the configured uid is Bifrost's and no other process's. `Forks` and `Fork` return the WHOLE record
//! (cards, outcomes, every decider's note) to whoever passes that check; nothing in Huginn strips a
//! note or gates a read on `subject` or `deciders` (`deciders` only fills the card and the
//! showability gate). Bifrost applies visibility at its own door (a stripped read serves `note: null`,
//! CultLib `fork-card.md`). A second reader of this socket is therefore a note leak, and a deployment
//! that gives the socket to any uid but Bifrost's has no reader check left. The reads carry no
//! `authority` (a payload that names one is refused whole), and the door has no other way to ask who
//! is reading.
//!
//! It owns no rule. Which Mind method answers is `Daemon::handle_desk`'s; when the door is
//! served is `serve::run`'s, once per pass in the thread that owns the Mind. A peer that is
//! slow costs the loop at most `DESK_ACCEPTS_PER_PASS` connections of `DESK_CONNECTION_DEADLINE`

View file

@ -125,7 +125,7 @@ pub fn options_hash(question: &PipelineQuestion) -> Sha256Hex {
}
/// Every desk answer of one fork, with its key.
fn answers_of<'a>(docs: &'a Docs<'_>, fork: &'a str) -> impl Iterator<Item = (&'a str, &'a PipelineDeskAnswer)> + 'a {
pub(crate) fn answers_of<'a>(docs: &'a Docs<'_>, fork: &'a str) -> impl Iterator<Item = (&'a str, &'a PipelineDeskAnswer)> + 'a {
docs.of_kind(PipelineKind::DeskAnswer).filter_map(move |(key, document)| match document {
PipelineDocument::DeskAnswer(answer) if answer.fork.0 == fork => Some((key, answer)),
_ => None,

View file

@ -12,6 +12,8 @@
//! `desk-read-visibility-whole-record`). Nothing is stored.
use super::*;
use crate::desk::answers_of;
use crate::desk::options_hash;
/// The card document's schema id.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@ -223,22 +225,135 @@ impl JsonSchema for ForkLimit {
}
}
/// The card of a question held at the snapshot `docs` reads, shown to the call's `deciders`.
fn card_of(docs: &Docs<'_>, key: &str, question: &PipelineQuestion, deciders: &[Short]) -> Result<ForkCard, MindRefusal> {
let open = docs.in_force(PipelineKind::Question, key);
// The ruling that closed the fork: the in-force one that answers it (the key order picks if a second ever does).
let ruling = (!open)
.then(|| {
docs.of_kind(PipelineKind::Ruling)
.filter(|(ruling_key, document)| {
matches!(document, PipelineDocument::Ruling(ruling) if ruling.answers.as_ref().is_some_and(|answers| answers.0 == key))
&& docs.in_force(PipelineKind::Ruling, ruling_key)
})
.map(|(ruling_key, _)| ruling_key.to_string())
.min()
})
.flatten();
Ok(ForkCard {
schema_version: CardSchema::V0,
fork_id: key.into(),
title: question.title.0.clone(),
context: question.question.0.clone(),
options: question.options.iter().map(|option| ForkOption { id: option.label.0.clone(), text: option.text.0.clone() }).collect(),
recommendation: question.recommended.0.clone(),
depends: question.depends.iter().map(|line| line.0.clone()).collect(),
leans: None,
flag: None,
raised_in: question.raised_in.as_ref().map(|raised| ForkRef { kind: format!("{:?}", raised.kind), id: raised.id.0.clone() }),
deciders: deciders.iter().map(|decider| decider.0.clone()).collect(),
status: if open { CardStatus::Open } else { CardStatus::Closed },
ruling,
ruled_from: Vec::new(),
order: docs.ordinal_of(PipelineKind::Question, key).ok_or_else(unreadable_key)?,
options_hash: options_hash(question).0,
})
}
/// The record of a fork: its answers in admission order and the answer that counts at this snapshot.
fn record_of(docs: &Docs<'_>, key: &str, options_hash: &str) -> Result<ForkRecord, MindRefusal> {
let mut stored: Vec<_> = answers_of(docs, key).collect();
stored.sort_by_key(|(_, answer)| answer.sequence);
let answers = stored.into_iter().map(|(answer_key, answer)| project(answer_key, answer)).collect::<Result<Vec<_>, _>>()?;
let counted_answer_id = counted_answer(docs, key).map(|counted| answer_seq(key, &counted).map(n_text).ok_or_else(unreadable_key)).transpose()?;
Ok(ForkRecord { schema_version: RecordSchema::V0, fork_id: key.into(), options_hash: options_hash.into(), counted_answer_id, answers })
}
fn entry_of(docs: &Docs<'_>, key: &str, question: &PipelineQuestion, deciders: &[Short]) -> Result<ForkEntry, MindRefusal> {
let card = card_of(docs, key, question, deciders)?;
let record = record_of(docs, key, &card.options_hash)?;
Ok(ForkEntry { schema_version: EntrySchema::V0, card, record })
}
impl<S: MindStore> Mind<S> {
/// STUB
pub fn desk_forks(&self, _deciders: &[Short], _status: Option<ListStatus>, _cursor: Option<ForkCursor>, _limit: ForkLimit) -> DeskResponse {
DeskResponse::NotStored(fork_unknown())
/// One page of the desk's forks, read at one snapshot (`as_of`, also the page's stamp `s<as_of>`).
///
/// The showable forks only: a question `showable` refuses (fewer than two options, a blank option
/// or depends line, no decider) is in no count and no page. `open_count` and `recorded_count` cover
/// that whole set whatever `status`, `cursor` and `limit` ask. The page is the forks newest first by
/// `order` (the question's admission ordinal), those with the asked `status` (`None` is both) and
/// an order strictly below the cursor's; `limit` 0 returns no entries. `next_cursor` names the last
/// order returned and is `None` when no further fork matches. Several questions admitted in one
/// commit share an order, and a page that ends between them skips the rest (the contract's cursor
/// has no tie-break).
///
/// The call is handed the call's `deciders` and no subject: they fill each card's `deciders` and
/// feed the showability gate, and gate nothing else. The record is whole, notes included
/// (ruling `desk-read-visibility-whole-record`: Bifrost applies visibility, Huginn strips nothing).
pub fn desk_forks(&self, deciders: &[Short], status: Option<ListStatus>, cursor: Option<ForkCursor>, limit: ForkLimit) -> DeskResponse {
self.forks_page(deciders, status, cursor, limit).unwrap_or_else(door_refusal)
}
/// STUB
pub fn desk_fork(&self, _deciders: &[Short], _fork_id: &str) -> DeskResponse {
DeskResponse::NotStored(fork_unknown())
/// One fork, as the entry a page carries, read at one snapshot. A fork that is not held, is not a
/// question or is not showable is `fork_unknown`, the answer path's refusal. Whole record, as `desk_forks`.
pub fn desk_fork(&self, deciders: &[Short], fork_id: &str) -> DeskResponse {
self.fork_entry(deciders, fork_id).unwrap_or_else(door_refusal)
}
fn forks_page(&self, deciders: &[Short], status: Option<ListStatus>, cursor: Option<ForkCursor>, limit: ForkLimit) -> Result<DeskResponse, MindRefusal> {
let image = self.image()?;
let docs = Docs::at(image, image.head);
let mut shown = Vec::new();
for (key, document) in docs.of_kind(PipelineKind::Question) {
let PipelineDocument::Question(question) = document else { continue };
if showable(question, deciders) {
let order = docs.ordinal_of(PipelineKind::Question, key).ok_or_else(unreadable_key)?;
shown.push((order, key, question, docs.in_force(PipelineKind::Question, key)));
}
}
// Newest first; the key breaks a tie so that a page is the same page on every read.
shown.sort_by(|left, right| right.0.cmp(&left.0).then_with(|| right.1.cmp(left.1)));
let open_count = shown.iter().filter(|fork| fork.3).count() as u64;
let recorded_count = shown.len() as u64 - open_count;
let mut matching = shown.iter().filter(|(order, _, _, open)| {
cursor.is_none_or(|cursor| *order < cursor.order())
&& match status {
None => true,
Some(ListStatus::Open) => *open,
Some(ListStatus::Recorded) => !*open,
}
});
let page: Vec<_> = matching.by_ref().take(limit.get() as usize).collect();
let next_cursor = if matching.next().is_some() { page.last().and_then(|(order, ..)| ForkCursor::new(*order)) } else { None };
let forks = page.into_iter().map(|(_, key, question, _)| entry_of(&docs, key, question, deciders)).collect::<Result<Vec<_>, _>>()?;
let list = ForkList {
schema_version: ListSchema::V0,
mind: self.instance().0.clone(),
stamp: format!("s{}", image.head),
open_count,
recorded_count,
forks,
next_cursor,
};
Ok(DeskResponse::Forks { list, as_of: image.head })
}
fn fork_entry(&self, deciders: &[Short], fork_id: &str) -> Result<DeskResponse, MindRefusal> {
let image = self.image()?;
let docs = Docs::at(image, image.head);
let Some(PipelineDocument::Question(question)) = docs.in_image(PipelineKind::Question, fork_id) else {
return Ok(DeskResponse::NotStored(fork_unknown()));
};
if !showable(question, deciders) {
return Ok(DeskResponse::NotStored(fork_unknown()));
}
Ok(DeskResponse::Fork { entry: entry_of(&docs, fork_id, question, deciders)?, as_of: image.head })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::desk::options_hash;
use crate::envelope::{decode_desk_request, encode_desk_request, encode_desk_response};
use crate::fixtures::*;
use crate::store::test_stores::MemoryStore;

View file

@ -69,6 +69,11 @@ impl<'i> Docs<'i> {
self.image.id_of(kind, id).filter(|row| self.visible(*row)).map(|row| &self.image.row(row).view.document)
}
/// The ordinal of the receipt that wrote an image document: its admission order.
pub(crate) fn ordinal_of(&self, kind: PipelineKind, id: &str) -> Option<u64> {
self.image.id_of(kind, id).filter(|row| self.visible(*row)).map(|row| self.image.row(row).ordinal)
}
pub(crate) fn find(&self, kind: PipelineKind, id: &str) -> Option<&PipelineDocument> {
self.in_batch(kind, id).or_else(|| self.in_image(kind, id))
}

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "DeskRequest",
"description": "Every request the desk door answers. The fields are the wire names: the `mind` addressed\n(the persona and pipeline requests say `instance`; the sets are separate), the verified\n`subject` and `authority` Bifrost vouches for (door context, never body fields), and the\n`deciders` of this call (1 to 64, distinct; the Mind never stores them). Closed: a key outside the set refuses\nthe whole payload. The reads answer in follow-up desk-wire-reads.",
"description": "Every request the desk door answers. The fields are the wire names: the `mind` addressed\n(the persona and pipeline requests say `instance`; the sets are separate), the verified\n`subject` and `authority` Bifrost vouches for (door context, never body fields), and the\n`deciders` of this call (1 to 64, distinct; the Mind never stores them). Closed: a key outside\nthe set refuses the whole payload.\n\nThe reads, `Forks` and `Fork`, carry NO `authority` (CultLib `list_request` and\n`read_request` are closed and have none; one that names it is refused whole). Their `subject`\nand `deciders` gate nothing: `deciders` only fills the card's `deciders` and the showability\ngate, `subject` is read by no read. `Forks` takes the contract's `list_request` fields flat\nbeside them (`status`, `cursor`, `limit`), each key written, `null` where absent. Huginn\nanswers every read with the whole record and Bifrost applies visibility; see `DeskResponse`\nfor the trust boundary. These docs and the doc of `Mind::desk_forks`/`Mind::desk_fork` are the\nreads spec.",
"oneOf": [
{
"type": "object",
@ -52,6 +52,10 @@
"Forks": {
"type": "object",
"properties": {
"cursor": {
"type": "string",
"pattern": "^c[1-9][0-9]*$"
},
"deciders": {
"type": "array",
"items": {
@ -61,9 +65,20 @@
"minItems": 1,
"uniqueItems": true
},
"limit": {
"$ref": "#/$defs/ForkLimit"
},
"mind": {
"$ref": "#/$defs/Slug"
},
"status": {
"description": "Which forks a list asks for; absent is both. `recorded` is a closed card.",
"type": "string",
"enum": [
"open",
"recorded"
]
},
"subject": {
"$ref": "#/$defs/Short"
}
@ -72,7 +87,10 @@
"required": [
"mind",
"subject",
"deciders"
"deciders",
"status",
"cursor",
"limit"
]
}
},
@ -185,6 +203,11 @@
"request_id"
]
},
"ForkLimit": {
"type": "integer",
"maximum": 50,
"minimum": 0
},
"RequestSchema": {
"description": "The request document's schema id; any other value fails to decode.",
"type": "string",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "DeskResponse",
"description": "What the door answers one desk request with. `Answered` is a stored answer, refused ones\nincluded (a stale fork, a non-decider and the rest are stored), fresh or replayed alike.\n`NotStored` is a request that stored nothing. `Refused` is any other Mind refusal; an\n`Unavailable` is Bifrost's to turn into its 503, and Huginn writes no document for it.",
"description": "What the door answers one desk request with. `Answered` is a stored answer, refused ones\nincluded (a stale fork, a non-decider and the rest are stored), fresh or replayed alike.\n`Forks` and `Fork` are the reads: one page of entries, or one entry, with the head they were\nall read at. `NotStored` is a request that stored nothing (an unknown fork included).\n`Refused` is any other Mind refusal; an `Unavailable` is Bifrost's to turn into its 503, and\nHuginn writes no document for it.\n\nThe trust boundary of the reads (ruling `desk-read-visibility-whole-record`): `Forks` and `Fork`\ncarry the WHOLE record, every answer's note included, to whatever the peer-checked socket\ncarries. Huginn strips nothing and gates nothing; Bifrost applies visibility at its door (a\nstripped read serves `note: null`, CultLib `fork-card.md`). Huginn must therefore be reachable\nonly by Bifrost's peer-checked socket: the peer-uid check IS the whole authentication, and a\nsecond reader of that socket is a note leak.",
"oneOf": [
{
"type": "object",
@ -35,6 +35,58 @@
"Answered"
]
},
{
"type": "object",
"properties": {
"Forks": {
"type": "object",
"properties": {
"as_of": {
"type": "integer",
"format": "uint64",
"minimum": 0
},
"list": {
"$ref": "#/$defs/ForkList"
}
},
"required": [
"list",
"as_of"
]
}
},
"additionalProperties": false,
"required": [
"Forks"
]
},
{
"type": "object",
"properties": {
"Fork": {
"type": "object",
"properties": {
"as_of": {
"type": "integer",
"format": "uint64",
"minimum": 0
},
"entry": {
"$ref": "#/$defs/ForkEntry"
}
},
"required": [
"entry",
"as_of"
]
}
},
"additionalProperties": false,
"required": [
"Fork"
]
},
{
"type": "object",
"properties": {
@ -68,6 +120,21 @@
"gamecult.desk_answer.v0"
]
},
"CardSchema": {
"description": "The card document's schema id.",
"type": "string",
"enum": [
"gamecult.fork.card.v0"
]
},
"CardStatus": {
"description": "Whether a ruling closed the fork.",
"type": "string",
"enum": [
"open",
"closed"
]
},
"DeskAuthority": {
"type": "string",
"enum": [
@ -76,6 +143,114 @@
"Member"
]
},
"EntrySchema": {
"description": "The entry document's schema id.",
"type": "string",
"enum": [
"gamecult.fork.entry.v0"
]
},
"ForkCard": {
"description": "`gamecult.fork.card.v0`: closed, her field order.",
"type": "object",
"properties": {
"context": {
"type": "string"
},
"deciders": {
"type": "array",
"items": {
"type": "string"
}
},
"depends": {
"type": "array",
"items": {
"type": "string"
}
},
"flag": {
"type": "string"
},
"fork_id": {
"type": "string"
},
"leans": {
"type": "string"
},
"options": {
"type": "array",
"items": {
"$ref": "#/$defs/ForkOption"
}
},
"options_hash": {
"type": "string"
},
"order": {
"type": "integer",
"format": "uint64",
"minimum": 0
},
"raised_in": {
"description": "A typed reference, as the question holds it (`raised_in`).",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"kind",
"id"
]
},
"recommendation": {
"type": "string"
},
"ruled_from": {
"type": "array",
"items": {
"type": "string"
}
},
"ruling": {
"type": "string"
},
"schemaVersion": {
"$ref": "#/$defs/CardSchema"
},
"status": {
"$ref": "#/$defs/CardStatus"
},
"title": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"schemaVersion",
"fork_id",
"title",
"context",
"options",
"recommendation",
"depends",
"leans",
"flag",
"raised_in",
"deciders",
"status",
"ruling",
"ruled_from",
"order",
"options_hash"
]
},
"ForkDeskAnswer": {
"description": "`gamecult.desk_answer.v0`: one stored answer as the desk shows it, her field order,\n`request_id` last. Every value is a stored fact; `answer_id`, `of` and `retracts` are the\nrecord keys in her `n<seq>` spelling.",
"type": "object",
@ -158,6 +333,121 @@
"request_id"
]
},
"ForkEntry": {
"description": "`gamecult.fork.entry.v0`: a card and its record.",
"type": "object",
"properties": {
"card": {
"$ref": "#/$defs/ForkCard"
},
"record": {
"$ref": "#/$defs/ForkRecord"
},
"schemaVersion": {
"$ref": "#/$defs/EntrySchema"
}
},
"additionalProperties": false,
"required": [
"schemaVersion",
"card",
"record"
]
},
"ForkList": {
"description": "`gamecult.fork.list.v0`: a page of entries, the counts over the showable set and the stamp.",
"type": "object",
"properties": {
"forks": {
"type": "array",
"items": {
"$ref": "#/$defs/ForkEntry"
}
},
"mind": {
"type": "string"
},
"next_cursor": {
"type": "string",
"pattern": "^c[1-9][0-9]*$"
},
"open_count": {
"type": "integer",
"format": "uint64",
"minimum": 0
},
"recorded_count": {
"type": "integer",
"format": "uint64",
"minimum": 0
},
"schemaVersion": {
"$ref": "#/$defs/ListSchema"
},
"stamp": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"schemaVersion",
"mind",
"stamp",
"open_count",
"recorded_count",
"forks",
"next_cursor"
]
},
"ForkOption": {
"description": "One shown option.",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"text": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"id",
"text"
]
},
"ForkRecord": {
"description": "`gamecult.fork.record.v0`: the answers in admission order and the derived counted answer.",
"type": "object",
"properties": {
"answers": {
"type": "array",
"items": {
"$ref": "#/$defs/ForkDeskAnswer"
}
},
"counted_answer_id": {
"type": "string"
},
"fork_id": {
"type": "string"
},
"options_hash": {
"type": "string"
},
"schemaVersion": {
"$ref": "#/$defs/RecordSchema"
}
},
"additionalProperties": false,
"required": [
"schemaVersion",
"fork_id",
"options_hash",
"counted_answer_id",
"answers"
]
},
"ForkRefusal": {
"description": "`gamecult.fork.refusal.v0`. `error` is a fixed sentence, never an echo of what was sent;\nfor `body_invalid` it names the field.",
"type": "object",
@ -188,6 +478,13 @@
"request_id_conflict"
]
},
"ListSchema": {
"description": "The list document's schema id.",
"type": "string",
"enum": [
"gamecult.fork.list.v0"
]
},
"MindRefusal": {
"description": "Every way a mind refuses. `Document` wraps the leaf's four (bounds,\nformats, key identity, foreign type); the rest are the organ's: store\nidentity, epoch, ownership, batch shape, references, and every cross-field\nand cross-document rule of admission.\n\nThe two sequenced kinds refuse in the same pair of shapes: an\n`OutOfSequence` when the writer's `sequence` is not the previous plus one,\nand `AlreadyResolved`/`AlreadyStewarded` when a record of that scope is\nstill in force.",
"oneOf": [
@ -1497,6 +1794,13 @@
}
]
},
"RecordSchema": {
"description": "The record document's schema id.",
"type": "string",
"enum": [
"gamecult.fork.record.v0"
]
},
"RefusalSchema": {
"description": "The refusal document's schema id.",
"type": "string",