4009eb34cb818ebf6d2b9478dfb4e71dd20dcbb2
3860 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4009eb34cb |
FN-8683: remove unreachable SQLite task polling replica
Document PostgreSQL task-deletion observation and remove the obsolete SQLite polling path. - Remove polling state, replica emissions, and activity-log suppression from TaskStore. - Retain backend-aware cache warming while documenting the transactional-outbox follow-up. - Add tombstone and soft-delete abort coverage across core and engine lanes. Files changed: docs/architecture.md | 3 +- ...tgres-cross-process-task-deleted-observation.md | 128 ++++++++++++++++ docs/storage.md | 3 +- .../task-delete-nonblocking-cleanup.test.ts | 54 +++++++ .../task-deleted-polling-replica-tombstone.test.ts | 57 +++++++ .../task-updated-lanes-emit-surfaces.test.ts | 26 ---- packages/core/src/store.ts | 13 +- packages/core/src/task-store/lifecycle-ops.ts | 168 ++------------------- packages/core/src/task-store/task-artifacts-ops.ts | 4 - .../__tests__/executor-soft-delete-abort.test.ts | 15 ++ .../src/__tests__/triage-soft-delete-abort.test.ts | 14 ++ 11 files changed, 284 insertions(+), 201 deletions(-) Fusion-Task-Id: FN-8683 Fusion-Task-Lineage: a052db0c-b6fc-4b05-b6b2-f8217b56ded0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
3793b576d8 |
FN-8673: comment on split source issue closures
Explain GitHub source-issue closures when imported work is split into subtasks. - carry split closure context through task deletion and triage - post one explanatory comment before closing source and tracking GitHub issues - preserve exactly-one-comment behavior when close retries after transient failures - document split closure behavior and cover source/tracking scenarios Files changed: .changeset/fn-8673-split-close-issue-comment.md | 7 ++ docs/settings-reference.md | 2 +- docs/task-management.md | 1 + .../task-delete-caller-attribution.test.ts | 32 +++++++ packages/core/src/index.ts | 2 +- packages/core/src/store.ts | 10 +-- packages/core/src/task-delete-attribution.ts | 16 ++++ .../core/src/task-store/archive-lifecycle-2.ts | 14 ++-- packages/core/src/task-store/archive-lifecycle.ts | 6 +- packages/core/src/types.ts | 13 +++ .../src/__tests__/github-tracking-state.test.ts | 98 +++++++++++++++++++++- packages/dashboard/src/github-tracking-state.ts | 82 +++++++++++++++--- packages/engine/src/__tests__/triage.test.ts | 4 + packages/engine/src/triage.ts | 10 +++ 14 files changed, 268 insertions(+), 29 deletions(-) Fusion-Task-Id: FN-8673 Fusion-Task-Lineage: 904c2445-64d9-47b9-b706-f64b23c4e3a6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8a6949dd24 |
FN-8661: resolve selected credential instances for sessions
Resolve requested provider credential instances before creating agent sessions. - Thread lane credential instance selections through planning, validation, execution, review, and merge sessions. - Resolve selected instances into runtime credential stores while retaining provider-default fallback behavior. - Preserve selected credentials for mission validation, executor retries, and spawned child agents. Files changed: .../fn-8661-credential-instance-resolution.md | 7 ++ AGENTS.md | 1 + docs/architecture.md | 2 +- docs/secrets.md | 2 + docs/settings-reference.md | 1 + .../dashboard/src/__tests__/routes-auth.test.ts | 80 +++++++++++++++++++ .../dashboard/src/routes/register-model-routes.ts | 78 +++++++++++++++++++ .../src/__tests__/agent-session-helpers.test.ts | 16 ++++ .../credential-instance-resolution.test.ts | 49 ++++++++++++ packages/engine/src/agent-heartbeat.ts | 1 + packages/engine/src/agent-runtime.ts | 9 ++- packages/engine/src/agent-session-helpers.ts | 67 +++++++++++----- packages/engine/src/auth-storage.ts | 90 ++++++++++++++++++---- packages/engine/src/executor.ts | 29 ++++++- packages/engine/src/merger-ai.ts | 2 + packages/engine/src/merger.ts | 5 ++ packages/engine/src/mission-execution-loop.ts | 4 +- packages/engine/src/pi.ts | 7 +- packages/engine/src/pr-response-run-ops.ts | 1 + packages/engine/src/reviewer.ts | 7 ++ packages/engine/src/triage.ts | 2 + 21 files changed, 420 insertions(+), 40 deletions(-) Fusion-Task-Id: FN-8661 Fusion-Task-Lineage: 1e34a3ce-0857-4619-9746-ce0dc12dc2ba Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
13f7266b67 |
FN-8670: reuse model runtime fixtures in engine tests
Share a warmed Pi model runtime across engine catalog tests. - Add an isolated in-memory model registry fixture backed by a per-file shared runtime. - Warm the runtime before catalog tests and verify custom-provider registry isolation. - Document the required fixture pattern for real Pi SDK catalog tests. Files changed: docs/testing.md | 1 + .../engine/src/__tests__/_model-runtime-fixture.ts | 40 ++++++++++++++++++++++ .../custom-providers-openai-completions.test.ts | 15 +++----- .../custom-providers-openai-responses.test.ts | 15 +++----- .../src/__tests__/provider-registration.test.ts | 33 ++++++++++++------ 5 files changed, 73 insertions(+), 31 deletions(-) Fusion-Task-Id: FN-8670 Fusion-Task-Lineage: 7410d114-1853-44cc-a09b-a997fbc7f119 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ebe514c3e4 |
FN-8677: propagate task update lanes before conversion
Propagate cache-warmed workflow lanes through task updates so synchronous engine consumers support renamed boards. - Add task lane cache and attach resolved lanes to task:updated metadata. - Update scheduler, triage, and notification consumers to use carried lanes with bridge-safe fallbacks. - Cover lane propagation and renamed-lane event behavior with core and engine tests. Files changed: .changeset/fn-8677-manual-merge-hold-lanes.md | 7 ++ .changeset/task-updated-carries-lanes.md | 7 ++ ...orkflow-ir-readers-always-return-the-default.md | 22 +++++ .../sync-workflow-ir-second-blocker.test.ts | 43 +++----- .../core/src/__tests__/task-lane-cache.test.ts | 30 ++++++ .../task-updated-lanes-emit-surfaces.test.ts | 92 ++++++++++++++++++ .../__tests__/task-updated-lanes-payload.test.ts | 42 ++++++++ packages/core/src/index.ts | 1 + packages/core/src/store.ts | 36 ++++++- packages/core/src/task-lane-cache.ts | 63 ++++++++++++ .../core/src/task-store/archive-lifecycle-2.ts | 3 + packages/core/src/task-store/moves.ts | 1 + packages/core/src/task-store/task-artifacts-ops.ts | 1 + packages/core/src/task-store/task-update.ts | 1 + packages/core/src/task-store/update-task-deps.ts | 4 +- .../core/src/task-store/workflow-definitions.ts | 71 +++++--------- .../__tests__/scheduler-task-updated-lanes.test.ts | 108 +++++++++++++++++++++ .../task-updated-lanes-bridge-compat.test.ts | 94 ++++++++++++++++++ ...task-updated-lanes-engine-emit-surfaces.test.ts | 101 +++++++++++++++++++ .../src/__tests__/triage-pause-abort.test.ts | 22 +++++ .../src/__tests__/triage-planning-wake.test.ts | 25 +++++ .../notification-renamed-lifecycle-columns.test.ts | 84 +++++++++++++++- .../__tests__/task-wedge-notification.test.ts | 19 ++++ .../src/notification/notification-service.ts | 56 ++++------- packages/engine/src/scheduler.ts | 62 +++--------- packages/engine/src/triage.ts | 105 ++++++-------------- scripts/lib/inert-sync-lane-baseline.json | 5 +- 27 files changed, 858 insertions(+), 247 deletions(-) Fusion-Task-Id: FN-8677 Fusion-Task-Lineage: d8fef9db-0f88-4dfd-9813-be25e10e3588 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
5f12044168 |
FN-8652: add multiple provider credential instances
Enable operators to create, select, and remove named credentials for each supported provider. - Add provider-auth instance discovery and credential mutation endpoints. - Add dashboard authentication controls, status handling, and instance coverage. - Document instance behavior and add a release changeset. Files changed: .changeset/fn-8652-provider-credential-instances.md | 7 + docs/secrets.md | 2 + docs/settings-reference.md | 4 + packages/dashboard/app/api.ts | 1 + packages/dashboard/app/api/provider-status.ts | 94 +++++- .../dashboard/app/components/SettingsModal.tsx | 138 ++++----- .../AuthenticationSection.instances.test.tsx | 75 +++++ .../settings/sections/AuthenticationSection.css | 13 + .../settings/sections/AuthenticationSection.tsx | 199 +++++++----- .../dashboard/src/__tests__/routes-auth.test.ts | 27 +- packages/dashboard/src/routes.ts | 12 +- .../dashboard/src/routes/register-auth-routes.ts | 334 ++++++++++++++++++--- .../src/__tests__/provider-auth-instances.test.ts | 65 ++++ packages/engine/src/provider-auth.ts | 89 ++++++ 14 files changed, 845 insertions(+), 215 deletions(-) Fusion-Task-Id: FN-8652 Fusion-Task-Lineage: 39568d58-7f57-4d17-97cb-1837323b3a94 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1e83dcceec |
chore(release): v0.74.0-beta.6
Version bump via changesets. |
||
|
|
9b82ff29e1 |
fix: enforce active capacity and clarify queued cards
Refresh canonical live-task claims at serialized scheduler, planning, and merge admission boundaries, including workflow-step leases and reservation handoffs. Back off capacity-denied merges safely across abort and restart lifecycles. Render queued planning cards in the header badge family with compact reason-specific icons. |
||
|
|
dd569395e8 |
FN-8671: isolate triage admission state in tests
Prevent leaked singleton admission state from affecting later triage polling tests. - Add test-only coordinator reset and inspection seams for all admission categories. - Stop tracked triage processors before clearing shared reservation and pre-held-slot state. - Cover teardown behavior and document singleton-state isolation guidance. Files changed: docs/testing.md | 6 ++ packages/engine/src/__tests__/concurrency.test.ts | 56 ++++++++++++++++ packages/engine/src/__tests__/triage.test.ts | 82 ++++++++++++++++++++--- packages/engine/src/concurrency.ts | 30 +++++++++ 4 files changed, 166 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-8671 Fusion-Task-Lineage: 41217db4-1fd6-45d6-a846-c57fc3c7052e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
713e9320b0 |
fix(engine): enforce capacity for workflow continuations
Route durable planning and review continuations through shared project admission, retain reservations through execution, and prevent duplicate continuations from releasing another run's slot. |
||
|
|
897cce9d94 |
FN-8656: resolve scheduler lanes for renamed holds
Resolve scheduler lane lookup against workflow-defined hold and terminal columns. - Use asynchronous workflow lane resolution after the synchronous event prologue - Preserve legacy lane fallback and recognize all terminal workflow columns - Update scheduler regression coverage, sync-lane guardrails, and release notes Files changed: .changeset/fn-8656-scheduler-renamed-hold-lanes.md | 7 ++ .../sync-workflow-ir-callsite-allowlist.test.ts | 10 +- .../scheduler-renamed-hold-events.test.ts | 20 ++-- ...ow-scheduler-parked-columns-live-e2e.pg.test.ts | 13 ++- ...-sync-role-conversion-inert-live-e2e.pg.test.ts | 8 +- packages/engine/src/scheduler.ts | 121 +++++++++------------ scripts/check-inert-sync-lane-conversions.mjs | 5 + scripts/lib/inert-sync-lane-baseline.json | 3 +- 8 files changed, 92 insertions(+), 95 deletions(-) Fusion-Task-Id: FN-8656 Fusion-Task-Lineage: 389a95a1-289f-4dde-86b3-1e450f8d43db Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
dcf1b921a3 | fix(engine): close active-slot reservation handoff gaps | ||
|
|
5a19d1da6e |
fix: count only actively running tasks against worktree capacity
Retained directories on queued, paused, blocked, or terminal tasks no longer consume scheduler slots. Agent concurrency and worktree capacity now count the same canonical live-task population through one project admission ceiling (resolveActiveTaskCapacityLimit) with an atomic reserveIfAvailable claim, so planning, execute, and merge lanes cannot each observe and claim the final worktree slot independently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
182e3fdbe8 |
FN-8651: add named provider credential instances
Add provider-instance identity and auth.json storage for multiple credentials per provider. - Export provider-instance key parsing, validation, and reserved metadata helpers. - Resolve, list, mutate, and select named credential instances atomically in auth storage. - Preserve legacy bare-key hydration and document the auth.json instance contract. - Cover provider instance parsing, storage behavior, and concurrent writes. Files changed: .changeset/fn-8651-provider-instances.md | 7 + docs/secrets.md | 8 + .../src/__tests__/oauth-credential-interop.test.ts | 16 ++ packages/core/src/index.ts | 16 ++ packages/core/src/oauth-credential-interop.ts | 15 +- packages/core/src/provider-instance.test.ts | 24 +++ packages/core/src/provider-instance.ts | 65 +++++++ .../src/__tests__/auth-storage-concurrency.test.ts | 13 ++ .../src/__tests__/auth-storage-instances.test.ts | 75 ++++++++ packages/engine/src/auth-storage.ts | 214 +++++++++++++-------- 10 files changed, 367 insertions(+), 86 deletions(-) Fusion-Task-Id: FN-8651 Fusion-Task-Lineage: cfabe496-60f9-4166-a09f-87ea2028cfd9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a3c0501822 |
fix(engine): unblock retained worktree dispatch
Allow queued tasks to reuse worktrees they already hold when the durable worktree ledger is at or above capacity. Preserve independent agent and semaphore limits, and avoid releasing a worktree slot that a rejected transfer never acquired. |
||
|
|
408e558f54 |
FN-8648: audit sync workflow resolver test fixtures
Audit workflow resolver test fixtures so they reflect authoritative async resolution and document intentional sync coverage. - Remove redundant synchronous resolver fixtures from async workflow tests. - Preserve and document the scheduler fixture that exposes the known synchronous listener defect. - Clarify the deliberate default-workflow sync contrast and strengthen recovery fixture intent. Files changed: .../executor-planner-lanes-resolved.test.ts | 45 +++++----------------- .../src/__tests__/planner-lane-resolution.test.ts | 11 +++--- .../planner-lanes-async-resolution.test.ts | 9 ++++- .../recover-approved-intake-post-u11.test.ts | 11 +++--- .../scheduler-renamed-hold-events.test.ts | 9 +++++ .../__tests__/triage-release-renamed-hold.test.ts | 6 ++- .../triage-undeclared-column-rescue.test.ts | 11 +++--- packages/engine/src/__tests__/triage.test.ts | 23 +++++------ 8 files changed, 56 insertions(+), 69 deletions(-) Fusion-Task-Id: FN-8648 Fusion-Task-Lineage: c75fe9ca-30a3-467e-9268-f8d6d95c49e7 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7bdeaa8b0c |
fix(engine): the new worktree ledgers count terminal lanes by NAME — renamed boards stall (#3296)
## Census **Before: `COLUMN guards (the backlog): 2`, `--strict` RED. After: `BACKLOG ZERO`, all five gates green.** Two commits from last night's `maxWorktrees` rollout copied the same holder ledger, both with literals: | commit | file | gate | |---|---|---| | `374956ef23` | `triage.ts` | planning admission | | `6c7467a78d` | `executor.ts` | `fn_spawn_agent` | ```ts t.column !== "done" && t.column !== "archived" ``` ## What it costs Both exclude terminal lanes because a finished card's worktree is **cleanup-owned, not capacity**. On a renamed board neither literal matches, so every finished card keeps counting as a live holder. The count only grows, the gate reaches zero room on a board with free slots, and planning admission is withheld forever / every spawn is refused. That is the **mirror** of the breach these commits fixed, and strictly worse: 8 planners on a 4-slot board is visible; a permanent stall is silent. The recorded reason even names the worktree budget, which the operator then checks and finds has room. ## The conversion `resolveProjectColumnsForRoles(store, ["complete", "archived"])` — project-level, because the ledger spans the whole board with no single task to resolve against. Matches triage's existing use in `sweepStalePlanningStatuses` and executor's at the wip gates. Legacy-seeded, so a default board still excludes exactly `done` and `archived` — byte-identical there. ## Both conversions were UNCOVERED when written Measured with #3214's blinding procedure **before** writing tests: reverting either to the literals left **all 19 tests in the capacity suites green**. Nothing in the tree could tell the conversion from what it replaced — which is how the literals got there in the first place. Each now has a renamed-board case that fails when blinded: ``` triage converted 2 passed | BLINDED 1 failed | 1 passed | restored 2 passed executor converted 8 passed | BLINDED 1 failed | 7 passed | restored 8 passed ``` ## The pairing earned itself immediately Both new cases assert an **absence** (no throttle / no refusal), so each is paired with a positive proving the gate still fires on the same renamed board when a card genuinely holds the last worktree. That caught a real defect in my own fixture: the candidate scan resolves each task's **own workflow selection**, not `listWorkflowDefinitions`, so my first version fell back to the default board where `drafting` isn't a hold lane. No card was eligible, nothing throttled, and the absence assertion **passed for the wrong reason**. The positive failed and exposed it. Recorded at the fixture so the next reader doesn't reintroduce it. ## Verification ``` 42 tests across 6 capacity suites pass check-fnxc-future-dates green check-inert-sync-lane-conversions green check-lane-wiring green check-sql-column-literals green census --strict green (BACKLOG ZERO restored) ``` No changeset: internal engine fix, no published-package surface change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
69bc9fc7ab |
fix(engine): near-duplicate check runs before planning for imported issues
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c19bec3752 |
fix(fnxc): two scheduler stamps to their authoring commits' real times (10 days and 1 day off) (#3294)
Last loose thread from the 2026-07-31 stamp-repointing wave. Two comment lines. ``` line 1027 ConcurrencyAdmission 2026-07-31-09:00 -> 2026-07-21-22:30 ( |
||
|
|
6c7467a78d |
fix(engine): spawned children gate on maxWorktrees too — the spawn note promised both dimensions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
374956ef23 |
fix(engine): planning admission respects maxWorktrees — the merge-drain freeze was masking the missing gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
42a2699b9a |
test(scheduler): pin the worktree-capacity arithmetic — both failure directions (#3288)
## What Pins the **worktree-capacity arithmetic** — the gap #3262 measured, named, and explicitly left for someone to claim. Two commits: a behaviour-preserving seam extraction, then the test. #3262's own scope note: > blinding this predicate to `false` leaves all 22 scheduler suites green (365 tests). The capacity logic it feeds has no behavioural coverage at all. ## Two live defects, opposite directions Both came out of these few lines: - **UNDER-COUNT admits work over the cap.** `maxWorktrees=4`, four planning sessions each holding a worktree, and a replan dispatch admitted as the **fifth** — the ledger counted WIP cards only and never learned to count planners. - **OVER-COUNT self-deadlocks.** A planned Ready card *retains* its planning worktree for execution reuse, so counting it as a holder blocks its own release: `2 wip + 3 idle-held = 5/4`, and the first unpause released 2 of 4 slots' worth of work. **Both are pinned, and the asymmetry is why.** Under-counting breaks the cap and lets real work over it; over-counting only starves dispatch. A test covering the "safe" direction alone would leave the expensive one open. ## Mutation-tested — all four caught | mutation | result | |---|---| | drop the terminal exclusion | 1 failed / 7 passed | | count WIP cards twice | 2 failed / 6 passed | | count cards holding no worktree | 1 failed / 7 passed | | drop the self-slot subtraction | 1 failed / 7 passed | ``` clean: 8 passed (8) scheduler suite: 16 files / 151 tests passed (behaviour preserved by the extraction) typecheck, lint: clean ``` ## Scope, stated rather than implied The terminal predicate is **injected**, not resolved here. Which lanes are terminal is #3262's test; resolving it in this file would make it fail for that reason instead of this one. This pins the **set arithmetic** — who is excluded, and how the total is formed. Still not covered, and I am not claiming otherwise: the *stateful* half of the ledger — the `+= 1` on dispatch and the `Math.max(0, … - 1)` on failure inside `schedule()`'s loop. Extracting that would mean restructuring dispatch itself, which is a different change from this one. ## Process note I claimed this on #3262 **before** starting rather than after, because `scheduler.ts` is the hottest file in the tree and I produced three duplicate PRs earlier tonight by picking up small shared-surface work someone else already had in flight. Announcing first cost one comment; the duplicates cost three PRs and two closes. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved scheduler capacity calculations for tasks that retain existing worktrees. * Prevented WIP tasks from being counted twice. * Excluded completed and worktree-less tasks from reserved capacity. * Corrected candidate capacity calculations when no worktree capacity is reserved. * **Tests** * Added coverage for worktree reservation totals and candidate reuse scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
6834ba35bd |
fix(engine): hung-pass watchdogs on scheduler, merge queue, and continuation drain
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
00769fad7c |
fix(engine): run merges outside the admission drain — every merge froze planning admission
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e51ebff381 |
fix(engine): hung-poll watchdog — triage admission recovers loudly instead of dying silently
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f2f6795010 |
FN-8635: keep worktree slider visible
Keep Command Center capacity controls visible and correctly editable across settings states. - Render Max worktrees in the shared full-width range wrapper. - Preserve capacity values after load failures and explain disabled worktree limits. - Add control tests, browser geometry coverage, documentation, and a release changeset. Files changed: .changeset/fn-8635-worktrees-slider.md | 7 + docs/dashboard-guide.md | 2 +- .../command-center/CommandCenterControls.css | 6 + .../command-center/CommandCenterControls.tsx | 63 +++++--- .../__tests__/CommandCenterControls.test.tsx | 76 ++++++++- packages/engine/e2e/fn-8635-worktrees-slider.mjs | 180 +++++++++++++++++++++ 6 files changed, 310 insertions(+), 24 deletions(-) Fusion-Task-Id: FN-8635 Fusion-Task-Lineage: fb9c2a46-3c5c-4871-bab4-c43af20cb4de Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
94ed527bb1 |
refactor(scheduler): the capacity gate's terminal check uses the shared isTerminalColumnRole (#3262)
**Rebased. The census claim in my original title was overtaken — this is now a correction, not a conversion.** ## Census: 0 before, 0 after #3261 got there first, by **recording** the fallback rather than converting it. Its `DELIBERATE` reasoning is correct and I kept it verbatim. ## What this corrects That note says: > Recorded rather than converted because **there is nothing to convert TO**. There is. **`isTerminalColumnRole` in core is this predicate, term for term** — verified against `column-roles.ts` rather than assumed: | | hand-rolled | `isTerminalColumnRole` | |---|---|---| | flags present | `flags.complete === true \|\| flags.archived === true` | same, via the two role helpers | | flags undefined | `columnId === "done" \|\| columnId === "archived"` | same, via `LEGACY_COMPLETE/ARCHIVED_COLUMN_ID` | And the helper's own doc names this exact case — it exists *"because the pattern `column !== \"done\" && column !== \"archived\"` is the single most repeated shape in the backlog"* and *"keeps callers from re-deriving it and from accidentally dropping one half."* **The rest of #3261's argument stands and is preserved.** The undefined-flags arm is a **live** path, and treating an unreadable workflow as non-terminal would count a finished card's retained worktree against live capacity. That reasoning is about the *fallback's existence*, not about *where the predicate lives* — and the shared helper carries the identical fallback. Second time in this file: `isWipColumnTask` two lines up records that it was itself once *"a hand-rolled copy of `isWipColumnRole`"*. That's an argument for the helper being easy to miss, not for anyone being careless. ## Coverage, stated rather than implied **Blinding this predicate to `false` leaves all 22 scheduler suites green (365 tests)** — the capacity logic it feeds has no behavioural coverage at all. The added test pins the **lane vocabulary** (both renamed terminal lanes, the non-terminal lanes, the legacy fallback). It does **not** pin the capacity arithmetic, which stays unguarded and belongs to that gate's owner. Under-counting is the dangerous direction: the commit adding the gate reports `maxWorktrees=4` with **a fifth worktree admitted**. ## Verification - 23 scheduler suites — **368 green**; `tsc` clean - Census 0 → 0; DELIBERATE count unchanged at 148; inert ratchet green 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c67aafde1c |
revert(fnxc): restore seven author stamps I falsified while chasing a gate bug (#3282)
Closes #3279. Undoes the damage my #3261 did, now that #3277 has landed and made it safe. ## What went wrong #3277 established that last night's "future-dated" stamps were **correct** — the author's local date in a UTC+1 container, written minutes before their commits. The gate compared against the runner's local calendar (PDT) and called them tomorrow. I diagnosed it as author error and repointed seven stamps to turn main green. The values I wrote were **neither the author's local time nor UTC** — invented times chosen to satisfy a broken check. The FNXC record is this project's why-does-this-exist trail, so those stamps misstated when the work happened. ## Restored verbatim | file | mine (wrong) | restored | |---|---|---| | `workflow-column-boundary-capacity.test.ts` | `22:30` | `2026-08-01-00:30` | | `runtimes/in-process-runtime.ts` | `22:20` | `2026-08-01-00:20` | | `scheduler.ts` (`MissionReconciliation`) | `22:00` | `2026-08-01-00:00` | | `workflow-column-boundary-hooks.ts` | `22:20` | `2026-08-01-00:20` | | `workflow-column-boundary.ts` ×2 | `22:20` | `2026-08-01-00:20` | | `workflow-graph-task-runner.ts` | `22:20` | `2026-08-01-00:20` | ## The check that mattered Sequencing was deliberate — #3277 had to land first or this would have re-reddened main. The real question is whether the gate now accepts the **originals**, measured across the rollover boundary at local `2026-07-31 17:23 PDT` / UTC `2026-08-01 00:23`: ``` America/Los_Angeles exit 0 Europe/Paris exit 0 UTC exit 0 Asia/Tokyo exit 0 ``` `123 known future-dated stamp(s), none added`. **No baseline change needed** — #3278's pruning already re-recorded `scheduler.ts`, and these are known stamps rather than new ones. Stamps only: `git diff` shows **zero** non-FNXC lines, 7 insertions / 7 deletions across 6 files. `census --strict` 0, `pnpm test:gate` 0. ## The part worth keeping I argued against exactly this on #3263 — *"it rewrites stamps whose authors are not us"* — and then did it myself six lines later, because I was confident about a cause I had not checked. The commits' timestamps were available the entire time; I read the runner's clock and never asked what timezone the **author** was in. Four of last night's seven PRs were fixing something that was not broken. This is the cleanup for my share of that. |
||
|
|
500f40e65b |
fix: descriptive waiting badges (Queued to revise / Queued behind FN-X) + dependency-free blocked exits replan calmly
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5acd8e987b |
fix(fnxc): three future-dated scheduler stamps — main red through three closed fixes (#3280)
**`check-fnxc-future-dates` exits 1 on `origin/main`.** ``` packages/engine/src/scheduler.ts: 3 future-dated stamps, baseline allows 2 FNXC:ConcurrencyAdmission 2026-08-06-09:00 (six days out) FNXC:WorkflowLifecycleColumns 2026-08-01-05:00 FNXC:WorkflowScheduling 2026-08-01-01:05 ``` All three repointed to `2026-07-31`, times preserved. Gate now exits 0. ## This red has outlived three owners #3270, #3272 and #3274 were each opened against it and each **closed without merging**. Main has been red on this gate for hours while three fixes came and went. Claimed with `check-file-claimed.mjs` before starting — only #3262 touches `scheduler.ts`, and it is a terminal-role refactor rather than a stamp fix, so this was genuinely unowned. ## Why this keeps recurring Seven incidents in roughly two hours. The mechanism, in one line: **the date check runs only in CI** (`pr-checks.yml:66`, no pre-commit or pre-push hook), so every PR is validated against main's baseline *at its own CI time* and cannot see a concurrent or later change. Two PRs stamping the same file both pass, then compose into a red main. One case (#3273) was a stale branch **reverting** an already-merged fix. Patching instances has not converged — this PR is the eighth attempt at the same class. Two structural options, neither of which I am landing unilaterally since the second changes the gate's contract: - run the date check at **author time** (pre-push); it needs no baseline for "is this date in the future", so it cannot be raced - make the date rule **baseline-free** — a future-dated stamp is always wrong, unlike a lifecycle literal that may be a deliberate fallback `2026-08-06` being six days out also suggests these are not off-by-one timezone slips but stamps written from an intended future date. ## Verification - `check-fnxc-future-dates` — **exit 0** (was exit 1 on main) - `scheduler` suites — **148 pass** - `tsc --noEmit` (engine) — 0 errors - comment-only diff, no behaviour change Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
911b7f1c31 |
test(engine): memoize identical full-repo census spawns in lifecycle-column-census
The lifecycle-column-census test file spawned the census CLI ~14 times, each parsing every tracked source file to a TypeScript AST (~2s for ~1960 files). Many spawns were byte-identical, deterministic, read-only real-repo scans: the --json census 4x, the plain report 2x, plus a repeated identical --update-baseline tree-sync across the ratchet cases. Memoize each distinct read-only spawn's output (keyed by argv) and reuse the synced baseline JSON, collapsing duplicate full-AST scans without changing any assertion. File wall-time: 30.5s -> 18.6s (-39%). 57/57 tests still pass. Fusion-Task-Id: FN-slow-test-census |
||
|
|
3f95c6d53e |
fix(engine): a Ready card's retained worktree transfers on release instead of blocking it
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8e6b0ad67e |
test(engine): main is red — the census preconditions were MET, repoint two guards that expired on success (#3260)
## main is red, and this is the second half of it Running the full `engine-default` project on `origin/main` — 754 files, 10,538 tests — returns **3 files / 4 tests failing**. One is the parked-seam audit counter, fixed in #3258. The other three are here. All three assert that lifecycle debt **still exists**. It does not: | assertion | expected | actual | | --- | --- | --- | | `finds >10 literal move targets, so the census is not vacuous` | > 10 | **0** | | `both recoveryRehome groups are non-empty` | > 0 each | **0 / 0** | | `still says ROSE when guards genuinely grew` | exit 1 | **exit 0** (mutation was a no-op) | Nothing regressed. The conversion program drove the engine's literal move-target population to zero and the census baseline to zero entries. **Each guard's premise was "the debt still exists", so each expired the moment the work succeeded — and expired by failing, which reads as a regression in the very thing it was guarding.** The third is the sharpest: it manufactured a rise by finding a baseline entry with more than one guard and zeroing it. With `byFile` empty there was nothing to find, so it mutated nothing, the census correctly passed, and the test asserted exit 1 against a correct pass. ## Repointed, not deleted A vacuity guard must not depend on real debt existing. Two halves: - **Vacuity now runs against a synthetic fixture** — a small in-memory source with three `moveTask` literals (one with `recoveryRehome`, one targeting an undeclared column). The collector is exercised forever regardless of how much real debt remains. This is what keeps the rest honest: at a real population of 0 the zero-assertions are trivially true, and **only the fixture proves they would still fire**. - **The real-tree assertions now assert zero**, so a reintroduced literal move target fails them. Same guarantee as before, pointed at the state the tree is actually in. The ROSE case builds its own one-file tree through the `FUSION_CENSUS_FILE_ROOT`/`FILE_LIST` seam from #3230 rather than borrowing a baseline entry that no longer exists — a rise **constructed** instead of borrowed. It also now asserts the failure is *not* the reclassification wording, which is the distinction that file exists to protect. ## Verification | mutation | expected | result | | --- | --- | --- | | reintroduce a literal `moveTask(id, "in-review")` in the engine | fail | exit 1 ✅ | | blind the collector to return `[]` | fail | exit 1 ✅ | | remove the `ROSE` wording from the census script | fail | exit 1 ✅ | | clean tree | pass | exit 0 ✅ | 60 tests green across the three census suites. All eight ratchets exit 0. Test-only; no changeset. **One honest note on my own method.** My first `ROSE` mutation replaced 1 of the 2 occurrences in the script and the test stayed green — which looks exactly like a dead assertion. It was an ineffective mutation, not a dead test; the manual run still printed `ROSE` from the other occurrence. Re-run against both, it failed. A mutation that does not actually change behaviour proves nothing, and it is worth checking that the mutation landed before concluding the test is dead — the same trap as reading a report-only ratchet's exit 0 as a pass. ## Not in scope `defaultColumnIds()` has a pre-existing type error (`Property 'columns' does not exist on WorkflowIrV1` — union narrowing). Untouched by this PR and the test executes fine; flagging rather than fixing, since it is unrelated to the red. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved census analysis reliability when evaluating guard-count increases and reclassification messages. * Improved detection and classification of literal move targets and declared columns. * Enhanced file path reporting for inputs outside the primary source directory. * **Tests** * Added isolated test scenarios using temporary census data and fixtures. * Strengthened validation of recovery, plain, and declared-column classifications. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
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. |
||
|
|
9094d1640e |
fix(engine): globalPause gates every graph node entry; maxWorktrees counts planning/review holders
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c8a6af13a0 |
test(census): pin file DISCOVERY, which every existing test was blind to (#3259)
Follow-through on the recommendation I made reviewing #3256: **a gate needs a test for its file discovery, not only its matcher.** ## The gap This suite pinned the matcher and never the scan. Every case either feeds the classifier a source string or drives the CLI against the real tree — so **the file list could return empty and all 53 tests would still pass.** Not hypothetical. `git ls-files` lists tracked files only, so a new file with a plain `task.column === "in-review"` scored 0 until staged (#3254). The identical bug then turned up in the move-target ratchet **behind its own 12 matcher tests** (#3256) — I wrote those 12 specifically to stop that gate regressing, and they could not see it, because they import the matcher and never run a scan. ## Four cases, on a synthetic tree Driven through `FUSION_CENSUS_FILE_ROOT` + `FUSION_CENSUS_FILE_LIST`, so discovery is testable without creating files inside a checkout the operator writes to concurrently: - a guard in a scanned file **reaches the classifier** and is counted - `--strict` fails **for the right reason** (message names the file; not an ENOENT fail-closed) - **every** listed file is counted, not just the first - files are read from the **scan root**, so a listed path and a read path cannot diverge ## The second case earns its wording Its first version asserted only `code === 1` — and **passed while discovery was broken.** With the injected list ignored, paths come from the real repo while reads resolve against the fixture root, every read misses, and the gate fails closed with exit 1. Right code, unrelated cause. A test that cannot tell *"found a guard"* from *"could not read anything"* is not testing the ratchet. Asserting the message is what separates them. I found that only by checking which cases the control actually failed — 3 of 4, not 4 of 4. Had I stopped at "the control fails, ship it", I would have added a test that passes for the wrong reason to a suite whose whole purpose is catching tests that pass for the wrong reason. ## Measured | check | result | |---|---| | suite | **57 passed** (53 + 4) | | anti-vacuity: `injectedList` forced undefined | **all 4 fail** (3/4 before strengthening case 2) | | restored | 57/57 | | `census --strict` / `check-fnxc-future-dates` | 0 / 0 | Tests only — no gate or product change. The same four assertions port directly to the other lifecycle gates once each grows the fixture seam; the move-target ratchet is the obvious next one, and its `.mjs` is currently claimed by #3256. |
||
|
|
f3d7b73741 |
test(engine): main is red — re-record the parked-seam audit at 4-of-6, split by resolution path (#3258)
## main is red `workflow-optional-role-param-caller-audit-live-e2e.pg.test.ts` fails on `origin/main` in `engine-default`: ``` AssertionError: expected 4 to be 2 expect(parkedConverted.length).toBe(2); ``` Found by running the whole live-E2E corpus rather than trusting that it passes — 27 files, 159 tests, 1 red. Not in the merge gate, so it has been sitting there. The alarm fired **downward**, exactly as that file was written to: two more call sites started passing `parkedColumns`, and a counter fails when someone *closes* a gap as well as when someone widens it. ## Why I did not just write 4 Re-recording it at 4 would have laundered an inert conversion through the audit written to catch inert conversions. Walking all six sites before touching the number: | site | `parkedColumns` provenance | verdict | | --- | --- | --- | | `agent-heartbeat.ts:1267` | — | unconverted | | `agent-heartbeat.ts:3796` | — | unconverted | | `self-healing.ts:13184` | `await resolveProjectColumnsForRoles` (:13170) | async-resolved | | `self-healing.ts:13294` | `await resolveProjectColumnsForRoles` (:13293) | async-resolved | | `task-agent-sync.ts:243` | `await resolveLinkSyncColumnRoles` (:225) | async-resolved | | `scheduler.ts:1798` | `resolveTaskParkedColumnsSync` (:1797) | **SYNC — INERT** | `scheduler.ts:1798` resolves through `resolveTaskParkedColumnsSync` → `getTaskWorkflowSelectionImpl`, which is `undefined` for every task under PostgreSQL. The resolver then takes its `!workflowId` branch and returns the **default builtin IR** — not `undefined` falling through to a legacy arm, but a real IR resolving real traits, with full confidence. It answers `hold`/`intake` as `todo`/`triage` on every board, exactly as the literal did. Driven proof: `workflow-scheduler-sync-role-conversion-inert-live-e2e.pg.test.ts`. **The shape count (4) and the live count (3) are different numbers, and only the second is about behaviour.** Both are now asserted, plus the sync-resolved site by name so it cannot quietly become "just one of the four". ## A mistake worth recording, because the test caught it My first draft keyed on `await` appearing inside the call window. The argument is nearly always a variable (`[...driftedParkedColumns]`, `roles.parked`) and the `await` lives in that variable's **assignment**, several lines above. That draft classified all four sites as inert — and it **would have passed** had I written the expected number to match what it measured. It failed only because I asserted 2 live from reading the source first, and the mismatch exposed the detector. Classification is now by provenance: take the root identifier, find where the file assigns it, ask whether *that* is awaited. The same bug recurred in my named-site check and failed the same way. That is the whole hazard of this program in miniature — a source-text audit that measures nothing looks exactly like one that measures everything, and it is the *number you expected* that catches it, not the green. ## Not asserted, deliberately `self-healing.ts:13184` is inert for an unrelated reason: its gate is `hasFreshRun || hasActiveExecution` and never reads `shouldPreserveParkedLink`, so its correctly-resolved set decides nothing today. Its own FNXC note says so. Resolution path is mechanically checkable; "the gate never reads the answer" is not, and asserting it on a string match would produce a number nobody could maintain. Recorded as prose in the header. I also did not touch `scheduler.ts`. It is a real defect, not a deferral, but converting it is not this file's job — it is named in the test so the next person converting it is sent here to move it from the inert list to the live one. ## Verification Mutation-verified — a passing audit proves nothing until it has been seen to fail: | mutation | expected | result | | --- | --- | --- | | convert the scheduler site to the async resolver | fail (3/1 → 4/0) | exit 1 ✅ | | drop `parkedColumns` from a converted site | fail (shape 4 → 3) | exit 1 ✅ | | add a new unconverted caller | fail (calls 6 → 7) | exit 1 ✅ | | clean tree | pass | exit 0 ✅ | Full 27-file live-E2E corpus green (159 tests). Ratchets exit 0. Test-only; no changeset. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Expanded end-to-end audit coverage for workflows with optional role parameters. * Improved validation of caller resolution paths, including asynchronous and synchronous scheduling scenarios. * Updated expectations to reflect all supported conversion paths and strengthened verification of scheduler behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
1e50b71255 |
fix(engine): reap leaked fn-verify verification worktrees in the temp-dir sweep
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
623581837a |
fix(engine): mock provider sends 0-based steps — test mode full-task runs complete again (#3231)
Found by a live browser E2E of the coding workflow in test mode: every
scripted full-task run failed at `steps#0:step-execute` with `Step 4 out
of range (task has 4 steps)`, rebounding through recovery forever.
**Root cause:** `fn_task_update.step` has been **0-based since FN-6607**
(executor.ts FNXC:StepNumbering — the old `step - 1` conversion made
Step 0 impossible to mark). `mock-provider.ts` still sent `index + 1`,
so test mode marked steps 1..N instead of 0..N-1: Step 0 (Preflight)
never completed and step N threw out-of-range. Test mode's full-task
path has been broken since June.
**Also fixes the test that pinned the bug:** `mock-provider.test.ts`
expected `{ step: 1 }` for a fixture whose first unfinished step is
index 0 — the expectation encoded the 1-based off-by-one.
Verified: 12/12 mock-provider tests; the live E2E instance completes the
task after this patch (see follow-up screenshot in the session).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
3f06d7201b |
test(engine): assert the evaluator's exact archived-lane set, not toContain (#3232)
## What Follow-up to the #3224 review comment *"reject legacy archived identifiers for renamed workflows."* Test-only. That comment had two halves. **The half it got wrong** is already answered on main: asserting an exact single-column set `["vaulted"]` *fails*, because `resolveProjectColumnsForRoles` unions `LEGACY_COLUMN_IDS_BY_ROLE` in as a documented floor — so a row whose workflow cannot be resolved still classifies. Pinning `["vaulted"]` would encode the opposite of the design. **The half it got right was never addressed.** `toContain` also passes when the set grows a lane nobody intended, and an over-broad archived set silently classifies *live* rows as archived. So the reviewer's worry was legitimate even though the proposed fix was not. The exact set is assertable — it just is not the one the review proposed: | board | resolved set | |---|---| | default | `["archived"]` | | renamed | `["archived", "vaulted"]` — legacy floor + the board's own lane | `[...].sort()` in the helper makes ordering stable, so these pin the resolver's whole answer rather than a substring of it. ## Proven to catch what `toContain` missed Giving the fixture a second `archived`-trait column fails both new assertions: ``` AssertionError: expected [ 'archived', 'cold-store' ] to deeply equal [ 'archived' ] AssertionError: expected [ 'archived', 'cold-store', 'vaulted' ] to deeply equal [ 'archived', 'vaulted' ] Tests 2 failed | 1 passed (3) ``` The previous `toContain` assertions pass unchanged against that same spurious lane. That is the whole justification for this PR — without the injection test it would be a stylistic preference. ``` clean: Tests 3 passed (3) spurious lane: Tests 2 failed | 1 passed (3) lint clean ``` ## Note I said in the review thread I would tighten this, so this closes that loop. I also checked before editing whether another worker had already done it — main already carries the FNXC tag and the legacy-floor explanation from the same review round, so this PR adds only the part still missing rather than redoing settled work. |
||
|
|
d4c25384ae |
test(engine): record what the zero-backlog early return stops testing (#3228)
## A green case that stopped testing what its name says #3226 fixed a red `main` correctly: `fileWithGuards()` now returns `null` at zero backlog, and with nothing to inflate there is no rise to manufacture. Asserting `totals.column === 0` and returning is the honest response. What went unrecorded is the cost. At zero, these two cases: - *"exits 0 and REWRITES the baseline under `--update-baseline`, even when the count rose"* - *"exits 1 and LEAVES the baseline alone on a rise without `--update-baseline`"* no longer exercise the CLI's ordering or exit codes. They assert the backlog is empty and return. **If the write-before-exit ordering regressed — the exact bug those cases were written for — both would still pass.** That matters more than it would elsewhere, because **zero is not a state to wait out.** It is this program's terminal state: the backlog went 126 → 0 and is meant to stay there. So the vacuity is permanent, not transitional. This file already legislates against precisely this, two hundred lines down: > `/* Anti-vacuity: an empty exclusion list would make the assertion below trivially true. */` ## What this PR does Adds a comment on `fileWithGuards()` recording (a) which cases go vacuous at zero and why, (b) that zero is terminal so it will not resolve itself, and (c) the durable fix. **Comment only. No behaviour change — suite stays 53/53.** ## The durable fix, recorded rather than done Point the scan at a synthetic tree so the fixture stops being a function of the real backlog — the same seam `FUSION_CENSUS_BASELINE_PATH` already provides for the baseline, applied to the file list. It needs one CLI correction to work, and that is a genuine bug in my own code regardless of this suite: `triageFindings` and the sync-resolver check read files via `join(REPO_ROOT, f.file)`, where `REPO_ROOT` is derived from the **script's** location. An overridden file list therefore changes which paths are *listed* without changing where they are *read from*, and every read misses with `ENOENT`. I verified that approach works (a three-file fixture yields a stable `1 backlog / 1 deliberate / 1 sync-resolved`) and got two of the six failing cases green with it, then stopped rather than keep guessing in a file being actively revised. Left as a comment so whoever takes it does not re-derive the diagnosis. ## Why this is worth a PR at all This program's recurring failure is instruments that report green while measuring nothing — an inert conversion the census scored as a win, a ratchet wired to nothing, a gate that could not fail. A test asserting `0 === 0` under a name promising ordering coverage is the same shape at the test layer. The suite cannot be fixed in this PR without re-opening work someone else owns, but it can at least stop being silent about it. ## Census before / after ``` before: COLUMN guards (the backlog): 0 after: COLUMN guards (the backlog): 0 ``` ## Verification `test:gate` exit 0 · `lifecycle-column-census.test.ts` **53 passed** · `fnxc-future-dates`, `lifecycle-columns`, `inert-sync-lanes`, `quarantine-ledger`, `inert-flag-seams`, `lane-wiring`, `sql-column-literals` — all exit 0. |
||
|
|
12c4ab5a6e |
test(engine): pin the evaluator's archived-lane read — the service had no test at all (#3224)
## What Pins the **evaluator's archived-lane read**. Test-only — no product change. `HybridEvaluatorService.evaluateTask` resolves the board's archived lanes and hands them to `collectDeterministicSignals`, which decides which of a task's related rows count as archived when scoring a run. **The service had no test anywhere in the repo.** Four test files import the module; none construct or exercise it. So this conversion was unobservable for the simplest possible reason — *nothing ran the code*. That is a different failure from the ones this audit has been finding (harnesses that run the code but cannot see the difference), and worth distinguishing: no amount of fixture care helps when the entry point is never called. ## Measured | | default (control) | renamed | differential | |---|---|---|---| | converted | pass | pass | pass | | blinded to `["archived"]` | pass | **FAIL** | **FAIL** | ``` converted: Test Files 1 passed (1) / Tests 3 passed (3) blinded: Test Files 1 failed (1) / Tests 2 failed | 1 passed (3) the 4 files importing evaluator.ts, plus this one: 5 files/76 tests, all green lint clean; fnxc-future-dates: none added; census unchanged ``` Per the rule I documented in #3223, the blind was confirmed applied with `git diff --stat` **before** the run rather than trusting the tool's exit code. ## What breaks without it On a board whose archived lane is `vaulted`, the evaluator hands the collector the legacy `{archived}` set. Rows resting in `vaulted` are not recognised as archived, and the deterministic half of every evaluation score is computed from a wrong picture of the task's history. **Nothing errors, the run completes, the number is just wrong** — which is why it survived unnoticed. ## Pinned without faking a provider response The assertion is about what the collector *receives*, which is decided before any model call. `collectDeterministicSignals` is mocked to record its arguments and throw a sentinel; the test asserts the resolved lane set and stops. This is deliberate over the obvious alternative of feeding `runPrompt` a canned AI payload: `deps.runPrompt` is injectable so either approach is offline, but a canned payload has to satisfy `parseAiResponse` and every `EVAL_SCORE_CATEGORIES` entry, and would silently rot into a maintenance burden on a test whose subject is one `Set`. Reversible if someone later wants full end-to-end evaluator coverage — that is a different test, not this one. ## Completes the engine audit With this, every `resolveProjectColumnsForRoles` call site in `packages/engine` has been blinded: | file | resolvers | result | |---|---|---| | `self-healing.ts` | 64 | 21 pinned, 1 recorded inert by construction, remainder mapped | | `executor.ts` | 2 | both already covered | | `scheduler.ts` | 1 | uncovered → pinned (#3219, merged) | | `triage.ts` | 1 | uncovered → pinned (#3221) | | `restart-recovery-coordinator.ts` | 1 | already covered | | `notification-service.ts` | 1 | already covered | | `evaluator.ts` | 1 | uncovered → pinned (this PR) | `project-engine.ts:5154` takes `roles` as a **parameter**, so it is a generic wrapper with no fixed role set to blind — flagged rather than guessed at; its callers are where the question belongs. **`packages/core`'s 17 files remain entirely unaudited** and I am claiming nothing about them. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added regression coverage to verify reliable resolution of archived workflow lanes. * Covered both the default archived-lane name and custom renamed configurations. * Confirmed compatibility with legacy archived-lane naming behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3c7dd9a803 |
test(engine): keep census regressions valid at zero backlog (#3226)
## Summary - keeps lifecycle-census end-to-end fixtures valid after the conversion backlog reaches zero - treats zero backlog as a real protected end state instead of requiring a remaining guard or claim target - preserves nonzero rise/claim assertions when guards remain ## Test plan - `corepack pnpm --filter @fusion/engine exec vitest run src/__tests__/lifecycle-column-census.test.ts --silent=passed-only --reporter=dot --project=engine-default` - `corepack pnpm --filter @fusion/engine typecheck` - verified the same suite at zero backlog on the aggregate runtime (53/53) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved lifecycle validation to handle empty backlogs without errors. * Added coverage for zero-item results and changing file sets. * Enhanced baseline and trend checks to report completed backlogs consistently. * Improved resilience when expected result entries are unavailable, ensuring validation completes cleanly with accurate zero counts. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
719cf281fd |
test(engine): pin the startup stale-planning sweep's planner-lane read (#3221)
## What Pins the **startup stale-planning sweep's** planner-lane read in `triage.ts`. Test-only — no product change. A card can hold `status: "planning"` while triage specifies it in place. A crash or restart before planning completes leaves that status set, and a startup sweep clears it. If the sweep misses the card, it occupies a planning admission slot **permanently** and new triage work is never admitted. The lane read was converted from `resolvePlannerLanes(store, "")` — called with an **empty task id**, so it could never resolve a task and always answered with the default board — to a project-level `resolveProjectColumnsForRoles(store, ["intake", "hold"])`. **No test could observe that conversion.** All 25 existing triage test files stayed green (375/375) with the resolver neutered — under *both* blinds tried. ## Measured | | default (control) | legacy ids | renamed | differential | |---|---|---|---|---| | converted | pass | pass | pass | pass | | blinded to empty set | pass | pass | **FAIL** | **FAIL** | | blinded to legacy pair | pass | pass | **FAIL** | **FAIL** | ``` converted: Tests 4 passed (4) blinded to empty set: Tests 2 failed | 2 passed (4) blinded to legacy: Tests 2 failed | 2 passed (4) existing 25 files: green under BOTH blinds (only this new file fails) triage suite: 25 files/375 tests -> 26 files/379 tests, all green lint clean; fnxc-future-dates: none added; census unchanged ``` ## A prediction I got wrong, and what it changes The site is **seed-then-union**: ```ts const sweepColumns = [...new Set(["triage", "todo", ...projectPlannerColumns])]; ``` I expected blinding the resolver to its legacy pair `["triage","todo"]` to be a **no-op by construction** — the seed already contains both. It is not. That reasoning holds only for a *default* board; on a renamed board the resolver is the sole contributor of `drafting`, so the legacy blind drops it and the renamed cases fail. The corrected rule, which is narrower than the one I was carrying: **a seed-then-union site hides a defect only while every lane you assert on is already in the seed.** Assert on a lane that is not, and the union stops protecting it. The empty-set blind remains the stricter of the two because it also models a resolver returning nothing at all. Both are recorded in the test file so the next person does not re-derive it. ## A tooling failure worth naming My first triage measurement reported **375/375 green under blinding** — and was a lie. The blinding script had no mapping for `["intake","hold"]` and exited `2` **silently**; the `&&` short-circuited the check while a `;` let vitest run against **unmodified source**. A no-op blind produces a green run that is indistinguishable from real coverage. The script now prints what it substituted and where, fails loudly on an unmapped role or a missing variable, and I self-tested both directions (bogus var → rc 2 with a message; real var → rc 0 with the substitution echoed) before trusting any number above. This is the same standard I have been applying to other people's guards — *a guard that reports success without checking anything is worse than no guard* — and my own instrument failed it. ## Also pinned The **legacy half** of the union. `triage`/`todo` stay in the sweep even on a board whose workflow declares neither, because pre-U11 and Coding (Ideas) rows can rest there. Dropping them in favour of the resolved lanes alone would strand exactly those rows, so there is now a case asserting it. ## Flagged, not guessed - The FNXC stamp at `triage.ts:987` reads `2026-07-31-23:59`, hours ahead of the `date -u` clock. It is one of the 181 grandfathered stamps so the gate is green; I left it rather than widen this PR. |
||
|
|
9ab9822b8d |
test(engine): pin the deleted-blocker WIP-lane read, which no test could see (#3219)
## What Pins the deleted-blocker sweep's **WIP-lane read** in `scheduler.ts`. Test-only — no product change. When a task is soft-deleted, a `task:deleted` listener clears `blockedBy` on every dependent so the work can be scheduled again. It reads two lanes to find them: hold and WIP. The WIP read was already converted to `resolveProjectColumnsForRoles(store, ["countsTowardWip"])`. **Blinding that resolver back to the literal `["in-progress"]` left all 14 existing scheduler test files green — 145/145.** The conversion was load-bearing and unpinned. ## Why nothing could see it Two independent harness properties, **either one sufficient** to hide the defect: 1. **`resolveProjectColumnsForRoles` returns legacy ids and nothing else when the store has no `listWorkflowDefinitions`** — an intentional degrade in `project-lane-vocabulary.ts` so an unreadable workflow list cannot fail a sweep. The shared scheduler harness does not define it, so *the resolved set and the literal set were equal by construction* in every existing test. 2. **`listTasks` was mocked as `vi.fn(async () => tasks)`**, ignoring its `column` filter. A mock that returns every task regardless of the lane asked for cannot detect a wrong lane. This is worth naming because #1 is not a test bug — it is correct production behaviour that happens to erase the difference a test is trying to measure. A harness can satisfy a conversion's *shape* while making its *effect* unobservable. There is a test file named `scheduler-renamed-dependency-and-review-lanes.test.ts` covering dependency satisfaction, file-scope leases, base-branch stacking, PR hydration and mission completion on a renamed board. It does not reach this sweep, and its harness has both properties above. ## What breaks without the conversion On a board whose WIP lane is `building`, the literal read asks for `in-progress`, finds nothing, and the in-flight dependent is never reconciled. It keeps `blockedBy` pointing at a task that no longer exists — **permanently**, because the blocker can never be completed or re-deleted to trigger another sweep. Work stops with nothing to rescue it. ## Measured | | default (control) | renamed | differential | |---|---|---|---| | converted | pass | pass | pass | | blinded to `["in-progress"]` | pass | **FAIL** | **FAIL** | ``` converted: Test Files 1 passed (1) / Tests 3 passed (3) blinded: Test Files 1 failed (1) / Tests 2 failed | 1 passed (3) scheduler suite: 14 files/145 tests -> 15 files/148 tests, all green lint: clean fnxc-future-dates: none added ``` The default-vocabulary control passes in **both** columns by design: it is what keeps a generic break in this path from hiding in the renamed assertion. ## Census Unchanged — **2 / 148 deliberate**. `scheduler.ts` was already at 0. This PR adds coverage, not conversions; the census counts comparisons and would not have moved either way, which is the same blind spot that let #3078 merge green. ## Flagged, not guessed - The `resolveTaskParkedColumns` **hold** read one line above (`scheduler.ts:1402`) is the other half of this pair. My scenario places its dependent in the WIP lane, so it does not exercise the hold read and I am not claiming it is pinned. - The FNXC stamp at `scheduler.ts:1404` reads `2026-08-01-05:00`, which is future-dated. It is one of the 181 grandfathered stamps, so the gate is green and I left it alone rather than widen this PR. |
||
|
|
3146a745bf |
test(engine): cover two reporter resolvers that no test could tell from the literal (#3217)
## What Applied #3214's blinding procedure **outside `self-healing.ts`**, where that measurement has never been run. Two of the five resolvers across the two reporters were uncovered; this covers both. ## The measurement One resolver at a time, blinded back to its legacy ids, against each file's existing suite: | site | blinded to | result | |---|---|---| | `backlog-pressure-reporter.ts:87` hold | `["todo"]` | 2 failed — covered | | **`backlog-pressure-reporter.ts:88` wip** | `["in-progress"]` | **0 failed of 11 — UNCOVERED** | | `backlog-pressure-reporter.ts:89` terminal | `["done","archived"]` | 1 failed — covered | | `stale-task-reporter.ts:59` wip | `["in-progress"]` | 1 failed — covered | | **`stale-task-reporter.ts:60` review** | `["in-review"]` | **0 failed of 7 — UNCOVERED** | Both uncovered resolvers sit in a `Promise.all` **beside one that is covered**, so each sweep reads as converted while half of it was held by nothing. That is rule 1 in the doc — coverage is per-resolver, not per-sweep — and it is why the census cannot answer this: a syntactic scan sees five resolved sites and five is what it counts. `stale-task-reporter.ts` is the sharper case. Its describe block **already declared `signoff` in the fixture IR** and no case ever put a card there, so the review resolver was decorative. ## What they cost on a renamed board - **wip** feeds `inProgressCount`, the *denominator* of `ratio = todoCount / max(inProgressCount, 1)`. Against the literal, busy work in a renamed lane counts as **zero**, the ratio inflates, and the backlog-pressure alert fires on a queue that is draining normally — the operator is paged that the board is jammed while agents work through it. - **review** decides which rows the staleness read *fetches at all*. A review stalled for days in a renamed lane is never queried and never surfaced — precisely the condition this reporter exists to report. ## Following the four rules **Rule 2 — the fixture reaches the guarded branch.** 12 hold cards over 2 wip cards is a ratio of 6, *under* the default threshold of 10, so the correct answer is "no alert"; blinding collapses the denominator to 1, the ratio becomes 12, and it alerts. A fixture whose ratio cleared the threshold either way would exercise the sweep and never touch the line under test. **Rule 3 — assert the path-specific side effect.** `upsertInsight` not called, and `logEntry` called with `column=signoff`. Asserting `alerted === false` alone would also pass if the run bailed for an unrelated reason — missing insight store, cooldown, too few candidates — none of which involve the wip lane. **Rule 4 — the store fake honours `options.column`.** Both harnesses already did; reused rather than replaced. Each new case is paired with a negative so it cannot pass vacuously: the "does not alert" case is backed by a *same renamed board still alerts when in-progress work really is thin* case, so a reporter broken into never firing fails. ## Census **Unchanged — `CONVERSION QUEUE EMPTY`, `AVAILABLE: 0` before and after.** This converts nothing. It closes coverage on conversions the census already counts as done, which is the gap #3214 names: *"the census counts comparisons; it cannot tell a working conversion from one a later merge silently reverted."* ## Verification Blind-verified in both directions — blinding each resolver fails **exactly** the new case and nothing else: ``` backlog-pressure BLIND wip -> 1 failed | 12 passed (13) restored: 13 passed stale-task BLIND review -> 1 failed | 7 passed (8) restored: 8 passed combined 21 passed (2 files) ``` No changeset: test-only, behavior-preserving, no published-package surface. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cfcbba6f81 |
fix(census): 4 RED ratchet tests on main, and the report said nothing at zero (#3218)
Two problems, both caused by the backlog actually shrinking. ## 1. Four failing tests on main **Pre-existing, not introduced here** — running this file on clean `origin/main` gives `49 passed / 4 failed` with identical messages. I checked that before touching anything, because the failures surfaced while I was editing the same file. The ratchet cases build their fixture like this: ```ts Object.entries(baseline.byFile).find(([, c]) => c > 1) // needs a file with MORE THAN ONE guard ``` After the tail reclassification no such entry exists. `find` returns undefined → `byFile[undefined] = NaN` → the baseline is corrupt → every case fails with `expected … to contain 'TIGHTENED'`, a message that points squarely at the CLI when the **fixture** is at fault. That misdirection is why this sat red. The ratchet doesn't care *which* file it tightens, only that an allowance exceeds the measured count. So `inflate` now takes any entry, and synthesises one against a real scanned file when the backlog is empty. `deflate` is the harder half: a RISE needs an allowance **below** the real count, and once every measured count is 0 the only value below is negative. The empty case uses `-1`. That is not a realistic baseline value and the comment says so — it is the sole way to exercise the `measured > allowed` comparison against a tree with nothing left to count, which is the tree this suite now runs on. Same class as the unbounded-slice rot in #3207: **census self-tests coupled to the size of a shrinking backlog.** That is now twice, so it is a pattern rather than an accident. ## 2. The report went silent at the finish line The verdict was two inline branches and neither fired at zero — `CONVERSION QUEUE EMPTY` required `totals.column > 0`. So the one state the entire fleet phase was working toward printed **nothing**, which reads as a broken scan rather than the protected end state. Extracted to a pure `describeBacklogState({ columnGuards, unexaminedGuards })` returning lines, so the caller stays a dumb printer: ``` BACKLOG ZERO: no lifecycle-column guard remains. This is the protected end state, not an empty scan — `--strict` fails on any RISE, so a new guard cannot land silently. Use the role helpers (resolveLifecycleColumns / columnHasRole). ``` Pure **specifically** so the zero state is testable before the tree reaches zero. While it was inline, only the *current* backlog state was observable — and a message nobody can test before they need it is the one that is wrong when they do. ## Evidence | check | result | |---|---| | census test file | **53 passed** (was 49 passed / 4 failed) | | behaviour on today's tree | **unchanged** — identical `CONVERSION QUEUE EMPTY` block | | empty-baseline probe | exits 1, `column-guard count ROSE` | | forced zero verdict | prints `BACKLOG ZERO … not an empty scan` | | `--strict` / `check-fnxc-future-dates` / eslint | 0 / 0 / clean | | `pnpm test:gate` | exit 0 (744 tests) | Four new tests pin all three states, including that the unexamined branch must **not** claim the queue is empty while real work is outstanding. ## Census No guard converted — this is tooling and test repair. Backlog unchanged at 1, which #3215 takes to 0. |
||
|
|
c66b434b7b |
fix(self-healing): a renamed hold lane re-logged the same overlap blocker on every sweep (#3216)
## The defect `clearStaleBlockedBy` keeps a per-task memo of which overlap blocker it already logged, so a sweep running every few seconds doesn't repeat the same line forever. The memo was retained only while the card sat in a column matching the literal `todo` — so on a renamed board it was dropped on **every** sweep and `still blocked by file scope overlap with <id>` was re-logged each time. ## Census before / after | | before | after | |---|---|---| | COLUMN guards (backlog) | 9 | **8** | | `packages/engine/src/self-healing.ts` | 1 | **0 — converted** | Baseline re-recorded in the same commit; `--strict` green. (Counts follow #3215, which took 10 → 9.) ## The stated blocker was not real The note here declined the conversion because the lane prefetch is keyed on `candidates`, *"which this closure helps build"*. Measured — it does not: ``` 6033| for (const task of blockedTasks) candidates.set(task.id, task); 6034| for (const task of queuedDependencyTasks) candidates.set(task.id, task); 6036| for (const [taskId, lastLoggedBlockerId] of this.preservedQueuedOverlapLogged) { <- only CLEARS memos ``` `candidates` is fully populated two statements earlier, and this loop only clears memo entries. So the prefetch was hoistable; it now sits above the loop. That is a pure move of a read-only computation with no conditional between the two positions. Reaching the lane clause already proves the id is a candidate — `!candidates.has(taskId)` is the first arm of the same `||` chain, so short-circuit means the lane question is only asked for ids the prefetch covered (`referencedIds.add(task.id)` runs for every candidate). `lanesOf` still falls back to the legacy set, so an unresolvable workflow answers exactly as the literal did. This is the second inherited "too expensive" estimate to fail on inspection this session (see #3215). Both were written in good faith and both were checkable in a few minutes. ## One thing typecheck caught that review would not have `memoTask?.column !== "todo"` was **also** the undefined check, and tsc narrowed the later clauses on it. Replacing it without that arm compiled clean to the eye but broke narrowing — `TS18048: 'memoTask' is possibly 'undefined'` on the next line. `|| !memoTask` is now explicit rather than implied. ## Evidence The test drives the sweep **twice**, because a single pass cannot observe a dedup memo at all. | | pre-fix literal | converted | |---|---|---| | `still blocked by file scope overlap` log lines | **2 — FAILS** | **1 — passes** | Failure message against the pre-fix code: `expected [ [ 'FN-DEPENDENT', …(1) ], …(1) ] to have a length of 1 but got 2`. Worth correcting the record: the note called the cost *"a duplicate log line, not a wrong lifecycle decision"*. The lifecycle half is right — but it is a duplicate on **every sweep**, so it is recurring log spam, not a one-off. That is a bigger cost than the note implies, though still not a correctness bug. | check | result | |---|---| | `census --strict` / `check-fnxc-future-dates` | exit 0 / exit 0 | | `eslint` / engine `tsc --noEmit` | clean / exit 0 | | self-healing + overlap suites | 15 / 21 / 6 passed | | `pnpm test:gate` | exit 0 (744 tests) | Reused the existing `RENAMED_BOARD_IR` harness in that file rather than building a new one. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved cleanup of stale workflow blockers, including renamed workflow lanes. - Prevented duplicate overlap warnings during repeated cleanup. - More reliably preserves valid queued overlaps while ignoring missing or inactive tasks. - **Tests** - Added regression coverage for repeated stale-blocker cleanup and duplicate warning prevention. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
aa1655ccd9 |
fleet: reclassify the census tail — 10 → 2 guards, all reasoning already in the code (#3213)
## Census before / after
```
before after
COLUMN guards (backlog) 10 2
DELIBERATE-LITERAL 138 148
```
Baseline re-recorded in the same commit; `--strict` green.
## This converts nothing — the tail was never backlog
All ten remaining guards already carried an explicit in-code decision.
**None carried the `DELIBERATE-LITERAL` marker the census reads**, so
each re-appeared to every fleet pass as if unexamined. That is the whole
defect this fixes.
| site | the reasoning already at the site |
| --- | --- |
| `audit-ops.ts`, `moves.ts` | the degraded fallback arm of an
**already-converted** site; the live arm uses the resolved lane set |
| `scheduler.ts` ×2 | *"LEFT COUNTED"* — an await behind the
`tracked.has` re-entrance guard lets two updates double-start a monitor;
the sibling is the measured-expensive `task:updated` emit path (26 sites
against 7) |
| `notification-service.ts` | this method and its only caller are
**sync**, reached from a listener the store invokes as `(task: Task):
void`; resolving makes the chain async and reorders notification
classification against every other `task:updated` handler |
| `lifecycle-ops.ts` | *"Recorded rather than converted"* — dead code |
| `task-id-integrity.ts` | sync, no store-scoped read; converting alone
would disagree with `getLiveTaskColumn` |
| `triage.ts` | *"LEFT COUNTED until then"* — wants a non-sync-resolved
lane answer |
## Marker placement is load-bearing, and I got it wrong twice
The census reads a node's **leading** comments. A marker in a nearby
block comment attaches to the wrong node and is **silently ignored** —
it reads as reviewed while the count still lists the site.
- `task-id-integrity.ts` — my first marker went into the block comment
above the `const`; the literal is in the `return`. Count stayed at 1
until I moved it.
- `ResearchTaskActionModal.tsx` — marker added, **measured that it did
not register**, reverted.
Every edit was verified by re-running the census, not assumed. That is
the only reason the count actually moved.
## Two sites deliberately left counted
- **`ResearchTaskActionModal.tsx`** — the literal sits mid-expression
inside a `.then()` chain, so no marker can attach. The census's own
guidance is to hoist it into a named helper; the site's note asks for
that to be someone's deliberate change rather than a drive-by, so it
stays counted and honest.
- **`self-healing.ts`** — the memo closure I converted and reverted in
#3049. Its note: a renamed board costs a duplicate log line, not a wrong
lifecycle decision.
## Correction I owe on the measurement itself
For many turns I reported "zero unclaimed guards". That came from a bug
in **my own** query — `byFile` is an array of `[file, count]` pairs and
I had switched to `Object.entries()`, which yields `[index, pair]`, so
`n > 0` was always false and the filter returned zero regardless of
state. It agreed with reality while open PRs held every file, which is
why it went unnoticed; it was still wrong, and a constant zero against a
falling backlog should have prompted me to check it sooner.
## Verification (measured)
- engine `self-healing` + `scheduler` suites — **1003 passed / 56
files**
- core `task-id` / `moves` suites — green
- `tsc --noEmit` clean in core, engine and dashboard; `eslint` clean
- `pnpm test:gate` — green
- `lifecycle-column-census --strict`, `check-lane-wiring`,
`check-sql-column-literals`, `check-fnxc-future-dates` — green
No changeset: `@fusion/core`, `@fusion/engine` and `@fusion/dashboard`
are private, and no runtime behaviour changes.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Clarified internal annotations for archived, in-progress, and
in-review workflow states.
* Documented fallback behavior and timing safeguards across lifecycle,
scheduling, notification, and triage flows.
* **Chores**
* Updated internal lifecycle tracking baselines to reflect current
annotations and state coverage.
* **Bug Fixes**
* No user-visible behavior changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
f94ff68391 |
docs(engine): record that agentParkedColumns is inert at this call site (passed-but-unread) (#3212)
Closes the last entry I could not pin on the #3115 coverage map — by establishing **why** it cannot be pinned, rather than leaving it open or forcing a green test around it. ## `agentParkedColumns` is inert at this call site It influences exactly one output — `shouldPreserveParkedLink` — and `recoverAgentsRunningOnInactiveTasks` **never reads it**. The gate is: ```ts if (proof.hasFreshRun || proof.hasActiveExecution) continue; ``` Neither depends on the lane. So passing a resolved set changes nothing today, and blinding it back to the legacy ids leaves every test green **because there is no behaviour to observe**. That is not a coverage gap — there is nothing there to cover. ## Kept, not deleted - Removing it makes this call site read as **unwired** to the lane-wiring ratchet, inviting the next worker to "fix" it by re-adding exactly this. - If the gate ever adopts `shouldPreserveParkedLink` — the parked-specific semantics the sibling sweep uses (`recoverDriftedAgentTaskLinks`, wired in #3208 an hour ago) — the resolved set is already correct here. ## The shape worth naming **Passed-but-unread** is the mirror of the **resolved-gate, literal-branch** defect #3208 fixed. Both read as converted while deciding nothing — and only one of them is a bug. A ratchet that counts call sites cannot tell them apart: #3208's site looked *unwired* and was a live defect; this one looks *wired* and is dead code. That is why the distinction belongs in a comment at the site rather than in a baseline number. ## Map status **21 of 26 pinned**, 1 established as unpinnable-by-construction, 4 remaining with obstacles recorded: - `reclaimHoldColumns` / `reclaimReviewColumns` — both audit paths emit the same `branch:auto-reclaim` type, differing only by a `trigger` string the branch-level scan also produces. - `completedHoldColumns`, `wsDoneColumns`, `doneMetaColumns` and others have since gone green from other workers' PRs. ## Verification `pnpm test:gate` 13 + 161 + 499 + 71 · lint · census `--strict` · fnxc-dates (TZ=UTC) — green. Comment-only change. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added an explanatory note clarifying parked-column handling and its connection to future parked-link preservation behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |