Disciplines · Audits

Docs Center — Audit & Suggestions for Improvement

1.

12sections31 minread

On this page

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 waysnyx/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.htmldocs/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 integritycheck_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 harnessplaywright.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).