# admin.donto.org — Observability Audit

admin.donto.org is a genuinely high-quality, schema-introspection-driven observability console — 8 pages, ~13 API routes, 27 live SQL "lens" panels and a WebGL Belief Nebula, all driven off `pg_catalog` with **zero hardcoded table lists**, a real verified read-only Postgres posture (`donto_ro`), and disciplined big-table query guards (reltuples-not-scan, bounded CTEs, matviews). It nails the "packageable for any donto instance / every table & process introspectable" half of the vision. **Honest verdict: we are realizing roughly 55–60% of the full observability vision.** The substrate-*state* layer is excellent; the forward-looking half is essentially unbuilt — there is no *global* contradiction/argument/identity/discovery explorer (all the differentiator lenses live only inside the per-job page), no "measurement-as-steering-wheel" surface at all, and no real Lens-Engine discovery surface. Three headline gaps dominate: (1) **the console is fully PUBLIC with no auth** over a substrate containing CARE-governed native-title data and PII (P0); (2) **the Contexts page serves a 6-day-stale matview** reporting 23,406 contexts when the substrate has 64,055 (a 63% undercount feeding wrong numbers to operators — P0); and (3) the differentiator analytics (`donto_argument`, `donto_evidence_link`, paraconsistency density) have no substrate-wide dashboard.

## What's working

The good parts are concrete and load-bearing, not stubs:

