docs(process): #303 follow-ups — Codex-skip rubric + write-path live-E2E requirement
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Two of the four non-hook #303 process follow-ups (the two docs items; the security scan and migration-on-prod-copy smoke are deferred to their own sessions): 1. Codex-skip rubric (kickoff workflow lore): an independent review pass is MANDATORY for diffs touching locks/concurrency, auth/security, API write-path handlers, or migrations, or >~150 changed C# lines; skippable only for a pure-SPA/docs leaf, and a skip must be stated + justified. Makes self-exemption an auditable claim (the correlated-blindspot net). 2. Live-E2E is now a STATED REQUIREMENT for API write-path handler changes: new "When live-E2E is required" section in docs/e2e-local.md + a decisions.md entry, formalizing the #229 lore bullet. The seeding recipe was already in e2e-local.md (added for #220), so the stale "recipe not yet in docs" lore bullet is pruned to a pointer. Docs-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,7 @@ keep append-only from accreting stale, contradictory, or unreadably-large histor
|
||||
- [2026-07-12 — decisions.md is append-only, enforced; root-screenshot guard (#303 H9/H3)](#2026-07-12--decisionsmd-is-append-only-enforced-root-screenshot-guard-303-h9h3)
|
||||
- [2026-07-12 — Review-verdict merge-gate: latest commit must be reviewed (#303 H10)](#2026-07-12--review-verdict-merge-gate-latest-commit-must-be-reviewed-303-h10)
|
||||
- [2026-07-12 — Cross-editor ETag rotation completed for Collection/Playout config siblings (#269)](#2026-07-12--cross-editor-etag-rotation-completed-for-collectionplayout-config-siblings-269)
|
||||
- [2026-07-12 — Live-E2E is a required step for API write-path handler changes (#303)](#2026-07-12--live-e2e-is-a-required-step-for-api-write-path-handler-changes-303)
|
||||
|
||||
---
|
||||
|
||||
@@ -1410,6 +1411,7 @@ Scope: the Claude PreToolUse gate on the Gitea merge tool only. A direct `git pu
|
||||
PR comments to check, so the `.husky/pre-push` backstop is not extended for H10 (the merge tool is the
|
||||
real merge path; docs-only PRs remain exempt via H6's file-set exemption). Rationale, as with the whole
|
||||
Wave-1/2/3 hook set: make the process rule a derivation/hook, not prose to remember (#303).
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-12 — Cross-editor ETag rotation completed for Collection/Playout config siblings (#269)
|
||||
@@ -1465,3 +1467,22 @@ for Add/Remove/Update) and `PlayoutScheduleFileEtagRotationTests` (ScheduleFile
|
||||
the no-op guard proven non-vacuous by inverting the membership check. The `#265` RFC-7232 If-Match parser
|
||||
refinement (valid-but-non-matching/weak/list → 412 not 400) is a **separate** PR (disjoint surface: the shared
|
||||
parser + `CheckVersion`, not the handler saves). Refs #253 #269 #197 · `api-conventions.md` §7a.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-12 — Live-E2E is a required step for API write-path handler changes (#303)
|
||||
|
||||
**A PR that changes an API write-path handler (a `POST`/`PUT`/`DELETE` `/api/*` command that mutates
|
||||
state and reloads it through the read path) MUST include a live-E2E pass** — driving the real endpoint
|
||||
or its SPA screen and confirming the mutation round-trips through a subsequent read — not only unit /
|
||||
characterization tests. Rationale: this class has a **correlated blind spot** unit and characterization
|
||||
tests share. A green fixed-point test passed while the write-path returned a production 500 because the
|
||||
handler returned a *lazy* LanguageExt `Map` the test never enumerated (#229; reload-through-read-path
|
||||
mechanics in `api-conventions.md` §7); the failure surfaces only when the result is materialised, which
|
||||
the SPA does and the test did not. Live driving is the only reliable net for it.
|
||||
|
||||
Non-write-path (pure-SPA/read-only) and docs PRs don't need it. The requirement is auditable, not
|
||||
silent: the PR/close comment states that live-E2E ran, or — for a non-write-path change — that it
|
||||
wasn't required (the same stated-exemption discipline as the review skip rubric). Recipe +
|
||||
"When live-E2E is required": `docs/e2e-local.md`. This formalizes the #229 lore bullet ("live E2E
|
||||
remains the only net for this class") into a standing convention.
|
||||
|
||||
@@ -7,6 +7,24 @@ for manual or Playwright-MCP-driven end-to-end verification — no live Docker/p
|
||||
This is for interactive/agent-driven verification, not CI (`docs/ci-cd.md` covers the CI pipeline,
|
||||
which never runs the app itself).
|
||||
|
||||
## When live-E2E is required (not optional)
|
||||
|
||||
A live-E2E pass through this recipe is a **required** step — not a nicety — for any PR that changes
|
||||
an **API write-path handler**: a `POST`/`PUT`/`DELETE` `/api/*` command that mutates state and then
|
||||
reloads it through the read path. Reason: this class has a **correlated blind spot** that unit and
|
||||
characterization tests share. A green fixed-point test passed while a write-path returned a 500 in
|
||||
production because the handler returned a *lazy* LanguageExt `Map` the test never enumerated (#229,
|
||||
PR — see `docs/decisions.md` and `api-conventions.md` §7); the reload-through-read-path mechanics can
|
||||
throw only when the result is actually materialised, which the SPA does and the test did not. Live
|
||||
driving the real screen (or `curl`ing the real endpoint) is the only net that reliably catches it.
|
||||
|
||||
Concretely, for a write-path PR: stand up the instance (Steps below), exercise the changed
|
||||
create/update/delete flow against the real endpoint or its SPA screen, and confirm the mutation
|
||||
**round-trips through a subsequent read** (list/detail/browse) — not just that the write returned 2xx.
|
||||
Pure-SPA/read-only or docs PRs don't need it. State in the PR/close comment that live-E2E ran (or,
|
||||
for a non-write-path change, that it wasn't required) — the same auditable-exemption rule the review
|
||||
skip rubric uses (kickoff workflow lore).
|
||||
|
||||
## Why the steps are in this order
|
||||
|
||||
- **`ErsatzTV/Startup.cs`'s `/app` SPA middleware resolves its static-file root once, at startup**
|
||||
|
||||
@@ -118,9 +118,10 @@ HARD CONSTRAINTS:
|
||||
- **"Make X consistent with Y" review findings deserve new-code scrutiny** — a #216 review fix
|
||||
propagated MediaBrowseScreen's mount-debounce selection-wipe race to SearchScreen instead of
|
||||
catching it. For any timer/effect, ask "when does this fire?" (incl. mount).
|
||||
- Live E2E seeding: the local library isn't API-seedable; the #216 E2E agent generated tiny
|
||||
ffmpeg testsrc MKVs + inserted LibraryPath rows via SQL then scanned. Recipe not yet in
|
||||
docs/e2e-local.md — worth adding next time it's needed.
|
||||
- Live-E2E seeding recipe now lives in `docs/e2e-local.md` → "Seeding a local TV library for E2E"
|
||||
(tiny ffmpeg testsrc MKVs + `LibraryPath` SQL rows + scan). Live-E2E is a **stated requirement**
|
||||
for write-path handler changes — see that doc's "When live-E2E is required" + the decisions.md
|
||||
entry; it's the only net for the lazy-enumeration / reload-through-read-path 500 class (#229).
|
||||
- **Parallel sessions (2026-07-11 protocol)**: claim before working (`in-progress` label — the
|
||||
tiny read→claim race window is accepted; later claimant backs off). Claiming prevents
|
||||
duplicate pickup, NOT overlapping code changes — check the tracker's dependency notes
|
||||
@@ -140,6 +141,14 @@ HARD CONSTRAINTS:
|
||||
stuck-diagnosis second opinions) via `codex:rescue`, never for implementation. Check
|
||||
readiness/quota with `/codex:setup`; on exhaustion fall back to fork/subagent review and
|
||||
retry next window.
|
||||
**Skip rubric (self-exemption is an auditable claim, not a vibe)**: an *independent* review pass
|
||||
(Codex, or a fork/subagent when Codex is exhausted) is **mandatory** for any diff touching
|
||||
locks/concurrency, auth/security, API write-path handlers, or DB migrations, or larger than
|
||||
~150 changed C# lines. It is **skippable** only for a pure-SPA/docs leaf with no server-state
|
||||
effect — and when you skip it, **say so and why** in the PR or close comment (a stated exemption,
|
||||
not silence). This is the correlated-blindspot net: the fork and Codex miss *different* classes
|
||||
(see the #235 "enumerate every producer" and #250 lessons), so a single reviewer on a risky diff
|
||||
is the gap.
|
||||
- **Force enumeration of lazy LanguageExt returns in tests** (2026-07-11, PR #229): a write-path 500
|
||||
survived a green fixed-point test because the handler returned a lazy `Map` the test never enumerated.
|
||||
Enumerate handler return values in tests; live E2E remains the only net for this class. (Write-path
|
||||
|
||||
Reference in New Issue
Block a user