ea53cbd4ff3682981b840a48a2b65dc105d9b72e
596 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c7451ec6b2 |
chore(release): v0.76.0-beta.2
Version bump via changesets. |
||
|
|
366dfe123e |
FN-9015: refresh Grok runtime model example
Refresh the Grok runtime README with the current flagship model invocation. - Update the optional Grok ACP command to use grok-4.6. - Document why the illustrative model identifier tracks the provider default. Files changed: plugins/fusion-plugin-grok-runtime/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-9015 Fusion-Task-Lineage: 6dd04810-fe06-4462-82b9-5abb38e229c0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
36691e54d2 |
chore(release): v0.76.0-beta.1
Version bump via changesets. |
||
|
|
f038d04dc5 |
FN-8986: stage computer-use skill only on Darwin
Stage the bundled computer-use discovery skill exclusively for supported macOS runtimes. - Gate Claude and Grok session skill resolution and staging by platform. - Install the Hermes discovery skill on Darwin while preserving user overrides and non-Darwin suppression. - Document runtime discovery behavior, add coverage, and add a minor release changeset. Files changed: .../darwin-only-computer-use-skill-staging.md | 7 + docs/computer-use.md | 10 +- .../custom-provider-routes-hermes-additive.test.ts | 2 +- .../src/__tests__/skill-loader.test.ts | 197 +++++++++++++++++++++ .../src/skill-loader.ts | 145 ++++++++++----- .../src/__tests__/skill-loader.test.ts | 51 +++++- .../fusion-plugin-grok-runtime/src/skill-loader.ts | 145 ++++++++++----- .../src/__tests__/fusion-skill-install.test.ts | 62 ++++++- .../src/__tests__/index.test.ts | 37 +++- .../src/fusion-skill-install.ts | 151 ++++++++-------- plugins/fusion-plugin-hermes-runtime/src/index.ts | 21 ++- 11 files changed, 649 insertions(+), 179 deletions(-) Fusion-Task-Id: FN-8986 Fusion-Task-Lineage: 373d558a-1d0a-4737-99be-f53189a9aeaf Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
0eaa3c7b9a |
FN-8990: clean up plugin reload scratch files
Keep plugin hot-reload cache-busting copies ephemeral and remove legacy tracked artifacts. - Delete tracked plugin reload scratch files. - Remove temporary import copies after successful and failed reloads. - Add cleanup and tracked-artifact regression coverage. Files changed: .changeset/fn-8990-plugin-reload-scratch-cleanup.md | 7 ++ packages/core/src/__tests__/plugin-hot-reload.test.ts | 25 ++++ - packages/core/src/plugins/plugin-loader.ts | 16 ++- plugins/fusion-plugin-droid-runtime/src/.index.reload-1.ts | 84 ---------------- plugins/fusion-plugin-droid-runtime/src/.index.reload-2.ts | 84 ---------------- plugins/fusion-plugin-hermes-runtime/src/.index.reload-1.ts | 108 -------------------- plugins/fusion-plugin-hermes-runtime/src/.index.reload-2.ts | 108 -------------------- plugins/fusion-plugin-hermes-runtime/src/.index.reload-3.ts | 108 -------------------- plugins/fusion-plugin-openclaw-runtime/src/.index.reload-1.ts | 95 ----------------- plugins/fusion-plugin-paperclip-runtime/src/.index.reload-1.ts | 112 --------------------- scripts/__tests__/no-tracked-plugin-reload-artifacts.test.mjs | 27 +++++ 11 files changed, 72 insertions(+), 702 deletions(-) Fusion-Task-Id: FN-8990 Fusion-Task-Lineage: 2221ebaf-b8ab-4a3c-8934-addfa348c98c Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
878db6dca7 |
FN-8951: repair script-test governance drift
Keep test-shard timing governance aligned with the current workspace and workflow seams. - Add a safe timing-snapshot pruning mode with coverage. - Align Todo plugin Vitest isolation and Docker dependency manifests. - Refresh workflow reliability evidence and remove deleted test timings. Files changed: Dockerfile | 7 ++- docs/testing.md | 9 ++- plugins/fusion-plugin-todos/vitest.config.ts | 26 ++++++-- scripts/__tests__/ci-test-shard-timings.test.mjs | 71 ++++++++++++++++++++++ scripts/ci-test-shard.mjs | 65 ++++++++++++++++++-- .../lib/workflow-reliability-release-check.json | 22 +++---- scripts/test-timings.json | 21 ------- 7 files changed, 175 insertions(+), 46 deletions(-) Fusion-Task-Id: FN-8951 Fusion-Task-Lineage: fbf7e79f-4cb2-43e2-9982-09f3f94de70d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a3e37757e6 |
fix: default --no-auto-update off to fix Grok ACP startup on CLI v1.0.0 (#3390)
The released Grok CLI (v1.0.0, latest stable) does not recognize the --no-auto-update flag and exits immediately with error: unexpected argument. This causes Fusion to report 'ACP connection closed' when spawning grok agent stdio. buildGrokAcpArgs previously defaulted noAutoUpdate to true (via !== false). Changed to opt-in (=== true) so the flag is only passed when explicitly enabled. Updated acp-settings.test.ts assertions accordingly. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Grok ACP startup no longer disables automatic updates by default. - Automatic update prevention is applied only when explicitly enabled in settings. - **Tests** - Updated startup argument validation to reflect the revised default behavior. - **Documentation** - Added release notes documenting the change. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com> |
||
|
|
ec52a9131a |
FN-8859: add native roadmap embeds to chat
Enable roadmap structures to open, preview, and embed directly from chat and dashboard views. - Register roadmap views as native structures with chat-reference support. - Add composer previews, in-place dashboard embedding, and drag-safe interactions. - Document the native-structure plugin contract and add coverage across supported surfaces. Files changed: .changeset/fn-8859-roadmap-native-structure-embeds.md | 7 + docs/PLUGIN_AUTHORING.md | 1 + docs/dashboard-guide.md | 6 +- packages/dashboard/app/components/MessageComposer.tsx | 16 +- packages/dashboard/app/components/__tests__/MessageComposer.test.tsx | 102 ++++++++++- packages/dashboard/app/components/__tests__/overflowViewRegistry.test.tsx | 68 +++++++- packages/dashboard/app/components/dashboard/MainContent.tsx | 22 ++- packages/dashboard/app/components/dashboard/__tests__/MainContent.native-structure-roadmap.test.tsx | 188 +++++++++++++++++++++ packages/dashboard/app/components/nativeStructureChatRef.ts | 4 +- packages/dashboard/app/components/overflowViewRegistry.tsx | 34 ++-- packages/dashboard/app/plugins/types.ts | 10 +- packages/dashboard/app/utils/__tests__/nativeStructureDrag.test.ts | 46 +++-- packages/dashboard/app/utils/nativeStructureDrag.ts | 18 +- plugins/fusion-plugin-roadmap/src/dashboard-view.tsx | 7 +- plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx | 22 ++- plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx | 51 ++++++ 16 files changed, 560 insertions(+), 42 deletions(-) Fusion-Task-Id: FN-8859 Fusion-Task-Lineage: 99a7292b-2a06-4325-8131-ca74431214e3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
03f84d406c |
chore(release): v0.76.0-beta.0
Version bump via changesets. |
||
|
|
039db99307 |
fix: restore Todos and Roadmaps plugin nav entries via manifest dashboardViews
Both plugins declared dashboardViews only in their src/index.ts module. PluginLoader.getCurrentManifestDashboardViews treats a successfully-read manifest as authoritative and returns an empty list when the key is absent, and getPluginDashboardViews only falls back to the module definition when the manifest read fails. The module-level entries were therefore discarded and neither view appeared on any nav surface -- header overflow, desktop sidebar, or mobile More sheet all consume the same array from usePluginDashboardViews. Mirror the manifest shape used by fusion-plugin-compound-engineering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
64413f4e8d |
chore(release): v0.75.1
Version bump via changesets. |
||
|
|
cb500eeb4c |
chore(release): v0.75.1-beta.2
Version bump via changesets. |
||
|
|
68e964383f |
chore(release): v0.75.1-beta.1
Version bump via changesets. |
||
|
|
6a5e1b0145 |
chore(release): v0.75.1-beta.0
Version bump via changesets. |
||
|
|
5dbfef7b64 |
chore(release): v0.75.0
Version bump via changesets. |
||
|
|
b1d5e0fec3 |
chore(release): v0.75.0-beta.2
Version bump via changesets. |
||
|
|
993a2f9d86 |
chore(release): v0.75.0-beta.1
Version bump via changesets. |
||
|
|
77c698a249 |
chore(release): v0.75.0-beta.0
Version bump via changesets. |
||
|
|
5d8d494230 |
fix(todos): restore clean build and navigation coverage (#3321)
## Summary - remove an unavailable jest-dom type from the Todo plugin production TypeScript build - update the dashboard navigation fixture for the plugin-owned Todo destination and root test id ## Test plan - `corepack pnpm --filter @fusion-plugin-examples/todos build` - `corepack pnpm --filter @fusion-plugin-examples/todos test` - `FUSION_DASHBOARD_DEEP=1 corepack pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/navigation-history.test.tsx --project dashboard-app-quality-components-a --silent=passed-only --reporter=dot` - `corepack pnpm check:changesets` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Restored CLI packaging for the bundled Todo Lists plugin. * Made `AgentStore` available to bundled plugins at runtime. * **Tests** * Updated navigation coverage for Todo Lists dashboard views, overflow placement, and ordering. * Added coverage for opening and dismissing the Todo view through browser history navigation. * Improved validation of runtime exports. * **Chores** * Simplified test type configuration for the Todo Lists plugin. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
19f6456821 | fix(FN-8762): restore Todos plugin build | ||
|
|
5b2b31d2c9 |
FN-8762: extract Todo Lists into bundled plugin
Move Todo Lists into a bundled, project-enabled plugin package. - Move Todo UI, client API, and server routes into the plugin package. - Register and bundle Todo as an enabled plugin dashboard view rather than a static host feature. - Preserve Todo route validation and server-error semantics, including task assignment agent lookup. - Keep disabled and legacy Todo views out of project navigation and main content. Files changed: .changeset/fn-8762-todos-plugin.md | 7 + AGENTS.md | 3 +- docs/PLUGIN_AUTHORING.md | 4 + docs/dashboard-guide.md | 4 + docs/todo-view.md | 151 +---- .../cli/src/plugins/staged-bundled-plugin-ids.ts | 1 + packages/cli/tsup.config.ts | 8 + .../core/src/board/mobile-nav-primary-items.ts | 2 - .../__tests__/bundled-plugin-install.test.ts | 2 + .../core/src/plugins/bundled-plugin-install.ts | 1 + packages/dashboard/app/App.tsx | 18 +- .../app/__tests__/lazy-loaded-views-docs.test.ts | 9 +- packages/dashboard/app/api/legacy.ts | 15 - packages/dashboard/app/api/system/index.ts | 1 - packages/dashboard/app/api/system/todo.ts | 85 --- packages/dashboard/app/components/Header.tsx | 23 +- .../dashboard/app/components/LeftSidebarNav.tsx | 1 - packages/dashboard/app/components/MobileNavBar.tsx | 4 - .../dashboard/app/components/SettingsModal.tsx | 2 - .../app/components/__tests__/App.test.tsx | 7 - .../app/components/__tests__/Header.test.tsx | 42 -- .../app/components/__tests__/RightDock.test.tsx | 18 +- ...skDetail.mobile-transition.board-panel.test.tsx | 1 - .../__tests__/TaskDetail.swipe-back.test.tsx | 1 - .../__tests__/TodoView.mobile-css.test.ts | 66 --- .../app/components/__tests__/TodoView.test.tsx | 649 --------------------- .../__tests__/navigation-history.test.tsx | 3 - .../__tests__/overflowViewRegistry.test.tsx | 118 +--- .../app/components/dashboard/MainContent.tsx | 27 +- .../dashboard/app/components/dashboard/types.ts | 6 +- .../app/components/overflowViewRegistry.tsx | 21 +- .../app/hooks/__tests__/useTodoLists.test.ts | 291 --------- .../app/hooks/__tests__/useViewState.test.ts | 11 + packages/dashboard/app/hooks/useAppSettings.ts | 5 - packages/dashboard/app/hooks/useViewState.ts | 5 + .../__tests__/registerBundledPluginViews.test.tsx | 14 + packages/dashboard/app/plugins/bundled-todos.d.ts | 5 + .../app/plugins/registerBundledPluginViews.ts | 18 + packages/dashboard/app/plugins/types.ts | 4 + .../src/__tests__/todo-documentation.test.ts | 68 --- .../dashboard/src/__tests__/todo-routes.test.ts | 577 ------------------ packages/dashboard/src/registry-manifest.json | 101 +++- packages/dashboard/src/routes.ts | 1 - .../src/routes/plugin-bundled-runtimes.ts | 1 + .../src/routes/register-integrated-routers.ts | 2 - packages/dashboard/src/shared/dashboard-views.ts | 6 - packages/dashboard/src/todo-routes.ts | 342 ----------- packages/dashboard/vite.config.ts | 8 + packages/dashboard/vitest.config.ts | 8 + packages/desktop/scripts/workspace-tools.ts | 1 + plugins/fusion-plugin-todos/README.md | 20 + plugins/fusion-plugin-todos/manifest.json | 6 + plugins/fusion-plugin-todos/package.json | 38 ++ .../fusion-plugin-todos/src/dashboard-interop.d.ts | 12 + plugins/fusion-plugin-todos/src/dashboard-view.tsx | 4 + .../src/dashboard/LoadingSpinner.tsx | 1 + .../src/dashboard}/TodoView.css | 0 .../src/dashboard}/TodoView.tsx | 16 +- plugins/fusion-plugin-todos/src/dashboard/api.ts | 15 + .../src/dashboard/projectStorage.ts | 3 + .../fusion-plugin-todos/src/dashboard/swrCache.ts | 6 + .../src/dashboard/useConfirm.ts | 1 + .../src/dashboard}/useTodoLists.ts | 4 +- plugins/fusion-plugin-todos/src/index.ts | 4 + .../fusion-plugin-todos/src/todo-routes.test.ts | 46 ++ plugins/fusion-plugin-todos/src/todo-routes.ts | 156 +++++ plugins/fusion-plugin-todos/tsconfig.json | 28 + plugins/fusion-plugin-todos/vitest.config.ts | 9 + pnpm-lock.yaml | 64 +- pnpm-workspace.yaml | 1 + 70 files changed, 671 insertions(+), 2531 deletions(-) Fusion-Task-Id: FN-8762 Fusion-Task-Lineage: 3beb502c-3793-451b-b357-50c243395410 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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** |
||
|
|
a3bb584bff |
chore(release): v0.74.0
Version bump via changesets. |
||
|
|
bc34b724ac |
chore(release): v0.74.0-beta.9
Version bump via changesets. |
||
|
|
0ae6c396d0 |
chore(release): v0.74.0-beta.8
Version bump via changesets. |
||
|
|
546a19aef6 |
chore(release): v0.74.0-beta.7
Version bump via changesets. |
||
|
|
1e83dcceec |
chore(release): v0.74.0-beta.6
Version bump via changesets. |
||
|
|
920d68e10f |
fix(dashboard): expose column roles to browser bundle (#3151)
## Summary - export the browser-safe `@fusion/core/column-roles` subpath - keep Vite/Vitest aliases ahead of broad `@fusion/core` aliases - restore production dashboard builds after task undo classification adopted shared column-role helpers ## Test plan - `node scripts/check-no-node-only-core-imports-in-dashboard.mjs` - `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest run app/utils/__tests__/taskRevert.test.ts --pool=threads --maxWorkers=1` - `pnpm --filter @fusion/core typecheck` - `pnpm --filter @fusion/dashboard typecheck` - `CI=true pnpm check:changesets` - `pnpm build` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed dashboard build compatibility for browser-based environments. * Improved reliability when importing column role functionality across supported application components. * **Refactor** * Made column role utilities available through a dedicated browser-safe entry point. * **Chores** * Updated development and test configurations to consistently resolve the new entry point. * Documented the browser-safe module classification and recorded the release patch. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
ccf562f178 |
gate: compare mirrored INTERFACES too, and delete the dead prop that found (#3034)
> **Re-landing the second half of #3031.** That PR merged into #3029's branch and only its first commit reached `main` — the arity rule shipped, the interface rule and its finding did not. Verified on `main`: the gate reports *"7 mirrored function(s)"* with no interface count, and the dead prop below is still there. ## What The arity rule covers exported functions. The same files also mirror **interfaces**, which is the larger surface — six copies of `PluginDashboardViewContext` alone. **One direction only.** A mirror may declare *fewer* properties, and all six do (6, 8, 7, 7, 3, 6 against the real nine) because a plugin mirrors the fields it uses. Demanding equality would fail every plugin for not using everything — which is how a check gets ignored and then deleted. A property the real type **doesn't have** is the drift that matters: a rename nobody propagated, where the plugin keeps compiling and reads a field the host never sends. ## Its first interface run found a live one ``` dashboard-interop.d.ts:67 TaskCardProps.workflowStepNameLookup is not a property of the real TaskCardProps ``` Git history says it **was** one when FN-2466 and FN-7039 added this threading. The dashboard removed it later; nothing propagated that to the plugin's hand-written declaration. So the plugin built a lookup map from `context.workflowSteps` on every render, threaded it through two components, and handed it to a `TaskCard` with no such prop. Deleted rather than exempted — a new gate shouldn't ship with a waiver for its own first finding. Behaviour-preserving: the value never reached anything. ## Measured on `main` | check | result | |---|---| | population | **7 functions + 10 interfaces across 6 plugins**, all matching after the deletion | | control probe | phantom property **caught**; clean tree exits 0 | | anti-vacuity | now also requires a non-zero *interface* comparison | | gate's own suite | **5 → 8** | | dependency-graph suite | 179 green; `tsc` clean | | other five gates · census | green | ## Running total for this check Three real drifts, none of which any other instrument reported: 1. `isTaskStuck` stuck at three parameters through the whole lane conversion (#3003) 2. `taskStuckTimeoutMs?: number` vs the required `number | undefined` — in **two independent authors'** declarations 3. `workflowStepNameLookup` outliving its removal from `TaskCard` Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a460a9bbc0 |
fix(plugins,dashboard): the dependency graph drew every card with the LEGACY lane vocabulary (#3029)
## The third producer of unflagged cards — the one a host-side fix could not reach #3025 fixed the two producers that go through `renderTaskCard`. `GraphTaskNode` is a third: it imports `TaskCard` **directly** through the plugin's interop shim, so that fix bypassed it and every role helper inside a graph card kept reading the legacy ids. The same component also called the stuck predicate without its flags: ```ts const isStuck = isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs); // no columnFlags ``` so `isWipColumnRole` fell back to the literal and **no card in the graph could ever be stuck on a renamed board**. Because `isStuck` gates `isActive`, a wedged card rendered with the **active** styling — the graph reported *"running"* about a task that had not moved in hours, while the main board showed the same card as stuck. That asymmetry between two views of one task is the defect, and it is what the new test pins. ## One cause, so one fix Both symptoms came from the same gap: `PluginDashboardViewContext` exposed `tasks` and nothing about the board's vocabulary. It now carries `columnFlagsByTaskId` — the same per-task map `renderTaskCard` already uses, **two lines away in the same object literal**. ## I filed this twice as blocked on a public-API change. It was not. ``` packages/dashboard @fusion/dashboard private: true packages/plugin-sdk @fusion/plugin-sdk private: true plugins/fusion-plugin-dependency-graph @fusion-plugin-examples/dependency-graph private: true ``` No published surface anywhere in the path — three in-repo private packages and a hand-written `.d.ts`. **#3026 landed the general form of that mistake while I was still making it**: a deferral's stated blocker is a claim, and mine decayed unchecked until I finally measured it. ## Two type decisions worth reviewing - **`Partial<TraitFlags>`** in the plugin-facing type, not the dashboard's `ExecutorColumnFlags` — that module's own header restricts it to `@fusion/core` and `react` imports so external plugin builds can consume it. Same runtime object either way. - **`MainContentProps.columnFlagsByTaskId` widened** from `{complete, archived, intake, hold}` to the flags the map really carries. It is built from `workflow.columns.find(...).flags`, so the four-flag declaration was a narrower view than the value — and `countsTowardWip`, which every wip predicate needs, was invisible through it. That narrow type is why threading this looked impossible at first. Absent still means legacy, matching how the host treats remote rows and off-board columns: the degraded answer is the documented literal, never *"this board has no wip lane"*. ## Revert proof Dropping the 4th argument: ``` AssertionError: expected 'graph-task-node graph-task-node--acti…' not to contain 'graph-task-node--active' Tests 1 failed | 26 passed (27) ``` The paired case (a fresh legacy `in-progress` card still reads active) passes both ways by design — it guards against over-detection, so I am not counting it as coverage. The gate agrees independently: `plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx: 1 -> 0`, baseline re-recorded 16 → 15 in the same commit. ## Verification (measured) - plugin suite — **185 passed / 20 files** - dashboard `dashboard/` + `plugins/` suites — **48 passed / 6 files** - `tsc --noEmit` clean in both packages; `pnpm lint` clean - `lifecycle-column-census --strict`, `check-lane-wiring` (15, none added), `check-sql-column-literals`, `check-inert-flag-seams`, `check-fnxc-future-dates` — green No changeset: all three packages are `private: true`. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
25b3c06d2d |
fix(plugins): compound-engineering pipelines stalled forever on a renamed board (#3022)
Closes #3020 — which I filed **instead of** fixing, on a rationale that turned out to be wrong. I said the plugin had no scaffolding for faking `CePipelineStore` + `taskStore` together. It does: `_harness.ts` already builds a real `PluginContext` over a live PostgreSQL layer. The gap was **two missing readers on its task-store stub**, not missing infrastructure. I checked the harness only after filing. ## The defect `TERMINAL_COLUMNS` is `{in-review, done}`, and the reconciler advances a pipeline only when **every** current-stage board task is in that set. On a board whose review and completion lanes are renamed that's false for every task, permanently: - the pipeline never advances a stage - it never creates its outbound task - it sits `running` indefinitely Nothing errors, so it reads as work that hasn't finished. Unlike the display defects in this family (#3014, #3017), the CE flow actually **stops**. ## Shape The decision is extracted to an exported `isStageTerminalColumn` because it *is* the whole decision. Left private it could only be reached through a pipeline-state + links + board-tasks fixture, and the half that needed proving is that a renamed board resolves to its own lanes through this store. It uses `resolveReviewColumns` rather than re-deriving the union — that helper is the documented review **set** (`mergeOrchestration ∪ mergeBlocker ∪ humanReview`), so a board splitting those across a merge lane and a human lane is covered without this site drifting from it. ## Two things my first attempt got wrong **The fixture spelled traits in camelCase** — `{ trait: "humanReview" }`. Trait **ids** are kebab-case (`human-review`, `merge-blocker`, `wip`); the camelCase names are the resolved **flags**. Those columns therefore resolved to *no roles at all*, silently, because an unknown trait isn't an error. `complete` is spelled identically in both vocabularies, which is exactly what made the first run look like *"complete works, review is broken"* rather than *"the fixture is wrong"* — I nearly went debugging the production union. **The harness extension is additive** and inert until a test seeds it, so all 24 existing plugin suites see the previous shape. ## Measured | check | result | |---|---| | new suite | **4/4** | | reverting to the literal-only gate | fails **exactly 2** — the renamed-terminal case, and a board declaring a NON-terminal column named `done` — while the legacy control and the WIP/intake negative still pass | | plugin suite | **24 files, 184 tests green** | | `tsc` + all five gates | clean | That second row is the one that matters: the `done`-without-`complete` board is the only shape where a real resolution and a legacy fallback disagree, so it's what separates the fix from a lucky agreement. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
083f8f5c5b |
fix(glasses): every card on a renamed board badged "todo", including cards in review (#3015)
## Every card on a renamed board badged `todo` — including cards in
review
```ts
export function statusBadge(column: Task["column"]): string {
return COLUMN_BADGES[column] ?? "todo";
}
```
`COLUMN_BADGES` maps the six legacy ids to themselves. On a board whose
lanes are named anything else **every lookup misses**, so every card
badges `todo` — a card sitting in review tells the wearer it is
un-started, and the whole board carries one identical badge.
On a display with room for a single word, that is worse than an
unrecognised lane: it is a *confident wrong answer* rather than a
missing one.
Reached from `taskToCard` (the main card) and `notificationCard` (the
notification badge).
## Fix, and the dead weight it exposed
The badge **is** the column id, so the function now says so:
```ts
export function statusBadge(column: Task["column"]): string {
return column;
}
```
That also retires `COLUMN_BADGES`. Once the fallback is the id, a table
mapping each legacy id to *itself* decides nothing — six lane literals
sat in this file doing no work. It was module-private with `statusBadge`
as its only consumer and it was a pure identity, so behaviour on legacy
boards is byte-identical: this is not a behaviour change riding along
with a cleanup, it is the dead weight the fix exposed, removed rather
than left as a decoy.
Mirrors `columnLabel` in the CLI (`COLUMN_LABELS[column] ?? column`) for
the same reason: a board that calls its lane `checking` should read
`checking`. No resolution needed; the id is in hand at the call site.
Note the census count for this file does **not** move — those six were
object keys, not comparisons, which is exactly the scope the census
documents for itself.
## This is a miss in my own #2968
That PR fixed the summary card's counts **in this same file** and never
looked one function further at the per-card badge those counts sit
above. Worth saying plainly, because it is the practical reminder behind
the census finding I have been repeating all run: *a file having had a
defect fixed is not evidence about its neighbours* — and here the
neighbour was nine lines away, in a function I had read.
## Revert proof
```
AssertionError: expected 'todo' to be 'checking'
Tests 1 failed | 9 passed (10)
```
The paired case ("still badges the legacy ids exactly as before") passes
both ways by design — it guards against the fallback change altering
known boards, so I am not counting it as coverage of the defect.
## Verification (measured)
- plugin suite — **198 passed / 19 files**
- `tsc --noEmit`, `eslint` — clean
- `lifecycle-column-census --strict`, `check-lane-wiring`,
`check-sql-column-literals`, `check-inert-flag-seams`,
`check-fnxc-future-dates` — green
No changeset: the plugin is `private: true` and is not bundled into the
published CLI.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Status badges now accurately display a card’s actual lane, including
previously unrecognized lanes.
* Preserved existing badge behavior for known lanes.
* **Tests**
* Added coverage to verify accurate lane reporting and legacy behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
cfe47b3754 |
chore(plugins): delete the superseded fusion-plugin-even-cards (#2790) (#2988)
Closes #2790 by finishing a decision that was already made and written down. ## The issue's premise was wrong, including as I filed it I raised this as "a package accidentally missing from `pnpm-workspace.yaml`." It wasn't. `CHANGELOG-archive.md:9596`: > Consolidate Even Realities plugin support into `fusion-plugin-even-realities-glasses` and **remove `fusion-plugin-even-cards` from the active workspace package list to avoid duplicate user-facing integrations.** The removal was deliberate, for a stated reason. The directory is what got left behind. That also rules out the option I had been weighting first — adding it back would undo a shipped consolidation and re-create the duplicate integration it was removed to prevent. ## Unreachable by every path | check | result | |---|---| | `pnpm-workspace.yaml` globs | no — never installed or built | | CLI bundle list (`packages/cli/tsup.config.ts`) | no — 0 mentions, while seven other plugins are named | | runtime `plugins/*` directory-scan discovery | none exists — plugins are enumerated explicitly | | `package.json` | `private: true` — never published | | imports outside its own directory | none | | kept as a docs/authoring example | no — zero references in `docs/` or any root `*.md` | | successor in the workspace | yes — `fusion-plugin-even-realities-glasses` | ## It was also polluting two ratchets Dead code in a scanned tree is worse than dead code: both censuses are **source-text scanners**, so they counted debt in files no build or typecheck covers. Nobody could retire those entries through a normally-verified refactor, and they inflated how much of the remaining debt looked actionable. Both baselines regenerated, and I checked each diff rather than trusting the totals: | baseline | change | |---|---| | `lane-wiring` | 26 → 23 sites, 21 → 20 files — **one entry removed**, `board-routes.ts: 3` | | `lifecycle-column-census` | exactly its two `board-cards.ts` entries | Neither regeneration tightened anything unrelated — worth confirming explicitly, because `lifecycle-column-census.mjs --strict` **writes** its baseline as a side effect and could have folded an unrelated drop into this commit. **Verified:** lane-wiring, SQL-literal and FNXC gates all exit 0 after the deletion; lint clean. 15 files removed. ## Why I went ahead I said twice I would not delete this unilaterally. What changed is that the trade-off dissolved — once the consolidation decision turned out to be documented and the "is it a teaching example?" question answered by a docs grep, there was nothing left to decide, only to execute. The deletion is git-reversible and the standing guidance is that reversible calls are mine to make. Fourth time today a thing I filed as "needs someone else's judgement" turned out to have its answer already in the repository. Cheap habit worth keeping: before deferring, grep for whether the judgement has already been made. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
eb9cd431c1 |
fix(glasses): settings silently discarded the operator's own lane names, so notifications never fired (#2973)
## The operator configures their lanes, and the plugin throws it away
`TaskColumn` was the closed legacy union and `COLUMN_SET` gated every
settings read against it:
```ts
.filter((value): value is TaskColumn => COLUMN_SET.has(value as TaskColumn));
```
**`notifyOnColumns` is a free-text string array in the schema** (`type:
"array", itemType: "string"`) — the UI invites any lane name. So an
operator on a renamed board types `checking`:
1. `getNotifyColumns` filters it out — not in the legacy five
2. `columns.length === 0`, so it substitutes `DEFAULT_NOTIFY_COLUMNS` =
`["in-review"]`
3. their board has no `in-review`
4. `notifier.ts` builds `new Set(getNotifyColumns(...))`, and
`diffSnapshots` tests `notifyOnColumns.has(task.column)`
**No notification ever fires.** The feature is silently off while the
setting reads as configured, and nothing surfaces an error.
`quickCaptureDefaultColumn` had the same shape with an extra irony: it
was rewritten to `todo` *before* `normalizeCaptureColumn` saw it — and
that function already validates against the board's **declared** columns
and falls back to the workflow's own intake lane. The pre-filter
destroyed the operator's answer immediately before the code that could
have honoured it.
## Fix
Validation is now **structural** (non-empty string) rather than
**vocabulary-based**. `TaskColumn` mirrors core's `ColumnId`
(`LegacyTaskColumn | (string & {})`), so legacy ids keep autocomplete
while custom ids are admitted. `COLUMN_SET` survives only as the
quick-capture dropdown's suggestions, not as a gate.
**Deliberately not resolving the board's columns.** That needs a
board-columns endpoint `FusionApiClient` does not have — a new read, not
a rename — and that gap is already recorded in this file by an earlier
audit. This change is orthogonal to it: accepting operator input
requires no resolution at all, which is why it doesn't wait on the
endpoint.
**Trade-off, taken knowingly:** a typo'd lane is now honoured and will
match no card. That is the milder failure. Before, a *correct* custom
lane was discarded categorically, so renamed boards could not use the
feature at all; now the only broken case is one the operator typed wrong
and can see in their own settings.
## Two existing assertions asserted the defect
```ts
expect(getNotifyColumns({ notifyOnColumns: ["nope"] })).toEqual(["in-review"]);
expect(getQuickCaptureColumn({ quickCaptureDefaultColumn: "bad-column" })).toBe("todo");
```
I checked provenance before touching them: they date to the plugin's
original commits (FN-3738 / FN-3970), when the five ids genuinely were
the whole vocabulary. They carry no reasoning comment and no later
change defended custom-lane rejection as a contract — so this is a stale
assumption being corrected, not a peer's tested decision being
overwritten. Structural rejection (non-strings, blanks, whitespace-only)
and trimming are still asserted, because that part was always right.
## Revert proof
```
AssertionError: expected [ 'in-review' ] to deeply equal [ 'checking', 'shipped' ]
AssertionError: expected [ 'todo' ] to deeply equal [ 'todo', 'spaced' ]
Tests 2 failed | 5 passed (7)
```
## Verification (measured)
- plugin suite — **196 passed / 19 files**
- `tsc --noEmit`, `eslint` — clean
- `lifecycle-column-census --strict`, `check-fnxc-future-dates` — green
No changeset: the plugin is `private: true` and is not bundled into the
published CLI.
|
||
|
|
cadf011b25 |
fix(dependency-graph): an allowlist of four legacy lanes rendered a blank graph on a renamed board (#2972)
## A renamed board gets a blank dependency graph
`filterGraphTasks` gated on an allowlist of four legacy lane ids:
```ts
export const INCLUDED_COLUMNS = new Set(["triage", "todo", "in-progress", "in-review"]);
export function filterGraphTasks(tasks: Task[]): Task[] {
return tasks.filter((task) => INCLUDED_COLUMNS.has(task.column));
}
```
On a board whose lanes are named anything else — `backlog`, `building`,
`checking` — **no card matches and the graph renders completely empty**.
This is not a mislabelled node or a missing edge: the entire feature is
blank, and it reads as *"this project has no dependencies"* rather than
as a bug. `triage` is in that allowlist too, a lane U11 (#2515) deleted.
## Fix: gate on the finished lanes instead
Inverted to a denylist, so the **default is the safe one**. An
unrecognised lane is active work by assumption and renders; only lanes
that genuinely mean "finished" drop out.
An allowlist fails **closed** — hide everything unknown. A denylist
fails **open** — show it. For a graph, an extra node is a far smaller
error than no graph.
## The residual, named rather than hidden
`EXCLUDED_COLUMNS` is still two literals. `DependencyGraph.tsx` is a
client React component handed plain `Task` rows as a prop, with no async
seam to resolve a workflow IR — so a board that renames its DONE lane
still shows finished cards here. That is deliberately the mild failure
direction: "one extra node", not "no graph". It is documented in the
code rather than papered over with an optional resolved-lanes parameter
no caller could fill.
The invalid-column guard is now **explicit**. Under the allowlist,
`column: undefined` was excluded as a side effect of not being in the
set; under a denylist it would sail through, so it is checked directly
and covered.
## Revert proof
Restoring only `filters.ts`:
```
AssertionError: expected [] to deeply equal [ 'FN-1', 'FN-2', 'FN-3' ]
AssertionError: expected [] to deeply equal [ 'FN-1' ]
Tests 2 failed | 13 passed (15)
```
Worth noting explicitly: **every pre-existing case passes either way.**
They only ever enumerate the six legacy ids, so the allowlist and the
denylist agree on all of them — no existing test could have seen this
blackout. (The new empty-string case also passes both ways; it guards
the new implementation rather than proving the fix, and I am not
claiming it as coverage of the defect.)
## Verification (measured)
- plugin suite — **183 passed / 20 files**
- `tsc --noEmit`, `eslint` — clean
- `lifecycle-column-census --strict`, `check-fnxc-future-dates` — green
## Checked and deliberately not changed
`GraphTaskNode.tsx` holds `column === "in-progress"` and `column ===
"in-review"`. Both are already audited with an in-code note, and both
degrade mildly rather than blanking anything — `hasExecutionSignal` also
ORs on `ACTIVE_STATUSES`, so a renamed WIP lane still reads as active
via status. Converting them needs column traits this component is not
given, so they stay noted rather than half-converted.
No changeset: `fusion-plugin-dependency-graph` is `private: true`.
|
||
|
|
5d01164994 |
test(glasses): re-green 15 agent-action tests, and revive a guard that asserted nothing (#2969)
## 15 tests have been red on `main`
`startWork`, `requestReview` and `approvePlan` gained a third `moveTask`
argument, `{ moveSource: "user" }`. The assertions in
`agent-actions.test.ts` kept the two-argument form:
```
AssertionError: expected "vi.fn()" to be called with arguments: [ 'FN-1', 'building' ]
Received: [ "FN-1", "building", + { "moveSource": "user" } ]
Tests 15 failed | 41 passed (56)
```
Reproduces on clean `origin/main`. This suite is outside the merge gate,
which is why it went red unnoticed.
## The part that is worse than staleness
Three of these are **negative** assertions, and they did not fail — they
went **dead**. `expect(fn).not.toHaveBeenCalledWith(id, column)` cannot
match a three-argument call, so it passes whether or not the forbidden
move happened. `requestReview`'s "never lands on the legacy `in-review`"
guard has been asserting nothing since the option landed.
Verified rather than reasoned about — a scratch case, run and then
deleted:
```ts
const fn = vi.fn();
fn("FN-1", "in-review", { moveSource: "user" }); // the forbidden move HAPPENED
expect(fn).not.toHaveBeenCalledWith("FN-1", "in-review"); // old form: passes anyway ✓
```
Both cases passed, confirming the old form is vacuous and the
three-argument form throws.
## Applied per action, not uniformly
Only **3 of 5** product `moveTask` calls take the option, and the split
is deliberate:
| action | source | why |
| --- | --- | --- |
| `startWork`, `requestReview`, `approvePlan` | `{ moveSource: "user" }`
| the wearer's tap is a human gesture, matching the dashboard move route
|
| `returnToAgent`, `retryTask` | default (engine) | per the Move-Task
contract a user-source move parks the row `userPaused`, defeating the
return/retry intent |
So `returnToAgent`/`retryTask` assertions are **correct as
two-argument** and are left untouched — including their negatives, which
genuinely assert because the real call is also two-argument.
Attribution was done by scoping each assertion to its enclosing `it(`
block, not to the nearest preceding call: the latter mis-attributes the
`await expect(startWork(...)).resolves` form, which reads as `expect`.
Blocks mixing a user-source and a default-source action were excluded
from rewriting (there were none).
I did **not** "fix" the 3/5 split. It is documented in the product with
its reasoning and it matches the Move-Task contract; changing it would
be a behavior change riding in a test-only commit.
## Verification (measured)
- `agent-actions.test.ts` — **56/56 passed** (was 15 failed / 41 passed)
- full plugin suite — **192 passed / 19 files** (was 180 passed, 15
failed)
- `lifecycle-column-census --strict`, `check-sql-column-literals`,
`check-fnxc-future-dates` — green
Tests only; no product file is touched. An FNXC note now records the
arity contract and which actions take which source, so the next
assertion added here doesn't reintroduce a dead guard.
|
||
|
|
1c19540f50 |
fix(glasses): the summary card reported five hardcoded lanes, so a renamed board read as empty (#2968)
## The defect
`boardSummaryCardFromCounts` built its text from five hardcoded lane
ids:
```ts
`Triage ${counts.triage} Todo ${counts.todo} Doing ${counts["in-progress"]} Review ${counts["in-review"]} Done ${counts.done}`
```
`boardSummary` seeds exactly those five keys to `0`, then counts by real
`task.column`. On a board whose lanes are named anything else, **every
interpolated value is the seeded zero** — so the summary card, which is
the first card in every deck and the entire body of `GET
/board/summary`, reads:
```
Triage 0 Todo 0 Doing 0 Review 0 Done 0
```
…while the real work sits in lanes it never mentions. The wearer is told
the board is empty.
It is also wrong on the **default** board today: U11 (#2515) deleted the
`triage` lane, so `Triage 0` is permanently dead text — 9 of the 24
characters this display gets per line.
## Why there was no test
The only summary coverage in `cards.test.ts` exercised
`boardSummaryCard`, an **export no production file calls** (its sole
caller is that test). The function the deck actually ships had none.
That gap is why five hardcoded ids survived here.
## The fix, and one deliberate choice
The lanes are derived from `counts` instead of taken as an optional
resolved-lanes parameter.
That is on purpose. This program's recurring defect is precisely the
"optional lane answer + documented literal fallback" shape shipped
without wiring the caller — `unwired-lane-parameter-guard.test.ts`
documents **five live on `main` at once**, and in four of five the
parameter was unreachable because the caller held a larger defect.
`counts` is already keyed by real `task.column` values, so the
vocabulary is in hand with **no resolution, no new plumbing, and nothing
that can be left unwired**.
Zero-count lanes are dropped so the scarce line budget goes to lanes
with work; legacy ids keep their familiar labels and order, unknown ids
sort after them alphabetically so output is deterministic. `counts`
itself is untouched — the route returns it as the API body and consumers
still see every key.
## Revert proof
Restoring only `cards.ts` fails all three new cases, printing the defect
verbatim:
```
AssertionError: expected 'Triage 0 Todo 0 Doing 0 Review 0 Done…' to contain 'backlog 2'
AssertionError: expected 'Triage 0 Todo 1 Doing 0 Review 0 Done…' not to match /Triage/
AssertionError: expected 'Triage 0 Todo 0 Doing 0 Review 0 Done…' to be 'No active work'
Tests 3 failed | 5 passed (8)
```
## Verification (measured)
- `cards.test.ts` — **8/8 passed**
- full plugin suite — **180 passed**, 19 files
- `eslint`, `tsc --noEmit` — clean
- `lifecycle-column-census --strict`, `check-sql-column-literals`,
`check-fnxc-future-dates` — green
No changeset: `@fusion-plugin-examples/even-realities-glasses` is
`private: true` and is not bundled into the published CLI.
## Pre-existing failure, NOT from this change
The plugin suite also has **15 failures in
`src/__tests__/agent-actions.test.ts`**. They reproduce identically on
clean `origin/main` with this branch stashed (`15 failed | 41 passed`),
so they are not caused by this PR and are not fixed by it. Flagging
separately — this suite is outside the merge gate, which is why it has
been red unnoticed.
|
||
|
|
189f237a07 |
fix(security): make the approval and permission gates actually enforce (#2457)
## Why
An AI agent autonomously deleted a live task (FN-8609) via
`fn_task_delete`. Investigating that led to two audits, which found the
gate machinery is well-built and its *enforcement* well-tested — but the
*decision boundary* was unguarded and the tools that matter never
reached the gate at all.
The headline defect: all ~76 tools in `packages/cli/src/extension.ts`
are delivered through `hostExtensionPaths` → `DefaultResourceLoader`,
which never passes through `wrapToolsWithActionGate`. `fn_task_delete`
*was* classified as gated in `gating-classifications.ts` — that
classification was dead code. Roughly 15 destructive tools were ungated
even for an operator who had locked their policy all the way down.
Second: `POST /api/approvals/:id/decision` took the approver identity
from the request body, validated for shape only, with no check that the
decider was human or differed from the requester. Combined with a bash
tool whose command string was never inspected (only `params.cwd`), an
agent could read the daemon token from `~/.fusion/settings.json`, find
its own pending request, and approve it.
## Scope
This does **not** tighten defaults. The `unrestricted` preset is
byte-for-byte unchanged — `git diff` on `agent-permission-policy.ts` is
empty — and regression tests assert that an out-of-the-box install
behaves exactly as before. The bug was never "the default is too
permissive"; it was "strict policy doesn't enforce." This makes turning
security up actually work.
The one deliberate exception: the containment that stops an agent
escalating its *own* privileges (reading the daemon token / credentials,
calling the approvals API to self-approve) applies at every preset
including `unrestricted`. That is a privilege-escalation boundary rather
than a permission preference — if it only engaged under strict policy it
would not have prevented the incident that prompted this.
## What changed
8 bisectable commits:
- **Approval lifecycle** — self-approval blocked via server-derived
deciders; same-verdict replay 409s; decide re-reads and re-validates
inside the transaction; expiry TTLs; `markCompleted` ownership check;
session identity registry in core.
- **Engine gates enforce for real** — unclassified tools resolve to a
policy-governed category instead of hardcoded `allow`; missing-policy
fail-open closed; bash containment floor + exact-command approval
binding.
- **Dashboard decision routes** — stop trusting client-supplied actors
(decision, bypass-review, worktrunk → 403 on forged actors).
- **`fn serve` authenticated by default** — auto-mints a token following
the existing `fn dashboard` precedent; `--no-auth` opts out.
- **Sibling entry points closed** — user-sourced hard-cancel moves, ACP
execute-once approvals, plugin task-store gating.
- **pi-extension principal resolution** — the extension resolves the
acting principal and can withhold or policy-gate the previously ungated
destructive tools.
- **Root-cause bonus fix** — `findLatestByDedupeKey` was broken in
PostgreSQL backend mode (already-parsed jsonb fed through a string-only
parser), so approved-grant redemption **never matched in production**,
minting duplicate requests. This explains the live DB state of 17
approved / 0 completed. *(Also cherry-picked to `main` as `a9b30013bb`,
since it is an active production defect on its own.)*
- **Review follow-ups** (`627f1b1fa8`) — operator-configured
provisioning privilege and a configurable grant TTL; see below.
## Review follow-ups
**Provisioning privilege is operator-configured, not role-derived.**
`isCallerPrivileged` had gone from `caller.reportsTo == null` (every
top-level agent privileged — permanent escalation by creating a
manager-less agent) to `caller.role === "ceo"`, which swapped an
implicit rule for a magic string: any agent config can claim that role,
while an operator who genuinely wants a privileged agent had no
supported way to say so. Privilege now derives solely from
`agentProvisioning.trustedAgentIds` / `trustedRoles` and fails closed
when settings are unresolvable.
It is also no longer forwarded to `resolveAgentProvisioningPolicy` as
`isPrivileged`, because that flag short-circuits ahead of
`alwaysApproveDelete` — a trusted caller was bypassing delete approval
entirely. The policy applies the same trusted rules itself, in the right
order. The function now governs only the org-chart escape hatch (acting
outside your own direct reports).
**Grant TTL defaults to 1 hour and is configurable.** Approval →
redemption is not instantaneous: an operator approving from their phone,
an engine restart, a queued lane, or a task waiting on a worktree all
routinely exceeded 15 minutes, after which the grant expired and the
agent silently re-requested. One hour remains far short of the
"redeemable forever" hazard the TTL exists to bound. Override via
`FUSION_APPROVAL_GRANT_TTL_MS` or `configureApprovalRequestTtls()`;
invalid overrides are ignored rather than widening the window to
infinity or collapsing it to zero.
## Behavior changes requiring operator review before rollout
1. `fn serve` requires a bearer token by default (`--no-auth` opts out);
unauthenticated clients get 401.
2. Agents can no longer run withheld destructive tools
(`fn_task_delete`, `fn_task_bypass_review`,
mission/milestone/slice/feature/workflow deletes, `experiment_finalize`,
`skills_install`). Operators keep them via CLI/dashboard. **This is the
incident fix.**
3. Agents get provisioning privilege only when the operator lists them
in `agentProvisioning.trustedAgentIds` / `trustedRoles`; the
provisioning gate is now live in production. Previously-implicit
privilege (top-level position, or a `ceo` role) no longer grants
anything on its own.
4. Decision replay 409s (was 200); pending approvals expire after 24h,
approved grants after 1h (configurable); bash approvals bind per exact
command.
5. Forged/body actors on decision, bypass-review, worktrunk routes →
403; `archive-all-done` requires `{confirm:true}` (external scripts
affected).
6. `fn_secret_get` approvals grant exactly one reveal (previously
granted nothing and looped forever); ACP approvals are execute-once
(previously infinite reuse).
7. Bash containment denies token/credential/approvals-API commands in
all agent sessions at every preset.
## Verification
Independently re-run against the branch, not just self-reported:
- 5 typechecks (core, engine, cli, dashboard `tsconfig.json` +
`tsconfig.app.json`) — clean
- `pnpm lint` — clean
- `pnpm test:gate` — 379 passed
- `pnpm build --force` — green (a plain `pnpm build` skips packages as
unchanged and does **not** compile the branch)
- `pnpm check:changesets` — clean
- ~650 file-scoped tests including new negative-path suites for the
decision boundary, which previously had **zero** test coverage
`packages/engine/src/__tests__/plugin-runner.test.ts` fails 56/80 —
**verified pre-existing**, reproducing identically at base commit
`93a403af67` on `main`. Not in the merge gate.
### A mutation check that failed to fail
Worth recording, because it nearly shipped an untested security fix. The
first mutation check on the provisioning change reintroduced the `ceo`
hardcode and **all 17 tests still passed** — the tests asserted through
the policy path, which can no longer observe `isCallerPrivileged` at
all, precisely because `isPrivileged` is no longer forwarded there.
Org-chart cases that do exercise the function were added; the hardcode
now fails exactly 1 of 19, and restoring is green. A green mutation run
is only meaningful if the test can actually see the code under test.
## Known limitations (stated, not papered over)
- The bash containment floor is string-matching: a cost-raiser, not a
sandbox. Quoting, encoding, `$HOME`, symlinks, or an interpreter
one-liner can evade it. The durable protection is the decision route
refusing agent-originated deciders — the filter is the belt, not the
braces.
- Approval expiry is lazy (evaluated at decide/complete/redeem), not
swept, so an expired pending row stays visible in lists until touched.
- The extension's require-approval path returns a pending message but
cannot suspend a pi session mid-turn; engine-side pause hooks cover
engine lanes only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Security**
* Hardened approval and permission gating with server-side decider
attribution, self-approval blocking, ownership checks, replay/race
protection, and status/TTL enforcement.
* Added fail-closed behavior for sensitive/unclassified tools and
sandbox provisioning approvals.
* Blocked credential/approval access via bash containment; plugin
destructive task operations now require explicit permission.
* **New Features**
* `fn serve` now defaults to bearer-token auth, with `--no-auth` as the
explicit opt-out.
* **Bug Fixes**
* Improved task move-source attribution (`moveSource: "user"`) and
tightened dashboard archive/bypass confirmation and operator attribution
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
32b6041149 |
fix(linear): every imported issue was created into a column U11 deleted (#2860)
The **same** defect as the GitLab importer fixed in #2843, in a plugin written from the same template — found only because I re-grepped my own area with a wider pattern after declaring it clean. ```ts export function buildLinearTaskCreateInput(issue: LinearIssue): TaskCreateInput { return { title, description, column: "triage", … }; } ``` `triage` was **deleted by U11**; the default board's lanes are `todo | in-progress | in-review | done | archived`. An explicit `column` **overrides** the intake column `createTask` resolves for the workflow it selects — which is precisely how the literal survived the deletion. Nothing rejects the write and nothing logs it: the route answers with a task id, and the card is not on the board. Fix: omit `column`, exactly as #2843 did for GitLab. ## Two tests were pinning the bug ```ts expect(input.column).toBe("triage"); // import-linear.test.ts expect(createTask).toHaveBeenCalledWith(objectContaining({ column: "triage" })); // routes.test.ts ``` That is how this survived a lifecycle sweep that *did* reach the GitLab importer. The census cannot see a lane literal passed as a **call argument**, and the tests asserted the behaviour was intended — so both the automated check and the human check said this file was fine. Both now assert the column is **absent**, which is the property that hands the decision to `createTask` and the one that fails on revert. ## The finding worth carrying forward "We fixed the import path" was true of the forge everybody uses and false of the other one. Two importers, one template, one of them audited. When a defect is found in a file that had a sibling, the sibling is the next place to look — and it is not something the census will tell you, because this whole class is invisible to it. ## Revert proof (measured) Restore `column: "triage"`: ``` AssertionError: expected 'triage' to be undefined AssertionError: expected "vi.fn()" to be called with arguments: [ ObjectNotContaining{…} ] ``` ## Verification - `pnpm test:gate` — 161 / 487 / 13 / 71 passed - `pnpm lint` — clean - `tsc --noEmit` (`@fusion-plugin-examples/linear-import`) — clean - full plugin suite — 35 passed across 5 files - census `--strict` — exit 0 (unchanged: this class is invisible to it) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4ce3ff75b6 |
fix(ce): the sync queue recorded "done" for a card that finished somewhere else (#2859)
A lane literal the census cannot see — it is a **call argument**, not a
comparison — and the two sibling hooks disagree about how to record the
same kind of fact:
```ts
onTaskMoved: async (task, fromColumn, toColumn, ctx) => {
await store.enqueueSyncAsync({ …, toColumn }); // the real column
},
onTaskCompleted: async (task, ctx) => {
await store.enqueueSyncAsync({ …, toColumn: "done" }); // ← a guess
},
```
On a board whose complete lane is named anything else, the sync-queue
row names a column that board does not have.
## Why fix a field nothing reads
`toColumn` is written to `ce_pipeline_sync_queue` and **never consumed
by any logic** — I checked every reference; it appears only in the
schema, the store's insert, and the row type. It is audit metadata.
That is both why it went unnoticed and why it is worth one token: the
single thing a wrong audit row costs you is the ability to reconstruct
what happened after the fact. A queue that says a card went to `done` on
a board with no `done` is worse than a queue with no column at all,
because it reads as authoritative.
## Structural ratchet, and I would rather say so than imply otherwise
`getCePipelineStore` requires a live PostgreSQL `AsyncDataLayer` and
throws without one, so driving the hook means standing up PG to
re-assert a one-token substitution — against the standing rule on slow
tests. The repo already takes this trade in the same shape
(`packages/core/src/__tests__/analytics-timing-roles-resolved.test.ts`,
whose analytics aggregators have the identical problem).
The test comment states plainly what it does and does not prove: it pins
that the hook reads the card's own column and holds no completion
literal; it does not exercise the write.
Comments are stripped before the negative assertion — the test's own
explanation names the old literal, and a ratchet that matches its own
prose passes forever without checking anything. (That mistake is already
in this program's history, which is why it is guarded here.)
## Revert proof (measured)
Restore `toColumn: "done"` — **both** assertions fail (the positive one
on the missing `task.column`, the negative one on the literal).
## Verification
- `pnpm test:gate` — 161 / 487 / 13 / 71 passed
- `pnpm lint` — clean
- `tsc --noEmit` (`@fusion-plugin-examples/compound-engineering`) —
clean
- full plugin suite — 319 passed across 32 files
🤖 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**
* Sync audit records now capture the task’s actual completion lane
instead of assuming it is named “done.”
* Improved audit accuracy for boards with custom completion lane names.
* **Tests**
* Added coverage to verify completion records use the task’s real
destination lane.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
92d82b7a17 |
fix(guard): the unwired-lane check reported 0 because its question was too weak (#2852)
A guard nobody has proven can fail is a number, not a check. This one
was returning a clean `[]` while **18** real unwired lane declarations
sat on `main` — including one it was specifically built to catch.
## The escape that found it
`diffSnapshots` in the glasses plugin:
```ts
opts: { notifyOnColumns: ReadonlySet<ColumnId>; completeColumnsByTaskId?: ReadonlyMap<...> }
...
const completeColumns = opts.completeColumnsByTaskId?.get(task.id);
const isComplete = completeColumns ? completeColumns.has(task.column) : task.column === "done";
```
**No file anywhere builds that map.** The conversion was decorative —
the literal decided every real poll, so on a renamed board the wearer is
notified of every column transition *except the card finishing*, the one
they care about. The name was already in the guard's vocabulary list,
the declaration was exported and optional; it satisfied every condition
the guard checks, and the guard said nothing.
## Three independent blind spots
| # | blind spot | why it mattered |
|---|---|---|
| 1 | `SCANNED_PACKAGES` omitted `plugins/` | plugins hold lane logic
like anything else — this one resolves workflow IRs and decides what
"finished" means |
| 2 | inline options-object types were not walked | only bare parameters
and *named* interfaces were. Whether a lane answer arrives as a
parameter, an interface property, or an inline field is a style choice —
**a check evadable by a style choice is decorative** |
| 3 | the mention rule was `source.includes(parameter)` **anywhere** |
satisfied by coincidence for any ordinarily-named parameter |
Fixing 1 or 2 alone would still have missed it: **measured on `main`,
the guard found 0 unwired across 1753 files, and 0 again across 2114
once `plugins` was added**, because the shape was invisible too.
### On (3), I proved it on myself
Renaming the unwired parameter from `completeColumnsByTaskId` to
`completeColumns` — a better name, chosen for good reasons — **silenced
the guard instantly**, because 15 unrelated production files declare a
local called `completeColumns`. The check had not been satisfied; it had
been switched off by a rename. That is exactly the failure the code
comment two lines up condemns, committed one edit later.
The fix is the cause, not a name blocklist: a file that never references
`diffSnapshots` cannot be the thing that wires `diffSnapshots`'s
options. Still deliberately loose — a co-occurrence test, not call-graph
analysis — which keeps the low false-positive rate that makes the guard
bearable while removing a false **negative** that scaled with how
ordinary a parameter's name was.
## The 17 this uncovered
Tightening (3) surfaced 17 further unwired declarations across core,
engine and dashboard. **Spot-checked, not assumed**:
`buildUnblockWeightMap` in `task-priority.ts` declares `terminalColumns`
and `reviewColumns`, and the only files that pass either are its own
tests — the production caller silently uses the built-in `{done,
archived}` default. That is the inert-conversion shape this module
exists to name.
They span three other batches, so they are recorded as a **ratcheted
baseline** in the shape `scripts/lifecycle-column-census.mjs` already
uses here — keyed on `file + parameter` so an unrelated edit above them
cannot manufacture a failure. A new one fails immediately; these can
only leave the list. Listing them beats pretending for another week that
they do not exist.
## The glasses fix
`completeColumnsByTaskId` -> a flat `completeColumns` set, matching its
sibling `notifyOnColumns` in the same options object, resolved **once
per poll** by `notifier.ts` via `resolveProjectColumnsForRoles`.
Project-scoped and not per task because this runs on a polling timer
over the whole board — a per-card workflow read would scale with the
board on every tick. Best-effort: a failed resolve leaves the diff on
its documented legacy default rather than dropping a poll.
Still gated by `alsoNotifyOnDone`, which the production caller passes as
`false`, so it remains unobservable at runtime. Wired anyway: the day
someone enables the flag the resolution must already be right — and now
the guard will say so if the wiring is removed.
## Revert proofs (measured, one per fix)
| revert | failure |
|---|---|
| unwire `notifier.ts` | baseline gains `plugins/…/diff.ts
completeColumns` |
| drop the inline-options walk | "covers an INLINE options-object type"
fails `expected [] to deeply equal [ 'completeColumns' ]`, and the repo
scan loses the glasses entry |
| drop the owner scoping | the repo scan loses **all 17** pre-existing
entries |
| restore `task.column === "done"` | both new `diff.test.ts` cases fail
|
The new diff cases assert **both** directions — a card in the resolved
lane fires, and a card in the legacy `done` does *not* once the caller
resolved other lanes. The second is what proves the resolved set
replaces the default rather than being unioned with it.
## Verification
- `pnpm test:gate` — 161 / 487 / 13 / 71 passed
- `pnpm lint` — clean
- `tsc --noEmit` — clean
- guard suite — 9/9; full glasses plugin — 188 passed across 19 files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
21e688e1f9 |
fix(glasses): ?columns= silently returned the WHOLE board on a renamed board (#2849)
A lane-literal defect the census cannot see — the literals are
**Set/Array members**, not comparisons — in a live plugin, with **no
test coverage on the filter at all**. That absence is how the inversion
survived.
## The bug
```ts
const ALLOWED_COLUMNS = ["triage", "todo", "in-progress", "in-review", "done", "archived"];
function parseColumns(raw) {
const parsed = raw.split(",").filter(v => ALLOWED_COLUMNS.includes(v));
return parsed.length ? new Set(parsed) : null; // ← `null` ALSO means "no filter requested"
}
```
On a board whose lanes are named anything else, every requested id is
discarded, `parsed.length` is `0`, and the function returns `null` —
**the same value it returns when no filter was requested**. The caller
then does `columns ? all.filter(...) : all`, so the route answers `200`
with the **entire board**.
Asking for one column returns all of them. Nothing in the response says
the filter was dropped. The list also still named `triage`, a column U11
deleted, so it described a board that no longer exists in either
direction.
## The fix
No allow-list can be correct here and none is needed. Valid ids are
whatever the project's workflows declare, and `Task["column"]` is
already `ColumnId = Column | (string & {})` — open by construction.
Filtering directly on the requested ids needs **no resolution source at
all**, which is why this literal, unlike the display ordering in
`cards.ts` (documented DELIBERATE-LITERAL: this package depends on
`@fusion/plugin-sdk` only, so there is no IR or store to resolve from),
is a defect rather than a deferral.
**Deliberate behaviour change:** `?columns=nonsense` now returns an
**empty deck** instead of the whole board. "Show me column X" answered
with every column is not a lenient default — it is the bug wearing a
200.
## Revert proof (measured)
Restore the allow-list and **both** new cases fail:
```
FAIL > filters on a RENAMED lane instead of silently returning the whole board
expected [ { id: 'summary', …(5) }, …(2) ] to have a length of 2 but got 3
FAIL > answers an unknown column with an EMPTY deck, not with everything
expected [ { id: 'summary', …(5) }, …(2) ] to have a length of 1 but got 3
```
Both directions are asserted on purpose: a filter that matched *nothing*
would satisfy the renamed-lane case alone while being equally broken.
## Not changed, and why
`plugins/fusion-plugin-even-cards` carries the **identical** bug — I
wrote the fix there first. It was removed from the pnpm workspace
(`858bab2`, "remove `fusion-plugin-even-cards` from the active workspace
package list to avoid duplicate user-facing integrations") and its
README names this plugin as its replacement, so it is not built, tested,
or shipped by anything. Fixing it would only imply it still runs.
Reverted and left alone.
## Verification
- `pnpm test:gate` — 161 / 487 / 13 / 71 passed
- `pnpm lint` — clean
- `tsc --noEmit` (`@fusion-plugin-examples/even-realities-glasses`) —
clean
- full plugin suite — 188 passed across 19 files
- census `--strict` — exit 0 (unchanged: these literals are Set members,
invisible to it)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
d252c4e0cf |
fix(glasses): finished cards were crowding live work off the deck on a renamed board (#2854)
Not a labelling bug. `boardToDeck` filters finished cards out of a deck that is **capped at `maxCards`**, so on a board whose complete lane is named anything but `done`/`archived`, every finished card **consumes a slot and displaces live work**. The wearer sees fewer live tasks the more the team finishes — which reads as "nothing is happening", not as a defect. ```ts const active = tasks.filter((task) => task.column !== "archived" && task.column !== "done") .sort(...) .slice(0, Math.max(0, maxCards - 1)); // ← the cap is what makes this bite ``` ## Correcting an earlier DELIBERATE-LITERAL call This site was marked **DELIBERATE-LITERAL — no resolution source in this package**. That reasoning was inherited from the **deprecated** `fusion-plugin-even-cards`, which depends on `@fusion/plugin-sdk` alone. **This** package lists `@fusion/core` as a runtime dependency and already calls `resolveWorkflowIrById` / `resolveLifecycleColumns` in `quick-capture.ts` and `agent-actions.ts`. The half that *was* right: `cards.ts` genuinely cannot resolve anything — it takes plain `Task` rows. So the lane answer becomes a parameter and the **route** supplies it: one `listWorkflowDefinitions()` read per request regardless of board size, matching how `?columns=` already treats the board as a single pool. Best-effort, so a failed resolve leaves the deck on its documented default rather than failing the request — a slightly-wrong deck beats no deck on a pair of glasses. `terminalColumns` is in the `unwired-lane-parameter` vocabulary, so an unwired version of this parameter fails the build instead of sitting here looking converted. (That guard only learned to see this class in #2852.) ## Revert proof (measured) ``` FAIL > does not let a card in a RENAMED complete lane displace live work expected [ 'summary', 'FN-SHIPPED' ] to deeply equal [ 'summary', 'FN-LIVE' ] ``` `maxCards: 2` in the fixture is load-bearing — one summary card plus exactly one task slot, so an unfiltered finished card **displaces** the live one rather than merely joining it. A larger cap would let both through and the case would pass either way. The second case pins the degraded default (legacy `done`/`archived` still filtered when the caller resolved nothing), since most boards never rename anything. ## Not changed `cards.ts:187` — `task.column === "in-review" ? "In review" : "Moved in"` in the notification title. Genuinely cosmetic: a review card on a renamed board reads "Moved in" instead of "In review". No slot is lost and no decision is made from it, and `notificationCard` has no options object to thread a lane answer through, so converting it means widening a signature for a label. Left with the finding stated rather than silently swept in. ## Verification - `pnpm test:gate` — 161 / 487 / 13 / 71 passed - `pnpm lint` — clean - `tsc --noEmit` (`@fusion-plugin-examples/even-realities-glasses`) — clean - full plugin suite — 188 passed across 19 files - unwired-lane guard — 6/6, no new entries - census `--strict` — exit 0 Touches `board-routes.ts`, which #2849 also edits (different hunk — `parseColumns` vs the handlers), so the two merge cleanly in either order. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ed6d54485b |
glasses plugin: the review actions could never resolve a review lane (4 guards + 3 invisible destinations) (#2816)
Four agent actions still keyed on literals, with three census-invisible `moveTask` destinations between them. `agent-actions.ts` already had `laneContext`/`destination` from an earlier partial conversion — these were simply never migrated. ## Census | file | main | here | | --- | ---: | ---: | | `plugins/fusion-plugin-even-realities-glasses/src/agent-actions.ts` | 4 | **0** | Plus 3 hardcoded `moveTask` destinations the census cannot see (`requestReview` → `in-review`, `returnToAgent` → `todo`, `retryTask` → `todo`). ## The real finding: this plugin could never resolve a review lane `resolveLifecycleColumns` keys its `review` role on the **`mergeOrchestration` trait alone**. A board whose review column carries only `merge-blocker` and/or `human-review` — the common custom shape, since `merge` is opt-in — resolves **no review lane at all**. So every review-gated action here (`requestReview`, `acceptReview`, `returnToAgent`, `retryTask`) compared against `undefined` and **refused every card**, and `requestReview` had nowhere to move one. This is not a regression from converting them; it is why they *could not* be converted with `lanes.review` as-is. Converting the four guards without noticing would have shipped four actions that fail closed on exactly the boards this program exists to support — a conversion that looks complete, passes its suite, and makes the plugin useless on a custom board. **Widened in `laneContext`, not in the shared resolver.** `resolveLifecycleColumns` is consumed well beyond this plugin, and its `review` role deliberately means "the merge-orchestration column" for the merge queue. The gap is already recorded in `notification-renamed-lifecycle-columns.test.ts` and in #2807 — reconciling the two definitions is a core-level decision, not one to take from a plugin. `mergeBlocker` is preferred over `humanReview` because a card cannot leave a merge-blocking column until the gate clears, which is the closer analogue of the legacy `in-review`. ## The suite caught an over-reach of mine My first version put a blanket `if (degraded) conflict(...)` at the top of `retryTask`, which broke a pinned invariant the test names outright: **"a degraded workflow does not block retries that move nothing."** The status-only retry just clears fields; refusing it because the workflow could not be read breaks a recovery that needs no lane at all. Degraded now blocks only the branches that actually **move**. Same reasoning applied to `acceptReview`, which also moves nothing. The existing `startWork` convention — conflict on degraded — is right precisely *because* it moves. ## Ordering `returnToAgent` and `retryTask` now resolve their destination **before** the field clear. Both cleared first, so a rejected move left the assignee and status — or the worktree, branch and base refs — nulled with the card exactly where it was. That is the fifth instance of this half-applied shape in the audit, and it is rule 3 in the class doc. ## Revert results (measured, each independently) | conversion | reverted → | | --- | --- | | `requestReview` destination | 1 failed — moves to the literal `in-review`, which this workflow does not declare | | `returnToAgent` destination | 1 failed — moves to the literal `todo`, same | Plus a non-vacuous companion: a renamed card *not* in the wip lane must still be refused by `requestReview`, so a gate admitting everything would not pass. ## Verification - Plugin suite — **186/186** - `pnpm test:gate` — 161 + 487 + 13 + 71, green - `tsc` on the plugin — clean - `pnpm lint`, `check:changesets`, census `--strict` — all clean (run explicitly) |
||
|
|
4184fde08d |
batch-cli-plugins: 7 guards — 3 were a foreign enum, and fn pr create refused every card on a renamed board (#2775)
`batch-cli-plugins` — the u7 worker's mega-batch: `packages/cli` + `plugins` + anything left. ## The batch is 7 guards, and 3 of them are not guards at all The census's per-file list gives this batch seven sites. Reading them, **three are a foreign vocabulary the census matches on the string alone**: | file | site | verdict | |---|---|---| | `plugins/fusion-plugin-reports/store/report-store.ts` | `next === "archived"` ×2 | **not a column** — `next` is a `ReportStatus` | | `plugins/fusion-plugin-reports/store/report-types.ts` | `to === "failed" \|\| to === "archived"` | **not a column** — same enum, its own terminal states | The reports plugin has its own status lineage (`draft → generating → review_* → approved → published`, plus `failed`/`archived`) that shares two spellings with the lifecycle vocabulary. A report is not on a board and has no workflow, so resolving an IR there would answer a question nobody asked. All three are marked `DELIBERATE-LITERAL` with the reason at the site. **This cuts the other way from #2763.** That PR establishes the census total as a *floor* (25 membership predicates it structurally cannot see). This is the opposite error in the same number: a foreign enum inflating it. The total is neither a ceiling nor a floor — it is an estimate with error in both directions, and the per-file list is worth reading before trusting a file's count. ## Converted (census before → after, per file) | file | before | after | |---|---|---| | `packages/cli/src/commands/pr.ts` | 1 | **0** | | `plugins/…/even-realities-glasses/notifications/diff.ts` | 1 | **0** | | `plugins/…/reports/store/report-store.ts` | 2 | **0** (deliberate) | | `plugins/…/reports/store/report-types.ts` | 1 | **0** (deliberate) | ### `fn pr create` refused every card on a renamed board The live defect in this batch. The gate was `task.column !== "in-review"`, and its error told the operator to move the task to a column their board does not have: ``` Error: Task must be in 'in-review' column to create a PR (current: signoff) ``` There is no way to satisfy that short of renaming the workflow back. Now resolved through core's `resolveReviewColumns`, and the message names the lanes that actually exist. **The SET, not `lifecycle.review`.** A board may declare more than one review lane, and a card parked in a `humanReview`-only lane is still a card you can open a PR from. A single-id answer keeps refusing those — the same narrowing #2728's review caught in the CLI retry gate, which is why the test pins both lanes. ## Skipped, with the reason **`plugins/fusion-plugin-even-cards` (2 guards) — blocked on packaging, not on analysis.** The defect is real: `boardToDeck` filters with `column !== "archived" && column !== "done"`, so on a renamed board every finished card stays in the deck, fills `maxCards`, and pushes the active cards off the display. The wearer sees a board that never finishes anything. I implemented the fix and **reverted it**: this plugin is not in `pnpm-workspace.yaml` and depends only on `@fusion/plugin-sdk` — it has no `@fusion/core` dependency, so the route cannot reach `resolveTaskLifecycleColumns`. Adding one is a packaging change, which this program's rules put out of scope. Shipping only the injected parameter without a caller was the alternative, and that is precisely the decorative conversion #2759 documents: the census would drop by 2 and the deck would keep the bug. Flagged for whoever owns the plugin's dependency surface. The glasses plugin next door *does* depend on `@fusion/core`, so this is a one-plugin problem, not a plugin-wide one. ## Honest note on the glasses conversion `diff.ts`'s completion branch is **currently unreachable** — the only production caller (`notifier.ts`) passes `alsoNotifyOnDone: false`. So that conversion changes nothing at runtime today. It is converted rather than marked deliberate because the literal is not deliberate: it is wrong, and would ship the bug the day someone turns the flag on. Stated here rather than left for a reviewer to discover. ## Verification - new CLI suite **4 passed**; `pr-command` + `pr-automerge-cleanup` + `bin-pr-router` **35 passed** - glasses plugin **181 passed (19 files)** · reports plugin **110 passed (23 files)** - `pnpm test:gate` — **158 / 10 / 487 / 71** · `pnpm lint` clean · `--strict` exits 0 **Revert proof, measured.** Restoring `if (task.column !== "in-review")` fails 3 of the 4 new cases (`process.exit:1` on both renamed lanes, and the refusal message reverts to naming `in-review`). The unresolvable-workflow case keeps passing — it is the legacy path — so the negative cases alone do not pin the fix and all four are required. ## Handoff to `batch-engine` `packages/engine/src/project-engine.ts` **5 → 0** is finished, green, and pushed as `handoff/project-engine-lanes-for-batch-engine` (`34dbb35209`) for the capacity worker to cherry-pick — it is engine-owned, not mine to land. It fixes two live defects: a card that **had merged** reported as a failed merge to `fn task merge` and the dashboard button (`merged: finalTask?.column === "done"`), and the three post-finalize `column === "done" && mergeConfirmed` fast-path checks, which on a renamed board sent an already-landed card down the bounce path — re-queued, retry-counted, and in the capped branch parked `failed` with its merge sitting on main. Plus `hasAutoHealableVerificationBufferFailure`, which returned false for every card on a renamed board, so a buffer-overflow verification failure was never auto-healed. 8 new tests, revert-proven (restoring the literal fails 4 of 8), gate green. --- ## Completion pass (u7) — the batch is now closed Two workers converged on this branch. I rebased onto the first-landed commit rather than force-pushing over it, took its wording wherever the conclusion was identical, and added what was missing. ### What this pass added 1. **`even-cards` (2 sites)** — the only in-scope file the first pass left open. Marked DELIBERATE-LITERAL: the package depends on `@fusion/plugin-sdk` only, and the SDK does not re-export the lifecycle role helpers, so there is no IR, no store, and no trait flags to resolve *from*. Fixing it properly means the SDK exposing role flags on the task shape it hands plugins — a structural change, out of scope, and recorded at the site as the correct home. Live consequence is cosmetic: a finished card on a renamed board shows as active in the glasses deck. 2. **A red test in the `fn pr create` conversion.** The incoming version rendered `Task must be in 'in-review' to create a PR`, dropping the word `column`. `task.test.ts:3422` pins `must be in 'in-review' column`, so that hunk failed `runTaskPrCreate > exits with error when task not in in-review column`. Restoring the word makes the single-lane message **byte-identical** to the pre-conversion one, which is what a vocabulary conversion should be — the guard's own test now passes unmodified. Marked at the site so it is not "simplified" back. 3. **Duplicate imports** — the two independent conversions each added `resolveWorkflowIrForTask`/`resolveReviewColumns`, which does not compile. Deduped in its own commit. ### Census Measured with `--json` on `origin/main` and on this branch. | file | before | after | action | |---|---|---|---| | `packages/cli/src/commands/pr.ts` | 1 | 0 | converted | | `plugins/fusion-plugin-reports/src/store/report-types.ts` | 1 | 0 | marked | | `plugins/fusion-plugin-reports/src/store/report-store.ts` | 2 | 0 | marked | | `plugins/fusion-plugin-even-cards/src/cards/board-cards.ts` | 2 | 0 | marked | | `plugins/fusion-plugin-even-realities-glasses/.../diff.ts` | 1 | 0 | marked | Backlog **415 → 408** (−7, exactly the in-scope count). Deliberate **40 → 46** (+6 marked); 6 + 1 converted = 7. `--strict` exits 0. **Nothing remains in `cli` + `plugins` + everything-else — there is no follow-up batch behind this one.** ### One note on the `even-realities-glasses` site Worth recording beyond "cannot resolve": its only production caller (`notifier.ts:80`) passes `alsoNotifyOnDone: false`, so that arm is **unreachable today**. Converting it could not have changed observed behaviour either way. ### Verification (measured, on the merged branch) - `pnpm --filter @runfusion/fusion exec tsc --noEmit` → exit 0 - `pnpm lint` → 0 errors - CLI `task.test.ts` → 144 passed, including the `runTaskPrCreate` guard test - `@fusion-plugin-examples/reports` → 110 passed; `even-realities-glasses` → 181 passed **Pre-existing failures, not from this change:** the 5 `runTaskImportFromGitHub` / `runTaskImportGitHubInteractive` tests fail identically on `origin/main` — verified by stashing this diff and re-running (5 failed / 144 passed both ways). --- ## Census audit (unowned follow-on) After closing the batch scope I audited whether the **392** column-backlog number is inflated by foreign vocabularies — the class this batch found in the reports plugin, where `"archived"` is a `ReportStatus` rather than a board lane. If that class were widespread, every remaining batch would be chasing sites that must not be converted. **It is not. The number is real.** A receiver-level pass over all 392 column-category sites found exactly **3** false positives, all in `plugins/fusion-plugin-reports` (`next`, a `ReportStatus`), all now marked in this PR. What was checked and cleared: - **Property-reached foreign enums** (`step.status`, `feature.status`, `mission.status`) — already correctly bucketed into the separate `status` category (185), not the column backlog. Verified against `merge-queue-ops.ts`: 11 lifecycle-spelled literals in the file, census counts **1**, and that 1 is the genuine `.column` guard. - **Bare step-status variables** (`status`, `currentStatus`, `liveStatus` compared to `"done"`/`"skipped"`) — likewise excluded. - **Every other receiver in the backlog** — `to`, `from`, `column`, `fromColumn`, `toColumn`, `latestColumn`, `state`, `preArchiveColumn`. All resolve to genuine task columns. `executor.ts`'s 15 sites were spot-checked line by line: all 15 are real. The gap the classifier genuinely cannot close is a foreign enum held in a **bare variable** — the receiver name carries no type information, so `next === "archived"` is indistinguishable from a lifecycle guard by AST alone. That is why the reports sites need a marker rather than a classifier fix, and it is now documented in `lifecycle-column-census-ast.mjs`'s header alongside the measured scope, so the remaining batches do not re-run this hunt. Census tests: **43 passed**. The change is comment-only. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3e80dcb8ef |
fix(test): a Vite prefix-match alias silently unresolved a core subpath (greens full-suite shard 1) (#2686)
## What `full-suite.yml` shard 1 on main fails with **zero test failures** — it dies on a resolution error: ``` Failed to resolve import "@fusion/core/task-delete-attribution" from "packages/dashboard/app/api/client.ts" ``` **Root cause.** Vite string aliases match by **PREFIX**. So `find: "@fusion/core"` → `core/src/index.ts` rewrites `@fusion/core/task-delete-attribution` into `core/src/index.ts/task-delete-attribution`, which cannot resolve. The narrower subpath alias has to come *first*. The module exists and *is* correctly declared in `packages/core/package.json` exports — this is purely a test-config trap, and `packages/dashboard/vitest.config.ts` already documents it in a comment. Six configs alias `@fusion/dashboard` (whose `app/api/client.ts` imports that browser-safe leaf) while lacking the narrower alias, so they inherited the trap. This carries the same one-line pattern to all six. ## Measured `dependency-graph` — the project actually red on main: | | Test files | Tests collected | |---|---|---| | before | 3 failed \| 17 passed | 147 | | after | **20 passed** | **180** | **33 tests were never collected** — neither passing nor reported as failing. That is the part worth flagging: an unresolved import removes tests from the run silently, and the shard's own summary printed no `Tests N failed` line at all, which is why this red looked like infrastructure noise rather than a real defect. No regressions: `reports` 110, `cli-printing-press` 41, `compound-engineering` 317, **gate 726** — all green. `pnpm lint` clean. `@fusion/desktop` is `1 failed | 264 passed` **both before and after**; verified pre-existing on clean `origin/main` by reverting just that one config and re-running. Cause is `@fusion-plugin-examples/roadmap` entry resolution, unrelated — **flagged, not fixed.** ## Deliberately not changed Engine's *second* `@fusion/core` alias (the `.gate-bundle/core.mjs` entry) is untouched: that lane bundles core on purpose, and pointing it at source would defeat the isolation the gate bundle exists to provide. ## Full-suite triage this came out of (for whoever owns the rest) Reading the four red shards of the last completed run on main (`30523568756`): | Shard | Real cause | Owner | |---|---|---| | 1/4 | **this PR** — resolution error, 0 test failures | — | | 2/4 | 23 failed: `store-wedge-resolution.pg`, `central-archive-secrets`, `task-delete-caller-attribution`, `task-delete-nonblocking-cleanup` | #2669 / #2675 cover the first two | | 3/4 | **watchdog SIGKILL** mid-`@fusion/engine [1/2]` — no test failures, no summary | unowned | | 4/4 | 17 failed, all in `@runfusion/fusion` CLI (`project.test.ts` 8, `task.test.ts` 5, `extension.test.ts` 2, +2) | unowned | Two of the four shard reds contain **no failing test at all**, so "main's full-suite failure count" cannot be read off the shard conclusions — it has to be read off `Tests N failed` summary lines, and shards 1 and 3 emit none. |
||
|
|
dca20496f4 |
consolidate/u7: plugins to zero + 8 executor rebound guards + resume lanes (supersedes #2607, #2635, #2640) (#2644)
Consolidation branch for U7, per the new one-branch working mode. **Supersedes #2607, #2635, #2640** — the three of my PRs that were stuck on review threads. My other seven (#2602, #2605, #2606, #2611, #2621, #2628, #2633) are green with **zero unresolved threads** and are deliberately left alone for the merge sweep. ## What is in here, file by file | file | change | guards before → after | |---|---|---| | `plugins/…/glasses/src/agent-actions.ts` | gates, destinations and degraded-resolution refusal all resolve from the task's own workflow | 2 → 0 | | `plugins/…/glasses/src/quick-capture.ts` | accepted capture columns come from the board; default no longer names the deleted column | 1 → 0 | | `plugins/…/glasses/src/settings.ts` | quick-capture default was `triage`, the column #2515 removed | (assignment, uncounted) | | `plugins/…/dependency-graph/src/GraphTaskNode.tsx` | redundant column condition deleted | 1 → 0 | | `packages/engine/src/executor.ts` | 8 rebound guards compare the resolved column; 4 resume-eligibility literals share one resolver | 151 → 143 (+4 off-bar) | | `packages/engine/src/__tests__/` | 4 new suites, 26 cases | — | `plugins/` reaches **zero** column guards with this branch. ## The three threads it closes **#2607 — five findings, all mine, all the same rule.** I kept *qualifying* a legacy-id fallback instead of removing it: | attempt | rule | hole review found | |---|---|---| | 1 | fall back to `todo` when the role is missing | moved cards to phantom columns | | 2 | …only if the workflow **declares** `todo` | aliased **review** lane named `todo` | | 3 | …and only if no other role is assigned to it | **traitless** parking column named `todo` | The qualifications were the mistake. Once `resolveLanes` returns a lane set the workflow *has* a column vocabulary, so "no column carries the hold trait" is a complete answer — refuse. `destination()` is two lines now, with no aliasing surface left to qualify. Plus a sixth, which is a genuinely different state: **degraded resolution is indistinguishable from the default board.** `resolveWorkflowIrForTask` is total by design — a missing definition silently returns the *default* coding IR — so a card on a custom board whose definition could not be read resolved to `todo`/`in-progress`. `undefined` lanes cannot express that (it means "no workflow at all", where the legacy ids *are* the answer). The actions now refuse with 409. #2618 would replace this check with resolver provenance; it is not merged, so this does not depend on it. **#2635 — "seven rebound sites remain untested."** Fair; my "same shape" note was an assertion, not coverage. Seven of the eight need a live graph run to reach, so the *shape* is pinned instead: a static check that no guard in front of a rebound move compares against a column literal, with a vacuity case (the same detection run against the original shape) and a match-count floor (≥8), because a guard reporting success on zero matches is worse than no guard. **#2640 — duplicate workflow resolution.** Framed as I/O; it is also a correctness bug. Eligibility and re-entry are two halves of one decision and resolved the workflow separately, so a workflow edit landing between them has the halves reading *different boards*. Now one caller-owned memo per decision — caller-owned because a process-lifetime cache would have to guess when a mid-flight workflow edit invalidates it. ## Behavioural findings, not tidying - **The last-resort recovery for completed-but-stranded work did not exist off the default lineage.** `promotedFromPlannerColumn` was false on a renamed board, so finished work resting in planning was never promoted; the code fell through to a review handoff that role adjacency rejects, and the card stayed stuck with its work complete. - **Rebound guards could not see the column their own move targeted.** U5b converted the move target; the eight `column !== "todo"` checks in front of it were left literal, so on a renamed board the engine moved a card into the column it was already in — and `moveTaskInternal` runs reset-on-entry on every real move, so at the `preserveProgress: false` site it reset step progress a second time. - **The FN-1404 `task:move` audit row was lying**, recording `to: "todo"` while the move target was resolved. A run-audit trail that disagrees with the move it describes is worse than none. Not a comparison, so no census counts it. - **A task interrupted by an engine pause never resumed on a renamed board** (off-bar, `in-review`/`in-progress` literals): four comparisons decided one question and had to agree; two of them disagreed on a renamed board, so re-entry silently never fired. ## Revert proofs, isolated per site | reverted | result | |---|---| | `destination()` back to attempt 3 | 3 of 38 fail | | degraded-resolution refusals removed | 2 of 42 fail | | capture set back to the legacy five | 2 of 3 fail (renamed-board suite) | | forward exclusions → literals | 1 of 14 fails | | missing-wip refusal removed | 2 of 14 fail | | `promotedFromPlannerColumn` → literals | 3 of 7 fail | | promotion target → `"in-progress"` | 3 of 7 fail | | one rebound guard → `!== "todo"` | 1 of 3 fails (static shape) | | resume lanes → legacy trio | 1 of 5 fails | Every conversion is paired with a negative — a forward move, a not-a-planner-lane card, a default-lineage card, an unresolvable workflow — so neither "always fire" nor "never fire" can pass for "resolve the role". ## Commit discipline Twelve commits, each one thing: the code move (`resolvePlannerLanes` out of `triage.ts`) is separate from every behavior change, and each review fix is its own commit with its own revert proof. ## Verification - `pnpm test:gate` **71/71** - 162/162 across the glasses plugin's 19 files; 26/26 across the four new engine suites - engine + glasses typecheck clean; `pnpm lint` clean 🤖 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** * Engine recovery and retries now work correctly with renamed or customized workflow columns. * Tasks in manual-intake columns are no longer automatically planned. * Agent actions and quick capture now respect each board’s declared columns and lifecycle stages. * Awaiting-approval tasks are recognized regardless of their current column. * Command Center SDLC funnel stages now accurately reflect customized workflows. * **Documentation** * Added guidance for safely changing workflow-column logic and interpreting lifecycle-column checks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4819c26349 |
chore(deps-dev): bump jsdom from 27.4.0 to 29.1.1 (#2450)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 27.4.0 to 29.1.1. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/jsdom/jsdom/releases">jsdom's releases</a>.</em></p> <blockquote> <h2>v29.1.1</h2> <ul> <li>Fixed <code>'border-radius'</code> computed style serialization. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> <li>Fixed computed style computation when using <code>'background-origin'</code> and <code>'background-clip'</code> CSS properties. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> <li>Significantly optimized initial calls to <code>getComputedStyle()</code>, before the cache warms up. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> </ul> <h2>v29.1.0</h2> <ul> <li>Added basic support for the ratio CSS type. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> <li>Fixed <code>getComputedStyle()</code> sometimes returning outdated results after CSS was modified. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> </ul> <h2>v29.0.2</h2> <ul> <li>Significantly improved and sped up <code>getComputedStyle()</code>. Computed value rules are now applied across a broader set of properties, and include fixes related to inheritance, defaulting keywords, custom properties, and color-related values such as <code>currentcolor</code> and system colors. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> <li>Fixed CSS <code>'background</code>' and <code>'border'</code> shorthand parsing. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> </ul> <h2>v29.0.1</h2> <ul> <li>Fixed CSS parsing of <code>'border'</code>, <code>'background'</code>, and their sub-shorthands containing keywords or <code>var()</code>. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> <li>Fixed <code>getComputedStyle()</code> to return a more functional <code>CSSStyleDeclaration</code> object, including indexed access support, which regressed in v29.0.0.</li> </ul> <h2>v29.0.0</h2> <p>Breaking changes:</p> <ul> <li>Node.js v22.13.0+ is now the minimum supported v22 version (was v22.12.0+).</li> </ul> <p>Other changes:</p> <ul> <li>Overhauled the CSSOM implementation, replacing the <a href="https://www.npmjs.com/package/@acemir/cssom"><code>@acemir/cssom</code></a> and <a href="https://github.com/jsdom/cssstyle"><code>cssstyle</code></a> dependencies with fresh internal implementations built on webidl2js wrappers and the <a href="https://www.npmjs.com/package/css-tree"><code>css-tree</code></a> parser. Serialization, parsing, and API behavior is improved in various ways, especially around edge cases.</li> <li>Added <code>CSSCounterStyleRule</code> and <code>CSSNamespaceRule</code> to jsdom <code>Window</code>s.</li> <li>Added <code>cssMediaRule.matches</code> and <code>cssSupportsRule.matches</code> getters.</li> <li>Added proper media query parsing in <code>MediaList</code>, using <code>css-tree</code> instead of naive comma-splitting. Invalid queries become <code>"not all"</code> per spec.</li> <li>Added <code>cssKeyframeRule.keyText</code> getter/setter validation.</li> <li>Added <code>cssStyleRule.selectorText</code> setter validation: invalid selectors are now rejected.</li> <li>Added <code>styleSheet.ownerNode</code>, <code>styleSheet.href</code>, and <code>styleSheet.title</code>.</li> <li>Added bad port blocking per the <a href="https://fetch.spec.whatwg.org/#bad-port">fetch specification</a>, preventing fetches to commonly-abused ports.</li> <li>Improved <code>Document</code> initialization performance by lazily initializing the CSS selector engine, avoiding ~0.5 ms of overhead per <code>Document</code>. (<a href="https://github.com/thypon"><code>@thypon</code></a>)</li> <li>Fixed a memory leak when stylesheets were removed from the document.</li> <li>Fixed <code>CSSStyleDeclaration</code> modifications to properly trigger custom element reactions.</li> <li>Fixed nested <code>@media</code> rule parsing.</li> <li>Fixed <code>CSSStyleSheet</code>'s "disallow modification" flag not being checked in all mutation methods.</li> <li>Fixed <code>XMLHttpRequest</code>'s <code>response</code> getter returning parsed JSON during the <code>LOADING</code> state instead of <code>null</code>.</li> <li>Fixed <code>getComputedStyle()</code> crashing in XHTML documents when stylesheets contained at-rules such as <code>@page</code> or <code>@font-face</code>.</li> <li>Fixed a potential hang in synchronous <code>XMLHttpRequest</code> caused by a race condition with the worker thread's idle timeout.</li> </ul> <h2>v28.1.0</h2> <ul> <li>Added <code>blob.text()</code>, <code>blob.arrayBuffer()</code>, and <code>blob.bytes()</code> methods.</li> <li>Improved <code>getComputedStyle()</code> to account for CSS specificity when multiple rules apply. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> <li>Improved synchronous <code>XMLHttpRequest</code> performance by using a persistent worker thread, avoiding ~400ms of setup overhead on every synchronous request after the first one.</li> <li>Improved performance of <code>node.getRootNode()</code>, <code>node.isConnected</code>, and <code>event.dispatchEvent()</code> by caching the root node of document-connected trees.</li> <li>Fixed <code>getComputedStyle()</code> to correctly handle <code>!important</code> priority. (<a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a>)</li> <li>Fixed <code>document.getElementById()</code> to return the first element in tree order when multiple elements share the same ID.</li> <li>Fixed <code><svg></code> elements to no longer incorrectly proxy event handlers to the <code>Window</code>.</li> <li>Fixed <code>FileReader</code> event timing and <code>fileReader.result</code> state to more closely follow the spec.</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href=" |
||
|
|
af3d78d2cd |
chore(deps-dev): bump vitest from 4.1.8 to 4.1.10 (#2446)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.8 to 4.1.10. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vitest-dev/vitest/releases">vitest's releases</a>.</em></p> <blockquote> <h2>v4.1.10</h2> <h3> 🐞 Bug Fixes</h3> <ul> <li><strong>browser</strong>: Check fs access in builtin commands [backport to v4] - by <a href="https://github.com/hi-ogawa"><code>@hi-ogawa</code></a>, <strong>Hiroshi Ogawa</strong> and <strong>OpenCode (claude-opus-4-8)</strong> in <a href="https://redirect.github.com/vitest-dev/vitest/issues/10680">vitest-dev/vitest#10680</a> <a href="https://github.com/vitest-dev/vitest/commit/5c18dd267"><!-- raw HTML omitted -->(5c18d)<!-- raw HTML omitted --></a></li> <li><strong>vm</strong>: Fix external module resolve error with deps optimizer query for encoded URI [backport to v4] - by <a href="https://github.com/SveLil"><code>@SveLil</code></a> and <a href="https://github.com/hi-ogawa"><code>@hi-ogawa</code></a> in <a href="https://redirect.github.com/vitest-dev/vitest/issues/10661">vitest-dev/vitest#10661</a> <a href="https://github.com/vitest-dev/vitest/commit/bae52b511"><!-- raw HTML omitted -->(bae52)<!-- raw HTML omitted --></a></li> </ul> <h5> <a href="https://github.com/vitest-dev/vitest/compare/v4.1.9...v4.1.10">View changes on GitHub</a></h5> <h2>v4.1.9</h2> <h3>🐞 Bug Fixes</h3> <ul> <li>Fix <code>importOriginal</code> with optimizer and query import [backport to v4] - by <strong>Hiroshi Ogawa</strong>, <strong>David Harris</strong>, <strong>Codex</strong>and <strong>Vladimir</strong> in <a href="https://redirect.github.com/vitest-dev/vitest/issues/10546">vitest-dev/vitest#10546</a> <a href="https://github.com/vitest-dev/vitest/commit/a5180190c"><!-- raw HTML omitted -->(a5180)<!-- raw HTML omitted --></a></li> <li><strong>browser</strong>: <ul> <li>Wait for orchestrator readiness before resolving browser sessions [backport to v4] - by <strong>Vladimir</strong> and <strong>Séamus O'Connor</strong> in <a href="https://redirect.github.com/vitest-dev/vitest/issues/10555">vitest-dev/vitest#10555</a> <a href="https://github.com/vitest-dev/vitest/commit/7fb29651a"><!-- raw HTML omitted -->(7fb29)<!-- raw HTML omitted --></a></li> <li>Wait for iframe tester readiness before preparing [backport to v4] - by <strong>Vladimir</strong> and <strong>Séamus O'Connor</strong> in <a href="https://redirect.github.com/vitest-dev/vitest/issues/10497">vitest-dev/vitest#10497</a> and <a href="https://redirect.github.com/vitest-dev/vitest/issues/10556">vitest-dev/vitest#10556</a> <a href="https://github.com/vitest-dev/vitest/commit/fbc626c40"><!-- raw HTML omitted -->(fbc62)<!-- raw HTML omitted --></a></li> </ul> </li> <li><strong>mocker</strong>: <ul> <li>Hoist vi.mock() for vite-plus/test imports [backport to v4] - by <strong>Hiroshi Ogawa</strong>, <strong>LongYinan</strong>, <strong>Claude Opus 4.8</strong> and <strong>Vladimir</strong> in <a href="https://redirect.github.com/vitest-dev/vitest/issues/10548">vitest-dev/vitest#10548</a> <a href="https://github.com/vitest-dev/vitest/commit/2c9559c02"><!-- raw HTML omitted -->(2c955)<!-- raw HTML omitted --></a></li> </ul> </li> <li><strong>pool</strong>: <ul> <li>Prevent test run hang on worker crash [backport to v4] - by <strong>Ari Perkkiö</strong> and <strong>Jattioui Ismail</strong> in <a href="https://redirect.github.com/vitest-dev/vitest/issues/10543">vitest-dev/vitest#10543</a> and <a href="https://redirect.github.com/vitest-dev/vitest/issues/10564">vitest-dev/vitest#10564</a> <a href="https://github.com/vitest-dev/vitest/commit/934b0f587"><!-- raw HTML omitted -->(934b0)<!-- raw HTML omitted --></a></li> </ul> </li> </ul> <h5><a href="https://github.com/vitest-dev/vitest/compare/v4.1.8...v4.1.9">View changes on GitHub</a></h5> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href=" |
||
|
|
5ae6332563 |
refactor: collapse dead SQLite dual-path code; keep migration-only readers (#2454)
# Remove dead SQLite dual-path code; keep migration-only readers ## Summary PostgreSQL cutover left hundreds of production dual-path branches (`backendMode ? PG : SQLite/store.db`) whose SQLite arms only hit throwing `Database`/`ArchiveDatabase`/`CentralDatabase` stubs. This change mechanically collapses those unreachable arms so production authority is AsyncDataLayer/PostgreSQL only, while preserving the six authorized read-only migration/recovery `DatabaseSync` seams. ## Dual-path mass removed | Metric | Before | After | |---|---|---| | `if (…backendMode)` (non-test) | ~328 | ~70 | | `store.db` / `this.db` refs in core (non-test) | ~570+ | ~375 (mostly pure legacy MissionStore/eval/insight SQLite classes + thin getters) | | Net diff | — | **~6.7k lines removed** across 41 files | Remaining `backendMode` checks are intentional (incomplete-PG sync safe-defaults, settings-sync disabled-on-PG, symbol-lock PG-only gates, “requires PostgreSQL” config versioning throws), not live SQLite authority. ## Subsystems cleaned - **Core TaskStore / task-store/***: collapsed if/else and early-return dual-path across reads, moves, lifecycle, mutations, workflow, archive, branch/PR, artifacts, comments, audit, project ops, etc. `initImpl` is PostgreSQL-only (SQLite startup tail deleted). - **Satellite stores**: automation, agent, routine, plugin, secrets, approval-request, central-core dual-path arms collapsed. - **Plugins**: reports async methods, compound-engineering pipeline + session stores, CLI Printing Press store — SQLite fallbacks removed; PG required. - **Engine**: no functional dual-path change beyond whitespace (settings-sync / peer-exchange PG-disabled behavior kept). ## Six migration-only readers retained (allowlist unchanged) 1. `packages/core/src/postgres/sqlite-migrator.ts` 2. `packages/core/src/project-identity.ts` 3. `packages/core/src/sqlite-validation.ts` 4. `packages/core/src/postgres/startup-factory.ts` 5. `packages/cli/src/commands/db.ts` 6. `scripts/lib/start-local-project.mjs` Plus low-level `sqlite-adapter` and migrator/startup-import tests. Inventory ratchet still requires exactly these six `new DatabaseSync(` production sites, all `readOnly: true`. ## Not treated as SQLite - `.fusion/project.json`, `task.json`, `agent-log.jsonl` file storage - AsyncDataLayer / Drizzle PG paths - Incomplete-PG sync safe-default stubs (still return empty/false/null under backend without consulting SQLite) ## Verification - `sqlite-production-reader-inventory.test.ts` — 15/15 pass - `incomplete-pg-ports.pg.test.ts` — 6/6 pass - Targeted PG tests (create-task, move, handoff, runtime-persistence, agent, mission, insight, central-core) — green - `tsc --noEmit` for `@fusion/core`, `@fusion/engine`, `@fusion/dashboard` — green - `scripts/check-no-getdatabase.mjs` — clean <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Improved end-to-end consistency by making PostgreSQL/async persistence the standard across core task/workflow, automation, agents, plugins, routines, secrets, approvals, central operations, and session storage. * Unified scheduling, settings, configuration revision writes, run/workflow selection, queues/leases/transitions, and audit/lifecycle updates around consistent async transaction behavior. * **Bug Fixes** * Fixed edge cases for archived/deleted reads, unarchive/recovery flows, not-found handling, and task/artifact/document/log/comment operations, including more reliable emissions and hydration across search/list and lifecycle operations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |