FN-6596: guard compound engineering debug launches
Guard Compound Engineering debug stage launches against stale stage settings and artifacts. - Add source-level coverage proving stale enabledStages snapshots do not block registered CE stages, including debug. - Add dist freshness checks for the disabledStages opt-out launch model so stale compiled plugin artifacts fail fast. - Mirror newly observed core suite-load flakes in the quarantine ledger and Vitest excludes. Files changed: packages/core/vitest.config.ts | 5 ++ .../src/__tests__/dist-freshness.test.ts | 38 +++++++++++++++ .../src/__tests__/stage-launch-guard.test.ts | 57 ++++++++++++++++++++++ scripts/lib/test-quarantine.json | 10 ++++ 4 files changed, 110 insertions(+) Fusion-Task-Id: FN-6596 Fusion-Task-Lineage: 3cd75e5f-c6f8-4bde-8202-54eeac82b894
This commit is contained in:
@@ -17,7 +17,12 @@ const quarantinedCoreTests = [
|
||||
|
||||
FNXC:CoreTests 2026-06-15-07:39:
|
||||
FN-6486 rescued store-concurrent-writes by making the transient lock helper release independent of event-loop timer scheduling, then removed the quarantine in lockstep with scripts/lib/test-quarantine.json. Keep this array empty unless a future observed flake is mirrored in the ledger in the same commit.
|
||||
|
||||
FNXC:CoreTests 2026-06-17-17:21:
|
||||
FN-6596 verification observed task-list-format and test-project timing out only in the broad changed-package core lane after the merge gate had passed; both files passed immediate isolated reruns. Quarantine the suite-load flakes without widening timeouts or weakening assertions.
|
||||
*/
|
||||
"src/__tests__/task-list-format.test.ts",
|
||||
"src/__tests__/test-project.test.ts",
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const settingsPath = fileURLToPath(new URL("../../dist/settings.js", import.meta.url));
|
||||
const orchestratorPath = fileURLToPath(new URL("../../dist/session/orchestrator.js", import.meta.url));
|
||||
|
||||
function readRequiredDistFile(path: string): string {
|
||||
if (!existsSync(path)) {
|
||||
throw new Error(`dist/ is missing — run pnpm build first (FN-6596): ${path}`);
|
||||
}
|
||||
return readFileSync(path, "utf8");
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:CompoundEngineering 2026-06-17-13:15:
|
||||
The plugin loader imports dist/index.js before src/index.ts, while dist is gitignored and can drift from the fixed TypeScript source. This fast textual guard fails a stale artifact ship before ce-debug regresses to the old enabledStages allow-list.
|
||||
*/
|
||||
describe("compiled Compound Engineering dist freshness", () => {
|
||||
it("keeps compiled settings on the disabledStages opt-out model", () => {
|
||||
const settings = readRequiredDistFile(settingsPath);
|
||||
|
||||
expect(settings).toMatch(/export function getDisabledStages\s*\(\s*settings\s*\)/);
|
||||
expect(settings).toMatch(/asStringArray\(settings,\s*["']disabledStages["'],\s*DEFAULT_DISABLED_STAGES\)/);
|
||||
expect(settings).toMatch(/const disabled = new Set\(getDisabledStages\(settings\)\);/);
|
||||
expect(settings).toMatch(/listStages\(\)\.map\(\(s\) => s\.stageId\)\.filter\(\(stageId\) => !disabled\.has\(stageId\)\)/);
|
||||
expect(settings).not.toContain('asStringArray(settings, "enabledStages"');
|
||||
expect(settings).not.toContain("asStringArray(settings, 'enabledStages'");
|
||||
});
|
||||
|
||||
it("keeps compiled orchestrator launch gating on disabledStages", () => {
|
||||
const orchestrator = readRequiredDistFile(orchestratorPath);
|
||||
|
||||
expect(orchestrator).toMatch(/import \{ getDefaultModelId, getDefaultProvider, getDisabledStages \} from ["']\.\.\/settings\.js["'];/);
|
||||
expect(orchestrator).toMatch(/if \(getDisabledStages\(this\.ctx\.settings\)\.includes\(stageId\)\) \{\s*throw new Error\(`CE stage is not enabled: \$\{stageId\}`\);\s*\}/);
|
||||
expect(orchestrator).not.toMatch(/getEnabledStages\(this\.ctx\.settings\)\.includes\(stageId\)/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { CeOrchestrator } from "../session/orchestrator.js";
|
||||
import { makeHarness, makeScriptedSession, type TestHarness } from "./_harness.js";
|
||||
|
||||
/*
|
||||
FNXC:CompoundEngineering 2026-06-17-13:22:
|
||||
A stale persisted enabledStages snapshot must not block any registered CE stage, including newly added stages such as debug. Keep this runnable source regression outside the quarantined skill-wiring suite so opt-out launch gating remains covered by normal test runs.
|
||||
*/
|
||||
describe("CE stage launch guard", () => {
|
||||
let h: TestHarness;
|
||||
|
||||
beforeEach(() => {
|
||||
h = makeHarness();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
h.close();
|
||||
});
|
||||
|
||||
it.each(["strategy", "work", "debug"])(
|
||||
"launches %s when settings only contain a stale enabledStages snapshot",
|
||||
async (stageId) => {
|
||||
h.ctx.settings = { enabledStages: ["strategy", "ideate", "brainstorm", "plan", "work"] };
|
||||
const factory = vi.fn(async () => ({
|
||||
session: makeScriptedSession([{ type: "complete", data: { artifact: `# ${stageId} done` } }]),
|
||||
}));
|
||||
const orch = new CeOrchestrator({
|
||||
ctx: h.ctx,
|
||||
createInteractiveAiSession: factory,
|
||||
projectRoot: h.projectRoot,
|
||||
turnTimeoutMs: 5000,
|
||||
});
|
||||
|
||||
await orch.start(stageId, { openingMessage: `launch ${stageId}` });
|
||||
|
||||
expect(factory).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects debug launch when debug is explicitly disabled", async () => {
|
||||
h.ctx.settings = { disabledStages: ["debug"] };
|
||||
const factory = vi.fn(async () => ({
|
||||
session: makeScriptedSession([{ type: "complete", data: { artifact: "# debug done" } }]),
|
||||
}));
|
||||
const orch = new CeOrchestrator({
|
||||
ctx: h.ctx,
|
||||
createInteractiveAiSession: factory,
|
||||
projectRoot: h.projectRoot,
|
||||
turnTimeoutMs: 5000,
|
||||
});
|
||||
|
||||
await expect(orch.start("debug", { openingMessage: "investigate" })).rejects.toThrow(
|
||||
"CE stage is not enabled: debug",
|
||||
);
|
||||
expect(factory).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,16 @@
|
||||
"file": "plugins/fusion-plugin-compound-engineering/src/__tests__/work-bridge.test.ts",
|
||||
"reason": "CE broad package verification under NODE_ENV=production fix hit a 10000ms beforeEach hook timeout only in the full @fusion-plugin-examples/compound-engineering lane, while an immediate isolated compound-engineering-node run of work-bridge.test.ts passed in 10.96s. Quarantined per deletion-ratchet policy without hookTimeout increases, retries, or assertion loosening.",
|
||||
"quarantinedAt": "2026-06-17"
|
||||
},
|
||||
{
|
||||
"file": "packages/core/src/__tests__/task-list-format.test.ts",
|
||||
"reason": "FN-6596 verification: pnpm test failed in the broad changed-package @fusion/core lane with a beforeEach hook timeout in task-list-format after the merge gate had passed; immediate isolated rerun of the file passed. Quarantined as a suite-load timeout flake without timeout bumps, retries, or assertion loosening.",
|
||||
"quarantinedAt": "2026-06-17"
|
||||
},
|
||||
{
|
||||
"file": "packages/core/src/__tests__/test-project.test.ts",
|
||||
"reason": "FN-6596 verification: pnpm test failed in the broad changed-package @fusion/core lane with a test timeout in test-project after the merge gate had passed; immediate isolated rerun of the file passed. Quarantined as a suite-load timeout flake without timeout bumps, retries, or assertion loosening.",
|
||||
"quarantinedAt": "2026-06-17"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user