Commit Graph

12 Commits

Author SHA1 Message Date
gsxdsm
cb57093d03 refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary

Wave 17 organizes Fusion into **domain folders** (stacks on #2397).

### Layout
- **core/types/** — board, task, agents, settings, merge, workflow,
mesh, …
- **core/src/** — agents, ai, async-stores, workflows, tasks, config,
db, …
- **dashboard/app/api/** — client, tasks, agents, git, missions,
planning, …
- **engine/src/** — agents, auth, execution, merge, missions, overseer,
worktree, …

Root keepers retained for large entrypoints (`store.ts`, `executor.ts`,
`merger.ts`, …).

Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy)
stay stable.

## Test plan
- [x] `@fusion/core` typecheck
- [x] `@fusion/engine` typecheck (pre-existing playwright-core noise
only)
- [ ] CI merge gate

**Stack:** #2394 → #2397 → **this PR**
2026-08-03 00:20:53 -07:00
gsxdsm
577dcb6c0c FN-8643: correct TaskWedgeNotifications FNXC timestamp
Correct the TaskWedgeNotifications migration-baseline stamp and remove its obsolete future-date allowance.

- Set the baseline FNXC annotation to its intended non-future timestamp.
- Remove the resolved migration from the future-date baseline allowlist.

Files changed:
 packages/core/src/postgres/migrations/0000_initial.sql | 2 +-
 scripts/lib/fnxc-future-dates-baseline.json            | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

Fusion-Task-Id: FN-8643

Fusion-Task-Lineage: 0f27e9e6-0581-4e38-8005-e93f94ad4f78

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-31 19:32:55 -07:00
gsxdsm
e56116d8e9 chore(fnxc): retire the stale scheduler allowance left by the rollover re-record (#3283)
**The date gate passes on `main` — but not because this was fixed.**

## What actually happened

UTC rolled over to `2026-08-01`. The gate compares against the later of
local and UTC, so two `2026-08-01` stamps in `scheduler.ts` became valid
on their own. That is the ratchet's normal drop path and is fine.

`#3278` then re-recorded the baseline "after the UTC rollover", which
set `scheduler.ts` to **allow 1** — exactly enough to absorb the one
stamp that did *not* age out:

```
FNXC:ConcurrencyAdmission 2026-08-06-09:00     ← six days out, wrong on any calendar
```

So the gate reports `123 known future-dated stamp(s), none added` and
exits 0, with a stamp inside it that will not be valid until next week.

## Why this is the failure the gate exists to catch

A blanket re-record cannot distinguish **aged out** from **still
wrong**, so it launders the second past the first. The sibling ratchet
states the rule outright:

> Do NOT re-record the baseline to clear this — that is the same false
green one layer up.

This is that, one layer up again: not a guard cleared by a baseline, but
a *baseline refresh* clearing a guard as a side effect.

## The fix

- stamp repointed to `2026-08-01` — today in UTC, which is the calendar
the gate actually compares against
- **allowance removed**, not left at 1, so the entry cannot be regrown
into

**Mutation-verified**: with the allowance gone, restoring `2026-08-06`
exits **1**. Before this change the same stamp exited **0**. That is the
whole point — the ratchet can now see it.

## One thing worth carrying forward

A six-days-out stamp is not a timezone slip. Neither the old `date -u`
guidance nor the current local-date guidance in AGENTS.md would have
prevented it, and CI-only checking cannot catch it before merge. This is
the concrete case for running the date check at author time, which I
have flagged but not landed since it changes the gate's contract.

## Verification

- `check-fnxc-future-dates` — exit 0, allowance removed
- `scheduler` suites — **148 pass**
- `tsc --noEmit` (engine) — 0 errors

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 17:41:47 -07:00
gsxdsm
208c32c970 chore(fnxc): re-record the future-dates baseline after the UTC rollover (92 → 64 files) (#3278)
## What

Re-records the future-dates baseline after the UTC rollover. **92 → 64
file entries.** Tooling only, no source changes.

The gate's own instruction is *"If a count went DOWN, re-record the
baseline in the same commit."* UTC is now `2026-08-01`, so every stamp
dated `2026-08-01` is dated **today** rather than after it, and **28
files no longer carry any future-dated stamp at all.**

## Why pruning matters more than the number

Those 28 files kept a non-zero allowance they no longer need, and **an
allowance is a hole a new violation can hide in.** Four separate commits
landed a future-dated stamp last evening — in that environment, a stale
allowance on a file is exactly where the fifth would go unnoticed. With
the entries pruned, the next one in any of those files is caught on the
first run instead of being absorbed silently.

This is the same direction as #3168 (*"tightens the allowance 1 → 0"*)
and #3211, just triggered by the clock rather than by a fix.

## What this is not

- **Not a correction to anyone's stamp** — no source file is touched.
- **Not a loosening** — no entry increases and no file is added.
- The **123 stamps still dated beyond today** (e.g. `2026-08-06`) keep
their existing allowances untouched.

```
baseline file entries   92 -> 64
check-fnxc-future-dates rc=0
lint                    clean
```

## Related, deliberately not included

`scheduler.ts:2258` carries `FNXC:WorkflowScheduling 2026-08-01-01:05` —
dated today, one hour ahead of the current clock. I repointed it while
preparing this change and then reverted: after the rollover it is no
longer a gate violation, and mixing a cosmetic timestamp edit into a
baseline re-record would make both harder to review. Noting it so the
residual I flagged when closing #3270 does not get lost — it is now an
accuracy nit rather than a gate concern.
2026-07-31 17:14:53 -07:00
gsxdsm
78d411cfe2 fix: main is RED on two gates — record the new fallback, repoint six future-dated stamps (#3261)
`9094d1640e` (globalPause gates every graph node entry) reddened **two**
lifecycle gates on main. Both are fixed here, in separate commits.

## 1. The census ratchet went 0 → 2

`isTerminalColumnTask` in `scheduler.ts`:

```ts
const flags = columnFlagsForTask(task);
if (flags) return flags.complete === true || flags.archived === true;
return task.column === "done" || task.column === "archived";   // ← counted
```

**The code is correct.** It resolves traits first and falls back only
when the workflow is unreadable. The census counts fallback literals on
purpose — *"a fallback literal is still a literal and should go when the
trait path becomes unconditional"* — and reports them beside the backlog
as already-converted. Its own remedy for a legitimate one is a
`DELIBERATE-LITERAL` marker at the site.

Recorded rather than converted because **there is nothing to convert
to**: a task whose workflow cannot be read has no resolved lane, and
treating it as non-terminal would count a finished card's retained
worktree against live capacity — the opposite of what the surrounding
fix does.

Marker sits in the declaration's **leading** comments; an inline one
attaches to the wrong node and is silently ignored, which cost a
miscount once before. Baseline re-recorded in the same commit, since the
census tracks deliberate counts and reports a marker addition as
`RECLASSIFIED`.

## 2. The stamp gate was red as well

Six files stamped `2026-08-01-00:2x` while UTC was `2026-07-31`:

```
workflow-column-boundary.ts             2      workflow-graph-task-runner.ts   1
workflow-column-boundary-hooks.ts       1      in-process-runtime.ts           5 (allows 4)
workflow-column-boundary-capacity.test  1
```

This checkout is UTC-7, so "just after midnight local" is tomorrow in
UTC — the case AGENTS.md documents, which passes `pnpm lint` locally
*because* the local clock agrees with what was written. Second
occurrence today; I fixed the same shape on #3208 for another worker.

Repointed to `2026-07-31-22:2x`, preserving relative order. **Zero
non-comment lines changed** — 8 lines across 6 files, verified by
diffing out FNXC lines.

## Measured

| check | before | after |
|---|---|---|
| `census --strict` | **1** | **0** |
| backlog | **2** | **0** (DELIBERATE-LITERAL 148 → 150) |
| `check-fnxc-future-dates` | **1** | **0** |
| `pnpm test:gate` | 0 | 0 |
| `census-reclassification-message` | 2 failed | **1 failed** |

That last row is deliberate: the remaining failure is the
expired-premise case #3260 fixes, and I have not touched it. The
capacity test from `9094d1640e` still passes 9/9.

## Why this landed at all

Both gates run in `pr-checks.yml`, so a PR carrying either would have
gone red. Worth someone checking how it merged — a stale merge base
would explain it, and if so the same hole is open for the next merge.
2026-07-31 16:29:37 -07:00
gsxdsm
8d393422ac chore(fnxc): tighten the future-dates baseline — merge-queue-ops-2 4 -> 3 (#3211)
One-line baseline tightening, produced by the gate's own auto-tighten
path.

`check-fnxc-future-dates` deliberately auto-tightens rather than failing
on a drop, because its population moves with the calendar and a drop has
**no author** — the counterpart asymmetry to
`check-inert-sync-lane-conversions`, where a drop *does* have an author
and must fail. Any gate run regenerates this; `main`'s committed
baseline had simply not caught up.

**Why this isn't churn:** left loose, the baseline permits 4 future
stamps in a file that now has 3. That slack silently absorbs one genuine
future-dated stamp — precisely the failure this gate exists to catch,
and one the fleet hit four times in a single day (`scheduler.ts`, a
scheduler PG test, `task-update.ts` twice by different authors), each a
real time on the wrong day that passed locally and reddened `main` for
everyone else.

Verified: both `check-fnxc-future-dates` and
`check-inert-sync-lane-conversions` green on the tightened baseline.

No changeset: tooling baseline, no published-package surface.

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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:47:03 -07:00
gsxdsm
2a57820dd2 chore(gate): normalize the last future-dated stamp in task-update.ts (tightens the allowance 1 → 0) (#3168)
**Main is red on the FNXC gate again** — third occurrence of this class
today, third different file.

```
packages/core/src/task-store/task-update.ts: 2 future-dated FNXC stamp(s), baseline allows 1
```

Stamps dated **2026-08-01** while UTC is **2026-07-31-14:24**. Every
open PR inherits the failure; #3164 merged carrying it.

## The fix

Date only, to today. Clock times preserved exactly — they were real
times on the wrong day — and no comment text touched, so the record
reads identically, just in order:

```
-FNXC:StateMachine 2026-08-01-10:20 (PR #2793's finding — the INNER half, merged with #2821):
+FNXC:StateMachine 2026-07-31-10:20 (PR #2793's finding — the INNER half, merged with #2821):
```

Baseline **tightened** as a side effect (`1 → 0`): one future stamp was
grandfathered, normalizing the file cleared it too, and the gate refuses
a stale allowance on the way down. Re-recorded in the same commit.

## The recurrence is the point, not this fix

Three separate files have tripped this in one day — `scheduler.ts`, the
scheduler PG test, and now `task-update.ts` — plus the midnight-rollover
variant this morning that reddened everyone's baseline.

**Stamps are written from a local clock and validated against UTC.** A
worker behind UTC writes what is genuinely "today" for them and produces
a future stamp the moment UTC has already rolled. Nothing in the local
loop catches it: `pnpm lint` passes locally because the local date
agrees.

The durable fix is to generate the stamp from `date -u` rather than a
wall clock — one line in whatever produces these, and the class
disappears. I have patched the symptom three times today; someone should
take the cause. I have not done it myself because the stamps are
authored by hand across every worker's flow, so the change belongs
wherever that convention is documented, not in a file I happen to be
touching.

## Verification

`check-fnxc-future-dates` green (TZ=UTC CI=true) · `pnpm test:gate` 13 +
161 + 487 + 71 · lint · core typecheck clean · diff is date
substitutions only.
2026-07-31 07:56:09 -07:00
gsxdsm
1de0141ab8 fix(dashboard): Task Detail's blocking count read the LEGACY lanes (last of the three fan-out surfaces) (#3004)
Third and last of the three surfaces calling the blocker fan-out
wrapper, completing the sweep started in #2990 (Board + Executor bar).

## What was wrong, precisely

The dependent **list** is lane-independent — core pushes `dependentIds`
without consulting lanes — so this section looked broadly right. Two
things beside it are not:

- `overlapBlockedTodoCount`, rendered as **"FN-X is blocking N todo
task(s) via blockedBy overlap"** — counted against the literal `todo`,
so on a renamed board it read **0 while cards were genuinely blocked**.
- the `stale` marker on each blocking dependent — decided against
`terminal`/`review` lanes the operator does not use.

A wrong number sitting beside a right list is the easiest kind to miss,
which is why I checked what the modal actually consumes before deciding
this was worth a PR rather than assuming the whole section was broken.

## Why a prop and not a hook

This was the surface I deferred in #2990 because it had no trait index
in scope. Two options:

- `useBoardWorkflows` inside the modal — rejected. The hook documents
that it does **not** dedupe across consumers: each call installs its own
visibilitychange/focus listeners and its own SSE subscription. That is a
new fetch and subscription per modal open, to answer a question the app
has already answered.
- **Thread the index that already exists** — `App` builds
`footerColumnFlagsByTaskId` for the footer; this forwards it through
`AppModals` as an optional prop. Chosen.

Optional throughout: a card with no entry keeps the documented legacy
fallback, so the remote-node case (where local workflow metadata must
never be applied to foreign ids) and the pre-load window stay
byte-identical.

## Reverted

The new case fails on the rendered text — the modal cannot find `"FN-B
is blocking 2 todo task(s) via blockedBy overlap"`. The pre-existing
legacy-column case above it passes either way, because `todo` satisfies
the literal default; that is exactly why it never caught this.

## Verification

TaskDetailModal.rendering + ExecutorStatusBar + useBlockerFanout **206
passed** · dashboard app suite 11986 passed / 5 skipped (581 files) ·
`pnpm test:gate` 161 + 13 + 487 + 71 · lint · census `--strict` ·
lane-wiring · fnxc-dates · changesets — green.

## One note for whoever owns the FNXC gate

`check-fnxc-future-dates.mjs` **rewrites its baseline as a side effect
and still exits 0**. Today's date roll dropped 183 stamps out of
"future", so any run dirties
`scripts/lib/fnxc-future-dates-baseline.json` in the working tree. It
cost me a stash conflict before I noticed. Not bundled here — it is
repo-wide midnight drift, not this change — but a check that mutates
tracked state on a read is worth a look.
2026-07-31 00:13:43 -07:00
gsxdsm
1f0d371228 fix(tests): three more portal query-root failures (pr-tab, worktree-terminal, milestone-slice) (#2959)
Three dashboard test files asserted against `render()`'s `container`,
but the components under test mount through `createPortal` — so
`container` is **empty** and every query returns nothing. Same root
cause as the earlier portal batch; these are the three that were still
held back.

| File | Before | After |
|---|---|---|
| `TaskDetailModal.pr-tab` | failing | pass |
| `TaskDetailModal.worktree-terminal` | failing | pass |
| `MilestoneSliceInterviewModal` | failing | pass |

**Measured: 39/39 passing**, rebased on current main (`3461ae7a92`).
Lint clean, FNXC date gate exit 0.

### Why this stayed hidden

The queries were a **mix** of `container.querySelector(...)` and
`screen.*`. `screen` queries `document`, so they kept working — a
portal-mounted modal makes only the `container` half go blind. The
result is a file that looks half-alive rather than obviously broken, and
the failures present as five different-looking symptoms (`null`,
`undefined`, `+0`, `[]`, `-1`) that don't read as one bug.

Grouping candidate files by **`container.querySelector` call count**
rather than by symptom is what identified these correctly, and — the
part that mattered — correctly *excluded* the neighbouring files that
were failing for unrelated reasons.

### One thing to know if you repeat this

A blanket `container` → `document` replace is wrong: it also rewrites
`renderResult.container.querySelector` into
`renderResult.document.querySelector`, which is not a thing. That broke
two already-passing tests on my first attempt. This uses two separate
passes with a lookbehind so only the bare receiver is rewritten.

### Scope

Test-side only — **no product code changes**, so no changeset. This does
not fix the *cause* (tests are still free to query the wrong root); a
lint rule for that is worth considering separately, but it would need to
distinguish portal-mounting components from ordinary ones, and I did not
want to guess at that boundary inside a test-fix PR.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Improved modal and task detail accessibility test reliability by
querying rendered elements from the document.
* Updated coverage for keyboard navigation, Pull Request status
indicators, tab ordering, and onboarding provider cards.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 21:42:33 -07:00
gsxdsm
255741e9ab fix(gate): scan every file type that carries an FNXC stamp (#2954)
The walk's extension filter was `/\.(tsx?|m?js|cjs|md)$/` — the file
types stamps were **expected** in, rather than the ones they **occur**
in. Wherever the convention spread on its own, the gate could not see
it.

## How I found it

Chasing four stamps dated `2026-10-19` — three months out, so unlike the
rest of the population they would not age out on their own. All four
were in `packages/core/dist/`, which the gate correctly skips as
generated. The *source* they were compiled from is a `.sql` migration,
which the gate skips for a different and much worse reason: it was never
scanned at all.

## Why `.sql` is the expensive omission

A migration's stamp records **when a schema change landed**. That is the
case where a wrong date misleads most — it is the file you read to
reconstruct the order schema changes happened in. 69 migration files
carry stamps; 10 were future-dated and none were visible.

`.css` had drifted furthest by volume: **1023 stamps across 123 files**,
almost all from the dashboard CSS split. `.html`, `.ya?ml`, `.json`,
`.sh` are included too; they add coverage but contribute no baseline
entries.

## The 9 new baseline entries are newly VISIBLE, not new

5 `.css` + 4 `.sql`. Every one predates this change and would have been
caught had the gate ever looked. Recording them is a
**reclassification**, the same distinction the census draws for its
DELIBERATE-LITERAL marker — a baseline that grows here is the gate's
coverage improving, not the codebase regressing. Reading the rise as a
regression would be exactly backwards.

## Verified by mutation, not by reading

- A future-dated stamp appended to `ChatView.css` → gate **exit 1**.
- A future-dated stamp appended to `0036_chat_session_tags.sql` → gate
**exit 1**.
- Both reverted → **exit 0**.

Without this, both probes pass silently.

## Two notes on the diff

- **Zero removals.** My first attempt rewrote the baseline with sorted
keys, which turned unmoved lines into add/remove pairs and made it look
like entries were being dropped. Rebuilt in walk order so the diff is
additions only.
- `reads.ts` is deliberately left at `2` here even though it now
measures `0`. That drop belongs to #2953; duplicating it across two open
PRs is how this queue got tangled before. The gate auto-tightens it at
runtime and still exits 0.

## What this does not fix

The **478** future-dated stamps still in the tree. They are
agent-written (mine included) and most are one or two days out, so the
count falls on its own as the clock advances — it should not be read as
cleanup progress. This PR only makes the gate able to *see* the SQL and
CSS ones, so no new stamp can land there unnoticed.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 20:53:25 -07:00
gsxdsm
f49e487d91 feat(core): untraited-project lane opt-in — and main was red on the FNXC gate (#2949)
Two things, and the second is why the first does not ship alone.

## The opt-in

`resolveProjectColumnsForRoles` gains `untraitedProject:
"declared-columns"`. When **no** workflow in the project expresses
**any** lifecycle trait, every declared column id joins the answer.

This is the three-state rule at **project** scope — the last item on the
deferred list, recorded at three self-healing call sites (#2869, #2876).
A board that renames its lanes and declares no traits contributes
nothing today, so its cards are **absent from every role-keyed query**,
and the correct per-card fallback downstream never runs for them. A
fallback cannot rescue a card the query never returned.

**Not "no workflow declares this role."** A project that expresses
traits and has no review lane has *answered*; widening there would
invent lanes it deliberately lacks. Mutation-verified both directions —
widening unconditionally fails 1 of 12, making the option a no-op fails
1 of 12.

**Opt-in, not default**, because the safe direction differs per caller —
the finding in `project-union-versus-per-task-lanes.md`:

| caller | over-inclusion costs |
|---|---|
| sweep | nothing — the per-card check discards the extra rows |
| aggregator | an inflated number an operator reads (#2864, #2866) |
| action site | a card routed or notified under a vocabulary that is not
its own (#2852, #2891) |

Making it the default moves all three at once, in the one direction two
of them must not. Verified byte-identical without the option, so this
lands with **no caller changes** and each site adopts it on its own
reasoning.

## Main was red, and my own gate caught me first

I dated the new comments `2026-07-31` while today is `2026-07-30` —
**the exact defect `check-fnxc-future-dates` exists to prevent,
committed while writing the feature.** The gate I added yesterday failed
my own commit.

Correcting mine surfaced that the merged sentinel batch, #2947, and
three engine test files carried future-dated stamps too, so **the gate
was failing on `main` for everyone**, not just here.

All corrected to real dates rather than raising the ceiling. The stamps
were simply wrong, and a baseline bump would have recorded the error as
permitted — which is the failure mode that ratchet exists to prevent.

Core and engine `tsc` clean, `pnpm lint` clean, census `--strict` 0,
FNXC gate 0 (469 known, none added), gate green (161/487/13/71).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 20:17:18 -07:00
gsxdsm
b1bd571682 batch-sql-ratchet: the census / gate-ratchet family — collection branch, fold here (#2941)
## Family branch for consolidation directive item 4

`batch-sql-ratchet` did not exist and ~10 open PRs are waiting for a
collection point, so this establishes it. **Fold your census/ratchet
commit here and close your own PR as superseded.**

```bash
git fetch origin batch-sql-ratchet
git checkout -B batch-sql-ratchet origin/batch-sql-ratchet
git cherry-pick <your-sha>
# verify scoped, not full suite:
pnpm --filter @fusion/core exec vitest run src/__tests__/archived-column-gate-parity.test.ts --silent=passed-only --reporter=dot
git push origin HEAD:batch-sql-ratchet
```

**Candidates I can see open right now** (owners: please fold + close):

| PR | branch |
|---|---|
| #2938 | `fix/comments-ops-sentinel` |
| #2935 | `fix/task-artifacts-sentinels` |
| #2933 | `chore/commit-tightened-census-baseline` |
| #2931 | `fix/async-comments-sentinels` |
| #2928 | `fix/audit-ops-sentinel-marker` |
| #2925 | `live-task-column-lanes` |
| #2923 | `fix/task-id-integrity-sentinel` |
| #2921 | `fix/plugin-store-migration-marker` |
| #2894 | `gate/sql-literals-match-census-placement` |

That is **10 → 1** once folded. I have not cherry-picked anyone else's
commits — folding someone's work without them verifying it is how a
batch lands broken.

---

## What is in it so far (mine, from #2924)

**Clears a live main red:** `archived-column-gate-parity` fails on
`origin/main` today.

```
AssertionError: TypeScript encoding changed.
  async-comments-attachments.ts: 8 → 5
```

#2886 fixed a real bug — archived-document guards failing in *opposite*
directions on a renamed lane — by replacing three `column ===
"archived"` comparisons with `isArchivedLane(column, archivedColumns)`.
The AST scan counts raw comparisons, so the tally dropped.

**What I did not do is record it as three sites converted**, because
measured, it is not:

```
grep -rn "archivedColumns:" packages/core/src packages/engine/src --include="*.ts" | grep -v __tests__
→ (no matches)
```

No caller passes it. The parameter defaults to `LEGACY_ARCHIVED_LANES =
new Set(["archived"])`, so every call resolves to the literal it
replaced — byte-identical behaviour, resolved branch dead.

That matters for this guard's whole argument: its header warns that
converting the TypeScript half while the Drizzle and raw-`sql` halves
still compare the string is a split brain *"no test would catch, because
every builtin workflow spells the column `archived` so the two halves
agree by accident on every board we ship."* **There is no split brain
today precisely because the resolved half is unwired** — it becomes one
the moment a caller threads real lanes in without the SQL sides moving.
Recorded inline so `5` cannot be read as "3 sites done"; flagged on
#2886.

Verified not a split brain: the Drizzle and raw-sql inventories are
unchanged and both pass — worth stating because those assertions run
*after* the TypeScript one, so a plain red says nothing about them.

Scoped edit to `AUDITED_TS_SITES` by line range: these paths appear in
more than one inventory here, and an unscoped replace would quietly edit
the raw-sql side too, making the parity guard agree with itself (the
trap I hit in #2817).

Guard still bites: appending a real `task.column === "archived"` to an
audited file fails it. Core **4852 passed / 0 failed**, lint clean,
test-only.

Closing #2924 as superseded by this.

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved task delegation messages when workflow pickup cannot be
confirmed.
* Delegation results now clearly indicate when a task has not been
verified for pickup.

* **Quality Improvements**
* Added validation checks to catch future-dated markers and inconsistent
SQL-column usage.
* Refined workflow checks to distinguish stale configuration from
incomplete configuration.

* **Documentation**
* Updated lifecycle conversion guidance with more accurate audit
findings and limitations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 19:59:14 -07:00