# Scan problems — mod-cms user journeys

Audience: AI coding agents first.

Write-ahead log. Append findings the moment evidence surfaces them. Classes: `missing-evidence`,
`source-conflict`, `implementation-defect`, `missing-fixture`, `missing-fault-seam`,
`coverage-omission`.

## Blockers and findings

| ID | Class | Journey | Finding | Evidence | Resolution | Status |
|---|---|---|---|---|---|---|
| SP-001 | missing-fault-seam | UJ-001 | No post-commit response-loss fault seam exists in the mod-cms e2e harness. `POST /api/install` writes `auth_users` and flips `site_configured` before emitting its `303`; a lost response after that commit leaves the operator with a form error while the site is already live and claimed. The harness runs a real dev server with no transport-fault injection. | `apps/mod-cms/playwright.config.ts:37-44`, `apps/mod-cms/src/pages/api/install.ts:95` | Add a transport-fault seam that drops the response after the authoritative commit (route interception in Playwright, or an injectable transport in the install wizard client). | open |
| SP-002 | missing-fault-seam | UJ-001 | No concurrency barrier seam exists to interleave two `claimInstall` racers. The lease is a single atomic `INSERT … ON CONFLICT DO UPDATE … RETURNING`, so exactly one racer wins — but the harness cannot hold two in-flight installs at the claim statement to prove it. Playwright runs `workers: 1`. | `apps/mod-cms/src/lib/install.ts:159-168`, `apps/mod-cms/playwright.config.ts:32` | Add a concurrency barrier (two parallel browser contexts released at the claim, or a direct two-connection integration test against real Postgres). | open |
| SP-003 | missing-fault-seam | UJ-001 | No fault seam can produce the partial-install state the code explicitly defends: claim won → `createUser` committed → `finalizeInstall` fails. Reaching the `createUser` 409 `conflict` guard requires a pre-existing `auth_users` row on a site whose `site_configured` is still `claiming`. The install fixture excludes it (fresh branch, no admin). Same gap blocks the settings-write catch and the post-install `signIn` catch. | `apps/mod-cms/src/pages/api/install.ts:86`, `apps/mod-cms/src/pages/api/install.ts:112`, `apps/mod-cms/src/pages/api/install.ts:122` | Add an injectable write-fault seam between `createUser` and `finalizeInstall`, then author the partial-commit branch. | open |
| SP-004 | coverage-omission | UJ-001 | The SQLite/D1 dialect path of `POST /api/install` is untraced. The ops-gated install fixture runs the Postgres dialect only, so `applyD1Schema` and the SQLite `json_extract` claim variant carry no journey evidence — despite D1 being the documented zero-external-DB default. | `apps/mod-cms/src/pages/api/install.ts:71`, `apps/mod-cms/src/lib/install.ts:153-158`, `apps/mod-cms/e2e/install-appearance.spec.ts:4-7` | Author a sibling journey (or a dialect-parameterized fixture) covering the D1 install path. | open |
| SP-006 | source-conflict | UJ-001 | The validator forbids the word `or` in a `Response` field (to stop `400 or 429` hedging), but the endpoint's real 409 body literal is “Setup is already in progress or complete.” — a legitimate literal the rule cannot express. Resolved without falsifying: A3/A4 pin status + error code in `Response` and the exact message literal in `Visible`, where the actor observes it. Nothing asserted is untrue, but the body literal is split across two fields. | `apps/mod-cms/src/pages/api/install.ts:77`, `~/.claude/skills/create-journeys/scripts/validate-journeys.mjs:246` | Narrow the validator's `or` check to status-hedging patterns (e.g. `\d{3}\s+or\s+\d{3}`) rather than any prose `or`, then re-inline the full literal. | open |
| SP-007 | source-conflict | N/A — catalog-level | The `create-journeys` skill was rewritten mid-run (SKILL.md 16:51:50, validator 16:53:04, run in progress). The loaded contract required `## Reachability proof` / `## Trace ranges` / `## Guard coverage` / `## Risk coverage` and per-branch `Commit state` + `Risks`; the on-disk contract requires `## Path` and nine branch fields. UJ-001 was authored twice and conforms to the **on-disk** contract, which the gate enforces. | `~/.claude/skills/create-journeys/SKILL.md`, `~/.claude/skills/create-journeys/scripts/validate-journeys.mjs` | If further skill edits land, re-run the gate for the whole catalog before authoring more journeys. `references/complete-journey-example.md` (11:24) still shows the OLD contract and will fail the current gate — it needs regenerating. | open |
| SP-005 | implementation-defect | N/A — inventory-level | The boot gate is gated on `cfEnv?.DATABASE_URL`. A D1-only deployment (the documented default: `env.DB` binding, no `DATABASE_URL` secret) never runs `runBootGate`, so an unconfigured D1 site does not redirect to `/install` and serves the public site instead of onboarding. Suspected — not yet proven against a running D1 worker. | `apps/mod-cms/src/middleware.ts:25`, `apps/mod-cms/src/lib/db.ts` | Gate the boot check on database availability, not on the Postgres-specific `DATABASE_URL` secret; then prove with a D1 fixture. | suspected |

