diff --git a/contracts/cultnet/governance/vote-api-vectors.json b/contracts/cultnet/governance/vote-api-vectors.json index b9ae2ba5..4f52152d 100644 --- a/contracts/cultnet/governance/vote-api-vectors.json +++ b/contracts/cultnet/governance/vote-api-vectors.json @@ -93,7 +93,8 @@ "round": 1, "phase": "Commit", "commitClosesAtUtc": "2026-11-12T18:00:00Z", - "closesAtUtc": "2026-11-15T18:00:00Z" + "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false }, { "id": "a7d3f0c2-6b15-4e29-8f3a-91c4d5e6f708", @@ -101,11 +102,12 @@ "round": 1, "phase": "Closed", "commitClosesAtUtc": "2026-11-12T18:00:00Z", - "closesAtUtc": "2026-11-15T18:00:00Z" + "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": true } ] }, - "messagePackHex": "82AD736368656D6156657273696F6EBC67616D6563756C742E766F74652E6D6F74696F6E5F6C6973742E7630A76D6F74696F6E739286A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA5726F756E6401A57068617365A6436F6D6D6974B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305A86A26964D92461376433663063322D366231352D346532392D386633612D393163346435653666373038A57469746C65AF5365636F6E642074686F7567687473A5726F756E6401A57068617365A6436C6F736564B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305A" + "messagePackHex": "82AD736368656D6156657273696F6EBC67616D6563756C742E766F74652E6D6F74696F6E5F6C6973742E7630A76D6F74696F6E739287A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA5726F756E6401A57068617365A6436F6D6D6974B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C287A26964D92461376433663063322D366231352D346532392D386633612D393163346435653666373038A57469746C65AF5365636F6E642074686F7567687473A5726F756E6401A57068617365A6436C6F736564B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C3" }, { "name": "motion_list_empty", @@ -143,11 +145,37 @@ ], "commitClosesAtUtc": "2026-11-12T18:00:00Z", "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false, "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", "yourCommitment": null, "announcement": null }, - "messagePackHex": "8EAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436F6D6D6974AA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" + "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436F6D6D6974AA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C2AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" + }, + { + "name": "motion_high_stakes_commit_phase", + "def": "motion", + "message": { + "schemaVersion": "gamecult.vote.motion.v0", + "id": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "title": "Ledger or lantern", + "summary": "Pick one.", + "round": 1, + "priorMotionId": null, + "ballotSchema": "gamecult.sealed_ballot.v0", + "phase": "Commit", + "candidates": [ + "Adopt the ledger", + "Defer to round two" + ], + "commitClosesAtUtc": "2026-11-12T18:00:00Z", + "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": true, + "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", + "yourCommitment": null, + "announcement": null + }, + "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436F6D6D6974AA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C3AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" }, { "name": "motion_reveal_phase_committed", @@ -167,6 +195,7 @@ ], "commitClosesAtUtc": "2026-11-12T18:00:00Z", "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false, "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", "yourCommitment": { "schemaVersion": "gamecult.vote.receipt.v0", @@ -179,7 +208,7 @@ }, "announcement": null }, - "messagePackHex": "8EAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A652657665616CAA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E7487AD736368656D6156657273696F6EB867616D6563756C742E766F74652E726563656970742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133B2766F746572557365724163636F756E744964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AA636F6D6D69746D656E74D94038363533323863623461333134386638656263616137356237323936653235376332383761623766656336376663363062363664396463363461306433653862A87265766973696F6E01AE636F6D6D69747465644174557463BC323032362D31312D31325431393A30343A31312E313233343536375AA872657665616C6564C2AC616E6E6F756E63656D656E74C0" + "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A652657665616CAA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C2AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E7487AD736368656D6156657273696F6EB867616D6563756C742E766F74652E726563656970742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133B2766F746572557365724163636F756E744964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AA636F6D6D69746D656E74D94038363533323863623461333134386638656263616137356237323936653235376332383761623766656336376663363062363664396463363461306433653862A87265766973696F6E01AE636F6D6D69747465644174557463BC323032362D31312D31325431393A30343A31312E313233343536375AA872657665616C6564C2AC616E6E6F756E63656D656E74C0" }, { "name": "motion_closed_revealed", @@ -199,6 +228,7 @@ ], "commitClosesAtUtc": "2026-11-12T18:00:00Z", "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false, "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", "yourCommitment": { "schemaVersion": "gamecult.vote.receipt.v0", @@ -211,7 +241,27 @@ }, "announcement": null }, - "messagePackHex": "8EAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436C6F736564AA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E7487AD736368656D6156657273696F6EB867616D6563756C742E766F74652E726563656970742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133B2766F746572557365724163636F756E744964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AA636F6D6D69746D656E74D94038363533323863623461333134386638656263616137356237323936653235376332383761623766656336376663363062363664396463363461306433653862A87265766973696F6E02AE636F6D6D69747465644174557463BC323032362D31312D31325431393A30343A31312E313233343536375AA872657665616C6564C3AC616E6E6F756E63656D656E74C0" + "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436C6F736564AA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C2AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E7487AD736368656D6156657273696F6EB867616D6563756C742E766F74652E726563656970742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133B2766F746572557365724163636F756E744964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AA636F6D6D69746D656E74D94038363533323863623461333134386638656263616137356237323936653235376332383761623766656336376663363062363664396463363461306433653862A87265766973696F6E02AE636F6D6D69747465644174557463BC323032362D31312D31325431393A30343A31312E313233343536375AA872657665616C6564C3AC616E6E6F756E63656D656E74C0" + }, + { + "name": "cast_reveal_reason_140_astral_280_units", + "def": "cast", + "message": { + "schemaVersion": "gamecult.vote.cast.v0", + "motionId": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "verb": "reveal", + "commitment": null, + "ballot": { + "schema": "gamecult.sealed_ballot.v0", + "motionId": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "round": 1, + "candidate": "Adopt the ledger", + "reason": "😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀", + "salt": "d41d8cd98f00b204e9800998ecf8427e5f1c0a7b3e92d6a48b15c7f0e3a9d2b6", + "persona": "" + } + }, + "messagePackHex": "85AD736368656D6156657273696F6EB567616D6563756C742E766F74652E636173742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A476657262A672657665616CAA636F6D6D69746D656E74C0A662616C6C6F7487A6736368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A5726F756E6401A963616E646964617465B041646F707420746865206C6564676572A6726561736F6EDA0230F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880A473616C74D94064343164386364393866303062323034653938303039393865636638343237653566316330613762336539326436613438623135633766306533613964326236A7706572736F6E61A0" }, { "name": "cast_commit", @@ -466,6 +516,27 @@ "why": "reason is at most 280 characters", "messagePackHex": "85AD736368656D6156657273696F6EB567616D6563756C742E766F74652E636173742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A476657262A672657665616CAA636F6D6D69746D656E74C0A662616C6C6F7487A6736368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A5726F756E6401A963616E646964617465B041646F707420746865206C6564676572A6726561736F6EDA01197878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878A473616C74D94064343164386364393866303062323034653938303039393865636638343237653566316330613762336539326436613438623135633766306533613964326236A7706572736F6E61A0" }, + { + "name": "ballot_reason_141_astral_282_units", + "def": "cast", + "message": { + "schemaVersion": "gamecult.vote.cast.v0", + "motionId": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "verb": "reveal", + "commitment": null, + "ballot": { + "schema": "gamecult.sealed_ballot.v0", + "motionId": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "round": 1, + "candidate": "Adopt the ledger", + "reason": "😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀", + "salt": "d41d8cd98f00b204e9800998ecf8427e5f1c0a7b3e92d6a48b15c7f0e3a9d2b6", + "persona": "" + } + }, + "why": "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", + "messagePackHex": "85AD736368656D6156657273696F6EB567616D6563756C742E766F74652E636173742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A476657262A672657665616CAA636F6D6D69746D656E74C0A662616C6C6F7487A6736368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A86D6F74696F6E4964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A5726F756E6401A963616E646964617465B041646F707420746865206C6564676572A6726561736F6EDA0234F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880A473616C74D94064343164386364393866303062323034653938303039393865636638343237653566316330613762336539326436613438623135633766306533613964326236A7706572736F6E61A0" + }, { "name": "ballot_reason_multiline", "def": "cast", @@ -713,12 +784,13 @@ ], "commitClosesAtUtc": "2026-11-12T18:00:00Z", "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false, "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", "yourCommitment": null, "announcement": null }, "why": "a motion has two or more candidates", - "messagePackHex": "8EAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436F6D6D6974AA63616E6469646174657391A44F6E6C79B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" + "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436F6D6D6974AA63616E6469646174657391A44F6E6C79B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C2AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" }, { "name": "motion_phase_unknown", @@ -738,12 +810,78 @@ ], "commitClosesAtUtc": "2026-11-12T18:00:00Z", "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false, "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", "yourCommitment": null, "announcement": null }, "why": "phase is Commit, Reveal or Closed", - "messagePackHex": "8EAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A44F70656EAA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" + "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A44F70656EAA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C2AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" + }, + { + "name": "motion_high_stakes_not_boolean", + "def": "motion", + "message": { + "schemaVersion": "gamecult.vote.motion.v0", + "id": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "title": "Ledger or lantern", + "summary": "Pick one.", + "round": 1, + "priorMotionId": null, + "ballotSchema": "gamecult.sealed_ballot.v0", + "phase": "Commit", + "candidates": [ + "Adopt the ledger", + "Defer to round two" + ], + "commitClosesAtUtc": "2026-11-12T18:00:00Z", + "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": "yes", + "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", + "yourCommitment": null, + "announcement": null + }, + "why": "highStakes is a boolean, fixed at open", + "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436F6D6D6974AA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573A3796573AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" + }, + { + "name": "motion_without_high_stakes", + "def": "motion", + "message": { + "schemaVersion": "gamecult.vote.motion.v0", + "id": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "title": "Ledger or lantern", + "summary": "Pick one.", + "round": 1, + "priorMotionId": null, + "ballotSchema": "gamecult.sealed_ballot.v0", + "phase": "Commit", + "candidates": [ + "Adopt the ledger", + "Defer to round two" + ], + "commitClosesAtUtc": "2026-11-12T18:00:00Z", + "closesAtUtc": "2026-11-15T18:00:00Z", + "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", + "yourCommitment": null, + "announcement": null + }, + "why": "map-mode writes every key; a motion states whether it is high-stakes", + "messagePackHex": "8EAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A6436F6D6D6974AA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0" + }, + { + "name": "motion_list_item_without_high_stakes", + "def": "motionListItem", + "message": { + "id": "5b1c2e9a-3f4d-4c8e-9a61-0d7e2f4b8c13", + "title": "x", + "round": 1, + "phase": "Commit", + "commitClosesAtUtc": "2026-11-12T18:00:00Z", + "closesAtUtc": "2026-11-15T18:00:00Z" + }, + "why": "every list item states whether the motion is high-stakes", + "messagePackHex": "86A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65A178A5726F756E6401A57068617365A6436F6D6D6974B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305A" }, { "name": "motion_leaks_tally", @@ -763,13 +901,14 @@ ], "commitClosesAtUtc": "2026-11-12T18:00:00Z", "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false, "yourVoterId": "0c9f6a4e-7d2b-4a35-b8e1-3f5a6c7d8e90", "yourCommitment": null, "announcement": null, "tally": [] }, "why": "mid-vote a client sees no tallies; the document is closed", - "messagePackHex": "8FAD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A652657665616CAA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0A574616C6C7990" + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E766F74652E6D6F74696F6E2E7630A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65B14C6564676572206F72206C616E7465726EA773756D6D617279A95069636B206F6E652EA5726F756E6401AD7072696F724D6F74696F6E4964C0AC62616C6C6F74536368656D61B967616D6563756C742E7365616C65645F62616C6C6F742E7630A57068617365A652657665616CAA63616E6469646174657392B041646F707420746865206C6564676572B2446566657220746F20726F756E642074776FB1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C2AB796F7572566F7465724964D92430633966366134652D376432622D346133352D623865312D336635613663376438653930AE796F7572436F6D6D69746D656E74C0AC616E6E6F756E63656D656E74C0A574616C6C7990" }, { "name": "motion_list_item_without_phase", @@ -782,12 +921,13 @@ "title": "x", "round": 1, "commitClosesAtUtc": "2026-11-12T18:00:00Z", - "closesAtUtc": "2026-11-15T18:00:00Z" + "closesAtUtc": "2026-11-15T18:00:00Z", + "highStakes": false } ] }, "why": "every item names its phase", - "messagePackHex": "82AD736368656D6156657273696F6EBC67616D6563756C742E766F74652E6D6F74696F6E5F6C6973742E7630A76D6F74696F6E739185A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65A178A5726F756E6401B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305A" + "messagePackHex": "82AD736368656D6156657273696F6EBC67616D6563756C742E766F74652E6D6F74696F6E5F6C6973742E7630A76D6F74696F6E739186A26964D92435623163326539612D336634642D346338652D396136312D306437653266346238633133A57469746C65A178A5726F756E6401B1636F6D6D6974436C6F7365734174557463B4323032362D31312D31325431383A30303A30305AAB636C6F7365734174557463B4323032362D31312D31355431383A30303A30305AAA686967685374616B6573C2" }, { "name": "refusal_code_unknown", @@ -967,6 +1107,18 @@ }, "messagePackHex": "84AD736368656D6156657273696F6EB867616D6563756C742E766F74652E7265667573616C2E7630A4636F6465AF756E61757468656E74696361746564A56572726F72BB412062656172657220746F6B656E2069732072657175697265642EB1726574727941667465725365636F6E6473C0" }, + { + "name": "reauthentication_required", + "httpStatus": 401, + "trigger": "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", + "message": { + "schemaVersion": "gamecult.vote.refusal.v0", + "code": "reauthentication_required", + "error": "Sign in again to commit on this motion.", + "retryAfterSeconds": null + }, + "messagePackHex": "84AD736368656D6156657273696F6EB867616D6563756C742E766F74652E7265667573616C2E7630A4636F6465B9726561757468656E7469636174696F6E5F7265717569726564A56572726F72D9275369676E20696E20616761696E20746F20636F6D6D6974206F6E2074686973206D6F74696F6E2EB1726574727941667465725365636F6E6473C0" + }, { "name": "forbidden", "httpStatus": 403, @@ -1051,10 +1203,22 @@ }, "messagePackHex": "84AD736368656D6156657273696F6EB867616D6563756C742E766F74652E7265667573616C2E7630A4636F6465B17061796C6F61645F746F6F5F6C61726765A56572726F72BC54686520626F6479206578636565647320343039362062797465732EB1726574727941667465725365636F6E6473C0" }, + { + "name": "held", + "httpStatus": 503, + "trigger": "the Ratatoskr voting brake is set (Bifrost PR #37, D5): no cast is read or stored while it is", + "message": { + "schemaVersion": "gamecult.vote.refusal.v0", + "code": "held", + "error": "Voting is held.", + "retryAfterSeconds": null + }, + "messagePackHex": "84AD736368656D6156657273696F6EB867616D6563756C742E766F74652E7265667573616C2E7630A4636F6465A468656C64A56572726F72AF566F74696E672069732068656C642EB1726574727941667465725365636F6E6473C0" + }, { "name": "rate_limited", "httpStatus": 429, - "trigger": "more than the per-credential cast rate; Retry-After mirrors retryAfterSeconds", + "trigger": "more than the per-account cast rate; Retry-After mirrors retryAfterSeconds", "message": { "schemaVersion": "gamecult.vote.refusal.v0", "code": "rate_limited", diff --git a/contracts/cultnet/governance/vote-api.md b/contracts/cultnet/governance/vote-api.md index cb953bcd..d38b2c75 100644 --- a/contracts/cultnet/governance/vote-api.md +++ b/contracts/cultnet/governance/vote-api.md @@ -6,7 +6,7 @@ Facts it rests on: `/opt/commons/handoff/bifrost-browser-voting-facts.md` (Bifro ## Scope -Sealed-ballot motions only (`gamecult.sealed_ballot.v0`). Standard motions have no JSON read or cast API at all today, no Mind path, and no receipt; they are not in v0 (open choice 1). The contract names four exchanges and one refusal document: +**Which API covers which.** This contract covers **sealed-ballot motions only** (`gamecult.sealed_ballot.v0`). **Standard motions are not covered here.** Bifrost PR #37 also serves them (`PUT /api/votes/motions/{id}/ballot`, a receipt `{motionId, choice, revision, castAtUtc, receiptId}`), and that API is #37's own: this page defines no document for it, and a Standard motion id sent to any exchange below is answered `not_found`. Today Standard motions have no JSON read or cast API at all, no Mind path, and no receipt (open choice 1). The contract names four exchanges and one refusal document: | exchange | request | response | |---|---|---| @@ -15,22 +15,23 @@ Sealed-ballot motions only (`gamecult.sealed_ballot.v0`). Standard motions have | cast (commit or reveal) | `gamecult.vote.cast.v0` | `gamecult.vote.receipt.v0` | | any refusal | | `gamecult.vote.refusal.v0` | -Every document is CultNet map-mode: all keys are written, nil where absent, and the schema is closed (`additionalProperties: false`). Instants are RFC 3339 UTC with a `Z` and up to seven fractional digits. Ids are lowercase `D`-format UUIDs. The bytes of every document are pinned by `messagePackHex` in the vectors (shortest integers, shortest strings, keys in the order the schema lists them); JSON with the same keys is the xenos-boundary form of the same documents (open choice 4: the wire format). +Every document is CultNet map-mode: all keys are written, nil where absent, and the schema is closed (`additionalProperties: false`). Instants are RFC 3339 UTC with a `Z` and up to seven fractional digits. Ids are lowercase `D`-format UUIDs. A motion states `highStakes`, a boolean fixed when the motion opens and never changed after (Bifrost PR #37, D2): it is the property that makes a commit demand a fresh sign-in (`reauthentication_required`). The bytes of every document are pinned by `messagePackHex` in the vectors (shortest integers, shortest strings, keys in the order the schema lists them); JSON with the same keys is the xenos-boundary form of the same documents (open choice 4: the wire format). ## Documents - **motion_list_request**: `{schemaVersion, phase}`; `phase` is `Commit`, `Reveal`, `Closed` or nil for all. -- **motion_list**: `{schemaVersion, motions: [{id, title, round, phase, commitClosesAtUtc, closesAtUtc}]}`. **Heimdall must add this endpoint**: Bifrost has no list route for sealed motions today, only fetch-by-id. No pagination in v0 (open choice 2). +- **motion_list**: `{schemaVersion, motions: [{id, title, round, phase, commitClosesAtUtc, closesAtUtc, highStakes}]}`. **Heimdall must add this endpoint**: Bifrost has no list route for sealed motions today, only fetch-by-id. No pagination in v0 (open choice 2). - **motion_read_request**: `{schemaVersion, motionId}`. -- **motion**: Bifrost's `SealedMotionState` with its names: `{schemaVersion, id, title, summary, round, priorMotionId, ballotSchema, phase, candidates (two or more), commitClosesAtUtc, closesAtUtc, yourVoterId, yourCommitment (a receipt or nil), announcement}`. It never carries a tally, a turnout, or another voter's commitment or ballot (a `tally` key is a negative vector). `yourVoterId` is the voter's `UserAccount.Id`, the `voterUserAccountId` of the commitment. +- **motion**: Bifrost's `SealedMotionState` with its names: `{schemaVersion, id, title, summary, round, priorMotionId, ballotSchema, phase, candidates (two or more), commitClosesAtUtc, closesAtUtc, highStakes, yourVoterId, yourCommitment (a receipt or nil), announcement}`. It never carries a tally, a turnout, or another voter's commitment or ballot (a `tally` key is a negative vector). `yourVoterId` is the voter's `UserAccount.Id`, the `voterUserAccountId` of the commitment. - **cast**: `{schemaVersion, motionId, verb, commitment, ballot}` with exactly one of two shapes: `verb: "commit"` with a 64-hex `commitment` and `ballot: nil`, or `verb: "reveal"` with `commitment: nil` and a `ballot`. One document for both verbs; today's two routes (`/commit`, `/reveal`) may stay, taking this document as the body, with a `verb` that must match the route (open choice 3). -- **ballot** (inside a reveal): `{schema: "gamecult.sealed_ballot.v0", motionId, round, candidate, reason, salt, persona}`. `candidate` and `reason` are single-line and **already trimmed** (no Unicode White_Space at either end, using the White_Space list of `write-receipt.md`); the contract refuses an untrimmed value rather than trimming it, so the bytes hashed are the bytes sent (Bifrost trims silently today: open choice 5). `reason` is 1 to 280 characters (UTF-16 code units, as Bifrost counts; the vectors keep the boundary cases to the BMP). `salt` is 32 to 128 lowercase hex. `persona` is `""` for a member and a printable-ASCII, space-free id of at most 64 characters for a Mind. +- **ballot** (inside a reveal): `{schema: "gamecult.sealed_ballot.v0", motionId, round, candidate, reason, salt, persona}`. `candidate` and `reason` are single-line and **already trimmed** (no Unicode White_Space at either end, using the White_Space list of `write-receipt.md`); the contract refuses an untrimmed value rather than trimming it, so the bytes hashed are the bytes sent. **This is a behaviour change Bifrost must make:** it trims `candidate` and `reason` silently on reveal today (facts file section 3c, `SealedBallotService.cs:154, 385-387`), so an untrimmed reveal is accepted and hashed trimmed; under this contract it is `invalid` (open choice 5). `reason` is 1 to 280 characters counted in UTF-16 code units, as Bifrost counts. A JSON Schema `maxLength` counts code points, so the schema alone accepts up to 280 astral characters; both suites add the rule (vectors `cast_reveal_reason_140_astral_280_units` valid, `ballot_reason_141_astral_282_units` refused). `salt` is 32 to 128 lowercase hex. `persona` is `""` for a member and a printable-ASCII, space-free id of at most 64 characters for a Mind. - **receipt**: Bifrost's `SealedBallotReceipt`: `{schemaVersion, motionId, voterUserAccountId, commitment, revision (>= 1), committedAtUtc, revealed}`. The receipt for a reveal has `revealed: true` and **echoes nothing of the ballot** (no candidate, reason or salt; negative vectors for each). - **refusal**: `{schemaVersion, code, error, retryAfterSeconds}`; the `code` set is closed: | code | HTTP | trigger | |---|---|---| | `unauthenticated` | 401 | no bearer, or one Heimdall does not know (the API must answer this, not redirect to a sign-in page) | +| `reauthentication_required` | 401 | a commit on a `highStakes` motion with no sign-in fresh enough (#37 D2: `prompt=login`, `max_age`); the voter signs in again at the top level and retries | | `forbidden` | 403 | authenticated but not an Active member, or a Mind key used on a route it may not call | | `not_found` | 404 | unknown motion id, a Standard motion id, or a reveal with no prior commit | | `wrong_phase` | 409 | commit outside Commit, reveal outside Reveal | @@ -38,10 +39,15 @@ Every document is CultNet map-mode: all keys are written, nil where absent, and | `invalid` | 400 | the body fails the schema or an in-document rule (untrimmed text, candidate not in the motion, `round` or `motionId` mismatch, `persona` wrong for the credential) | | `commitment_mismatch` | 400 | a reveal whose canonical bytes do not hash to the stored commitment | | `payload_too_large` | 413 | body over 4096 bytes | -| `rate_limited` | 429 | cast rate exceeded; `retryAfterSeconds >= 1`, mirrored in `Retry-After` | +| `rate_limited` | 429 | cast rate exceeded, per account; `retryAfterSeconds >= 1`, mirrored in `Retry-After` | +| `held` | 503 | the Ratatoskr voting brake is set (#37 D5): nothing is read or stored while it is | Each code has a vector (`refusals`), and every failure of the schema has a negative vector (`invalid`). +**Not on Bifrost's member API yet.** The **413 body cap of 4096 bytes** and the **429 per-account cast limit** are not on Bifrost's member sealed API today (facts file sections 3c and 5): both exist only on the Mind intake route (4096 bytes; 30 requests a minute per source address). #37 proposes 30 casts per 10 minutes per account for the member routes (its D6). `reauthentication_required` and `held` are #37 proposals (D2, D5) and exist nowhere in code. All four are spec text until Heimdall builds them. + +**Two refusals Bifrost must split first.** Bifrost returns a reveal whose bytes do not hash to the stored commitment as a plain `400` `Invalid` ("The ballot does not match the stored commitment."), the same status and error type as every other bad body, so `commitment_mismatch` and `invalid` are one today. It returns a contended reveal (a race with the close fence, `SealedBallotService.cs:186-189`) as `WrongPhase` `409`, the same type as a reveal outside the Reveal phase, so `contended` and `wrong_phase` are one today. A client cannot tell "send it again" from "too late", or "wrong ballot" from "malformed body", until Bifrost splits each pair. + ## Commitment `commitment = lowercase hex SHA-256 of the UTF-8 bytes of` @@ -60,7 +66,19 @@ A Persona votes only on sealed motions, through a Mind key (Bifrost binds an Ed2 ## Authentication -An opaque `Authorization: Bearer` header. The token route, claims, scopes and audience belong to Heimdall and Bifrost and are not designed here. A request without a usable bearer is answered `unauthenticated` as a typed refusal. +On the wire the credential is an opaque `Authorization: Bearer` header. The token route, claims, scopes and audience belong to Heimdall and Bifrost and are not designed here. A request without a usable bearer is answered `unauthenticated` as a typed refusal. + +**Auth per route, now and after #37.** Routes are Bifrost's; this contract fixes the documents, not the paths (open choice 3). + +| exchange | today (`/api/sealed-motions`, facts file 3c) | after #37 (`/api/votes`) | +|---|---|---| +| motion list | no route | bearer, scope `votes.read` | +| motion read | session cookie + Active member | bearer, scope `votes.read` | +| commit, reveal | session cookie + Active member | bearer, scope `votes.cast`, audience `bifrost-votes`; a high-stakes commit also needs a fresh sign-in | +| Mind cast | anonymous; the Ed25519 signature on a `MindEnvelope` is the credential | unchanged: Minds never use `/api/votes` (#37) | +| open a motion, Mind-key registry | session cookie + moderator | unchanged, cookie only | + +Today a member route has **no bearer scheme**: it accepts the host-only `__Host-bifrost_session` cookie and answers an unauthenticated call with a redirect to the sign-in page, not a 401. After #37 `/api/votes` is bearer-only and answers a typed 401. The wire header stays an opaque bearer either way; only Bifrost knows which of the two it is checking. ## PR #37 (Bifrost `docs/ratatoskr-voting-auth.md`, head `447a7cb`) @@ -72,9 +90,9 @@ Checked 2026-11-10 by an Eyes pass over the PR and the schema; conflicts for Hei 4. **Refusals.** The PR lists 401/403/404/409/429 with `WWW-Authenticate`; the contract adds 400 (`invalid`, `commitment_mismatch`) and 413, and a typed body. 5. **Minds.** The PR says Minds never use `/api/votes` and keeps the anonymous signed `/mind` route; the contract carries `persona` in the ballot and a `forbidden` case for a Mind key on a route it may not call. The PR's token is never a Persona's. 6. **Authority.** The contract's `unauthenticated` says a bearer "Heimdall does not know"; the PR makes Bifrost the OIDC issuer (a second client, scopes `votes.read` and `votes.cast`, audience `bifrost-votes`) and notes that Bifrost OIDC is not live (its D7). -7. **Rate limits.** The PR keys per account (30 casts per 10 minutes); the contract says per credential. +7. **Rate limits.** The PR keys per account (30 casts per 10 minutes); the contract now says per account too, and neither exists on the member API yet (above). 8. **Wire format.** The PR's errors are JSON; the contract's vectors are MessagePack. Nothing settles the media type. -9. **The PR's seven open decisions** (consent for `votes.cast`, re-authentication, no refresh token, grant revocation, a Ratatoskr brake, rate numbers, MAS rollout order) assume nothing the contract contradicts. D2 (re-authentication at cast) would need a refusal code if answered yes; `unauthenticated` is the nearest. +9. **The PR's seven open decisions** (consent for `votes.cast`, re-authentication, no refresh token, grant revocation, a Ratatoskr brake, rate numbers, MAS rollout order) assume nothing the contract contradicts. D2 (re-authentication at cast) is answered yes for sealed commit in this contract: `reauthentication_required`, with `highStakes` fixed at open. D5 (a brake) is `held`. ## Open choices for Heimdall diff --git a/contracts/cultnet/governance/vote-api.schema.json b/contracts/cultnet/governance/vote-api.schema.json index b025b59d..cef56a52 100644 --- a/contracts/cultnet/governance/vote-api.schema.json +++ b/contracts/cultnet/governance/vote-api.schema.json @@ -99,7 +99,8 @@ "round", "phase", "commitClosesAtUtc", - "closesAtUtc" + "closesAtUtc", + "highStakes" ], "properties": { "id": { @@ -125,6 +126,10 @@ }, "closesAtUtc": { "$ref": "#/$defs/instant" + }, + "highStakes": { + "type": "boolean", + "description": "Fixed when the motion opens and never changed after: a high-stakes sealed motion needs a fresh sign-in at commit (Bifrost PR #37, D2)." } } }, @@ -215,6 +220,7 @@ "candidates", "commitClosesAtUtc", "closesAtUtc", + "highStakes", "yourVoterId", "yourCommitment", "announcement" @@ -270,6 +276,10 @@ "closesAtUtc": { "$ref": "#/$defs/instant" }, + "highStakes": { + "type": "boolean", + "description": "Fixed when the motion opens and never changed after: a high-stakes sealed motion needs a fresh sign-in at commit (Bifrost PR #37, D2)." + }, "yourVoterId": { "$ref": "#/$defs/uuid" }, @@ -378,13 +388,15 @@ "enum": [ "unauthenticated", "forbidden", + "reauthentication_required", "not_found", "wrong_phase", "contended", "invalid", "commitment_mismatch", "payload_too_large", - "rate_limited" + "rate_limited", + "held" ] }, "error": {