grithdocs

Digest

Read the quarantine queue and resolve items over REST, including the nonce-authenticated review callback.

The quarantine digest over HTTP. The dashboard's review buttons are these routes; so is grith digest.

MethodPathAuth
GET/api/digestDashboard token
POST/api/digest/{id}/approvex-grith-csrf
POST/api/digest/{id}/denyx-grith-csrf
POST/api/digest/{id}/learnx-grith-csrf
POST/api/digest/{id}/escalatex-grith-csrf
POST/api/digest/{id}/unlock-egressx-grith-csrf
POST/api/digest/{id}/deny-terminatex-grith-csrf
POST/api/digest/{id}/allow-alwaysx-grith-csrf
POST/api/digest/clear-allx-grith-csrf
POST/api/digest/{id}/webhook-reviewSingle-use nonce

Full forensics on one item is a daemon route, GET /api/ipc/digest/items/{id} - see Daemon IPC.

GET /api/digest

Everything still open - pending and escalated - highest composite_score first. limit defaults to 20 and is capped at 100; offset defaults to 0. Informational rows are in here too: when a session resolves its own queue decisions - logged and allowed, or auto-denied because there is no terminal to prompt - the record is still filed as pending with informational_only: true. Filter on that field if you only want items a human can still act on.

GET /api/digest?limit=1
{
"items": [ { "id": "7d1f...", "created_at": "2026-08-24T09:14:23Z", "session_id": "abc1...",
  "tool_call_type": "FileRead", "arguments_summary": "/home/u/.ssh/config",
  "decision_reason": "Score 4.2 in escalation zone", "composite_score": 4.2,
  "severity": "medium", "filter_breakdown": [], "task_context": null,
  "plugin_id": "supervisor:claude-code", "status": "pending",
  "reviewed_at": null, "review_action": null, "reviewer_notes": null,
  "informational_only": false, "escalated_at": null, "escalated_by": null } ],
"total": 1,
"pending_count": 1,
"escalated_count": 0,
"limit": 1,
"offset": 0
}

total is the actionable count, which is pending_count plus escalated_count.

Resolving an item

Every decision route takes an optional body of {"notes": "..."} and answers with a small status object rather than 204. The status it echoes names the route, not always the state it stored - learn answers "learned" while recording the item as approved:

POST /api/digest/{id}/approve
{ "status": "approved", "id": "7d1f..." }

Each route records a different review_action on the item, and the supervisor that is waiting on the call reads it back:

RouteStatus recordedreview_action
approveapprovedapprove
denydenieddeny
learnapprovedlearn
escalateescalated - the item stays in the queue-
unlock-egressapproved, and the session is released from containmentunlock_egress
deny-terminatedenied, and the supervised tool is stoppeddeny_and_terminate
allow-alwaysapprovedallow_always

unlock-egress adds containment_released to its response. An unknown id is 404 NOT_FOUND. escalate, unlock-egress, deny-terminate and allow-always return 409 with a route-specific code when the item is in the wrong state - already reviewed, for instance.

POST /api/digest/clear-all marks every pending and escalated item expired in one statement and returns {"status": "cleared", "cleared": <n>}. The rows stay in the database with review_action: "clear"; they simply leave the queue. Nothing is approved and nothing runs.

POST /api/digest/{id}/webhook-review

The one write with no CSRF layer, because it carries its own proof. The body must include the single-use nonce that was issued with the review link:

POST /api/digest/{id}/webhook-review
{ "action": "approve", "nonce": "<single-use nonce>", "reviewer": "alice", "notes": "expected" }

action is approve, deny or escalate; anything else is 400 INVALID_ACTION. A nonce that is wrong or already spent is 403; an item that cannot take the action is 409. Success is {"status": "reviewed", "action": "..."}, or {"status": "no_action"} when the callback resolved to nothing.

See also

Last updated: 2026-08-24Edit this page on GitHub →