## Summary Wave 17 organizes Fusion into **domain folders** (stacks on #2397). ### Layout - **core/types/** — board, task, agents, settings, merge, workflow, mesh, … - **core/src/** — agents, ai, async-stores, workflows, tasks, config, db, … - **dashboard/app/api/** — client, tasks, agents, git, missions, planning, … - **engine/src/** — agents, auth, execution, merge, missions, overseer, worktree, … Root keepers retained for large entrypoints (`store.ts`, `executor.ts`, `merger.ts`, …). Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy) stay stable. ## Test plan - [x] `@fusion/core` typecheck - [x] `@fusion/engine` typecheck (pre-existing playwright-core noise only) - [ ] CI merge gate **Stack:** #2394 → #2397 → **this PR**
55 lines
2.4 KiB
TypeScript
55 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { WorkflowIr } from "@fusion/core";
|
|
import {
|
|
parseRequiredArtifactMissingValue,
|
|
requiredArtifactMissingValue,
|
|
isRequiredArtifactReadFailedValue,
|
|
requiredArtifactReadFailedValue,
|
|
workflowEntryArtifacts,
|
|
} from "../execution/required-workflow-artifacts.js";
|
|
|
|
// FNXC:WorkflowArtifacts 2026-07-21-17:00: This suite locks the typed missing/read
|
|
// distinction and the non-empty planning/step-source contract used by every gate.
|
|
describe("required workflow artifact contracts", () => {
|
|
it("treats planning-owned and step-source declarations as workflow-entry inputs", () => {
|
|
const ir = {
|
|
version: "v2",
|
|
name: "artifact inputs",
|
|
columns: [],
|
|
nodes: [],
|
|
edges: [],
|
|
artifacts: [
|
|
{ key: "PROMPT.md", producedBy: "planning", role: "step-source" },
|
|
{ key: "manual-context", producedBy: "manual", role: "context" },
|
|
{ key: "steps", role: "step-source" },
|
|
],
|
|
} as unknown as WorkflowIr;
|
|
|
|
expect(workflowEntryArtifacts(ir).map((artifact) => artifact.key)).toEqual(["PROMPT.md", "steps"]);
|
|
});
|
|
|
|
it("keeps storage-read failures distinct from confirmed missing artifacts", () => {
|
|
const value = requiredArtifactReadFailedValue("PROMPT.md");
|
|
expect(value).toBe("required-artifact-read-failed:PROMPT.md");
|
|
expect(isRequiredArtifactReadFailedValue(value)).toBe(true);
|
|
expect(parseRequiredArtifactMissingValue(value)).toBeNull();
|
|
});
|
|
|
|
it("round-trips a deduplicated typed missing-artifact failure", () => {
|
|
const value = requiredArtifactMissingValue([" PROMPT.md ", "PROMPT.md", "steps"]);
|
|
expect(value).toBe('required-artifact-missing:["PROMPT.md","steps"]');
|
|
expect(parseRequiredArtifactMissingValue(value)).toEqual(["PROMPT.md", "steps"]);
|
|
expect(parseRequiredArtifactMissingValue("required-artifact-missing:PROMPT.md,steps")).toEqual(["PROMPT.md", "steps"]);
|
|
expect(parseRequiredArtifactMissingValue("failed")).toBeNull();
|
|
});
|
|
|
|
it("round-trips artifact keys containing the legacy delimiter", () => {
|
|
const value = requiredArtifactMissingValue(["plans,primary.md", "steps"]);
|
|
expect(parseRequiredArtifactMissingValue(value)).toEqual(["plans,primary.md", "steps"]);
|
|
});
|
|
|
|
it("rejects an empty missing-artifact payload instead of emitting an unparsable value", () => {
|
|
expect(() => requiredArtifactMissingValue(["", " "])).toThrow("At least one required artifact key is needed");
|
|
});
|
|
});
|