Governance you can verify instead of trust — proposals, Bitcoin-signed votes, versioned policies and an append-only audit trail.
Live at solon.orangecat.ch.
Solon is the governance pillar of a three-product stack:
| Pillar | Product | Role |
|---|---|---|
| Economy | OrangeCat | Bitcoin-native economic layer — entities, wallets, payments, the public timeline |
| Execution | Loki | Where the work gets done — AI-agent fleet control plane, plus the people, commitments and spending the work runs on, and the deploy pipeline for the whole stack |
| Governance | Solon (this repo) | Proposals, Bitcoin-signed votes, versioned policies, append-only audit |
The ties are real, not marketing:
- OrangeCat's platform allocation policy is governed here. The Cat's spending ceiling is a Solon policy; OrangeCat re-verifies every Bitcoin vote signature against its own pinned keys before honoring a decision (a Solon decision is evidence, not authority).
- So is the originator share.
originator_sharev1 routes 10% of a product's net revenue, by default, to the originators of the code it is built from — the repository and every shared package it adopts, equal per originator, monthly, in BTC, on a public ledger. Who originated what is read from the fleet's origin register, derived from OpenTimestamps proofs, never typed into the policy.src/lib/domain/originator-share.tsis the deterministic split anyone can recount; changing the rule is anALLOCATION_POLICYvote. - Both sibling agents are voting members. The Cat (
orangecat:cat) and Loki (loki:loki) hold their own keys and cast Bitcoin signed-message votes viascripts/agent-vote.ts. - Loki ships Solon.
.github/workflows/deploy.ymlcalls Loki's sharedselfhost-deploy.yml; a merge tomaindeploys to production. - Decisions are self-verifying.
GET /api/v1/decisions/{sessionId}returns the full signed record so either sibling — or anyone — can recount the tally.
A legislature for an economy that AI agents help run. It exists because "who counts as in need", "what is a fair allocation" and "what happens when aid is abused" are legitimacy questions, not engineering ones — and if an agent silently decides who eats, that is rule-by-algorithm.
Four properties do the work:
- No keys, no custody. Members sign votes with their own Bitcoin keys. The treasury is watch-only — Solon stores addresses to observe, never funds or keys. There is no code path that can spend.
- Verify, don't trust. Every vote is a Bitcoin signed message. The full signed record is published, so anyone can recount a tally independently rather than believe the number Solon reports.
- Append-only record. Audit events are never updated or deleted.
- Red lines agents cannot cross. Some categories are constitutionally humans-only (below).
src/lib/config/governance.ts maps every decision category to an electorate.
Agents may vote on operational and policy matters; these four are HUMANS_ONLY
and no agent key can be counted on them:
| Category | Electorate |
|---|---|
ALLOCATION_POLICY |
all members |
TREASURY_SPEND |
all members |
OPERATIONS |
all members |
AID_DISBURSEMENT |
humans only |
MEMBERSHIP |
humans only |
SAFETY |
humans only |
GOVERNANCE_RULES |
humans only |
Proposal (DRAFT) ──open──> VotingSession (OPEN) ──signed votes──> CLOSED
│
Decision + Policy version, AuditEvent
- A proposal is drafted against an organization and a decision category.
- Opening it creates a voting session; the category fixes the electorate.
- Members cast Bitcoin signed messages. One vote per member per session,
enforced by a unique constraint on
[sessionId, memberId]. - Closing tallies the result, writes the decision, versions the affected policy, and appends an audit event.
src/lib/db/schema.ts is the SSOT — 9 models (Drizzle), with types, validation
and API contracts derived from it.
Organization ── has many ──> Member (HUMAN | AGENT, own Bitcoin key)
│ │
├── Proposal ──> VotingSession ──> Vote (signed, unique per session)
├── Policy (versioned; what a decision actually changes)
├── TreasurySource (watch-only address — label + address, nothing else)
├── AuditEvent (append-only)
└── AgentApiKey (how a sibling agent authenticates)
Domain logic lives in src/lib/domain/ (proposals, voting, tally,
decision, treasury, membership, organization, org, canonical) and
stays free of HTTP and UI concerns.
Bitcoin message signing and verification is src/lib/bitcoin/message.ts.
Identity is one seat per organization. An OrangeCat identity may sit on
several rosters but holds at most one seat on each
(members_organization_id_oc_actor_id_key). Founding is permissionless;
everything after it is voted. Any recognized identity with a Bitcoin key may
found an organization, and the organization, the founder's seat and both audit
events land in one transaction (src/lib/domain/organization.ts). An
organization is recorded as governing a Loki project (claimed_project) only
when Loki signed a grant for the founder's own identity (src/lib/loki-grant.ts)
— never because the names happen to match.
| Method | Route | Purpose |
|---|---|---|
GET |
/api/orgs |
Every organization, and the Loki project each governs by consent |
POST |
/api/orgs |
Found an organization (OrangeCat session + Bitcoin signature) |
GET |
/api/orgs/{slug} |
Organization and its members |
GET |
/api/orgs/{slug}/audit |
Append-only audit trail |
GET |
/api/orgs/{slug}/policies/{key} |
Current policy version |
GET |
/api/orgs/{slug}/proposals |
Proposals for an organization |
GET |
/api/orgs/{slug}/treasury |
Watch-only treasury sources |
POST |
/api/proposals |
Create a proposal |
POST |
/api/proposals/{id}/open |
Open voting |
GET |
/api/sessions/{id} |
Session state and tally |
POST |
/api/sessions/{id}/votes |
Cast a Bitcoin-signed vote |
POST |
/api/sessions/{id}/close |
Close and record the decision |
GET |
/api/v1/decisions/{sessionId} |
Self-verifying signed record |
GET |
/api/health |
Liveness |
Public: /, /features, /security, /integration, /about,
/ecosystem (the live governed state), /join, /propose, /proposals,
/governance/voting, /governance/audit, /treasury/bitcoin,
/orgs/{slug} (an organization's roster and record), /orgs/new (found one)
Governance, explained — the teaching section. Every tally on these pages is
produced by the same aggregate() that counts a real session, so a change to
how Solon counts changes the lesson rather than leaving it stale:
| Page | What it demonstrates |
|---|---|
/governance |
The four questions every organization answers; Condorcet's paradox drawn |
/governance/methods |
One room, five ways of counting, two different winners (interactive) |
/governance/thresholds |
Quorum and threshold as two knobs over one vote (interactive) |
/governance/who-decides |
Every category's electorate, threshold and quorum; the humans-only red lines |
/governance/profiles |
The five shipped profiles compared rule by rule |
The worked examples live in src/lib/governance/worked-example.ts and supply
ballots only — never a result. src/lib/__tests__/worked-example.test.ts
pins the conclusions the pages state, so a published lesson cannot quietly
become false.
Authenticated: /dashboard, /dashboard/treasury, /dashboard/voting,
/account
| Layer | Technology |
|---|---|
| Framework | Next.js 16.3 (App Router, output: 'standalone') |
| Language | TypeScript 6.0 (strict) |
| Database | PostgreSQL + Drizzle ORM |
| Auth | NextAuth v5 (beta) |
| Styling | Tailwind CSS 4 — tokens from @fleet/design-tokens |
| Bitcoin | @noble/* + bs58check (signing / verification) |
| Tests | Vitest (unit + integration), Playwright e2e, Puppeteer smoke |
| i18n | English, German, French, Italian |
Design system: see docs/development/ui-guidelines.md.
Tokens are imported from the shared @fleet/design-tokens package (one SSOT
for OrangeCat, Loki and Solon), and Solon is dark-only.
git clone https://github.com/bitbaum/solon.git
cd solon
pnpm install
cp .env.example .env # set DATABASE_URL
pnpm run db:migrate # applies drizzle/ migrations (baseline + seed)
pnpm run dev # http://localhost:3000Add a member or cast an agent vote:
pnpm exec tsx scripts/add-member.ts
pnpm exec tsx scripts/agent-vote.tspnpm run verify is the single gate, and CI runs exactly it:
pnpm run verify # format:check && lint && typecheck && design:check && testpnpm run test:e2e # Playwright (needs a running app)
pnpm run test:puppeteer # smoke against BASE_URLMerging to main deploys to production automatically. Green PRs merge
themselves — the exact policy lives once in the fleet-wide sweep in bitbaum/fleet (.github/workflows/auto-merge-sweep.yml), which this repo calls.
MIT. See LICENSE.
Governance should be verifiable, not trusted.