06 — Directus / API Exposure Readiness (Branch E)
title: 06 — Directus / API Exposure Readiness (Branch E) date: 2026-05-31 status: contract pack + static mock; NO Directus mutation
06 — Directus / API Exposure Readiness (Branch E)
Prepare a read API for Registries-Pivot with no Nuxt/JS business logic and no count math in the API (Đ28). The six foundation views are NOT committed (verified 0 present), so shapes come from the rehearsed SQL contract (Macro 1 docs 03/04).
The GOOD pattern (already in production — follow it)
server/api/registry/pivot-query.get.ts: reads pivot_definitions + pivot_results via the
Directus items API with a service token (§0-AV: reads via Directus API, never PG direct); no count math, no VIEW_MAP hardcode. Every new endpoint mirrors this: fetch
rows from a committed view/pivot, pass them through, compute nothing.
Proposed read endpoints (8)
| endpoint | backing | response shape | rule |
|---|---|---|---|
/api/registries-pivot/grand-total |
PIV-500 (view-backed) via pivot_results | {total} |
total is a pivot SUM, never JS reduce |
/api/registries-pivot/integrity |
v_count_integrity |
[{code,counted,actual_count,pivot_backed,pivot_count,count_integrity_status,drift_classification}] |
status from PG, not JS |
/api/registries-pivot/drift |
v_count_drift |
[{code,gap,drift_side,drift_classification,source_model}] |
classification from PG |
/api/registries-pivot/tree |
v_registries_pivot_tree |
[{code,parent_code,is_root,has_children}] |
hierarchy from PG parent_code |
/api/registries-pivot/living-lists |
v_living_lists |
[{code,name,pivot_code|PIVOT_MISSING}] |
honest PIVOT_MISSING |
/api/registries-pivot/substrate?code= |
fn_registries_pivot_node_substrate |
{registry_collection,source_location,record_count,actual_count,pivot_backed,pivot_count} |
leaf panel |
/api/registries-pivot/labels |
PIV-311 (native) | [{label_code,count}] |
grouped-count pivot |
/api/registries-pivot/pins?scope= |
registry_pin / PIV-321 |
[{object_ref,object_kind,scope,priority,reason}] |
needs registry_pin (RG6) |
Exposure mechanism (two options)
- Directus collections (preferred): register the six views as Directus read collections
(
directus_collections+directus_fields), grant a read-only role/policy. Nuxt then reads them exactly likepivot-query.get.tsreadspivot_definitions. Requires Directus mutation → gated (RG7); not done this macro. - Nuxt server route over Directus (interim): a
*.get.tsthat calls the Directus items API for the view. No business logic; pass-through only.
Permissions / read roles
- A dedicated read policy (e.g.
registries_pivot_read) on the six views + the two pivots. - Service token used server-side only (as today); never expose PG creds to the client.
- No write permission on any of these endpoints (read API only).
Response / error states (every endpoint)
200with rows; empty list is valid (not an error).count_integrity_statussurfaced honestly (FAILED/ok/unverified).pivot_count: null⇒ renderPIVOT_MISSING, never 0-as-truth.500only on infra/token failure (mirrorpivot-query.get.tscreateError).- A leaf with
source_model='B'returns a file path substrate — client renders a file reference, not a table grid.
No-count-in-API rule (hard)
No endpoint may reduce, Math.abs, sum, or classify. Totals come from PIV-500; orphan from
PIV-301; drift/phantom-candidate from v_count_drift/PIV-302/303; labels from PIV-311. This
directly retires health.get.ts Σ|gap| and raw-counts.get.ts Σrecord (doc 09).
Static mock (safe, this macro)
data.json shipped with the v2 preview (doc 07) IS the response contract — generated from
the live read snapshot — so the preview and the future endpoints share one shape. No Directus
was mutated.
Verdict
API exposure pack COMPLETE. 8 endpoints specified to the GOOD pattern, permissions and error states defined, no-count-in-API enforced, static mock shipped. Directus collection registration deferred to RG7 (needs the six views committed first + Directus write approval).