Skip to content

fix(merge-boundary): count optional-group pre-merge results as boundary proof - #5

Open
timoteo7 wants to merge 1 commit into
mainfrom
fix/merge-boundary-optional-group-proof
Open

timoteo7 wants to merge 1 commit into
mainfrom
fix/merge-boundary-optional-group-proof

Conversation

@timoteo7

Copy link
Copy Markdown
Owner

Problem

A card running builtin:coding whose only pre-merge workflow step results came from enabled optional steps was parked at merge-boundary-unproven — operator action required, even though every review had passed.

Measured on a live card (project proj_9ef728e7cc084681):

fact value
workflow_step_results exactly 2 rows
both rows phase="pre-merge", status="passed", source="optional-group"
steps plan-review, code-review
enabled_workflow_steps ["plan-review","code-review"]

evaluateWorkflowMergeBoundary filtered relevant results with result.source === "node", so those optional-group rows were invisible: hasRelevantNodeResult was false → blocker code no-node-result → the card never reached in-review.

Root cause

The graph writes pre-merge results with exactly two source values:

  • node — graph-authored node progress (workflow-graph-executor.ts, node-progress writes)
  • optional-group — an enabled optional step (e.g. the builtin Plan Review / Code Review groups)

The boundary proof only counted node, so any workflow shape whose pre-merge gate work happens in optional groups could never prove its boundary.

Fix

packages/engine/src/executor/evaluate-workflow-merge-boundary.ts

  • New exported predicate isGraphNativePreMergeResult(result): (source === "node" || source === "optional-group") && (phase ?? "pre-merge") === "pre-merge".
  • The proof uses it in place of the literal filter.

packages/engine/src/executor/workflow-merge-boundary-helpers.ts

  • shouldCompleteChecklistAtWorkflowMerge (proof-free branch) uses the same predicate, so the two engine sites cannot drift. Its production caller always passes a proof object today; sharing the predicate means a future caller that drops the proof argument cannot re-park an approved card.

Not loosened: a non-pre-merge (post-merge) result stays out of the proof; terminality (allResultsTerminal) stays mandatory; terminal foreach instance coverage (coverageComplete / missing-foreach-instances) stays mandatory; legacy compiled step results (no source) remain non-graph-native.

Tests

packages/engine/src/__tests__/executor-graph-boundary.test.ts (non-PG harness, merge-gate-friendly) — 5 new cases:

  1. Symptom: only two passed pre-merge optional-group results → boundary not blocked, proof resolved/complete, card moves to in-review.
  2. A pending optional-group result still blocks with non-terminal-node-result.
  3. A post-merge-only passed result still blocks with no-node-result.
  4. Unfinished foreach step instances still block with missing-foreach-instances.
  5. Both directions of the shouldCompleteChecklistAtWorkflowMerge fallback.

Red → green evidence (file-scoped, no DB):

# before the fix
Tests  4 failed | 10 passed (14)

# after the fix
pnpm --filter @fusion/engine exec vitest run src/__tests__/executor-graph-boundary.test.ts --silent=passed-only --reporter=dot
Test Files  1 passed (1)
     Tests  16 passed (16)

Also re-run green: executor-skip-bypass-taint.test.ts + merge-boundary-unproven-park.test.ts (13 passed).

Verification limits

  • File-scoped vitest only. No pnpm install, build, typecheck, or full suite (low-RAM host).
  • The PG-backed evaluator file executor-merge-boundary-foreach-proof.pg.test.ts was not run locally (its harness needs a local PostgreSQL instance; writing to the shared instance was out of scope for this change). Its fixtures all use source: "node", so its coverage is unaffected by the widening; the non-PG file covers both origins and runs in the merge gate.
  • pnpm check:changesets and pnpm check:fnxc-future-dates pass; scoped eslint reports no errors.

Changeset

.changeset/fix-merge-boundary-optional-group-proof.md — @runfusion/fusion patch.

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

ThreatCrush Security Scan

4586 finding(s)

HIGH/CRITICAL: 43 | MEDIUM: 4032 | LOW: 511

