Files
ersatztv/docs/decisions/records/startup/parallel-orientation.md
T
timothy fba5233caf
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Failing after 23s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m29s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 16m5s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m6s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
feat(610): split the decision corpus into one YAML-frontmatter file per record
168 records -> docs/decisions/records/<area>/<topic>.md (163 active, 23 dirs) and
docs/decisions/archive/<area>/<topic>.md (5 archived). The filename IS the key,
so one-active-record-per-key becomes a filesystem property rather than a
validator check, and supersession becomes a `git mv`.

WHY: the monolith was a concurrency problem before an aesthetic one. A
3,900-line append target made parallel sessions collide -- PR #605 and PR #614
both hit append-vs-append conflicts during routine rebases, and hand-resolving
those inside the corpus is exactly the operation the rationale-rewrite guard
exists to police.

HOW IT IS VERIFIED: a ~170-file diff cannot be meaningfully read, so correctness
does not rest on reading it. The parser was taught BOTH formats first, so the
body-diff guard parses the old form at the merge-base and the new form at head --
the migration validates itself, no bypass. The proof is a field-level equivalence
harness: 168 records before and after, zero lost, zero gained, zero field
mismatches, zero rationale bodies differing. Reviewers should scrutinise the
harness; it is the actual evidence.

What measuring caught that reading would not have:

- ~500 lines sit OUTSIDE any record -- decisions.md's lifecycle schema and each
  topic file's preamble, mostly the only copy. Source files are kept and
  stripped, never deleted. They also cannot be filed per-area: topic files hold
  several areas and 4 of 23 areas span several files.
- Archive discovery was a non-recursive glob; after the split it found ZERO
  archived records, surfacing as four bogus "supersedes points to unknown key"
  errors rather than an obvious failure.
- ~32 live docs point into the corpus BY DATE, which the split dangles. Each
  stripped file now ends with a generated "Records formerly in this file" index,
  which also rescues the identical breadcrumbs in old issue comments.
- decisions.md's "In this file:" list was 97 same-file anchor bullets that the
  split makes WRONG, not merely stale. Dropped; the generated index replaces
  them with links that resolve.

The equivalence harness now runs against a checked-in FIXTURE, not the live
corpus. The earlier version migrated the real tree, which made it a one-shot:
the moment the migration landed there was nothing left to move and the tests
failed for reasons unrelated to the code. A fixture keeps them testing the
SCRIPT rather than the repo's current state.

Keys preserved verbatim, warts included: `sched` (12) and `scheduling` (1) remain
two directories for one concept. Renaming a key is not a move -- it changes
identity, breaks the equivalence proof, and invalidates MemPalace's per-key
drawers. Taxonomy normalisation is separate work.

refs #610
2026-07-25 19:45:09 +02:00

4.7 KiB
Raw Blame History

key, title, status, since, supersedes, superseded-by, rule, signals, mechanics
key title status since supersedes superseded-by rule signals mechanics
startup.parallel-orientation 2026-07-21 — Parallel orientation + selection is the startup protocol; #237 retired (#520) active 2026-07-21 docs.queue-state-gitea-tracker@2026-07-11 none A fresh session runs two concurrent tracks at startup — Orientation (`AGENTS.md`/`CLAUDE.md` → `docs/README.md` task-signal map → the active decisions catalog `docs/decisions/README.md`) and, only when no issue is named, Selection (`scripts/select-queue.sh N`, deterministic live-Gitea ranking). A named issue skips Selection entirely. ersatztv#237, the closed pickup tracker this replaces, is reduced to a single archival breadcrumb and MUST NOT be read for live state. startup protocol, queue selection, MemPalace retrieval, breadcrumb rule · paths: `docs/handoffs/chicorytv-issue-queue.md`, `scripts/select-queue.sh`, `docs/README.md`, `scripts/check-kickoff-guard.sh` · issues: #237, #520, #521, server-management#642 `docs/handoffs/chicorytv-issue-queue.md` → "Two concurrent tracks at session start" + "Knowledge retrieval"; `scripts/select-queue.sh`; regression guard `scripts/check-kickoff-guard.sh` (wired into the `decisions lifecycle` CI job)

Why this reverses docs.queue-state-gitea-tracker (2026-07-11), not just extends it. That record's rule was "queue state lives in pinned tracker #237, not in the handoff file" — sound in 2026-07-11 when the arc was live and #237's body/comments were the only concurrency-safe place to put session state. Two things changed: (1) scripts/select-queue.sh (2026-07-19) already replaced the mechanical selection logic with a deterministic query over live Gitea state — no script reads #237's prose; and (2) the arc itself completed and #237 closed on 2026-07-13. A closed issue cannot be "where live queue state lives" — continuing to point agents at it was no longer just stale, it was actively wrong, and the old record's own text ("Pinned tracker issue #237 holds the goal + ordered arc") was live regression bait: a session reading it verbatim would try to treat a closed tracker as authoritative. So this is a genuine reversal (the store changes from "a specific Gitea issue" to "live Gitea queries, no pinned issue required"), not a mechanical follow-on.

What the new protocol is. Session start runs two independent tracks, neither blocking the other: Orientation — read AGENTS.md/CLAUDE.md, then docs/README.md's task-signal map (replacing the old mandatory 110 reading order), then the active decisions catalog docs/decisions/README.md for "why do we do X" questions. Selection — only when the user hasn't named an issue, run scripts/select-queue.sh [N], which excludes in-progress/parked/PRs, resolves GET /issues/{n}/dependencies, tiers by LOCAL .milestone.state/review/priority: filters, and orders deterministically; it flags CLAIM?/UMBRELLA? for the orchestrator to resolve by reading the flagged issue, and does not rank an arc tier (none is active in maintenance/backlog mode — if one is ever re-established, extend the script, never revert to prose-derived ranking). A user-named issue skips Selection outright and goes straight to focused retrieval on that issue.

MemPalace retrieval contract (server-management#642, the sole Gitea→MemPalace exporter). MemPalace is candidate discovery only, verified against its cited source before use, at a default ErsatzTV-Decisions wing; the four load-bearing orientation bullets — catalog-first for current rules, issue history as evidence not authority, the breadcrumb rule (a file path in a historical comment resolves by concept via the active wing, never by literal path-chase), and the catalog→rg exact-search fallback when MemPalace is stale/down — are carried verbatim in docs/README.md and docs/handoffs/chicorytv-issue-queue.md. Never derive live queue state from MemPalace, #237, or historical comments; live Gitea state via scripts/select-queue.sh is the only source of truth for pickup ordering. The ## Closing record template (Outcome / Root cause / Decisions changed / Reusable knowledge / Verification / Deferred / Docs updated) is the structured per-issue artifact this retrieval contract is built to consume — see CLAUDE.md → Task Completion Protocol and the kickoff's "Closing record" section.

Regression guard. scripts/check-kickoff-guard.sh greps the standing startup docs (docs/handoffs/chicorytv-issue-queue.md, docs/README.md, CLAUDE.md, scripts/select-queue.sh) for #237-as-live-state phrasings ("read #237", "tracker #237", "queue state lives in", …), exempting lines/sections that are explicitly archival. It is wired into the decisions lifecycle CI job so a future PR cannot silently reintroduce the reversed rule.