FN-7486: fix no-diff merge recovery ownership checks
Fix no-op task branch recovery by recognizing canonical branches with no unique diff before rejecting inherited foreign trailers. - Add no-diff ownership classification for already-merged detection when canonical task branches inherit another task's landed commit. - Teach self-healing and branch-misbound recovery to ignore foreign branch-tip trailers only for branches proven to have no unique task diff. - Skip synthetic verify:fast typechecks for JavaScript alias packages without tsconfig files and cover the behavior with tests. - Add regression coverage and a patch changeset for the recovery fix. Files changed: .changeset/fn-7486-merge-recovery-noop-ownership.md | 7 ++ .../already-merged-detector.real-git.test.ts | 68 +++++++++++++++++ .../self-healing-already-merged.real-git.test.ts | 89 ++++++++++++++++++++-- packages/engine/src/already-merged-detector.ts | 88 +++++++++++++++------ packages/engine/src/self-healing.ts | 61 +++++++++++++-- scripts/__tests__/verify-fast.test.mjs | 16 +++- scripts/verify-fast.mjs | 15 +++- 7 files changed, 303 insertions(+), 41 deletions(-) Fusion-Task-Id: FN-7486 Fusion-Task-Lineage: 4185c6ed-9731-4ffc-b033-34bf0c3a83ad Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -110,7 +110,7 @@ test("buildVerifyPlan: typecheck for all eligible, then builds, then boot smoke
|
||||
test("buildVerifyPlan: a package without a build script gets a typecheck step but no build step", () => {
|
||||
const packageMeta = new Map([
|
||||
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
|
||||
["@fusion/test-only", { hasTypecheck: false, hasBuild: false }],
|
||||
["@fusion/test-only", { hasTypecheck: false, hasTsconfig: true, hasBuild: false }],
|
||||
]);
|
||||
const plan = buildVerifyPlan({ packages: ["@fusion/engine", "@fusion/test-only"], packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
|
||||
assert.deepEqual(stepIds(plan), [
|
||||
@@ -126,6 +126,20 @@ test("buildVerifyPlan: a package without a build script gets a typecheck step bu
|
||||
assert.deepEqual(tc.args, ["--filter", "@fusion/test-only", "exec", "tsc", "--noEmit", "-p", "."]);
|
||||
});
|
||||
|
||||
test("buildVerifyPlan: skips synthetic typecheck for JavaScript alias packages with no tsconfig", () => {
|
||||
const packageMeta = new Map([
|
||||
["runfusion.ai", { hasTypecheck: false, hasTsconfig: false, hasBuild: false }],
|
||||
["@runfusion/fusion", { hasTypecheck: true, hasTsconfig: true, hasBuild: true }],
|
||||
]);
|
||||
const plan = buildVerifyPlan({ packages: ["runfusion.ai", "@runfusion/fusion"], packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
|
||||
assert.deepEqual(stepIds(plan), [
|
||||
"bootstrap-artifacts",
|
||||
"typecheck:@runfusion/fusion",
|
||||
"build:@runfusion/fusion",
|
||||
"boot-smoke",
|
||||
]);
|
||||
});
|
||||
|
||||
test("buildVerifyPlan: desktop/mobile are excluded from scoped steps but boot smoke still runs", () => {
|
||||
const packageMeta = new Map([
|
||||
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
|
||||
|
||||
@@ -32,7 +32,7 @@ of blocking forever, and we exit nonzero on the first failing step.
|
||||
*/
|
||||
|
||||
import path from "node:path";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -135,7 +135,7 @@ export function buildArtifactBootstrapStep(bootstrapScriptPath, nodeBin = proces
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {string[]} [opts.packages] affected package names
|
||||
* @param {Map<string, { dir?: string, hasTypecheck?: boolean, hasBuild?: boolean }>} [opts.packageMeta]
|
||||
* @param {Map<string, { dir?: string, hasTypecheck?: boolean, hasTsconfig?: boolean, hasBuild?: boolean }>} [opts.packageMeta]
|
||||
* @param {string} opts.bootSmokeScriptPath
|
||||
* @param {string} [opts.artifactBootstrapScriptPath]
|
||||
* @param {string} [opts.nodeBin]
|
||||
@@ -147,7 +147,13 @@ export function buildVerifyPlan({ packages = [], packageMeta = new Map(), bootSm
|
||||
|
||||
const steps = [buildArtifactBootstrapStep(bootstrapScriptPath, nodeBin)];
|
||||
for (const pkg of eligiblePackages) {
|
||||
steps.push(buildTypecheckStep(pkg, packageMeta.get(pkg) ?? {}));
|
||||
const meta = packageMeta.get(pkg) ?? {};
|
||||
/*
|
||||
FNXC:TestInfrastructure 2026-07-03-21:54:
|
||||
Workspace alias packages such as `runfusion.ai` are publishable JavaScript shims with no tsconfig. verify:fast should not synthesize a `tsc -p .` fallback for those packages; their executable behavior is covered by the required CLI build and boot smoke.
|
||||
*/
|
||||
if (meta.hasTypecheck === false && meta.hasTsconfig === false) continue;
|
||||
steps.push(buildTypecheckStep(pkg, meta));
|
||||
}
|
||||
|
||||
const builtPackages = new Set();
|
||||
@@ -181,7 +187,7 @@ export function buildVerifyPlan({ packages = [], packageMeta = new Map(), bootSm
|
||||
* @param {string[]} packages
|
||||
* @param {Map<string, string>} packageDirByName pkg name → repo-relative dir
|
||||
* @param {string} [root]
|
||||
* @returns {Map<string, { dir: string, hasTypecheck: boolean, hasBuild: boolean }>}
|
||||
* @returns {Map<string, { dir: string, hasTypecheck: boolean, hasTsconfig: boolean, hasBuild: boolean }>}
|
||||
*/
|
||||
export function readPackageMeta(packages, packageDirByName, root = repoRoot) {
|
||||
const meta = new Map();
|
||||
@@ -199,6 +205,7 @@ export function readPackageMeta(packages, packageDirByName, root = repoRoot) {
|
||||
meta.set(pkg, {
|
||||
dir: dir ?? null,
|
||||
hasTypecheck: typeof scripts.typecheck === "string",
|
||||
hasTsconfig: dir ? existsSync(path.join(root, dir, "tsconfig.json")) : true,
|
||||
hasBuild: typeof scripts.build === "string",
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user