Commit Graph

12350 Commits

Author SHA1 Message Date
dependabot[bot]
ff58df22e4 chore(deps-dev): bump @vitest/coverage-v8 from 4.1.8 to 4.1.10 (#2445)
Bumps
[@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8)
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/coverage-v8'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="db616d227b"><code>db616d2</code></a>
chore: release v4.1.10 (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8/issues/10718">#10718</a>)</li>
<li><a
href="a7a61e78c7"><code>a7a61e7</code></a>
chore: release v4.1.9 (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8/issues/10598">#10598</a>)</li>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/coverage-v8">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-07-27 19:21:32 -07:00
dependabot[bot]
a7856e8a90 chore(deps): bump @xyflow/react from 12.11.0 to 12.11.2 (#2447)
Bumps
[@xyflow/react](https://github.com/xyflow/xyflow/tree/HEAD/packages/react)
from 12.11.0 to 12.11.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/xyflow/xyflow/releases">@​xyflow/react's
releases</a>.</em></p>
<blockquote>
<h2><code>@​xyflow/react</code><a
href="https://github.com/12"><code>@​12</code></a>.11.2</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5825">#5825</a> <a
href="5072914791"><code>5072914</code></a>
- Only create an <code>XYDrag</code> instance for draggable nodes.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5847">#5847</a> <a
href="742860c293"><code>742860c</code></a>
- Stop the <code>MiniMap</code> from re-rendering on every store
update.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5846">#5846</a> <a
href="17c64a1585"><code>17c64a1</code></a>
- Apply the viewport pan/zoom transform imperatively so the
<code>Viewport</code> component only renders once.</p>
</li>
<li>
<p>Updated dependencies [<a
href="c707267bc8"><code>c707267</code></a>,
<a
href="0c0cebc08c"><code>0c0cebc</code></a>,
<a
href="56cf3b0036"><code>56cf3b0</code></a>,
<a
href="a01bb6bf51"><code>a01bb6b</code></a>,
<a
href="9b3f390e21"><code>9b3f390</code></a>,
<a
href="cdfcbeb961"><code>cdfcbeb</code></a>]:</p>
<ul>
<li><code>@​xyflow/system</code><a
href="https://github.com/0"><code>@​0</code></a>.0.79</li>
</ul>
</li>
</ul>
<h2><code>@​xyflow/react</code><a
href="https://github.com/12"><code>@​12</code></a>.11.1</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5815">#5815</a> <a
href="87da45cdf5"><code>87da45c</code></a>
- Fix <code>FinalConnectionState</code> type so it preserves the
discriminated union.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5823">#5823</a> <a
href="a6afc91160"><code>a6afc91</code></a>
- Update attribution link</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5824">#5824</a> <a
href="6b1dac50ff"><code>6b1dac5</code></a>
- Do not fire on pane click when connection ends on pane</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5818">#5818</a> <a
href="4ddc2d8420"><code>4ddc2d8</code></a>
- Provide the shared handle config (<code>connectOnClick</code>,
<code>noPanClassName</code>, <code>rfId</code>) through context instead
of subscribing to the store in every <code>Handle</code>, so a store
update no longer runs a selector once per handle</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5817">#5817</a> <a
href="8df250b095"><code>8df250b</code></a>
- Reduce per-handle work on store updates by returning a shared
connection state while no connection is in progress.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5822">#5822</a> <a
href="a6249de56c"><code>a6249de</code></a>
- Return stable reference for edge position when connected node gets
deleted</p>
</li>
<li>
<p>Updated dependencies [<a
href="ceb8604047"><code>ceb8604</code></a>,
<a
href="87da45cdf5"><code>87da45c</code></a>]:</p>
<ul>
<li><code>@​xyflow/system</code><a
href="https://github.com/0"><code>@​0</code></a>.0.78</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/xyflow/xyflow/blob/main/packages/react/CHANGELOG.md">@​xyflow/react's
changelog</a>.</em></p>
<blockquote>
<h2>12.11.2</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5825">#5825</a> <a
href="5072914791"><code>5072914</code></a>
- Only create an <code>XYDrag</code> instance for draggable nodes.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5847">#5847</a> <a
href="742860c293"><code>742860c</code></a>
- Stop the <code>MiniMap</code> from re-rendering on every store
update.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5846">#5846</a> <a
href="17c64a1585"><code>17c64a1</code></a>
- Apply the viewport pan/zoom transform imperatively so the
<code>Viewport</code> component only renders once.</p>
</li>
<li>
<p>Updated dependencies [<a
href="c707267bc8"><code>c707267</code></a>,
<a
href="0c0cebc08c"><code>0c0cebc</code></a>,
<a
href="56cf3b0036"><code>56cf3b0</code></a>,
<a
href="a01bb6bf51"><code>a01bb6b</code></a>,
<a
href="9b3f390e21"><code>9b3f390</code></a>,
<a
href="cdfcbeb961"><code>cdfcbeb</code></a>]:</p>
<ul>
<li><code>@​xyflow/system</code><a
href="https://github.com/0"><code>@​0</code></a>.0.79</li>
</ul>
</li>
</ul>
<h2>12.11.1</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5815">#5815</a> <a
href="87da45cdf5"><code>87da45c</code></a>
- Fix <code>FinalConnectionState</code> type so it preserves the
discriminated union.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5823">#5823</a> <a
href="a6afc91160"><code>a6afc91</code></a>
- Update attribution link</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5824">#5824</a> <a
href="6b1dac50ff"><code>6b1dac5</code></a>
- Do not fire on pane click when connection ends on pane</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5818">#5818</a> <a
href="4ddc2d8420"><code>4ddc2d8</code></a>
- Provide the shared handle config (<code>connectOnClick</code>,
<code>noPanClassName</code>, <code>rfId</code>) through context instead
of subscribing to the store in every <code>Handle</code>, so a store
update no longer runs a selector once per handle</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5817">#5817</a> <a
href="8df250b095"><code>8df250b</code></a>
- Reduce per-handle work on store updates by returning a shared
connection state while no connection is in progress.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/xyflow/xyflow/pull/5822">#5822</a> <a
href="a6249de56c"><code>a6249de</code></a>
- Return stable reference for edge position when connected node gets
deleted</p>
</li>
<li>
<p>Updated dependencies [<a
href="ceb8604047"><code>ceb8604</code></a>,
<a
href="87da45cdf5"><code>87da45c</code></a>]:</p>
<ul>
<li><code>@​xyflow/system</code><a
href="https://github.com/0"><code>@​0</code></a>.0.78</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3ab66eff0d"><code>3ab66ef</code></a>
chore(packages): bump</li>
<li><a
href="01d58b69ff"><code>01d58b6</code></a>
Merge pull request <a
href="https://github.com/xyflow/xyflow/tree/HEAD/packages/react/issues/5846">#5846</a>
from AlaricBaraou/perf/viewport-imperative-transform</li>
<li><a
href="742860c293"><code>742860c</code></a>
perf(react): skip minimap re-renders when its geometry is unchanged</li>
<li><a
href="17c64a1585"><code>17c64a1</code></a>
perf(react): apply the viewport transform imperatively instead of
re-renderin...</li>
<li><a
href="775bdd6b67"><code>775bdd6</code></a>
Merge pull request <a
href="https://github.com/xyflow/xyflow/tree/HEAD/packages/react/issues/5825">#5825</a>
from AlaricBaraou/perf/xydrag-draggable-only</li>
<li><a
href="576a43d5be"><code>576a43d</code></a>
refactor(react): pass nodesDraggable to prevent wrong initial state</li>
<li><a
href="eedbc81fe3"><code>eedbc81</code></a>
chore(packages): bump</li>
<li><a
href="c7bdce429e"><code>c7bdce4</code></a>
chore(pane): cleanup</li>
<li><a
href="cd5ec450b5"><code>cd5ec45</code></a>
chore(react/pane): cleanup</li>
<li><a
href="f919daa393"><code>f919daa</code></a>
fix(pane): do not fire pane click when connection ends on pane</li>
<li>Additional commits viewable in <a
href="https://github.com/xyflow/xyflow/commits/@xyflow/react@12.11.2/packages/react">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-07-27 19:12:43 -07:00
dependabot[bot]
74d6513fae chore(deps): bump actions/upload-artifact from 4 to 7 (#2444)
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 4 to 7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>v7 What's new</h2>
<h3>Direct Uploads</h3>
<p>Adds support for uploading single files directly (unzipped). Callers
can set the new <code>archive</code> parameter to <code>false</code> to
skip zipping the file during upload. Right now, we only support single
files. The action will fail if the glob passed resolves to multiple
files. The <code>name</code> parameter is also ignored with this
setting. Instead, the name of the artifact will be the name of the
uploaded file.</p>
<h3>ESM</h3>
<p>To support new versions of the <code>@actions/*</code> packages,
we've upgraded the package to ESM.</p>
<h2>What's Changed</h2>
<ul>
<li>Add proxy integration test by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/upload-artifact/pull/754">actions/upload-artifact#754</a></li>
<li>Upgrade the module to ESM and bump dependencies by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/762">actions/upload-artifact#762</a></li>
<li>Support direct file uploads by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/764">actions/upload-artifact#764</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Link"><code>@​Link</code></a>- made
their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/754">actions/upload-artifact#754</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v6...v7.0.0">https://github.com/actions/upload-artifact/compare/v6...v7.0.0</a></p>
<h2>v6.0.0</h2>
<h2>v6 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/upload-artifact@v6 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v5 had preliminary
support for Node.js 24, however this action was by default still running
on Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Upload Artifact Node 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/719">actions/upload-artifact#719</a></li>
<li>fix: update <code>@​actions/artifact</code> for Node.js 24 punycode
deprecation by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/744">actions/upload-artifact#744</a></li>
<li>prepare release v6.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/745">actions/upload-artifact#745</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0">https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0</a></p>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<p><strong>BREAKING CHANGE:</strong> this update supports Node
<code>v24.x</code>. This is not a breaking change per-se but we're
treating it as such.</p>
<ul>
<li>Update README.md by <a
href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/681">actions/upload-artifact#681</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/712">actions/upload-artifact#712</a></li>
<li>Readme: spell out the first use of GHES by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/727">actions/upload-artifact#727</a></li>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/upload-artifact/pull/725">actions/upload-artifact#725</a></li>
<li>Bump <code>@actions/artifact</code> to <code>v4.0.0</code></li>
<li>Prepare <code>v5.0.0</code> by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/734">actions/upload-artifact#734</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="043fb46d1a"><code>043fb46</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/797">#797</a>
from actions/yacaovsnc/update-dependency</li>
<li><a
href="634250c138"><code>634250c</code></a>
Include changes in typespec/ts-http-runtime 0.3.5</li>
<li><a
href="e454baaac2"><code>e454baa</code></a>
Readme: bump all the example versions to v7 (<a
href="https://redirect.github.com/actions/upload-artifact/issues/796">#796</a>)</li>
<li><a
href="74fad66b98"><code>74fad66</code></a>
Update the readme with direct upload details (<a
href="https://redirect.github.com/actions/upload-artifact/issues/795">#795</a>)</li>
<li><a
href="bbbca2ddaa"><code>bbbca2d</code></a>
Support direct file uploads (<a
href="https://redirect.github.com/actions/upload-artifact/issues/764">#764</a>)</li>
<li><a
href="589182c5a4"><code>589182c</code></a>
Upgrade the module to ESM and bump dependencies (<a
href="https://redirect.github.com/actions/upload-artifact/issues/762">#762</a>)</li>
<li><a
href="47309c993a"><code>47309c9</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/754">#754</a>
from actions/Link-/add-proxy-integration-tests</li>
<li><a
href="02a8460834"><code>02a8460</code></a>
Add proxy integration test</li>
<li><a
href="b7c566a772"><code>b7c566a</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/745">#745</a>
from actions/upload-artifact-v6-release</li>
<li><a
href="e516bc8500"><code>e516bc8</code></a>
docs: correct description of Node.js 24 support in README</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/upload-artifact/compare/v4...v7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-07-27 19:06:09 -07:00
dependabot[bot]
b848a13509 chore(deps): bump actions/checkout from 4 to 7 (#2443)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to
7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>block checking out fork pr for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the
minor-actions-dependencies group across 1 directory by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li>
<li>Bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li>
<li>Bump js-yaml from 4.1.0 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li>
<li>Bump <code>@​actions/core</code> and
<code>@​actions/tool-cache</code> and Remove uuid by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li>
<li>upgrade module to esm and update dependencies by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li>
<li>Bump the minor-npm-dependencies group across 1 directory with 3
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li>
<li>getting ready for checkout v7 release by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2464">actions/checkout#2464</a></li>
<li>update error wording by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2467">actions/checkout#2467</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.3...v7.0.0">https://github.com/actions/checkout/compare/v6.0.3...v7.0.0</a></p>
<h2>v6.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li><strong>[BREAKING]</strong> backport
<code>allow-unsafe-pr-checkout</code> to v6 by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2500">actions/checkout#2500</a></li>
<li>backport fixes to releases-v6 by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2527">actions/checkout#2527</a></li>
</ul>
<p><a
href="https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/">https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/</a>
for more details about this breaking change</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.3...v6.1.0">https://github.com/actions/checkout/compare/v6.0.3...v6.1.0</a></p>
<h2>v6.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update changelog by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>Update changelog for v6.0.3 by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yaananth"><code>@​yaananth</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p>
<h2>v6.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Add orchestration_id to git user-agent when ACTIONS_ORCHESTRATION_ID
is set by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2355">actions/checkout#2355</a></li>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.1...v6.0.2">https://github.com/actions/checkout/compare/v6.0.1...v6.0.2</a></p>
<h2>v6.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update all references from v5 and v4 to v6 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2314">actions/checkout#2314</a></li>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
<li>Clarify v6 README by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2328">actions/checkout#2328</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v7.0.1</h2>
<ul>
<li>Skip running unsafe pr check if input is default by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2518">actions/checkout#2518</a></li>
<li>Trim only ascii whitespace for branch by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2521">actions/checkout#2521</a></li>
<li>Escape values passed to --unset by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2530">actions/checkout#2530</a></li>
<li>Various dependency updates</li>
</ul>
<h2>v7.0.0</h2>
<ul>
<li>Block checking out fork PR for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Various dependency updates</li>
</ul>
<h2>v6.0.3</h2>
<ul>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<h2>v6.0.2</h2>
<ul>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<h2>v6.0.1</h2>
<ul>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
</ul>
<h2>v6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>v5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>v5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>v4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>v4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3d3c42e5aa"><code>3d3c42e</code></a>
prep v7.0.1 release (<a
href="https://redirect.github.com/actions/checkout/issues/2531">#2531</a>)</li>
<li><a
href="28802689a1"><code>2880268</code></a>
escape values passed to --unset (<a
href="https://redirect.github.com/actions/checkout/issues/2530">#2530</a>)</li>
<li><a
href="12cd2235ef"><code>12cd223</code></a>
trim only ascii whitespace for branch (<a
href="https://redirect.github.com/actions/checkout/issues/2521">#2521</a>)</li>
<li><a
href="62661c4e71"><code>62661c4</code></a>
skip running unsafe pr check if input is default (<a
href="https://redirect.github.com/actions/checkout/issues/2518">#2518</a>)</li>
<li><a
href="e8d4307400"><code>e8d4307</code></a>
Bump the minor-actions-dependencies group with 2 updates (<a
href="https://redirect.github.com/actions/checkout/issues/2499">#2499</a>)</li>
<li><a
href="631c942040"><code>631c942</code></a>
eslint 9 (<a
href="https://redirect.github.com/actions/checkout/issues/2474">#2474</a>)</li>
<li><a
href="4f1f4aec02"><code>4f1f4ae</code></a>
Bump actions/upload-artifact from 4 to 7 (<a
href="https://redirect.github.com/actions/checkout/issues/2476">#2476</a>)</li>
<li><a
href="ba097532fb"><code>ba09753</code></a>
Bump actions/checkout from 6 to 7 (<a
href="https://redirect.github.com/actions/checkout/issues/2488">#2488</a>)</li>
<li><a
href="b9e0990d21"><code>b9e0990</code></a>
Bump docker/login-action from 3.3.0 to 4.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2479">#2479</a>)</li>
<li><a
href="e8cb398be4"><code>e8cb398</code></a>
Bump docker/build-push-action from 6.5.0 to 7.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2478">#2478</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/checkout/compare/v4...v7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-07-27 18:56:31 -07:00
dependabot[bot]
17caa3d260 chore(deps): bump softprops/action-gh-release from 2 to 3 (#2442)
Bumps
[softprops/action-gh-release](https://github.com/softprops/action-gh-release)
from 2 to 3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/releases">softprops/action-gh-release's
releases</a>.</em></p>
<blockquote>
<h2>v3.0.0</h2>
<p><code>3.0.0</code> is a major release that moves the action runtime
from Node 20 to Node 24.
Use <code>v3</code> on GitHub-hosted runners and self-hosted fleets that
already support the
Node 24 Actions runtime. If you still need the last Node 20-compatible
line, stay on
<code>v2.6.2</code>.</p>
<h2>What's Changed</h2>
<h3>Other Changes 🔄</h3>
<ul>
<li>Move the action runtime and bundle target to Node 24</li>
<li>Update <code>@types/node</code> to the Node 24 line and allow future
Dependabot updates</li>
<li>Keep the floating major tag on <code>v3</code>; <code>v2</code>
remains pinned to the latest <code>2.x</code> release</li>
</ul>
<h2>v2.6.2</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<h3>Other Changes 🔄</h3>
<ul>
<li>chore(deps): bump picomatch from 4.0.3 to 4.0.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/775">softprops/action-gh-release#775</a></li>
<li>chore(deps): bump brace-expansion from 5.0.4 to 5.0.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/777">softprops/action-gh-release#777</a></li>
<li>chore(deps): bump vite from 8.0.0 to 8.0.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/781">softprops/action-gh-release#781</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/softprops/action-gh-release/compare/v2...v2.6.2">https://github.com/softprops/action-gh-release/compare/v2...v2.6.2</a></p>
<h2>v2.6.1</h2>
<p><code>2.6.1</code> is a patch release focused on restoring linked
discussion thread creation when
<code>discussion_category_name</code> is set. It fixes
<code>[#764](https://github.com/softprops/action-gh-release/issues/764)</code>,
where the draft-first publish flow
stopped carrying the discussion category through the final publish
step.</p>
<p>If you still hit an issue after upgrading, please open a report with
the bug template and include a minimal repro or sanitized workflow
snippet where possible.</p>
<h2>What's Changed</h2>
<h3>Bug fixes 🐛</h3>
<ul>
<li>fix: preserve discussion category on publish by <a
href="https://github.com/chenrui333"><code>@​chenrui333</code></a> in <a
href="https://redirect.github.com/softprops/action-gh-release/pull/765">softprops/action-gh-release#765</a></li>
</ul>
<h2>v2.6.0</h2>
<p><code>2.6.0</code> is a minor release centered on
<code>previous_tag</code> support for
<code>generate_release_notes</code>,
which lets workflows pin GitHub's comparison base explicitly instead of
relying on the default range.
It also includes the recent concurrent asset upload recovery fix, a
<code>working_directory</code> docs sync,
a checked-bundle freshness guard for maintainers, and clearer
immutable-prerelease guidance where
GitHub platform behavior imposes constraints on how prerelease asset
uploads can be published.</p>
<p>If you still hit an issue after upgrading, please open a report with
the bug template and include a minimal repro or sanitized workflow
snippet where possible.</p>
<h2>What's Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md">softprops/action-gh-release's
changelog</a>.</em></p>
<blockquote>
<h2>0.1.13</h2>
<ul>
<li>fix issue with multiple runs concatenating release bodies <a
href="https://redirect.github.com/softprops/action-gh-release/pull/145">#145</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3d0d9888cb"><code>3d0d988</code></a>
release 3.0.2 (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/818">#818</a>)</li>
<li><a
href="7e13ed4ac5"><code>7e13ed4</code></a>
fix: clarify release creation 404 errors (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/817">#817</a>)</li>
<li><a
href="e6c70a53cf"><code>e6c70a5</code></a>
fix: replace existing release assets on Gitea (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/816">#816</a>)</li>
<li><a
href="f345337888"><code>f345337</code></a>
fix: publish existing draft releases as prereleases (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/801">#801</a>)</li>
<li><a
href="d8a89a2066"><code>d8a89a2</code></a>
fix: upload small checksum assets reliably (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/815">#815</a>)</li>
<li><a
href="45ece40c31"><code>45ece40</code></a>
chore(deps): remove unused TypeScript tooling (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/814">#814</a>)</li>
<li><a
href="f6b913c3f9"><code>f6b913c</code></a>
feat: improve release error reporting and test coverage (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/813">#813</a>)</li>
<li><a
href="15f193d7d8"><code>15f193d</code></a>
chore(deps): upgrade TypeScript to 7 (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/812">#812</a>)</li>
<li><a
href="cc8268d46a"><code>cc8268d</code></a>
chore(deps): bump actions/checkout in the github-actions group (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/810">#810</a>)</li>
<li><a
href="fd0ed1e85b"><code>fd0ed1e</code></a>
chore(deps): bump the npm group with 3 updates (<a
href="https://redirect.github.com/softprops/action-gh-release/issues/811">#811</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/softprops/action-gh-release/compare/v2...v3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=softprops/action-gh-release&package-manager=github_actions&previous-version=2&new-version=3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-07-27 18:49:25 -07:00
Phil Larson
3822e1081c test(engine): remove orphan dependency reporter coverage (#2483)
## Summary
- remove the remaining dependency-blocked reporter test after the
reporter was deleted in #2477
- prevent Vitest from failing during module collection on a deleted
import

## Test plan
- `pnpm --filter @fusion/engine typecheck`
- `pnpm --filter @fusion/engine build`
2026-07-27 18:36:51 -07:00
gsxdsm
17fdf2f0ae U4: scope the user-pause safeguard to lifecycle MUTATION, not observation (re-ratified) (#2486)
Stacked on #2482. Base is
`feature/workflow-vocabulary-u4-override-layer` — do not merge before
it.

Implements the coordinator's **re-ratification** of the user-pause
safeguard with a narrower definition.

## The invariant, written into the code

> The user-pause safeguard means **NEVER MUTATE LIFECYCLE STATE** of a
user-paused card. It does **NOT** mean never observe one.

Respecting a pause exists to stop the engine acting on a card *behind*
the operator who paused it — moving, rebounding, archiving, resuming. A
read-only diagnostic does the opposite: it tells that same operator what
their paused card is doing. Blinding them to their own paused work is
not safety; it is the engine deciding they should not be told.

The sentence is in the source, because the distinction is the whole
point and a future reader will otherwise re-broaden it.

## Why it needed narrowing

Ratified broadly first, that reading was caught suppressing the very
sweeps it was meant to protect. `surfaceStalePausedTodos` exists to
report cards that have sat paused too long — routing it through a
reconciler that suppresses paused cards turns a diagnostic into one that
**silently reports nothing**. Measured, not argued: #2484 proves that
sweep surfaces user-paused cards on current main.

## Scoped by action, never by sweep

`OBSERVATIONAL_ACTIONS` is an **allow-list**, so a newly added mutating
action is suppressed by default — the scoping fails closed. A sweep
cannot opt itself out.

`RecoveryActionKind` deliberately names actions the policy vocabulary
cannot yet author (`rebound`, `archive`, `requeue`, `resume`). The
scoping is only testable if those exist as values, and **a rule that
cannot be tested is a rule that erodes**. `parseWorkflowIr` keeps a
closed action list, so nothing becomes authorable by being named.

## Which field — chosen, not inherited

The gate reads `userPaused`, **not** `paused`. The two diverge
(`branch-group-ops.ts:128` says so outright), and `paused` also covers
engine-authored automation pauses like dispatch-storm, which carry no
operator intent to respect — gating on it would suppress recovery from
the engine's own throttles. The safeguard defers to a **human**
decision, so it keys on the field that records one.

## Both halves kept

The broad case is **narrowed, not deleted**: one test proves mutation is
still suppressed, one proves observation is now permitted. A future
reader must be able to tell the scoping was *deliberate* rather than
eroded by someone who found the broad rule inconvenient.

**Mutation-verified in both directions**, since either error is silent:

| mutation | result |
|---|---|
| re-broaden (suppress observation) | **2 tests fail** |
| over-narrow (`rebound` treated observational) | **3 tests fail** |

## Verification

46 tests green (28 safety + 18 inheritance); tsc clean; lint clean;
merge gate green (299+10+71).

No changeset: `@fusion/engine` is private.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 17:37:20 -07:00
gsxdsm
89284df85e E2E: table-driven converted-sweep coverage, two new sites, and an honest unproven-sites ledger (#2485)
Follow-up to #2475 (merged). Test-only, plus one test-utility seam.

## Why a table

#2475 proved one converted sweep. The count has since gone to **three**,
twice while this work was open — `surfaceStalePausedTodos` appeared
during #2475's review, and #2478 landed `recovery-reconciler.ts` while
this branch was open. A suite with a bespoke `describe` per sweep is a
coverage claim that quietly becomes false.

Replaced with a table of `(seed, run, acted, roles, observability)`. The
driver derives four assertions per entry:

| | positive | negative |
|---|---|---|
| **renamed vocabulary** | acts on the card | inert in a non-target
column |
| **default vocabulary** | acts (regression floor) | inert |

Adding a converted sweep is **one entry** — the #2478 site proved that
in practice, not in principle. `actsOnRole`/`inertRole` are keys of
`Vocabulary`, not column strings, so an entry cannot hardcode `todo` and
pass for the wrong reason.

## Two findings, both from mutation rather than reading

**1. The census was wrong about `recovery-reconciler.ts:198.`** It was
flagged as a `resolveLifecycleColumns` site, so the row was first
labelled as covering it. **Destroying that role resolution leaves all 18
tests green** — `decideRecovery` looks policy up by *column id* and
never consults a role. The row is relabelled to what it actually proves,
and mutation-verified against that instead: keying the reconciler's
policy lookup on the `todo` literal fails exactly its renamed test.

**2. `resolveRoleRecovery` is an unreachable export.** It is the only
use of `resolveLifecycleColumns` in that file and has **no production
caller anywhere** in engine, core, or dashboard. So that census line is
not a live converted site — it is a helper written ahead of its
consumer. **Not fixed here:** it is production code owned by the U4
slice, and whether the consumer is still to land or it should be deleted
is its author's call.

## Observability is now explicit in the type

`persisted-row` is the strong form. `returned-decision` is recorded as
**weaker evidence** and the reconciler row uses it, because
`reconcileRecovery` decides and does not apply — there is no row to
read. Naming it in the type is what stops a return-value assertion from
quietly passing as observed state, and it is what keeps the ledger
truthful per site.

## Harness seam

`PgTestHarness` now exposes its raw admin SQL client. The store
**stamps** `updatedAt`/`columnMovedAt` on every write, so `updateTask`
cannot express an aged row at all — the patch is accepted and the value
silently replaced with `now`. **Found by the new case failing on BOTH
vocabularies**, which is what distinguishes a broken fixture from a
broken guard. Seeding only; assertions still read back through the real
`getTask` path.

## Mutation verification

| Mutation | Result |
|---|---|
| revert **only** `recoverStrandedCompletedTodoTasks`'s resolution |
**exactly** that row's renamed test fails |
| revert **only** `surfaceStalePausedTodos`'s resolution | **exactly**
its own renamed test fails |
| reconciler policy lookup keyed on `todo` | exactly the reconciler
row's renamed test fails |
| `resolveRoleRecovery` role resolution destroyed | **nothing fails** →
finding #2 |
| `hold-release` `isHeldTask` keyed on `todo` | 5 of 18 fail; default
spine survives |
| `markMoveInFlight` dropped | both spine tests fail |

Per-site verification matters here: three rows could all be riding one
guard. They are not.

## The honest number

**Proven end to end: 5** (two self-healing sweeps, the reconciler's
policy lookup at the weaker observability, hold-release's capacity
release, and the graph boundary + `moveTask` + post-commit bus).

**Not proven: 11 call sites** — `merger.ts:324-326`,
`merger-ai.ts:1022,1039`, `auto-merge-finalization.ts:20-22`,
`executor.ts:1763,6339,6341`, `self-healing.ts:713,6732`,
`mesh-lease-manager.ts:61`, `task-agent-sync.ts:59`,
`core/task-store/reads.ts:130`, `core/live-agent-count.ts:63-75`, and
four dashboard route sites.

The ledger lives in the file, not just here, so it stays with the code.

## Where the table does not fit — reported, not papered over

The **merge/rebound family** cannot be a table row: those sweeps have no
observable persisted effect without a real git repository, so `acted`
cannot be written against the row at all. They need an engine-slow
real-git lane. The dashboard sites need an HTTP route test with a live
store. Both are different lanes, not missing entries.

## Verification

- 18/18 green; engine + core `tsc --noEmit` clean; `pnpm test:gate`
green (299 + 10 + 71)
- full core PG suite run (the harness is shared): 1036 passed, 3 failed
in `central-archive-secrets` and `workflow-settings-project-identity` —
**reproduce identically with this change stashed**, pre-existing

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 15:25:17 -07:00
gsxdsm
2dfac47917 U4: recovery policy as an OVERRIDE LAYER (unset defers to the operator setting) (#2482)
Stacked on #2478. Base is
`feature/workflow-vocabulary-u4-reconciler-slice` — do not merge before
it.

Implements the ratified precedence: **declared explicitly → policy wins;
left unset → defer to the project/global setting, exactly as today.**

## The design

`resolveEffectiveRecovery(declared, inherited)` composes the two **per
field**, so a workflow may declare a threshold while inheriting the
action. It mirrors the two-tier merge `effective-settings.ts` already
implements for workflow settings (a stored value overrides the base; a
declaration default only fills an absent key) rather than inventing a
fourth precedence system beside model selection, project settings, and
workflow settings.

**Absence stays absent.** `??` treats an explicitly-`undefined` field as
unset, so a policy is never normalized into a built-in default. The
distinction a naive implementation gets wrong:

> **equal-to-default is not the same as unset**

A declaration whose value happens to equal the legacy literal is a
*deliberate choice* and must still override a customized operator
setting. Only true absence defers.

An effective policy requires **both** halves — a threshold with no
action never fires, an action with no threshold has nothing to fire on —
so a half-resolved policy yields `undefined` rather than something
present but inert.

## The test that matters was written first, and failed

> a project with a CUSTOMIZED threshold and the policy key UNSET must
observe the customized value

This is where a green suite lies. "Read the policy, else use the
built-in default" passes every obvious test while silently resetting an
operator who tuned `stalePausedTodoThresholdMs` — no error, nothing in
any diff, the sweep just starts firing on a schedule nobody chose.

**Mutation-verified in both directions:**

| mutation | result |
|---|---|
| substitute a built-in default for the inherited setting | **5 tests
fail** |
| invert precedence (inherited beats declared) | **3 tests fail** |

## Upgrade guarantee

Asserted as a property over several operator values: an undeclared
workflow observes *exactly* the operator's value. That is what makes
landing the policy table a zero-behavior-change upgrade that touches no
project.

## What is NOT here

**`surfaceStalePausedTodos` is not retired.** Migrating it surfaced a
safeguard-semantics collision I escalated rather than resolved
unilaterally: the sweep exists to surface cards that have been
**paused** too long, but the reconciler's ratified user-pause safeguard
suppresses `surface` on user-paused cards — so migrating it as-is would
suppress a large part of what the sweep is for. `paused` and
`userPaused` are distinct fields that can diverge (see
`branch-group-ops.ts:128`). The sweep is untouched pending that
decision.

## Verification

- 34 tests green (10 new inheritance + 24 safety)
- `tsc --noEmit` clean, `pnpm lint` clean

No changeset: `@fusion/engine` is private.

🤖 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**
- Recovery decisions now correctly inherit operator settings when a
workflow does not specify a recovery policy.
- Workflow-specific recovery settings override inherited values,
including when matching built-in defaults.
- Recovery settings can now be applied independently by field, allowing
thresholds and stale-item actions to inherit separately.
- Recovery is suppressed safely when no complete policy is available,
preventing unintended recovery actions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 15:24:19 -07:00
gsxdsm
3578903b16 U4: characterize surfaceStalePausedTodos before the policy migration (regression floor + safeguard evidence) (#2484)
Based on `main`, independent of #2482 — mergeable on its own.

Pins what `surfaceStalePausedTodos` does **today**, so the first real
policy migration is judged against *observed* behavior rather than
against what the sweep looks like it should do. **No production code
changed.** Passes against current main; must still pass after the
migration.

## 1. Threshold source — the case you asked to see proven first

The sweep reads `settings.stalePausedTodoThresholdMs` directly, so under
the override layer an undeclared workflow must keep observing exactly
that value. A migration that reaches for a declaration default instead
finds the card fresh and **silently stops surfacing it** — resetting a
deliberate operator choice with no error and nothing in any diff.

Both directions are asserted, deliberately:
- a customized **1h** threshold surfaces a 2h-old card;
- the **same card is NOT surfaced** under the 24h built-in.

Without that discriminator, "always surface" would pass the first test
and prove nothing about which threshold was used.

## 2. User-paused cards — evidence for the open safeguard question

This replaces my argument with a measurement.

`getStalePausedTodoSignal` gates on `paused === true` and **never
consults `userPaused`**, so a user-paused card that has sat too long
**is surfaced today**. That test passes on current main.

This is what blocks the migration: the reconciler's ratified user-pause
safeguard suppresses the `surface` action for `userPaused` cards, so
routing this sweep through it unchanged would **stop surfacing them** —
a silent behavior change dropping a diagnostic operators rely on.

`paused` and `userPaused` are distinct fields that can diverge (see the
comment at `branch-group-ops.ts:128` about `userPaused` remaining true
while legacy `paused` is false), so these are genuinely separable states
rather than one condition spelled two ways.

**The decision needed:** does the user-pause safeguard mean *"never act
on a user-paused card"* or *"never mutate lifecycle state of one"*? It
generalizes — `surfaceStalePausedReviews` and `surfaceInReviewStalls`
are the same shape.

## 3. Things easiest to lose in a rewrite

Also pinned: inert under `globalPause` and `enginePaused`; a
non-positive threshold disables it entirely; an unpaused card is never
surfaced; automation-paused and user-paused cards are not distinguished
today.

## Verification

8 tests green against unmodified main; lint clean.

No changeset: `@fusion/engine` is private.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 15:20:22 -07:00
gsxdsm
b133d521c4 U4 vertical slice: recovery-policy reconciler + ratified safety invariant (measured: engine ~780, real cost is a settings migration) (#2478)
Stacked on #2477. Base is
`feature/workflow-vocabulary-u4-delete-dep-blocked` — do not merge
before it.

The smallest end-to-end slice of the U4 reshape, built to **measure**
the real cost before committing to the full policy table. The survey's
~900-line reconciler figure was reasoned, not prototyped; this replaces
it with numbers.

**Everything here is additive and unwired. No behavior changes.**

## What lands

| | lines | what |
|---|---:|---|
| `WorkflowColumnRecovery` (IR) | 42 | one key — `stalenessMs` +
`onStale`. Optional and omitted when unset, so existing workflows
serialize byte-identically. |
| `recovery-reconciler.ts` | 176 | one engine: walks live cards,
resolves each card's policy from **its own** workflow (per task, shared
`irCache` — a 400-card board across three workflows reads three IRs),
returns decisions. Decision and application are separate so the safety
boundary is assertable without running an engine. |
| `recovery-policy-safety.test.ts` | 156 | one-time. The **ratified
invariant**. |

## Measured cost vs the ~900 estimate

**Engine + IR types = 222 lines** for one action (`surface`) and one
safeguard.

Extrapolating the rest — `rebound` (target resolution, attempt budgets,
backward-move proof, five more safeguards) ≈ +350, `archive` ≈ +50, the
`budgets`/`dependencies` keys ≈ +150 — lands near **780**.

So **~900 was a good estimate for the engine**, and the vertical slice
does not move it much. That is the answer to the question asked.

## But the estimate's real miss is not lines

**16 of the 34 POLICY sweeps read an operator setting today** — ~17
distinct policy-threshold keys, including `stalePausedTodoThresholdMs`,
`inReviewStalledThresholdMs`, `taskStuckTimeoutMs`,
`doneAutoArchiveDays`, `maxPostReviewFixes`.

Moving those sweeps into workflow policy is **not a code refactor — it
is a settings migration with operator-visible blast radius**, and it
needs three decisions the line estimate never surfaced:

1. Does workflow policy **override** the global setting, or defer to it?
2. What happens to **existing projects** that already configured those
settings?
3. Does an **unset** policy inherit the setting, or the built-in
default?

That is the gating question for the full table — not the reconciler's
size.

## Why the sweep is not retired here

Retiring `surfaceStalePausedTodos` requires builtin:coding to declare
the policy **and** `stalePausedTodoThresholdMs` to migrate — or the
behavior silently disappears for every existing project. That is the
settings migration above, and it belongs behind its own decision rather
than smuggled into a measurement slice.

The reconciler is therefore **unwired — deliberately dead code**, for
exactly as long as it takes to get that decision.

## The ratified safety invariant

The six safeguards (user pause, `autoMerge:false`, dependency, capacity,
merge-proof, at-most-once) live **outside** the policy table. A workflow
must never be able to author a safety invariant away.

Encoded two ways, because either alone is defeatable:
- **structural** — the policy exposes only an allow-listed key set;
adding a key requires editing the test and re-stating the safety
argument (the friction is the point);
- **behavioral** — a policy attempting every spelling of "ignore the
user pause" has no effect.

**Both halves mutation-verified**, because a safety test that cannot
fail is worse than none:
- making the reconciler honor a policy field that disables the
user-pause safeguard → **fails**
- adding an unreviewed key to the policy schema → **fails**

A third test asserts the reconciler still **acts** on an unpaused card,
so a reconciler that suppressed everything cannot pass by doing nothing.

## Scope limits stated rather than implied

Only the `surface` action is implemented, so only its relevant safeguard
is wired. `surface` mutates no lifecycle state; the other five gate
lifecycle-**mutating** actions that do not exist yet, and wiring them
now would be untestable dead code. A test records this so the absence
reads as deliberate and must be updated when `rebound` lands.

## Verification

- `tsc --noEmit` clean in core and engine; `pnpm lint` clean
- merge gate green (299 + 10 + 71)
- 23 safety tests green; `workflow-lifecycle-traits` green

No changeset: `@fusion/core` and `@fusion/engine` are private.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 15:06:19 -07:00
gsxdsm
2dce642ccc E2E validation: run a RENAMED-column workflow against a live engine (real graph + real PostgreSQL) (#2475)
Stacked on #2472 (`feature/workflow-vocabulary-b3-stranded-todo`).

Test-only. No production file is touched.

## Why

Every slice of this program has closed with the same caveat: *no renamed
workflow was run against a live engine; all evidence is unit-level*.
That caveat is load-bearing — eight times this session a test passed
without exercising its subject. This PR removes it for the lifecycle
spine.

## What actually runs

`packages/engine/src/__tests__/workflow-lifecycle-live-e2e.pg.test.ts`
drives the REAL pieces:

- a **real PostgreSQL `TaskStore`** on a throwaway per-file database
(shared PG harness; never the operator's DB, never port 4040),
- the **real graph interpreter** (`WorkflowGraphTaskRunner`) with the
**real column-boundary controller** wired to the **real
`store.moveTask`** — all of its guards, traits, capacity reservation,
and post-commit emission,
- the **real scheduler release** (`runHoldReleaseSweep`),
- the **real post-commit lifecycle bus** (`getWorkflowEventBus`),
- the **real converted self-healing sweep**
(`SelfHealingManager.recoverStrandedCompletedTodoTasks`, slice B3.1).

Only the AI **seams** are scripted — the same boundary `testMode`/`mock`
draws in production.

**Assertion rule:** every lifecycle claim is asserted on **persisted
state** (a fresh `getTask` with the store's task cache defeated,
`run_audit_events` rows, `workflow_work_items` rows), never on "a
function was called". The one spy — the event-bus subscriber — is
asserted on the **received payload**, because the bus silently drops
events that fail its shape check, so "emit was called" proves nothing.

**Differential design:** the default-vocabulary
(`todo`/`in-progress`/`in-review`/`done`) and renamed-vocabulary
(`backlog`/`building`/`checking`/`shipped`) workflows come from ONE
builder and differ ONLY in their four column ids. Any behavioral delta
is attributable to the vocabulary alone.

## Coverage (9 tests, all green)

| Scenario | What is proven |
|---|---|
| Default vocabulary, full spine | planning runs in the hold column, the
card parks (graph does not self-promote), the **scheduler** performs
hold→wip, the resumed run walks exec → review → merge-gate → end,
persisted column is `done` |
| **Renamed vocabulary, full spine** | identical, and no leg of the run
touches any legacy column id |
| Audit differential | the graph-owned boundary crossings are the same
crossings node-for-node on both vocabularies; no legacy id appears in
the renamed trail |
| Event seam | a real subscriber **receives** a well-formed
`TaskTransitioned` for the renamed `backlog`→`building` release and for
the terminal move; `NodeEntered` arrives for every traversed node
including `end` |
| Crash / restart | exactly one durable continuation row at `exec`; a
brand-new runner resumes from the row and the already-completed
`planning` seam does **not** re-run; no duplicate continuation |
| Converted sweep (B3.1) | a completed card in a **renamed** hold column
is promoted (asserted on its persisted column), a card in the renamed
**wip** column is not, and the default `todo` case still works |

## Mutation verification (both directions)

Green suites are not evidence in this codebase, so both halves were
falsified:

1. Keying `hold-release`'s `isHeldTask` on the `todo` literal → **5 of 6
spine tests fail, and the one that survives is the default-vocabulary
one.** That is the exact signature the conversion program cares about.
2. Reverting slice B3.1's per-task hold-column resolution to the literal
→ **only the renamed stranded-todo test fails**; the default regression
floor stays green.

## Findings surfaced by running it

1. **The IR validator refuses a `merge-blocker` column with no reachable
merge-class node** ("the gate can never clear without one"). Kept rather
than worked around — it means the review column here is genuinely gated.
2. **Entry into the merge region collapses to the legacy `merge` seam**
(`MERGE_REGION_KINDS`), so a `merge-gate` node reaches the merge lane.
Documented in the fixture.
3. **The transition policy refuses a direct hold → review move**, and it
refuses it *workflow-resolved*: on the renamed board the only legal
target is its own `building`, not `in-progress`. The recovery callback
therefore promotes hold → wip → review rather than bypassing the policy.
4. **`moves.ts` still special-cases the `done` literal** (`if (toColumn
=== "done") clearNearDuplicateReferencesTo...`) after the post-commit
emit. Not converted here and not in this PR's scope — flagged for the
Phase B owner.

## Not driven end to end (stated plainly)

- **Triage / specification.** The lifecycle starts from a task already
bound to a workflow; `triage.ts` was not driven. The `planning` seam is
scripted.
- **Real merge.** No git worktree, no branch, no squash. `merge-gate` is
pure policy; the `merge` seam is scripted.
- **Lightweight / self-healing-off workflow.** The Tier 1 policy keys do
not exist on this tip — there is no `policies` surface on the IR to set.
Not drivable; not substituted with a unit test.
- **Process-level crash.** The restart is an in-process one: a brand-new
runner resuming from the persisted `workflow_work_items` row with no
carried-over memory. No OS process was killed, so this proves
durable-state resumption, not signal handling.

## Lane

`.pg.test.ts` under the engine-default include glob, gated by
`pgDescribe` so it skips cleanly with no PostgreSQL. The merge gate is
untouched. Engine `tsc --noEmit` is clean.

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

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

## Summary by CodeRabbit

* **New Features**
* Added comprehensive live PostgreSQL workflow lifecycle coverage,
including graph execution, suspension and resume, scheduler capacity
release, crash recovery, and durable continuation.
* Added validation for renamed workflow column configurations and
columnless task movements.
* Added event delivery checks for task transitions and node entry
events.
* Added self-healing recovery for stranded completed tasks in valid hold
columns.

* **Refactor**
* Centralized workflow boundary handling, including task moves,
continuation state, audit events, and diagnostics.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 15:05:05 -07:00
gsxdsm
710d56b2db U4 trim: delete the dependency-blocked-todo feature (unreachable in production) and revert 5a2de7d (#2477)
Stacked on #2474. Base is `feature/workflow-vocabulary-u4-dead-code` —
do not merge before it.

Deletes an **entire feature that has never executed in production**, and
reverts `5a2de7d`, which only threaded resolved lifecycle columns
through it.

## Reachability evidence — the whole basis for this

```
surfaceDependencyBlockedTodos          ← in NEITHER sweep registry; no caller in
  └─ getDependencyBlockedTodoReporter()      engine/dashboard/cli — only tests
      └─ engine/dependency-blocked-todo-reporter.ts   ← sole caller of ↓
          └─ core/computeDependencyBlockedTodoReport
```

self-healing owns two name-based sweep registries (`runStartupRecovery`,
58 entries; `runMaintenance`, 76). `surfaceDependencyBlockedTodos` is in
**neither**, so nothing ever invoked the chain below it. Its four tests
passed while proving nothing about production.

## Why delete rather than wire it up

Wiring was the tempting option and is the riskier one. Switching on a
450-line path that has never run — whose tests therefore establish
nothing about its behavior against real data — is a **behavior change
with unquantified blast radius**. This program already refused exactly
that move for the **pool-id sentinel**, a one-line change that would
switch on dormant enforcement across every project. This is the same
class of move at ~450× the size.

Deleting is also the recoverable direction: git keeps the feature, and
it can be resurrected deliberately — with tests that prove it *runs* —
if dependency-blocked reporting is actually wanted.

## The settings keys go with it

`dependencyBlockedTodoReportEnabled` defaulted `true` while driving
nothing. A schema/API-visible switch that lies about what the system
does is worse than no switch. (It had no dashboard UI field — the
dashboard test allowlist already recorded it as *"no UI field"*.) Four
sibling tuning keys are removed with it.

## Against my own earlier work

`5a2de7d` threaded resolved lifecycle roles into
`computeDependencyBlockedTodoReport` and its reporter, answering a
review finding I confirmed as real. **The code was correct; the impact
claim was not**, because the path never executes. Neither the reviewer
nor I checked *reachability* before agreeing the defect mattered — only
correctness. A correction is posted on that thread in #2470.

**Scope limit on that admission:** the same finding also described
*incorrect scheduler ordering*. That half runs through
`buildUnblockWeightMap` in `task-priority.ts`, which is **live** and was
already threading `terminalColumns` (B1, `434b385`). Scheduler ordering
was never affected, before or after.

## What survives

`blocker-fanout.ts` **stays** — it is live via `task-priority.ts`. Only
the plural `holdColumns` option added by `5a2de7d` is reverted, since
the deleted report was its sole consumer. `holdColumn` (singular, from
B1) remains.

## Net

**1,244 deletions / 5 insertions across 15 files** — ~450 production
lines, ~684 test lines, 5 settings keys.

## Verification

- `tsc --noEmit` clean in **core, engine, and dashboard-app**; `pnpm
lint` clean
- merge gate green (299 + 10 + 71)
- self-healing suite: 411 passed, 1 **pre-existing** failure
(`archiveStaleDoneTasks`)
- dashboard settings-descriptions suite green
- `settings-parity.test.ts` has one **pre-existing** failure
(`agentToolOutputMaxChars` overlap) that fails identically with these
changes stashed — unrelated to this deletion

No changeset: `@fusion/core` and `@fusion/engine` are private.

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


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

## Summary by CodeRabbit

* **New Features**
* Added quiet-window backlog health diagnostics for stalled items in
review, with repeat-alert suppression.
  * Added default thresholds for backlog-pressure alerts.

* **Changes**
  * Removed dependency-blocked todo reporting and related alerts.
* Removed the dependency-blocked todo enable/disable setting; remaining
tuning options are no longer active.
* Updated the workflow hold classification to use a single todo column.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 14:54:32 -07:00
gsxdsm
923a7c0fc9 U4 trim: delete the dead resetStepsIfWorkLost duplicate in self-healing (#2474)
Stacked on #2472 (Phase B slice B3.1). Base is
`feature/workflow-vocabulary-b3-stranded-todo` — do not merge before it.

First output of the **U4 reshape survey**: dead code removed with
reachability evidence, not a redundancy argument.

## What is deleted

**`SelfHealingManager.resetStepsIfWorkLost`** — 50 lines including its
docblock and a section header left with no other member.

Evidence:
- `private`, with **zero in-file references** beyond its own
declaration.
- TypeScript already reported it as `declared but its value is never
read` — the compiler has been flagging this.
- The **live** implementation is `executor.ts:20080`, an independent
copy that `executor.ts` actually calls (12667, 14664). The self-healing
copy is an orphaned duplicate of it.
- Not in either sweep registry, no test of its own, no caller anywhere
in engine / dashboard / cli.

Safe against every U4 constraint: it enforces none of user pause,
`autoMerge:false`, dependency, capacity, merge-proof, or an at-most-once
safeguard.

## Why the second approved deletion is NOT here

`surfaceDependencyBlockedTodos` was approved alongside this one as a
28-line orphan. It is not. On inspection it is the **tip of an entire
unreachable feature**:

```
surfaceDependencyBlockedTodos   (in NEITHER registry, no production caller)
  └─ getDependencyBlockedTodoReporter()        ← sole caller
      └─ engine/dependency-blocked-todo-reporter.ts   223 lines  ← sole caller of ↓
          └─ core/dependency-blocked-todo-report.ts   184 lines
```

≈ **450 production lines across three files, plus 684 lines of tests in
four files.** The operator-visible setting
`dependencyBlockedTodoReportEnabled` defaults `true` in
`settings-schema.ts` and drives nothing.

Deleting only the approved 28-line tip would be **strictly worse than
leaving it** — it orphans the getter and field and strands 407 lines of
module with no remaining reference to explain why. Escalated for a
decision (delete the subtree / wire the feature up / leave it) rather
than resolved unilaterally.

## Verification

- `tsc --noEmit` clean, `pnpm lint` clean
- self-healing suite: **415 passed**, 1 failure **pre-existing**
(`archiveStaleDoneTasks` — fails identically before this change)

No changeset: `@fusion/engine` is private.

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


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

## Summary by CodeRabbit

* **Behavior Changes**
* Removed automatic detection and reset of task steps when no unique
work is found on a task branch.
* Tasks with completed or in-progress steps will no longer be
automatically returned to pending based on this condition.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 14:44:28 -07:00
gsxdsm
a4dee1162e Phase B slice B3.1 (U4): resolve the hold column in recoverStrandedCompletedTodoTasks — query and guard together (#2472)
Stacked on #2471 (Phase B slice B2). Base is
`feature/workflow-vocabulary-b2` — do not merge before it.

**First landable slice of U4 (self-healing.ts).** One sweep, one PR, per
the phase's sub-split rule.

## The finding: the guard and the query must convert together

`recoverStrandedCompletedTodoTasks` promotes a card whose steps are all
done/skipped but which is still sitting in the hold column — finished
work that never handed off to review. It decided *"is this card in the
hold column?"* **twice**, and both were literal:

| | was |
|---|---|
| the QUERY | `listTasks({ column: "todo", slim: true })` |
| the GUARD | `task.column !== "todo"` |

**Either half alone is a green diff with zero behavior change.** A
correct guard behind a literal query never runs; a converted query
behind a literal guard rejects every row it just fetched. This is the
shape that made B1's stale-paused-todo fix cosmetic, and the phase brief
predicted more of it here — correctly.

I proved it rather than asserting it:

- literal **QUERY** restored (converted guard kept) → **3 tests fail**
- literal **GUARD** restored (converted query kept) → **2 tests fail**

Neither half passes the suite alone.

## Falsification came first

Per the brief I tried to prove the work unnecessary before doing it. It
is necessary, and the evidence is empirical, not assumed: the 7 tests
were written against unmodified code and 3 failed. Unlike B2's
hold-release — which turned out already converted — **self-healing is
uniformly unconverted at the query level**: 53 of its sweeps carry a
hardcoded `column:` filter (survey in the worker report).

## Negative half, per the brief

A completed card resting in a WIP or review column is **not** promoted.
Dropping a column filter without a per-task hold check would promote
finished cards out of every column — laundering work past review, a
louder bug than the silent one being fixed.

## Test-harness hazard (will recur in every remaining U4 slice)

The pre-existing self-healing store mock returns its fixture from
`listTasks` **regardless of arguments**. A renamed-hold test on that
harness passes while the query stays hardcoded, because the mock hands
the sweep rows the real store never would. The new harness **honors**
the column filter, and one test asserts the query is no longer scoped to
the literal. This is documented in the new file's header for whoever
writes the next slice.

## Cost

The column filter is gone, so the cheap non-column rejections (paused /
executing / incomplete steps / errored / no-commits / skip-bypass taint)
run **first and synchronously**; only survivors pay an IR resolution,
shared through an `irCache`. A board spanning three workflows resolves
three IRs regardless of card count. `includeArchived: false` preserves
what the column filter did implicitly. The hold column resolves **per
task** — a board spans workflows, and a card in *another* workflow's
hold column must not be promoted.

## One pre-existing assertion changed, deliberately

`self-healing.test.ts` pinned `listTasks` being called with `{ column:
"todo", slim: true }`. That query shape changed on purpose; the
assertion now pins the new one. The behavioral assertions either side of
it (one qualifying card, promoted exactly once) are untouched and still
pass.

## Carried A3 questions — both answered

**Q1 — does the sync/SQLite counter have the same pool-id mismatch?**
**Not applicable: there is no sync counter.**
`occupantsByColumnForWorkflowImpl` and `listWorkflowOccupantTaskIds` are
async/PG-only and throw without an initialized `AsyncDataLayer`; the
sync twin went with the PG cutover. There is no second counter that
could mismatch. The surviving pool-id sentinel sites are
`project-store-ops.ts:767/819` and `moves.ts` — both parked by operator
decision, untouched here.

**Q2 — are custom workflows with an explicit numeric limit affected?**
**No, by design.** `resolveColumnCapacity` gives `config.limit` top
precedence (`configLimit` → `limitSetting` → default-workflow
read-through → `Infinity`), and `resolveWipBudgetColumns` documents that
a column with an explicit numeric limit is **independent — its budget is
itself alone**. Such a column never pools, so there is no pool id to
mismatch. Read-only analysis; no code changed for either question.

## Remaining U4 scope (not in this PR)

214 literal occurrences across ~70 methods; **53 sweeps carry a
query-level column filter**. Hold-gated sweeps still to convert:
`clearStaleBlockedBy`, `reclaimSelfOwnedBranchConflicts`,
`reconcileCompletedTask`, `recoverMergedReviewTasks`,
`recoverStuckMergeDeadlocks`, plus non-query `todo` guards in
`recoverPausedAbortFailures`, `reconcileDependencyBlockingLeases`, and
others. `surfaceStalePausedTodos` was already converted (B1 follow-up)
and is verified intact on this branch.

## Verification

- 7 new tests green; **both mutations kill the suite**
- self-healing suite: 415 passed, **1 failure pre-existing**
(`archiveStaleDoneTasks` — confirmed identical by stashing my changes)
- merge gate green (299 + 10 + 71)
- `tsc --noEmit` clean, `pnpm lint` clean

No changeset: `@fusion/engine` is private.

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


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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved recovery of completed tasks stranded in workflow-specific
hold columns, including renamed hold columns.
* Preserved recovery for built-in workflows while correctly handling
boards with mixed workflow configurations.
* Prevented recovery for tasks in non-hold columns or with paused,
incomplete, or errored states.
  * Added fallback handling when workflow details cannot be resolved.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 14:36:32 -07:00
gsxdsm
02b0f4f860 Phase B slice B2: U5 small movers — 12 literal sites converted, plus a negative result on hold-release (#2471)
Stacked on #2470 (Phase B slice B1). Base is
`feature/workflow-vocabulary-conversion` — do not merge before it.

## What this is

Phase B slice B2 — the U5 small movers. **12 literal sites converted,
plus one negative result.**

The plan estimated 36 sites. A survey found 12 genuinely-convertible
ones, and separately found that the plan's headline hold-release
scenario **was already fixed**. Both are reported below rather than
padded into a bigger-looking diff.

## The negative result (commit 1)

The plan named hold-release.ts as a target on the scenario *"release
readiness must hold and release identically for a RENAMED hold column."*
I wrote that test first, to prove it broken.

**It is not broken.** All five assertions passed against unmodified
`hold-release.ts`. U6/KTD-5 had already converted the module —
`isHeldTask`, `resolveReleaseTarget`, and `dependencySatisfied` each
resolve the task's IR. **`hold-release.ts` has no production change in
this PR.**

The tests are kept as a regression floor: the invariant rests on three
independent trait resolutions any of which could be "simplified" back to
a literal, and nothing else covered a renamed vocabulary end-to-end
through the sweep.

**I verified the tests can actually fail.** Mutating `isHeldTask` back
to `task.column === "todo"` kills all five. Without that check, a green
run against unmodified code is indistinguishable from a test asserting
something trivially true.

Two drafting notes kept in the file: the renamed ids deliberately avoid
colliding with any legacy literal, and the first draft's two dependency
tests used a `capacity` hold — which never consults dependencies at all,
so one passed **vacuously**. Both now use a `dependency` hold.

## The 12 conversions

Each was red-green: the renamed-workflow test written first and
**observed failing**, then made to pass.

| Site | Was | Now |
|---|---|---|
| `task-agent-sync` CLEAR_COLUMNS | `{done,archived,todo,triage}` |
resolved complete+archived+hold+intake |
| `task-agent-sync` isParkedTaskColumn | `{todo,triage}` |
`parkedColumns` param (hold+intake) |
| `task-agent-sync` handler branch | `to === "todo" \|\| "triage"` |
resolved parked set |
| `mesh-lease` parked guard | `task.column !== "todo"` | resolved
rebound column |
| `mesh-lease` rebound move | `moveTask(id,"todo")` | resolved rebound
column |
| `mesh-lease` audit decisionPath | `=== "todo" ? … : …` | same resolved
column |
| `mesh-lease` audit newColumn | `… : "todo"` | same resolved column |
| `merger-ai` already-finalized | `=== "done" \|\| "archived"` |
resolved complete+archived |
| `merger-ai` ×4 rebounds | `moveTask(id,"todo")` | shared
`resolveFinalizeReboundColumn` |

Rebound targets all use KTD-10 `resolveReboundTarget` (hold → intake →
first column), the helper `self-healing.ts:714` already uses — reused,
not invented.

## Three findings worth reading

**1. The mesh-lease bug was in the AUDIT, not the move.** The guard and
the audit were *independent* `=== "todo"` comparisons, so `newColumn`
asserted the card landed in `todo` regardless of what the move did. For
a workflow with no `todo` column that produced a lease-recovery trail
naming a nonexistent column — and run-audit is the only post-hoc record
of a lease recovery. Now resolved once and threaded to both, so they are
structurally incapable of disagreeing.

**2. The merger-ai failure mode was not what I predicted.** I expected
the already-finalized guard to fail open and re-merge a finished card.
The red run showed it actually throws `Cannot merge FN-1: task is in
'shipped', must be in 'in-review'` — a hard error blaming the column, on
a task whose real state is "already done". The thing preventing the
re-merge is *itself* a literal in core's `getTaskMergeBlocker`, outside
this slice. Two bugs coinciding, not a design.

**3. A fourth site had to move that wasn't on the list.**
`evaluateParkedAgentTaskLink` calls `isParkedTaskColumn` internally.
Converting only the handler would have left the preservation branch on
legacy ids after the caller resolved a renamed workflow — trading a
stale-link bug for a **worse** dropped-link bug (a live agent's link
cleared mid-run).

## Deliberately NOT converted

Both keep their literals with the reason recorded at the site under a
greppable `DELIBERATE-LITERAL` tag:

- **`hold-release.ts:326` `legacyDependencySatisfied`** — the FN-5719
dual-accept half. Converting makes both halves compute the same answer,
deleting the compatibility signal *and* its divergence detector while
looking like a cleanup.
- **`replan-target.ts` final fallback** — its value is precisely that it
is *not* trait-resolved; resolving it against the workflow is the
stranded-card bug it was written to fix.

⚠️ **The U12 literal ratchet does not exist in the tree yet.** The brief
assumed an allowlist to add entries to; there is none. `grep -rn
DELIBERATE-LITERAL packages/*/src` enumerates the sites it must admit.

## What I could NOT verify

- **One of the four merger-ai rebound sites is untested.** The
`landWorkspaceTask` rebound is verified by inspection and the shared
resolver's unit tests only — `landWorkspaceTask` is only ever *mocked*
(project-engine.test.ts), never executed. Covering it needs a multi-repo
git fixture and a full land run. The **other three are genuinely
exercised** by pre-existing merger-ai.test.ts (lines 676/716/777/895
assert `moveTask("FN-1","todo",…)` through a real git repo) and pass
unchanged — real wiring proof for those.
- **3 of the 9 task-agent-sync tests passed before the conversion too**,
vacuously — the literal handler early-returned and cleared nothing. They
assert nothing about the old code; they are guardrails against the
conversion over-clearing.
- **No renamed workflow was run against a live engine.** All evidence is
unit-level.

## Call sites outside this slice — NOT converted, byte-identical

They keep the legacy defaults: `scheduler.ts:1273`,
`agent-heartbeat.ts:1169/3642`, `self-healing.ts:11600/11665` (all
`evaluateParkedAgentTaskLink`), and `merger.ts:6585` (the sibling
terminal guard). Each is its own Phase C/D surface.

## Behavior changes (not a pure refactor)

For a **renamed** workflow: agent links now actually get cleared on
terminal moves (they never were); lease rebounds land in the resolved
hold column; finalize-blocked rebounds land in the resolved hold column
and their operator-facing task-log lines name the real column;
already-finalized cards short-circuit cleanly instead of throwing.

For **builtin:coding** and any unresolvable workflow: byte-identical.
Every new parameter defaults to the legacy set, and both merger-ai
resolvers fail *soft* to legacy ids in opposite directions — the
terminal guard keeps `done`/`archived` (losing it sends a finished card
into the merge path), the rebound keeps `todo` (abandoning it strands
the card in the merge lane with no owner).

## Verification

- Merge gate **green**: 299 + 10 + 71 tests
- Slice suites **green**: 100 tests across 8 files (new + all
pre-existing neighbours)
- Existing merger suites **green**: 82 tests across 5 files, unchanged
- `tsc --noEmit` clean, `pnpm lint` clean

No changeset: `@fusion/engine` is private.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 14:28:53 -07:00
gsxdsm
5d0f1ef631 Phase B slice B1: lifecycle column roles in the U6 policy modules (4 guards, red-green) (#2479)
**Stacked on #2469** → #2468 → #2467. Base is
`feature/workflow-capacity-ground-truth`.

This is **slice B1 of Phase B, not all of Phase B.** Sizing escalation
sent separately; the census is below.

## Why this is a slice

Measured census of code lines referencing a lifecycle column literal
(comments excluded):

| Unit | Files | Sites |
|---|---|---:|
| U4 | `self-healing.ts` | 203 |
| U5 | `executor.ts` 171, `scheduler.ts` 55, `replan-target.ts` 20,
`merger-ai.ts` 5, `hold-release.ts` 4, `mesh-lease-manager.ts` 4,
`task-agent-sync.ts` 3 | 262 |
| U6 | `moves.ts` 34, `default-workflow-hooks.ts` 13, `board-config.ts`
9, `blocker-fanout.ts` 6, `task-priority.ts` 5,
`dependency-blocked-todo-report.ts` 2, `stale-paused-todo.ts` 1 | 70 |
| | **Total** | **535** |

The plan's "~207" counts the guard category only. Under the phase's
non-negotiable rule — a test that **fails before** conversion, per guard
— that is ~200 red-green cycles. Doing it as one sweep would reproduce
exactly the failure this phase exists to prevent: converted guards
nobody proved still fire.

`moves.ts` and `default-workflow-hooks.ts` stay **parked** per the
dispatch constraint (move-path convergence and the pool-id sentinel are
on an operator decision).

## Guards converted (4), each red-green

Every case below was written **first** and observed failing against the
literal implementation.

| Module | Guard | Before → After |
|---|---|---|
| `stale-paused-todo.ts` | stall detection | `column !== "todo"` →
resolved **hold** column |
| `blocker-fanout.ts` | active | `ACTIVE_COLUMNS.has(col)` →
`!terminalColumns.has(col)` |
| `blocker-fanout.ts` | hold-wait metric | `col === "todo"` → resolved
**hold** column |
| `task-priority.ts` | unblock active | `UNBLOCK_ACTIVE_COLUMNS`
**deleted**, folded into the terminal set |

Three of the seven new cases are **regression floors** that pass before
and after. One of them earned its keep immediately: it failed on my own
fixture (`activeCount` vs the public `totalCount`), catching a bad test
rather than bad code — which is the point of asserting the default path
alongside the renamed one.

### The `task-priority` finding

`UNBLOCK_ACTIVE_COLUMNS` and `DONE_COLUMNS` encoded **one concept
twice**, two lines apart, and disagreed for any custom column:
dependency counting treated a `drafting` card as unmet (correct) while
the active check treated it as inactive (wrong), zeroing the blocker's
unblock weight. The enumeration wasn't just legacy-shaped — it
contradicted its own neighbour.

## ⚠️ Behavior change, not a pure refactor

Inverting active from enumeration to exclusion means **a card in a
column that is neither terminal nor in the legacy enum now counts as
active where it previously did not.** That is the plan's stated intent,
but it is a real change for any project already using a custom column —
**Coding (Ideas)' `ideas` column is the in-tree case.** Fan-out counts
and unblock weights for such cards will rise.

## Verification

- Four affected suites green (45 tests), each conversion observed
red→green.
- `pnpm lint`, `tsc --noEmit` (core) green.

**Not verified / not done, stated plainly:**

- **Call sites are not wired.** These modules now *accept* resolved
roles; every parameter still defaults to the legacy set, so at the call
sites the vocabulary is unchanged. A caller that cannot resolve a
workflow keeps literal behavior. Threading `resolveLifecycleColumns`
through `reads.ts` and `self-healing.ts` is follow-on work — until then
the guards are *convertible*, not *converted end-to-end*.
- `dependency-blocked-todo-report.ts` and `board-config.ts` are
untouched in this slice.
- 19 core-suite failures exist on this branch; all confirmed
**pre-existing** by stashing and re-running on a clean tree
(`duplicate-guard`, `log-severity-spam-contract`, `settings-parity`,
`task-delete-caller-attribution`, `settings-defaults`).

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

---

**Supersedes #2470**, which GitHub force-closed when its base branch was
deleted by the merge of #2469 and refuses to reopen. Same head branch,
same commits (rebased onto `main`), now based on `main` directly. The
two P1 review threads on #2470 were resolved there — one of them with a
correction noting the threading half landed in code that was
subsequently deleted as a dead feature in #2477.

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

## Summary by CodeRabbit

* **Bug Fixes**
* Dependency and blocker reports now correctly recognize custom hold,
active, and terminal workflow columns.
* Blockers in renamed terminal columns are no longer incorrectly
reported as active.
* Stale paused-task badges and self-healing now work with
workflow-specific hold columns.
* Mixed boards with different workflow column names are handled
consistently.
* Existing default workflow behavior remains compatible, including
fallback handling when workflow details cannot be resolved.

* **Enhancements**
* Reporting and task-priority calculations now support configurable
single or multiple hold and terminal columns.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 14:19:32 -07:00
gsxdsm
553cc3b517 Phase A3: the in-txn capacity invariant does NOT hold — root-caused, ratcheted, not fixed (#2469)
**Stacked on #2468**, which is stacked on #2467. Base is
`feature/workflow-move-path-convergence`.

**Answer: the A2 observation was real. The documented invariant is
broken.** Not a false alarm, not a harness artifact.

`workflow-capacity.ts` states enforcement "runs INSIDE
`moveTaskInternal`'s transaction and is **NEVER bypassable** (not a
guard — runs regardless of bypassGuards/recoveryRehome/moveSource)". It
does not hold for default-workflow tasks, for two independent reasons.

## R1 — Pool-id sentinel mismatch (the defect)

| Site | Sentinel for "no workflow selection" |
|---|---|
| `moves.ts:319` (in-txn check, **asks**) | `?? "builtin:coding"` |
| `countActiveInCapacitySlotAsyncImpl` (**answers**) | `??
DEFAULT_WORKFLOW_POOL_ID` → `"__default-workflow__"` |
| `hold-release.ts:116` (sweep, second enforcement point) | `??
DEFAULT_WORKFLOW_POOL_ID` ✅ |

The check asks for occupants of a pool that no occupant is ever bucketed
into, so the count comes back `0` and the limit can never bind. The
sweep is correct, so the two enforcement points **disagree about pool
identity** — precisely what the module docstring says is impossible
("the two enforcement points can never disagree on what a limit *is*,
only on the live count").

Note the shape of the bug: it is not a missing check. The check runs,
queries correctly, and returns a confidently wrong answer.

## R2 — The `useWorkflow` gate

The whole block sits inside `if (useWorkflow && workflowIr && fromColumn
!== toColumn)` (`moves.ts:921`), and `useWorkflow` reads the raw
`experimentalFeatures.workflowColumns` key nothing in production sets.
**On the live path the check cannot run at all**, so R1 is latent today
and becomes reachable the moment A2 converges onto the flag-ON side.

## How this was established, not guessed

Three of my assumptions failed earlier in this program, so this one is
pinned by a **discriminating experiment** rather than a code reading:

| Case | Path | Selection | Result |
|---|---|---|---|
| DEFECT (R2) | inline (live) | none | accepted — check cannot run |
| DEFECT (R1) | hooks | none | accepted — sentinels disagree |
| **DISCRIMINATOR** | hooks | explicit `builtin:coding` | **refused,
`capacity-exhausted`** |

The third case changes nothing but sentinel agreement. That rules out
"capacity is simply not wired" and isolates the cause to the mismatch.

Both-path forcing reuses A2's `assertPathActive` probe. Without it this
suite would silently run one path twice and report a tautology — the
failure mode that produced sixteen false passes across A2's two harness
bugs.

## The deliverable: an invariant ratchet

The three cases above assert today's wrong behavior, so on their own
they would let the defect live forever. A fourth case states the
invariant **as written** and is marked `it.fails`:

- **today** — the body fails, so `it.fails` passes; CI stays green while
honestly recording the breach;
- **when fixed** — the body passes, `it.fails` *fails*, forcing whoever
lands the fix to flip it and the two `DEFECT:` expectations.

That is "a test that fails if the invariant is broken" in the only shape
that does not park a permanently-red test in CI.

## Blast radius (step 4) — why I did not fix it

The fix is one line: make `moves.ts:319` use `DEFAULT_WORKFLOW_POOL_ID`,
matching the sweep. The consequences are not one line.

**Today: zero.** `useWorkflow` is false everywhere, so the corrected
check still cannot run on the live path. The sentinel fix is safe to
land in isolation.

**At A2 convergence: every default-workflow task move into `in-progress`
becomes capacity-checked against `maxConcurrent` (default 2), for the
first time.** Affected movers:

- **The graph column boundary** catches `capacity-exhausted` and *parks
the run*. Runs that previously proceeded would begin suspending — this
is a scheduling behavior change across every project, not an error path.
- **`executor.ts`, `project-engine.ts`, `pr-comment-handler.ts`** each
move tasks into `in-progress` and would begin seeing a rejection they
have never seen.
- **Operator drags and the promote route** would start refusing beyond
`maxConcurrent`.

Scheduler-side admission (`maxConcurrent`) is a separate, still-live
control, so this is not "capacity is unenforced today" — it is "the
store-level check the graph and promote paths are written against
returns 0 and never binds".

**Recommendation:** land the sentinel fix on its own (provably inert
today), with the ratchet flipped in the same commit, *before* A2
convergence — so convergence does not simultaneously switch paths and
switch on a previously-dead enforcement.

## Verification

4 cases green against real PostgreSQL (3 passed + 1 expected-fail), path
flip proven live on every case. `pnpm lint` and `tsc --noEmit` green.

## Follow-up: both "not verified" items are now answered

Recorded here rather than left as open questions, since this is where
anyone investigating capacity will look.

**1. Does the sync/SQLite counter carry the same mismatch? — YES,
identically, but it is unreachable.**

`countActiveInCapacitySlotSyncImpl` (`project-store-ops.ts:767`) buckets
rows the same way as the async one:

```ts
const effectiveWorkflowId = row.wid ?? TaskStore.DEFAULT_WORKFLOW_POOL_ID;
```

So it disagrees with `moves.ts:319` in exactly the same way. **However**
its only caller is the public `TaskStore.countActiveInCapacitySlotSync`
wrapper, which has no in-repo caller at all — it is dead API surface.
The mismatch is real but currently unreachable, which makes it a
landmine for whoever wires it up rather than an active defect. Fixing
the sentinel should fix both call sites together.

**2. Are custom workflows with an explicit numeric `limit` affected? —
NO, and this is already proven by the discriminator above.**

The mismatch fires **only when the selection row is absent** — that is
what the `??` fallback is for. A custom workflow necessarily *has* a
selection row; that is what makes it custom. The DISCRIMINATOR case adds
an explicit selection and the rejection appears, which is exactly the
custom-workflow shape. So the defect is scoped to **no-selection
(default-workflow) tasks only**.

The explicit-numeric-`limit` question turns out to be orthogonal:
`resolveColumnBudgetKey` returning `col:${columnId}` decides *which
columns share a budget*, not which pool id is passed to the counter. It
does not interact with the sentinel at all.

Net effect on blast radius: **narrower than first stated.** Only
default-workflow (no-selection) tasks slip the limit today.
Custom-workflow tasks are already enforced — meaning the fix does not
switch enforcement on for them, it only closes the gap for the default
workflow.

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


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

* **Tests**
* Added coverage for workflow column capacity enforcement during
transactions.
* Documented scenarios where capacity limits are bypassed, including
tasks without workflow selection.
* Verified that explicit workflow selection correctly rejects moves when
capacity is exhausted.
* Added a tracked failing test for the expected invariant once
enforcement is corrected.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 13:59:56 -07:00
gsxdsm
b941d3cba5 Phase A2 (steps 1-2): differential characterization of the two move paths (#2468)
**Stacked on #2467** (base is `feature/workflow-owned-lifecycle`, not
`main`).

Phase A2 steps 1 and 2. **Step 3 — make one path authoritative and
delete the other — is NOT done.** It is blocked on a measured
divergence, escalated to the operator. This PR is the evidence that
decision needs.

## The setup

`moves.ts` branches on `useWorkflow =
isWorkflowColumnsCompatibilityFlagEnabled(settings)`, which reads the
raw `experimentalFeatures.workflowColumns` key. Nothing in production
writes it, so the **inline branch is LIVE** and
**`default-workflow-hooks.ts` is DEAD**.

Because only one implementation runs, equivalence cannot be observed by
running the suite normally — the dead path is never entered. Every case
here forces both paths explicitly through one shared fixture and
compares a 19-field observation, not "it moved".

## Step 1-2 result: side-effect equivalence is PROVEN

Eight behaviors, field-by-field identical across both paths:

| Behavior | Verdict |
|---|---|
| `in-progress → todo` user reopen field clears | equivalent |
| Engine-source reopen does not set `userPaused` | equivalent |
| `preserveStatus` keeps status/error | equivalent |
| `preservePause` keeps an operator park (FN-7851) | equivalent |
| Timing / `cumulativeActiveMs` across exit and re-entry | equivalent |
| `preserveResumeState` step progress | equivalent |
| `preserveWorktree` | equivalent |
| Default worktree clear on reopen | equivalent |

### Why this is a proof and not a green suite

Two independent guards, both of which caught a real silent failure in
this PR's own development:

- **The forcing mechanism is self-checked.** `assertPathActive` probes
an undeclared target column — whose rejection message differs per path —
before every case. The first version of this suite wrote the flag with
`updateSettings` instead of `updateGlobalSettings`
(`experimentalFeatures` is global-scoped, which is exactly why
`moves.ts` reads it through `getSettingsFast()`), and reported **nine
passing "equivalence" cases while running the inline path twice**. The
check then caught a second failure: `updateGlobalSettings` *merges*, so
resetting with `{}` left a previous `true` in place and leaked the hooks
path into seven cases that believed they were on inline.
- **The suite is mutation-tested.** Deleting `task.blockedBy =
undefined` from `applyResetOnEntryEffects` fails the reopen case, naming
the field. Restored before commit.

Timestamps are compared by presence rather than value — the two runs
happen at different wall-clock instants by construction — but a path
that forgets to stamp `executionCompletedAt`, or wrongly clears
`firstExecutionAt`, still fails.

## ⚠️ Read this before writing any both-paths test

**A differential harness that cannot prove which path it is on will
report a tautology, confidently, and in green.** This suite hit that
twice in one afternoon:

1. **Global-scoped key written to project scope.** The first version set
the flag with `updateSettings`. `experimentalFeatures` is
**global**-scoped — which is exactly why `moves.ts` reads it through
`getSettingsFast()` (merged global + project). The write was silently
accepted and never reached `useWorkflow`. Result: **nine passing
"equivalence" cases while running the inline path twice.**
2. **Merge-on-write leaking a stale `true`.** `updateGlobalSettings`
*merges*, so resetting with `{ experimentalFeatures: {} }` left the
previous `workflowColumns: true` in place. Result: the hooks path leaked
into **seven cases that believed they were on inline.**

Neither failure produced a red test. Both were caught only by
`assertPathActive` — a per-case probe that moves a task to an undeclared
column and asserts on the rejection *message*, which differs per path
(`Valid targets: …` inline vs `Unknown column for this workflow` on
hooks).

This is the same failure class as a spy passing on a refused payload
(see #2467): **the observation confirms the assumption instead of the
behavior.** The rule that generalizes:

> When a test forces a code path, assert that the path is active using a
signal only that path can produce — before every case, not once in
setup. A forcing mechanism that can fail silently makes every assertion
downstream worthless.

Any future work touching both move paths needs this probe or it will get
a confident wrong answer.

## Why step 3 is blocked

### Divergence: rejection type and message

| | Inline (live) | Hooks (dead) |
|---|---|---|
| Validates against | legacy `VALID_TRANSITIONS` | the task's own
workflow |
| Throws | bare `Error` | `TransitionRejectionError` with a
machine-readable `rejection` |
| Message | `Valid targets: …` | `Unknown column for this workflow` |

Both reject, so neither is "broken" — but they are not interchangeable.
Making either authoritative changes what every catch site observes,
including the flag-OFF characterization suite that pins the bare-Error
contract and the callers that branch on `rejection.code`.

### Unproven, recorded as an honest negative: in-transaction capacity

The capacity block sits inside `if (useWorkflow && workflowIr &&
fromColumn !== toColumn)`, so it **cannot** run on the live path. The
natural inference is "convergence turns store-level capacity rejection
on for every project at once" — a serious blast radius, since
`capacity-exhausted` is what the graph column boundary parks on and what
the promote route surfaces to operators.

**That inference did not survive measurement.** With `maxConcurrent: 1`
and an already-occupied wip column, the second move was **accepted on
both paths**. Something further in — `resolveColumnCapacity`'s limit
resolution, or what `countActiveInCapacitySlotAsync` counts as an
occupant — keeps the check from firing even flag-on. This suite does not
establish which.

So the capacity blast radius is **unquantified, not absent**. The test
pins today's observed behavior so the investigation starts from a fact
rather than from the code reading; if a future change makes it reject,
that failure is the signal to reopen the question.

## Verification

- 10/10 green against real PostgreSQL, with the path flip proven live by
`assertPathActive` on every case.
- Mutation-tested (see above).
- `pnpm lint` and `tsc --noEmit` (core) green.

**Not verified:** whether the capacity gate would activate under some
other configuration; the plugin column-gate and post-commit plugin-hook
divergences (also inside the `useWorkflow` gate) are identified
structurally but not characterized here.

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


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

## Summary by CodeRabbit

* **New Features**
* Workflow lifecycle updates now provide more consistent task state
notifications and workflow activity handling.
* Task lists display resolved workflow column names and lifecycle
details more reliably.
* **Bug Fixes**
* Workflow-based task promotion and board views no longer depend on an
obsolete feature setting.
  * Improved recovery for tasks left in transitional states.
* Preserved task status, pause, progress, timing, and worktree behavior
across workflow transitions.
* **Reliability**
* Added stronger validation and durable handling for workflow events and
follow-up processing.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 13:49:55 -07:00
gsxdsm
4158cf1ab7 Phase A: workflow-owned lifecycle foundation (U1, U2, U3) (#2467)
Phase A (Foundation) of
`docs/plans/2026-07-26-001-refactor-workflow-owned-lifecycle-plan.md`.
Three units, one commit each. No operator-visible behavior change.

## U1 — Lifecycle-column resolution seam

`resolveLifecycleColumns(ir)` returns `{ intake, hold, wip, review,
complete, archived }` — the first column carrying each trait,
`undefined` for a role no column carries.
`resolveTaskLifecycleColumns(store, taskId, cache?)` is the store-aware
form; the cache is caller-owned so a sweep reads one IR per workflow
rather than one per card.

A v1/column-less IR resolves to `undefined` for the **whole struct**
rather than a struct of undefined roles. A caller must be able to
distinguish "this workflow declares no hold column" (a real shape to
honor) from "no column vocabulary at all" (skip and log) — only the
second licenses conservative fallback.

Nothing consumes the seam yet; Phases B–D convert the ~207 hardcoded
column literals onto it.

## U2 — Delete the pre-cutover parity machinery (delete-only)

**`workflow-columns-settings.ts`** — `isWorkflowColumnsEnabled` had the
body `return true`. Six live call sites branched on it, so every
flag-OFF arm was dead code that read as a supported configuration.
Deleted; surviving side inlined at self-healing's transitionPending
sweep, the scheduler's per-column capacity diagnostic, merge-trait's
policy resolver, the board-workflows payload, two task-workflow routes,
and the CLI TUI's column enrichment.

**`workflow-parity.ts`** — asserted the default workflow's adjacency
*equals* the legacy `VALID_TRANSITIONS`. U11 deliberately breaks that
equality by merging Todo into Planning, so this is not a stale assertion
to update; it is a contract against the target state. Its emitter
(`workflow-parity-observer.ts`) is already a tombstone, so
`getWorkflowParitySummary` and `computeWorkflowColumnsGraduationReport`
aggregated run-audit rows nothing writes and had no caller outside
`TaskStore`. Both store methods go with it.

`flagEnabled` stays on the board-workflows **wire** as a constant `true`
— shipped dashboard clients still branch on it, and changing the
response shape is not a deletion. U10 retires the field once no client
reads it.

The `legacy-tombstones` ratchet is extended to both files plus seven
symbols, each with the reason it is gone.

### ⚠️ Finding: the third listed deletion was NOT dead

The plan also lists "the flag-off inline move path" in
`task-store/moves.ts`. It is **not** deleted, per U2's execution note
("any behavior change found while removing a branch means the branch was
not dead").

That path is gated on `isWorkflowColumnsCompatibilityFlagEnabled`
(`store.ts:38`) — a **different** function from the always-true public
helper. It reads the raw `experimentalFeatures.workflowColumns` setting,
which nothing in production sets (`settings-schema.ts:396` — "no default
flags are emitted"; zero non-test writers; the operator's own
`~/.fusion/settings.json` has no such key). So `useWorkflow` is false
for effectively every real project: the flag-OFF inline side effects are
the **live** default move path and the flag-ON `default-workflow-hooks`
path is the dead one. The code says so itself at `moves.ts:638`.

Deleting that branch would swap every project onto an untravelled code
path — a behavior change, not a deletion.

**Carry this into Phases B and C, stated plainly so the plan's error is
not repeated:**

> **The inline move path in `moves.ts` is LIVE.
`default-workflow-hooks.ts` (the trait-hook path) is DEAD.** KTD-6
asserted the inverse. Until the convergence unit lands, **nothing may
assume trait hooks run** — a guard, sweep, or subscriber written against
`applyDefaultWorkflowMoveEffects` would never fire in production and
would still pass its tests.

Convergence is **not** attempted here. It is its own unit (Phase A2)
with a proper equivalence proof, per operator decision.

### U3's emit point is on the LIVE path — the seam is not born dead

Worth stating explicitly because it is the failure mode that would make
every later subscriber silently never fire: the `TaskTransitioned` emit
is **not** inside the `if (useWorkflow)` branch. That block closes at
`moves.ts:1212`; the emit sits at `:1214`, beside the existing
`store.emit("task:moved", …)`, on the unconditional post-commit path. It
therefore fires on **both** the live inline path and the dead hooks
path, and the convergence unit inherits the obligation to keep it firing
on whichever path survives — same events, same order, same payloads.

The graph-side emitters (`NodeEntered`, `RunSuspended`) carry the same
risk from a different direction: the bus refuses an invalid payload
*silently* by design, so an emitter regression would stop the event with
no test failure. They are asserted end-to-end through the real bus —
"did a subscriber actually receive it", not "was emit called" — because
a spy passes on a refused payload. The `moveTaskInternalImpl` emit does
**not** yet have that end-to-end assertion against a real store move;
that proof belongs to the convergence unit, which has to build the
both-paths fixture anyway.

## U3 — Post-commit event seam with a transactional outbox

**The bus is not a queue, not a transaction participant, and not a
delivery guarantee.** Durable follow-on work uses the transactional
outbox — a `workflow_work_items` row written *inside* the transition
transaction (the shape `createCompletionHandoffWorkflowWork` already
uses). "Emit after commit, let a subscriber enqueue the work" has a
crash window where a process dies between commit and subscriber, leaving
no event *and* no work-item row, so required work is skipped permanently
with nothing to recover from. Post-commit subscribers therefore carry
only losable reactions.

Emission is consequently lossy and isolated by design: a throwing or
rejecting subscriber is caught and logged, cannot roll back the
transition, and cannot stop the others. Deliveries append to one serial
chain, so two transitions on a task deliver in commit order.

The ids/outcomes-only rule is **mechanised, not documented** —
run-audit's equivalent lives only in prose and has been violated
repeatedly. A payload carrying an object body or a prose string is
refused at the emit boundary and never reaches a subscriber or log sink.
It degrades rather than throws: the emitter is post-commit, so a shape
bug must not become a lifecycle failure.

Emit points: `TaskTransitioned` from the single post-commit point in
`moveTaskInternalImpl`; `NodeEntered` and `RunSuspended` from the graph
column boundary, the latter *after* the durable continuation is
persisted so an observed suspension implies a resumable run.

`registerWorkflowEventSubscribers` (engine) is empty on purpose —
U7/U8/U10 move real reactions onto it, each with the characterization
test proving the reaction was non-authoritative first.

## Verification

- `pnpm test:gate` — green (2/10, 16/299, 1/71).
- `pnpm lint`, `pnpm build`, `tsc --noEmit` on core and engine — green.
- U1: 20 tests in `workflow-lifecycle-traits.test.ts`, including the
fully-renamed-workflow case (fails if the resolver falls back to a
literal) and a shared-cache read-count assertion.
- U2: `legacy-tombstones.test.ts` green with the extended ratchet;
`board-workflows`, `merge-trait`, `workflow-graph-executor-parity`, and
move-hook suites green with no expectation edits.
- U3: 20 bus-invariant unit tests (isolation, ordering, the allowed-key
and required-key halves of the ids-only rule, lossiness) plus 3
end-to-end emitter-delivery tests; 5 outbox tests against a **real
PostgreSQL** work-item table (crash survival, rollback, at-least-once
redelivery on lease expiry, idempotent handler → one effect,
dropped-subscriber vs. durable work). A hand-written fake of the lease
predicate would only prove the fake redelivers.

**Not verified:** the `moveTaskInternalImpl` emit is confirmed on the
unconditional post-commit path by structure and by the surrounding
tests, but is *not* yet asserted end-to-end against a real store move on
both flag settings — that is Phase A2's fixture. The engine subscriber
registry ships empty by design, so no production subscriber exercises
the bus end-to-end yet. `settings-defaults.test.ts` has one pre-existing
failure on `main` (a logger-prefix mismatch in the
`mergeIntegrationWorktree=cwd-main` warning) — confirmed present on a
clean tree, unrelated to this branch.

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

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

* **New Features**
* Workflow lifecycle columns are now derived from workflow definitions,
supporting renamed and custom workflows.
* Added post-commit lifecycle events for task transitions, node entry,
and run suspend/resume with validated payloads.
* Follow-on processing for lifecycle emissions is now more robust
(rollback-safe, at-least-once delivery, idempotent handling).
* **Bug Fixes**
* Workflow board responses, task enrichment, and promotion no longer
depend on workflow-columns feature-flag gating.
  * Subscriber failures no longer impact committed workflow transitions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 13:30:13 -07:00
gsxdsm
d873ee8160 docs: correct KTD-6 — the "flag-off" move path is live, the hooks path is dead (#2466)
Docs only. Corrects a factual error in the program plan merged in #2463,
found during Phase A execution.

## What was wrong

KTD-6 listed three deletions as "provably dead." Two are. The third —
the flag-off inline move path in `task-store/moves.ts` — is the **live**
path:

- It is gated on `isWorkflowColumnsCompatibilityFlagEnabled`
(`store.ts:38`), which reads the **raw**
`experimentalFeatures.workflowColumns` key.
- That is a *different* function from the always-true public
`isWorkflowColumnsEnabled`, which is what the plan reasoned from.
- No production code writes that key, so the flag reads false — the
inline branch runs for essentially every project, and
`default-workflow-hooks.ts` is the dead one.
- `moves.ts:637` already said so: *"this 'flag-OFF' branch is the
DEFAULT move path for nearly every project — the strict compat flag
reads false because nothing sets it."*

Deleting it would have swapped every project onto an untravelled code
path and called it a cleanup.

## Changes

- **KTD-6** carries the correction inline, with the two consequences
that bind the rest of the program.
- **U2** rescoped to the two proven-dead deletions; `moves.ts` is out of
scope for it.
- **New U2b** converges the two implementations with a per-behavior
equivalence proof, and **blocks Phase B** — nothing downstream may
assume the trait-hook path runs until it lands.
- **U3's emit point must attach to the live inline path.** Wired into
the dead hooks path, the event seam would never fire — and because
subscribers are non-authoritative by design, *nothing would fail a
test*.
- Risk table gains: *a "dead" branch turns out to be live*.

## How it was caught

The Phase A worker escalated instead of following the instruction,
because U2 carried a delete-only Execution note: *any behavior change
found while removing a branch means the branch was not dead — stop and
treat it as a finding*. I verified the claim independently (flag
function, absent writers, live `settings.json`, source comment) before
accepting it.

That note stays on every deletion unit.

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

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

* **Documentation**
* Clarified terminology in the workflow lifecycle migration plan,
distinguishing the live inline move path from the compat-flag behavior.
* Expanded the convergence phase with explicit behavior-equivalence
requirements, pinned observable expectations, and removal of compat-flag
branching.
* Updated the migration diagram and phase ordering to reflect the new
gating structure, revised scope boundaries, and refreshed risk
mitigations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 13:28:26 -07:00
gsxdsm
8b039a543e fix(desktop): advance Pi runtime pin to 0.82.1 for packaging PR lane (#2465)
## Summary
- Advance the matched Pi runtime pin (`pi-ai`, `pi-coding-agent`,
`pi-agent-core`, `pi-tui`) from **0.82.0 → 0.82.1** so
electron-builder's production-dependency walk accepts `pi-agent-core`'s
`pi-ai@^0.82.1` requirement.
- Fixes the Desktop packaging PR-lane failure:
`Production dependency @earendil-works/pi-ai not found for package
@earendil-works/pi-agent-core` (required `^0.82.1`).
- Keep the workspace override guard; update pin-policy fixtures and CLI
package-config expectations.
- Tighten the advisory packaging step-order test so it asserts against
the real `electron-builder --dir` step (not a missing release-only step
name that previously passed via `indexOf === -1`).
- Run `pnpm dedupe` so the packaging lane's lockfile dedupe
early-warning is clean.

## Context
#2439 pinned the full Pi closure at 0.82.0 and made recent main-based
packaging runs green. This advances to the current upstream patch so
deploy + electron-builder stay aligned with `pi-agent-core@0.82.1`'s
declared dependency range.

## Test plan
- [x] `node scripts/check-pi-versions-pinned.mjs`
- [x] `node --test scripts/__tests__/check-pi-versions-pinned.test.mjs`
- [x] `pnpm --filter @runfusion/fusion exec vitest run
src/__tests__/package-config.test.ts`
- [x] `pnpm --filter @fusion/desktop exec vitest run
src/__tests__/release-workflow.test.ts`
- [x] `pnpm dedupe --check`
- [ ] GitHub: Desktop packaging (should run full packaging walk —
lockfile/package.json touched)
- [ ] GitHub: PR Checks (Lint, Typecheck, Build, Gate)
2026-07-26 23:47:49 -07:00
gsxdsm
3c5d07ab01 docs: workflow-owned lifecycle program plan + column-placement contract (#2463)
Docs only — no code. Companion to #2462.

##
`docs/solutions/architecture-patterns/workflow-node-column-placement-and-graph-entry-contract.md`

Why a workflow node's `column` is a lifecycle contract rather than a
display choice: it decides **who can drive the node**, **whether the
card holds a WIP slot**, and **whether anything can move it onward**.

Contents:
- The graph **entry contract** (`resolveColumnResumeNode`, shipped in
#2462) with the resume table.
- The **plan-in-place chain** — triage → finalize → continuation seed →
drain → resume → capacity suspend → release — annotated with the check
each link performs. Notably `todo`, not `triage`: an intake column has
no releaser, so a card parked there waits for a human.
- Why the pre-release gate must be narrow (column match **and**
enablement).
- The measured failure table from three reverted placement attempts.
- Why removing a column is a lifecycle-vocabulary refactor, not a
workflow edit: **82 guards** that silently stop matching, **43 writes**
to a column that no longer exists, **59 dashboard literals**. A guard
that never fires doesn't fail a test — it disables a recovery path.

## `docs/plans/2026-07-26-001-refactor-workflow-owned-lifecycle-plan.md`

The program that finishes the job, in four movements:

1. Resolve lifecycle columns from the workflow instead of ~207 string
literals.
2. Move every lane — planning, execution, review, merge — behind graph
nodes; lane services keep substrate only (storage, leases, timers,
supervision, capacity, recovery, audit).
3. A **post-commit event seam**: transitions commit transactionally,
*then* emit; subscribers react and may enqueue durable work items, but
no subscriber performs a transition. Enforced by test — dropping every
subscriber must change no lifecycle outcome.
4. Only then merge Todo into a single Planning column.

Phased so each phase lands green independently, with the IR change
deliberately **last** (KTD-7). Changing the workflow first makes the
suite green over dead guards — that's how the earlier attempts hid their
own breakage.

The merge lane **adopts** the existing design in
`docs/plans/2026-06-09-003-refactor-workflow-owned-merge-full-migration-slices-plan.md`
(slices S02–S08, still `draft-stack-handoff`) rather than authoring a
competing one, with a note to re-validate against current `main` since
it was drafted seven weeks ago.

Scale is stated honestly: ~48k lines across the four lane services, with
the executor unit explicitly landing across several commits rather than
one sweep.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 23:43:45 -07:00
gsxdsm
99c9f14ee0 feat: run Plan Review in the planning lane with a Plan Review badge (#2462)
## What

Plan Review, planning, and the replan loop move from the implementation
column into the **planning lane** (`todo`), so a task under
specification never holds a WIP slot. The card crosses into
`in-progress` exactly once, at `parse`, released by the scheduler.

Operators also finally see a **Plan Review** badge while the gate runs —
it was previously invisible on the default workflow.

## The part that made it possible

Moving the node is ten lines. It was attempted three times and reverted
each time, because a graph run with no durable continuation replayed
from `start` and dragged an in-progress card *backward* out of the WIP
column, firing `abort-on-exit` and stranding it in a pre-WIP column with
no releaser.

So this PR adds the graph **entry contract** —
`resolveColumnResumeNode`:

| Card is in | Resumes at |
|---|---|
| `triage` | `start` |
| `todo` | `plan` |
| `in-progress` | `parse` — never re-plans, never moves backward |
| `in-review` | first review node — gates are not skipped |

`ir.columns` is ordered and that order is the lifecycle order; rework
and failure edges are excluded so the entry point is always the main
path. The proof it's the right fix: **`executor-task-done-invariant`
passes unmodified** after failing every previous attempt.

## Also in here

- **Release gate narrowed twice.** `isUnplannedForExecution` applies its
pre-release plan-review gate only when the node's column equals the
card's column *and* the group is enabled for the task. The enablement
check fixes a real deadlock — a task with Plan Review toggled off was
held forever waiting for evidence nothing would ever write.
- **Badge cleanup.** Gate badge reads "Plan Review" instead of the
ambiguous "Reviewing" and no longer hides behind a lane restriction; the
status badge stops duplicating it; `planning` renders as "Planning"
instead of the raw engine token.
- **Coding (Ideas)** renames its planner column to "Planning" (id `todo`
unchanged) and loses its private planning-node re-home — the graph it
clones is already plan-in-place.
- **New sweep** `reconcileUndeclaredTaskColumns` re-homes a row whose
column its workflow no longer declares. Written for a follow-up, kept
because it makes any column edit survivable.

## Test changes

Scheduler and release fixtures now model a card whose Plan Review passed
— the state every real card is in when the capacity sweep sees it. A
held unreviewed card is the gate working, and that path stays owned by
`pre-release-plan-review.test.ts`.

New `workflow-graph-entry-contract.test.ts` covers the invariant at
every lifecycle position, plus the gap-column and remediation-node
cases.

## Verification

Gate 299 + 70 + 10, dashboard badge suites 672, engine
workflow/entry/executor suites 147, core 122. Lint and typecheck clean.
Full engine suite sits at the pre-existing baseline (notifier /
plugin-runner / notification-service, untouched by this).

## Follow-up

Removing the Todo column entirely is a separate ~207-site
lifecycle-vocabulary refactor — planned in
`docs/plans/2026-07-26-001-refactor-workflow-owned-lifecycle-plan.md`
(companion docs PR).

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

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

## Summary by CodeRabbit

* **New Features**
* Plan Review now runs in the Planning lane before implementation
begins.
* Cards resume from their current workflow column without replaying
earlier steps.
* Added automatic recovery for cards stranded in outdated workflow
columns.
* **Improvements**
  * Renamed the Coding (Ideas) planner column to “Planning.”
* Refined Plan Review gating to respect enabled settings and the card’s
current column.
* Updated planning and Plan Review badges for clearer, consistent labels
across cards and lists.
* **Bug Fixes**
* Improved workflow transitions and release behavior around planning,
review, and execution.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 23:42:46 -07:00
gsxdsm
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 -->
2026-07-26 23:28:42 -07:00
Phil Larson
52d64fa66e fix(engine): project CE steps after review handoff (#2464)
## Summary

- reconcile successful graph-native workflow results with pending task
checklist steps even when review handoff already moved the card into the
merge column
- preserve terminal, paused, and no-redundant-move behavior
- cover the real Compound Engineering post-review-handoff state with a
regression test

## Root cause

Compound Engineering runs `review-handoff` before `merge`. Review
handoff moves the task to `in-review`, which is also the merge column.
`ensureWorkflowMergeBoundaryTask()` returned immediately for cards
already in that column, before projecting successful
`workflowStepResults` onto legacy `Task.steps[]`. The merger then saw
`0/N` and rejected approved work with `task has incomplete steps`.

## Verification

- RED: regression test failed before the fix because `store.updateTask`
was never called
- GREEN: `executor-graph-boundary.test.ts` — 6 passed
- relevant non-PostgreSQL set — 31 passed, 5 PostgreSQL tests explicitly
skipped
- `@fusion/engine` typecheck passed
- changeset format passed
- `git diff --check` passed

## Baseline note

`ce-workflow-step-executor.test.ts` currently has three failures on
clean `origin/main` after FN-8601 foreach-proof hardening. The same
failures reproduce without this patch and are not regressions from this
change.


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

* **Bug Fixes**
* Improved reconciliation after review handoff by projecting completed
step results onto the legacy checklist when reaching the merge column.
* Prevented tasks from being marked approved with incomplete step counts
(including “0/N” style states).
* Reduced unnecessary merge failures and deadlock/pause scenarios when
merge-column progress was already recorded.
* **Tests**
* Added coverage for execute-and-merge workflows, ensuring
merge-boundary resolution updates pending steps without moving the task.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-26 23:13:21 -07:00
dependabot[bot]
d6c7e7dc74 chore(deps): bump actions/cache from 4 to 6 (#2441)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/releases">actions/cache's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update packages, migrate to ESM by <a
href="https://github.com/Samirat"><code>@​Samirat</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1760">actions/cache#1760</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v6.0.0">https://github.com/actions/cache/compare/v5...v6.0.0</a></p>
<h2>v5.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@​actions/cache</code> to v5.1.0 - handle read-only cache
access by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1775">actions/cache#1775</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.1.0">https://github.com/actions/cache/compare/v5...v5.1.0</a></p>
<h2>v5.0.5</h2>
<h2>What's Changed</h2>
<ul>
<li>Update ts-http-runtime dependency by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1747">actions/cache#1747</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.5">https://github.com/actions/cache/compare/v5...v5.0.5</a></p>
<h2>v5.0.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Add release instructions and update maintainer docs by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1696">actions/cache#1696</a></li>
<li>Potential fix for code scanning alert no. 52: Workflow does not
contain permissions by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1697">actions/cache#1697</a></li>
<li>Fix workflow permissions and cleanup workflow names / formatting by
<a href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1699">actions/cache#1699</a></li>
<li>docs: Update examples to use the latest version by <a
href="https://github.com/XZTDean"><code>@​XZTDean</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li>
<li>Fix proxy integration tests by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1701">actions/cache#1701</a></li>
<li>Fix cache key in examples.md for bun.lock by <a
href="https://github.com/RyPeck"><code>@​RyPeck</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li>
<li>Update dependencies &amp; patch security vulnerabilities by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1738">actions/cache#1738</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/XZTDean"><code>@​XZTDean</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li>
<li><a href="https://github.com/RyPeck"><code>@​RyPeck</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.4">https://github.com/actions/cache/compare/v5...v5.0.4</a></p>
<h2>v5.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a
href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li>
<li>Bump <code>@actions/core</code> to v2.0.3</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.3">https://github.com/actions/cache/compare/v5...v5.0.3</a></p>
<h2>v.5.0.2</h2>
<h1>v5.0.2</h1>
<h2>What's Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's
changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h2>How to prepare a release</h2>
<blockquote>
<p>[!NOTE]
Relevant for maintainers with write access only.</p>
</blockquote>
<ol>
<li>Switch to a new branch from <code>main</code>.</li>
<li>Run <code>npm test</code> to ensure all tests are passing.</li>
<li>Update the version in <a
href="https://github.com/actions/cache/blob/main/package.json"><code>https://github.com/actions/cache/blob/main/package.json</code></a>.</li>
<li>Run <code>npm run build</code> to update the compiled files.</li>
<li>Update this <a
href="https://github.com/actions/cache/blob/main/RELEASES.md"><code>https://github.com/actions/cache/blob/main/RELEASES.md</code></a>
with the new version and changes in the <code>## Changelog</code>
section.</li>
<li>Run <code>licensed cache</code> to update the license report.</li>
<li>Run <code>licensed status</code> and resolve any warnings by
updating the <a
href="https://github.com/actions/cache/blob/main/.licensed.yml"><code>https://github.com/actions/cache/blob/main/.licensed.yml</code></a>
file with the exceptions.</li>
<li>Commit your changes and push your branch upstream.</li>
<li>Open a pull request against <code>main</code> and get it reviewed
and merged.</li>
<li>Draft a new release <a
href="https://github.com/actions/cache/releases">https://github.com/actions/cache/releases</a>
use the same version number used in <code>package.json</code>
<ol>
<li>Create a new tag with the version number.</li>
<li>Auto generate release notes and update them to match the changes you
made in <code>RELEASES.md</code>.</li>
<li>Toggle the set as the latest release option.</li>
<li>Publish the release.</li>
</ol>
</li>
<li>Navigate to <a
href="https://github.com/actions/cache/actions/workflows/release-new-action-version.yml">https://github.com/actions/cache/actions/workflows/release-new-action-version.yml</a>
<ol>
<li>There should be a workflow run queued with the same version
number.</li>
<li>Approve the run to publish the new version and update the major tags
for this action.</li>
</ol>
</li>
</ol>
<h2>Changelog</h2>
<h3>6.1.0</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v6.1.0 to pick up <a
href="https://redirect.github.com/actions/toolkit/pull/2435">actions/toolkit#2435
Handle cache write error due to read-only token</a></li>
<li>Switch redundant &quot;Cache save failed&quot; warning to debug log
in save-only</li>
</ul>
<h3>6.0.0</h3>
<ul>
<li>Updated <code>@actions/cache</code> to ^6.0.1,
<code>@actions/core</code> to ^3.0.1, <code>@actions/exec</code> to
^3.0.0, <code>@actions/io</code> to ^3.0.2</li>
<li>Migrated to ESM module system</li>
<li>Upgraded Jest to v30 and test infrastructure to be ESM
compatible</li>
</ul>
<h3>5.0.4</h3>
<ul>
<li>Bump <code>minimatch</code> to v3.1.5 (fixes ReDoS via globstar
patterns)</li>
<li>Bump <code>undici</code> to v6.24.1 (WebSocket decompression bomb
protection, header validation fixes)</li>
<li>Bump <code>fast-xml-parser</code> to v5.5.6</li>
</ul>
<h3>5.0.3</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a
href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li>
<li>Bump <code>@actions/core</code> to v2.0.3</li>
</ul>
<h3>5.0.2</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="55cc834586"><code>55cc834</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1768">#1768</a>
from jasongin/readonly-cache</li>
<li><a
href="d8cd72f230"><code>d8cd72f</code></a>
Bump <code>@​actions/cache</code> to v6.1.0 - handle cache write error
due to RO token</li>
<li><a
href="2c8a9bd745"><code>2c8a9bd</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1760">#1760</a>
from actions/samirat/esm_migration_and_package_update</li>
<li><a
href="e9b91fdc3f"><code>e9b91fd</code></a>
Prettier fixes</li>
<li><a
href="e4884b8ff7"><code>e4884b8</code></a>
Rebuild dist</li>
<li><a
href="10baf0191a"><code>10baf01</code></a>
Fixed licenses</li>
<li><a
href="e39b386c90"><code>e39b386</code></a>
Fix test mock return order</li>
<li><a
href="b692820337"><code>b692820</code></a>
PR feedback</li>
<li><a
href="60749128a4"><code>6074912</code></a>
Rebuild dist bundles as ESM to match type:module</li>
<li><a
href="5a912e8b4a"><code>5a912e8</code></a>
Fix lint and jest issues</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/cache/compare/v4...v6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=4&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-26 22:49:14 -07:00
dependabot[bot]
88cc152d12 chore(deps): bump actions/setup-node from 4 to 7 (#2440)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4
to 7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-node/releases">actions/setup-node's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements:</h3>
<ul>
<li>Add cache-primary-key and cache-matched-key as outputs by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1577">actions/setup-node#1577</a></li>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1574">actions/setup-node#1574</a></li>
</ul>
<h3>Bug fixes:</h3>
<ul>
<li>Remove dummy NODE_AUTH_TOKEN export by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1558">actions/setup-node#1558</a></li>
<li>Only use <code>mirrorToken</code> in <code>getManifest</code> if
it's provided by <a
href="https://github.com/deiga"><code>@​deiga</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
</ul>
<h3>Documentation updates:</h3>
<ul>
<li>Add documentation for publishing to npm with Trusted Publisher
(OIDC) by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li>docs: Update restore-only cache documentation by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1550">actions/setup-node#1550</a></li>
<li>docs: Update caching recommendations to mitigate cache poisoning
risks by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1567">actions/setup-node#1567</a></li>
</ul>
<h3>Dependency update:</h3>
<ul>
<li>Upgrade <code>@​actions/cache</code> to 5.1.0, log cache write
denied by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li><a href="https://github.com/deiga"><code>@​deiga</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
<li><a href="https://github.com/jasongin"><code>@​jasongin</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6...v7.0.0">https://github.com/actions/setup-node/compare/v6...v7.0.0</a></p>
<h2>v6.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update <code>@​actions/cache</code> to 5.1.0 and add security
overrides for undici and fast-xml-parser by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1579">actions/setup-node#1579</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0">https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0</a></p>
<h2>v6.4.0</h2>
<h2>What's Changed</h2>
<h3>Dependency updates:</h3>
<ul>
<li>Upgrade <a
href="https://github.com/actions"><code>@​actions</code></a>
dependencies by <a
href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1525">actions/setup-node#1525</a></li>
<li>Update Node.js versions in versions.yml and bump package to v6.4.0
by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1533">actions/setup-node#1533</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1525">actions/setup-node#1525</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6...v6.4.0">https://github.com/actions/setup-node/compare/v6...v6.4.0</a></p>
<h2>v6.3.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements:</h3>
<ul>
<li>Support parsing <code>devEngines</code> field by <a
href="https://github.com/susnux"><code>@​susnux</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1283">actions/setup-node#1283</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8207627860"><code>8207627</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-node/issues/1574">#1574</a>)</li>
<li><a
href="04be95cf35"><code>04be95c</code></a>
Add cache-primary-key and cache-matched-key as outputs (<a
href="https://redirect.github.com/actions/setup-node/issues/1577">#1577</a>)</li>
<li><a
href="7c2c68d20d"><code>7c2c68d</code></a>
docs: Update caching recommendations to mitigate cache poisoning risks
(<a
href="https://redirect.github.com/actions/setup-node/issues/1567">#1567</a>)</li>
<li><a
href="6a61c0375d"><code>6a61c03</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/setup-node/issues/1569">#1569</a>
from jasongin/update-actions-cache-5.1.0</li>
<li><a
href="30eb73b41d"><code>30eb73b</code></a>
Resolve high-severity audit issues</li>
<li><a
href="4e1a87a501"><code>4e1a87a</code></a>
Update dist</li>
<li><a
href="360237f0c0"><code>360237f</code></a>
Strict equality</li>
<li><a
href="4f8aac5beb"><code>4f8aac5</code></a>
Bump <code>@​actions/cache</code> to 5.1.0, log cache write denied</li>
<li><a
href="f4a67bbeca"><code>f4a67bb</code></a>
Only use <code>mirrorToken</code> in <code>getManifest</code> if it's
provided (<a
href="https://redirect.github.com/actions/setup-node/issues/1548">#1548</a>)</li>
<li><a
href="0355742c94"><code>0355742</code></a>
Remove dummy NODE_AUTH_TOKEN export (<a
href="https://redirect.github.com/actions/setup-node/issues/1558">#1558</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-node/compare/v4...v7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=4&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-26 22:48:53 -07:00
gsxdsm
0e3d2a2265 refactor: delete meta-task auto-archive and automated recovery follow-ups (#2461)
Deletes two pieces of automated "meta" machinery that filed and
garbage-collected cards restating state already on the task that failed.
Net **-1015 lines**.

## Why

**Automated recovery follow-ups.** `createAutomatedFollowup` and its
dedup engine (289 lines of signature matching, 1h recurrence
rate-limiting, 24h supersedes windows) existed to file recovery cards
for verification-cap and merge-conflict give-ups. In both cases the
parent is *already* parked `failed` with a descriptive `error` and a log
entry carrying the failing command, branch, and output — the card was a
second copy of that.

**Meta-task auto-archive.** The sweeps that garbage-collected those
cards were worse than redundant: the regex classifier matched ordinary
feature work, and its positional fallback bound cards to unrelated
tasks, so **live work could be archived**.

They are removed together, because the auto-archive sweeps only existed
to clean up after the follow-up engine.

## What changed

### Deleted
- `packages/engine/src/verification-followup-dedup.ts` in full —
`createAutomatedFollowup`, `decideAutomatedFollowup`,
`AutomatedFollowupKind`, `computeVerificationFailureSignature`,
`extractFailingTestFiles`.
- `findActiveRecoveryFollowUp` — dead code, defined and never called
(`tsc` independently flagged it `6133 declared but its value is never
read`).
- The meta-task auto-archive sweeps `autoArchiveResolvedMetaTasks` /
`autoArchiveStalledMetaTasks` and helpers `classifyMetaTask` /
`resolveMetaTargetTaskId` / `computeMetaChainDepth` / `archiveMetaTask`
/ `evaluateMetaAutoArchiveGuards`, plus settings
`metaTaskStallAutoCloseMs` and `metaTaskActiveExecutionGraceMs`.
- Run-audit types `task:auto-archived-meta-resolved`,
`task:auto-archived-meta-stalled`,
`task:auto-archive-meta-resolved-skipped`,
`task:auto-archive-meta-stalled-skipped`,
`verification:followup-created`, `verification:followup-deduped`.

The two signature helpers were **deleted rather than relocated** — once
the three call sites went they were provably unreachable:
`buildVerificationFailureSignature` had exactly one caller, and it was
the only caller of `extractFailingTestFiles`.

### Call sites 1 and 2 — park kept, card dropped
Verification-cap and merge-conflict give-ups keep their park, audit
event, operator comment, and log entry. Site 1's `error` string was
reworded off `"See follow-up task for investigation."` (no follow-up
will exist) to carry the guidance itself. `autoResolveDisabled` was
**kept** — it still drives the outer park guard and the `reason` string;
only the inner branch that guarded card creation is gone.

### Call site 3 — autostash orphan, replaced not deleted
This one is a genuine data-loss guard, so it keeps a durable trail. A
`live`-classified orphan is a merger stash holding **real uncommitted
work**, and unlike sites 1–2 there is no parked parent — the parent may
already be `done` and merged, so nothing else on the board would ever
mention the stash.

The card is replaced by a `logEntry` **and** an `addTaskComment` on the
parent, preserving every fact the old description carried: the sha,
`record.label` (the handle `git stash` recovery needs),
`record.detectedByTaskId`, and `sourcePhase`. New truthful run-audit
event `task:autostash-orphan-live-detected` replaces the borrowed
`verification:followup-*` name, with ids/outcomes-only metadata per
AGENTS.md.

### Kept unchanged: the two real product features
Eval follow-ups (`eval-followups.ts`) and PR-comment follow-ups
(`pr-comment-handler.ts`) only borrowed the shared engine for its dedup
pass. Both keep their exact behavior, column, priority, `sourceType`,
and log lines, with dedup inlined as a `listTasks` scan on
`suggestionId` / `prNumber` respectively. Both fail open (create) if the
listing throws, matching the old engine.

## Test changes — read this one

Two tests asserted the *deleted* engine's rate-limited `"[verification
recurrence]"` logEntry. Those assertions were removed, **not loosened**:
both tests still assert no duplicate card is created, and the eval test
still asserts the existing id is reported back. No coverage of surviving
behavior was weakened. The three `meta-*` test files were deleted along
with the sweeps they covered.

## Verification

```
$ pnpm test:gate
 Test Files  2 passed (2)     Tests   10 passed (10)    # core
 Test Files  16 passed (16)   Tests  299 passed (299)   # engine-core
 Test Files  1 passed (1)     Tests   70 passed (70)    # ci-shape
GATE_EXIT=0

$ pnpm --filter @fusion/engine --filter @fusion/core exec tsc --noEmit -p tsconfig.json
TSC_EXIT=0   (no output)
```

Plus a file-scoped run over the touched surfaces (`eval-followups`,
`pr-comment-handler`, `merger-autostash-orphan-surface`,
`merger-autostash-cleanup`, `run-audit`, `run-audit-secret-taxonomy`,
`project-engine`, `project-engine-manager`): **213/213 passed**.

A repo-wide grep confirms no surviving references to any deleted symbol,
module, or audit event.

🤖 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**
* Failed tasks now retain recovery and verification details directly on
the original task instead of generating separate follow-up cards.
* Live autostash issues now preserve stash information in task comments
and activity logs.
* Existing evaluation and pull-request follow-ups continue to be reused
when appropriate.

* **Changes**
  * Removed automatic archival of meta-tasks.
  * Removed obsolete meta-task timing settings.

* **Documentation**
* Updated architecture and settings documentation to reflect these
workflow changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 22:38:58 -07:00
Phil Larson
d19c82b33b test(dashboard): align modal and locale fixtures (#2460)
## Summary
- query Task Detail modal assertions from the document after its
FloatingWindow portal migration
- sync secondary locale catalogs with the current English key structure

## Test plan
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/TaskDetailModal.attachments-and-tabs.test.tsx
--silent=passed-only --reporter=dot`
- `pnpm i18n:status`
- `pnpm --filter @fusion/dashboard build`

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

* **Documentation**
* Added missing localization entries for workflow selection/help text,
action-based report targeting, agent tool output limit hints,
auto-update/restart messaging, and task refinement status labels across
Spanish, French, Korean, Simplified Chinese, and Traditional Chinese.

* **Tests**
* Updated task detail modal tests to query the correct rendered document
root so portal/modal content is asserted reliably.
* Minor test formatting adjustments to keep assertions consistent
without changing coverage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-26 22:36:09 -07:00
Phil Larson
11887625f1 test(dashboard): align touch fixture with FloatingWindow (#2459)
## Summary
- update the touch-resize browser fixture assertions for New Task's
FloatingWindow migration
- verify the shared nine touch targets, resize handle ID, and unified
geometry persistence key

## Test plan
- `corepack pnpm --filter @fusion/dashboard build`
- `corepack pnpm --filter @fusion/dashboard exec vitest run --project
dashboard-browser-touch --silent=passed-only --reporter=dot
src/__tests__/task-modal-touch-resize-browser.test.ts`
- `corepack pnpm check:changesets`


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

* **Tests**
* Updated tablet touch-resize coverage for task-detail to use the shared
southeast resize handle and corrected expected hit-target counts.
* Refreshed hit-target detection and drag target assertions after
switching the test surface to the task-detail/floating window layout.
* Updated persistence/geometry validations to compare the stored unified
width/height against the resized panel dimensions.
* Reworked desktop vs tablet assertions, including tighter overlay
padding checks and standardized shared resize-handle sizing
expectations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-26 22:35:53 -07:00
Phil Larson
232e17d1cb test(engine): complete runtime logger mock (#2458)
## Summary
- add the missing `debug` method to the runtime-resolution logger mock
- prevent logger calls from short-circuiting runtime selection
assertions

## Test plan
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/runtime-resolution.test.ts`
- `pnpm --filter @fusion/engine typecheck`
- `pnpm check:changesets`

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

* **Tests**
* Updated the runtime-resolution test suite’s mocked logger to also
support debug-level messages, alongside existing log, warn, and error
handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-26 22:35:28 -07:00
Phil Larson
159fbbe9f8 fix(i18n): restore auto-update locale parity (#2456)
## Summary
- restore the two auto-update settings keys in all five secondary locale
catalogs
- keep generated locale structure aligned with the authored English
catalog

## Test plan
- `pnpm i18n:status`


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

* **Localization**
* Added new settings text keys for “automatic updates and restart” (and
its help text) in Spanish, French, Korean, Simplified Chinese, and
Traditional Chinese.
* The new values are placeholders (currently empty), so the UI may show
missing/blank text until translations are completed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-26 22:34:39 -07:00
Victor Canô
d6c917d726 feat(dashboard): add view and settings-section enumeration API (#2453)
## Summary

Gives external integrations (command palettes, plugin launchers,
alternate dashboard shells) a supported way to **discover the host UI**
— instead of hardcoding the dashboard's view ids, labels and settings
search terms and hand-syncing them on every release. This is the
read-only metadata slice of the "constrained by a stable host context
and API client" idea in
`docs/proposals/2026-07-01-dashboard-theme-plugin-system.md`, and the
follow-on to #2415 (theme tokens + overlay layering).

Two additions, both inert unless called:

| Endpoint | Returns |
|---|---|
| `GET /api/views` | Every registered built-in view id, in dashboard
order — `id`, English `label`, plus optional i18n `labelKey`, legacy
`aliases` and `internal` flag. |
| `GET /api/settings/sections` | Selectable Settings sections — `id`,
`label`, `labelKey`, `scope`, `group`, `keywords`, `searchableKeys`,
`advanced`. |

Both are read-only, return static project-independent metadata, take no
project id, and are mounted inside `createApiRoutes` so they sit behind
exactly the same `/api` authentication as every other dashboard route —
no more, no less.

## What actually changed — one source of truth

The endpoints are the small part. The core of the diff is **collapsing
duplicated UI metadata into two shared registries that now drive both
the dashboard UI and the API**:

- `packages/dashboard/src/shared/dashboard-views.ts` — canonical view
ids + English labels + i18n keys + legacy aliases.
- `packages/dashboard/src/shared/settings-sections.ts` — canonical
settings sections + scope/group/search metadata, with `group` and
`advanced` derived from the list's own structure.

`LeftSidebarNav`, `SettingsModal` and `useViewState` were rewritten to
consume those registries instead of carrying their own copies (net
**−230 lines** in `SettingsModal` alone). Edit the registry and the
rendered UI and the API move together.

## Drift protection

Being precise about what each test can and cannot catch, because
"no-drift" claims are easy to overstate:

- `left-sidebar-nav-registry-parity.test.tsx` — the one test that
catches drift the registry does not already determine. It **renders**
the sidebar with a recording `t()` spy and pins each entry's translation
key and English fallback to the registry (the sidebar still hardcodes
its keys). It also asserts the rendered destination count equals the
enrolled id list, so a newly added sidebar view fails until it is
enrolled.
- `ui-metadata-sync.test.ts` — pins the Settings navigation list,
advanced-visibility set, persisted view list, reset-key registry and
both endpoint payloads to the registries. Since those consumers are now
*derived* from the registries, these assertions mainly guard against a
future consumer **re-hardcoding** its own copy. Two of them do stand on
their own: each section's served `group` is pinned to the group header
it actually renders under, and no published `labelKey` may resolve to a
non-leaf i18n node.
- `register-ui-metadata-routes.test.ts` — drives the real Express router
and asserts each endpoint serves the registry payload verbatim, with no
filtering or reshaping.
- Exactly two **existing** tests are updated, both for the same reason:
they asserted that `SettingsModal.tsx`'s *source text* contains a
section literal that now lives in the registry.
`VoiceInputSection.modal-visibility.test.tsx` now asserts Voice Input's
Basic-mode contract against `SETTINGS_SECTION_METADATA`, and
`mcp-documentation.test.ts` reads the registry for the two MCP section
ids. No other existing test in the package changes.

## Design notes / decisions for review

- **`GET /api/views` returns the full registry, not the live menu.** It
includes flag-gated / experimental ids and `internal` (non-navigable)
destinations; reachability depends on flags and plugins this endpoint
does not evaluate. Documented as "known view ids", not "visible nav
entries".
- **`labelKey` is optional and best-effort; `label` is the guarantee.**
A `labelKey` is published only where the dashboard itself renders that
view's title through it. `graph` (labelled from a plugin manifest) and
the internal `task-detail` carry none rather than advertise a key that
resolves to nothing — and `task-detail` in particular must not point at
`taskDetail.title`, which is an occupied i18n *namespace* whose lookup
returns an object rather than falling through to a default. A guard test
now enforces that. Separately, a few published keys (`nav.ideation`,
`nav.importTasks`, `nav.automations`, `pr.view.title`) are the
dashboard's real keys but aren't in the shipped catalogs yet because the
host supplies their English inline; the docs say plainly that consumers
must fall back to `label`.
- **`keywords` / `searchableKeys` are explicitly non-contractual.**
`searchableKeys` exposes the raw i18n translation-key strings backing a
section's searchable copy; values, ordering and presence may change
between releases. Documented as best-effort search hints, never stable
identifiers.
- **Migration is deliberately partial.** The desktop sidebar, Settings
navigation and persisted view list now come from the registries;
`Header.tsx` and the mobile More sheet still hardcode a few of the same
labels. They can still drift from what `GET /api/views` reports;
converting them is left to a follow-up so this diff stays reviewable.
- **No project scoping, deliberately.** The proposal doc rightly pushes
plugin traffic through a project-scoped client — these two endpoints are
the exception that proves the rule: they return static registry metadata
that is identical for every project, so threading a `projectId` would
imply a scoping guarantee that does not exist here. They never touch
`getScopedStore` / `TaskStore`.
- **Two endpoints rather than one `/api/ui-metadata` envelope.** Views
and Settings sections are independent registries with different
consumers, and `/settings/sections` sits naturally beside the existing
`/settings/*` routes. A consumer that only needs navigation doesn't pay
for settings metadata.
- **The registry extraction ships with the endpoints rather than as a
separate PR.** The registries *are* the mechanism that keeps the API
honest — split apart, the first half is a refactor with no observable
effect and the second can't land without it.
- **Placement:** `packages/dashboard/src/shared/` is a new directory,
and these are the first *production* `app/ → src/` imports in the
package (today the only one is in `ProviderIcon.test.tsx`). They sit
under `src/` because `src/`'s tsconfig cannot import `app/`, so a module
both sides consume has nowhere else to go; both registries are
dependency-free data leaves, and `vite build` plus
`check-no-node-only-core-imports-in-dashboard` confirm the client bundle
is unaffected. The considered alternative was `packages/core/src` behind
the `dashboard-browser-safe-core-modules.json` allowlist, where
`mobile-nav-primary-items.ts` keeps a destination→labelKey table — these
stayed out of `core` because they are dashboard-owned UI ids, and
because the two tables describe different surfaces (core mirrors the
mobile nav's `nav.skills`/`nav.settings`; this registry mirrors the
desktop sidebar's `header.skillsView`/`header.settings`).
- Ships a `@runfusion/fusion` **minor** changeset (`category: feature`).

Happy to adjust any of the above — shape, placement, or dropping
`searchableKeys` — if you'd rather it landed differently.

## Verification

- Rebased onto `main@26dcccb7c`. Two conflicts, both resolved by
absorbing upstream's work rather than reverting it:
- `SettingsModal.tsx` — upstream's `voice-input` section (and the
`FNXC:VoiceInput` decision comment explaining it stays out of the
advanced-only set) moved into the registry. The registry's section list
is byte-identical to `main`'s `SETTINGS_SECTIONS` (45/45 entries, all
fields), and the registry-derived `ADVANCED_SETTINGS_SECTION_IDS` is
identical to `main`'s hardcoded set (19/19, same order) — both verified
mechanically, not by eye. Upstream's `RUNTIME_*`
hide-uninstalled-runtimes sets are untouched.
- `routes/README.md` — the `mount-sequence` list regenerated from
`CREATE_API_ROUTES_REGISTRAR_MOUNT_SEQUENCE`, so `registerVoiceRoutes`
and `registerUiMetadataRoutes` are both in place and the contract test
passes.
- `DASHBOARD_VIEWS` covers exactly `main`'s `BuiltInTaskView` union,
aliases included, and `BUILT_IN_TASK_VIEWS` reproduces `main`'s 27-entry
array in order (`devserver` still preceding `dev-server` for the
migration path).
- Every one of the 20 sidebar labels the refactor rewrote was checked to
be byte-identical to `main`'s hardcoded fallback, and every `FNXC:`
decision comment displaced by the move was accounted for — all 75 in
`SettingsModal.tsx` and all 11 in `useViewState.ts` survive, relocated
onto the registry entries they document.
- The full `dashboard-app` + `dashboard-api` suites were run at this
commit (**20,706 passing**) and again on unmodified `main@26dcccb7c`,
and the failing-file sets compared: **every file that fails here also
fails on `main`** — nothing regresses. The overlap is environment-driven
(Postgres-backed `*.pg.test.ts`, tests needing built `dist` artifacts,
and `SettingsModalNodeRouting.test.tsx`'s `No "fetchSystemInfo" export
is defined on the "../../api" mock`), none of it touched by this change.
- `tsc --noEmit` clean for both dashboard projects, `eslint` clean on
every changed file, and `vite build` of the client bundle succeeds (the
two pre-existing `@fusion-plugin-examples/claude-runtime` /
`playwright-core` module-resolution errors reproduce on unmodified
`main`).
- Repo gate scripts pass: `check-changeset-format`,
`check-routes-modular`, `check-no-node-only-core-imports-in-dashboard`,
`check-no-cwd-relative-dashboard-test-reads`, `check-mock-completeness`.
- The three new assertions were mutation-tested rather than assumed
load-bearing: breaking the registry's `group` derivation, dropping an
enrolled sidebar id, and re-pointing `task-detail` at the
`taskDetail.title` namespace each make their test fail.
- Local CodeRabbit review over two passes: 3 minor findings, all
addressed (parity projection missing `group`; route tests asserting
partial instead of exact payloads; the `labelKey` guard not covering the
settings registry).


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

## Summary by CodeRabbit

* **New Features**
* Added authenticated, read-only APIs for discovering dashboard views
and selectable Settings sections.
* Added dashboard view metadata, including labels, aliases, internal
status, and translation keys.
* Added Settings metadata with grouping, scope, advanced status, and
search-related information.
  * Updated navigation and Settings UI labels to use shared metadata.

* **Documentation**
  * Documented the new metadata endpoints and integration guidance.

* **Bug Fixes**
* Added safeguards and automated checks to keep UI navigation and API
metadata synchronized.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-26 22:34:18 -07:00
Phil Larson
00778cb10f test(engine): refresh shellout allowlist (#2451)
## Summary
- refresh the engine synchronous-shellout allowlist after recent
self-healing and executor source additions shifted audited call sites
- keep the guard's path, primitive, and signature checks unchanged

## Test plan
- `corepack pnpm --filter @fusion/engine exec vitest run
src/__tests__/engine-no-blocking-shellout.test.ts --silent=passed-only
--reporter=dot`


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

* **Tests**
* Updated static validation allowlists for audited synchronous shell
command operations so matching stays accurate with the latest call-site
locations.
* Kept safeguards that prevent unapproved blocking shell commands from
passing validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-07-26 22:33:21 -07:00
gsxdsm
f1a2d9ae1f FN-8626: validate committed test timing snapshot
Add an automated guard that keeps CI test-sharding timings usable.

- Validate snapshot structure, freshness, and recorded test-file paths.
- Confirm planning loads the snapshot and shard dry-runs use it without stale warnings.

Files changed:
 scripts/__tests__/ci-test-shard-timings.test.mjs | 50 +++++++++++++++++++++++-
 1 file changed, 49 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8626

Fusion-Task-Lineage: ada6525f-8dcc-4494-8586-3aa2e41618f3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 20:42:30 -07:00
gsxdsm
3f33cb000f feat: per-origin workflow selection + feedback-derived refinement titles
Two task origins had no workflow picker in front of the operator and always
inherited the project default: `fn task create` (CLI + the `fn_task_create`
agent tool) and refinement tasks. Add a Project General setting for each, where
blank/unset means "Selected workflow" (the operator's current Board lane,
falling back to the project default) and a concrete id pins that origin.

Because the Board lane lives in browser localStorage, non-browser callers could
not resolve "Selected workflow" at all. `boardSelectedWorkflowId` mirrors the
lane into project settings so they can. Note this makes the mirrored lane
project-scoped: two operators on one project share it, last switch wins. The
Board never reads it back, so the only effect is which workflow a newly created
task inherits.

Resolution is `TaskStore.resolveOriginWorkflowOverrideId(origin)`: pinned
setting -> mirrored lane -> `undefined` to inherit each caller's existing
default-workflow path unchanged. A deleted or fragment id degrades to inherit
rather than throwing, so a stale settings value can never break task creation.
An explicit `workflow_id` argument to `fn_task_create` still wins.

Separately, a refinement is now titled by the operator's own feedback via the
shared `deriveFallbackTaskTitle`, not `Refinement: <parent title>`. Ten
refinements of one task previously rendered ten identical titles, so the board
could not tell them apart while the text saying what each one asked for sat in
the description. Provenance moves to a `Refines <id>` card chip alongside the
existing detail-view parent link and dependency edge.

Verified: merge gate (299 tests), lint, full build, and typecheck for core, CLI,
and dashboard all pass. New coverage: origin resolution across both origins and
the full precedence ladder, the two settings pickers, the board-lane mirror,
refinement titling (including sibling distinctness), and the card chip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 20:33:25 -07:00
gsxdsm
e4cb957b57 FN-8619: migrate modal geometry to FloatingWindow
Unify migrated modal geometry and responsive behavior under FloatingWindow.

- Cover Agent Detail and GitHub Import floating-window interaction and persistence contracts.
- Preserve unique Agent Detail labels, mouse-only backdrop dismissal, and tablet touch layouts.
- Update the dashboard modal migration inventory.

Files changed:
 docs/dashboard-modal-inventory.md                  |   6 +-
 .../dashboard/app/components/AgentDetailView.css   |  49 +++++-----
 .../dashboard/app/components/AgentDetailView.tsx   |   6 +-
 .../dashboard/app/components/FloatingWindow.css    |  10 +-
 .../dashboard/app/components/TaskDetailModal.css   |  15 +++
 .../AgentDetailView.floating-window.test.tsx       | 102 +++++++++++++++++++++
 .../__tests__/GitHubImportModal.test.tsx           |  58 ++++++++++++
 ...etailModal.responsive-and-dependencies.test.tsx |  24 +++++
 8 files changed, 241 insertions(+), 29 deletions(-)

Fusion-Task-Id: FN-8619

Fusion-Task-Lineage: efa95e41-b12e-4e5d-aeb2-65b43c2dd8e0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 20:04:33 -07:00
gsxdsm
43160a7aae FN-8621: migrate complex modals to FloatingWindow
Unify complex dashboard modal presentation under persisted FloatingWindow geometry.

- Migrate Create Room, Task Detail, Agent Detail, and GitHub Import modal presentations.
- Preserve documented embedded and docked exceptions, dismissal behavior, and nested scrolling.
- Add presentation-contract coverage and publish dashboard guidance and changesets.

Files changed:
 ...n-8619-resize-persist-modals-floating-window.md |   7 ++
 .changeset/fn-8621-create-room-floating-window.md  |   7 ++
 docs/dashboard-guide.md                            |  16 +++-
 docs/dashboard-modal-inventory.md                  |  12 +--
 .../dashboard/app/components/AgentDetailView.css   |  16 +---
 .../dashboard/app/components/AgentDetailView.tsx   | 102 ++++++++++++++++-----
 .../dashboard/app/components/CreateRoomModal.css   |  19 +++-
 .../dashboard/app/components/CreateRoomModal.tsx   |  57 ++++++++----
 .../dashboard/app/components/FloatingWindow.css    |  13 ++-
 .../dashboard/app/components/FloatingWindow.tsx    |  15 +++
 .../dashboard/app/components/GitHubImportModal.css |  11 +--
 .../dashboard/app/components/GitHubImportModal.tsx |  40 ++++++--
 .../dashboard/app/components/TaskDetailModal.css   |  52 +----------
 .../dashboard/app/components/TaskDetailModal.tsx   |  58 ++++++------
 .../__tests__/AgentDetailView.core.test.tsx        |   2 +-
 .../AgentDetailView.mobile-scroll.test.tsx         |   6 +-
 .../components/__tests__/CreateRoomModal.test.tsx  |  62 +++++++++++--
 .../components/__tests__/FloatingWindow.test.tsx   |   1 +
 .../__tests__/GitHubImportModal.test.tsx           |   8 +-
 ...etailModal.responsive-and-dependencies.test.tsx |  77 +++++++---------
 .../__tests__/modal-presentation-contract.test.tsx |  74 +++++++++++++++
 .../dashboard/app/hooks/useEmbeddedPresentation.ts |   2 +-
 .../dashboard/app/hooks/useModalResizePersist.ts   |   5 +
 23 files changed, 441 insertions(+), 221 deletions(-)

Fusion-Task-Id: FN-8621

Fusion-Task-Lineage: 04b6f3fe-d527-4a21-a0cb-489eb20f5e91

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 19:52:32 -07:00
gsxdsm
7715a83e6f FN-8624: fix task detail activity right padding
Keep Task Detail activity insets symmetric while reserving overlay clearance only for first-row content.

- Move Live/Feed overlay clearance from the activity container to first-row elements.
- Preserve responsive overlay spacing and the full-width Interventions timeline.
- Cover modal, pop-out, embedded, and responsive activity layouts with updated tests.
- Add a patch changeset for the Task Detail padding fix.

Files changed:
 .changeset/fn-8624-task-detail-inset.md            |  7 ++++
 .../dashboard/app/components/TaskDetailModal.css   | 41 ++++++++++------------
 .../TaskDetailModal.attachments-and-tabs.test.tsx  |  6 ++--
 .../__tests__/TaskDetailModal.css.test.ts          | 21 ++++++-----
 ...etailModal.responsive-and-dependencies.test.tsx | 25 +++++++------
 5 files changed, 54 insertions(+), 46 deletions(-)

Fusion-Task-Id: FN-8624

Fusion-Task-Lineage: 578cfe86-3021-421d-841b-2670efc10a5d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 19:45:47 -07:00
gsxdsm
d3c1955866 FN-8625: remove heartbeat controls from agent org charts
Keep org charts read-only for heartbeat configuration while preserving controls on board and list surfaces.

- Remove heartbeat toggle props, markup, and styling from org chart nodes
- Cover both org chart layouts and board heartbeat availability with regression tests
- Add a patch changeset for the dashboard behavior fix

Files changed:
 .../fn-8625-remove-org-chart-heartbeat-toggle.md     |  7 +++++++
 packages/dashboard/app/components/AgentsView.css     |  7 -------
 packages/dashboard/app/components/AgentsView.tsx     | 14 +++++---------
 .../__tests__/AgentsView.orgchart.test.tsx           | 20 ++++++++++++++++++++
 .../app/components/__tests__/AgentsView.test.tsx     | 20 +++++++++++---------
 5 files changed, 43 insertions(+), 25 deletions(-)

Fusion-Task-Id: FN-8625

Fusion-Task-Lineage: 454c05e6-fe31-486b-975a-80ba5e8c518c

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 19:18:53 -07:00
gsxdsm
89b4621a93 FN-8607: migrate shared modals to FloatingWindow
Standardize agent, onboarding, and utility modal behavior on the shared FloatingWindow contract.

- Migrate modal hosts to shared geometry, sheet, dismissal, and focus behavior.
- Make FloatingWindow the sole owner of modal ARIA semantics and protect portal-safe dismissal surfaces.
- Cover migrated modal contracts and document the responsive sheet requirements.

Files changed:
 docs/dashboard-guide.md                            |   6 +-
 packages/dashboard/app/components/AgentGenerationModal.tsx        |   8 +-
 packages/dashboard/app/components/AgentImportModal.tsx  |   4 +-
 packages/dashboard/app/components/AgentListModal.tsx    |   4 +-
 packages/dashboard/app/components/AgentOnboardingModal.tsx        |   4 +-
 packages/dashboard/app/components/DockerNodeOnboardingModal.tsx   |   4 +-
 packages/dashboard/app/components/ExperimentalAgentOnboardingModal.tsx |   4 +-
 packages/dashboard/app/components/FloatingWindow.css    |  36 ++++--
 packages/dashboard/app/components/FloatingWindow.tsx    |  12 ++
 packages/dashboard/app/components/MilestoneSliceInterviewModal.tsx |   2 +-
 packages/dashboard/app/components/NativeShellOnboardingModal.tsx  |   2 +-
 packages/dashboard/app/components/SetupWizardModal.tsx  |   3 +-
 packages/dashboard/app/components/SubtaskBreakdownModal.tsx       |   2 +-
 packages/dashboard/app/components/__tests__/AgentModals.floatingWindow.test.tsx | 134 ++++++++++++++++++---
 packages/dashboard/app/components/__tests__/OnboardingModals.floatingWindow.test.tsx | 128 +++++++++++++++++---
 packages/dashboard/app/components/__tests__/UtilityModals.floatingWindow.test.tsx | 124 +++++++++++++++++--
 packages/dashboard/app/components/__tests__/migratedModalFixtures.tsx |  59 ++++++---
 packages/dashboard/app/components/__tests__/modalFloatingWindowContract.test.tsx |  20 ++-
 18 files changed, 458 insertions(+), 98 deletions(-)

Fusion-Task-Id: FN-8607

Fusion-Task-Lineage: 11ba6a9c-ce89-4358-83c4-a15ef15b6128

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 18:57:02 -07:00
gsxdsm
bdfa5186c4 FN-8622: capture Chromium CDP modal screenshots
Add assertion-guarded CDP evidence for migrated Agent List and Setup Wizard modal touch geometry.

- Mount production modal surfaces in the resize browser fixture
- Verify tablet resize and drag behavior plus true-phone sheet behavior
- Store the captured phone and tablet screenshots as regression evidence
- Extend mobile sheet styling to the migrated modal hosts

Files changed:
 .../dashboard/app/components/FloatingWindow.css    |   8 ++
 .../app/task-modal-touch-resize-e2e-fixture.tsx    |  17 +++-
 .../fn-8607/phone-fullscreen-sheet.png             | Bin 0 -> 17823 bytes
 .../fn-8607/tablet-agent-list-after.png            | Bin 0 -> 21489 bytes
 .../fn-8607/tablet-setup-wizard-after.png          | Bin 0 -> 81964 bytes
 .../task-modal-touch-resize-browser.test.ts        |  99 +++++++++++++++++++++
 6 files changed, 120 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-8622

Fusion-Task-Lineage: 3fabd950-bac0-47f4-9649-08709db31e02

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 18:43:18 -07:00
gsxdsm
ba24a530ef FN-8620: migrate dashboard modals to FloatingWindow
Standardize dashboard modal geometry and interaction behavior through the shared FloatingWindow contract.

- Migrate New Task, right-dock expansion, terminal, onboarding, and utility modals to FloatingWindow.
- Expand shared window resizing, positioning, accessibility, and contract coverage.
- Document the modal migration inventory and publish changesets.

Files changed:
 .changeset/fn-8607-floating-modal-contract.md      |   7 +
 .changeset/fn-8620-bespoke-geometry-modals.md      |   7 +
 docs/dashboard-guide.md                            |  47 ++
 docs/dashboard-modal-inventory.md                  |   6 +-
 .../app/components/AgentGenerationModal.tsx        |  14 +-
 .../dashboard/app/components/AgentImportModal.tsx  |   7 +-
 .../dashboard/app/components/AgentListModal.tsx    |   8 +-
 .../app/components/AgentOnboardingModal.tsx        |   6 +-
 .../app/components/DockerNodeOnboardingModal.tsx   |  14 +-
 .../ExperimentalAgentOnboardingModal.tsx           |   6 +-
 .../dashboard/app/components/FloatingWindow.css    |  21 +
 .../dashboard/app/components/FloatingWindow.tsx    |  62 ++-
 packages/dashboard/app/components/MailboxModal.tsx |  14 +-
 .../components/MilestoneSliceInterviewModal.tsx    |  12 +-
 .../app/components/NativeShellOnboardingModal.tsx  |   6 +-
 packages/dashboard/app/components/NewTaskModal.css | 196 +------
 packages/dashboard/app/components/NewTaskModal.tsx | 593 +++++++--------------
 packages/dashboard/app/components/RightDock.css    |  84 +--
 .../app/components/RightDockExpandModal.tsx        | 333 ++----------
 .../dashboard/app/components/SetupWizardModal.tsx  |  20 +-
 .../app/components/SubtaskBreakdownModal.tsx       |   6 +-
 .../dashboard/app/components/TerminalModal.css     | 120 +----
 .../dashboard/app/components/TerminalModal.tsx     | 321 ++---------
 .../__tests__/AgentModals.floatingWindow.test.tsx  |  26 +
 .../components/__tests__/FloatingWindow.test.tsx   |  27 +-
 .../app/components/__tests__/NewTaskModal.test.tsx |  66 +--
 .../OnboardingModals.floatingWindow.test.tsx       |  20 +
 .../app/components/__tests__/RightDock.test.tsx    |  18 +-
 .../components/__tests__/TerminalModal.test.tsx    |  61 +--
 .../UtilityModals.floatingWindow.test.tsx          |  18 +
 .../components/__tests__/migratedModalFixtures.tsx |  25 +
 .../__tests__/modalFloatingWindowContract.test.tsx |  30 ++
 32 files changed, 726 insertions(+), 1475 deletions(-)

Fusion-Task-Id: FN-8620
Fusion-Task-Lineage: 2a912260-3292-4b13-bcf0-9de5a3df8ccd
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 18:35:10 -07:00
gsxdsm
256c64a7bd chore(release): v0.74.0-beta.5
Version bump via changesets.
2026-07-26 18:11:47 -07:00
gsxdsm
034827f251 FN-8623: restore CDP touch geometry test lane
Restore a dedicated Chromium CDP lane for dashboard touch-geometry coverage.

- Add an opt-in touch-geometry test command and isolated Vitest project.
- Keep the browser-dependent spec out of deep and quality backfill collection.
- Document browser discovery, port, and single-collection requirements.

Files changed:
 docs/testing.md                                    | 10 ++-
 packages/dashboard/package.json                    |  1 +
 .../__tests__/dashboard-test-config-guard.test.ts  | 71 +++++++++++++++++++++-
 .../task-modal-touch-resize-browser.test.ts        |  5 ++
 packages/dashboard/vitest.config.ts                | 28 ++++++++-
 scripts/lib/test-inventory-spec.json               |  3 +-
 6 files changed, 114 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-8623

Fusion-Task-Lineage: eafd7497-9302-49a4-8e9e-aa93c9f56a6f

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-26 18:04:28 -07:00
gsxdsm
73b2a32e2b test: backfill dashboard test mocks for new api and viewport exports
Concurrent dashboard work added runtime exports that hardcoded module
mocks did not expose, so any suite rendering the affected component threw
"No <export> is defined on the mock". Adds the missing `../api` exports
(system-info probe, update install/restart, cloudflared, provider key and
login helpers, git remotes/branches) and the `isTabletTouchViewport`
viewport helper across the 26 suites that mock those modules.

Verified: all 26 files pass (1012 tests).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 18:00:07 -07:00
gsxdsm
beebd270bd fix: make Queued to plan / Ready badges agree with the planning lane
TaskCard inferred "unplanned" from steps.length === 0 while triage's
todo-discovery and the scheduler's dispatch filter both decide from
PROMPT.md seed-ness, so the badges disagreed with the engine in both
directions: a real spec that parsed to zero steps read as "Queued to
plan" while the scheduler already treated it as a WIP-slot candidate, and
a re-seeded card still carrying old steps read as "Ready" while triage
was about to plan it. Either way the badge sent operators to the wrong
cap.

Adds the shared isTaskAwaitingPlanning predicate (replan park, missing
spec, seed-vs-real content) used by both triage's discovery and a new
best-effort `awaitingPlanning` enrichment on GET /api/tasks. TaskCard
derives both badges from that one value — strict complements — and keeps
the step count only as a fallback for SSE payloads and older servers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 17:48:46 -07:00
gsxdsm
01b27b13f1 test(dashboard): add per-character typing coverage to uncovered field surfaces
Sweeping the other views for the FN-8606 typing bug found no further breakage,
but it did expose why the bug shipped: almost all field coverage uses
fireEvent.change, which sets a value in one shot on a node it already holds and
never needs the input to stay mounted. A remount is invisible to it.

Adds expectStableTyping (types character by character via userEvent, then asserts
DOM node identity, accumulated value, and retained focus) and applies it to the
uncovered surfaces: the FN-8606-migrated AddNode, ConnectNode, NodeDetail,
Scripts, and WorkflowAddStep modals, plus the board's QuickEntryBox composer and
SubtaskBreakdownModal title editing. All pass — this is a detection floor, not a
fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 17:42:39 -07:00
gsxdsm
d193dcdbb0 chore(lint): ban React components declared inside another component
Adds fusion-react/no-nested-component-definitions, a custom rule in the house
style of the existing detached-spawn guard. A component declared in render is a
new element type every render, so React remounts its subtree on each parent
update and destroys focus, scroll, and local state.

This pattern shipped three times without review or tests catching it: FN-8606's
ModalShell left Planning Mode and Settings untypable, and MailboxModal's
ReplyContextExpandable collapsed expanded reply rows. Tests missed it because
fireEvent.change sets a value without needing the node to stay mounted.

The rule reports PascalCase functions (including memo()/forwardRef()-wrapped)
that return JSX and are declared inside another JSX-returning function.
Lowercase render helpers are deliberately allowed — they are the sanctioned fix.
Escape hatch: // nested-component-allowlist: <reason>.

Scoped to production .tsx, with a vitest guard for the rule itself. Hoists the
two pre-existing violations (ProviderStatusBadge, GitHubStatusBadge in
ModelOnboardingModal) to module scope so the rule lands clean at "error".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 17:34:45 -07:00