- **Schema-introspection core is real.** `lib/introspect.ts` discovers all 159 live relations (124 tables / 8 matviews / 27 views) from `pg_catalog` with no allowlist; `listRelations()`/`relationExists()` build the SQL-injection allow-set live, and every dynamic identifier goes through `ident()` double-quote escaping *and* allow-set validation. Live-probed: `bogus_table` and `donto_statement; DROP` both return a clean 404, and `donto_statement?view=count` returns the reltuples estimate (39.5M) in ~66ms with no scan.
- **Read-only posture is verified at the DB, not just claimed.** `donto_ro` is `rolsuper=f`, `default_transaction_read_only=on`, has `statement_timeout`+`idle_in_transaction` baked into the role, zero non-SELECT grants, and `CREATE TABLE` is rejected with "cannot execute … in a read-only transaction." The pool (`lib/db.ts`) is server-only and `redactDsn()` strips the password before any render.
- **Big-table safety is principled and data-adaptive.** All large counts use `pg_class.reltuples` (`estRows`, `cheapCount`), never blind `count(*)`; `SMALL_THRESHOLD=50k` (derived per-relation from reltuples) decides whether to ORDER BY / exact-count / compute distributions, so it adapts to a fresh empty donto vs this 39.5M one. Job panels `LIMIT 3000`, all use bounded CTEs (`statement_id IN`, `context = ANY`, `object_iri IN`).
- **Differentiator data is live, not mocked.** The health view returns 39.5M believed statements, 994K predicates, 64K contexts, ~2.48M evidence links, 6.36M contested subject+predicate pairs, 7.66M contradiction pressure, 2,433 argument edges, 124 identity hypotheses — and Overview/Fabric render all of these as tiles.
- **The per-job detail page is the strongest realization of the vision.** `/jobs/[id]` renders a WebGL Belief Nebula (entity⊕predicate PCA, maturity=size, evidence=core, contradictions=multiple star systems, valid_time=comet-tails, tx_time=global clock) plus 27 live SQL lenses that exercise exactly donto's differentiators: contradictions-held, predicate-alignment (closure folding), embedding-neighbors, bitemporal belief, identity hypotheses, cross-source-lens, argument-graph, corroboration, two-hop, aligned-claims, version-history. The `audit.test.ts` rubric encodes a real channel→field mapping (every visual channel must cite a real donto field).
- **The alignment daemon is genuinely observable and rock-solid.** `donto-align-daemon.service` + `donto-embed-coordinator.service` both active; `/processes` shows `donto_align_daemon_status` (state=sleeping, 550 total_ticks, last_tick ~30s ago, no last_error), 15 recent ticks, backlogs, GLM rate-cap state, acceptance rates, closure rebuilds, plus alignment runs (189) and extraction runs (93). *(Caveat: this health is **self-reported** by the daemon's own tick table — exactly the "looked-launched-but-stalled" trap; an independent liveness probe, e.g. tick-row recency vs wall-clock, would harden it.)*
- **Distributed embedding + BEAM are surfaced.** `/embed` monitors the embed queue per target + contributor throughput (m1/m5/m15/m60) + active worker count + last-seen liveness; `/jobs` tracks the BEAM extract queue with status breakdown and per-chunk claim counts.
- **API layer is uniform and won't drift.** Every route is `force-dynamic`/`revalidate=0`, wrapped in `guard()`/`ok()`/`fail()` with a consistent `{ok,data,at}` envelope and `cache-control:no-store`; JSON routes reuse the same fetchers the RSC pages use (e.g. `/api/overview` reuses `getHealth`/`getDaemon`).
- **Graceful degradation everywhere.** `to_regclass` guards every optional-table read, so a missing relation renders an "—" tile instead of crashing; HTTP service probes use `AbortController` timeouts → a down upstream degrades one tile, not the page.
- **No-brittle-logic rule honored.** `statusTone()` is heuristic regex over free text, predicate families come from the live closure + embedding fabric (not a synonym map), `RECENCY_PREFERENCE` is a ranked fallback — no hardcoded per-table assumptions.

## What needs work (prioritized, merged & deduped across streams)

| Pri | Issue | Evidence | Fix |
|---|---|---|---|
| **P0** | **Console is fully PUBLIC, no auth** — entire substrate world-readable incl. CARE-governed native-title data, genealogy PII, source bodies, raw LLM transcripts | `/etc/caddy/Caddyfile` admin block is just `tls internal; encode; reverse_proxy localhost:3103`. Live-probed: `curl -A curl/8 https://admin.donto.org/api/health` → 200, and `/api/table/donto_statement?view=rows&limit=1` returns real statement rows with no token. Read-only stops corruption, NOT exfiltration. (Compounded by `/schema` + `lib/schema-dump.ts`, a full unauthenticated schema/catalog export surface.) | Add `basicauth`/forward_auth / Cloudflare Access / IP allowlist to the Caddy admin block, or Next.js middleware on a shared secret. At minimum exclude CARE-restricted contexts from row/source/transcript surfaces until access control exists. |
| **P0** | **Contexts page serves 6-day-stale matview** — shows 23,406 contexts vs 64,055 live (63% undercount) | Verified live: `donto_mv_context_stats` `max(refreshed_at)=2026-06-03 14:44:11`, `count=23406`; health view `distinct_contexts=64055`. `app/contexts/page.tsx` (via `getTopContexts`) reads only the matview with **no staleness warning**. Directly violates the "verify it actually works / don't trust a self-reported number" operating rule. | Add `REFRESH MATERIALIZED VIEW CONCURRENTLY donto_mv_context_stats` to the alignment daemon's periodic-rebuild tick (it already rebuilds closure) or a small cron; AND surface `refreshed_at` age as a visible "stale N days" badge (the fetcher `getTopContexts` *already reads* `refreshed_at` — only the page render is missing, so this is a few lines). Note the denominators: 23,406 (stale matview) vs 64,065 *registered* contexts (`donto_context`) — of which ~50,397 actually carry live statements; the 23,406-vs-64,065 gap is the apples-to-apples staleness measure. |
| **P1** | **`SET LOCAL statement_timeout` called outside a transaction** — floods Postgres logs and raises the cap 8× | `lib/db.ts:119` runs `SET LOCAL statement_timeout=120000` per query; node-pg auto-commits each query as its own txn → "SET LOCAL can only be used in transaction blocks" warnings (30+/sec at peak). Also *raises* the cap from the role's safe 15s to 2 min (×10 pool), so a runaway lens pins a connection for 2 min instead of aborting at 15s. `introspect.ts` comments still cite "15s" — files disagree. | Decide one cap: drop `SET LOCAL` and rely on pool-level `statement_timeout` (already 120s at `db.ts:66`) + role default; or wrap in `BEGIN…COMMIT`; or scope 2-min only to known-heavy panel/nebula routes. Reconcile the stale comments. |
| **P1** | **One job page fires ~29 concurrent heavy queries against a max:10 pool — the "lazy" panels aren't lazy** | `app/jobs/[id]/page.tsx:161-163` maps ALL 27 panels to `<LazyPanel>` + `<BeliefNebula>` + `<JobCharts>`. `components/lazy-panel.tsx:30,38-41` default `open=true` and `useEffect(()=>load(),[])` fires on mount → 27 panel fetches + nebula + charts ≈ 29 requests at once. Contradictions lens alone measures 3.6s. The burst saturates the 10-conn pool and can starve every other admin page (and the daemon shares donto-pg). | Default `open=false`, load on first expand; or render first ~3 lenses eagerly + rest on demand; and/or a small client-side concurrency limiter (N panels at once). |
| **P1** | **Identity fabric metrics underestimate 1.7–4.3×** (reltuples on small tables) | `/api/fabric` uses `estRows()`: `donto_identity_edge` 53 reported vs **124 actual** (verified), `donto_identity_proposal` 48 vs 85, `donto_identity_hypothesis` 3 vs 13, `donto_identity_membership` 72 vs 123. Root cause: `lib/health.ts getFabricCoverage()` ~lines 282-289. | Replace `estRows()` with real `count(*)` for `donto_identity_*` (all <2K rows, no perf impact). |
| **P1** | **Evidence-link count uses stale reltuples** | `/api/fabric` reports 2,251,264 (estimate) vs 2,484,472 ground truth (9.4% under). The health view already has the correct count; fabric ignores it. | Use `donto_v_substrate_health.evidence_links` (or `count(*)`) in fabric; decide one policy (estimate vs real) for consistency. |
| **P1** | **No global argument/contradiction explorer** (`donto_argument` 2,433 rows; verified) | Only queried per-document in the "contradictions held" lens. `v_arguments` view exists in schema but is **never invoked anywhere**. Health view shows 6.36M contested pairs + 7.66M contradiction pressure substrate-wide — operator cannot drill into any of it. | New `/arguments` page: count + trend by relation-type (probe the real column — it is NOT `edge_type`), top contested (subject,predicate) pairs by argument volume, strength distribution, review-state breakdown, evidence-anchor coverage. |
| **P1** | **No evidence-link analytics page** (`donto_evidence_link` ~2.48M) | Visible only as a per-job lens. No coverage ratio (anchor coverage is 6.29% substrate-wide), no confidence distribution, no target-type (document/revision/span) breakdown, no per-context gaps. | New `/evidence` page guarded by reltuples + pre-aggregated matview pattern; coverage ratio, confidence histogram, target-type breakdown, contexts/predicates <50% covered. |
| **P1** | **No "measurement-as-steering-wheel" surface at all** | `DONTO-ABUNDANCE.md §5` makes this the *scarce resource* (EIG-ordered what-to-generate-next queue, info-gain/Bayesian-surprise, novelty/diversity, collapse-delta, downstream task-lift). `grep` found zero references to EIG/info-gain/collapse-delta/novelty/diversity/task-lift anywhere in `app/`, `lib/`, or routes. | New Steering/Abundance page: claims-emitted-over-time + valid-fact% (from `beam.ts` data already present), embedding-distance diversity panel, contradiction-pressure-over-time, a collapse-delta tile once the Milestone-2 experiment exists. Start with what's already derivable. |
| **P1** | **No query-time-alignment / Lens-Engine discovery surface; `donto_match_aligned` is absent** | Verified: `to_regclass('public.donto_match_aligned')` = **false** (does not exist), despite CLAUDE.md §0.5 describing it as the query-time predicate-folding table. The cross-source lens only counts shared entities per other-context — no open-discovery (fan-out from A) or closed-discovery (B-paths between A and C). | Surface closure-folding live (type a predicate → aligned/folded forms + confidence, using `donto_predicate_closure`, which IS populated); minimal discovery surface (entity → fabric neighbors → shared-predicate bridges). Reconcile the `donto_match_aligned` reference — build it or fix CLAUDE.md. |
| **P2** | **Extract & embed queues effectively hidden in ground-truth terms** (`donto_extract_queue` 360,899; `donto_embed_queue` 200,008 — both verified) | Two streams disagree: a `/api/embed` and `/api/beam` route DO exist and respond (35ms/151ms) and `/embed` page renders the embed queue; but the ground-truth stream found queue *status* not faithfully exposed and BEAM's 343K-pending backlog not prominent on a global surface. Net: queues are partially surfaced but the operator can't see "is the firehose keeping up." | Ensure `/embed` and `/jobs` show pending/leased/done/failed *trends*, not just point values; add a backlog-over-time view. |
| **P2** | **Empty panels for some jobs despite extracted statements** | Job `df7a0c3a-…` returns 0 rows for evidence-anchors/contradictions/cross-source despite 187 statements; job `0adc46f5-…` returns 107/60 for the same panels. Suggests evidence-link population gaps during ingest. | Add a per-job evidence-coverage diagnostic panel; verify `donto_evidence_link` population + indexing during ingest. |
| **P2** | **Heavy analytical endpoints exceed client timeouts** | `/api/jobs/{id}/charts` ~17.7s (12 distributions w/ PCA), `/api/jobs/{id}/nebula` ~12.6s (102KB), `/jobs` listing 9.6–20s. With a 10s client timeout these fail; server cap is 120s. | Result caching (30–60s TTL) on charts/nebula; lazy-load / paginate the `/jobs` listing; route-specific refresh intervals; skip refresh for in-flight slow requests. |
| **P2** | **`/api/jobs` full `count(*)` on `donto_document` + double `agentContextCounts` GROUP BY** = 7.5s | `lib/jobs.ts:152` full table scan when no filter; `agentContextCounts` (62-116) runs 2 sequential GROUP BYs over 40M rows (bounded ~50 contexts). | reltuples estimate when unfiltered; merge the 2 GROUP BYs into one UNION; cache the adapter list; consider `donto_statement(context,tx_time)` index. |
| **P2** | **ILIKE substring search on `donto_document` (1M+ rows) has no index** | `lib/jobs.ts:136` `ILIKE` with `%` wildcards, no index — acceptable now, degrades with growth. | GIN `pg_trgm` index on `lower(iri)||' '||lower(coalesce(label,''))`; reltuples fallback for count when no query. |
| **P2** | **Raw Postgres errors leaked with wrong HTTP status** | `GET /api/jobs/not-a-uuid/panel/contradictions` → 500 `{"error":"invalid input syntax for type uuid: \"not-a-uuid\""}`. `guard()` (`api.ts:27-33`) and table route return `(e as Error).message` verbatim; no UUID shape validation. | Validate job id is a UUID (404 if not, 400 for malformed); `fail()` returns a generic message and logs detail server-side. |
| **P2** | **Daemon observability is tabular only — no time-series** | `app/processes/page.tsx` renders `donto_align_daemon_tick` (712 rows) as a 15-row table + point-value backlog tiles; no throughput/backlog sparkline → operator can't *see* if the daemon keeps up (the exact "looked-launched-but-stalled" failure). | Add a sparkline of per-tick predicates_embedded/entities_embedded/accepted + backlog over the last N ticks (data already in the tick table), reusing `job-charts.tsx`. |
| **P2** | **No bitemporal time-travel / "as-of" viewer** | Bitemporality (a headline invariant) appears only as per-statement columns in the per-job bitemporal lens + the nebula's tx_time clock. No global/context "what did this believe as of date X." | Add an "as-of" control to Contexts/Tables drill-down: `WHERE tx_time @> :asof` (tx_time is a tstzrange) — makes retract-not-delete visibly demonstrable. |
| **P2** | **Paraconsistency density table completely dark** | `donto_paraconsistency_density` exists in the 151 relations but is never queried — zero operator visibility into paraconsistent belief load, donto's core feature. | One-shot aggregate (density by predicate/context, global avg) on `/fabric` or `/arguments`; expose "total paraconsistency pressure" tile on Overview. |
| **P2** | **No global statement / predicate analytics** | `donto_statement` (39.5M) observable only per-job; no polarity/E-level/maturity/modality distribution. No local predicate frequency/minting-rate/closure-efficiency (Fabric shows top predicates from dontosrv's upstream endpoint, not local). | Statement-statistics tile or `/statements` page (cheap GROUP BY on small indexed cols via the health view); `/predicates` page (frequency, minted-vs-canonical, closure accuracy, minting rate). |
| **P2** | **Memory chunk embeddings not surfaced** | `donto_x_memory_chunk_embedding` has 59,705 embeddings, absent from Fabric — incomplete embedding-fabric picture (predicates + entities + memory). | Add to `FabricCoverage` type and fetch in `getFabricCoverage()`. |
| **P2** | **The no-gimmick audit test can't run** | `components/belief-nebula/audit.test.ts:18` imports `vitest`, but `package.json` scripts are only `[dev,build,start,lint,typecheck]` and no vitest in devDeps. The test that's supposed to enforce "every channel cites a real field" is dead — a decorative-only flourish would NOT fail CI. | Add vitest + a `test` script to CI (or move to the monorepo runner). Until then the audit guarantee is documentation, not enforcement. |
| **P2** | **Statement-timeout errors every ~8 min — NOT the admin console** | Postgres logs: 29 `canceling statement due to statement timeout` in 48h, recurring ~8min, from `SELECT count(*) FROM donto_x_memory_record r JOIN donto_statement s … WHERE upper(s.tx_time) IS NULL AND s.predicate <> ALL($1)`. `grep` found zero matches in the admin codebase — this is upstream (donto-api / memory / daemon), a full join over 40M rows. | Not an admin bug. Upstream should optimize/cache the join. Admin could optionally surface a timeout warning badge for visibility. |

## Coverage: every table + every process — analyzable or not? (the vision's core test)

This is the vision's literal test ("EVERY table + process + everything about donto COMPLETELY analyzable"). Two measurements:

**Tables — schema-introspection coverage is 100% by design; analytics coverage is partial.**
- **Browsable: 159/159 relations (100%).** Every table/matview/view (124 tables / 8 matviews / 27 views) is discoverable and row-/schema-browsable via `/tables` and `/api/table/{relation}` with no allowlist. This is the strong part — a fresh empty donto would render identically.
- **First-class *analytic* surface (a purpose-built dashboard beyond generic browse): roughly 30–40 of 159.** The differentiator tables that are *only* generic-browse or only per-job, with no substrate-level dashboard: `donto_argument` (2,433; per-job only, `v_arguments` never called), `donto_evidence_link` (2.48M; per-job only), `donto_paraconsistency_density` (never queried), `donto_statement` (39.5M; per-job drill-down only, no global aggregates), `donto_identity_*` (counts only, and those counts are 1.7–4.3× wrong via reltuples), `donto_predicate`/`donto_predicate_closure` (closure relation-type breakdown is shown on Fabric; local frequency/minting is not), `donto_x_memory_chunk_embedding` (absent), `v_derivations` (listed in schema, never called), `donto_match_aligned` (referenced in CLAUDE.md but does not exist on this instance).

**Processes — well covered.**
- **With a dashboard: ~6–7 of 7.** Alignment daemon (✓, live tick table + status + backlogs + GLM cap), distributed embedding workers (✓, `/embed`), BEAM extraction (✓, `/jobs` + extract queue), donto-api extraction jobs (✓, `/processes`), memory memorize/recall/ingest (✓, `donto_x_memory_job_log` on `/processes`), 3 upstream service health probes (✓, dontosrv/donto-api/donto-memory). The one gap is *not a missing process* but missing time-series for the daemon (point values, no throughput-over-time chart).

**Net:** infrastructure/process observability is essentially complete; the gap is **differentiator-analytics at substrate scale** — contradiction pressure, evidence quality, identity quality, and paraconsistency are either per-document-only or completely dark.

## Performance & reliability

The console is well-architected for the 40M-row substrate, and the measured numbers bear that out. Fast paths are genuinely fast; the slow paths are slow for understood reasons.

**Fast / instant (pure pg_catalog or matview, no data scan):**
- `/api/table/donto_statement?view=count` ~66–135ms (reltuples, no scan) · `/api/schema` 196ms · `/api/contexts` 41–49ms (pre-aggregated `donto_mv_context_stats`) · `/api/embed` 35ms · `/api/beam` 120–151ms (small queue reads, not the statement table).

**Medium (multiple bounded queries / upstream HTTP):**
- `/api/overview` ~3.0–4.4s (4 concurrent queries, cold) · `/api/fabric` 3.4–3.9s (7 reltuples + 1 GROUP BY) · `/api/processes` 2.3–2.7s (HTTP upstream probes).

**Heavy (by design — graph traversal / analytics):**
- Job panels: contradictions 0.5–3.6s, entity-reach 533ms, outgoing-graph 665ms, evidence-anchors 678ms, embedding-neighbors 0.37s, aligned-claims 0.34s, two-hop 0.30s; nebula 0.85s; charts 1.3s. `/api/jobs` (limit 50–100) **7.5s** (full `count(*)` on `donto_document` + double `agentContextCounts` GROUP BY). The 40M-row traps that are correctly *avoided*: blind `count(*)` (always reltuples), unbounded scans (panels `LIMIT 3000` + bounded CTEs), and an `IS NULL`-matching closure. The traps that remain: the `/api/jobs` unfiltered count, the unindexed ILIKE document search (growth risk), and the contradictions/entity-reach panels' unbounded final JOIN over `donto_statement` (pessimistic plan, fine today at ~500ms but fragile for high-abundance entities).

**Reliability findings:**
- The recurring ~8-min statement timeouts (29 in 48h) are **upstream, not the admin console** — verified by `grep`: the offending `donto_x_memory_record JOIN donto_statement` query is nowhere in the admin codebase. The console's own 120s cap lets its queries complete; no admin-originating timeouts were observed.
- The one *admin-side* reliability smell is the `SET LOCAL` log flood (P1) and the eager-"lazy" 26-query burst against a max:10 pool (P1) — the latter is a real availability bug where one popular job page can starve the whole console.
- Service: `donto-admin-web.service` active ~2 days, RSS ~75.8M, stable. TLS valid through Aug 2026, zero SSL/protocol errors across all probes.

## The vision gap — full console vs today

The vision (project_donto_observability + DONTO-ABUNDANCE.md + the Lens Engine docs) asks for *everything about donto completely analyzable*, the differentiator machinery *visible and exercised*, and **measurement as the steering wheel**. Here is the honest delta.

**What the FULL console looks like:** substrate-STATE observability (done) **plus** a set of *global* differentiator explorers and a steering layer:

- **Alignment-daemon dashboard with time-series** — TODAY: live status + a 15-row tick table + point-value backlogs (✓ rock-solid daemon, ✓ its own section). MISSING: throughput/backlog *over time* (data is already in the 712-row tick table), so the operator can SEE whether it's keeping up. **~70% there.**
- **Contradiction / argument explorer** — TODAY: per-document "contradictions held" lens only; `v_arguments` never called. MISSING: a global `/arguments` page over the 6.36M contested pairs / 7.66M pressure / 2,433 edges — top contested (subject,predicate) pairs, strength distribution, review-state. **~20% there.**
- **Identity browser** — TODAY: counts on Fabric (and those counts are wrong: 53 vs 124 actual). MISSING: confidence/method distribution, hypothesis-age, cluster browser over `donto_identity_cluster_cache` + edges (124 edges: 72 same_referent / 35 distinct / 16 possibly / 1 no-info). **~15% there.**
- **Discovery / Lens surface** — TODAY: per-job cross-source shared-entity *counts*. MISSING: open-discovery (fan-out from A) and closed-discovery (B-paths between A and C) per the Lens Engine; a query-time alignment demo. `donto_match_aligned` is **absent on this instance** (verified) though closure IS populated (883K rows, but ~98.9% are 'self' loops — only ~9,623 real edges, which is itself a signal worth surfacing). **~5% there.**
- **Abundance / steering metrics** — TODAY: abundance shown only as *volume* (predicate/statement counts). MISSING: the *entire* measurement-as-steering-wheel layer the CANONICAL doc calls the scarce resource — EIG-ordered generation queue, information-gain/Bayesian-surprise, novelty/diversity, the collapse-delta benchmark, downstream task-lift-per-fact. `grep` found none of these terms anywhere in the codebase. **~5% there.**
- **Bitemporal time-travel** — TODAY: per-statement tx_time/valid_time in one lens + the nebula clock. MISSING: a global/context "as-of" scrubber (`WHERE tx_time @> :asof`). **~10% there.**
- **Per-context deep dive** — TODAY: a context list (from a 6-day-stale matview showing 63% of the real context count). MISSING: a per-context drill-down with its own contradiction pressure, evidence coverage, predicate inventory, and an as-of view. **~25% there.**

The single most damaging *concrete* defect is the stale Contexts matview (P0) — it directly violates the operator's "don't trust a self-reported number" rule by silently serving 6-day-old, 63%-low data on the one page whose purpose is per-context truth.

## Roadmap to the full version

Ordered: quick wins (safety + correctness first, low effort), then the bigger surfaces. Steps 1–6 are cheap and high-leverage; 7–11 build the missing half of the vision (and they reuse SQL already proven in `job-panels.ts`, just un-scoped from a single `document_id`).

1. **Add auth in front of admin.donto.org (P0).** Caddy `basicauth`/forward_auth / Cloudflare Access / IP allowlist, or Next.js middleware. Until then, exclude CARE-restricted contexts from row/source/transcript surfaces. *(Hours.)*
2. **Refresh the context matview + add a staleness badge (P0).** Add `REFRESH MATERIALIZED VIEW CONCURRENTLY donto_mv_context_stats` to the daemon's periodic tick; render `refreshed_at` age. *(Hours.)*
3. **Fix the `SET LOCAL` flood + reconcile the timeout cap (P1).** Drop `SET LOCAL`, rely on the pool-level 120s, fix the stale "15s" comments. *(Minutes.)*
4. **Make the job-page panels actually lazy (P1).** Default `open=false`, render first ~3 eagerly, add a client concurrency limiter. Kills the 26-query pool-saturation burst. *(Hours.)*
5. **Replace `estRows()` with real `count(*)` on the small identity tables + use the health-view evidence count (P1).** `lib/health.ts getFabricCoverage()` ~282-289 and the fabric route. *(Minutes — fixes 1.7–4.3× and 9.4% errors.)*
6. **Harden inputs (P2):** validate job ids are UUIDs (404/400 not raw-PG-500), generic `fail()` messages with server-side logging; add the `vitest` test script to CI so the no-gimmick audit actually runs.
7. **Daemon time-series sparkline (P2).** Per-tick predicates/entities/accepted + backlog over the last N ticks (data already in `donto_align_daemon_tick`), reusing `job-charts.tsx`.
8. **Global `/arguments` (contradiction) explorer (P1).** Top contested (subject,predicate) pairs, relation-type counts (probe the real column), strength distribution, review-state — reuse the contradictions lens SQL un-scoped + the LIMIT-2000 candidate-CTE pattern from `/search`.
9. **Global `/evidence` page + `/identity` cluster browser (P1).** Coverage ratio, confidence distribution, target-type breakdown; identity confidence/method/age + cluster browser. Guard with reltuples + matview pattern.
10. **Steering / Abundance page (P1).** Claims-over-time + valid-fact% (from `beam.ts` data already present), embedding-distance diversity, contradiction-pressure trend, a collapse-delta tile once the Milestone-2 experiment exists. This is the biggest single move toward the *forward-looking* half of the vision.
11. **Lens-Engine discovery surface + bitemporal as-of viewer (P1/P2).** A "type a predicate → see folded forms + confidence" closure widget (closure IS populated); entity → neighbors → shared-predicate B-paths; and a global/context `WHERE tx_time @> :asof` scrubber. Reconcile `donto_match_aligned` (build it or fix CLAUDE.md, since it's absent on this instance).

After steps 1–6 the console is *safe and correct*; after 7–11 it crosses from ~55–60% to a genuine realization of the full vision.

---

*Verified 2026-06-09 via live probes against admin.donto.org and direct `donto_ro`/substrate queries: public no-auth access confirmed (`/api/health` 200 in 3.4s and `/api/table/donto_statement?view=rows` returns real rows with no token); `donto_mv_context_stats` last refreshed 2026-06-03 14:44 UTC with 23,406 rows vs live health-view; `donto_extract_queue`=360,899; `donto_embed_queue`=200,008; `donto_identity_edge`=124; `donto_argument`=2,433; `donto_match_aligned` absent (`to_regclass`=false). Substrate snapshot: ~39.5M believed statements, 994K predicates, 64,055 contexts, ~2.48M evidence links, 6.36M contested pairs, 7.66M contradiction pressure. Latencies and file:line references drawn from the six audit streams against `/mnt/donto-data/workspace/donto-web/apps/admin/`. No files were modified — review only.*