Read-API proposals to governance service owners (HD-4)
Status: IMPLEMENTED 2026-08-06 (owner directive "Finish all / Open
gates next") — all eight endpoints below now exist in
services/service-*/main.py with the D6/D11 conventions (tenant REQUIRED +
resolved against the caller; bounded reads; explicit truncation; read-only;
no battery subsetting anywhere) and are covered by
tests/remediation/test_hd4_read_apis.py (8 tests). Envelope for listings:
{items, limit, truncated}.
Deployment reality — SUPERSEDED, re-measured 2026-08-30: all six services
now carry these endpoints in their serving revision. Each service's serving
revision was resolved to its image tag (the build commit) and that commit's
services/<svc>/main.py was inspected for the endpoint:
| Service | Serving revision | Endpoint | Present in serving image |
|---|---|---|---|
| decision-control-plane | 00039-x4k |
/api/v1/decisions, /api/v1/decisions/summary |
yes |
| approval-routing | 00021-992 |
/api/v1/history |
yes |
| audit-replay | 00031-wwb |
/api/v1/recent |
yes |
| validation-orchestrator | 00023-6qp |
/api/v1/passports |
yes |
| action-runner | 00041-4xv |
/api/v1/outcomes, /api/v1/actuators |
yes |
| policy-engine | 00032-gxl |
/api/v1/policies |
yes |
The six were redeployed 2026-08-22…08-30, well after the 2026-08-06 implementation, which is what closed the gap; the operator redeploy this note was waiting on has therefore already happened.
Measurement limit (do not overstate this): presence was verified in the
image's build commit, NOT exercised over the wire. These services are
--no-allow-unauthenticated and admit only ALLOWED_CALLER_SA, so a
workstation identity token returns an ingress 401 and cannot prove response
shape. What is proven: the routes are in the code that is serving. What is
NOT proven: live response bodies against real tenant data.
Consequence for the frontend program: Phase 1D's "strict zero mock imports" is no longer blocked upstream — the UI already references all eight endpoints. Remaining work on those surfaces is UI-side consumption, not a backend wait.
Original note, kept as the dated record (2026-08-06): the governance services
deploy ONLY via the operator-run ops/remediation/deploy_all.sh — no Deploy
Router workflow watches their paths — so these endpoints were implemented,
not serving until an operator redeployed the six services; until then the
UI's tri-state rendered the honest upstream state (404 on the new paths) and
kept its labeled illustrative fallbacks.
Implementation notes per the original constraints:
- audit-replay recent: audit-chain records deliberately carry no tenant
field (chain shape untouched) — tenant scoping is a JOIN (tenant's newest
decisions → their proposal-time audit ids → full trails), never a filter
that could leak refs.
- action-runner outcomes: outcome docs are tenant-stamped forward-only at
the execute path from 2026-08-06 (from the authorization's tenant);
pre-stamp rows are not returned — operator-timed backfill extends
coverage (GOVERNANCE 5.4 migration pattern).
- decisions status filter validates against the Eligibility enum (422 on
unknown) and filters only the RECORDED value — presentation only.
Original proposal text (2026-08-05) kept below for the record:
Why: Phase 1D live composition wired every read the services expose (approval pending queue, decision/passport lookup-by-id, audit verify/replay/ chain). The remaining Command Center surfaces need list/aggregate reads that do not exist. The UI will keep rendering labeled illustrative content for those sections until an owner accepts and ships the additions.
Common requirements for every proposed endpoint (matching the deployed
services' existing conventions):
- verify_caller + resolve_tenant; tenant-scoped, tenant REQUIRED (D6 class)
- Bounded: limit capped server-side, cursor/offset pagination, newest-first
- Read-only; no filter parameter may weaken a validation battery (D-class:
no caller-selected subsets)
- Standard /health /readyz untouched
Per-service proposals
| Service | Proposed endpoint | Consumer surface | Rationale |
|---|---|---|---|
| decision-control-plane | GET /api/v1/decisions?tenant_id=&status=&limit=&cursor= |
/command-center/decisions queue |
Only GET /decision/{id} exists — operators cannot see the queue without already knowing each id |
| decision-control-plane | GET /api/v1/decisions/summary?tenant_id= (counts by status/domain, DEL distribution buckets) |
Overview tiles | Honest live tiles need aggregates; today any "N pending decisions" tile would be fabrication |
| approval-routing | GET /api/v1/history?tenant_id=&limit=&cursor= (resolved approvals: approved/denied/expired w/ approver + resolved_at) |
Approvals "recently resolved" | Only pending is readable — resolved records are written but not listable |
| audit-replay | GET /api/v1/recent?tenant_id=&limit=&cursor= (recent audit records) |
Audit table (replaces the illustrative example traces) | Replay requires a known audit id; there is no way to discover recent ids |
| validation-orchestrator | GET /api/v1/passports?tenant_id=&limit=&cursor= |
Validation surfaces (2A) | Lookup-by-id only today |
| action-runner | GET /api/v1/outcomes?tenant_id=&limit=&cursor= + GET /api/v1/actuators (registry w/ stage + ceiling) |
Action center (authorization lifecycle, Stage-3/4 truth) | Outcome records and the actuator stage ledger are written but not listable; the Action center cannot render honest lifecycle state without them |
| policy-engine | GET /api/v1/policies (read-only active policy set + thresholds/version) |
Policy center (read-only) | Evaluation exists; the active policy inventory is not readable |
| canonical-ingestion | (none — events/point-in-time suffices for the evidence explorer) |
— | — |
Explicitly NOT proposed
- Any unscoped/all-tenant listing (reintroduces defect D6).
- Any write/mutation endpoint (the governed pathway already covers those).
- Any endpoint that returns validation subsets or lets a caller filter a battery (the ACT-991 lesson: subset-selectable validation is a scoring exploit).
Process
Backend scope belongs to the service owners. Acceptance path per the repo's
governance: owner picks up a row → implements in services/service-* with the
existing verify_caller/resolve_tenant/store conventions + contract tests →
deploys via ops/remediation → the UI's Phase 1D wiring consumes it through
the existing typed adapter (one function + one contract-table row per
endpoint). Until then the affected UI sections stay labeled illustrative.