de677b231a623a7dfbd4a5eeefa8d44fef47d2b7
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
245086dad6 |
docs: the census total is a floor — 25 membership predicates it structurally cannot see, one a live defect (#2763)
Docs only, extending the entry #2748 landed. Opening it because the fleet reads the census total as its completion bar, and that total excludes a whole predicate class — a measurement that should not live in a chat reply. ## Measured on `origin/main` - **47** array/Set literals of two or more lifecycle ids, in 35 files. - **25 are membership predicates against a task's column** — `SET.has(task.column)` / `ARRAY.includes(task.column)` — in 19 files. Two are documented fallbacks behind a resolved primary, so **~23 are unconverted guards**. - The census scans `===` / `!==` against a column. **None of these is a comparison, so none is counted.** | file | constant | | --- | --- | | `cli/src/commands/task.ts` (3) | `retryReviewColumns` | | `dashboard/app/components/TaskCard.tsx` (2) | `TIME_INDICATOR_COLUMNS` | | `engine/src/eval-followups.ts` (2) | `OPEN_COLUMNS` | | `engine/src/merger.ts` (2) | `sourceTerminal` | | `engine/src/task-revert.ts` (2) | `REVERTABLE_COLUMNS` | | `core/src/agent-role-policy.ts` (1) | `IMPLEMENTATION_TASK_COLUMNS` | ## One is a proven live defect `isImplementationTask` is `IMPLEMENTATION_TASK_COLUMNS.has(task.column)`, and `evaluateImplementationTaskBind` short-circuits to `allowed: true` when it returns false. **On a renamed board every agent is bind-compatible with every task** — the role check that stops a liaison being handed implementation work (the NEXT-871 loop FN-7851 fixed) does not apply. It surfaced only because a reviewer questioned a coverage claim in one of my dispatch tests (#2739). Passing an agent wasn't proof the evaluator ran, so I asserted a `custom`-role agent must be *refused* — and that test failed against production. Flagged at the site in #2739, not fixed: `isImplementationTask` is a sync pure predicate with no store, and making the routing policy async is a behaviour change to agent admission. ## What this does and does not argue The census is the right instrument — AST-based, honest about what it measures, and it has caught real drift in both directions (it failed on me in #2724 when merged conversions moved an inventory *down*). This is not an argument against it. It is an argument against reading **"backlog: N" as "N guards remain"**. The same shape already appeared in the archived gate (#2724), where the rule is additionally encoded in Drizzle predicates and raw `sql` templates that no comparison scan can see. Two independent classes now, found the same way — by looking at what the instrument's definition excludes. **Extending the census to count membership predicates is deliberately left to you, not done here.** It would move every worker's number mid-fleet, and deciding which sets are lifecycle guards versus board-config definitions or type unions is exactly the judgement `DELIBERATE-LITERAL` exists for — 47 collections would each need that call. ## Verification `pnpm lint` clean · census `--strict` exits 0 · no code changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1322a1bb11 |
docs(solutions): the optional-flags seam kept four green suites blind to their own conversion — and why I did not ship a ratchet for it (#2748)
Docs only. No code, no census movement. ## The finding, measured Four consecutive files in this program had **fully green suites at conversion time** that could not have detected the conversion — correct or broken: | file | pre-existing cases blind to the change | | --- | --- | | `github-tracking-reconciler.ts` | **33** (fake store had no workflow reader) | | `TaskReviewTab.tsx` | **45** (`columnFlags` omitted everywhere) | | `plan-approval-hold-invariant` drain | **25** (`opts.lifecycle` omitted everywhere) | | `task-age-staleness.ts` | **12** (`context.lifecycle` omitted everywhere) | The cause is structural. Every conversion here uses the same seam — the caller passes resolved flags, the helper falls back to the legacy id when they are absent — and every pre-existing test omits the flags. So the suite passes **before** the conversion, **after a correct one**, and **after a wrong one**, as long as the fallback is intact. "The suite is green" carries no information about the change. I reported this observation four times in PR bodies. Restating it a fifth time is worth less than writing it where the next worker will actually find it. ## It also corrects the obvious test The natural property is "hold the traits fixed, change the id, behaviour is identical". That is only half the invariant. It does not catch: ```ts // Not a fallback — an OVERRIDE. The id wins even when traits disagree. return column === "in-review" || flags?.mergeBlocker === true; ``` Renaming `in-review` → `checking` leaves that correct, because the trait arm answers. The defect appears in the **converse** direction — a column that still *carries* a lifecycle name while its traits say otherwise, which is what you get by repurposing a default column rather than renaming one. That is the direction that found a live **"Merge & Close" offered on a mid-implementation card** in #2718. ## Why this is not a ratchet — a negative result, recorded I tried to automate it, and I am shipping the reason it failed rather than a guard I do not trust. The **consumer scan is sound**: AST-based, 31 files, 66 role-helper call sites. The **coverage half is not**. The renamed ids this program uses — `building`, `checking`, `converted`, `published`, `backlog` — are ordinary English words that appear in unrelated test prose, and a test merely *importing* the module under test does not prove it exercises the role path. My scan reported `TaskCard.tsx` as covered by `Column.test.tsx` on a **filename coincidence**. A guard built on that reports coverage that does not exist, which is worse than no guard, so it is not shipped. A sound alternative — pin the consumer set and make each new file declare its status — was also rejected: a 31-entry status inventory would conflict with every concurrent fleet PR that adds coverage. That is the same churn already removed from the census baseline by dropping its derived aggregates. The attempt is written down so the next person does not repeat it from scratch, and the requirement lives as a review criterion until someone finds a sound signal. ## What it asks for 1. **A flags-supplying case** — if every case omits the new parameter, the conversion is untested in both directions. 2. **Both directions where both are reachable** — renamed lane, and repurposed column. 3. **A non-vacuous companion** — assert what the widened predicate must still *exclude*, or a predicate matching every column satisfies your new cases. (Both `TaskReviewTab` and the dispatch filters needed this.) 4. **Run the revert and record the failure text.** Twice in this program a new case passed with the change reverted: once because the branch was gated behind an unwired handler (`refine` needs `onOpenRefine`), once because the hook was dispatched by trait and the test IR did not declare that trait, so it never ran at all. Cross-linked both ways with the adjacent `store-fake-defects` entry, with the distinction stated so the two are not confused: **there** a fake is missing a method so a branch never runs and production looks wrong; **here** the fake is complete and the test is correct, but a parameter is absent so production takes its documented fallback. ## Verification `pnpm lint` clean · census `--strict` exits 0 (unmoved — this PR changes no code). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6d10683dbd |
docs(solutions): store fakes that lie — six fixture defects that each looked like a production bug (#2534)
Six consecutive slices of U7 produced **six test-fixture defects, and every one first presented as a bug in the code under test.** Not one was real. Each cost 15–60 minutes debugging the wrong file. **Two would have shipped a false green** — a test passing while asserting nothing — if the failure had happened to look plausible rather than implausible. This is not a story about carelessness. Every one of these fakes was modelled on an existing fixture in this repo, and the repo's fixtures are inconsistent about exactly the things that matter. ## The catalogue | # | Defect | How it presented | Real cause | |---|---|---|---| | 1 | `moveTaskIf` ignores its predicate | Test passed; in-txn guard untested and indistinguishable from absent | Fake never invoked the callback | | 2 | `updateTaskAtomic: vi.fn()` never invokes its callback | *Every* finalize bailed before the branch under test | Success is derived from whether the callback ran | | 3 | Harness default parameter swallows the input | "Task vanished" case became a duplicate of the control | `harness(undefined)` triggers the default | | 4 | `logEntry: vi.fn()` returns `undefined` | Sweep appeared to match only one column | `.catch` on a non-promise throws, aborting the loop after item one | | 5 | Harness lets `poll()` reach the real `specifyTask` | **exit 1 with every test green** | Real agent path threw *asynchronously*, after assertions passed | | 6 | `updateTask: vi.fn()` returns `undefined` | Branch "did not run" | Same as #4 | **4 and 6 are the same shape, found a week apart, because nothing prevented the second.** That is the argument for writing this down. ## The three rules 1. **Every store method a fake exposes returns what the real one returns** — overwhelmingly a promise. Production writes `await store.m(...).catch(h)` as a fail-soft idiom; `.catch` on `undefined` throws a `TypeError` that unwinds into a broad *"never let housekeeping break the poll"* handler and vanishes. Symptom is never "your fake is wrong" — it is *"the loop only processed the first item"*. 2. **A fake handed a predicate or callback must invoke it.** Ignoring it makes the guarded and unguarded implementations *indistinguishable*, so a test named for the guard cannot detect the guard's removal. Includes the `onLockedRead` hook, without which an in-transaction recheck stays untestable even once the predicate is invoked. 3. **Stub the agent-dispatch boundary.** `poll()` ends in "start an agent", which in a unit test throws *after* the test resolved — `17 passed`, exit code 1, which on CI reads as infrastructure noise. > Never accept a non-zero exit on a green run. It is the only signal that something escaped your assertions entirely. ## Also covered - **How to spot a fixture defect fast** — the tell is *failing for the wrong reason*. Three concrete checks before you open the production file. - **Why differential tests earn their keep** even when they feel redundant: the default-vocabulary half doubles as a fixture self-check, because it asserts behavior that is by definition already shipping. On this program, "both halves failed" was the signal that found three of the six. - **The connection to guards that cannot fire** — six of those on this program too, including a ratchet I wrote that matched only a double-quoted literal (#2527). Same discipline either way: *prove the check fails on the thing it claims to catch before trusting that it passes.* Including the warning that one ratchet injection silently failed to apply, leaving a green run that would have "proven" the ratchet worked. ## The concrete next step, stated plainly A shared `createTaskStoreFake({ tasks, workflowIr })` with promise-resolving, callback-invoking defaults would remove this whole class in one small PR. **It is not built here** because it is cross-unit and needs adopters — building it inside U7 and hoping others find it is how conventions die. The doc says: if you are about to hand-roll a seventh store fake, build the helper instead and link it. Docs-only; no changeset (AGENTS.md excludes internal docs). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added guidance on six store-fake defect patterns that can resemble production bugs during testing. * Documented best practices for creating reliable store fakes, including promise handling, callback invocation, and async dispatch isolation. * Added diagnostic techniques for distinguishing fixture issues from genuine application defects. * Included guidance for validating production guards and links to related documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
66d829abed | docs(solutions): schema-version literal sweep must include plugin workspaces |