FN-9057: bound workspace trailer fallback scans

Bound degraded workspace landing checks to recent task evidence.

- Pass task creation time through workspace landing and recovery paths
- Limit branch-gone trailer scans by time and commit count
- Cover recycled task-id fallback behavior with real-git tests

Files changed:
 ...fn-9057-workspace-land-predicate-recycled-id.md |  7 +++
 ...ace-land-predicate-recycled-id.real-git.test.ts | 58 ++++++++++++++++++++++
 .../workspace-merger-idempotency.slow.test.ts      | 19 +++++++
 packages/engine/src/merge/merger-ai.ts             |  6 +++
 .../engine/src/merge/workspace-land-predicate.ts   | 39 +++++++++++++--
 packages/engine/src/self-healing.ts                |  5 +-
 6 files changed, 127 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-9057

Fusion-Task-Lineage: e522a33e-0f11-49ad-af88-3fa29129fad2

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-08-15 00:25:04 -07:00
parent 1372218b1f
commit 6adcab3b0e
6 changed files with 127 additions and 7 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Prevent stale workspace task trailers from falsely proving a repo landed.
category: fix
dev: Bounds findProvenLandedCommit degraded scans using taskCreatedAt and recent evidence limits.

View File

@@ -0,0 +1,58 @@
import { afterEach, describe, expect, it } from "vitest";
import { execSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { findProvenLandedCommit, isRepoLanded } from "../merge/workspace-land-predicate.js";
import { hasGit } from "./_workspace-fixture.js";
const describeIfGit = hasGit ? describe : describe.skip;
function git(cwd: string, command: string, env?: NodeJS.ProcessEnv): string {
return execSync(command, { cwd, encoding: "utf8", env: { ...process.env, ...env } }).trim();
}
/** Commit a real exact trailer line; explicit dates model recycled ids without relying on wall time. */
function commit(repo: string, name: string, taskId: string, date?: string, body = false): string {
writeFileSync(join(repo, name), `${name}\n`);
git(repo, `git add ${name}`);
const message = body ? `note\n\nmentions Fusion-Task-Id: ${taskId}` : `land\n\nFusion-Task-Id: ${taskId}`;
git(repo, `git commit -m "${message.split("\n")[0]}" ${body ? `-m "mentions Fusion-Task-Id: ${taskId}"` : `-m "Fusion-Task-Id: ${taskId}"`}`, date ? { GIT_AUTHOR_DATE: date, GIT_COMMITTER_DATE: date } : undefined);
return git(repo, "git rev-parse HEAD");
}
describeIfGit("workspace land predicate recycled task id (real git)", () => {
let repo = "";
afterEach(() => repo && rmSync(repo, { recursive: true, force: true }));
it("rejects ancient branch-gone trailers while preserving recent, skew, legacy-window, and primary proofs", async () => {
repo = mkdtempSync(join(tmpdir(), "fn-9057-land-predicate-"));
git(repo, "git init -b main");
git(repo, 'git config user.email "test@example.com"');
git(repo, 'git config user.name "Test"');
const now = new Date();
const createdAt = now.toISOString();
const ancient = commit(repo, "ancient", "FN-2002", "2024-01-01T00:00:00Z");
expect(await findProvenLandedCommit(repo, "main", undefined, "FN-2002", "fusion/fn-2002", undefined, createdAt)).toBeUndefined();
expect(await isRepoLanded(repo, "main", undefined, "FN-2002", "fusion/fn-2002", undefined, createdAt)).toBe(false);
const recent = commit(repo, "recent", "FN-2002");
expect(await findProvenLandedCommit(repo, "main", undefined, "FN-2002", "fusion/fn-2002", undefined, createdAt)).toBe(recent);
expect(await findProvenLandedCommit(repo, "main", undefined, "FN-2002")).toBe(recent);
expect(await findProvenLandedCommit(repo, "main", recent, "FN-2002", "fusion/fn-2002", undefined, "2999dead")).toBe(recent);
expect(ancient).not.toBe(recent);
});
it("leaves merge-base ranges and trailer-line precision unchanged", async () => {
repo = mkdtempSync(join(tmpdir(), "fn-9057-land-predicate-"));
git(repo, "git init -b main"); git(repo, 'git config user.email "test@example.com"'); git(repo, 'git config user.name "Test"');
commit(repo, "base", "OTHER");
git(repo, "git branch fusion/fn-2002");
const oldAfterBase = commit(repo, "old-after-base", "FN-2002", "2024-01-01T00:00:00Z");
expect(await findProvenLandedCommit(repo, "main", undefined, "FN-2002", "fusion/fn-2002", undefined, new Date().toISOString())).toBe(oldAfterBase);
const mention = commit(repo, "mention", "FN-9999", undefined, true);
expect(await findProvenLandedCommit(repo, "main", undefined, "FN-2002", "fusion/fn-2002")).toBe(oldAfterBase);
expect(mention).not.toBe(oldAfterBase);
expect(await findProvenLandedCommit(repo, "missing", undefined, "FN-2002")).toBeUndefined();
});
});

View File

@@ -411,6 +411,25 @@ describeIfGit("landWorkspaceTask — DB-failure resilience (Phase C review A1/A4
expect(second.finalized).toBe(true);
});
it("does not treat an ancient recycled trailer on a gone branch as already landed", async () => {
fx = await createWorkspaceFixture(["repo-a"]);
configureIdentity(fx.repoPath("repo-a"));
writeFileSync(path.join(fx.repoPath("repo-a"), "ancient.txt"), "ancient\n", "utf-8");
fx.git("repo-a", "git add ancient.txt");
execSync(`git commit -m "historic land" -m "Fusion-Task-Id: ${TASK_ID}"`, {
cwd: fx.repoPath("repo-a"), stdio: "pipe",
env: { ...process.env, GIT_AUTHOR_DATE: "2024-01-01T00:00:00Z", GIT_COMMITTER_DATE: "2024-01-01T00:00:00Z" },
});
const task = makeTask({ "repo-a": { worktreePath: fx.repoPath("repo-a"), branch: BRANCH } });
const store = createStore(task);
const result = await landWorkspaceTask(store, store.task, fx.rootDir, {}, {
mergeAgent: squashMergeAgent(BRANCH), reviewAgent: approveReviewAgent,
});
const repo = result.repos[0]!;
expect(repo.alreadyLanded).toBeFalsy();
expect(repo.status).toBe("empty");
});
it("A4: WorkspacePartialLandError is a real class (instanceof + retryable + payload)", () => {
const err = new WorkspacePartialLandError(2, ["repo-b"], "partial");
expect(err).toBeInstanceOf(WorkspacePartialLandError);

View File

@@ -2020,6 +2020,11 @@ export async function landWorkspaceTask(
// ancestor of (or equals) its CURRENT integration tip is already landed — SKIP
// it so a retry never re-advances the ref. This makes a re-run after a partial
// land idempotent for the already-landed repos.
/*
FNXC:Workspace 2026-08-15-07:05:
Supply task creation time so a missing workspace branch cannot let a recycled historical
trailer prove this repo landed and skip its current work.
*/
const provenLandedSha = await findProvenLandedCommit(
repoRootDir,
integrationBranch,
@@ -2027,6 +2032,7 @@ export async function landWorkspaceTask(
taskId,
entry.branch,
entry.revertBoundarySha,
task.createdAt,
);
if (provenLandedSha) {
/*

View File

@@ -15,6 +15,9 @@ const execFileAsync = promisify(execFile);
/** Canonical Fusion task-id trailer key stamped on every land squash commit. */
export const FUSION_TASK_ID_TRAILER_KEY = "Fusion-Task-Id";
export const TRAILER_SCAN_CLOCK_SKEW_MS = 24 * 60 * 60 * 1_000;
export const UNBOUNDED_TRAILER_SCAN_WINDOW_MS = 30 * 24 * 60 * 60 * 1_000;
export const UNBOUNDED_TRAILER_SCAN_MAX_COMMITS = 1_000;
async function git(args: string[], cwd: string, opts: { timeout?: number } = {}): Promise<string> {
const { stdout } = await execFileAsync("git", args, {
@@ -72,8 +75,9 @@ async function gitCapture(args: string[], cwd: string): Promise<string | undefin
* `Fusion-Task-Id` trailer (always stamped onto the squash by `taskTrailers` + the
* ensureTaskMetadata safety net) is the only reliable "this task's work is already on the ref"
* signal that does not depend on the landedSha row, so it is what survives a lost persist. We
* bound the scan to commits the integration tip has gained since the branch's merge-base (the
* land base) so an unrelated historical reuse of the same trailer cannot false-positive.
* normally bound the scan to commits the integration tip has gained since the branch's merge-base
* (the land base). When the branch or merge-base is gone, that graph bound degrades; the fallback
* is instead bounded by task creation time (or a conservative recent window for legacy callers).
*
* Exported (A6) so Phase D self-healing reuses THIS canonical predicate instead of
* reimplementing the ancestor/trailer check.
@@ -93,6 +97,7 @@ export async function findProvenLandedCommit(
taskId?: string,
branch?: string,
revertBoundarySha?: string,
taskCreatedAt?: string | number,
): Promise<string | undefined> {
const intRef = `refs/heads/${integrationBranch}`;
if (!(await gitOk(["rev-parse", "--verify", intRef], repoRootDir))) {
@@ -123,10 +128,24 @@ export async function findProvenLandedCommit(
if (taskId) {
const branchRef = branch ? `refs/heads/${branch}` : undefined;
let range = intRef;
let degradedRange = true;
if (branchRef && (await gitOk(["rev-parse", "--verify", branchRef], repoRootDir))) {
const base = await gitCapture(["merge-base", branchRef, intRef], repoRootDir);
if (base) range = `${base.trim()}..${intRef}`;
if (base) {
range = `${base.trim()}..${intRef}`;
degradedRange = false;
}
}
/*
FNXC:Workspace 2026-08-15-07:05:
A missing branch loses the merge-base boundary, so scanning all integration history could
treat a recycled task id as landed and silently drop current work. Prefer a false-negative
(idempotent re-land or honest recovery classification) by requiring recent trailer evidence.
*/
const parsedCreatedAt = taskCreatedAt === undefined ? Number.NaN : new Date(taskCreatedAt).getTime();
const minCommitTimeMs = Number.isFinite(parsedCreatedAt)
? parsedCreatedAt - TRAILER_SCAN_CLOCK_SKEW_MS
: Date.now() - UNBOUNDED_TRAILER_SCAN_WINDOW_MS;
const trailer = `${FUSION_TASK_ID_TRAILER_KEY}: ${taskId}`;
/*
FNXC:Workspace 2026-07-07-10:50 (Phase C A1 precision — Greptile P1, trailer-line verification):
@@ -138,14 +157,23 @@ export async function findProvenLandedCommit(
own landing commit.
*/
const candidates = await gitCapture(
["log", "--format=%H", `--grep=${trailer}`, "--fixed-strings", range],
[
"log", "--format=%H", `--grep=${trailer}`, "--fixed-strings",
...(degradedRange ? [`--since=${new Date(minCommitTimeMs).toISOString()}`, `--max-count=${UNBOUNDED_TRAILER_SCAN_MAX_COMMITS}`] : []),
range,
],
repoRootDir,
);
if (candidates) {
for (const sha of candidates.trim().split("\n")) {
if (!sha) continue;
const commitTime = degradedRange
? await gitCapture(["show", "-s", "--format=%ct", sha], repoRootDir)
: undefined;
const commitTimeMs = commitTime === undefined ? Number.NaN : Number(commitTime) * 1_000;
const body = await gitCapture(["show", "-s", "--format=%B", sha], repoRootDir);
if (
(!degradedRange || (Number.isFinite(commitTimeMs) && commitTimeMs >= minCommitTimeMs)) &&
body &&
body.split("\n").some((line) => line.trim() === trailer) &&
!(await isAtOrBehindRevertBoundary(sha))
@@ -165,8 +193,9 @@ export async function isRepoLanded(
taskId?: string,
branch?: string,
revertBoundarySha?: string,
taskCreatedAt?: string | number,
): Promise<boolean> {
return Boolean(
await findProvenLandedCommit(repoRootDir, integrationBranch, landedSha, taskId, branch, revertBoundarySha),
await findProvenLandedCommit(repoRootDir, integrationBranch, landedSha, taskId, branch, revertBoundarySha, taskCreatedAt),
);
}

View File

@@ -10114,7 +10114,8 @@ const movedTask = await this.store.moveTask(task.id, completeLane);
A boundary-invalidated repo is unlanded here and therefore follows the existing
branch-present retry path; FORK-A remains fail-closed when its branch is absent.
*/
if (await isRepoLanded(repoRootDir, integrationBranch, entry.landedSha, task.id, entry.branch, entry.revertBoundarySha)) {
/* FNXC:Workspace 2026-08-15-07:05: Creation time bounds branch-gone trailer recovery against recycled task ids. */
if (await isRepoLanded(repoRootDir, integrationBranch, entry.landedSha, task.id, entry.branch, entry.revertBoundarySha, task.createdAt)) {
landedRepos.push(repoRel);
continue;
}
@@ -10613,7 +10614,7 @@ const movedTask = await this.store.moveTask(task.id, completeLane);
repoRootDir,
{ ...settings, integrationBranch: undefined, baseBranch: undefined },
);
safe = await isRepoLanded(repoRootDir, integrationBranch, entry.landedSha, task.id, branch, entry.revertBoundarySha);
safe = await isRepoLanded(repoRootDir, integrationBranch, entry.landedSha, task.id, branch, entry.revertBoundarySha, task.createdAt);
}
if (!safe && entry.baseCommitSha) {
const count = await this.execWorkspaceTeardownGit(`git rev-list --count ${shellQuote(entry.baseCommitSha)}..${shellQuote(branch)}`, { cwd: repoRootDir, timeout: 120_000 });