Desk wire: Libby's answer documents, the one n<seq> conversion, the projection and Mind::desk_answer
Vendors fork-card-vectors.json at CultLib 8ca1d184 and retires the options-hash file it contains. answer_desk's dead-code allowance goes: desk_answer is its live caller. No card is built here. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f0ca504456
commit
2062902c80
5 changed files with 15213 additions and 347 deletions
|
|
@ -242,7 +242,6 @@ impl<S: MindStore> Mind<S> {
|
|||
/// held fork, stores it: counted, a revision, a retraction, or refused with its
|
||||
/// 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,
|
||||
|
|
@ -933,10 +932,11 @@ mod tests {
|
|||
assert_eq!(counted(&mind, FORK), Some(answer));
|
||||
}
|
||||
|
||||
/// Libby's vectors (CultLib PR #72 head 7aec4961, `fork-card-vectors.json`, `optionsHash`): the seven
|
||||
/// of the binding file, A to G, and the eight she adds. Each is recomputed from its shown members.
|
||||
/// Libby's vectors (CultLib 8ca1d184, `contracts/cultnet/governance/fork-card-vectors.json`, the `optionsHash` array):
|
||||
/// the seven of the binding file, A to G, and the eight she adds. Each is recomputed from its shown members.
|
||||
fn vectors() -> Vec<serde_json::Value> {
|
||||
serde_json::from_str(include_str!("../vectors/fork-card-options-hash.json")).unwrap()
|
||||
let file: serde_json::Value = serde_json::from_str(include_str!("../vectors/fork-card-vectors.json")).unwrap();
|
||||
file["optionsHash"].as_array().unwrap().clone()
|
||||
}
|
||||
|
||||
fn shown(vector: &serde_json::Value) -> PipelineQuestion {
|
||||
|
|
|
|||
344
crates/huginn-mind/src/desk_wire.rs
Normal file
344
crates/huginn-mind/src/desk_wire.rs
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
//! The fork desk's answer wire, Huginn's half: Libby's answer-side documents
|
||||
//! (CultLib `contracts/cultnet/governance/fork-card.md` and `fork-card-vectors.json`)
|
||||
//! as Rust types, the one conversion between her `n<seq>` answer ids and the
|
||||
//! record keys, the projection of a stored answer, the refusal mapping and the
|
||||
//! showability predicate, and `Mind::desk_answer`, the one call that runs them in order.
|
||||
//!
|
||||
//! `Mind::answer_desk` stays the only owner of an answer's outcome, sequence,
|
||||
//! request id lookup, replay and conflict; this module converts, projects and
|
||||
//! maps, and never decides, recomputes or rewrites any of it. `counted_answer`
|
||||
//! 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.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use eureka_pipeline::{
|
||||
DeskAuthority, DeskOutcome, DeskRefusal, PipelineDeskAnswer, PipelineDocument, PipelineKind, PipelineQuestion, RequestId, Sha256Hex, Short, Words,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::desk::{DeskAnswerOutcome, DeskAnswerRequest, DeskBody, DeskStamp, counted_answer};
|
||||
use crate::docs::Docs;
|
||||
use crate::mind::Mind;
|
||||
use crate::refusal::MindRefusal;
|
||||
use crate::rows::root_and_local;
|
||||
use crate::store::MindStore;
|
||||
|
||||
/// The request document's schema id; any other value fails to decode.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum RequestSchema {
|
||||
#[serde(rename = "gamecult.fork.answer_request.v0")]
|
||||
V0,
|
||||
}
|
||||
|
||||
/// The desk answer document's schema id.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum AnswerSchema {
|
||||
#[serde(rename = "gamecult.desk_answer.v0")]
|
||||
V0,
|
||||
}
|
||||
|
||||
/// The refusal document's schema id.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum RefusalSchema {
|
||||
#[serde(rename = "gamecult.fork.refusal.v0")]
|
||||
V0,
|
||||
}
|
||||
|
||||
/// `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
|
||||
/// door's context and are no field here.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ForkAnswerRequest {
|
||||
#[serde(rename = "schemaVersion")]
|
||||
pub schema_version: RequestSchema,
|
||||
pub fork_id: String,
|
||||
pub options_hash: String,
|
||||
pub answer: Option<ForkAnswer>,
|
||||
pub retracted: bool,
|
||||
pub retracts: Option<String>,
|
||||
pub answered_at: String,
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
/// The person's words: a choice, a note, or both.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ForkAnswer {
|
||||
pub choice: Option<String>,
|
||||
pub note: Option<String>,
|
||||
}
|
||||
|
||||
/// The six codes a refused answer is stored with, as her schema spells them.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ForkAnswerCode {
|
||||
StaleFork,
|
||||
NotADecider,
|
||||
ForkClosed,
|
||||
ChoiceInvalid,
|
||||
BadRetraction,
|
||||
AlreadyRetracted,
|
||||
}
|
||||
|
||||
impl From<DeskRefusal> for ForkAnswerCode {
|
||||
fn from(refusal: DeskRefusal) -> Self {
|
||||
match refusal {
|
||||
DeskRefusal::StaleFork => Self::StaleFork,
|
||||
DeskRefusal::NotADecider => Self::NotADecider,
|
||||
DeskRefusal::ForkClosed => Self::ForkClosed,
|
||||
DeskRefusal::ChoiceInvalid => Self::ChoiceInvalid,
|
||||
DeskRefusal::BadRetraction => Self::BadRetraction,
|
||||
DeskRefusal::AlreadyRetracted => Self::AlreadyRetracted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `gamecult.desk_answer.v0`: one stored answer as the desk shows it, her field order,
|
||||
/// `request_id` last. Every value is a stored fact; `answer_id`, `of` and `retracts` are the
|
||||
/// record keys in her `n<seq>` spelling.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ForkDeskAnswer {
|
||||
#[serde(rename = "schemaVersion")]
|
||||
pub schema_version: AnswerSchema,
|
||||
pub answer_id: String,
|
||||
pub fork_id: String,
|
||||
pub outcome: String,
|
||||
pub of: Option<String>,
|
||||
pub retracts: Option<String>,
|
||||
pub code: Option<ForkAnswerCode>,
|
||||
pub options_hash: String,
|
||||
pub choice: Option<String>,
|
||||
pub note: Option<String>,
|
||||
pub answered_at: String,
|
||||
pub received_at: String,
|
||||
pub claim_in_future: bool,
|
||||
pub answerer: String,
|
||||
pub authority: DeskAuthority,
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
/// The three requests Huginn refuses without storing, by code.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ForkRefusalCode {
|
||||
ForkUnknown,
|
||||
BodyInvalid,
|
||||
RequestIdConflict,
|
||||
}
|
||||
|
||||
/// `gamecult.fork.refusal.v0`. `error` is a fixed sentence, never an echo of what was sent;
|
||||
/// for `body_invalid` it names the field.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ForkRefusal {
|
||||
#[serde(rename = "schemaVersion")]
|
||||
pub schema_version: RefusalSchema,
|
||||
pub code: ForkRefusalCode,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
/// What the door answers one desk request with. `Answered` is a stored answer, refused ones
|
||||
/// included (a stale fork, a non-decider and the rest are stored), fresh or replayed alike.
|
||||
/// `NotStored` is a request that stored nothing. `Refused` is any other Mind refusal; an
|
||||
/// `Unavailable` is Bifrost's to turn into its 503, and Huginn writes no document for it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum DeskResponse {
|
||||
Answered { answer: ForkDeskAnswer, counted_answer_id: Option<String>, as_of: u64 },
|
||||
NotStored(ForkRefusal),
|
||||
Refused(MindRefusal),
|
||||
}
|
||||
|
||||
// ---- 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.
|
||||
fn prefix(fork: &str) -> String {
|
||||
let (root, local) = root_and_local(fork);
|
||||
format!("{root}:desk_answer:{local}.")
|
||||
}
|
||||
|
||||
fn n_text(n: u32) -> String {
|
||||
format!("n{n}")
|
||||
}
|
||||
|
||||
/// `n[1-9][0-9]*` within a `u32`.
|
||||
fn parse_n(text: &str) -> Option<u32> {
|
||||
let digits = text.strip_prefix('n')?;
|
||||
if digits.is_empty() || digits.starts_with('0') || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
digits.parse().ok()
|
||||
}
|
||||
|
||||
/// The key of `fork`'s `n`th answer.
|
||||
fn answer_key(fork: &str, n: u32) -> String {
|
||||
format!("{}{}", prefix(fork), n_text(n))
|
||||
}
|
||||
|
||||
/// The sequence a key of `fork`'s answers names; `None` for another fork's key or any other text.
|
||||
fn answer_seq(fork: &str, key: &str) -> Option<u32> {
|
||||
parse_n(key.strip_prefix(prefix(fork).as_str())?)
|
||||
}
|
||||
|
||||
// ---- request, refusals ----
|
||||
|
||||
fn refusal(code: ForkRefusalCode, error: String) -> ForkRefusal {
|
||||
ForkRefusal { schema_version: RefusalSchema::V0, code, error }
|
||||
}
|
||||
|
||||
fn fork_unknown() -> ForkRefusal {
|
||||
refusal(ForkRefusalCode::ForkUnknown, "fork_unknown: no such fork".into())
|
||||
}
|
||||
|
||||
fn body_invalid(field: &str) -> ForkRefusal {
|
||||
refusal(ForkRefusalCode::BodyInvalid, format!("body_invalid: the body does not satisfy gamecult.fork.answer_request.v0 ({field})"))
|
||||
}
|
||||
|
||||
/// The Mind's request for a wire request, or the refusal that stores nothing. Which of the
|
||||
/// four shapes the person sent is decided here from the three fields that carry it; whether
|
||||
/// the words are within bounds is `answer_desk`'s shape step.
|
||||
fn request_of(wire: &ForkAnswerRequest) -> Result<DeskAnswerRequest, ForkRefusal> {
|
||||
let retracts = match &wire.retracts {
|
||||
None => None,
|
||||
Some(text) => Some(Short(answer_key(&wire.fork_id, parse_n(text).ok_or_else(|| body_invalid("retracts"))?))),
|
||||
};
|
||||
let sent = wire.answer.as_ref();
|
||||
if sent.is_some_and(|words| words.choice.is_none() && words.note.is_none()) {
|
||||
return Err(body_invalid("answer"));
|
||||
}
|
||||
let choice = sent.and_then(|words| words.choice.clone()).map(Short);
|
||||
let note = sent.and_then(|words| words.note.clone()).map(Words);
|
||||
let body = match (sent.is_some(), wire.retracted, retracts) {
|
||||
(true, false, None) => DeskBody::Answer { choice, note },
|
||||
(false, true, Some(retracts)) => DeskBody::Retraction { retracts },
|
||||
(false, false, _) => return Err(body_invalid("answer")),
|
||||
(_, retracted, retracts) => DeskBody::IllegalRetraction { choice, note, retracts, retracted },
|
||||
};
|
||||
Ok(DeskAnswerRequest {
|
||||
fork: Short(wire.fork_id.clone()),
|
||||
options_hash: Sha256Hex(wire.options_hash.clone()),
|
||||
body,
|
||||
answered_at: Short(wire.answered_at.clone()),
|
||||
request_id: RequestId(wire.request_id.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
/// A key the door could not have stored. No door path writes one, so this is a fault of the
|
||||
/// store, and it is `Unavailable`, never a wrong id.
|
||||
fn unreadable_key() -> MindRefusal {
|
||||
MindRefusal::Unavailable { detail: "a stored desk answer names a key outside its fork's n<seq> form".into() }
|
||||
}
|
||||
|
||||
/// The stored answer as Libby's document. Every value is stored; nothing is recomputed.
|
||||
fn project(key: &str, stored: &PipelineDeskAnswer) -> Result<ForkDeskAnswer, MindRefusal> {
|
||||
let fork = stored.fork.0.as_str();
|
||||
let id = |key: &str| answer_seq(fork, key).map(n_text).ok_or_else(unreadable_key);
|
||||
let (of, retracts, code) = match &stored.outcome {
|
||||
DeskOutcome::Counted => (None, None, None),
|
||||
DeskOutcome::Revision { of } => (Some(id(&of.0)?), None, None),
|
||||
DeskOutcome::Retraction { retracts } => (None, Some(id(&retracts.0)?), None),
|
||||
DeskOutcome::Refused { code, retracts } => (None, retracts.as_ref().map(|target| id(&target.0)).transpose()?, Some((*code).into())),
|
||||
};
|
||||
Ok(ForkDeskAnswer {
|
||||
schema_version: AnswerSchema::V0,
|
||||
answer_id: id(key)?,
|
||||
fork_id: stored.fork.0.clone(),
|
||||
outcome: stored.outcome.name().into(),
|
||||
of,
|
||||
retracts,
|
||||
code,
|
||||
options_hash: stored.options_hash.0.clone(),
|
||||
choice: stored.choice.as_ref().map(|choice| choice.0.clone()),
|
||||
note: stored.note.as_ref().map(|note| note.0.clone()),
|
||||
answered_at: stored.answered_at.0.clone(),
|
||||
received_at: stored.received_at.0.clone(),
|
||||
claim_in_future: stored.claim_in_future,
|
||||
answerer: stored.answerer.0.clone(),
|
||||
authority: stored.authority,
|
||||
request_id: stored.request_id.0.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The Mind's refusal as the door answers it: an unknown or moved fork, a body the shape step
|
||||
/// refused and a reused request id store nothing and have codes of their own; anything else,
|
||||
/// `Unavailable` included, passes through unchanged.
|
||||
fn door_refusal(refused: MindRefusal) -> DeskResponse {
|
||||
match refused {
|
||||
MindRefusal::MissingReference { .. } | MindRefusal::CampaignMoved { .. } => DeskResponse::NotStored(fork_unknown()),
|
||||
MindRefusal::DeskRequestInvalid { field } if field == "fork" => DeskResponse::NotStored(fork_unknown()),
|
||||
MindRefusal::DeskRequestInvalid { field } => DeskResponse::NotStored(body_invalid(&field)),
|
||||
MindRefusal::DeskRequestIdConflict { .. } => {
|
||||
DeskResponse::NotStored(refusal(ForkRefusalCode::RequestIdConflict, "request_id_conflict: this request_id was used for a different body".into()))
|
||||
}
|
||||
other => DeskResponse::Refused(other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a held question can be shown as a fork and so answered: a depends line and an option
|
||||
/// text are never empty, there are at least two options and at least one decider.
|
||||
fn showable(question: &PipelineQuestion, deciders: &[Short]) -> bool {
|
||||
!deciders.is_empty()
|
||||
&& question.options.len() >= 2
|
||||
&& question.options.iter().all(|option| !option.text.0.is_empty())
|
||||
&& question.depends.iter().all(|line| !line.0.is_empty())
|
||||
}
|
||||
|
||||
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
|
||||
/// caller's, `deciders` the door's list.
|
||||
pub fn desk_answer(
|
||||
&mut self,
|
||||
deciders: &[Short],
|
||||
subject: Short,
|
||||
attribution: DeskAuthority,
|
||||
request: ForkAnswerRequest,
|
||||
now: DateTime<Utc>,
|
||||
) -> (DeskResponse, bool) {
|
||||
match self.desk_response(deciders, subject, attribution, &request, now) {
|
||||
Ok(done) => done,
|
||||
Err(refused) => (door_refusal(refused), false),
|
||||
}
|
||||
}
|
||||
|
||||
fn desk_response(
|
||||
&mut self,
|
||||
deciders: &[Short],
|
||||
subject: Short,
|
||||
attribution: DeskAuthority,
|
||||
request: &ForkAnswerRequest,
|
||||
now: DateTime<Utc>,
|
||||
) -> Result<(DeskResponse, bool), MindRefusal> {
|
||||
let sent = match request_of(request) {
|
||||
Ok(sent) => sent,
|
||||
Err(refused) => return Ok((DeskResponse::NotStored(refused), false)),
|
||||
};
|
||||
let image = self.image()?;
|
||||
let unshowable = matches!(
|
||||
Docs::at(image, image.head).find(PipelineKind::Question, &sent.fork.0),
|
||||
Some(PipelineDocument::Question(question)) if !showable(question, deciders)
|
||||
);
|
||||
if unshowable {
|
||||
return Ok((DeskResponse::NotStored(fork_unknown()), false));
|
||||
}
|
||||
let stamp = DeskStamp { answerer: subject, authority: attribution, 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),
|
||||
DeskAnswerOutcome::Refused(refused) => return Err(refused),
|
||||
};
|
||||
let image = self.image()?;
|
||||
let docs = Docs::at(image, image.head);
|
||||
let Some(PipelineDocument::DeskAnswer(stored)) = docs.find(PipelineKind::DeskAnswer, &key) else { return Err(unreadable_key()) };
|
||||
let fork = stored.fork.0.as_str();
|
||||
let counted_answer_id = counted_answer(&docs, fork).map(|counted| answer_seq(fork, &counted).map(n_text).ok_or_else(unreadable_key)).transpose()?;
|
||||
Ok((DeskResponse::Answered { answer: project(&key, stored)?, counted_answer_id, as_of: image.head }, replayed))
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
pub mod admission;
|
||||
mod desk;
|
||||
pub mod desk_wire;
|
||||
mod docs;
|
||||
mod image;
|
||||
pub mod envelope;
|
||||
|
|
@ -54,6 +55,7 @@ mod read_tests;
|
|||
mod move_tests;
|
||||
|
||||
pub use desk::{DeskAnswerOutcome, DeskAnswerRequest, DeskBody, options_hash};
|
||||
pub use desk_wire::{DeskResponse, ForkAnswer, ForkAnswerCode, ForkAnswerRequest, ForkDeskAnswer, ForkRefusal, ForkRefusalCode};
|
||||
pub use docs::CitationRole;
|
||||
pub use index::{INDEX_TEXT_MAX_BYTES, INDEX_TEXT_VERSION, IndexEntry, index_text};
|
||||
pub use admission::{BATCH_MAX, PipelineAdmissionBatch, PipelineAdmissionOutcome};
|
||||
|
|
|
|||
|
|
@ -1,343 +0,0 @@
|
|||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
14863
crates/huginn-mind/vectors/fork-card-vectors.json
Normal file
14863
crates/huginn-mind/vectors/fork-card-vectors.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue