# Docs Center — Audit & Suggestions for Improvement

**Date:** 2026-06-29 **Scope:** the Oshun Documentation Center — generator
(`tools/docs_center/`, ~37 modules / ~12K LOC Python), rendered output
(`docs-center/**`, 228 pages), portal front door (`vdocs-index.html`), search
index (`vdocs-search-index.js`), measured against
`docs/proposals/DOCS_CENTER_PROPOSAL.md`. **Method:** a 12-dimension adversarial
multi-agent audit — every finding was refuted-or-confirmed by an independent
skeptic, then a completeness critic named blind spots and merged duplicates —
plus lead-verified deterministic checks (contrast math, gate runs, parser
repro). 43 findings survived verification; the headline correctness bugs were
reproduced by hand and are cited with exact `file:line`.

> **The central framing.** Both of the docs center's own gates are **green**
> right now — freshness (`--check`: 2976 files fresh, 2643 entity nodes) and
> integrity (`--verify`: 0 broken links, 0 dead source paths), and the
> self-report shows every product space at "100%". So "the gates pass" tells us
> nothing, and the large entity-scaffold authoring backlog is the system's own
> openly-published worklist — **neither is re-litigated here.** Every finding
> below is something the gates _cannot or do not_ measure, or a place where the
> center's self-description out-runs reality. **The recurring shape of the
> problem: the gates verify that committed output matches a re-render of source,
> but almost nothing verifies that the source _parsers_ extracted the source
> correctly — so wrong/incomplete data ships fully green.**

---

## Scorecard

| Dimension                      | Grade          | Headline                                                                                                                               |
| ------------------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Content depth (where authored) | **strong**     | Authored prose genuinely meets the V1 bar (V8 leaves 2.2–3.3K words); the scaffold backlog is a real queue, not a cover for thin pages |
| Content accuracy / drift       | adequate       | Prose claims are remarkably accurate; drift is concentrated, not pervasive                                                             |
| Coverage blind spots           | **needs-work** | Discovery roots are narrower than the surfaces the center claims to cover                                                              |
| Generator code quality         | adequate       | Disciplined overall; a few regex parsers silently under-enumerate, one is outright wrong                                               |
| Gate robustness                | adequate       | Strong link/path/facet gates, but real holes (orphan output, `#anchors`, parser output)                                                |
| Reader UX / IA                 | adequate       | One strong shared chrome; the 2,643-node headline asset is poorly navigable in the large aggregates                                    |
| Accessibility                  | adequate       | Strong foundation (skip-link, focus, reduced-motion); one real WCAG-AA contrast failure + a silent search palette                      |
| Performance / scale            | adequate       | Lean median page, but a 1 MB render-blocking search index ships on every page                                                          |
| Search quality                 | needs-work     | Whole-query substring match — multi-word / out-of-order queries miss                                                                   |
| Generator testing              | **needs-work** | ~12K LOC, 35 modules, **zero** Python unit tests                                                                                       |
| Maintainability                | adequate       | Strong docstrings; adding a surface is a 3-place shotgun edit; the only safety net is tautological                                     |
| Honesty ledger / proposal      | adequate       | Mostly scrupulous; one localized over-claim about what the depth gate enforces                                                         |

---

## Top priorities (the high-leverage fixes)

1. **Validate parser _output_, not just output-matches-source.** Today nothing
   catches silent under-enumeration: the Yemaya API reference renders ~18 of a
   real **499-path / 719-operation** spec; the Prisma enum parser drops values
   so **91 enums render "0 values"**; the contract parser drops name-re-exported
   modules. All ship green. Add golden-output unit tests + a `--verify`
   parser-sanity pass. _(Themes A & B)_
2. **Widen discovery, or stop claiming "every part."** Entity/reference
   discovery keys on tracked `project.json` and a hardcoded root `.env.example`,
   so ~40 Rust crates (~160K LOC), npm packages without a `project.json`,
   app-level OpenAPI specs, and 26+ per-service env files are structurally
   invisible while the report advertises "100%." _(Theme A)_
3. **Give the generator a test suite.** ~12K LOC across 35 parser/render modules
   has no `test_*.py` and pytest isn't even a dependency; the freshness gate is
   _tautological_ for logic bugs (a wrong computation passes the moment its
   buggy output is committed). This is the root enabler of #1. _(Theme C)_
4. **Make the 2,643 entity nodes navigable.** On the 1 MB aggregate pages most
   entities are reachable by neither the 12-item TOC nor global search —
   directly contradicting the §8 "every entity node is addressable" promise. One
   fix (`<details>` cards + real `<h3>` headings) serves UX, a11y, and perf at
   once. _(Themes D & F)_
5. **Defer the 1 MB search index.** It loads render-blocking at top-of-`<body>`
   on all 228 pages + portal, though its only consumer is the palette on first
   keypress; half of it (504 KB) is secondary heading-anchor data. _(Theme E)_

---

## What's genuinely strong (so the rest is credible)

- **Where content is authored, it is deep and on-spec — not padded.** Measured:
  V8 leaf deep-dives 2,235–3,326 prose words; gate-enforced platform pages
  1,231–3,620; 771 walkthrough "view" pages median 645 (min 356) with real
  source-path frontmatter. The disclosed scaffold backlog is an honest authoring
  queue, not thin pages dressed up.
- **Deterministic by construction.** No `datetime.now` / `random` / `time.time`
  in the generator; sets are sorted before emit; discovery is built from
  `git ls-files` (not a raw FS walk), so build output and vendored checkouts
  can't surface phantom nodes. This is why `--check` is stable.
- **An honest disclosure culture.** The depth proxy is repeatedly and correctly
  described as _advisory / necessary-but-not-sufficient / never certifying_;
  entity scaffolds are counted as a worklist, not passed off as prose; a
  deferred-surfaces ledger and an orphan report both exist;
  as-built-vs-aspirational is scrupulously labeled in prose (`@nyx/sdk` "an
  aspirational SDK surface", `@maat/contracts` "a scaffold, honestly labelled").
- **Disciplined code.** Zero bare `except:`; `html.escape` used consistently (no
  injection vector found); real responsive media queries (`@media 980px/1200px`,
  `min-width:0` guards); the integrity gate genuinely validates that every
  entity `source_dir` and journey spec exists and that page facets bind to live
  space slugs — so dead paths and stale facets can't ship.
- **A11y foundation is real:** a verified skip-link, a high-contrast
  `:focus-visible` outline, a `role=dialog`/`aria-modal` palette, labeled icon
  controls, and a comprehensive `prefers-reduced-motion` CSS block; body text
  contrast is excellent (16.4:1).

---

## Findings by theme

Severity: **HIGH** = ships wrong/incomplete data or breaks a core promise ·
**MEDIUM** = real defect, meaningful reader/maintainer impact · **LOW/NIT** =
polish. Findings are grouped by root cause (per the completeness critic's merge
guidance), not by audit dimension.

### Theme A — Silent under-enumeration: gates green, data wrong or incomplete

**A1 · [HIGH] App-level OpenAPI specs are never discovered — the Yemaya API
reference renders a small fraction of the real surface.**
`apps/yemaya/api/openapi/openapi.json` defines **499 paths / 719 operations**
(git-tracked), yet the rendered `docs-center/api-and-contracts/api-yemaya.html`
is built from a different, much smaller bundled spec (the auditor measured ~18
operations). The reference discovery roots scan `libs/openapi` bundles + source
specs but never `apps/**/openapi/`. → **Fix:** extend reference discovery to
enumerate tracked `apps/**/openapi*.json` (the same instinct already applied to
rescue source-only `libs/openapi/src/specs`), and add a `--verify` check that
flags any large tracked OpenAPI spec with no corresponding reference page.

**A2 · [HIGH] The Prisma enum-value parser silently drops values — 91 enums
render "0 values" in the shipped Data reference.**
`tools/docs_center/data.py:136`:
`vals = re.findall(r"^\s+([A-Z][A-Z0-9_]*)\s*$", ebody, re.M)`. The `[A-Z]`
start-anchor drops any lowercase/PascalCase value and the `\s*$` end-anchor
drops any value carrying a trailing `@map("…")` or `//` comment. Reproduced:
`veritas SourceStatus` (`active/paused/retired/pending` — all lowercase) and
`bellona BuildStatus` (6 values with trailing attributes) both render **0
values**; `grep "0 values" docs-center/data/` returns **91** hits — all green. →
**Fix:** broaden to `^\s*([A-Za-z][A-Za-z0-9_]*)\b` and strip trailing
`@map(...)`/comments; add a golden test asserting a known enum's values render
(see C2).

**A3 · [MEDIUM] The contract reference drops modules re-exported by name (not
`export *`).** `reference.py:39` `_REEXPORT_RE` matches only `export * from`.
`libs/contracts/src/tts/index.ts:27` surfaces `./client` solely via
`export { TTSProviderStatusSchema, … } from './client'`, so `contract-tts.html`
shows 8 modules (the star-exports) and the `./client` module — including
`OshunTTSClient` and 6 symbols — vanishes with its source link; `llm` similarly
drops `./client`. `check_reference_coverage` only proves the page file exists,
so the loss is invisible. → **Fix:** unify on the robust parser the repo already
has in `entities.py:327-330` (`_API_EXPORT_RE` handles
`abstract`/`async`/`let`/`var`; `_API_NAMED_RE` handles `export { X as Y }`),
extended to capture the `from '…'` target of a named re-export and resolve it as
a module. (The `export abstract/async` weakness is real but currently _latent_
for contract pages — the named-re-export drop is the observable loss today.)

**A4 · [MEDIUM] The configuration reference reads only the root `.env.example`,
missing 26+ per-service config surfaces incl. feature flags.** `config.py:39`
hardcodes `ENV_EXAMPLE = ROOT / ".env.example"` and the docstring calls it "the
platform's single authoritative configuration surface." But `git ls-files` shows
27 tracked `*.env*.example` files; `apps/oshun/bff/.env.example` alone has 23
variables absent from root — `FEATURE_FLAGS`, `AUTH_ISSUER`, `AUTH_JWKS_URL`,
`CORS_ORIGINS`, `RATE_LIMIT_MAX_REQUESTS`, `OSHUN_NYX_API_BASE_URL` — and
`apps/lilith/svc-ai/.env.example` adds 17 more. None are surfaced. → **Fix:**
enumerate all tracked `**/.env*.example` via `git ls-files`; render each file's
unique-to-it variables attributed to its owning app/service; add a `--verify`
check that every tracked env-example is parsed.

**A5 · [MEDIUM] Real npm packages without a `project.json` are invisible to the
entity catalog _and_ silently drop their dependency-graph edges.** Entity
discovery is keyed entirely on tracked `project.json` (`entities.py:382`), so a
workspace package that ships only a `package.json` is absent from a catalog that
advertises "Every part of the system, addressable" — and any dependency edge
pointing at it is dropped from the graph with no gate noise. This is the same
root cause as the missing Rust crates (H-series) — see the merge note below.

> **Merge note (per critic):** A1/A5 and the missing Rust crates are **one root
> cause** — discovery keyed on `project.json` — with several instances. Fixing
> discovery (add `package.json`-only packages, `Cargo.toml` crates, and
> `apps/**/openapi`) closes them together. **A5 is the meta-fix that makes A1–A4
> detectable: nothing validates parser _output_.**

### Theme B — Gate blind spots (things that can rot fully green)

**B1 · [MEDIUM] Orphaned/stale _output_ files pass both gates forever — the
literal hole in "never-stale."** `cli.py:63-65` `emit()` is one-directional: it
proves every file the build _emits_ matches disk, but never that disk contains
_only_ what the build emits. Delete a project or rename a page and its old
rendered `.html` lingers, fully green. → **Fix:** add a reverse pass to
`--check` — collect the set of emitted paths, enumerate all generated locations
(`docs-center/**/*.html` + portal + index + rendered siblings), and fail on any
tracked generated file not emitted this run.

**B2 · [MEDIUM] "0 broken links" never validates `#fragment` anchors and never
scans the search index.** `coverage.py:40-41`: `_SKIP_PREFIXES` skips `#` and
`//` wholesale, and cross-page links are reduced by
`target = href.split("#",1)[0]` before the file-existence check — so the
fragment is never resolved. External URLs are skipped entirely. The entire TOC /
permalink / deep-link / palette target set (8,731 anchors) is unverified, and a
heading-id rename silently breaks every link to it. → **Fix:** when a target
carries `#frag`, parse the destination page's emitted `id="…"` set and fail if
absent (same-page and cross-page); add a pass asserting every search-index `h`
resolves to an emitted file + anchor. Optionally a non-blocking external-link
liveness report.

**B3 · [MEDIUM] No gate or test validates parser output — under-enumeration
ships silently.** The unifying defect behind Theme A. The freshness diff only
proves output is _reproducible_, not _correct_; the depth proxy measures prose,
not generated-table completeness. → **Fix:** golden-fixture tests per parser
(TS/Prisma/OpenAPI/env/Build.cs/event/error) + a `--verify` sanity layer (e.g.
"an `enum` block with `{…}` body must yield ≥1 value", "a discovered OpenAPI
spec must yield ≥1 operation").

### Theme C — The generator has zero automated logic tests

**C1 · [HIGH] ~12K LOC across 35 modules has no Python unit test; the only
safety net is tautological.** No `test_*.py`, no `conftest`, no pytest config;
pytest isn't in `tools/requirements.txt`. The sole automated test is a 662-line
Playwright `reader.spec.ts` that asserts against rendered DOM with loose
lower-bound floors (`count > 3`, `> 5`, `> 100`) — it never checks parser output
and can't catch a catastrophic drop from 499→18. The freshness gate is
_tautological_ for logic bugs: a wrong computation passes the instant its buggy
output is regenerated and committed (exactly how A2 ships green). → **Fix:** add
a `pytest` suite — golden-corpus parser tests (B3), a discovery test asserting
known projects/crates/specs appear, and the depth-proxy fixture (C2). This is
the highest-leverage structural fix in the report.

**C2 · [MEDIUM] The §11 depth proxy that gates "complete" has no fixture proving
it works.** The proxy (`coverage.py:317-350`) is `prose_words < 150` OR
(`≥12 table rows` AND `<260` prose) OR (no link/backtick). Nothing proves it
_flags_ a known-thin page and _passes_ a known-good one, so a future tweak could
quietly neuter the one gate that defends "complete." → **Fix:** two fixtures (a
100-word filler page → flagged; a real V1-quality page → clears) asserted in CI.

### Theme D — Navigability of the 2,643-node headline asset

**D1 · [MEDIUM] Most entity nodes on the large aggregate pages are reachable by
neither the on-page TOC nor global search.** The 1 MB `lib-iris.html` inlines
~262 entity sections but the TOC is capped at 12 items and search
section-anchors are capped at 12/page (`MAX_SEARCH_SECTIONS`), so the long tail
is addressable only by manual scroll or Ctrl-F — contradicting the §8 "every
entity node" promise. (The page _does_ carry per-entity `id=` anchors — 293 of
them — so the deep-links exist; they're just not surfaced in any index.) →
**Fix:** raise/remove the per-page section cap for entity pages, or index every
entity node as its own search entry with its anchor (see D2's structural fix,
which enables this).

**D2 · [MEDIUM] The 1 MB aggregate pages are a fully-expanded ~21K-word DOM with
no collapse or virtualization.** `lib-iris.html` is a single always-rendered
~18,880-node DOM; entity deep-dives are emitted as `<code>` runs, not headings,
so they don't appear in the document outline either (see F3). → **Fix (one
change, three payoffs):** wrap each entity card in `<details>` and emit a real
`<h3>` per entity. This collapses the DOM (perf), restores the
screen-reader/outline structure (a11y), and lets every entity be TOC- and
search-addressable (D1/UX).

**D3 · [MEDIUM] Search is whole-query substring matching — multi-word or
out-of-order queries return "No matches."** `search.py:246`
`var q = input.value.trim().toLowerCase()` and `:230`
`var h = row.hay, i = h.indexOf(q)` — the entire query is matched as one
contiguous, in-order substring. Searching `instancing iris` (when the haystack
reads "iris … instancing") returns nothing. → **Fix:** tokenize on whitespace
and require all tokens to hit (AND), scoring by summed per-token position with
the existing title-prefix/kind boosts — a ~5-line change to
`score()`/`render()`.

**D4 · [LOW] The portal front door has drifted into a 24-section vertical scroll
(14 single-card rails); first-run orientation is buried.** → **Fix:** collapse
single-card rails into a compact directory or merge them; lift a "new here?
start with the repo map / role lenses" band to the first screen.

### Theme E — Performance & scale

**E1 · [MEDIUM] The 1 MB search index loads render-blocking at top-of-`<body>`
on every one of 228 pages + portal.** `shell.py:155-157` injects
`<script src=".../vdocs-search-index.js">` synchronously, though the only
consumer is the palette on first keypress. (This is a _deliberate_ `file://`
tradeoff — browsers block `fetch()` of local files — so the fix must preserve
`<script>`-based loading, not switch to `fetch`.) → **Fix:** add `defer`, or
inject the index `<script>` lazily on the first `/` / Ctrl-K keypress; both keep
`file://` support.

**E2 · [MEDIUM] Half the eagerly-shipped index (504 KB of 1,009 KB) is secondary
heading-anchor data, and the index grows unbounded with the corpus.** Measured:
the per-entry `"s"` section arrays are 504,213 bytes = 50.0% of the payload
across 8,731 jump-targets; the index is one flat file that scales O(pages × 12)
— it doubles as authoring proceeds toward the proposal's thousands of nodes. →
**Fix:** split into a lean page-level index (loaded eagerly/deferred for instant
palette open) + an href-keyed sections shard loaded lazily **via injected
`<script>`** (not `fetch`, for `file://`). Keep `s` — it powers cross-page
heading search; don't drop it.

**E3 · [LOW] Diagram (mermaid) and code-highlight libraries load from external
CDNs — the reader silently degrades offline / under `file://` / strict CSP.** →
**Fix:** vendor both into `assets/` and reference locally, consistent with the
self-contained `file://` design.

### Theme F — Accessibility (strong base, specific defects)

**F1 · [MEDIUM] `--text-mute` (#6e7681) fails WCAG AA on every background.**
Lead-computed contrast: #6e7681 on `--bg` #0a0c0f = **4.26:1** (and 3.56:1 on
elevated surfaces) — below the 4.5:1 required for the small text it styles
(breadcrumbs, TOC sub-links, the search label, metadata, config defaults),
contradicting the proposal's "readable typography … accessible contrast."
(`--text` 16.4:1 and `--accent` 6.95:1 pass comfortably — only the mutest tier
fails.) → **Fix:** lighten the token to ≈ #8b949e / #9198a1 (≥4.5:1) or reserve
#6e7681 for ≥18px text only; add a contrast assertion (F4).

**F2 · [MEDIUM] The command palette is silent to screen readers — selection is a
CSS class only.** `search.py:140` the input is a plain textbox (no
`role=combobox`/`aria-expanded`/`aria-controls`); options carry no `id`;
arrow-key selection only toggles a `.sel` class (`reader.js:241-246`);
`aria-activedescendant` appears nowhere. A screen-reader user can type and
ArrowDown but hears nothing about the highlighted result. No JS focus trap backs
the `aria-modal`, so Tab can leave the open palette. → **Fix:**
`role=combobox` + `aria-expanded`/`aria-controls=cmdkResults`; give each option
a stable `id`; set `aria-activedescendant` in `setSel()`; add a minimal Tab
focus trap.

**F3 · [MEDIUM] Inlined entity deep-dives are `<code>`, not headings — 262
sections collapse to ~12 outline stops.** Same root as D2; fixed by emitting
real `<h3>` per entity.

**F4 · [LOW] The "accessibility smoke" gate checks structure only — it asserts
_zero_ contrast or ARIA.** The Playwright suite verifies skip-link/keyboard flow
but no contrast, `aria-activedescendant`, or heading-order — which is why
F1/F2/F3 ship green. → **Fix:** add `@axe-core/playwright` with a contrast +
ARIA ruleset to `reader.spec.ts`.

**F5 · [LOW] `prefers-reduced-motion` is honored in CSS but bypassed by the
back-to-top JS smooth-scroll** (`behavior:'smooth'` is unconditional). → gate
the JS on `matchMedia('(prefers-reduced-motion: reduce)')`.

**F6 · [LOW] Primary navigation lives in `<aside>` (complementary), not a
`<nav>` landmark; the TOC likewise.** → wrap nav regions in
`<nav aria-label="…">`.

### Theme G — Maintainability & architecture

**G1 · [MEDIUM] Adding a generated surface is a shotgun edit across three
parallel hand-maintained lists.** A new surface must be added to `cli.py`'s
import block, wired into `build()`, _and_ appended to the `rails=[…]` list —
three places, no single registry, easy to half-wire. → **Fix:** a surface
registry (a list of `{render, rail}` descriptors) iterated once in `build()`.

**G2 · [MEDIUM] Disclosure over-claim: the enforced depth gate is a ~150-word
floor, but one coverage-report paragraph says it "blocks at the V1 standard" and
"a done space can never silently rot."** The proxy is _honestly_ described as
advisory almost everywhere (and it is — confirmed at `coverage.py:307-350`); the
over-claim is localized to a single enforcement paragraph that implies the floor
guarantees V1-quality prose, which it cannot. _(Verified and deliberately
downgraded from the auditor's HIGH — this is a wording fix, not a systemic
deception.)_ → **Fix:** reword to "blocks a _regression below the structural
floor_"; optionally strengthen the proxy (e.g. require ≥1 worked example for
page types that warrant one).

**G3 · [LOW] God-modules & unlintable assets.** `coverage.py` is 1,144 lines
ending in a 488-line render function; 40 KB of CSS lives as a single Python
raw-string literal (unlintable/unformattable); renderer signatures are
inconsistent (the same two objects in opposite order across renderers); the one
architecture doc lists 8 of 37 modules and omits the two largest. → **Fix:**
split `coverage.py` (compute vs render), move CSS to a real `.css` source
compiled in, normalize renderer signatures, and regenerate the module index from
the package.

**G4 · [LOW] Committing 228 rendered HTML files + a 1 MB regenerated index makes
docs PRs unreviewable and drives the cross-worktree merge treadmill.** →
**Fix:** `.gitattributes merge=union` (or a custom driver) for
`vdocs-search-index.js`, split the index per-section to localize diffs, or
generate HTML/index as a CI artifact (gitignored) with the freshness gate
enforced in CI.

### Theme H — Content accuracy drift (minor, well-contained)

**H1 · [MEDIUM] The "~Nk lines" badge is computed two ways** — `nyx`/`themis`
include test files, everyone else excludes them — making cross-page size
comparison invalid and labeling test code as "domain-specific code." → pick one
rule (exclude tests) and apply uniformly; show files-counted. **H2 · [MEDIUM]
The euterpe deep-dive's "the two exceptions are the real-time audio engines"
enumeration is now stale** (more crates are implemented). → regenerate the
enumeration from disk or soften to a non-enumerated statement. **H3 · [LOW] ~37
navigation/index README hubs are counted as "authored pages clearing depth
proxy" toward the headline "100%."** → exclude index/hub pages from the depth
denominator (as TEMPLATE/result pages already are). **H4 · [LOW] A drifted
SDK-examples page is surfaced as authoritative API guidance.** It's a
_discipline_ page (`docs-center/disciplines/api-internal.html` ←
`docs/api/sdk-examples.md`), and it's _inconsistent_ not uniformly fake:
`@oshun/sophia-client`/`@oshun/bellona-client` are real, but
`@oshun/client`/`@oshun/hathor-client` and the `pip install oshun-sdk` /
`go get` sections are fabricated, and `createIsisClient` should be
`createClient` (`libs/isis/client/src/index.ts:31`). → correct the specifiers,
drop/flag the imaginary SDKs, and add a `verify()` check resolving workspace
`@scope/*` import specifiers in authored docs against `tsconfig.base.json`
paths. **H5 · [NIT] The self-report's drift-fix ledger says `nyx` "retains
`@oshun/database`,"** which the dependency graph doesn't bear out. → re-derive
ledger claims from the live graph.

---

## Suggested roadmap

**P0 — correctness the gates can't see (1–2 days):** fix the Prisma enum regex
(A2) and contract parser (A3); add app-OpenAPI + `package.json`/`Cargo.toml`
discovery (A1/A5); stand up `pytest` with golden-output parser tests + the
depth-proxy fixture (C1/C2/B3). This stops wrong data from shipping green and
pays for itself immediately.

**P1 — close the gate holes & the headline reader defects (2–4 days):**
reverse-pass orphan-output check (B1); `#anchor` + index validation in
`--verify` (B2); the `<details>` + `<h3>` entity-card refactor (D2 → D1/F3);
defer/lazy-load + split the search index (E1/E2); tokenized search (D3);
`--text-mute` contrast + axe gate (F1/F4); palette ARIA (F2).

**P2 — durability & ergonomics (ongoing):** config-surface discovery (A4);
surface registry (G1); `coverage.py` split + CSS extraction (G3); index merge
strategy / CI-artifact (G4); portal front-door trim (D4); the drift-uniformity
fixes (H1–H5); reword the depth-enforcement paragraph (G2).

---

## Appendix — methodology & honesty

**Dimensions audited (12):** content-depth-honesty, content-accuracy-drift,
coverage-blindspots, generator-code-quality, gate-robustness, reader-ux-ia,
accessibility, performance-scale, search-quality, generator-testing,
maintainability, honesty-ledger-proposal. Each finding was independently
refuted-or-confirmed by a skeptic agent before inclusion; a completeness critic
then named blind spots and merged duplicates.

