Twinned from https://github.com/cfsh/ninefold/issues/961 by tools/beads/import_github.py's reconcile pass.
Turn the 005b/006a/007 backfill (design 008 T4, #939) into an executable spec for design_docs/tools/operationalize_design.py: commit each doc's before and after states plus the GitHub side-effects as testdata, and add golden tests asserting the script transforms each before-state into exactly the after-state — then fix the script until those tests are green. The after-states are the *hand-corrected* live outcomes, so green means resolving #941, #951, #956 and asserting #960's repo-link, not just snapshotting today's behaviour.
Why: the script's unit tests were green throughout the backfill, yet every bug filed against it (#951 header-wrap corruption, #956 doubled Project title, #941/#938 hardcoded-OPEN status) surfaced only when --apply hit the real docs. Synthetic fixtures encode the author's assumptions; the three real docs encode the actual variety — wrapped Refs: bullets, <id> — title H1s, pre-convention issues hand-filled in Issue cells. Golden before→after tests make "what should --apply do" a test, not prose in 008 §6, and defend every future fix against the next regression.
design_docs/tools/tests/testdata/<docid>/)One directory per doc — 005b/, 006a/, 007/ — each holding:
before.md — the doc as it stood immediately pre---apply: Issue column present but empty (—), no **Project:** header line. The backfill PRs (#942/#952/#957) squashed column-add + apply into one commit, so reconstruct mechanically from after.md by inverting exactly what --apply writes: blank the Issue cells, delete the - **Project:** line. (For 007, #957 added the whole task table, so before = table present, cells empty — same inversion.)after.md — the doc as merged on main today (commit 8d3e532 or later). Byte-exact copies, including 006a's *correctly wrapped* Refs: bullet (the #951 hand-fix) and the orgs/cfsh/projects/{4,5,6} Project URLs.github_before.json — the pre-existing GitHub state the run saw: every issue referenced by the doc's after-table that predates the run, with real number/title/state (fetch via gh issue view <n> --json number,title,state). This includes the convention-titled ones list_task_issues finds (e.g. #908, #875, #893, #878) and the pre-convention ones only reachable via a hand-filled Issue cell (e.g. 005b T6 → #756) — the latter is the #941 case, and its real state at seed time is what makes that test bite. Also carries the seed needed so the fake mints the real numbers (see below).github_after.json — the curated expected Project spec: title (the hand-corrected, un-doubled one — the #956 fix's target), owner cfsh, linked repo cfsh/ninefold (the #960 fix's target), and one item per task with issue number and initial Status.Curation rule (important): projects cfsh/4,5,6 are live state and have drifted since the backfill — statuses move and issues close *because the design works*. Fetch the current specs (gh project view/item-list, or GraphQL for the repo link) as the generation aid and reference, but curate the committed golden down to expected initial provisioning: Status is Done iff the seeded issue's state was CLOSED at apply time, else Backlog — the script's actual contract. Do not commit the raw fetch as the oracle; it asserts things --apply never did. Record the fetch commands + curation rule in a testdata/README.md so the goldens are regenerable.
Issue-number fidelity: each doc's minted range is contiguous (005b minted only #940; 006a minted #943–950; 007 minted #953–955), and FakeGh mints sequentially from max(seeded)+1. Run each doc against its own FakeGh seeded so the first minted number matches reality (a seed entry at first−1 suffices) — then after.md comparison can be byte-exact, no normalization layer.
design_docs/tools/tests/test_golden.py or similar)Per doc: build FakeGh from github_before.json → copy before.md to a tmp path → apply(gh, path, owner="cfsh") (prefer driving via main() without --owner where practical — that also closes #959's missing default-owner regression test) → assert:
== after.md byte-for-byte;github_after.json;apply() run against the result is a no-op (the idempotency contract, now against real-shaped data).Plus one synthetic --close golden (no doc has been closed yet, so no real data exists): start from 007's operationalized after-state with a mix of Done and non-Done items, run close(gh, "007"), assert every non-Done item → Deprioritized and the project closed. That puts all three modes under one harness.
Fake fidelity fixes are in scope where the goldens demand them: FakeGh.project_create currently builds users/{owner} URLs, but the real after-docs carry orgs/cfsh/... — the fake needs to produce org-shaped URLs for an org owner (and carry project_link state, which #960's FakeGh.project_link recorder already adds).
The goldens will be red against today's script by construction; fixing these makes them green (close each with Closes #n in the PR that fixes it):
elif row.issue is not None: branch hardcodes issue_state = "OPEN"; look up the real state instead. The 005b golden's #756 row is the failing case._stamp_project_header inserts mid-bullet when - **Refs:** wraps; advance past continuation lines first. The 006a golden's byte-exact after.md is the failing case.f"{doc_id} — {doc_title}" doubles the id since every H1 already carries it; the curated github_after.json titles are the target.Explicit non-goal: 008 §6's "Ready for whatever §0 flagged as land-first" initial-status clause is not implemented and not golden-encoded — the contract under test is Backlog/Done. If that clause is ever wanted, it's its own issue with its own design (parsing §0 prose is not mechanical).
testdata/ holds the three before/after doc pairs + curated GitHub before/after specs + regeneration README;--apply (×3, incl. idempotent re-run), --close (synthetic), all green in the default presubmit.py python lane, no live network;Design 008 §6/T3/T4 · backfill PRs #942 #952 #957 · tracking #939 · script PR #937 · follow-ups #938 #941 #951 #956 #959 · PR #960 · live Projects cfsh/projects/4,5,6 (005b/006a/007 respectively).
Unclaimed — sized and specified for a fresh session to pick up self-contained (this issue + what's on main).
None.
No comments.