Skip to content

fix(guard): my own PR put two FALSE entries in the unwired-lane guard — name the type - #2936

Closed
gsxdsm wants to merge 1 commit into
mainfrom
guard-interface-owner-scope
Closed

fix(guard): my own PR put two FALSE entries in the unwired-lane guard — name the type#2936
gsxdsm wants to merge 1 commit into
mainfrom
guard-interface-owner-scope

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

unwired-lane-parameter-guard.test.ts has been failing on main since #2875 merged, and the two entries it reports are wrong.

+ "packages/core/src/task-store/async-audit.ts completeColumns",
+ "packages/core/src/task-store/async-audit.ts reviewColumns",

Both are wired — getInReviewDurationEventsImpl resolves the lane sets and passes them. So a guard I wrote, to catch parameters nobody supplies, was reporting the opposite of the truth about my own change.

Cause: owner-scoping cannot apply to a type the caller never writes

The rule I added earlier requires a mention to come from a file that also names the declaring symbol. Correct for a function — a caller has to name it to call it. Structurally impossible for an interface passed as an inferred object literal:

lanes = { reviewColumns: [...], completeColumns: [...] };   // wires it
getInReviewDurationEventsAsync(db, projectId, options, lanes);

Nothing there writes InReviewDurationLanes, and nothing ever will — the type comes from the parameter.

Fixed at the caller, after trying the tool three ways

attempt result
relax type-owned properties to the original loose rule hid 12 genuine entries
resolve owner to the functions consuming the type hid 6
annotate the local with the named type correct, one word, no rule change

Each heuristic refinement traded the false positive for false negatives. That is the usual sign a co-occurrence check has reached its limit — and an under-reporting guard is worse than an occasionally over-reporting one, because nobody notices the silence.

The other option was recording two wired parameters in KNOWN_UNWIRED. That would put non-debt in the debt list, which is how a ratchet starts lying — the same failure this PR is fixing, one layer up.

Naming the type is also just better code: the wiring is now visible to a reader, not only inferred.

Verification

  • unwired-lane-parameter-guard.test.ts9/9, baseline unchanged at 17
  • pnpm test:gate — 161 / 487 / 13 / 71 passed
  • pnpm lint — clean
  • tsc --noEmit (@fusion/core) — clean
  • activity-log-parity.pg.test.ts — 5 passed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Improved internal type safety for in-review duration event processing without changing user-visible behavior.

… — name the type

#2875 (merged) added `InReviewDurationLanes` and wired it: `getInReviewDurationEventsImpl`
builds both lane sets and passes them. The guard reported both properties as UNWIRED, so
`unwired-lane-parameter-guard.test.ts` has been failing on main since — a guard I wrote,
reporting the exact opposite of the truth, on my own change.

The cause is the owner-scoping I added earlier: a mention only counts if it comes from a
file that also names the DECLARING symbol. That is right for a function — a caller must
name it to call it — and structurally impossible for an interface passed as an inferred
object literal. The caller never writes `InReviewDurationLanes` and never will, because
the type comes from the parameter.

FIXED AT THE CALLER, NOT IN THE TOOL, after trying the tool three ways. Relaxing
type-owned properties to the original loose rule hid TWELVE genuine entries. Resolving
the owner to the functions that consume the type hid six. Each refinement traded the
false positive for false negatives, which is the usual sign a co-occurrence heuristic has
reached its limit — and a guard that under-reports is worse than one that occasionally
over-reports, because nobody notices the silence.

Annotating the local costs one word, makes the wiring visible to the reader as well as
the tool, and leaves the rule alone. The alternative — recording two wired parameters in
KNOWN_UNWIRED — would have put non-debt in the debt list, which is how a ratchet starts
lying.

Guard back to 9/9 with the baseline unchanged at 17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The workflow duration-event implementation now explicitly types its local lane mapping with the exported InReviewDurationLanes type. Lane resolution and fallback behavior remain unchanged.

Changes

Workflow duration typing

Layer / File(s) Summary
Annotate duration query lanes
packages/core/src/task-store/workflow-definitions.ts
Imports InReviewDurationLanes and uses it to type the local lanes value passed through duration-event handling.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes fixing false unwired-lane guard entries by naming the relevant type.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch guard-interface-owner-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/core/src/task-store/workflow-definitions.ts (1)

1012-1026: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the FNXC rationale while retaining the key decision.

This comment is substantially more detailed than necessary and duplicates implementation history. Keep the reason for naming the type and the tool-contract impact, but omit the heuristic experiments and PR-history details.

Suggested wording
 /*
   FNXC:WorkflowLifecycleColumns 2026-07-31-06:20:
-  Named type, not an inferred literal, and the reason is a tool contract rather than style.
-
-  `scripts/lib/unwired-lane-parameter.mjs` decides a lane parameter is WIRED when some other file
-  mentions both the parameter name and its declaring symbol. For an interface passed as an inferred
-  object literal there is no mention of the type anywhere, so this call site — which does supply
-  both lanes — was reported as unwired, and `#2875` landed two false entries into a guard written to
-  catch the opposite mistake.
-
-  Annotating the local is the smallest honest fix. I tried three variations of the heuristic first;
-  each traded the false positive for false NEGATIVES (the widest hid twelve genuine entries), which
-  is the usual sign that a co-occurrence check has reached its limit. Naming the type costs one
-  word, makes the wiring visible to both the reader and the tool, and leaves the guard's rule alone.
+  Name the lane type so wiring analysis sees both lane parameters explicitly
+  without changing runtime resolution or fallback behavior.
 */

As per coding guidelines, FNXC comments should be concise; decision-relevant causal provenance may remain when it prevents regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/task-store/workflow-definitions.ts` around lines 1012 -
1026, Shorten the FNXC comment associated with the workflow lifecycle columns to
state only that the local uses a named type because the wiring tool requires
references to the declaring symbol, preserving the tool-contract rationale.
Remove the heuristic experiment results, false-positive counts, PR history, and
other implementation-history details.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/task-store/workflow-definitions.ts`:
- Around line 1012-1026: Shorten the FNXC comment associated with the workflow
lifecycle columns to state only that the local uses a named type because the
wiring tool requires references to the declaring symbol, preserving the
tool-contract rationale. Remove the heuristic experiment results, false-positive
counts, PR history, and other implementation-history details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 636a6ac0-29d2-4b76-a7d4-57145dfbb8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 1dc8397 and d8e0311.

📒 Files selected for processing (1)
  • packages/core/src/task-store/workflow-definitions.ts

@gsxdsm

gsxdsm commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #2925 — folded per the queue freeze. Commit 7e26a3a3bb on live-task-column-lanes, cherry-picked onto current main and verified there (unwired-lane guard 9/9, baseline unchanged at 17).

Worth flagging for merge order: this is the commit that un-reds main's unwired-lane-parameter-guard test, which has been failing since #2875. It rides #2925 now.

@gsxdsm gsxdsm closed this Jul 31, 2026
gsxdsm added a commit that referenced this pull request Jul 31, 2026
…#2925, #2930, #2936) (#2925)

**Consolidated per the queue freeze.** Three single-fix PRs of mine
folded into this one branch; #2930 and #2936 are closed as superseded.
Net effect on the queue: **3 → 1**.

All three are the same root cause — a lifecycle lane compared against a
legacy id — and all three carry a measured revert proof. Verified scoped
(not full suite) on the folded branch: `tsc --noEmit` clean, `pnpm lint`
clean, SQL-literal gate green, census `--strict` green, and 61 tests
across five suites plus the guard at 9/9.

---

### 1. `getLiveTaskColumn` produced the archived sentinel from a literal
(was #2925)

`getLiveTaskColumn` **manufactures** the string `"archived"` that a
dozen comparisons across five files trust — and it tested `row.column
=== "archived"`. A live row in a renamed archived lane read as **live**,
so the gates hiding an archived card's artifacts and document listings
never closed.

Fixing those twelve comparisons individually would have been wrong twice
over: **they are sentinels, and the defect was in the producer.** One
line, once, and all twelve become correct. `resolveArchivedLanes` moved
to `project-lane-vocabulary.ts` — three private copies of one fact is
how the "write guard says yes, publication guard says no" disagreement
happens at scale.

*Revert proof (real PostgreSQL):* restore the literal → `expected [ {
…(14) } ] to deeply equal []`.

**Caught myself shipping the unwired shape here:** I added the parameter
to seven functions and wired none of their impl callers — the exact
inert-conversion defect this program exists to remove. The failing test
is the only reason I noticed.

### 2. Mission delivery repair refused a completed card (was #2930)

`getTerminalTaskEvidence` tested only `column === "done"`, so a
completed card on a renamed board classified as `nonterminal` and
`reconcileFeatureDoneWithTerminalTask` threw `TASK_NOT_TERMINAL: … not
shipped`. Valid operator work refused — with the message naming the real
column while the check couldn't see it.

The **type** blocked the fix from the far end: `TerminalTaskEvidence`
pinned `column: "done"` / `"archived"`, so the resolver couldn't report
the real column without a compile error. `kind` already carries the
role, so `column` is free to carry the truth.

*Revert proof (real PostgreSQL):* restore the literal →
`TerminalTaskReconciliationError: … not shipped`.

I had deferred this twice on the premise that `AsyncMissionStore` "holds
a layer, not a store". It holds an **optional `taskStore`**, and the
single production construction site supplies it.

### 3. The unwired-lane guard reported two FALSE entries (was #2936)

`unwired-lane-parameter-guard.test.ts` has been **red on main** since
#2875, flagging two `InReviewDurationLanes` properties as unwired when
the impl demonstrably supplies both. Cause: my own owner-scoping rule
requires a mention from a file naming the declaring symbol — correct for
a function, structurally impossible for an interface passed as an
inferred object literal.

Fixed at the caller (name the type) after trying the tool three ways:
relaxing type-owned properties hid **12** genuine entries; resolving
owners to consuming functions hid **6**. Each refinement traded the
false positive for false negatives — the sign a co-occurrence heuristic
has hit its limit. Recording two *wired* parameters in `KNOWN_UNWIRED`
was rejected: that puts non-debt in the debt list, which is how a
ratchet starts lying.

Guard back to **9/9**, baseline unchanged at 17. **This un-reds main.**

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant