Give player-built finance infra (banks, funds) a way to consume new ledger transactions event-driven, via the always-on REST API tailing the shared ledger. Substrate only — not network gameplay. Implements the external-consumer half of PAR-75.
(b) Cursor pull feed — GET /api/v1/accounts/{id}/transactions?since=<txn_id>&limit=N returns settled txns after the cursor, forward-ordered, capped (default 100 / max 1000), with nextCursor + hasMore.
(a) Webhooks — self-service POST/GET/PATCH/DELETE /api/v1/webhooks scoped to the API key; a background dispatcher tails the ledger into a durable outbox and POSTs HMAC-signed events with retries.
economy-schema V13): webhook_subscription, webhook_delivery (UNIQUE (subscription_id, txn_id)), webhook_cursor.settlement_time <= NOW() - INTERVAL :lag SECOND) to avoid AUTO_INCREMENT visibility skips; outbox dedups on (subscription_id, txn_id).X-Treasury-Signature: sha256=…) + delivery-id; rate-limited endpoints; cap subs/key; auto-disable after K failures.ActivityDispatcher (monotonic poll), tesks GithubSignature (HMAC), treasury TaxWebhookServiceImpl (HttpClient), rest-api VerifiedToken/LettuceBucketProvider.economy-schema — V13 migration (additive).treasury-rest-api — cursor endpoint, webhook mgmt API, dispatcher, config, HMAC util, Redis lock.k8s-gitops — wire RATE_LIMIT_REDIS_* + WEBHOOK_* onto prod overlays (DC + SC). No Redis provisioning (already exists).V13 migrate → rest-api develop→uat → prod env wiring → deploy DC + SC. Verify pull (forward/cursor/settled), webhook single-delivery across 2 replicas, failover, retry/backoff, SSRF rejection, global rate limiting.
Plan: parallel-whistling-swing.md. Aligns with the network hands-off philosophy (substrate, not gameplay).
Post-release defect found + fixed: tenant-shared dispatcher lock
The webhook dispatcher had stalled in prod. DC's cursor was frozen at txn
1,776,403since ~12:30 (≈13k txns behind) while the pods were healthy.Root cause:
WebhookRedisLockused the default fixed keytreasury:webhook:dispatcher, and DC and SC rest-api both point at the sameredis.production. So all four pods (DC×2 + SC×2) contended for one global lease — only one tenant's dispatcher could run, and whichever tenant lost the race had its dispatcher starved. An SC pod held the lock, so DC was dead.Fix (gitops, config-only — no rebuild): set
TREASURY_WEBHOOK_LOCK_KEYper overlay (…:dispatcher:democracycraft/…:statecraft); Spring relaxed-binds it totreasury.webhook.lock-key. Each tenant now holds its own lease. Pushed to gitopsmain, rolled. Verified: DC's cursor immediately caught up from 1,776,403 → current (now 1 behind latest = the settlement-lag buffer, updating every few seconds).Still outstanding for full rollout: the feature has 0 subscriptions / 0 deliveries — never exercised end-to-end in prod. Next: create a test subscription (via
/me/webhooksor the REST API) pointing at a controllable endpoint and confirm a signed delivery + the delivery-health view.