Skip to content

feat(legion): live governance view at /legions - #890

Merged
biwasxyz merged 13 commits into
mainfrom
feat/legion-governance-ui
Jul 23, 2026
Merged

feat(legion): live governance view at /legions#890
biwasxyz merged 13 commits into
mainfrom
feat/legion-governance-ui

Conversation

@biwasxyz

Copy link
Copy Markdown
Contributor

Adds aibtc.news/legions — a live view of the Legion, the contributor-funded pool where agents vote each week on which reporting gets paid.

Contracts are STGX5YP51NKM69ZMP6DVB6GAJAANCG5WB3718KD9.news-gov / news-treasury on Stacks testnet, deployed at block 4049423.

How it updates

Two paths, deliberately split:

chainhook ──▶ LegionDO (SQLite)  ──▶ purge /api/legion/state
              contribute, propose, vote, veto, conclude

GET /api/legion/state
   ├─ read DO          indexed event feed
   ├─ read get-phase   authoritative lifecycle position
   ├─ read get-params  live governance thresholds
   └─ tip height       countdown only, never a phase decision

Events are pushed; the phase is read. That split exists because three lifecycle transitions emit nothing at all — a week moves voting → veto → concludable → lapsed purely because blocks passed, with no transaction to hook. A webhook-only page would sit on "voting open" long after voting closed. There is no cron: the phase is computed per request from the contract's own get-phase.

Thresholds come from get-params rather than being mirrored in constants, so a governance redeploy cannot silently mispredict.

Storage

LegionDO is separate from NewsDO on purpose:

  • its write path is a public third-party webhook; a malformed payload that wedges the handler shouldn't wedge the primary datastore
  • it has no keep-alive alarm, so it hibernates between requests instead of inheriting the news singleton's always-on duration and manual-wrangler deploy cycle

A DO rather than KV because the requirement is immediate visibility, and KV is eventually consistent with propagation up to a minute. Cost difference measured at ~$0.01–0.04/month — not a factor either way.

Events carry contract_id, so the next governance redeploy lands without truncating the feed.

UI

Every state shows the same three things together — where we are, what block it is, what block the next change happens at — so nothing has to be inferred.

Two treatments carry the most weight:

  • concludable — the payout can't grow by waiting (the draw is snapshotted at propose), but it must be concluded by block N or the week closes unpaid. Live countdown.
  • lapsed — the inverse. Concluding now records not-concluded and pays nobody; the action shifts from releasing the draw to releasing the proposer's bond and reopening the week.

Failure states name their cause rather than saying "failed", because three of the four reopen the week and a bare "failed" reads as terminal.

Security

  • Webhook fails closed: no CHAINHOOK_CONSUMER_SECRET, no deliveries accepted
  • Constant-time secret compare; rejections log header names, never values
  • Idempotent by (txid, event_index); 500 on persist failure so Chainhooks retries rather than dropping
  • rollback handled, so a reorged vote doesn't linger
  • Failed transactions are skipped — an aborted conclude must not render as a conclusion

Tests

23 new, 499 total. Fixtures are real bytes captured from the deployed contracts, so a codec drift stops matching the chain rather than an assumption. Covers the branch precedence that is load-bearing: lapsed short-circuits a week that would otherwise pass, a lost vote outranks a pool shortfall, and pool-short compares the real disbursement rather than the draw (floor truncation puts actual spend up to totalSignals-1 sats lower).

Not included

The chainhook itself isn't registered yet — that needs the endpoint live first. Chainhooks 2.0 has no per-hook authorization_header, so the receiver accepts the account-wide consumer secret from any of four plausible carriers plus a query token, and logs header names on rejection so the real one can be pinned from the first delivery and the list narrowed to one.

Until it's registered the page renders its empty state; the pool is currently at 0 with no brief proposed.

biwasxyz and others added 13 commits July 23, 2026 21:55
Read-only calls return a hex-serialised Clarity value and nothing else — no
repr, no JSON. Rather than pull in the full Stacks SDK for a handful of call
sites, decode the SIP-005 wire format directly.

Principals are deliberately left as raw hex: rendering one needs c32check
encoding, and the only principal the UI wants already arrives decoded on the
chainhook payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contract ids, lifecycle phases, failure reasons and predicted outcomes for
the news-gov deployment at STGX5YP…, block 4049423.

Governance thresholds are a fallback shape only — the contract exposes
get-params, so live values are read from chain and never mirrored here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers the half of the pipeline chainhooks cannot: a week moves voting ->
veto -> concludable -> lapsed purely because blocks passed, with no
transaction and therefore no event to push.

Phase and parameters both come from the contract (get-phase, get-params) so
this module cannot drift from what the contract believes. predictOutcome
mirrors conclude's branch order exactly — lapsed, veto, quorum, threshold,
pool-short, passed — using truncating division so a brief on a boundary
predicts the way it will actually conclude.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Events carry contract_id so a governance redeploy lands without truncating
the feed: superseded deployments keep their history under their own id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Separate from NewsDO deliberately. Its write path is a public third-party
webhook, and it carries no keep-alive alarm so it hibernates between
requests rather than inheriting the news singleton's always-on duration and
manual-redeploy cycle.

A Durable Object rather than KV because the requirement is immediate
visibility — KV is eventually consistent with propagation up to a minute.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CHAINHOOK_CONSUMER_SECRET authenticates inbound webhooks; absent, the
endpoint refuses every delivery. HIRO_API_KEY is optional and only lifts the
anonymous read rate limit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /api/legion/chainhook ingests print events, idempotent by
(txid, event_index) so redelivery cannot duplicate, with rollback handling
so a reorged vote does not linger. Returns 500 on persist failure so
Chainhooks retries rather than dropping.

GET /api/legion/state merges indexed events with live chain reads and
derives the countdown. Short TTL plus a purge on write, so a new event shows
immediately while the TTL only bounds how stale a height-derived phase gets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrations are append-only, so LegionDO ships as its own tag rather than
editing v1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every state shows the same three things together — where we are, what block
it is, and what block the next change happens at — so a reader never has to
infer what comes next.

Two treatments matter most. concludable says the payout cannot grow by
waiting but must be concluded by block N or the week closes unpaid. lapsed
is its inverse: concluding now pays nobody, and the action shifts from
releasing the draw to releasing the bond and reopening the week.

Failure states name their cause, because three of the four reopen the week
and a bare 'failed' reads as terminal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixtures are real bytes captured from the deployed contracts, so a codec
drift stops matching the chain rather than stopping matching an assumption.

Covers the branch precedence that is load-bearing: lapsed short-circuits a
week that would otherwise pass, a lost vote outranks a pool shortfall, and
pool-short compares the real disbursement rather than the draw, since floor
truncation puts the actual spend up to totalSignals-1 sats lower.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
agent-news 8d0f32a Jul 23 2026, 04:12 PM

@github-actions

Copy link
Copy Markdown
Contributor

Preview deployed: https://agent-news-staging.hosting-962.workers.dev

This preview uses sample data — beats, signals, and streaks are seeded automatically.

@biwasxyz
biwasxyz merged commit 46bd699 into main Jul 23, 2026
6 of 7 checks passed
@biwasxyz
biwasxyz deleted the feat/legion-governance-ui branch July 23, 2026 16:16
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