Third unowned finding picked up after batch-core (#2783) merged without
addressing its reports. Same class as #2819, opposite failure direction.
## The defect
`clearNearDuplicateReferencesTo` runs on every complete/archive/delete
of a canonical task and clears the `nearDuplicateOf` markers pointing at
it. It first asks `isNearDuplicateCanonicalInactive` whether the
canonical really is finished — a safety check, so a live canonical's
markers are not cleared out from under an operator.
That call omitted the canonical's resolved column flags, so it fell back
to the legacy `done`/`archived` ids. On a renamed board a just-completed
canonical (`shipped`, `filed`) read as **still active**, the guard
early-returned, and the markers were **never cleared**. The flagged
duplicates stayed parked behind a user decision that could never arrive
— the exact stranding the predicate's own FNXC note says it was written
to prevent.
## I had the direction backwards, and it matters
My first report of this seam described it as *markers cleared against a
live canonical*. That is wrong. The legacy fallback errs toward "still
active", so the failure is the opposite: markers that never clear at
all. Same seam, same missing argument, entirely different symptom to
look for — which is why the direction is worth pinning in a test rather
than reasoning about.
## Measured
With the fix reverted, exactly one case flips:
```
✓ default vocabulary: completing the canonical clears the duplicate's marker
× renamed vocabulary: completing the canonical clears the duplicate's marker
✓ renamed vocabulary: a canonical still in the WIP lane does NOT clear the marker
✓ default vocabulary: a canonical still in the WIP lane does NOT clear the marker
✓ a soft-deleted canonical clears the marker under a renamed board
Tests 1 failed | 4 passed (5)
```
With it: `Tests 5 passed (5)`.
## Both negatives included
A canonical still in the WIP lane must **not** clear its duplicates'
markers, under each vocabulary. Resolving the real flags must not
degrade into "every column is terminal", which would clear markers out
from under an operator who has not made the duplicate decision yet. The
soft-deleted path is covered too, since that branch never consults
column flags at all.
## A fixture trap worth keeping
`sourceMetadata` is `jsonb` and must be seeded as an **object**. Seeding
a stringified value reads back fine through `getTask` — it parses either
shape — while the production query's
`source_metadata->>'nearDuplicateOf'` matches nothing. The fixture looks
correctly seeded and the code under test can never find the row. My
first version had this, and the self-check in the seed helper is what
caught it.
## Scope
Five of this predicate's six production call sites already resolved
flags. This was the sixth, and the one that runs on every
archive/complete transition.
Not touched here: the same function's SQL predicate excludes duplicates
by literal `ne(column, "archived")` / `ne(column, "done")`. That is a
per-row question across many rows in one statement, not a one-line
supply, so it is flagged rather than guessed at.
The five **engine** call sites of this predicate are separately reported
on #2785 and surfaced only via #2822.
## Verification
`pnpm test:gate` green, new suite 5/5, `tsc -p packages/core` 0, lint 0,
changeset included.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>