The dashboard
What the gate decided and the contract it decided with, read in a browser, with the escalation queue a person works.
The dashboard
The dashboard reads what the gate wrote. It does not decide anything, and the one
write a person can make through it is a decision on an escalation, through the same
ledger call coco approve uses, so the two surfaces cannot drift.
The page is a static shell and everything on it arrives over a JSON API. A filter changes a query rather than reloading a document, and every receipt goes over the wire whole, so the browser can render the findings, the contracts evaluated and the state each verdict was judged against.
The views
| View | What it shows |
|---|---|
| Live | Verdicts as the gate writes them, allows included, with the counts above them |
| Escalations | The queue waiting on a person, with approve and deny |
| Contracts | Every contract in force, as a list and as its own YAML |
| Receipts | The full table, filtered by verdict, searched by reason, exported as CSV |
| Integration | How to point a harness at this gate |
| Settings | Mode, mandate, contracts loaded, and whether the chain verifies |
Across the top of Live sit the verdict counts, the block rate, the escalations waiting and whether the receipt chain verifies. Clicking any row opens the receipt whole, with the contract that fired, every check that ran, the state the gate read and the contract source scrolled to the failing line.
That last part is the point of having a page at all. A compliance officer reading a block wants the sentence, the rule and the line of policy behind it on one screen.
Working an escalation
An escalation shows what the agent wanted to do, the contract that paused it and the reason in a sentence. Whoever holds the dashboard key clicks approve or deny and writes one line saying why, and that line goes on the record next to the receipt.
The agent waits. It cannot approve itself, and silence is not a yes.
A receipt that already carries a decision keeps it. A second decision on the same receipt is refused rather than overwriting the first.
The API
Everything the page shows is available directly.
| Route | What it returns |
|---|---|
GET /health | Open, for an uptime check |
GET /api/metrics | Counts, block rate, escalations pending, chain status, mode, mandate, contracts loaded |
GET /api/receipts | The receipts, newest first |
GET /api/receipts/<id> | One receipt whole, including findings and the state it read |
GET /api/escalations | The pending queue |
GET /api/rules | The compiled contract in force |
GET /api/packs | The contracts as a list |
GET /api/packs/<id>/yaml | One contract's source |
GET /receipts.csv | The table as CSV |
POST /api/escalations/<id>/approve | Record a decision |
POST /api/escalations/<id>/deny | The same |
Auth
HTTP Basic when COCO_DASH_KEY is set, falling back to COCO_API_KEY. Any username
works and the key is the password. /health stays open, because an uptime monitor
holds no secrets.
Without a key set the dashboard binds to localhost only. Setting a key opens it to
the network, and COCO_DASH_HOST overrides either way. That default is deliberate,
because a governance dashboard reachable with no password is worse than no dashboard.
The JSON posts carry no CSRF token. That is acceptable behind Basic auth on a design partner deployment and it is not acceptable beyond one, and it is named here rather than discovered later.
Running it
The same image carries the dashboard as a second role, as its own container sharing the ledger volume the gate writes. See Self-hosted.
python3 dashboard.py # read whatever COCO_HOME already holds
python3 dashboard.py --demo # the x402 pilot demo, with its own homeThe demo needs a YAML parser to compile its contracts, and falls back to Node when PyYAML is missing, so it runs from an environment with either.
The pilot demo
--demo plays a payment platform. It holds a wallet ledger, a merchant registry,
price history and an entitlement list as fixtures, builds the payload a platform
would send, and runs the real gate as a subprocess for every scenario.
Nine scenarios, and each one writes a real receipt.
| Scenario | Verdict | What it shows |
|---|---|---|
| Top up API credits | ALLOW | Registered payee, inside every limit, offer fresh |
| Retry-loop top-ups | BLOCK | Each payment is fine and the run is what the rule reads |
| Duplicate subscription | BLOCK | The entitlement ledger remembers what the context window forgot |
| Abnormal renewal price | ESCALATE | Under the budget cap, twice the history, so a person decides |
| Lookalike payee | BLOCK | An address one character off the registered one |
| Expired offer | BLOCK | An approval cannot resume a stale signature |
| Monthly budget breach | BLOCK | The budget is the wallet's ceiling, not the agent's opinion |
| Missing ledger state | BLOCK | No snapshot, no evaluation, no signature |
| Gate unreachable | BLOCK | The SDK's own answer when the gate does not respond |
The verdicts on screen are the gate's own, produced by the same file a hook install runs. Nothing about a decision is reimplemented for the demo, which is why it is worth showing to someone who does not believe the claim.
The lookalike scenario is the one people remember. The registered address and the poisoned one differ by their last character, and to a model the two strings look equally plausible. The registry does not read plausibility.
Next
- Reading the ledger for the same rows on the command line
- Escalations for the queue and what a decision records
- Architecture for where the dashboard sits