docs(core): resolveReviewColumns is the BROAD set — and one of its consumers must NOT migrate onto it (#2750)
## A flaw in the helper I merged in #2730 Found by trying to do the migration I had been advocating for three rounds. One **name** was answering two questions: | | question | answer | |---|---|---| | **broad** | "is this card in a lane where review happens?" | every `mergeOrchestration` lane + every `mergeBlocker`/`humanReview` lane — **this function** | | **narrow** | "is this card in *the* review lane the engine acts on?" | `resolveLifecycleColumns().review` = `columnsWithFlag(ir, "mergeOrchestration")[0]` — **one** lane | The narrow answer is what the executor, the scheduler and `project-engine` act on. **A caller that admits on the broad set and then MOVES the card moves cards the engine does not consider in review.** ## The correction I owe I have been arguing across #2722, #2723 and #2728 that the inline review unions should converge on this helper. For the notifier that is right — over-admission there just means an extra notification. For `register-task-workflow-routes.ts` it is **wrong**. That resolver is deliberately narrower (#2723): its re-engagement *moves* the card, so admitting a second merge lane is a state change the engine will not agree with. Its local copy is **not drift from this helper — it is the other question.** Migrating it would reintroduce precisely the over-admission that PR's review round reasoned away. I was about to make that change. Reading both implementations side by side is the only thing that stopped me, and "consolidate the duplicates" would have looked like an obvious cleanup to the next person too. ## What this PR does Nothing to behaviour. It writes the distinction down **at the helper**, where a consumer reaching for "the review columns" will see it, and pins the difference with a test. The test needs a board declaring `mergeOrchestration` **twice** — no default lineage does, which is exactly why the two answers look identical everywhere else and why the conflation survived review. **Mutation: narrowing this helper to the first merge lane — the consolidation someone would reasonably attempt — fails the test.** ## Verification 30 trait tests green · core `tsc` clean · lint clean (0 errors) · gate green (487 + 158 + 10 + 71). No census movement. ## Not done here Migrating the notifier and the CLI copies onto this helper. Those genuinely should converge, but both live in open PRs (#2722, #2728/#2736) with live review threads; switching them under their authors mid-flight is worse than letting them adopt it once this distinction is documented. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified the distinction between broad review-capable lanes and the workflow’s primary review lane. * Documented how review lane selection affects workflow state handling. * **Tests** * Added coverage confirming that review detection includes all matching merge lanes. * Verified lifecycle review selection continues to use only the primary merge lane. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -405,6 +405,32 @@ describe("resolveReviewColumns", () => {
|
||||
expect(resolveReviewColumns(ir([{ id: "building", traits: [{ trait: "wip" }] }]))).toEqual([]);
|
||||
});
|
||||
|
||||
it("is BROADER than `.review` on a board with two merge lanes — the distinction callers must choose between", () => {
|
||||
/*
|
||||
FNXC:WorkflowLifecycleColumns 2026-07-30-11:30:
|
||||
Pinned because one NAME was answering two questions, and the difference only appears on a board that
|
||||
declares `mergeOrchestration` twice — which no default lineage does.
|
||||
|
||||
BROAD (this helper) every merge lane, plus mergeBlocker/humanReview lanes.
|
||||
Safe where over-admission is harmless: notifications, badges.
|
||||
NARROW (`resolveLifecycleColumns`) the FIRST merge lane only — what the executor, scheduler and
|
||||
project-engine act on.
|
||||
|
||||
A caller that admits on the broad set and then MOVES the card moves cards the engine does not consider
|
||||
in review. `register-task-workflow-routes.ts` keeps its own narrower resolver for that reason (#2723);
|
||||
this test is what stops someone "consolidating" the two and silently re-admitting the second lane.
|
||||
*/
|
||||
const twoMergeLanes = ir([
|
||||
{ id: "building", traits: [{ trait: "wip" }] },
|
||||
{ id: "merge-gate", traits: [{ trait: "merge" }] },
|
||||
{ id: "second-gate", traits: [{ trait: "merge" }] },
|
||||
]);
|
||||
|
||||
expect(resolveReviewColumns(twoMergeLanes)).toEqual(["merge-gate", "second-gate"]);
|
||||
// The engine's answer is ONE lane, and it is the first.
|
||||
expect(resolveLifecycleColumns(twoMergeLanes)?.review).toBe("merge-gate");
|
||||
});
|
||||
|
||||
it("agrees with the shipped coding workflow", () => {
|
||||
expect(resolveReviewColumns(BUILTIN_CODING_WORKFLOW_IR)).toContain("in-review");
|
||||
});
|
||||
|
||||
@@ -84,6 +84,31 @@ MONOTONIC, which the #2723 review round argued about: a column carrying BOTH `hu
|
||||
`mergeOrchestration` is included. Adding a trait must never remove a lane from this set, or a card
|
||||
stops counting as in review because its column gained an unrelated capability.
|
||||
*/
|
||||
/*
|
||||
FNXC:WorkflowLifecycleColumns 2026-07-30-11:30 (a flaw in this helper as merged, found by trying to
|
||||
migrate its consumers onto it):
|
||||
THIS IS THE BROAD SET, AND IT IS THE WRONG ANSWER FOR STATE-CHANGING ADMISSION.
|
||||
|
||||
Two different questions were being answered by one name:
|
||||
|
||||
BROAD "is this card in a lane where review happens?" -> every mergeOrchestration lane, plus every
|
||||
mergeBlocker and humanReview lane. Safe when over-admission is harmless: notifications, badges,
|
||||
read-only surfaces. This function.
|
||||
|
||||
NARROW "is this card in THE review lane the engine acts on?" -> `resolveLifecycleColumns().review` is
|
||||
`columnsWithFlag(ir, "mergeOrchestration")[0]`, ONE lane, and that is what the executor, the
|
||||
scheduler and project-engine act on. A caller that ADMITS on the broad set and then MOVES the
|
||||
card will move cards the engine does not consider in review.
|
||||
|
||||
`register-task-workflow-routes.ts` keeps its own narrower resolver for exactly that reason (#2723): its
|
||||
re-engagement moves the card, so admitting a SECOND merge lane is a state change the engine will not
|
||||
agree with. That local copy is not drift from this helper — it is the other question, and migrating it
|
||||
onto this one would reintroduce the over-admission its review round reasoned away.
|
||||
|
||||
Stated here because the name does not carry the distinction: a future consumer reaching for "the review
|
||||
columns" on a state-changing path wants the narrow form. The pair below is pinned in
|
||||
`workflow-lifecycle-traits.test.ts`.
|
||||
*/
|
||||
export function resolveReviewColumns(ir: WorkflowIr): string[] {
|
||||
return [...new Set([
|
||||
...columnsWithFlag(ir, "mergeOrchestration"),
|
||||
|
||||
Reference in New Issue
Block a user