("motion_list_two","motionList",{"schemaVersion":"gamecult.vote.motion_list.v0","motions":[item(M1,"Ledger or lantern","Commit"),item(M2,"Second thoughts","Closed",True)]}),
("unauthenticated",401,"no bearer, or one Heimdall does not know",refusal("unauthenticated","A bearer token is required.")),
("reauthentication_required",401,"a commit on a high-stakes motion with no sign-in fresh enough (Bifrost PR #37, D2); the voter signs in again at the top level and retries",refusal("reauthentication_required","Sign in again to commit on this motion.")),
("forbidden",403,"authenticated, not an Active member, or a Mind key used on a motion route it may not call",refusal("forbidden","Only Active members may vote.")),
("not_found",404,"unknown motion id, a standard (non-sealed) motion id, or reveal without a prior commit",refusal("not_found","No such sealed-ballot motion.")),
("wrong_phase",409,"commit outside Commit, reveal outside Reveal",refusal("wrong_phase","The commit window is not open.")),
("contended",409,"reveal raced the close fence; send it again",refusal("contended","The reveal is contended; send it again.")),
("invalid",400,"body fails vote-api.schema.json or an in-document rule (untrimmed candidate or reason, candidate not in the motion, round or motionId mismatch, persona wrong for the credential)",refusal("invalid","The cast does not satisfy gamecult.vote.cast.v0.")),
("commitment_mismatch",400,"reveal whose canonical bytes do not hash to the stored commitment",refusal("commitment_mismatch","The ballot does not match the stored commitment.")),
("payload_too_large",413,"body over 4096 bytes",refusal("payload_too_large","The body exceeds 4096 bytes.")),
("held",503,"the Ratatoskr voting brake is set (Bifrost PR #37, D5): no cast is read or stored while it is",refusal("held","Voting is held.")),
("rate_limited",429,"more than the per-account cast rate; Retry-After mirrors retryAfterSeconds",refusal("rate_limited","Too many casts.",30)),
]
deftweak(d,**kw):
d=json.loads(json.dumps(d));
fork,vinkw.items():
ifk.startswith("b_"):d["ballot"][k[2:]]=v
else:d[k]=v
returnd
rev=cast_reveal(B_MEMBER)
invalid=[
("cast_commit_carries_a_ballot","cast",tweak(cast_commit(C_MEMBER),ballot=B_MEMBER),"a commit names exactly the commitment; a ballot beside it would put content on the wire before the reveal"),
("cast_reveal_carries_a_commitment","cast",tweak(rev,commitment=C_MEMBER),"a reveal names exactly the ballot"),
("cast_commit_without_commitment","cast",tweak(cast_commit(C_MEMBER),commitment=None),"commit needs its hash"),
("cast_verb_unknown","cast",tweak(rev,verb="recast"),"verb is commit or reveal"),
("cast_commitment_uppercase","cast",cast_commit(C_MEMBER.upper()),"commitment is lowercase hex"),
("cast_commitment_63_chars","cast",cast_commit(C_MEMBER[:63]),"commitment is 64 hex characters"),
("cast_motion_id_uppercase","cast",cast_commit(C_MEMBER,M1.upper()),"ids are lowercase D-format uuids"),
("ballot_reason_empty","cast",tweak(rev,b_reason=""),"reason is 1 to 280 characters"),
("ballot_reason_281","cast",tweak(rev,b_reason="x"*281),"reason is at most 280 characters"),
("ballot_reason_141_astral_282_units","cast",tweak(rev,b_reason="\U0001F600"*141),"reason is at most 280 UTF-16 code units (vote-api.md); the schema counts code points and accepts this, so a rule refuses it"),
("ballot_reason_multiline","cast",tweak(rev,b_reason="one\ntwo"),"reason is a single line"),
("ballot_reason_leading_space","cast",tweak(rev,b_reason=" because"),"the contract refuses untrimmed text; it never trims for the sender, so the hashed bytes are the sent bytes"),
("ballot_candidate_trailing_nbsp","cast",tweak(rev,b_candidate="Adopt the ledger"),"NO-BREAK SPACE is White_Space; trimming is judged by the same list as write-receipt Required fields"),
("ballot_salt_31_hex","cast",tweak(rev,b_salt=SALT[:31]),"salt is 32 to 128 lowercase hex characters"),
("ballot_salt_uppercase","cast",tweak(rev,b_salt=SALT.upper()),"salt is lowercase hex"),
("ballot_schema_wrong","cast",tweak(rev,b_schema="gamecult.sealed_ballot.v1"),"ballot schema is gamecult.sealed_ballot.v0"),
("ballot_persona_with_space","cast",tweak(rev,b_persona="my mind"),"persona is printable ASCII without space, at most 64"),
("ballot_round_zero","cast",tweak(rev,b_round=0),"rounds start at 1"),
("receipt_echoes_candidate","receipt",dict(receipt(C_MEMBER,2,True),candidate="Adopt the ledger"),"sealed casts never echo ballot content; the receipt is closed"),
("receipt_echoes_salt","receipt",dict(receipt(C_MEMBER,2,True),salt=SALT),"the salt never comes back before close"),
("receipt_revision_zero","receipt",receipt(C_MEMBER,0),"revision starts at 1"),
("receipt_time_without_zone","receipt",dict(receipt(C_MEMBER),committedAtUtc="2026-11-12T19:04:11"),"instants are UTC with a Z"),
("motion_one_candidate","motion",dict(motion("Commit"),candidates=["Only"]),"a motion has two or more candidates"),
("motion_phase_unknown","motion",dict(motion("Commit"),phase="Open"),"phase is Commit, Reveal or Closed"),
("motion_high_stakes_not_boolean","motion",dict(motion("Commit"),highStakes="yes"),"highStakes is a boolean, fixed at open"),
("motion_without_high_stakes","motion",{k:vfork,vinmotion("Commit").items()ifk!="highStakes"},"map-mode writes every key; a motion states whether it is high-stakes"),
("motion_list_item_without_high_stakes","motionListItem",{k:vfork,vinitem(M1,"x","Commit").items()ifk!="highStakes"},"every list item states whether the motion is high-stakes"),
("motion_leaks_tally","motion",dict(motion("Reveal"),tally=[]),"mid-vote a client sees no tallies; the document is closed"),
("motion_list_item_without_phase","motionList",{"schemaVersion":"gamecult.vote.motion_list.v0","motions":[{k:vfork,vinitem(M1,"x","Commit").items()ifk!="phase"}]},"every item names its phase"),
("refusal_code_unknown","refusal",refusal("teapot","no"),"the code set is closed"),
("refusal_without_error","refusal",dict(refusal("invalid","x"),error=""),"error is non-empty"),
{"name":"reveal_with_changed_reason_does_not_match","voterUserAccountId":V1,"commit":cast_commit(C_MEMBER),"reveal":cast_reveal(ballot(reason="It keeps the books honest!")),"matches":False,"refusal":"commitment_mismatch"},
"provenance":"Spec-authored with msgpack-python (scripts/gen-governance-vectors.py); every runtime re-encodes `message` and must match messagePackHex. Commitment hashes follow vote-api.md 'Commitment'.",
"""RFC 8785 over {context, depends, options:[{id,text}], recommendation, title} after NFC on every string; keys sorted; no numbers. (None is accepted only to check Heimdall's null digests.)"""
CD=dict(title="Caf\u00e9\u201cdesk\u201d",context="Line one\nLine two \u2014\U0001F600",options=[{"id":"a","text":"x"},{"id":"b","text":"y"}],recommendation="a",depends=[])
TAIL={"unauthenticated":"a verified session is required","body_forbidden_field":"answerer and authority are set by the receiver","body_unknown_field":"the body is a closed shape",
"body_invalid":"the body does not satisfy gamecult.fork.answer_request.v0","fork_unknown":"no such fork",
"insufficient_scope":"the token lacks the scope this route needs","forbidden":"only Active members may use the desk","request_id_conflict":"this request_id was used for a different body","request_invalid":"the request does not satisfy its document"}
defunavailable_doc(retry=30):return{"schemaVersion":"gamecult.fork.unavailable.v0","code":"unavailable","error":"unavailable: the outcome is not confirmed, try again with the same request_id","retryAfterSeconds":retry}
# A page may stop short of `limit` when the next entry would pass the receiver's response budget; a list whose first
# entry alone passes it is refused `fork_too_large` (422). The budget is not in the vector: a step records what the receiver answered, and the
# judges check it against the unbudgeted page (a nonempty strict prefix, resumed below its last entry) or against the entry the page would open with.
deftoo_large_doc(c):return{"schemaVersion":"gamecult.fork.refusal.v0","code":"fork_too_large","error":f"fork_too_large: fork {c['fork_id']} (order {c['order']}) alone is past the response budget"}
defread_too_large_doc(c):return{"schemaVersion":"gamecult.fork.refusal.v0","code":"fork_too_large","error":f"fork_too_large: fork {c['fork_id']} alone is past the response budget"}
# An answer on a stale card whose current-card re-read is past the cap: the same refusal document as the read form over a stored Refused(stale_fork), never counted.
dg("plain_answer_choice_A",MAIN,"8a4d5b2ed0b3452bfed2f3a276b2828974e6824a3edcc643cfe9cfcdda34173a","a plain answer: choice and note as sent, retracts null; no illegal or retracted key",choice="A"),
dg("legal_retraction",MAIN,"9f8075382556898c0bea364ed7c6deb2a86d8c78288a1f34e06da6e9bf5f58a3","a legal retraction as Huginn main pins it: retracts is `Q1.1`, a string no door produces (the key grammar is .n<seq>); kept for the frozen-form test",retracts="eureka-state:desk_answer:Q1.1"),
dg("illegal_i_and_iii_alike_on_main",MAIN,"69074cc60829ef5edb12358746f7549ad08da97653d59359c945cea102f53565","main writes `illegal_retraction: true` and no `retracted`, so (i) and (iii) collide: the defect the fix removes",choice="A",retracts=DT,illegal_retraction=True),
dg("legal_retraction_door_form",MAIN,"84576840247830b9b75754b460dc4ea52883ca1e410f9f011521e0f69b99eee1","a legal retraction as the door produces it: the wire's n1 converted to the full stored key Q1.n1 before the digest; the golden that stands for the real form (digest unaffected by .n<seq> vs .1)",retracts=DT),
dg("illegal_ii_on_main",MAIN,"1a36c4aba0d1c30d0736f20243ebfdb84fe84a5eabd4f69709a096a32c3cc6e3","(ii) no answer, no target, on main",illegal_retraction=True),
dg("illegal_iii_plain_answer_with_a_target_retracted_false",FIX,"82b24cccb4ed2535a549f95f844fcd935074c3712b5b832f0b2cbd89c71fd9a6","(iii) the same bytes as (i) with retracted false: another body, so request_id_conflict",choice="A",retracts=DT,illegal_retraction=True,retracted=False),
dg("illegal_i_retraction_with_an_answer_and_no_target_retracted_true","reproduced_on_huginn_main_f0ca504_reported_by_eureka_not_rerun_here","79eee623fd6712db6537a4867cedf54ca49ee4885416c2c8e4acec59f811b894","(i) with the target null (the order doc: retracts set or null); Eureka re-derived it with sha256sum (order doc addendum)",choice="A",illegal_retraction=True,retracted=True),
dg("illegal_ii_no_answer_no_target_retracted_true",FIX,"34ef86ed63b9318459965d4b85e794d2636ffc15736da866036b087c9c4a30eb","(ii) no answer, no target, retracted true",illegal_retraction=True,retracted=True),
"provenance":"Rule source: /opt/commons/handoff/fork-desk-answer-binding-20261110.md (Heimdall), amended by the Persona briefs of 2026-11-10 (Eureka's fit check). Bytes spec-authored with msgpack-python (scripts/gen-governance-vectors.py); every runtime re-encodes `message` and must match messagePackHex. options_hash follows fork-card.md 'Options hash'; the vectors named heimdall_* are that file's, with its digests.",
{"name":"leans_flag_closed_two_deciders","message":card(leans="yes",flag="provisional: Self expects this to move",deciders=[OP,HM],depends=[],raised_in=None,status="closed",ruling="x:resolution:y",ruled_from=["n1"],order=0),
"messagePackHex":hx(card(leans="yes",flag="provisional: Self expects this to move",deciders=[OP,HM],depends=[],raised_in=None,status="closed",ruling="x:resolution:y",ruled_from=["n1"],order=0))},
("depends_nine_lines","depends holds at most 8 lines (the stored question's limit)",card(depends=["a"]*9)),
("depends_line_1001_code_points","a depends line is at most 1000 characters",card(depends=["a"*1001])),
("depends_line_1001_bytes","rule: one byte over the limit (500 two-byte characters and one more; not expressible in the schema)",card(depends=["\u00e9"*500+"a"])),
("depends_line_over_1000_bytes","rule: a depends line is at most 1000 UTF-8 bytes (the stored Line limit; not expressible in the schema)",card(depends=["\u00e9"*501])),
("option_with_a_label","options are {id,text}; the old format's display label is not a member (fork-card.md, Old format)",card(options=[dict(OPT[0],label="Yes"),OPT[1]])),
("status_unknown","status is open or closed",card(status="provisional")),
("options_hash_not_hex","options_hash is 64 lowercase hex",dict(card(),options_hash="XYZ")),
("duplicate_option_ids","rule: option ids are unique (not expressible in the schema)",card(options=[OPT[0],dict(OPT[1],id=OPT[0]["id"])])),
("recommendation_not_an_option","rule: recommendation is an option id (not expressible in the schema)",card(recommendation="maybe")),
("leans_not_an_option","rule: leans is nil or an option id (not expressible in the schema)",card(leans="maybe")),
("duplicate_deciders","rule: deciders are unique (not expressible in the schema)",card(deciders=[OP,OP])),
("ruled_from_on_an_open_card","rule: a ruling that answers a fork closes it, so an open card cites no answer in ruled_from (not expressible in the schema)",card(ruled_from=["n1"])),
("open_card_names_a_ruling","rule: only a closed card names a ruling (not expressible in the schema)",card(ruling="cultlib-gaps:resolution:question.slot-mark-consent.n1")),
("ruled_from_without_a_ruling","rule: ruled_from lists answers a ruling was admitted from, so it needs the ruling (not expressible in the schema)",card(status="closed",ruled_from=["n1"])),
("options_hash_of_another_card","rule: options_hash equals the hash of the shown members (not expressible in the schema)",dict(card(),options_hash=STALE_H)),
("answer_with_neither","an answer object holds a choice, a note, or both",body(answer={"choice":None,"note":None})),
("blank_note","a blank note is no answer",body(None,"\u00a0")),
("request_id_missing","map-mode writes every key; a retry needs the id",{k:vfork,vinx1.items()ifk!="request_id"}),
("request_id_uppercase","request_id is lowercase",dict(x1,request_id="0000000A-0000-4000-8000-00000000000B")),
("request_id_not_a_uuid","request_id is a lowercase D-format UUID",dict(x1,request_id="retry-1")),
("note_over_4000","notes are at most 4000 UTF-8 bytes",body(None,"x"*4001)),
("note_multibyte_4001_bytes","a note is counted in UTF-8 bytes: 1999 e-acute is 3998 bytes, plus three ASCII is 4001 (rule: not expressible in the schema, 2002 characters)",body(None,"\u00e9"*1999+"abc")),
("note_astral_4004_bytes","1001 astral characters are 4004 bytes though only 1001 code points (rule: not expressible in the schema)",body(None,"\U0001F600"*1001)),
("note_combining_4002_bytes","e plus a combining acute is 3 bytes: 1334 of them are 4002 (rule: not expressible in the schema)",body(None,"e\u0301"*1334)),
("options_hash_uppercase","hash is lowercase hex",body("no",None,h=H.upper())),
("answerer_key","answerer is server-set; the door answers body_forbidden_field",dict(x1,answerer=OP)),
("authority_key","authority is server-set; the door answers body_forbidden_field",dict(x1,authority="Operator")),
("answer_object_with_a_key","the answer object is closed",body(answer={"choice":"no","note":None,"weight":1})),
("answered_at_without_zone","instants are UTC with a Z",body("no",None,at="2026-11-12T19:02:00")),
("retracted_not_a_boolean","retracted is a boolean",dict(x1,retracted="no")),
("fork_id_empty","fork_id names a fork",body("no",None,fork="")),
("revision_naming_none","a Revision names the counted answer",dict(REV,of=None)),
("revision_that_also_retracts","a variant carries only its own link (a retraction with no target, or a target with no retraction, cannot exist)",dict(REV,retracts="n1")),
("retraction_naming_no_target","a Retraction names the answer it retracts",dict(da("Retraction","n1",None,retraction("n1"),sr("n8")),retracts=None)),
("refused_naming_another_answer_as_of","a Refused answer names no counted answer (`of` is a Revision's)",dict(REF,of="n1")),
("refused_with_a_target_that_is_not_an_answer_id","a Refused answer's retracts is nil or an answer id (n<seq>), never the stored key",dict(REF,retracts="cultlib-gaps:desk_answer:slot-mark-consent.n1")),
("entry_without_record","an entry holds the card and its record",{k:vfork,vinENTRIES[0].items()ifk!="record"}),
("record_of_another_fork","rule: the record names the card's fork (not expressible in the schema)",dict(ENTRIES[0],record=dict(ENTRIES[0]["record"],fork_id="gamecult-chat:question:q9"))),
("record_under_another_hash","rule: the record is under the card's options_hash (not expressible in the schema)",dict(ENTRIES[0],record=dict(ENTRIES[0]["record"],options_hash=STALE_H))),
("counted_answer_not_derived","rule: counted_answer_id is the first live Counted or Revision answer under the card's hash (not expressible in the schema)",dict(ENTRIES[0],record=dict(ENTRIES[0]["record"],counted_answer_id=None))),
("card_hash_wrong","rule: the server computes options_hash and a client recomputes it; a card with another hash is no card",dict(ENTRIES[1],card=dict(ENTRIES[1]["card"],options_hash=STALE_H),record=dict(ENTRIES[1]["record"],options_hash=STALE_H)))]]},
("count_negative","counts are not negative",dict(LISTS[0]["steps"][0]["response"],open_count=-1))]]
+[dict(bad("two_cards_share_one_order","rule: order is a unique rank per Mind; the refusal names both fork ids (not expressible in the schema)",DUP_LIST),