Severity Rule Location
HIGH secret-database-url .github/workflows/full-suite.yml:55
HIGH secret-generic-credential .github/workflows/full-suite.yml:56
HIGH secret-database-url .github/workflows/full-suite.yml:284
HIGH secret-generic-credential .github/workflows/full-suite.yml:285
HIGH secret-database-url .github/workflows/full-suite.yml:324
HIGH secret-generic-credential .github/workflows/full-suite.yml:325
HIGH secret-database-url .github/workflows/pr-checks.yml:221
HIGH secret-generic-credential .github/workflows/pr-checks.yml:222
HIGH secret-generic-credential .github/workflows/release.yml:522
HIGH secret-generic-credential .github/workflows/release.yml:524
HIGH secret-generic-credential .github/workflows/test-release.yml:445
HIGH secret-generic-credential .github/workflows/test-release.yml:447
HIGH secret-generic-credential docs/cli-reference.md:80
HIGH secret-generic-credential docs/signals-connectors.md:34
HIGH secret-generic-credential docs/signals-connectors.md:77
HIGH secret-generic-credential docs/signals-connectors.md:94
HIGH secret-generic-credential docs/signals-connectors.md:117
HIGH secret-generic-credential docs/signals-connectors.md:159
HIGH secret-generic-credential packages/cli/STANDALONE.md:71
HIGH secret-database-url packages/core/src/postgres/credential-redact.ts:12
HIGH secret-database-url packages/core/src/postgres/credential-redact.ts:30
HIGH secret-database-url packages/core/src/postgres/credential-redact.ts:31
HIGH secret-database-url packages/core/src/postgres/credential-redact.ts:102
HIGH secret-database-url packages/core/src/postgres/credential-redact.ts:103
HIGH secret-generic-credential packages/core/src/postgres/embedded-lifecycle.ts:843
HIGH secret-database-url packages/core/src/postgres/embedded-lifecycle.ts:1574
HIGH secret-database-url packages/core/src/postgres/pg-backup.ts:756
HIGH js-ssrf-outbound-request packages/dashboard/app/public/sw.js:651
HIGH js-ssrf-outbound-request packages/dashboard/app/public/sw.js:727
HIGH js-host-header-trust packages/dashboard/src/cli-session-ws.ts:81
HIGH js-host-header-trust packages/dashboard/src/cli-session-ws.ts:115
HIGH js-ssrf-outbound-request packages/dashboard/src/routes.ts:1858
HIGH js-host-header-trust packages/dashboard/src/server.ts:2689
HIGH js-host-header-trust packages/dashboard/src/server.ts:2714
HIGH js-host-header-trust packages/dashboard/src/server.ts:3025
HIGH js-host-header-trust packages/dashboard/src/server.ts:3193
HIGH secret-slack-webhook plugins/examples/fusion-plugin-notification/README.md:46
HIGH secret-database-url scripts/pg-test-server.mjs:200
HIGH secret-database-url scripts/pg-test-server.mjs:231
HIGH secret-database-url scripts/pg-test-server.mjs:241
HIGH secret-generic-credential scripts/sync-fusion-skill-tools.mjs:550
HIGH secret-generic-credential scripts/verify-windows-elevated-restricted.mjs:81
HIGH secret-generic-credential scripts/verify-windows-encoding-recovery.mjs:41
MEDIUM redos-nested-quantifier docs/agents.md:1710
MEDIUM insecure-temp-file packages/cli/src/__tests__/bin.test.ts:136
MEDIUM insecure-temp-file packages/cli/src/__tests__/dev-with-memory-lib.test.ts:33
MEDIUM insecure-temp-file packages/cli/src/__tests__/dev-with-memory-lib.test.ts:34
MEDIUM insecure-temp-file packages/cli/src/__tests__/dev-with-memory-lib.test.ts:35
MEDIUM insecure-temp-file packages/cli/src/__tests__/dev-with-memory-lib.test.ts:43
MEDIUM insecure-temp-file packages/cli/src/__tests__/dev-with-memory-lib.test.ts:48

…and 4536 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@timoteo7
timoteo7 force-pushed the fix/merge-boundary-optional-group-proof branch from 5c9604a to 55a7455 Compare September 25, 2026 04:15
@timoteo7

Copy link
Copy Markdown
Owner Author

Review notes for the audited SHA 55a7455706494f0bbae1d09be04e185574c802b8:

The merge-boundary change looks sound: graph-authored optional-group results are real pre-merge evidence, and the shared predicate retains the phase, terminal-result, and foreach-coverage requirements. Please preserve those checks and the focused regression tests.

The same PR also introduces integration-branch validation/materialization, a separate contract change that accounts for most of the diff. Splitting that behavior from the boundary fix would let reviewers assess the new error/throw behavior and its callers independently; keep its real-git and invalid-ref tests with that change. The integration changeset currently has duplicate, contradictory dev entries, so please consolidate them into one accurate description. The engine shellout allowlist/test updates for this integration code are currently in #16; they should travel with the implementation they audit rather than land separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant