KB-280B

09 — Directus / API Exposure Readiness (PG views → read-only API, no math)

5 min read Revision 1
registries-pivotdirectusapiexposureread-onlyno-mathdieu28gated-P22026-05-31

title: 09 — Directus / API Exposure Readiness date: 2026-05-31 gate: P2 (after P1 committed). Design-only; no Directus mutation this session.

09 — Directus / API Exposure Readiness

Principle (Đ28): the API/Directus is a shaper, not a calculator. It exposes committed PG views/functions verbatim. No per-count math, no gap arithmetic, no hardcoded arrays in the API layer — those are exactly the legacy violations (doc 11).

A. What gets exposed (after P1 commit)

contract field PG source shape
leaf list v_living_lists rows {code,name,entity_type,composition_level,list_count,count_source,pivot_code,pivot_backed}
integrity summary v_count_integrity (aggregate) {leaf_rows,sum_record,sum_actual,net_gap,drift_rows,unverified_rows,pivot_backed,pivot_missing,status}
drift detail v_count_drift rows {code,gap,drift_side,drift_classification,...}
drill tree v_registries_pivot_tree rows {node_code,parent_code,is_root,has_children,...}
node substrate fn_registries_pivot_node_substrate(code) 1 row {…,record_count,actual_count,pivot_backed,pivot_count}
any count pivot_count(code) / pivot_query(code) {code,name,source,value} — PIVOT_MISSING if no pivot

B. Endpoint candidates (/api/registries-pivot/* — note: /api/registries-pivot/health is 404 today)

  • GET /api/registries-pivot/listsv_living_lists
  • GET /api/registries-pivot/integrityv_count_integrity aggregate (+ ?detail=driftv_count_drift)
  • GET /api/registries-pivot/treev_registries_pivot_tree
  • GET /api/registries-pivot/node/:codefn_registries_pivot_node_substrate(:code)
  • GET /api/registries-pivot/count/:pivot_codepivot_count(:pivot_code) Each handler is a single SELECT * FROM <view/fn> (or Directus collection read) → JSON. Zero business logic. Contrast: the legacy health.get.ts computes gap/totalGap in the handler (doc 11).

C. Two exposure routes (choose at P2)

  1. Directus collections over the views — register v_count_integrity, v_living_lists, v_count_drift, v_registries_pivot_tree as read-only Directus collections (Directus can map to views). Pros: permissions/UI for free. The fn needs a thin endpoint.
  2. Nuxt server routes under /api/registries-pivot/* that SELECT from the views/fn. Pros: functions exposable; total control. Either way the shaper does no counting.

D. Permissions / read-only role

  • Reuse a read-only DB role (like context_pack_readonly) for these endpoints; no write grant.
  • Directus: public/role read on the four view-collections; no create/update/delete.
  • Functions: GRANT EXECUTE on fn_registries_pivot_node_substrate(text) and pivot_count(text) to the read role only.

E. Response shapes & failure states (explicit, not silent)

  • Missing pivot → field pivot_count: null, pivot_backed: false, badge PIVOT_MISSING.
  • Unmeasured leaf → count_integrity_status: "unverified", list_count may be null with count_source: "unmeasured".
  • Unknown node code → fn returns 0 rows → API 404 with {error:"unknown_node"} (never a guessed count).
  • Stale pivot → include refreshed_at; never recompute client-side.

F. Security risks

risk mitigation
write via API read-only role + read-only Directus permissions; no INSERT/UPDATE endpoints
count math creeping into handler code review + no-hardcode CI (doc 12) greps for reduce/Math.abs/gap in handlers
info exposure (985k birth rows) endpoints return counts/aggregates, not row dumps; node endpoint returns substrate metadata only
injection on :code parameterized; validate against `^(CAT
over-fetch / DoS pagination on list endpoints; views are cheap (read meta_catalog/pivot_results, not live 985k scans)

G. Gate

P2, after P1 commit. Design-only here; no Directus mutation performed this session. The contract field→source map above is the acceptance artifact for P2.

Back to Knowledge Hub knowledge/dev/reports/architecture/registries-pivot-ratification-commit-ready-gateway-2026-05-31/09-directus-api-exposure-readiness.md