# Resolution Ledger — codebase-analysis-2026-07-01

Date: 2026-07-01 · Driver: `/rpi resolve everything` over the 13 audit findings + the flagged human-decisions.

Every finding is driven to a terminal state: **FIXED** (implemented + verified), **DEFERRED**
(consciously not done now, with reason + follow-up), **DECLINED** (won't-fix, with reason), or
**HUMAN** (needs Bo's call). Deterministic verification for the FIXED set is at the bottom.

## Ledger

| # | Finding (audit ref) | Disposition | Evidence / reason |
|---|---|---|---|
| 1 | Next.js on CSP-nonce XSS advisory (Sec/High) | **FIXED** | `next` `^16.2.4`→`^16.2.9`; GHSA-ffhc-5mcf-pf4q range is `≤16.2.5`. `npm audit --omit=dev --audit-level=high` now clean. |
| 1b | No CI audit gate | **FIXED** | `.github/workflows/ci.yml` — added `npm audit --omit=dev --audit-level=high` step in `validate`. |
| 2 | Home JSON-LD bypasses nonce + escaper (Sec/Correctness) | **FIXED** | `src/app/page.tsx` → `<JsonLdScript>`. Prod-verified: script nonce == CSP header nonce. |
| 3 | Per-request MDX recompile via `await connection()` (Perf/Med) | **PARTIAL** | `getPostBySlug` wrapped in React `cache()` (dedupes the metadata+page double-read). `connection()` KEPT — removing it breaks the per-request nonce model (static HTML would embed a build-time nonce that fails `strict-dynamic`). Deeper cross-request MDX-compile memo deferred. |
| 4 | Sitemap `spawnSync('git log')` "~9× per request" (Perf/Med) | **DECLINED** | Build output shows `○ /sitemap.xml` = **static** → git runs at BUILD, not per request. Not a runtime cost. Also Bo has uncommitted WIP in this file. |
| 5 | 7 copy-paste OG images (DRY/High) | **FIXED** | 6 uniform ones (`about, ai-partner, training, work, method, writing`) → `renderProofOg(...)`. Bespoke hero `src/app/opengraph-image.tsx` intentionally left. ~270 LOC deleted. |
| 6 | ~100 hardcoded ramp-hex literals (DRY/High) | **DEFERRED** | The bulk live in `src/components/construct/primitives.tsx`, which has **uncommitted WIP** — touching it now would clobber Bo's work. Best as its own focused reskin PR once WIP lands. |
| 7 | Mobile nav in keyboard tab order when closed (A11y/Med) | **FIXED** | `Header.tsx` — `inert={!isOpen}` on `#mobile-menu`. |
| 8 | Low-contrast accent metadata text (A11y/Med) | **FIXED** | `writing/[slug]/page.tsx` date/reading/tags line `rgba(var(--acc),0.6)`→`0.8` (≥4.5:1). |
| 9 | `img-src` allows any `https:` (Sec/Med) | **FIXED** | `constants.ts` → `https://covers.openlibrary.org` (the only external image origin, per `next.config.ts`). No external images in content. Prod-verified in CSP header. |
| 10 | Title/desc repeated ×3 across pages (DRY/P2) | **DEFERRED** | Pure DRY, zero behavior change, lowest value; full migration churns 10+ files incl. Bo's WIP (`ai-partner/page.tsx`) and the case pages just edited. Not worth the regression surface on a live site now. |
| 11 | Author node ×5, envelope ×3, case pages missing JSON-LD (DRY + SEO) | **FIXED** | `jsonld.ts` — `AUTHOR_NODE` + generic `wrapJsonLdGraph<T>()`. Added `TechArticle`+`Breadcrumb` JSON-LD to the 4 case pages that had none (real SEO gain). |
| 12 | API response envelope inconsistent (API/Low) | **DECLINED** | Changing `{count,posts}` / raw-post / `{status}` shapes is a breaking change to a public JSON API for Low value. Did the safe part only (DRY'd the cache header into `CACHE_PUBLIC_1H_1D`). |
| 13 | `StatRow`≈`Stats`; cache literal ×3 (DRY/P2) | **FIXED** | `StatRow` deleted (1 caller → `<Stats cols={4}>`); `CACHE_PUBLIC_1H_1D` constant. Note: dify stat-number 26px→24px (Stats canonical). |
| — | Node engines `24.x` vs CI/Dockerfile `node:20` (Dockerfile self-labeled "Next 15") | **HUMAN** | Vercel is canonical deploy; the Dockerfile may be dead metadata. Fix to 24/Next 16 or delete — Bo's call. Untouched. |
| — | Coverage thresholds low (stmts 14/branches 12) | **HUMAN** | Intentional for a personal site, or accidental erosion? Bo's call. Untouched. |
| — | js-yaml moderate DoS (transitive under gray-matter) | **NOTED** | Moderate, trusted-frontmatter input, needs replacing gray-matter to fix. Below the `high` CI gate. Left as-is. |

## Not touched (Bo's uncommitted WIP — no collision)

`src/app/sitemap.ts`, `src/app/ai-partner/page.tsx`, `src/components/construct/primitives.tsx`,
and everything under `career/` / `first-win/` / `outputs/` were modified by Bo before this run and
were deliberately left alone. My OG-image edit to `ai-partner/**` touched `opengraph-image.tsx`, NOT
his `page.tsx`.

## Deterministic verification (the FIXED set)

- `npm run type-check` — clean
- `npm run lint` — clean
- `npm test -- --run` — **201/201 pass** (incl. updated `constants.test.ts`)
- `npm run build` — succeeds; all changed routes compile; `sitemap.xml` static
- `npx next start` + prod curl — all changed routes 200; OG images `200 image/png`;
  home JSON-LD renders via `next/script` with **nonce matching the CSP header**;
  CSP `img-src` narrowed as intended
- `npm audit --omit=dev --audit-level=high` — clean (the CI gate will pass)

## Adversarial review (fresh-context, independent)

Verdict: **No Critical/High/Medium regressions — the diff is clean.** All 8 change-groups verified
against the `git HEAD` originals: JSON-LD output byte-identical (key order + values), nonce path
correct, `cache()` proven a passthrough outside render by the existing draft tests
(`blog.test.ts:227/245`), no `AUTHOR_NODE` mutation, OG label/statement/subline/alt/colors preserved,
case-page headlines === metadata titles, `img-src` host is the only remote origin.

One Low note: the 6 migrated OG cards' statement column widened `980`→`1000px` (unified onto the
shared renderer that the 5 `work/*` cards already use) — cosmetically negligible for the current
short statements; could shift a line-wrap only on a much longer future statement. Left unified for
consistency across all OG cards. The shared `AUTHOR_NODE` is now `Object.freeze`'d to close the
latent cross-builder-mutation risk the review flagged.

## Not committed

Conservative default — no commit/push authority was granted, and Bo's WIP is intermixed in the
working tree. Changes are staged in the working tree for his review.
