huginn-mind: desk answers take the decider list as input, store attribution, received_at and request_id
DeskStamp carries answerer, attribution (Operator/Steward/Member per CultLib PR #72) and the decider list; membership decides rule 5, the attribution never does. answer_desk and DeskStamp are crate-private. The receipt names the operator only for the operator's account. counted_answer and R1 consider options_hash. A stored answer keeps received_at, claim_in_future, request_id and a body digest; the same (answerer, request_id) and body replays the stored outcome and writes nothing, another body is request_id_conflict. Notes are Words (1..=4000 bytes, not blank); answered_at must be UTC. The vector file is re-sliced byte for byte from PR #72 head 2dea6c56. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f1691ffc0d
commit
dcc51c170f
10 changed files with 679 additions and 447 deletions
|
|
@ -32,7 +32,7 @@ sample.research eureka-state:research:p1.scope epiphany.pipeline.research.v2 Epi
|
|||
sample.idea eureka-state:idea:p1.i1 epiphany.pipeline.idea.v2 EpiphanyPipelineIdeaDocument 9189a863616d706169676eac657572656b612d7374617465a470617373a27031a56c6162656ca26931a57469746c65b24f6e65206e6f74652070657220636c61696da469646561d9204b65657020612070617373206173206d616e7920736d616c6c206e6f7465732ea974726164656f666673b74d6f726520646f63756d656e747320746f206c6973742ea872657374735f6f6e9182a46b696e64a85265736561726368a26964be657572656b612d73746174653a72657365617263683a70312e73636f7065a6746f776172649182a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131ab70726f706f7365645f6f6eaa323032362d30392d3135
|
||||
sample.reply yggdrasil:reply:mind-2026-10-09-1-r1 epiphany.pipeline.reply.v2 EpiphanyPipelineReplyDocument 9185a8696e7374616e6365a979676764726173696ca56c6162656cb46d696e642d323032362d31302d30392d312d7231a372756e82a46b696e64a352756ea26964bf79676764726173696c3a72756e3a6d696e642d323032362d31302d30392d31a7616e73776572739182a46b696e64a8466f6c6c6f775570a26964d923657572656b612d73746174653a666f6c6c6f775f75703a61736b2d7468652d73656c66a474657874d93054686520736861646572207061737320697320646f6e653b2074686520736f756e647320617265204c6962627927732e
|
||||
sample.thought yggdrasil:thought:t1793000000 epiphany.pipeline.thought.v2 EpiphanyPipelineThoughtDocument 91de0014a8696e7374616e6365a979676764726173696ca56c6162656cab7431373933303030303030a474657874d93d54686520736f756e647320617265204c6962627927733b2074686520736861646572732077657265206e6576657220746865206861726420706172742ea572656164739182a46b696e64a64d656d6f7279a26964d92879676764726173696c3a6d656d6f72793a323032362d31302d3331743034313230302d74302d6d30af706572736f6e615f72656365697074b06d696e642d636f6d6d69742d30613162a86964656e74697479be457572656b612c207368652f6865722c20706c61696e2d73706f6b656e2ea7636f6e74657874d92056616c7565733a20636f686572656e6365206f7665722076656c6f636974792ea87374696d756c7573bd4c696262792061736b65642061626f75742074686520736f756e64732eaf70726f6a6563746f725f6d6f64656cb070726f6a6563746f723a6c6174657374aa70726f6a656374696f6ed92949206b656570207475726e696e67206f7665722077686f20686f6c64732074686520736f756e64732eaa626173655f6d6f64656cab626173653a6c6174657374b474656d70657261747572655f7065726d696c6c65cd03e8ae6d696e5f705f7065726d696c6c6532aa6d61785f746f6b656e73cca0aa63616e6469646174657392d93d54686520736f756e647320617265204c6962627927733b2074686520736861646572732077657265206e6576657220746865206861726420706172742eaf41207365636f6e642064726966742eb06e6561726573745f7065726d696c6c6592cd0136cd0366a663686f73656e00ac70726f6a6563746f725f6d73cd1068ac636f6e74696e7565725f6d73cd238cae70726f6d70745f76657273696f6ebd67686f73746c696768743d633433656235343b20776f7264733d313830
|
||||
sample.desk_answer eureka-state:desk_answer:Q1.n1 epiphany.pipeline.desk_answer.v2 EpiphanyPipelineDeskAnswerDocument 918aa863616d706169676eac657572656b612d7374617465a4666f726bb8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501ac6f7074696f6e735f68617368d94063636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363a663686f696365a161a46e6f7465b853686172652069742c207769746820746865206d61726b2eab616e7377657265645f6174b4323032362d31312d31305431353a30303a30305aa8616e737765726572a6757365722d37a9617574686f72697479a744656369646572a76f7574636f6d6581a85265766973696f6e81a26f66be657572656b612d73746174653a6465736b5f616e737765723a51312e6e31
|
||||
sample.desk_answer eureka-state:desk_answer:Q1.n1 epiphany.pipeline.desk_answer.v2 EpiphanyPipelineDeskAnswerDocument 918ea863616d706169676eac657572656b612d7374617465a4666f726bb8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501ac6f7074696f6e735f68617368d94063636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363a663686f696365a161a46e6f7465b853686172652069742c207769746820746865206d61726b2eab616e7377657265645f6174b4323032362d31312d31305431353a30303a30305aab72656365697665645f6174b4323032362d31312d31305431353a30303a30335aaf636c61696d5f696e5f667574757265c2aa726571756573745f6964d92436663163306135322d336237652d346431612d396335382d306532643761346239663133ab626f64795f736861323536d94064646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464a8616e737765726572a6757365722d37a9617574686f72697479a64d656d626572a76f7574636f6d6581a85265766973696f6e81a26f66be657572656b612d73746174653a6465736b5f616e737765723a51312e6e31
|
||||
resolution.superseded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581aa5375706572736564656481a262799282a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a523882a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-none eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a26279c0a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-some eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a2627982a46b696e64a94375745265706f7274a26964d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
|
|
|
|||
|
|
@ -272,6 +272,27 @@ fn symbol_text(field: &str, value: &str) -> Result<(), PipelineRefusal> {
|
|||
within(128)(field, value)
|
||||
}
|
||||
|
||||
/// A lowercase D-format UUID: 8-4-4-4-12 lowercase hex digits, any version.
|
||||
fn uuid_text(field: &str, value: &str) -> Result<(), PipelineRefusal> {
|
||||
let shaped = value.len() == 36
|
||||
&& value.bytes().enumerate().all(|(at, byte)| match at {
|
||||
8 | 13 | 18 | 23 => byte == b'-',
|
||||
_ => matches!(byte, b'0'..=b'9' | b'a'..=b'f'),
|
||||
});
|
||||
if !shaped {
|
||||
return Err(format_error(field, value));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A person's words: 1 to 4000 UTF-8 bytes and not blank (`str::trim` leaves something).
|
||||
fn words_text(field: &str, value: &str) -> Result<(), PipelineRefusal> {
|
||||
if value.trim().is_empty() {
|
||||
return Err(format_error(field, value));
|
||||
}
|
||||
within(4000)(field, value)
|
||||
}
|
||||
|
||||
bounded_text! {
|
||||
/// Text of at most 200 UTF-8 bytes.
|
||||
Short = 200, |field, value| within(200)(field, value);
|
||||
|
|
@ -290,6 +311,11 @@ bounded_text! {
|
|||
Symbol = 128, symbol_text;
|
||||
/// Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.
|
||||
Para = 4000, |field, value| within(4000)(field, value);
|
||||
/// A person's words, verbatim: 1 to 4,000 UTF-8 bytes, not blank (CultLib PR #72, `answer.note`).
|
||||
#[schemars(extend("minLength" = 1))]
|
||||
Words = 4000, words_text;
|
||||
/// A client's retry token: a lowercase D-format UUID.
|
||||
RequestId = 36, uuid_text;
|
||||
/// A key label: `[A-Za-z0-9_-]{1,64}`.
|
||||
Label = 64, label_text;
|
||||
/// A campaign slug or key segment: label parts joined by `.`.
|
||||
|
|
@ -738,8 +764,9 @@ unit_enums! {
|
|||
RunOperator { Operator, Mind }
|
||||
RunTurn { PersonaTurn, SelfRun, Sleep }
|
||||
MemorySort { Asked, Decided, Committed, Learned, Did, Idea, Rapport }
|
||||
// What a desk answerer is, as the receiver stamped it (never a client's claim).
|
||||
DeskAuthority { Operator, Decider }
|
||||
// The role the verified account holds, an attribution copied from Bifrost's state (CultLib PR #72,
|
||||
// fork-card.md). It confers nothing: whether an answerer may decide is the decider list's, never this.
|
||||
DeskAuthority { Operator, Steward, Member }
|
||||
// Why a desk answer was refused and stored: binding spec 1.3 steps 4-7 and R1.
|
||||
DeskRefusal { StaleFork, NotADecider, ForkClosed, ChoiceInvalid, BadRetraction, AlreadyRetracted }
|
||||
}
|
||||
|
|
@ -843,13 +870,20 @@ value_types! {
|
|||
promises: Vec<Promise>[64],
|
||||
}
|
||||
/// One answer the fork desk's receiver passed to the Mind, refused ones included.
|
||||
/// `answerer` and `authority` are stamped by the Mind from the verified caller and
|
||||
/// are absent from the request type; `answered_at` is the client's claim kept as
|
||||
/// written, display only: order is `sequence`, the receiver's order. `sequence` is
|
||||
/// per question and is the last part of the key (the answer id).
|
||||
/// `answerer` and `authority` (an attribution, never a decision) are stamped by the
|
||||
/// Mind from the verified caller and are absent from the request type; `answered_at`
|
||||
/// is the client's claim kept as written, display only: order is `sequence`, the
|
||||
/// receiver's order. `received_at` is the Mind's admission time (UTC) and
|
||||
/// `claim_in_future` is set when `answered_at` is more than five minutes after it.
|
||||
/// `request_id` is the client's retry token and `body_sha256` the digest of the
|
||||
/// request it came with: the pair that makes a retry the same answer, never a new
|
||||
/// one; the key is `(answerer, request_id)`. A record holds no card: a replayed
|
||||
/// `stale_fork` is shown the current card. `sequence` is per question and is the
|
||||
/// last part of the key (the answer id).
|
||||
pub struct PipelineDeskAnswer {
|
||||
campaign: Slug, fork: Short, sequence: u32, options_hash: Sha256Hex, choice: Option<Label>, note: Option<Para>,
|
||||
answered_at: Short, answerer: Short, authority: Option<DeskAuthority>, outcome: DeskOutcome,
|
||||
campaign: Slug, fork: Short, sequence: u32, options_hash: Sha256Hex, choice: Option<Label>, note: Option<Words>,
|
||||
answered_at: Short, received_at: Short, claim_in_future: bool, request_id: RequestId, body_sha256: Sha256Hex,
|
||||
answerer: Short, authority: DeskAuthority, outcome: DeskOutcome,
|
||||
}
|
||||
pub struct PipelineVerdict { campaign: Slug, cut_report: Short, pass: u32, range: CommitRange, claims: Vec<VerdictClaim>[64] }
|
||||
pub struct PipelineFinding {
|
||||
|
|
@ -1837,7 +1871,8 @@ mod tests {
|
|||
(D::DeskAnswer(PipelineDeskAnswer {
|
||||
campaign: slug(CAMPAIGN), fork: id("question", "Q1"), sequence: 1, options_hash: Sha256Hex("c".repeat(64)),
|
||||
choice: Some(l("a")), note: Some("Share it, with the mark.".into()), answered_at: s("2026-11-10T15:00:00Z"),
|
||||
answerer: s("user-7"), authority: Some(DeskAuthority::Decider),
|
||||
received_at: s("2026-11-10T15:00:03Z"), claim_in_future: false, request_id: RequestId("6f1c0a52-3b7e-4d1a-9c58-0e2d7a4b9f13".into()), body_sha256: Sha256Hex("d".repeat(64)),
|
||||
answerer: s("user-7"), authority: DeskAuthority::Member,
|
||||
outcome: DeskOutcome::Revision { of: id("desk_answer", "Q1.n1") },
|
||||
}), format!("{CAMPAIGN}:desk_answer:Q1.n1")),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,19 +3,25 @@
|
|||
//! `Mind::answer_desk` is the one owner of whether a desk answer counts, its
|
||||
//! sequence, its stored outcome and the stamping of `answerer` and `authority`
|
||||
//! (binding spec /opt/commons/handoff/fork-desk-answer-binding-20261110.md,
|
||||
//! 1.3 steps 3-8 and R1, R2, R4). It builds one `desk_answer` and commits it
|
||||
//! through `admit_steps` with `Door::Desk`, the same commit primitive every
|
||||
//! other write uses; no other door may write the kind.
|
||||
//! 1.3 steps 3-8 and R1, R2, R4, and CultLib PR #72 `fork-card.md` for the
|
||||
//! request id). It builds one `desk_answer` and commits it through
|
||||
//! `admit_steps` with `Door::Desk`, the same commit primitive every other write
|
||||
//! uses; no other door may write the kind. `answer_desk` and `DeskStamp` are
|
||||
//! crate-private: only code inside this crate can stamp, so the verified door
|
||||
//! that will call them (follow-up desk-answer-door) widens them in its own cut.
|
||||
//!
|
||||
//! The decider list is an input (`DeskStamp`, from the caller); the Mind never
|
||||
//! reads one. The request types carry no `answerer` and no `authority`.
|
||||
//! `answered_at` is the client's claim, displayed and never compared: order is
|
||||
//! the per-question sequence, which is the receiver's order.
|
||||
//! The decider list is an input (`DeskStamp.deciders`, from the door); the Mind
|
||||
//! never reads one. Whether an answerer decides is membership in that list and
|
||||
//! nothing else: the stamped `authority` (Operator, Steward, Member) is an
|
||||
//! attribution copied into the record and never decides. The request types carry
|
||||
//! no `answerer` and no `authority`. `answered_at` is the client's claim: it is
|
||||
//! displayed and flagged (`claim_in_future`), never compared for order, which is
|
||||
//! the per-question sequence, the receiver's order. `received_at` is the Mind's.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, Duration, SecondsFormat, Utc};
|
||||
use eureka_pipeline::{
|
||||
DeskAuthority, DeskOutcome, DeskRefusal, Label, Para, PipelineDeskAnswer, PipelineDocument, PipelineKind, PipelineQuestion,
|
||||
PipelineRefusal, PipelineResolution, ResolutionOutcome, Sha256Hex, Short, Slug,
|
||||
DeskAuthority, DeskOutcome, DeskRefusal, Label, PipelineDeskAnswer, PipelineDocument, PipelineKind, PipelineQuestion, PipelineRefusal,
|
||||
PipelineResolution, RequestId, ResolutionOutcome, Sha256Hex, Short, Slug, Words,
|
||||
};
|
||||
|
||||
use crate::admission::{Door, PipelineAdmissionOutcome, prepare};
|
||||
|
|
@ -27,38 +33,46 @@ use crate::rows::root_and_local;
|
|||
use crate::store::MindStore;
|
||||
|
||||
/// What the desk's receiver sends the Mind. `answered_at` is the person's
|
||||
/// claim, RFC 3339, kept as written.
|
||||
/// claim, RFC 3339 in UTC, kept as written. `request_id` is the client's retry
|
||||
/// token (a lowercase UUID), required.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct DeskAnswerRequest {
|
||||
pub fork: Short,
|
||||
pub options_hash: Sha256Hex,
|
||||
pub body: DeskBody,
|
||||
pub answered_at: Short,
|
||||
pub request_id: RequestId,
|
||||
}
|
||||
|
||||
/// An answer, or the retraction of one: a sum, so a retraction carrying an
|
||||
/// answer, or naming no target, cannot be written down (R1 by construction).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DeskBody {
|
||||
Answer { choice: Option<Label>, note: Option<Para> },
|
||||
Answer { choice: Option<Label>, note: Option<Words> },
|
||||
Retraction { retracts: Short },
|
||||
}
|
||||
|
||||
/// Who answered and in what standing, as the verified caller states it. The
|
||||
/// Mind copies both into the record and decides nothing from the caller's
|
||||
/// word except that `authority: None` is no decider (rule 5).
|
||||
/// The door's verified context for one call: who answered, the role the account
|
||||
/// holds (an attribution, stored on every answer) and the decider list the door
|
||||
/// holds. Membership of `answerer` in `deciders` is rule 5's whole input; the
|
||||
/// attribution never enters it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct DeskStamp {
|
||||
pub answerer: Short,
|
||||
pub authority: Option<DeskAuthority>,
|
||||
pub(crate) struct DeskStamp {
|
||||
pub(crate) answerer: Short,
|
||||
pub(crate) authority: DeskAuthority,
|
||||
pub(crate) deciders: Vec<Short>,
|
||||
}
|
||||
|
||||
/// `Stored`: the answer id (the record key), its outcome and the commit's receipt.
|
||||
/// `Replayed`: the same `(answerer, request_id)` and body came before; the stored
|
||||
/// outcome comes back unchanged and nothing was written. (A record holds no card:
|
||||
/// the read side shows the current card beside a `stale_fork`, replayed or not.)
|
||||
/// `Refused`: nothing was stored (a shape fault, an unknown fork, a moved campaign,
|
||||
/// or a commit fault).
|
||||
/// a request id reused for another body, or a commit fault).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DeskAnswerOutcome {
|
||||
Stored { answer: Short, outcome: DeskOutcome, receipt_id: String },
|
||||
Replayed { answer: Short, outcome: DeskOutcome },
|
||||
Refused(MindRefusal),
|
||||
}
|
||||
|
||||
|
|
@ -114,13 +128,22 @@ fn is_retracted(docs: &Docs<'_>, fork: &str, key: &str) -> bool {
|
|||
answers_of(docs, fork).any(|(_, other)| matches!(&other.outcome, DeskOutcome::Retraction { retracts } if retracts.0 == key))
|
||||
}
|
||||
|
||||
/// Whether a stored answer is a candidate to count under the fork's current card:
|
||||
/// Counted or Revision, and written against the card's current options hash.
|
||||
fn counts_under(answer: &PipelineDeskAnswer, current: &Sha256Hex) -> bool {
|
||||
matches!(answer.outcome, DeskOutcome::Counted | DeskOutcome::Revision { .. }) && answer.options_hash == *current
|
||||
}
|
||||
|
||||
/// The answer that counts for `fork` now: the lowest-sequence answer whose stored
|
||||
/// outcome is Counted or Revision and that no Retraction names (R4). Derived on
|
||||
/// outcome is Counted or Revision under the card's current options hash and that no
|
||||
/// Retraction names (R4; an answer under an older hash stops counting). Derived on
|
||||
/// every call and never stored; retracting the counted answer promotes the next,
|
||||
/// retracting another changes nothing. The one place "counts" is decided.
|
||||
pub(crate) fn counted_answer(docs: &Docs<'_>, fork: &str) -> Option<String> {
|
||||
let Some(PipelineDocument::Question(question)) = docs.find(PipelineKind::Question, fork) else { return None };
|
||||
let current = options_hash(question);
|
||||
answers_of(docs, fork)
|
||||
.filter(|(key, answer)| matches!(answer.outcome, DeskOutcome::Counted | DeskOutcome::Revision { .. }) && !is_retracted(docs, fork, key))
|
||||
.filter(|(key, answer)| counts_under(answer, ¤t) && !is_retracted(docs, fork, key))
|
||||
.min_by_key(|(_, answer)| answer.sequence)
|
||||
.map(|(key, _)| key.to_string())
|
||||
}
|
||||
|
|
@ -128,10 +151,11 @@ pub(crate) fn counted_answer(docs: &Docs<'_>, fork: &str) -> Option<String> {
|
|||
/// Rules 4-7 and R1 in the binding spec's order; the first failure is the code.
|
||||
fn refusal_of(docs: &Docs<'_>, question: &PipelineQuestion, stamp: &DeskStamp, request: &DeskAnswerRequest) -> Option<DeskRefusal> {
|
||||
let fork = request.fork.0.as_str();
|
||||
if request.options_hash != options_hash(question) {
|
||||
let current = options_hash(question);
|
||||
if request.options_hash != current {
|
||||
return Some(DeskRefusal::StaleFork);
|
||||
}
|
||||
if stamp.authority.is_none() {
|
||||
if !stamp.deciders.contains(&stamp.answerer) {
|
||||
return Some(DeskRefusal::NotADecider);
|
||||
}
|
||||
if !docs.in_force(PipelineKind::Question, fork) {
|
||||
|
|
@ -144,7 +168,7 @@ fn refusal_of(docs: &Docs<'_>, question: &PipelineQuestion, stamp: &DeskStamp, r
|
|||
DeskBody::Answer { .. } => None,
|
||||
DeskBody::Retraction { retracts } => {
|
||||
let target = answers_of(docs, fork).find(|(key, _)| *key == retracts.0);
|
||||
if !target.is_some_and(|(_, answer)| matches!(answer.outcome, DeskOutcome::Counted | DeskOutcome::Revision { .. })) {
|
||||
if !target.is_some_and(|(_, answer)| counts_under(answer, ¤t)) {
|
||||
return Some(DeskRefusal::BadRetraction);
|
||||
}
|
||||
is_retracted(docs, fork, &retracts.0).then_some(DeskRefusal::AlreadyRetracted)
|
||||
|
|
@ -152,6 +176,28 @@ fn refusal_of(docs: &Docs<'_>, question: &PipelineQuestion, stamp: &DeskStamp, r
|
|||
}
|
||||
}
|
||||
|
||||
/// The digest of the request as the client sent it: what "the same body" means for a
|
||||
/// retry. Every part of the request but the id itself.
|
||||
fn body_digest(request: &DeskAnswerRequest) -> Sha256Hex {
|
||||
use sha2::{Digest, Sha256};
|
||||
#[derive(serde::Serialize)]
|
||||
struct Sent<'a> {
|
||||
fork: &'a str,
|
||||
options_hash: &'a str,
|
||||
answered_at: &'a str,
|
||||
choice: Option<&'a str>,
|
||||
note: Option<&'a str>,
|
||||
retracts: Option<&'a str>,
|
||||
}
|
||||
let (choice, note, retracts) = match &request.body {
|
||||
DeskBody::Answer { choice, note } => (choice.as_ref().map(|c| c.0.as_str()), note.as_ref().map(|n| n.0.as_str()), None),
|
||||
DeskBody::Retraction { retracts } => (None, None, Some(retracts.0.as_str())),
|
||||
};
|
||||
let sent = Sent { fork: &request.fork.0, options_hash: &request.options_hash.0, answered_at: &request.answered_at.0, choice, note, retracts };
|
||||
let bytes = serde_json::to_string(&sent).expect("strings serialise");
|
||||
Sha256Hex(Sha256::digest(bytes.as_bytes()).iter().map(|byte| format!("{byte:02x}")).collect())
|
||||
}
|
||||
|
||||
fn shape_fault(refusal: PipelineRefusal) -> MindRefusal {
|
||||
let field = match &refusal {
|
||||
PipelineRefusal::FieldBound { field, .. } | PipelineRefusal::InvalidFormat { field, .. } | PipelineRefusal::DuplicateRepo { field, .. } => field.clone(),
|
||||
|
|
@ -163,8 +209,10 @@ fn shape_fault(refusal: PipelineRefusal) -> MindRefusal {
|
|||
impl<S: MindStore> Mind<S> {
|
||||
/// Decides one desk answer and, when the request is well formed and names a
|
||||
/// held fork, stores it: counted, a revision, a retraction, or refused with its
|
||||
/// code. See the module header for the order and the stored/not-stored line.
|
||||
pub fn answer_desk(&mut self, stamp: DeskStamp, request: DeskAnswerRequest, now: DateTime<Utc>) -> DeskAnswerOutcome {
|
||||
/// code. A retry of a stored request is replayed, never stored again. See the
|
||||
/// module header for the order and the stored/not-stored line.
|
||||
#[cfg_attr(not(test), allow(dead_code))] // the verified door (follow-up desk-answer-door) is the live caller
|
||||
pub(crate) fn answer_desk(&mut self, stamp: DeskStamp, request: DeskAnswerRequest, now: DateTime<Utc>) -> DeskAnswerOutcome {
|
||||
match self.desk_steps(stamp, request, now) {
|
||||
Ok(outcome) => outcome,
|
||||
Err(refusal) => DeskAnswerOutcome::Refused(refusal),
|
||||
|
|
@ -178,9 +226,11 @@ impl<S: MindStore> Mind<S> {
|
|||
return Err(invalid("fork"));
|
||||
}
|
||||
let root = root_and_local(&request.fork.0).0.to_string();
|
||||
if DateTime::parse_from_rfc3339(&request.answered_at.0).is_err() {
|
||||
return Err(invalid("answered_at"));
|
||||
}
|
||||
// answered_at is the client's claim and must be RFC 3339 in UTC (offset zero).
|
||||
let claimed = match DateTime::parse_from_rfc3339(&request.answered_at.0) {
|
||||
Ok(at) if at.offset().local_minus_utc() == 0 => at.with_timezone(&Utc),
|
||||
_ => return Err(invalid("answered_at")),
|
||||
};
|
||||
let (choice, note, shape_outcome) = match &request.body {
|
||||
DeskBody::Answer { choice, note } => {
|
||||
if choice.is_none() && note.is_none() {
|
||||
|
|
@ -190,6 +240,9 @@ impl<S: MindStore> Mind<S> {
|
|||
}
|
||||
DeskBody::Retraction { retracts } => (None, None, DeskOutcome::Retraction { retracts: retracts.clone() }),
|
||||
};
|
||||
let body_sha256 = body_digest(&request);
|
||||
let received_at = Short(now.to_rfc3339_opts(SecondsFormat::Secs, true));
|
||||
let claim_in_future = claimed > now + Duration::minutes(5);
|
||||
let answer = |sequence: u32, outcome: DeskOutcome| PipelineDeskAnswer {
|
||||
campaign: Slug(root.clone()),
|
||||
fork: request.fork.clone(),
|
||||
|
|
@ -198,6 +251,10 @@ impl<S: MindStore> Mind<S> {
|
|||
choice: choice.clone(),
|
||||
note: note.clone(),
|
||||
answered_at: request.answered_at.clone(),
|
||||
received_at: received_at.clone(),
|
||||
claim_in_future,
|
||||
request_id: request.request_id.clone(),
|
||||
body_sha256: body_sha256.clone(),
|
||||
answerer: stamp.answerer.clone(),
|
||||
authority: stamp.authority,
|
||||
outcome,
|
||||
|
|
@ -206,10 +263,24 @@ impl<S: MindStore> Mind<S> {
|
|||
probe.validate().map_err(shape_fault)?;
|
||||
eureka_pipeline::pipeline_key(&probe).map_err(shape_fault)?;
|
||||
|
||||
// Rules 3 and the moved campaign: nothing is stored.
|
||||
let image = self.image()?;
|
||||
let docs = Docs::at(image, image.head);
|
||||
let fork = request.fork.0.as_str();
|
||||
|
||||
// The retry lookup: after the shape, before the fork is read, so a retry gets its stored
|
||||
// outcome even if the card has since changed. The key is (answerer, request_id).
|
||||
let bound = docs.of_kind(PipelineKind::DeskAnswer).find_map(|(key, document)| match document {
|
||||
PipelineDocument::DeskAnswer(held) if held.answerer == stamp.answerer && held.request_id == request.request_id => Some((key, held)),
|
||||
_ => None,
|
||||
});
|
||||
if let Some((key, held)) = bound {
|
||||
if held.body_sha256 != body_sha256 {
|
||||
return Err(MindRefusal::DeskRequestIdConflict { request_id: request.request_id.0.clone() });
|
||||
}
|
||||
return Ok(DeskAnswerOutcome::Replayed { answer: Short(key.to_string()), outcome: held.outcome.clone() });
|
||||
}
|
||||
|
||||
// Rules 3 and the moved campaign: nothing is stored.
|
||||
let Some(PipelineDocument::Question(question)) = docs.find(PipelineKind::Question, fork) else {
|
||||
return Err(MindRefusal::MissingReference { kind: PipelineKind::Question, id: fork.into() });
|
||||
};
|
||||
|
|
@ -231,8 +302,9 @@ impl<S: MindStore> Mind<S> {
|
|||
let sequence = answers_of(&docs, fork).map(|(_, held)| held.sequence).max().unwrap_or(0).checked_add(1).ok_or_else(|| invalid("sequence"))?;
|
||||
let document = PipelineDocument::DeskAnswer(answer(sequence, outcome.clone()));
|
||||
let envelope = prepare(&document, self.cache())?;
|
||||
// The receipt names the operator only when the verified account is the operator's.
|
||||
let provenance = PipelineProvenance {
|
||||
faculty: Faculty::Operator,
|
||||
faculty: if stamp.authority == DeskAuthority::Operator { Faculty::Operator } else { Faculty::SelfFaculty },
|
||||
agent: stamp.answerer.clone(),
|
||||
session: "desk".into(),
|
||||
tool: "huginn-desk".into(),
|
||||
|
|
@ -278,8 +350,27 @@ mod tests {
|
|||
options_hash(&question_with(label, &["A", "B"], "A"))
|
||||
}
|
||||
|
||||
/// The decider list the door holds in these tests.
|
||||
const LIST: [&str; 6] = ["x", "y", "z", "a", "op", "dec"];
|
||||
|
||||
fn stamp_as(who: &str, authority: DeskAuthority, list: &[&str]) -> DeskStamp {
|
||||
DeskStamp { answerer: s(who), authority, deciders: list.iter().map(|id| s(id)).collect() }
|
||||
}
|
||||
|
||||
fn decider(who: &str) -> DeskStamp {
|
||||
DeskStamp { answerer: s(who), authority: Some(DeskAuthority::Decider) }
|
||||
stamp_as(who, DeskAuthority::Member, &LIST)
|
||||
}
|
||||
|
||||
/// An account on no decider list.
|
||||
fn off() -> DeskStamp {
|
||||
stamp_as("nobody", DeskAuthority::Member, &LIST)
|
||||
}
|
||||
|
||||
/// A fresh lowercase UUID per call: every request is its own unless a test reuses one.
|
||||
fn fresh_id() -> RequestId {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static NEXT: AtomicU64 = AtomicU64::new(1);
|
||||
RequestId(format!("00000000-0000-4000-8000-{:012x}", NEXT.fetch_add(1, Ordering::Relaxed)))
|
||||
}
|
||||
|
||||
fn pick(fork: &str, label: &str, choice: &str, answered_at: &str) -> DeskAnswerRequest {
|
||||
|
|
@ -288,6 +379,7 @@ mod tests {
|
|||
options_hash: hash(label),
|
||||
body: DeskBody::Answer { choice: Some(l(choice)), note: None },
|
||||
answered_at: s(answered_at),
|
||||
request_id: fresh_id(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,6 +389,7 @@ mod tests {
|
|||
options_hash: hash(label),
|
||||
body: DeskBody::Retraction { retracts: s(target) },
|
||||
answered_at: s("2026-11-10T12:00:00Z"),
|
||||
request_id: fresh_id(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +399,7 @@ mod tests {
|
|||
|
||||
fn stored(outcome: DeskAnswerOutcome) -> (String, DeskOutcome) {
|
||||
match outcome {
|
||||
DeskAnswerOutcome::Stored { answer, outcome, .. } => (answer.0, outcome),
|
||||
DeskAnswerOutcome::Stored { answer, outcome, .. } | DeskAnswerOutcome::Replayed { answer, outcome, .. } => (answer.0, outcome),
|
||||
other => panic!("expected a stored answer, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -392,7 +485,7 @@ mod tests {
|
|||
#[test]
|
||||
fn an_answer_off_the_decider_list_is_refused() {
|
||||
let mut mind = desk();
|
||||
let off = DeskStamp { answerer: s("nobody"), authority: None };
|
||||
let off = off();
|
||||
let (_, outcome) = stored(ask(&mut mind, &off, pick(FORK, "Q1", "A", "2026-11-10T12:00:00Z")));
|
||||
assert_eq!(outcome, DeskOutcome::Refused { code: DeskRefusal::NotADecider });
|
||||
assert_eq!(counted(&mind, FORK), None);
|
||||
|
|
@ -440,7 +533,7 @@ mod tests {
|
|||
#[test]
|
||||
fn refusals_follow_the_binding_step_order() {
|
||||
let at = "2026-11-10T12:00:00Z";
|
||||
let off = DeskStamp { answerer: s("nobody"), authority: None };
|
||||
let off = off();
|
||||
let mut mind = desk();
|
||||
committed(admit(&mut mind, vec![resolution(r(K::Question, FORK), withdrawn())]));
|
||||
let stale = |mut request: DeskAnswerRequest| {
|
||||
|
|
@ -496,7 +589,7 @@ mod tests {
|
|||
let mut mind = desk();
|
||||
let at = "2026-11-10T12:00:00Z";
|
||||
let (first, _) = stored(ask(&mut mind, &decider("x"), pick(FORK, "Q1", "A", at)));
|
||||
let off = DeskStamp { answerer: s("nobody"), authority: None };
|
||||
let off = off();
|
||||
assert_eq!(stored(ask(&mut mind, &off, retract(FORK, "Q1", &first))).1, DeskOutcome::Refused { code: DeskRefusal::NotADecider });
|
||||
assert_eq!(counted(&mind, FORK), Some(first.clone()));
|
||||
assert_eq!(stored(ask(&mut mind, &decider("y"), retract(FORK, "Q1", &first))).1, DeskOutcome::Retraction { retracts: s(&first) });
|
||||
|
|
@ -536,23 +629,22 @@ mod tests {
|
|||
fn refused_answers_are_stored_with_their_code() {
|
||||
let mut mind = desk();
|
||||
let at = "2026-11-10T12:00:00Z";
|
||||
let off = DeskStamp { answerer: s("guest"), authority: None };
|
||||
let off = stamp_as("guest", DeskAuthority::Steward, &LIST);
|
||||
let (answer, outcome) = stored(ask(&mut mind, &off, pick(FORK, "Q1", "A", at)));
|
||||
let record = held(&mind, &answer);
|
||||
assert_eq!(record.outcome, outcome);
|
||||
assert_eq!(outcome, DeskOutcome::Refused { code: DeskRefusal::NotADecider });
|
||||
assert_eq!((record.answerer, record.authority, record.answered_at), (s("guest"), None, s(at)));
|
||||
assert_eq!((record.answerer, record.authority, record.answered_at), (s("guest"), DeskAuthority::Steward, s(at)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_stamp_alone_sets_answerer_and_authority() {
|
||||
let mut mind = desk();
|
||||
let at = "2026-11-10T12:00:00Z";
|
||||
for (who, authority) in [("op", DeskAuthority::Operator), ("dec", DeskAuthority::Decider)] {
|
||||
let stamp = DeskStamp { answerer: s(who), authority: Some(authority) };
|
||||
let (answer, _) = stored(ask(&mut mind, &stamp, pick(FORK, "Q1", "A", at)));
|
||||
for (who, authority) in [("op", DeskAuthority::Operator), ("dec", DeskAuthority::Steward), ("x", DeskAuthority::Member)] {
|
||||
let (answer, _) = stored(ask(&mut mind, &stamp_as(who, authority, &LIST), pick(FORK, "Q1", "A", at)));
|
||||
let record = held(&mind, &answer);
|
||||
assert_eq!((record.answerer, record.authority), (s(who), Some(authority)));
|
||||
assert_eq!((record.answerer, record.authority), (s(who), authority));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -582,10 +674,36 @@ mod tests {
|
|||
assert!(matches!(unknown, DeskAnswerOutcome::Refused(MindRefusal::MissingReference { kind: K::Question, .. })), "{unknown:?}");
|
||||
let mut empty = pick(FORK, "Q1", "A", at);
|
||||
empty.body = DeskBody::Answer { choice: None, note: None };
|
||||
let mut blank_note = pick(FORK, "Q1", "A", at);
|
||||
blank_note.body = DeskBody::Answer { choice: None, note: Some(" \n\t ".into()) };
|
||||
let mut empty_note = pick(FORK, "Q1", "A", at);
|
||||
empty_note.body = DeskBody::Answer { choice: Some(l("A")), note: Some("".into()) };
|
||||
let mut not_utc = pick(FORK, "Q1", "A", "2026-11-10T14:00:00+02:00");
|
||||
not_utc.answered_at = s("2026-11-10T14:00:00+02:00");
|
||||
let mut no_id = pick(FORK, "Q1", "A", at);
|
||||
no_id.request_id = RequestId("".into());
|
||||
let mut upper_id = pick(FORK, "Q1", "A", at);
|
||||
upper_id.request_id = RequestId("00000000-0000-4000-8000-00000000ABCD".into());
|
||||
let mut not_uuid = pick(FORK, "Q1", "A", at);
|
||||
not_uuid.request_id = RequestId("req-1".into());
|
||||
let mut braced = pick(FORK, "Q1", "A", at);
|
||||
braced.request_id = RequestId("{00000000-0000-4000-8000-00000000abcd}".into());
|
||||
let bad_time = pick(FORK, "Q1", "A", "yesterday");
|
||||
let not_a_question = pick("eureka-state:ruling:R1", "Q1", "A", at);
|
||||
let bad_target = retract(FORK, "Q1", "n1");
|
||||
for (request, field) in [(empty, "body"), (bad_time, "answered_at"), (not_a_question, "fork"), (bad_target, "desk_answer.outcome.retracts")] {
|
||||
for (request, field) in [
|
||||
(empty, "body"),
|
||||
(blank_note, "desk_answer.note"),
|
||||
(empty_note, "desk_answer.note"),
|
||||
(not_utc, "answered_at"),
|
||||
(no_id, "desk_answer.request_id"),
|
||||
(upper_id, "desk_answer.request_id"),
|
||||
(not_uuid, "desk_answer.request_id"),
|
||||
(braced, "desk_answer.request_id"),
|
||||
(bad_time, "answered_at"),
|
||||
(not_a_question, "fork"),
|
||||
(bad_target, "desk_answer.outcome.retracts"),
|
||||
] {
|
||||
assert_eq!(
|
||||
ask(&mut mind, &decider("x"), request),
|
||||
DeskAnswerOutcome::Refused(MindRefusal::DeskRequestInvalid { field: field.into() })
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ mod read_tests;
|
|||
#[cfg(test)]
|
||||
mod move_tests;
|
||||
|
||||
pub use desk::{DeskAnswerOutcome, DeskAnswerRequest, DeskBody, DeskStamp, options_hash};
|
||||
pub use desk::{DeskAnswerOutcome, DeskAnswerRequest, DeskBody, options_hash};
|
||||
pub use docs::CitationRole;
|
||||
pub use index::{INDEX_TEXT_MAX_BYTES, INDEX_TEXT_VERSION, IndexEntry, index_text};
|
||||
pub use admission::{BATCH_MAX, PipelineAdmissionBatch, PipelineAdmissionOutcome};
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ pub enum PipelineFacts {
|
|||
Research { pass: Label, label: Label, claim: Line, inferred: bool, observed_on: Date },
|
||||
Idea { pass: Label, label: Label, title: Title, proposed_on: Date },
|
||||
/// `outcome` is the stored outcome's variant name; which answer counts now is `counted_answer`'s, derived.
|
||||
DeskAnswer { fork: Short, sequence: u32, outcome: Short, authority: Option<DeskAuthority> },
|
||||
DeskAnswer { fork: Short, sequence: u32, outcome: Short, authority: DeskAuthority },
|
||||
}
|
||||
|
||||
impl PipelineFacts {
|
||||
|
|
@ -1822,16 +1822,17 @@ mod tests {
|
|||
|
||||
// Two desk answers on the open fork Q2: the second is a Revision of the first.
|
||||
let D::Question(shown) = question("Q2", &["A", "B"], "A") else { unreachable!() };
|
||||
let stamp = crate::desk::DeskStamp { answerer: s("x"), authority: Some(eureka_pipeline::DeskAuthority::Decider) };
|
||||
let pick = || crate::desk::DeskAnswerRequest {
|
||||
let stamp = crate::desk::DeskStamp { answerer: s("x"), authority: eureka_pipeline::DeskAuthority::Member, deciders: vec![s("x")] };
|
||||
let pick = |n: u32| crate::desk::DeskAnswerRequest {
|
||||
fork: s(&q2),
|
||||
options_hash: crate::desk::options_hash(&shown),
|
||||
body: crate::desk::DeskBody::Answer { choice: Some(l("A")), note: None },
|
||||
answered_at: s("2026-11-10T12:00:00Z"),
|
||||
request_id: eureka_pipeline::RequestId(format!("00000000-0000-4000-8000-{n:012x}")),
|
||||
};
|
||||
let (answer_1, answer_2) = (id("desk_answer", "Q2.n1"), id("desk_answer", "Q2.n2"));
|
||||
for _ in 0..2 {
|
||||
assert!(matches!(mind.answer_desk(stamp.clone(), pick(), now()), crate::desk::DeskAnswerOutcome::Stored { .. }));
|
||||
for n in 1..=2 {
|
||||
assert!(matches!(mind.answer_desk(stamp.clone(), pick(n), now()), crate::desk::DeskAnswerOutcome::Stored { .. }));
|
||||
}
|
||||
|
||||
let (n1, f1_closed, f2_closed) = (
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ pub enum MindRefusal {
|
|||
DeskAnswerOutsideDesk { id: String },
|
||||
/// A desk request the Mind cannot read as an answer: `field` names the part (the fork, `answered_at`, an answer carrying neither choice nor note, a malformed answer id).
|
||||
DeskRequestInvalid { field: String },
|
||||
/// The desk's `request_id_conflict` (CultLib PR #72): this `(answerer, request_id)` already bound another body. Nothing is stored.
|
||||
DeskRequestIdConflict { request_id: String },
|
||||
/// `campaign` names the refused document: a campaign, or a target revision that empties its predecessor's repos.
|
||||
EmptyRepos { campaign: String },
|
||||
RepoNotInCampaign { repo: String },
|
||||
|
|
|
|||
|
|
@ -1,343 +1,343 @@
|
|||
[
|
||||
{
|
||||
"name": "heimdall_A_five_members",
|
||||
"heimdall": "A",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_B_recommendation_changed",
|
||||
"heimdall": "B",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "yes",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"yes\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A22796573222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_C_option_order_changed",
|
||||
"heimdall": "C",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
},
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"no\",\"text\":\"Keep it local.\"},{\"id\":\"yes\",\"text\":\"Share it.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D2C7B226964223A22796573222C2274657874223A2253686172652069742E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "ac67b41539f869f6203ac0908819df046e4328b27795743a919bd4ab390596b5"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_D_nfc_escapes_astral",
|
||||
"heimdall": "D",
|
||||
"title": "Café “desk”",
|
||||
"context": "Line one\nLine two — 😀",
|
||||
"options": [
|
||||
{
|
||||
"id": "a",
|
||||
"text": "x"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"text": "y"
|
||||
}
|
||||
],
|
||||
"recommendation": "a",
|
||||
"depends": [],
|
||||
"canonical": "{\"context\":\"Line one\\nLine two — 😀\",\"depends\":[],\"options\":[{\"id\":\"a\",\"text\":\"x\"},{\"id\":\"b\",\"text\":\"y\"}],\"recommendation\":\"a\",\"title\":\"Café “desk”\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A224C696E65206F6E655C6E4C696E652074776F20E2809420F09F9880222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A2261222C2274657874223A2278227D2C7B226964223A2262222C2274657874223A2279227D5D2C227265636F6D6D656E646174696F6E223A2261222C227469746C65223A22436166C3A920E2809C6465736BE2809D227D",
|
||||
"sha256": "71d5c413478ad06aaa1cbdd9e91c53f0cb4cef1dbd4c51bf3d473bccc6a5a272"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_E_decomposed_equals_D",
|
||||
"heimdall": "E",
|
||||
"title": "Café “desk”",
|
||||
"context": "Line one\nLine two — 😀",
|
||||
"options": [
|
||||
{
|
||||
"id": "a",
|
||||
"text": "x"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"text": "y"
|
||||
}
|
||||
],
|
||||
"recommendation": "a",
|
||||
"depends": [],
|
||||
"canonical": "{\"context\":\"Line one\\nLine two — 😀\",\"depends\":[],\"options\":[{\"id\":\"a\",\"text\":\"x\"},{\"id\":\"b\",\"text\":\"y\"}],\"recommendation\":\"a\",\"title\":\"Café “desk”\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A224C696E65206F6E655C6E4C696E652074776F20E2809420F09F9880222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A2261222C2274657874223A2278227D2C7B226964223A2262222C2274657874223A2279227D5D2C227265636F6D6D656E646174696F6E223A2261222C227469746C65223A22436166C3A920E2809C6465736BE2809D227D",
|
||||
"sha256": "71d5c413478ad06aaa1cbdd9e91c53f0cb4cef1dbd4c51bf3d473bccc6a5a272"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_F_option_id_changed",
|
||||
"heimdall": "F",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "share",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"share\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A227368617265222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "fb53aa3a9f7e74029f755d774064eedd240ab90d8fc64723f97c31b3d2846d40"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_G_depends_emptied",
|
||||
"heimdall": "G",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "f856edce121db4121b5f1af400b1c471ebbfa6579a042aca9a34ae31c15f5c86"
|
||||
},
|
||||
{
|
||||
"name": "option_text_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it!"
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it!\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A22536861726520697421227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "9ce0d2770497fdd2d5338e66f9f1ae1741d87301db4c793407c25762929392b6"
|
||||
},
|
||||
{
|
||||
"name": "title_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent?",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent?\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E743F227D",
|
||||
"sha256": "30fe0c5bc88bd21d64592a495316c30e80fcee6cacf5c63fd9a818ca5cf87d0f"
|
||||
},
|
||||
{
|
||||
"name": "context_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should slot marks be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should slot marks be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C6420736C6F74206D61726B73206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "716de65096e34fd89cc92848b667d90f5c27eeb66929da3d340cabb80155e173"
|
||||
},
|
||||
{
|
||||
"name": "depends_order_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience",
|
||||
"x:question:y"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\",\"x:question:y\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365222C22783A7175657374696F6E3A79225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "fc3f651c6f498385256fc1232d5db31ece9dcc542e391f97245d27bc9e385b3d"
|
||||
},
|
||||
{
|
||||
"name": "depends_order_changed_reversed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"x:question:y",
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"x:question:y\",\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B22783A7175657374696F6E3A79222C2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "b22642e30f8d68839e3a0df5bc23b83ff50a839fcecf0366a65fa6e3cf873796"
|
||||
},
|
||||
{
|
||||
"name": "hidden_members_do_not_move_it",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48"
|
||||
},
|
||||
{
|
||||
"name": "control_characters_and_markup_are_escaped_minimally",
|
||||
"heimdall": null,
|
||||
"title": "q \" b \\ t \t n \n bell \u0007 del ",
|
||||
"context": "</script>
&",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"</script>
&\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"q \\\" b \\\\ t \\t n \\n bell \\u0007 del \"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A223C2F7363726970743E20E280A82026222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A2271205C222062205C5C2074205C74206E205C6E2062656C6C205C75303030372064656C207F227D",
|
||||
"sha256": "f2d5a09513ce728129f6ca13f7123aa75b2bb77541b1de8946f39239634b40fe"
|
||||
},
|
||||
{
|
||||
"name": "astral_option_id_and_text",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "😀",
|
||||
"text": "𝄞"
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"😀\",\"text\":\"𝄞\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22F09F9880222C2274657874223A22F09D849E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "836fbfce16127132bae100aa773d328a85ad08d241ae9bf47ea7f84766762cac"
|
||||
}
|
||||
]
|
||||
{
|
||||
"name": "heimdall_A_five_members",
|
||||
"heimdall": "A",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_B_recommendation_changed",
|
||||
"heimdall": "B",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "yes",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"yes\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A22796573222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_C_option_order_changed",
|
||||
"heimdall": "C",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
},
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"no\",\"text\":\"Keep it local.\"},{\"id\":\"yes\",\"text\":\"Share it.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D2C7B226964223A22796573222C2274657874223A2253686172652069742E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "ac67b41539f869f6203ac0908819df046e4328b27795743a919bd4ab390596b5"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_D_nfc_escapes_astral",
|
||||
"heimdall": "D",
|
||||
"title": "Café “desk”",
|
||||
"context": "Line one\nLine two — 😀",
|
||||
"options": [
|
||||
{
|
||||
"id": "a",
|
||||
"text": "x"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"text": "y"
|
||||
}
|
||||
],
|
||||
"recommendation": "a",
|
||||
"depends": [],
|
||||
"canonical": "{\"context\":\"Line one\\nLine two — 😀\",\"depends\":[],\"options\":[{\"id\":\"a\",\"text\":\"x\"},{\"id\":\"b\",\"text\":\"y\"}],\"recommendation\":\"a\",\"title\":\"Café “desk”\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A224C696E65206F6E655C6E4C696E652074776F20E2809420F09F9880222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A2261222C2274657874223A2278227D2C7B226964223A2262222C2274657874223A2279227D5D2C227265636F6D6D656E646174696F6E223A2261222C227469746C65223A22436166C3A920E2809C6465736BE2809D227D",
|
||||
"sha256": "71d5c413478ad06aaa1cbdd9e91c53f0cb4cef1dbd4c51bf3d473bccc6a5a272"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_E_decomposed_equals_D",
|
||||
"heimdall": "E",
|
||||
"title": "Café “desk”",
|
||||
"context": "Line one\nLine two — 😀",
|
||||
"options": [
|
||||
{
|
||||
"id": "a",
|
||||
"text": "x"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"text": "y"
|
||||
}
|
||||
],
|
||||
"recommendation": "a",
|
||||
"depends": [],
|
||||
"canonical": "{\"context\":\"Line one\\nLine two — 😀\",\"depends\":[],\"options\":[{\"id\":\"a\",\"text\":\"x\"},{\"id\":\"b\",\"text\":\"y\"}],\"recommendation\":\"a\",\"title\":\"Café “desk”\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A224C696E65206F6E655C6E4C696E652074776F20E2809420F09F9880222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A2261222C2274657874223A2278227D2C7B226964223A2262222C2274657874223A2279227D5D2C227265636F6D6D656E646174696F6E223A2261222C227469746C65223A22436166C3A920E2809C6465736BE2809D227D",
|
||||
"sha256": "71d5c413478ad06aaa1cbdd9e91c53f0cb4cef1dbd4c51bf3d473bccc6a5a272"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_F_option_id_changed",
|
||||
"heimdall": "F",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "share",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"share\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A227368617265222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "fb53aa3a9f7e74029f755d774064eedd240ab90d8fc64723f97c31b3d2846d40"
|
||||
},
|
||||
{
|
||||
"name": "heimdall_G_depends_emptied",
|
||||
"heimdall": "G",
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "f856edce121db4121b5f1af400b1c471ebbfa6579a042aca9a34ae31c15f5c86"
|
||||
},
|
||||
{
|
||||
"name": "option_text_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it!"
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it!\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A22536861726520697421227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "9ce0d2770497fdd2d5338e66f9f1ae1741d87301db4c793407c25762929392b6"
|
||||
},
|
||||
{
|
||||
"name": "title_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent?",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent?\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E743F227D",
|
||||
"sha256": "30fe0c5bc88bd21d64592a495316c30e80fcee6cacf5c63fd9a818ca5cf87d0f"
|
||||
},
|
||||
{
|
||||
"name": "context_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should slot marks be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should slot marks be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C6420736C6F74206D61726B73206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "716de65096e34fd89cc92848b667d90f5c27eeb66929da3d340cabb80155e173"
|
||||
},
|
||||
{
|
||||
"name": "depends_order_changed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience",
|
||||
"x:question:y"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\",\"x:question:y\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365222C22783A7175657374696F6E3A79225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "fc3f651c6f498385256fc1232d5db31ece9dcc542e391f97245d27bc9e385b3d"
|
||||
},
|
||||
{
|
||||
"name": "depends_order_changed_reversed",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"x:question:y",
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"x:question:y\",\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B22783A7175657374696F6E3A79222C2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "b22642e30f8d68839e3a0df5bc23b83ff50a839fcecf0366a65fa6e3cf873796"
|
||||
},
|
||||
{
|
||||
"name": "hidden_members_do_not_move_it",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48"
|
||||
},
|
||||
{
|
||||
"name": "control_characters_and_markup_are_escaped_minimally",
|
||||
"heimdall": null,
|
||||
"title": "q \" b \\ t \t n \n bell \u0007 del ",
|
||||
"context": "</script>
&",
|
||||
"options": [
|
||||
{
|
||||
"id": "yes",
|
||||
"text": "Share it."
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"</script>
&\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"q \\\" b \\\\ t \\t n \\n bell \\u0007 del \"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A223C2F7363726970743E20E280A82026222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A2271205C222062205C5C2074205C74206E205C6E2062656C6C205C75303030372064656C207F227D",
|
||||
"sha256": "f2d5a09513ce728129f6ca13f7123aa75b2bb77541b1de8946f39239634b40fe"
|
||||
},
|
||||
{
|
||||
"name": "astral_option_id_and_text",
|
||||
"heimdall": null,
|
||||
"title": "Slot mark consent",
|
||||
"context": "Should a slot mark be shared?",
|
||||
"options": [
|
||||
{
|
||||
"id": "😀",
|
||||
"text": "𝄞"
|
||||
},
|
||||
{
|
||||
"id": "no",
|
||||
"text": "Keep it local."
|
||||
}
|
||||
],
|
||||
"recommendation": "no",
|
||||
"depends": [
|
||||
"gamecult-chat:question:file-zone-audience"
|
||||
],
|
||||
"canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"😀\",\"text\":\"𝄞\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}",
|
||||
"canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22F09F9880222C2274657874223A22F09D849E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D",
|
||||
"sha256": "836fbfce16127132bae100aa773d328a85ad08d241ae9bf47ea7f84766762cac"
|
||||
}
|
||||
]
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
"DeskAuthority": {
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Decider"
|
||||
"Steward",
|
||||
"Member"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -94,9 +95,9 @@
|
|||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"RequestId": {
|
||||
"description": "A client's retry token: a lowercase D-format UUID.",
|
||||
"maxLength": 36,
|
||||
"type": "string"
|
||||
},
|
||||
"Sha256Hex": {
|
||||
|
|
@ -113,11 +114,17 @@
|
|||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Words": {
|
||||
"description": "A person's words, verbatim: 1 to 4,000 UTF-8 bytes, not blank (CultLib PR #72, `answer.note`).",
|
||||
"maxLength": 4000,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.desk_answer.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "One answer the fork desk's receiver passed to the Mind, refused ones included.\n`answerer` and `authority` are stamped by the Mind from the verified caller and\nare absent from the request type; `answered_at` is the client's claim kept as\nwritten, display only: order is `sequence`, the receiver's order. `sequence` is\nper question and is the last part of the key (the answer id).",
|
||||
"description": "One answer the fork desk's receiver passed to the Mind, refused ones included.\n`answerer` and `authority` (an attribution, never a decision) are stamped by the\nMind from the verified caller and are absent from the request type; `answered_at`\nis the client's claim kept as written, display only: order is `sequence`, the\nreceiver's order. `received_at` is the Mind's admission time (UTC) and\n`claim_in_future` is set when `answered_at` is more than five minutes after it.\n`request_id` is the client's retry token and `body_sha256` the digest of the\nrequest it came with: the pair that makes a retry the same answer, never a new\none; the key is `(answerer, request_id)`. A record holds no card: a replayed\n`stale_fork` is shown the current card. `sequence` is per question and is the\nlast part of the key (the answer id).",
|
||||
"properties": {
|
||||
"answered_at": {
|
||||
"$ref": "#/$defs/Short"
|
||||
|
|
@ -126,14 +133,10 @@
|
|||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"authority": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
"body_sha256": {
|
||||
"$ref": "#/$defs/Sha256Hex"
|
||||
},
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
|
|
@ -148,13 +151,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"claim_in_future": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"fork": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/Para"
|
||||
"$ref": "#/$defs/Words"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
|
|
@ -167,6 +173,12 @@
|
|||
"outcome": {
|
||||
"$ref": "#/$defs/DeskOutcome"
|
||||
},
|
||||
"received_at": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"request_id": {
|
||||
"$ref": "#/$defs/RequestId"
|
||||
},
|
||||
"sequence": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
|
|
@ -179,7 +191,12 @@
|
|||
"sequence",
|
||||
"options_hash",
|
||||
"answered_at",
|
||||
"received_at",
|
||||
"claim_in_future",
|
||||
"request_id",
|
||||
"body_sha256",
|
||||
"answerer",
|
||||
"authority",
|
||||
"outcome"
|
||||
],
|
||||
"title": "PipelineDeskAnswer",
|
||||
|
|
|
|||
|
|
@ -399,7 +399,8 @@
|
|||
"type": "string",
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Decider"
|
||||
"Steward",
|
||||
"Member"
|
||||
]
|
||||
},
|
||||
"DeskOutcome": {
|
||||
|
|
@ -1181,7 +1182,7 @@
|
|||
]
|
||||
},
|
||||
"PipelineDeskAnswer": {
|
||||
"description": "One answer the fork desk's receiver passed to the Mind, refused ones included.\n`answerer` and `authority` are stamped by the Mind from the verified caller and\nare absent from the request type; `answered_at` is the client's claim kept as\nwritten, display only: order is `sequence`, the receiver's order. `sequence` is\nper question and is the last part of the key (the answer id).",
|
||||
"description": "One answer the fork desk's receiver passed to the Mind, refused ones included.\n`answerer` and `authority` (an attribution, never a decision) are stamped by the\nMind from the verified caller and are absent from the request type; `answered_at`\nis the client's claim kept as written, display only: order is `sequence`, the\nreceiver's order. `received_at` is the Mind's admission time (UTC) and\n`claim_in_future` is set when `answered_at` is more than five minutes after it.\n`request_id` is the client's retry token and `body_sha256` the digest of the\nrequest it came with: the pair that makes a retry the same answer, never a new\none; the key is `(answerer, request_id)`. A record holds no card: a replayed\n`stale_fork` is shown the current card. `sequence` is per question and is the\nlast part of the key (the answer id).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answered_at": {
|
||||
|
|
@ -1191,14 +1192,10 @@
|
|||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"authority": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
"body_sha256": {
|
||||
"$ref": "#/$defs/Sha256Hex"
|
||||
},
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
|
|
@ -1213,13 +1210,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"claim_in_future": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"fork": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/Para"
|
||||
"$ref": "#/$defs/Words"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
|
|
@ -1232,6 +1232,12 @@
|
|||
"outcome": {
|
||||
"$ref": "#/$defs/DeskOutcome"
|
||||
},
|
||||
"received_at": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"request_id": {
|
||||
"$ref": "#/$defs/RequestId"
|
||||
},
|
||||
"sequence": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
|
|
@ -1244,7 +1250,12 @@
|
|||
"sequence",
|
||||
"options_hash",
|
||||
"answered_at",
|
||||
"received_at",
|
||||
"claim_in_future",
|
||||
"request_id",
|
||||
"body_sha256",
|
||||
"answerer",
|
||||
"authority",
|
||||
"outcome"
|
||||
]
|
||||
},
|
||||
|
|
@ -2606,6 +2617,11 @@
|
|||
"builds"
|
||||
]
|
||||
},
|
||||
"RequestId": {
|
||||
"description": "A client's retry token: a lowercase D-format UUID.",
|
||||
"type": "string",
|
||||
"maxLength": 36
|
||||
},
|
||||
"ResearchSource": {
|
||||
"description": "Where a research claim comes from. A `Repo` pin and a `Web` page are\noutside the document set; an `Observed` result reuses a finding's evidence;\na `Document` is the one source that is a citation edge.",
|
||||
"oneOf": [
|
||||
|
|
@ -3151,6 +3167,12 @@
|
|||
"name",
|
||||
"pins"
|
||||
]
|
||||
},
|
||||
"Words": {
|
||||
"description": "A person's words, verbatim: 1 to 4,000 UTF-8 bytes, not blank (CultLib PR #72, `answer.note`).",
|
||||
"type": "string",
|
||||
"maxLength": 4000,
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -408,7 +408,8 @@
|
|||
"type": "string",
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Decider"
|
||||
"Steward",
|
||||
"Member"
|
||||
]
|
||||
},
|
||||
"DeskOutcome": {
|
||||
|
|
@ -1533,6 +1534,27 @@
|
|||
"DeskRequestInvalid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The desk's `request_id_conflict` (CultLib PR #72): this `(answerer, request_id)` already bound another body. Nothing is stored.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"DeskRequestIdConflict": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"request_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"request_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"DeskRequestIdConflict"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "`campaign` names the refused document: a campaign, or a target revision that empties its predecessor's repos.",
|
||||
"type": "object",
|
||||
|
|
@ -2663,7 +2685,7 @@
|
|||
]
|
||||
},
|
||||
"PipelineDeskAnswer": {
|
||||
"description": "One answer the fork desk's receiver passed to the Mind, refused ones included.\n`answerer` and `authority` are stamped by the Mind from the verified caller and\nare absent from the request type; `answered_at` is the client's claim kept as\nwritten, display only: order is `sequence`, the receiver's order. `sequence` is\nper question and is the last part of the key (the answer id).",
|
||||
"description": "One answer the fork desk's receiver passed to the Mind, refused ones included.\n`answerer` and `authority` (an attribution, never a decision) are stamped by the\nMind from the verified caller and are absent from the request type; `answered_at`\nis the client's claim kept as written, display only: order is `sequence`, the\nreceiver's order. `received_at` is the Mind's admission time (UTC) and\n`claim_in_future` is set when `answered_at` is more than five minutes after it.\n`request_id` is the client's retry token and `body_sha256` the digest of the\nrequest it came with: the pair that makes a retry the same answer, never a new\none; the key is `(answerer, request_id)`. A record holds no card: a replayed\n`stale_fork` is shown the current card. `sequence` is per question and is the\nlast part of the key (the answer id).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answered_at": {
|
||||
|
|
@ -2673,14 +2695,10 @@
|
|||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"authority": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
"body_sha256": {
|
||||
"$ref": "#/$defs/Sha256Hex"
|
||||
},
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
|
|
@ -2695,13 +2713,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"claim_in_future": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"fork": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/Para"
|
||||
"$ref": "#/$defs/Words"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
|
|
@ -2714,6 +2735,12 @@
|
|||
"outcome": {
|
||||
"$ref": "#/$defs/DeskOutcome"
|
||||
},
|
||||
"received_at": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"request_id": {
|
||||
"$ref": "#/$defs/RequestId"
|
||||
},
|
||||
"sequence": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
|
|
@ -2726,7 +2753,12 @@
|
|||
"sequence",
|
||||
"options_hash",
|
||||
"answered_at",
|
||||
"received_at",
|
||||
"claim_in_future",
|
||||
"request_id",
|
||||
"body_sha256",
|
||||
"answerer",
|
||||
"authority",
|
||||
"outcome"
|
||||
]
|
||||
},
|
||||
|
|
@ -4011,14 +4043,7 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"authority": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"$ref": "#/$defs/DeskAuthority"
|
||||
},
|
||||
"fork": {
|
||||
"$ref": "#/$defs/Short"
|
||||
|
|
@ -4035,7 +4060,8 @@
|
|||
"required": [
|
||||
"fork",
|
||||
"sequence",
|
||||
"outcome"
|
||||
"outcome",
|
||||
"authority"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -5520,6 +5546,11 @@
|
|||
"builds"
|
||||
]
|
||||
},
|
||||
"RequestId": {
|
||||
"description": "A client's retry token: a lowercase D-format UUID.",
|
||||
"type": "string",
|
||||
"maxLength": 36
|
||||
},
|
||||
"ResearchSource": {
|
||||
"description": "Where a research claim comes from. A `Repo` pin and a `Web` page are\noutside the document set; an `Observed` result reuses a finding's evidence;\na `Document` is the one source that is a citation edge.",
|
||||
"oneOf": [
|
||||
|
|
@ -6124,6 +6155,12 @@
|
|||
"name",
|
||||
"pins"
|
||||
]
|
||||
},
|
||||
"Words": {
|
||||
"description": "A person's words, verbatim: 1 to 4,000 UTF-8 bytes, not blank (CultLib PR #72, `answer.note`).",
|
||||
"type": "string",
|
||||
"maxLength": 4000,
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue