feat(transparency): public read API, audit page, self-verifying decision documents - #67
Merged
Merged
Conversation
…ying decision documents S4+S5 of the Solon v1 plan: the integration surface consumers re-verify rather than trust. Public reads (auth-free — transparency is the product): - GET /api/orgs/[slug] — organization + public member roster (addresses, public keys, weights; there are no secrets to hide because Solon never holds any) - GET /api/orgs/[slug]/proposals — proposals with session state - GET /api/orgs/[slug]/policies/[key] — version history; v1 labeled bootstrap, later versions carry their approving session id - GET /api/orgs/[slug]/audit — append-only audit stream - GET /api/orgs/[slug]/treasury — live on-chain balances (moved from /api/bitcoin/wallet/[orgId]; slug-only, one canonical path) Keystone: GET /api/v1/decisions/[sessionId] — the self-verifying decision document: proposal with contentHash and the exact proposer message + signature, rules snapshotted at open, every vote's signed message + signature + public key, tally, outcome. The integration spec now does what OrangeCat will do: re-verifies every signature, re-hashes the content, and recomputes the tally from the votes — never trusting the server's arithmetic. Webhook rail: decision.finalized emitted on session close (HMAC X-Solon-Signature: sha256=<hex>, idempotent event_id). Doorbell, not courier — consumers fetch and re-verify the document. Env-gated (SOLON_WEBHOOK_URL/SECRET) and inert until configured; a DB-backed multi-endpoint rail is deferred until a second consumer exists. Site: /governance/audit renders the audit stream itself — no summaries, no derived metrics, nothing untraceable to a row. Nav + integration page updated to list exactly the live endpoints. Chore: .next excluded from tsconfig — stale generated route types broke typecheck after every route move/deletion (second occurrence; class ended). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
S4+S5 of the Solon v1 plan (S0 #64, S1+S2 #65, S3 #66) — shipped together because both are the public transparency surface. This is the integration surface OrangeCat's decision edge (OC2) consumes.
Public reads (auth-free)
GET /api/orgs/[slug]— organization + public member roster (addresses, public keys, weights — no secrets exist here because Solon never holds any).GET /api/orgs/[slug]/proposals— proposals with session state.GET /api/orgs/[slug]/policies/[key]— version history; v1 labeled bootstrap, later versions carry their approving session.GET /api/orgs/[slug]/audit— the append-only audit stream (limit param, capped 500).GET /api/orgs/[slug]/treasury— live on-chain balances, moved from/api/bitcoin/wallet/[orgId]to one canonical slug path.Keystone:
GET /api/v1/decisions/[sessionId]The self-verifying decision document: proposal (contentHash + reconstructed proposer message + signature), rules snapshotted at open, every vote's exact signed message + signature + voter public key, tally, outcome. Evidence, not authority. 409 while the session is still open; a decision only exists once finalized.
The integration spec now performs the consumer's full verification loop: re-verifies the proposer signature and all three vote signatures, re-hashes
proposedContentagainstcontentHash, checks each signed message binds this session and voter, and recomputes the tally from the votes — never trusting the server's arithmetic.Webhook rail
decision.finalizedemitted on session close — HMACX-Solon-Signature: sha256=<hex>, idempotentevent_id, 3 attempts with backoff. Doorbell, not courier: the payload names the decision; the consumer fetches the document and re-verifies before acting. Env-gated (SOLON_WEBHOOK_URL/SOLON_WEBHOOK_SECRET), inert until configured. Deviation from plan noted: a DB-backed multi-endpoint rail (OC'sdeliveryServiceshape) is deferred until a second consumer exists — one env-configured consumer is the v1 reality, and the consumer's reconciliation poll heals missed deliveries.Site
/governance/audit— renders the audit stream itself: no summaries, no derived metrics, nothing untraceable to a row. Added to nav.Chore
.nextexcluded from tsconfig — stale generated route types broke typecheck after every route move (second occurrence; class ended per never-twice).Verification
npm run verifygreen (29 unit tests).npm run buildgreen.🤖 Generated with Claude Code