feat: JSON /api/v1 admin API on a shared approve/deny core #35

Merged
pti merged 5 commits from impl/admin-json-api into main 2026-08-26 23:31:49 +02:00
Owner

Versioned JSON admin surface next to the HTML review UI, per design/webui/admin-json-api.md. internal/adminops extracts the approve/deny core from its two existing copies and both consumers are refactored onto it with byte-identical output. /api/v1 on the admin listener: info, requests list/get, approve, deny, calls, read-only rules. No SSE - deferred with requestlog.Watch. JSON errors everywhere including a catch-all for unknown /api/ paths. rules omits env/env_files deliberately. AuthMiddleware answers /api/ with 401 JSON + WWW-Authenticate Bearer, query token never authenticates the API. requestlog.ErrNotFound sentinel drives 404s. Adds wraptool requests list --json. Reviewed twice, merge-ready, all gates green. Generated with Claude Code.

Versioned JSON admin surface next to the HTML review UI, per design/webui/admin-json-api.md. internal/adminops extracts the approve/deny core from its two existing copies and both consumers are refactored onto it with byte-identical output. /api/v1 on the admin listener: info, requests list/get, approve, deny, calls, read-only rules. No SSE - deferred with requestlog.Watch. JSON errors everywhere including a catch-all for unknown /api/ paths. rules omits env/env_files deliberately. AuthMiddleware answers /api/ with 401 JSON + WWW-Authenticate Bearer, query token never authenticates the API. requestlog.ErrNotFound sentinel drives 404s. Adds wraptool requests list --json. Reviewed twice, merge-ready, all gates green. Generated with Claude Code.
The approve flow (store.Get -> UpdateStatus(approved) -> optional
config.ApplyRequest -> outcome classification, with the "approved but
apply failed" partial-failure case) was duplicated in
internal/webui.handleApprove and cmd/requests.go's approve command.
internal/adminops is the single copy; both consumers are wired onto
it in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GjxKV7SYeStUbfFBFe34E
Behavior is unchanged (existing webui tests pin the flash messages);
this just replaces the inlined store.Get/UpdateStatus/config.ApplyRequest
sequence with a single adminops.Approve/adminops.Deny call each.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GjxKV7SYeStUbfFBFe34E
Adds a read/act JSON surface on the same listener/mux/token as the HTML
Web UI: GET info/requests/requests/{id}/calls/rules, POST approve/deny.
Handlers are encode/decode veneers over the store and adminops — no new
logic. AuthMiddleware now recognizes /api/ paths: Bearer/cookie only,
401 JSON with WWW-Authenticate instead of the HTML login form, and
never sets a session cookie for a scripted client. No SSE/events
endpoint — deferred per design/webui/admin-json-api.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GjxKV7SYeStUbfFBFe34E
Mirrors the webui refactor: approve/deny now call adminops.Approve/
adminops.Deny instead of duplicating the store.Get/UpdateStatus/
config.ApplyRequest sequence, with the same printed output. `requests
list --json` marshals the same []requestlog.CapabilityRequest the
/api/v1/requests endpoint returns, for scripting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GjxKV7SYeStUbfFBFe34E
fix(webui,cmd): correct Outcome on apply failure; typed not-found error
Some checks failed
Lint / lint (pull_request) Failing after 37m26s
c63d14ddfc
adminops.Approve left result.Outcome at its zero value (OutcomeNewRule)
on the ApplyErr branch, so both the JSON API and CLI reported success
("new_rule"/"Added new allow rule...") when config.ApplyRequest had
failed and nothing was written. Set Outcome = OutcomeNotApplied there,
and reorder the two call sites' ApplyErr/OutcomeNotApplied checks so
the apply-failure message still wins over the apply=false one now that
both share the same Outcome value.

Also: requestlog.ErrNotFound replaces requestErrorStatus's "not found"
substring match; an /api/ catch-all returns the JSON error shape for
unknown paths/methods instead of ServeMux's default text/plain;
dropped the dead ApproveResult.Request field; corrected the Snippet
doc comment; added auth test pins (session cookie ok, wrong Bearer is
JSON 401) and a comment on /api/v1/rules omitting Env/EnvFiles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GjxKV7SYeStUbfFBFe34E
pti merged commit 303894ff7c into main 2026-08-26 23:31:49 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pti/wraptool!35
No description provided.