**Disclosure:** 2 of the 12 audit agents (search-quality,
honesty-ledger-proposal) failed to return valid structured output and
contributed no findings of their own. Their territory is covered by adjacent
dimensions and lead checks: search-quality by D1/D3/E1/E2 (the critic personally
surfaced the substring-match bug) and the honesty-ledger by G2/A1/H-series. No
dimension was left dark.

**False positives the audit checked and correctly did _not_ raise** (recorded so
they aren't re-raised): HTML injection — handled, `html.escape` is consistent;
mobile/responsive — real `@media` breakpoints with `min-width:0` guards exist;
"1 MB pages have no in-page nav" — false, they carry a sticky TOC and 293
anchors (the perf/cap concern is real, the nav-absence claim is not); "`--check`
is non-deterministic" — false, no time/random in the generator; "secret
redaction fails open" — narrower than first claimed: `_classify`
(`config.py:89`) is a name-pattern allow-list that _could_ miss a
credential-bearing var whose name doesn't match (`*_DSN`, `*_WEBHOOK`), but the
only source is `.env.example` placeholders, so impact is low/latent (worth
widening the classifier, not a live leak).

_Report generated from a verified multi-agent audit; every `file:line` citation
in the Findings section was reproduced or read directly._

---

## Remediation status (2026-06-30)

Most findings have been **fixed and shipped** (regenerated; `--check`,
`--verify`, and a new `pytest` suite all green). Summary:

**Fixed**

- **A1** OpenAPI discovery now spans canonical apps/services/libs specs, deduped
  per domain (Yemaya 18→**719 ops**; +metis 174, isis ×3, v7).
- **A2** Prisma enum-value parser rewritten — 91 "0 values" → **0** (1321
  values).
- **A3** contract reference now enumerates named re-exports (tts/llm
  `./client`).
- **A4** config reads every per-service `.env.example` (326→**762 vars**;
  feature flags/auth/CORS surfaced) + widened secret classifier.
- **A5** new generated **Rust crate index** — the 426 `Cargo.toml` crates (neith
  187, maya 95, …) are now addressable + searchable.
- **B1** `--check` gained a reverse orphan-output pass; **B2** `check_links` now
  validates `#fragment` anchors (caught + fixed 2 real latent broken anchors);
  **B3** parser-output now covered by tests.
- **C1/C2** new `tools/docs_center/tests/test_generator.py`
  (`docs:center:pytest`) — golden parser tests + depth-proxy flag/pass
  fixtures + discovery + crate index.
- **D1** every entity individually searchable; **D3** tokenized AND search;
  **E1/E2** the ~1 MB index is lazy-loaded on first palette use, not on every
  page.
- **F1** `--text-mute` raised to AA-passing #7d8590; **F2** palette combobox +
  `aria-activedescendant` + focus trap; **F3** entity nodes are real `<h3>`;
  **F4** a11y assertions added; **F5** reduced-motion-aware scroll; **F6**
  `<nav>`.
- **G2** depth-gate wording corrected to describe the structural floor honestly.
- **H2** euterpe "two exceptions" → all three Rust workspaces; **H4**
  SDK-examples guide marked illustrative + isis specifier corrected.

**Deliberately deferred (with rationale)**

- **D2 (`<details>` collapse)** — F3 + lazy-load already addressed the a11y/perf
  headline; full collapse changes default UX and the existing reader test, low
  marginal value.
- **G1 (surface registry)** / **G3 (god-module + CSS-in-Python split)** —
  internal-only refactors; deferred to avoid regression risk in a byte-stable
  generator for no reader-facing gain.
- **G4 (index merge strategy)** — `merge=union` would _corrupt_ the structured
  JSON index (it is not append-only); the safe resolution stays
  regenerate-on-conflict, and the real fix (gitignore + CI artifact) is too
  large to apply unilaterally.
- **D4 (portal trim), E3 (vendor CDN libs), H1 (line-count uniformity), H5** —
  lower-priority polish / authored-prose hygiene; H5 lives only in a dated
  historical audit snapshot, not the live center.

---

## Remediation status (2026-07-01) — second pass

The 2026-06-30 deferrals D4 and E3 are now **fixed**, plus a broken-anchor sweep
and a V1 content-quality pass the first remediation did not cover:

- **D4 (portal front door)** — `portal.py organize_rails()`: a "Start here"
  orientation band (role lenses + repo map) now renders first, above the product
  grid, and the 15 single-card rails collapse into one compact "Reference
  directory" grid. Each collapsed card keeps its old rail key as its DOM id, so
  every existing `vdocs-index.html#<key>` deep link still resolves. Covered by a
  new pytest (`test_organize_rails_collapses_singles_and_lifts_orientation`) and
  the updated portal Playwright spec (orientation-before-products assertion).
- **E3 (CDN libs)** — highlight.js 11.9.0 (JS + github-dark CSS) and the
  mermaid@11 UMD bundle are vendored under `docs-center/assets/vendor/` and
  referenced relatively (`theme.py` emits per-page root-prefixed paths; the
  mermaid loader switched from a CDN ESM import to the vendored classic script,
  which also fixes `file://` where module imports are CORS-blocked).
  `write_assets` re-emits the vendored files so the reverse orphan pass counts
  them as build outputs. Zero CDN references remain in rendered output.
- **Anchor integrity** — `check_links` now percent-decodes fragments before
  matching ids (browsers do; the checker didn't — the V7 `nàná` anchors were
  false positives). All 39 real content-broken `#anchors` were then fixed at
  their markdown sources: walkthrough deep-links retargeted to the
  post-decomposition V1 hub slugs, five `docs/` TOCs repaired, one missing
  proposal heading restored, and one over-deep relative path corrected.
  `check_links` now reports **0 structural / 0 content** broken links.
- **V1 content quality (7-agent adversarial sweep, all 131 pages + hubs)** —
  verdict: the corpus is genuinely strong (code claims overwhelmingly verify;
  honest-scope framing is pervasive). Fixed: a stale `buildSophiaGroundedAnswer`
  line citation, the contracts/common file count, a stale present-tense Nisaba
  edge-label callout, and small framing clarifications (Nisaba stub-shell
  tension in the opening, Metis BYOM contracts-vs-pipeline split). The two
  **completely undocumented surfaces** the sweep confirmed — the account hub
  (`/profile`, 9 walkthrough pages) and the Atelier (`/atelier`, 8 pages) — are
  now documented: `V1/features/account-and-profile.md` +
  `V1/features/atelier.md`, hub sections carrying the `#account` / `#atelier`
  anchors in both `V1/features.md` and `V1/ARCHITECTURE.md`, and README nav
  registration.

## Remediation status (2026-07-02) — third pass: UI/UX & ergonomics

A hands-on browser audit of the reader chrome (headless system Chrome via
Playwright at 1440/1024/390 px; real wheel/touch gestures, keyboard flows, and
focus tracking — not source-reading alone). Every finding below was reproduced
before the fix and re-verified after; the reader Playwright suite grew seven
assertions covering them (95 passing).

- **No in-page navigation below 1200 px** (the biggest gap) — the right "On this
  page" rail disappears at ≤1200 px with no replacement, on generated pages with
  59–123 sections. `shell.py` now renders the same per-page TOC markup inside a
  collapsed `<details class="toc-inline">` under the page header; CSS shows it
  exactly where the rail is hidden. Pages with no headings emit no block.
  Scrollspy/count/filter selectors were scoped to `.toc-rail` so the duplicate
  cannot double-count or cross-filter.
- **Palette Esc dead outside the input** — Escape was bound on `#cmdkInput`
  only, so keyboard focus on a facet chip trapped the dialog open. Moved to the
  `#cmdk` container (closes from input, facets, or results).
- **No focus restore** — closing the palette dropped focus on `<body>`; `open()`
  now records the trigger and `close()` restores it (guarded by
  `document.contains`).
- **Background scrolled under both overlays** — wheel over the palette backdrop
  or the mobile-drawer scrim scrolled the article behind it.
  `body.cmdk-lock{overflow:hidden}` while the palette is open and
  `body.sb-open{overflow:hidden}` inside the ≤980 px drawer media block.
- **Drawer a11y state** — `#hamburger` had no `aria-expanded`/`aria-controls`
  and Esc did not close the drawer; a `setDrawer()` helper now syncs the ARIA
  state everywhere the drawer toggles, and a document-level Escape handler
  closes it.
- **Facet chips** — rendered with no initial `aria-pressed` (now `"false"`), and
  a mouse click left focus on the chip so typing went nowhere (mouse clicks now
  hand focus back to the query input; keyboard activation keeps position).
- **Portal "What's new" blurbs cut mid-word** — the bare `[:90]` slice shipped
  "server lifecy" / "marketplace sys" on the front door;
  `entities._card_blurb()` truncates at a word boundary with an ellipsis.
- **Heading anchors invisible on touch** — `.hd-anchor` was hover-revealed only;
  `@media(hover:none)` keeps it faintly visible (opacity .35) and
  `:focus-visible` reveals it for keyboard users.
- **Small polish** — breadcrumb current-page ellipsis instead of hard clip;
  `<meta name="theme-color" content="#0a0c0f">` on portal + reader pages;
  `.toc-inline` added to the print-hide list.
- **Test harness** — `playwright.config.ts` accepts `PW_BROWSER_CHANNEL=chrome`
  so the reader suite runs against installed system Chrome where the bundled
  chromium is not downloaded.

## Remediation status (2026-07-12) — fourth pass: full visual & IA redesign

A complete professional redesign of the reader shell, portal, and command
palette — still strictly inside the §8 thesis (dark-mode-only minimalist
technical atlas, one action accent, no gradient wash). 102 reader specs + 15
generator pytests green; every page re-rendered and fresh.

**Typography now vendored, not wished for.** The stylesheet referenced
Inter/JetBrains Mono but never shipped them, so every reader fell back to system
fonts. Inter Variable 4.1 + JetBrains Mono 2.304 (400/600/700) are now committed
under `docs-center/assets/vendor/` with `@font-face` in the shared stylesheet —
same self-contained contract as the JS vendors (offline, `file://`, strict CSP).
`Builder.emit` gained a binary-safe path, the vendored-asset emit covers
`.woff2`, and the reverse orphan pass now counts fonts. Tabular numerals on
every count/metric surface.

**Palette retuned around a real elevation ramp** — `--bg #08090d` →
side/elev/soft/hover tiers, hairline borders plus a faint inset top-light on
cards instead of heavy shadows. Every text tier re-validated against WCAG AA
(the smallest tier, `--text-mute #808b9e`, now clears 5.1:1 on its worst surface
vs 4.68:1 before). The product `--tint` was promoted from a 7px eyebrow dot to
the page's identity thread: tinted eyebrow, H2 rule tick, list markers,
blockquote rule, TOC/page-nav active indicators, prev/next hover — while
`--accent` stays the single action/focus colour.

**Portal relayout.** Sticky top bar with section anchors; the dead right half of
the hero is now a search panel (large palette trigger + "jump to" pills to the
repo map, role lenses, API reference, systems catalog, journeys); stats as a
quiet hairline strip; product cards gained a tint top-edge, one large click
target, and a split Features/Architecture footer instead of nine pairs of boxed
buttons.

**IA: the sidebar now carries an "All spaces" switcher** (11 links: portal,
start-here, systems, domains, platform, journeys, API & contracts, data, engine,
disciplines, coverage) rendered on every reader page — article pages are no
longer dead ends that route through the portal.

**Ergonomics: a real keyboard layer.** `?` opens a shortcuts panel (true modal:
scroll lock, Tab trap, focus restore), `[` / `]` toggle the nav and TOC panes.
Guards proven by tests: editable targets never trigger keys, an open
palette/mermaid modal takes precedence, and while the panel is open only `?`/Esc
act.

**Fixed en route** — portal horizontal overflow at phone widths (the hero search
label's `white-space:nowrap` inflated the grid's min-content; fixed with
`min-width:0` + `minmax(0,1fr)`), found by the mobile overflow gate.

**Verification** — multi-viewport screenshot audit (1600/390 across portal, hub,
article, reference, systems, start-here), scripted behaviour probes for the new
keyboard layer, adversarial stub grep over changed files (0 actionable), Codex
read-only review (3 findings — overlay stacking, modal semantics, orphan-pass
gap — all fixed and re-verified), full reader suite + generator pytests green,
freshness gate green.

## Remediation status (2026-07-12, second pass) — journeys: evidence honesty, scale & visualization

The journey space closed the six gaps a hands-on review found between "very
good" and "ideal", plus a visualization layer. All deterministic — the staleness
anchor is the corpus's newest walk date, never the build clock, so the freshness
gate stays byte-stable.

- **Verdicts carry their walk date** — every verdict chip now reads
  `verdict: pass · 2026-05-29`; a result with no parseable `**Walked**` date
  says `date unknown` instead of posing as current. Verdicts more than 45 days
  behind the newest recorded walk get an explicit dashed `stale` chip, and the
  registry states the anchor so "stale" is interpretable.
- **The automation gap is a list, not a statistic** — each surface section opens
  with an explicit, linked "N journeys without automation" block (rendered only
  when the gap exists).
- **"Verification at a glance"** — the registry opens with one stacked status
  meter per surface (pass/partial/fail/no-result, 2px surface gaps, flex-grown
  segments), automation coverage beside it, and a single shared counts legend;
  colour is never the only channel (aria breakdown + visible counts, per the
  dataviz status-palette rules).
- **Registry tables are now a working tool** — per-surface text filter, a
  "failing first" sort toggle (rows carry a deterministic verdict rank), and an
  honest live shown-count; pure client-side reorder/hide over unchanged HTML.
- **Every journey gets an auto-generated flow diagram** — a mermaid flowchart
  derived from the rendered `## Steps` TOC (so it can never drift from the
  prose), each node deep-linking to its step heading, terminating in a verdict
  node linked to the recorded walk; joins the on-page TOC. Labels are sanitised
  for mermaid/htmlLabels injection.
- **Journeys joined the knowledge graph** — app entity nodes that are
  walkthrough surfaces now carry a "verified by N user journeys" row with the
  same meter grammar, counts, automation ratio, and a registry deep link;
  attachment is most-specific-path-prefix, insertion-order-free.
- **Markdown task lists render as designed checklists** — `- [ ]`/`- [x]` become
  GitHub-style disabled checkboxes via token surgery (tight, loose, and nested
  lists covered by unit tests; code fences untouched), ending the
  literal-bracket look on every journey's Steps section.

**Verification** — 17 generator pytests (2 new: task-list surgery, label
sanitisation + date-unknown honesty), reader suite grown to 134 checks with 7
new specs (meters/legend, dated verdicts, gap list, filter/sort, flow diagram,
checkboxes, entity verified-by), screenshots at 1600/390 (no overflow at 390px),
Codex read-only review (4 findings — undated-verdict honesty, mermaid label
escaping, tight-list coverage, prefix-overlap determinism — all fixed),
freshness gate green over 3,077 files.

### Addendum (same day) — registry visual redesign

A hands-on look at the shipped registry judged it data-complete but visually
noisy ("not very awesome"): the E2E-spec column ballooned rows to 9 lines of
filename chips, verdict pills wrapped into three-line blobs, the overview was
two thin bars covering only 2 of 5 surfaces, and every row repeated a "Journey:"
prefix. Redesigned:

- **Overview → dashboard cards.** One card per surface (all five — an honest
  zero beats absence): tinted badge + app path, big tabular stat figures
  (journeys / results / views / routes), the verdict meter with automation
  fraction, and quick links (journey table, coverage matrix, conventions) that
  render only when the target exists.
- **"Latest recorded walks"** — the eight newest dated walks as an evidence
  strip (verdict dot + date + surface + title), deterministic order.
- **Tables went calm.** Spec chip walls collapsed to a one-line expandable "N
  specs" fold (single specs link directly, ellipsised); the automation chip is
  one word (the count lives in the fold); verdict chips no longer wrap;
  "Journey:" prefixes stripped from row labels and gap lists.

## Remediation status (2026-07-12, third pass) — center-wide audit: broken components, scale-blind counts, viz layer

A hands-on screenshot audit of every major hub (16 surfaces at 1600px) found one
real bug class and a family of scale-blind count displays; all fixed in one
pass, single-threaded.

- **BUG: portal-scoped CSS used on reader pages.** `.rcard`/`.rail-grid` lived
  only in the portal's inline stylesheet, but five reader surfaces (data index,
  API & contracts index, engine, lenses, comfyui) emit those classes — rendering
  as walls of glued, unstyled links ("aphrodite23 tables", "Event & message
  catalog181 events"). The component moved into the shared reader stylesheet;
  all five surfaces got styled cards for free, plus a proportional magnitude bar
  on schema cards.
- **Testing map** — the by-area table's counts became visible magnitudes: a
  single-hue bar per row scaled to the largest area, and test-target coverage as
  a fraction meter that only earns green at 100%.
- **Browse by owner** — the three ownership tiers (537 declared / 1,843
  git-fallback / 808 unowned) are now one stacked meter with a counts legend at
  the top of the page, and 75-package rosters fold behind an expandable count
  instead of burying every owner below them.
- **Coverage & health** — "pages clearing depth proxy" per product and the
  per-kind depth percentages render as fraction meters (green only at 100%),
  replacing bare "86/86 (100%)" text.
- **Domains / Systems / Disciplines indexes** — every count card carries a
  bottom-edge footprint bar scaled to the group maximum, so Iris at 271 tracked
  projects no longer reads the same as Concordia at 1.
- Pluralisation nit fixed en route ("1 table").

Confirmed already strong, deliberately untouched: repo map, lifecycle, platform
hub, capability matrix, start-here, systems dependency graph.

**Verification** — before/after screenshots of all 16 surfaces, 17 generator
pytests, freshness gate over 3,077 files, full reader suite, adversarial stub
grep (0 actionable in changed files).
