Desk wire envelope: DeskRequest with Libby's wire names, deciders per call (max 64), generic envelope cores, desk schemas
envelope.rs cores are generic over service id, schema and payload; the mind's five functions and the desk's five instantiate them. desk_answer's attribution parameter is authority. huginn.desk_request.v1 and huginn.desk_response.v1 are derived and pinned. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ab1b150d2f
commit
a19fc5b494
5 changed files with 1972 additions and 46 deletions
|
|
@ -10,12 +10,12 @@
|
|||
//! stays the only owner of "counts": the response builder calls it at the head
|
||||
//! the response is read at, and nothing here stores it. Huginn writes
|
||||
//! `gamecult.desk_answer.v0` and its own refusals; the stale-fork display
|
||||
//! and the answer result are Bifrost's to attach and write. The envelope, the published
|
||||
//! schemas, the reads and the socket are other cuts.
|
||||
//! and the answer result are Bifrost's to attach and write. The request type, its
|
||||
//! constants and the published schemas are here; the reads and the socket are other cuts.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use eureka_pipeline::{
|
||||
DeskAuthority, DeskOutcome, DeskRefusal, PipelineDeskAnswer, PipelineDocument, PipelineKind, PipelineQuestion, RequestId, Sha256Hex, Short, Words,
|
||||
DeskAuthority, DeskOutcome, DeskRefusal, PipelineDeskAnswer, PipelineDocument, PipelineKind, PipelineQuestion, RequestId, Sha256Hex, Short, Slug, Words,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -55,7 +55,7 @@ fn required<'de, D: serde::Deserializer<'de>, T: Deserialize<'de>>(deserializer:
|
|||
}
|
||||
|
||||
/// `gamecult.fork.answer_request.v0`: closed, her field order. The wire types hold text; the
|
||||
/// shape step of `Mind::answer_desk` judges it. The subject and the attribution are the
|
||||
/// shape step of `Mind::answer_desk` judges it. The subject and the authority are the
|
||||
/// door's context and are no field here.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
|
|
@ -180,6 +180,126 @@ pub enum DeskResponse {
|
|||
Refused(MindRefusal),
|
||||
}
|
||||
|
||||
impl DeskResponse {
|
||||
/// The envelope's status: `rejected` exactly when nothing was stored or the Mind refused.
|
||||
pub fn status(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Answered { .. } => "accepted",
|
||||
Self::NotStored(_) | Self::Refused(_) => "rejected",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The envelope's `service_id` for the desk door.
|
||||
pub const DESK_SERVICE_ID: &str = "huginn.desk";
|
||||
pub const DESK_REQUEST_SCHEMA: &str = "huginn.desk_request.v1";
|
||||
pub const DESK_RESPONSE_SCHEMA: &str = "huginn.desk_response.v1";
|
||||
|
||||
/// The published schemas, derived from `DeskRequest` and `DeskResponse` and pinned byte for byte
|
||||
/// by `published_desk_schemas_match_derivation`.
|
||||
pub const DESK_REQUEST_SCHEMA_JSON: &str = include_str!("../../../schemas/cultnet/huginn.desk_request.v1.schema.json");
|
||||
pub const DESK_RESPONSE_SCHEMA_JSON: &str = include_str!("../../../schemas/cultnet/huginn.desk_response.v1.schema.json");
|
||||
|
||||
/// The frame bounds the door enforces, on the encoded CultNet message.
|
||||
pub const DESK_MAX_REQUEST_BYTES: usize = 65536;
|
||||
pub const DESK_MAX_RESPONSE_BYTES: usize = 65536;
|
||||
|
||||
/// Most deciders one call carries (CultLib `card.deciders` `maxItems`).
|
||||
pub const DESK_DECIDERS_MAX: usize = 64;
|
||||
|
||||
/// A decider list, read whole or not at all: at most `DESK_DECIDERS_MAX`, no subject twice. A
|
||||
/// bad list is the decoder's refusal, which names the field and never quotes a subject.
|
||||
fn deciders<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<Short>, D::Error> {
|
||||
struct List;
|
||||
impl<'de> serde::de::Visitor<'de> for List {
|
||||
type Value = Vec<Short>;
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a list of distinct deciders")
|
||||
}
|
||||
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<Vec<Short>, A::Error> {
|
||||
let mut list = Vec::new();
|
||||
while let Some(subject) = seq.next_element::<Short>()? {
|
||||
if list.len() == DESK_DECIDERS_MAX {
|
||||
return Err(serde::de::Error::custom("too many deciders"));
|
||||
}
|
||||
if list.contains(&subject) {
|
||||
return Err(serde::de::Error::custom("a decider is listed twice"));
|
||||
}
|
||||
list.push(subject);
|
||||
}
|
||||
Ok(list)
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_seq(List)
|
||||
}
|
||||
|
||||
fn deciders_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
schemars::json_schema!({
|
||||
"type": "array",
|
||||
"items": generator.subschema_for::<Short>(),
|
||||
"maxItems": DESK_DECIDERS_MAX,
|
||||
"uniqueItems": true
|
||||
})
|
||||
}
|
||||
|
||||
/// Every request the desk door answers. The fields are the wire names: the `mind` addressed
|
||||
/// (the persona and pipeline requests say `instance`; the sets are separate), the verified
|
||||
/// `subject` and `authority` Bifrost vouches for (door context, never body fields), and the
|
||||
/// `deciders` of this call, which the Mind never stores. Closed: a key outside the set refuses
|
||||
/// the whole payload. The reads answer in follow-up desk-wire-reads.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum DeskRequest {
|
||||
Answer {
|
||||
mind: Slug,
|
||||
subject: Short,
|
||||
authority: DeskAuthority,
|
||||
#[serde(deserialize_with = "deciders")]
|
||||
#[schemars(schema_with = "deciders_schema")]
|
||||
deciders: Vec<Short>,
|
||||
request: ForkAnswerRequest,
|
||||
},
|
||||
Forks {
|
||||
mind: Slug,
|
||||
subject: Short,
|
||||
#[serde(deserialize_with = "deciders")]
|
||||
#[schemars(schema_with = "deciders_schema")]
|
||||
deciders: Vec<Short>,
|
||||
},
|
||||
Fork {
|
||||
mind: Slug,
|
||||
subject: Short,
|
||||
#[serde(deserialize_with = "deciders")]
|
||||
#[schemars(schema_with = "deciders_schema")]
|
||||
deciders: Vec<Short>,
|
||||
fork_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl DeskRequest {
|
||||
/// The envelope's `operation`.
|
||||
pub fn operation(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Answer { .. } => "answer",
|
||||
Self::Forks { .. } => "forks",
|
||||
Self::Fork { .. } => "fork",
|
||||
}
|
||||
}
|
||||
|
||||
/// The mind the request addresses, for `Mind::require_instance`.
|
||||
pub fn mind(&self) -> &Slug {
|
||||
match self {
|
||||
Self::Answer { mind, .. } | Self::Forks { mind, .. } | Self::Fork { mind, .. } => mind,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deciders(&self) -> &[Short] {
|
||||
match self {
|
||||
Self::Answer { deciders, .. } | Self::Forks { deciders, .. } | Self::Fork { deciders, .. } => deciders,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- the one conversion between her answer ids and the record keys ----
|
||||
|
||||
/// The key prefix of one fork's answers. The only place a desk answer key is spelled.
|
||||
|
|
@ -324,17 +444,17 @@ impl<S: MindStore> Mind<S> {
|
|||
/// One desk answer from the door: the wire request converted, the question gated, the answer
|
||||
/// decided and stored (or replayed) by `answer_desk`, and the stored answer projected with the
|
||||
/// derived count at the head it was read at. The flag is `true` when nothing was committed (a
|
||||
/// replay); it is for the door and is never encoded. `subject` and `attribution` are the verified
|
||||
/// replay); it is for the door and is never encoded. `subject` and `authority` are the verified
|
||||
/// caller's, `deciders` the door's list.
|
||||
pub fn desk_answer(
|
||||
&mut self,
|
||||
deciders: &[Short],
|
||||
subject: Short,
|
||||
attribution: DeskAuthority,
|
||||
authority: DeskAuthority,
|
||||
request: ForkAnswerRequest,
|
||||
now: DateTime<Utc>,
|
||||
) -> (DeskResponse, bool) {
|
||||
match self.desk_response(deciders, subject, attribution, &request, now) {
|
||||
match self.desk_response(deciders, subject, authority, &request, now) {
|
||||
Ok(done) => done,
|
||||
Err(refused) => (door_refusal(refused), false),
|
||||
}
|
||||
|
|
@ -344,7 +464,7 @@ impl<S: MindStore> Mind<S> {
|
|||
&mut self,
|
||||
deciders: &[Short],
|
||||
subject: Short,
|
||||
attribution: DeskAuthority,
|
||||
authority: DeskAuthority,
|
||||
request: &ForkAnswerRequest,
|
||||
now: DateTime<Utc>,
|
||||
) -> Result<(DeskResponse, bool), MindRefusal> {
|
||||
|
|
@ -361,7 +481,7 @@ impl<S: MindStore> Mind<S> {
|
|||
if unshowable {
|
||||
return Ok((DeskResponse::NotStored(fork_unknown()), false));
|
||||
}
|
||||
let stamp = DeskStamp { answerer: subject, authority: attribution, deciders: deciders.to_vec() };
|
||||
let stamp = DeskStamp { answerer: subject, authority, deciders: deciders.to_vec() };
|
||||
let (key, replayed) = match self.answer_desk(stamp, sent, now) {
|
||||
DeskAnswerOutcome::Stored { answer, .. } => (answer.0, false),
|
||||
DeskAnswerOutcome::Replayed { answer, .. } => (answer.0, true),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use anyhow::Result;
|
|||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use cultnet_rs::CultNetMessage;
|
||||
use crate::desk_wire::{DESK_REQUEST_SCHEMA, DESK_RESPONSE_SCHEMA, DESK_SERVICE_ID, DeskRequest, DeskResponse};
|
||||
use crate::wire::{HuginnMindRequest, HuginnMindResponse, MIND_REQUEST_SCHEMA, MIND_RESPONSE_SCHEMA, MIND_SERVICE_ID};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -39,17 +40,20 @@ fn encode_payload<T: Serialize>(value: &T) -> Result<String> {
|
|||
Ok(STANDARD.encode(rmp_serde::to_vec_named(value)?))
|
||||
}
|
||||
|
||||
/// One request, addressed to the mind service and naming its own operation.
|
||||
pub fn encode_request(
|
||||
/// One request to `service_id`, naming its own operation, its payload under `payload_schema`.
|
||||
fn encode_operation_request<T: Serialize>(
|
||||
message_id: &str,
|
||||
request: &HuginnMindRequest,
|
||||
service_id: &str,
|
||||
payload_schema: &str,
|
||||
operation: &str,
|
||||
request: &T,
|
||||
source_runtime_id: Option<String>,
|
||||
) -> Result<CultNetMessage> {
|
||||
Ok(CultNetMessage::OperationRequest {
|
||||
message_id: message_id.to_string(),
|
||||
service_id: MIND_SERVICE_ID.to_string(),
|
||||
operation: request.operation().to_string(),
|
||||
payload_schema: MIND_REQUEST_SCHEMA.to_string(),
|
||||
service_id: service_id.to_string(),
|
||||
operation: operation.to_string(),
|
||||
payload_schema: payload_schema.to_string(),
|
||||
payload_encoding: "messagepack-base64".to_string(),
|
||||
payload: encode_payload(request)?,
|
||||
source_runtime_id,
|
||||
|
|
@ -59,43 +63,49 @@ pub fn encode_request(
|
|||
|
||||
/// The message, or the failure that says why it is not a request of this
|
||||
/// service. The envelope's `operation` string is checked against the decoded
|
||||
/// request rather than trusted, so a payload cannot arrive under another
|
||||
/// operation's name.
|
||||
pub fn decode_request(message: &CultNetMessage) -> Result<(String, HuginnMindRequest), OperationFailure> {
|
||||
let CultNetMessage::OperationRequest { message_id, service_id, operation, payload_schema, payload, .. } = message
|
||||
/// request (`operation_of`) rather than trusted, so a payload cannot arrive
|
||||
/// under another operation's name.
|
||||
fn decode_operation_request<T: serde::de::DeserializeOwned>(
|
||||
message: &CultNetMessage,
|
||||
service_id: &str,
|
||||
payload_schema: &str,
|
||||
operation_of: impl Fn(&T) -> &'static str,
|
||||
) -> Result<(String, T), OperationFailure> {
|
||||
let CultNetMessage::OperationRequest { message_id, service_id: sent_service, operation, payload_schema: sent_schema, payload, .. } = message
|
||||
else {
|
||||
return Err(OperationFailure::new(
|
||||
"not-an-operation-request",
|
||||
"huginn.mind reads cultnet.operation_request.v0",
|
||||
format!("{service_id} reads cultnet.operation_request.v0"),
|
||||
));
|
||||
};
|
||||
if service_id != MIND_SERVICE_ID {
|
||||
return Err(OperationFailure::new("wrong-service", format!("the service is not {MIND_SERVICE_ID}")));
|
||||
if sent_service != service_id {
|
||||
return Err(OperationFailure::new("wrong-service", format!("the service is not {service_id}")));
|
||||
}
|
||||
if payload_schema != MIND_REQUEST_SCHEMA {
|
||||
if sent_schema != payload_schema {
|
||||
return Err(OperationFailure::new(
|
||||
"wrong-payload-schema",
|
||||
format!("the payload schema is not {MIND_REQUEST_SCHEMA}"),
|
||||
format!("the payload schema is not {payload_schema}"),
|
||||
));
|
||||
}
|
||||
let bytes = STANDARD
|
||||
.decode(payload)
|
||||
.map_err(|_| OperationFailure::new("payload-not-base64", "the payload is not standard base64"))?;
|
||||
let request = read_request(&bytes)?;
|
||||
if operation != request.operation() {
|
||||
let request = read_payload::<T>(&bytes)?;
|
||||
let expected = operation_of(&request);
|
||||
if operation != expected {
|
||||
return Err(OperationFailure::new(
|
||||
"operation-mismatch",
|
||||
format!("the envelope's operation is not the payload's {}", request.operation()),
|
||||
format!("the envelope's operation is not the payload's {expected}"),
|
||||
));
|
||||
}
|
||||
Ok((message_id.clone(), request))
|
||||
}
|
||||
|
||||
/// The payload as a request, or a failure naming the field path and the class
|
||||
/// The payload as a `T`, or a failure naming the field path and the class
|
||||
/// of error and nothing the caller sent: a decoder's own text quotes the
|
||||
/// offending value (an unknown variant, an integer), so no decoder text
|
||||
/// leaves this function.
|
||||
fn read_request(bytes: &[u8]) -> Result<HuginnMindRequest, OperationFailure> {
|
||||
fn read_payload<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, OperationFailure> {
|
||||
use rmp_serde::decode::Error as Decode;
|
||||
let mut decoder = rmp_serde::Deserializer::new(bytes);
|
||||
serde_path_to_error::deserialize(&mut decoder).map_err(|error| {
|
||||
|
|
@ -114,19 +124,22 @@ fn read_request(bytes: &[u8]) -> Result<HuginnMindRequest, OperationFailure> {
|
|||
})
|
||||
}
|
||||
|
||||
/// One answer, whose status the response derives.
|
||||
pub fn encode_response(
|
||||
/// One answer, with the status its caller derived.
|
||||
fn encode_operation_response<T: Serialize>(
|
||||
message_id: &str,
|
||||
service_id: &str,
|
||||
payload_schema: &str,
|
||||
operation: &str,
|
||||
response: &HuginnMindResponse,
|
||||
status: &str,
|
||||
response: &T,
|
||||
source_runtime_id: &str,
|
||||
) -> Result<CultNetMessage> {
|
||||
Ok(CultNetMessage::OperationResponse {
|
||||
message_id: message_id.to_string(),
|
||||
service_id: MIND_SERVICE_ID.to_string(),
|
||||
service_id: service_id.to_string(),
|
||||
operation: operation.to_string(),
|
||||
status: response.status().to_string(),
|
||||
payload_schema: MIND_RESPONSE_SCHEMA.to_string(),
|
||||
status: status.to_string(),
|
||||
payload_schema: payload_schema.to_string(),
|
||||
payload_encoding: "messagepack-base64".to_string(),
|
||||
payload: encode_payload(response)?,
|
||||
diagnostics: vec![],
|
||||
|
|
@ -136,15 +149,16 @@ pub fn encode_response(
|
|||
|
||||
/// The answer to an envelope that never reached a mind. Infallible: the
|
||||
/// failure is two strings and the daemon must always be able to say no.
|
||||
pub fn encode_failure(
|
||||
fn encode_operation_failure(
|
||||
message_id: &str,
|
||||
service_id: &str,
|
||||
operation: &str,
|
||||
failure: &OperationFailure,
|
||||
source_runtime_id: &str,
|
||||
) -> CultNetMessage {
|
||||
CultNetMessage::OperationResponse {
|
||||
message_id: message_id.to_string(),
|
||||
service_id: MIND_SERVICE_ID.to_string(),
|
||||
service_id: service_id.to_string(),
|
||||
operation: operation.to_string(),
|
||||
status: "rejected".to_string(),
|
||||
payload_schema: FAILURE_SCHEMA.to_string(),
|
||||
|
|
@ -155,21 +169,78 @@ pub fn encode_failure(
|
|||
}
|
||||
}
|
||||
|
||||
/// The client's side: the correlation key and either the mind's answer or the
|
||||
/// failure that says the envelope never reached one.
|
||||
pub fn decode_response(message: &CultNetMessage) -> Result<(String, Result<HuginnMindResponse, OperationFailure>)> {
|
||||
/// The client's side: the correlation key and either the service's answer or
|
||||
/// the failure that says the envelope never reached one.
|
||||
fn decode_operation_response<T: serde::de::DeserializeOwned>(
|
||||
message: &CultNetMessage,
|
||||
service_id: &str,
|
||||
response_schema: &str,
|
||||
) -> Result<(String, Result<T, OperationFailure>)> {
|
||||
let CultNetMessage::OperationResponse { message_id, payload_schema, payload, .. } = message else {
|
||||
anyhow::bail!("huginn.mind answers with cultnet.operation_response.v0");
|
||||
anyhow::bail!("{service_id} answers with cultnet.operation_response.v0");
|
||||
};
|
||||
let bytes = STANDARD.decode(payload)?;
|
||||
let answer = match payload_schema.as_str() {
|
||||
MIND_RESPONSE_SCHEMA => Ok(rmp_serde::from_slice::<HuginnMindResponse>(&bytes)?),
|
||||
FAILURE_SCHEMA => Err(rmp_serde::from_slice::<OperationFailure>(&bytes)?),
|
||||
other => anyhow::bail!("{other} is neither {MIND_RESPONSE_SCHEMA} nor {FAILURE_SCHEMA}"),
|
||||
let answer = if payload_schema == response_schema {
|
||||
Ok(rmp_serde::from_slice::<T>(&bytes)?)
|
||||
} else if payload_schema == FAILURE_SCHEMA {
|
||||
Err(rmp_serde::from_slice::<OperationFailure>(&bytes)?)
|
||||
} else {
|
||||
anyhow::bail!("{payload_schema} is neither {response_schema} nor {FAILURE_SCHEMA}")
|
||||
};
|
||||
Ok((message_id.clone(), answer))
|
||||
}
|
||||
|
||||
/// One request, addressed to the mind service.
|
||||
pub fn encode_request(
|
||||
message_id: &str,
|
||||
request: &HuginnMindRequest,
|
||||
source_runtime_id: Option<String>,
|
||||
) -> Result<CultNetMessage> {
|
||||
encode_operation_request(message_id, MIND_SERVICE_ID, MIND_REQUEST_SCHEMA, request.operation(), request, source_runtime_id)
|
||||
}
|
||||
|
||||
pub fn decode_request(message: &CultNetMessage) -> Result<(String, HuginnMindRequest), OperationFailure> {
|
||||
decode_operation_request(message, MIND_SERVICE_ID, MIND_REQUEST_SCHEMA, HuginnMindRequest::operation)
|
||||
}
|
||||
|
||||
pub fn encode_response(
|
||||
message_id: &str,
|
||||
operation: &str,
|
||||
response: &HuginnMindResponse,
|
||||
source_runtime_id: &str,
|
||||
) -> Result<CultNetMessage> {
|
||||
encode_operation_response(message_id, MIND_SERVICE_ID, MIND_RESPONSE_SCHEMA, operation, response.status(), response, source_runtime_id)
|
||||
}
|
||||
|
||||
pub fn encode_failure(message_id: &str, operation: &str, failure: &OperationFailure, source_runtime_id: &str) -> CultNetMessage {
|
||||
encode_operation_failure(message_id, MIND_SERVICE_ID, operation, failure, source_runtime_id)
|
||||
}
|
||||
|
||||
pub fn decode_response(message: &CultNetMessage) -> Result<(String, Result<HuginnMindResponse, OperationFailure>)> {
|
||||
decode_operation_response(message, MIND_SERVICE_ID, MIND_RESPONSE_SCHEMA)
|
||||
}
|
||||
|
||||
/// One request, addressed to the desk service.
|
||||
pub fn encode_desk_request(message_id: &str, request: &DeskRequest, source_runtime_id: Option<String>) -> Result<CultNetMessage> {
|
||||
encode_operation_request(message_id, DESK_SERVICE_ID, DESK_REQUEST_SCHEMA, request.operation(), request, source_runtime_id)
|
||||
}
|
||||
|
||||
pub fn decode_desk_request(message: &CultNetMessage) -> Result<(String, DeskRequest), OperationFailure> {
|
||||
decode_operation_request(message, DESK_SERVICE_ID, DESK_REQUEST_SCHEMA, DeskRequest::operation)
|
||||
}
|
||||
|
||||
pub fn encode_desk_response(message_id: &str, operation: &str, response: &DeskResponse, source_runtime_id: &str) -> Result<CultNetMessage> {
|
||||
encode_operation_response(message_id, DESK_SERVICE_ID, DESK_RESPONSE_SCHEMA, operation, response.status(), response, source_runtime_id)
|
||||
}
|
||||
|
||||
pub fn encode_desk_failure(message_id: &str, operation: &str, failure: &OperationFailure, source_runtime_id: &str) -> CultNetMessage {
|
||||
encode_operation_failure(message_id, DESK_SERVICE_ID, operation, failure, source_runtime_id)
|
||||
}
|
||||
|
||||
pub fn decode_desk_response(message: &CultNetMessage) -> Result<(String, Result<DeskResponse, OperationFailure>)> {
|
||||
decode_operation_response(message, DESK_SERVICE_ID, DESK_RESPONSE_SCHEMA)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -556,7 +627,7 @@ mod tests {
|
|||
};
|
||||
let response = DeskResponse::Answered { answer, counted_answer_id: Some("n4294967295".into()), as_of: u64::MAX };
|
||||
let message = encode_desk_response("m-11", "answer", &response, &long(200)).unwrap();
|
||||
assert!(wire(&message) <= DESK_MAX_RESPONSE_BYTES && wire(&message) > DESK_MAX_RESPONSE_BYTES / 8, "{}", wire(&message));
|
||||
assert!(wire(&message) <= DESK_MAX_RESPONSE_BYTES && wire(&message) > 4000, "{}", wire(&message));
|
||||
assert_eq!(decode_desk_response(&message).unwrap().1, Ok(response));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
203
schemas/cultnet/huginn.desk_request.v1.schema.json
Normal file
203
schemas/cultnet/huginn.desk_request.v1.schema.json
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
{
|
||||
"$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, which the Mind never stores. Closed: a key outside the set refuses\nthe whole payload. The reads answer in follow-up desk-wire-reads.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Answer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"authority": {
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
"deciders": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"mind": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/$defs/ForkAnswerRequest"
|
||||
},
|
||||
"subject": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"mind",
|
||||
"subject",
|
||||
"authority",
|
||||
"deciders",
|
||||
"request"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Answer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Forks": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"deciders": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"mind": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"subject": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"mind",
|
||||
"subject",
|
||||
"deciders"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Forks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Fork": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"deciders": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"fork_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"mind": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"subject": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"mind",
|
||||
"subject",
|
||||
"deciders",
|
||||
"fork_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Fork"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$defs": {
|
||||
"DeskAuthority": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Steward",
|
||||
"Member"
|
||||
]
|
||||
},
|
||||
"ForkAnswerRequest": {
|
||||
"description": "`gamecult.fork.answer_request.v0`: closed, her field order. The wire types hold text; the\nshape step of `Mind::answer_desk` judges it. The subject and the authority are the\ndoor's context and are no field here.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answer": {
|
||||
"description": "The person's words: a choice, a note, or both.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"choice": {
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"choice",
|
||||
"note"
|
||||
]
|
||||
},
|
||||
"answered_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"fork_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"options_hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"request_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"retracted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"retracts": {
|
||||
"type": "string"
|
||||
},
|
||||
"schemaVersion": {
|
||||
"$ref": "#/$defs/RequestSchema"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"fork_id",
|
||||
"options_hash",
|
||||
"answer",
|
||||
"retracted",
|
||||
"retracts",
|
||||
"answered_at",
|
||||
"request_id"
|
||||
]
|
||||
},
|
||||
"RequestSchema": {
|
||||
"description": "The request document's schema id; any other value fails to decode.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"gamecult.fork.answer_request.v0"
|
||||
]
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"type": "string",
|
||||
"maxLength": 64
|
||||
}
|
||||
}
|
||||
}
|
||||
1512
schemas/cultnet/huginn.desk_response.v1.schema.json
Normal file
1512
schemas/cultnet/huginn.desk_response.v1.schema.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -262,6 +262,26 @@
|
|||
"schemaVersion": "huginn.mind_response.v1",
|
||||
"title": "Huginn mind response",
|
||||
"path": "huginn.mind_response.v1.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "huginn.desk_request.v1",
|
||||
"kind": "wire_message",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "huginn.desk_request.v1",
|
||||
"title": "Huginn desk request",
|
||||
"path": "huginn.desk_request.v1.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "huginn.desk_response.v1",
|
||||
"kind": "wire_message",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "huginn.desk_response.v1",
|
||||
"title": "Huginn desk response",
|
||||
"path": "huginn.desk_response.v1.schema.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue