KB-4EF8

10 — Directus/API Exposure Readiness (views → read-only API, existing route inventory)

3 min read Revision 1
registries-pivotdirectusapiread-onlyno-mathroute-inventorygated-P22026-05-31

title: 10 — Directus/API Exposure Readiness (Branch H) date: 2026-05-31 gate: P2 / macro M8 (after M2 commit). Design-only; no Directus mutation this session.

10 — Directus/API Exposure Readiness (Branch H)

Principle (Đ28): API/Directus is a shaper, not a calculator — exposes committed views/fns verbatim.

Existing registry API routes (verified in nuxt-repo/web/server/api/registry/)

health.get.ts (gap-math — RETIRE) · counts.get.ts (disagreeing total — REPLACE) · raw-counts.get.ts (reduce — RETIRE) · matrix.get.ts · species-matrix.get.ts · species-summary.get.ts · composition.get.ts · unmanaged.get.ts · system-issues*.get.ts · refresh-counts.post.ts · pivot-query.get.ts (already pivot-based — the GOOD pattern to follow). Plus server/api/registries/system-issues/*.

Exposure map (after M2 commit) — field → PG source, zero math

endpoint source shape
GET /api/registries-pivot/lists v_living_lists rows {code,name,list_count,count_source,pivot_code,pivot_backed}
GET /api/registries-pivot/integrity v_count_integrity (aggregate) {leaf_rows,net_gap,drift_rows,unverified,pivot_backed,pivot_missing,status}
GET …/integrity?detail=drift v_count_drift rows {code,gap,drift_side,drift_classification}
GET /api/registries-pivot/tree v_registries_pivot_tree rows {node_code,parent_code,is_root,has_children}
GET /api/registries-pivot/node/:code fn_registries_pivot_node_substrate(:code) 1 row substrate + pivot_count
GET /api/registries-pivot/count/:pivot pivot_count(:pivot) {code,name,source,value} or PIVOT_MISSING
Each handler = single SELECT * FROM <view/fn> → JSON. Model on pivot-query.get.ts, not health.get.ts.

Permissions / read-only role

Reuse a read-only DB role (like context_pack_readonly); no write grant. Directus: read-only on the four view-collections; GRANT EXECUTE on fn_registries_pivot_node_substrate(text) + pivot_count(text) to the read role only.

Failure states (explicit)

missing pivot → pivot_count:null, pivot_backed:false, badge:PIVOT_MISSING; unmeasured → status:"unverified"; unknown node → fn 0 rows → 404 {error:"unknown_node"}; stale → include refreshed_at, never recompute client-side.

Security

read-only role + read-only Directus perms (no write endpoints) · counts/aggregates only (no 985k row dumps) · :code validated against ^(CAT|PIV|MTX)- format (not a hardcoded list) · pagination on list endpoints · views are cheap (read meta_catalog/pivot_results, not live 985k scans).

Optional read-only rehearsal (gated P2)

After M2, a read-only role can SELECT the rolled-back-then-committed views; until M2 there is nothing to expose. No Directus mutation performed this session.

Back to Knowledge Hub knowledge/dev/reports/architecture/registries-pivot-ratify-foundation-commit-preview-campaign-2026-05-31/10-directus-api-exposure-readiness.md