Skip to content

feat(governance): vote spine end-to-end — propose, open, vote, close over HTTP - #66

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/solon-v1-vote-spine
Aug 7, 2026
Merged

feat(governance): vote spine end-to-end — propose, open, vote, close over HTTP#66
github-actions[bot] merged 1 commit into
mainfrom
feat/solon-v1-vote-spine

Conversation

@catomean

@catomean catomean commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

S3 of the Solon v1 plan (S0 #64, S1+S2 #65). The domain layer gets its public write surface, a reference agent signer, an operator bootstrap for the genesis roster, and a database-backed acceptance spec in CI.

Routes (thin Zod shells over lib/domain)

  • POST /api/proposals — file a signed proposal. The Bitcoin signature over proposalMessage() is the authorization; AGENT proposers additionally present Authorization: Bearer sk_solon_… (transport auth and vote signature deliberately separate).
  • POST /api/proposals/[id]/open — open the voting session. Permissionless by design: the proposal is already signed and public, opening only starts the clock, and it can happen exactly once.
  • GET /api/sessions/[id] — session, snapshotted rules, live tally.
  • POST /api/sessions/[id]/votes — cast a signed vote. Replaces /api/voting/[id]/cryptographic-vote (UI and integration page updated — one endpoint, not two).
  • POST /api/sessions/[id]/close — close and decide the outcome.

New domain code

  • createProposal() with content binding: contentHash = sha256(canonicalJson(proposedContent)). lib/domain/canonical.ts is the cross-repo contract OrangeCat will re-hash against — pinned by golden-hash unit tests so a drift breaks CI, not decision verification in prod.
  • closeRefusal() guard: a session cannot close while the voting window is open unless every eligible member has voted — otherwise anyone could stack a tally and slam the door.

Scripts

  • scripts/agent-vote.ts — the reference agent signer OC (Cat) and FC (Loki) will run: privkey from the agent's own env, signs and POSTs; the key never leaves the agent's box.
  • scripts/add-member.ts — documented operator bootstrap for the genesis roster (membership changes after genesis are HUMANS_ONLY votes), with MEMBER_ADDED audit events and one-time API-key minting.

CI: new integration job

Postgres 16 service container → prisma migrate deploy on a fresh database (proves the baseline + seed migrations replay) → the vote-spine spec: propose (agent transport auth enforced both ways) → open (rules snapshotted, 3 eligible) → three signed votes → early close refused → full participation closes early → APPROVED → policy v2 activated referencing the session, v1 superseded → every step present in the audit trail. Second spec: agents rejected from HUMANS_ONLY sessions, and excluded from the eligibility snapshot.

Note: the plan named a separate scripts/e2e-vote.ts; its coverage lives in the integration spec instead — one source of truth for the acceptance flow rather than a script duplicating it.

Verification

  • npm run verify green (29 unit tests; integration spec skips without INTEGRATION=1).
  • Integration spec run locally against dockerized postgres 16: 2/2 green, migrations replayed cleanly.
  • npm run build green.
  • add-member.ts smoke-tested: create + key mint + idempotent rerun.

🤖 Generated with Claude Code

…over HTTP

S3 of the Solon v1 plan: the domain layer gets its public write surface, a
reference agent signer, and a database-backed acceptance spec in CI.

Routes (thin Zod-validated shells over lib/domain):
- POST /api/proposals — file a signed proposal; the Bitcoin signature over
  proposalMessage() is the authorization, and AGENT proposers must also
  present their transport API key (Authorization: Bearer sk_solon_…)
- POST /api/proposals/[id]/open — open the voting session (permissionless by
  design: the proposal is already signed and public, opening only starts the
  clock, and it can happen exactly once)
- GET /api/sessions/[id] — session, snapshotted rules, live tally
- POST /api/sessions/[id]/votes — cast a signed vote (replaces
  /api/voting/[id]/cryptographic-vote; UI + integration page updated)
- POST /api/sessions/[id]/close — close and decide the outcome

New domain code:
- lib/domain/proposals.ts createProposal(): content binding via sha256 of
  canonical JSON (lib/domain/canonical.ts — the cross-repo contract OrangeCat
  will re-hash against, pinned by golden-hash tests)
- closeRefusal() guard: a session cannot be closed while the voting window is
  open unless every eligible member has voted — nobody slams the door on a
  tally they like

Scripts:
- scripts/agent-vote.ts — reference agent signer (privkey from the agent's own
  env, signs and POSTs; the key never leaves the agent's box)
- scripts/add-member.ts — documented operator bootstrap for the genesis
  roster, with MEMBER_ADDED audit events and one-time API-key minting

CI: new `integration` job — postgres service container, `prisma migrate
deploy` on a fresh database (proves baseline + seed replay), then the
vote-spine integration spec: propose (agent transport auth enforced) → open
(rules snapshotted) → three signed votes → early-close refused → full
participation closes → APPROVED → policy v2 activated referencing the
session, v1 superseded, every step in the audit trail; plus the humans-only
electorate gate. Verified locally against a dockerized postgres 16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions
github-actions Bot merged commit b6ea9e7 into main Aug 7, 2026
2 checks passed
@github-actions
github-actions Bot deleted the feat/solon-v1-vote-spine branch August 7, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant