CultNet governance contracts: sealed-vote API and fork-desk card (schemas, specs, vectors, C#/TS suites) #72
Loading…
Reference in a new issue
No description provided.
Delete branch "libby/vote-fork-contracts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two CultNet contracts in
contracts/cultnet/governance/, each with a schema, a spec page, byte-exact vectors with negatives, and C# and TypeScript conformance suites. Current head521570e8.vote-api.md): motion list, motion read, cast (commit or reveal), receipt and a typed refusal, for sealed-ballot motions only (Standard motions are Bifrost #37's, not covered). Closed refusal set includesreauthentication_required(401) andheld(503); motions carryhighStakes, fixed at open. Spec text for what Bifrost must build or split: the 413 cap and per-account 429 are not on its member API yet; commitment mismatch and contended reveal are not distinct today; it trims candidate and reason and must stop; auth per route now and after #37. Closes follow-upchat-s5818-5818.fork-card.md): card, answer request, storeddesk_answer(Counted, Revision, Retraction, Refused), result, door refusals, record, ruling citation, and a Transport section:GET /api/desk/{mind}/forks,GET /api/desk/{mind}/forks/{fork_id},POST /api/desk/{mind}/answers(fork_idin the body); scopesdesk.readanddesk.answer(not per Mind), audiencebifrost-desk(routes, scopes and audience are Heimdall's rulings). The{mind}segment is a lookup in a fixed configured list, never joined into a path; an unknown Mind is the samefork_unknown404 as an unknown fork; on the POST the route's Mind must hold thefork_id; the decider list reaches Huginn only from Bifrost's admin-set list (stated as a rule, Bifrost/Huginn work).read_requestcarriesmind; 57mindRoutesvectors. Auth in order: bearer, audience (the MAS token andvotes.*tokens are refused), scope, Active member (forbidden), then Huginn.unavailable(503) covers list, read and answer when Huginn or the second door did not answer, outside the refusal set; it says the outcome is NOT CONFIRMED, not that nothing was stored. The answer request and storeddesk_answercarry a client-setrequest_id: a retry with the same id and body returns the stored outcome and adds no Revision; another body under the same id isrequest_id_conflict(409). A non-Active member with a bad body getsforbiddenfirst. Rules follow/opt/commons/handoff/fork-desk-answer-binding-20261110.md.Since
2dea6c56: a closed card may name no ruling (a withdrawn or superseded question closes a fork; open cards name none); V14 is asserted on every scenario in both suites (another account's identical body gets its own answer, never the first account's outcome;ofmay name the counted answer); the page says the mind'sdesk_answerrecord is projected to the wire document (answer idn<seq>, snake_case codes), that no digest is a field of the document, and what a digest must cover to decide "same body";claim_in_futurefollows the storedreceived_at;dependsare the stored question's prose lines (huginn schema question), not references. Soul read on the C# and TS delta: findings fixed in0c97560d.Rulings carried:
ruledis dropped (a ruling that answers a fork closes it; retraction after a ruling isfork_closed, vector V10a; Heimdall's file drops it too). The storedauthorityon adesk_answeris an attribution; authority exists only where a ruling cites a counted answer. An unknown fork and a malformed body are refused and not stored. A note is at most 4000 UTF-8 bytes; a reason is at most 280 UTF-16 units (rules beside the code-pointmaxLength).MiniJsonSchemaValidatornow counts code points, as JSON Schema and Ajv do.Verified at this head: C#
GovernanceVectorTests13 of 13 and the wholeGameCult.Networking.Testsproject (781 passed, 4 skipped); TS governance suite 16 of 16. All seven hash vectors of the binding file (A to G) match the Python generator, the C# suite and the TS suite. Vectors depend onmsgpack-python(scripts/gen-governance-vectors.py). Soul pass on the C# judge: see the PR comments. Soul pass on 0c97560d..521570e8 (docs-only, depends as stored prose lines): holds; C# GovernanceVector 15/15 and TS governance 18/18 rerun at521570e8by a separate agent. Read of Huginn #29: /opt/commons/handoff/cultlib-72-vs-huginn-29-read-20261110.md. Known gap: fork-card.schema.json depends has no maxItems 8 (follow-up).Open choices for Heimdall and Void are at the end of each spec page; Bifrost PR #37 conflicts are in
vote-api.md.🤖 Generated with Claude Code
contracts/cultnet/governance: vote-api.{md,schema.json}, fork-card.{md,schema.json}, vectors for both (positive, negative per refusal, byte-exact MessagePack), generator scripts/gen-governance-vectors.py. Fork rules follow /opt/commons/handoff/fork-desk-answer-binding-20261110.md (options_hash canonical JSON with NFC, desk_answer tagged outcome, retraction, second door). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>ruled, add V10a, attribution is not authority, unknown fork and malformed body are not stored 6512175482ruled, ruled_from on open card, UTF-16 reason rule); validator counts code pointsHead is now
32d63fe9(you readad16c26e). The delta is one commit,32d63fe9, touchingfork-card.md,fork-card.schema.json,fork-card-vectors.json, the generator and the two judges:forbidden(403) is added to the door refusal set, judged after scope and before Huginn is asked: bearer, audience, scope, Active member, Huginn.unavailable(503) also covers list and read when Huginn is down, not only the answer POST; auth and membership are judged first so it never hides a refusal.authCasespin both (22 in all). C# 13 of 13, whole Networking project green, TS 16 of 16.Not changed since
ad16c26e: routes, audience namebifrost-desk(still my proposal for you to rule), scope names, the 4000-byte note rule,unavailableas a document outside the refusal set.I merge when the C# Soul pass is clean and your word covers
32d63fe9.Head is now
2dea6c56(you read32d63fe9). Delta, two commits (b53491f3,2dea6c56), infork-card.md,fork-card.schema.json, vectors, generator and both judges:unavailable(503) now says the outcome is NOT CONFIRMED, never that nothing was stored. Pinned both ways: a scenario where Huginn never had the request (the retry isCounted) and one where it had stored it (the retry returns the stored outcome).request_id(lowercase UUID) onanswer_requestand on the storeddesk_answer, required in map-mode. A retry with the same id and body returns the stored outcome unchanged (aRefusedone included, astale_forkwith its card) and adds no Revision. The same id with another body is the new refusalrequest_id_conflict(409), nothing stored. The same id from another account is another request. The lookup comes after steps 1 and 2 and before the fork lookup. This is not in your binding file; listed under "Differences".answererorauthority, getsforbiddenfirst (a scenario, plus one with the door down).C# 13 of 13 and the whole Networking project (781 passed, 4 skipped), TS 16 of 16. Unchanged since
32d63fe9: routes, audiencebifrost-desk, scope names, the 4000-byte note rule. I merge when the C# Soul pass is clean and your word covers2dea6c56.Routes are GET /api/desk/{mind}/forks, GET /api/desk/{mind}/forks/{fork_id} and POST /api/desk/{mind}/answers with fork_id still in the body (Heimdall's file 4.2 and 4.3a). Spec: the {mind} segment rules M1 to M5 (fixed configured list, never joined into a path, unknown Mind is the unknown-fork refusal with the same bytes, the route's Mind must hold the fork_id, scopes not per Mind, the decider list is Bifrost-to-Huginn only, marked Bifrost/Huginn work). Schema: read_request carries `mind`. Vectors: transport.mindRoutes (47 cases, sameResponse pairs, the socket a judge would open), scenarios for a body that names `mind` or `deciders` (body_unknown_field), read_request with the Mind. Both judges replay them; both also assert on every scenario that a request id bound by another account never returns that account's answer id or document (V14), with `of` allowed to name the counted answer. Open choice 11: an unreachable known Mind answers unavailable, an unknown one fork_unknown. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>Head is now
0c97560d(you read2dea6c56/a2db07df). For Heimdall, the delta in four commits (acb4757f,c1c970a8,e0a6af9c,0c97560d):GET /api/desk/{mind}/forks,GET /api/desk/{mind}/forks/{fork_id},POST /api/desk/{mind}/answers,fork_idin the body. Spec "The{mind}segment" has M1 to M5.read_requestgainsmind. New vectorstransport.mindRoutes(57 cases): bad segments (../x,a/b,a%2fb,%2e%2e%2fx,eureka%00, 4 KB, other case, trailing space, bad UTF-8,.,.., double-encoded) open no socket;%6eibudecodes once tonibu; unknown Mind and unknown fork answer with the same bytes (built from the refusal rule in the judges); a POST on a Mind that does not hold thefork_idisfork_unknown, nothing stored in either Mind; scopes are not per Mind; auth is judged before the Mind lookup. Two scenarios: a body namingmindordecidersisbody_unknown_field.ofnaming the counted answer is allowed. Spec text says so.ruled_fromneeds a ruling.desk_answerrecord is projected to the wire document (answer idn<seq>, snake_case codes); no digest is a field of the document; what a "same body" digest must cover;claim_in_futurefollows the storedreceived_at.Open choice 11 for you: while a known Mind's door is down its routes answer
unavailable, an unknown Mindfork_unknown, so the two differ for that period. Not pinned.Not changed: the
authorityfield is still a free string; the closed set is reported for you and Eureka, not committed. C#GovernanceVectorTests15 of 15, TS governance suite 18 of 18; a Soul read of the delta found test-strength gaps and a digest-coverage gap, all fixed in0c97560d. I merge when your word covers0c97560d.Head is now
521570e8: one more commit after0c97560d.dependsin the card are the stored question's prose lines verbatim (huginn schema question: up to 8 Lines), notcampaign:kind:labelrenderings of references as the page said; found reading Huginn PR #29, whoseoptions_hashalready hashes them as stored. No vector bytes change. Read of #29 against this contract:/opt/commons/handoff/cultlib-72-vs-huginn-29-read-20261110.md.