## Outcome-inventory omissions

Every outcome discovered in the scan that has no journey yet, with its one-clause reason.

| Surface | Outcome | Class | Reason |
|---|---|---|---|
| `POST /api/contact` | Anonymous visitor submits the contact form and the submission persists. | coverage-omission | Ranked below UJ-001 (multi-step durable side-effect, no ownership boundary); next journey to author. |
| `POST /api/comments` | Anonymous visitor posts a comment on a published post. | coverage-omission | Ranked below UJ-001; carries a known open defect (no error/repopulation flash path, `apps/mod-cms/AGENTS.md` learned rule `mod-cms-comment-form-no-error-flash`). |
| `POST /api/admin/session` | Admin authenticates and reaches the admin dashboard. | coverage-omission | Authn boundary — high rank; deferred to the next authoring pass. |
| `POST /api/admin/publish`, `unpublish`, `schedule`, `visibility` | Editor/admin transitions a content entry between draft, scheduled, and published. | coverage-omission | Durable state machine with role boundaries — high rank; deferred to the next authoring pass. |
| `POST /api/admin/users`, `users/[id]/roles`, `users/[id]/disable` | Admin grants, revokes, or disables another principal's access. | coverage-omission | Ownership/authority boundary — high rank; deferred to the next authoring pass. |
| `POST /api/admin/remove`, `restore`, `trash` | Admin soft-deletes and restores a content entry. | coverage-omission | Deferred; exactly-once semantics not yet traced. |
| `POST /api/admin/media`, `DELETE /api/admin/media/[...key]` | Admin uploads and deletes a media asset against the blob store and its quota. | coverage-omission | Deferred; quota is a conservation boundary worth its own journey. |
| `POST /api/admin/backup` | Admin exports site data. | coverage-omission | Deferred. |
| `POST /api/admin/privacy` | Admin exports or erases a subject's personal data. | coverage-omission | Deferred; irreversible erasure warrants its own journey. |
| `POST /api/admin/service-tokens` | Admin mints an agent bearer token. | coverage-omission | Deferred; credential issuance warrants its own journey. |
| `POST /api/admin/comments`, `discussion`, `submissions` | Admin moderates comments and reads form submissions. | coverage-omission | Deferred. |
| `POST /api/admin/settings`, `menus`, `fields`, `notifications/read`, `content/translate` | Admin edits configuration, menus, field groups, notifications, and translations. | coverage-omission | Deferred. |
| `/`, `/post/[slug]`, `/blueprint/[slug]`, `/module/[slug]`, `/search`, `/ui-kit`, `/404`, `/500`, `/privacy`, `/maintenance`, `/install/preview` | Read-only rendering of public and reference pages. | coverage-omission | Route-sweep and ui-matrix passes own per-page render assertions; sole outcome is "page renders", so no journey is earned. |
| `/cron-arm` | Scheduled-publish alarm is armed after deploy. | coverage-omission | Secret-gated infra route with no actor UI entry; not an actor goal. |
