feat(slice-05): /install routes declared workflow_specs after successful bootstrap (Item 6 Checkpoint 7.3e step 3) (OPR.0.3.2.5)

Mirror of the 7.3a skills + 7.3d plugins /install integrations for the
workflow_specs cross-primitive kind. Closes 7.3e (workflow_specs end-
to-end). Per orch ruling Candidate A on Item-6 completeness: 3-of-4
named kinds shipped (skills + plugins HYBRID + workflow_specs); 7.3f
context_packs + 7.3g agent_images next, then 7.4 gstack proof case.

routeWorkflowSpecsAfterBootstrap (bundles.ts +69):
- Resolves targetWorkflowSpecsDir via the CALLER CONTRACT pinned in
  d43b7729: nodePath.join(ContextPackSettingsStore.resolveConfig()
  .workspaceSpecsRoot, "workflows") — SettingsStore is sole authority,
  no hardcoded default. If SettingsStore throws or workspaceSpecsRoot
  is undefined, returns null (no-op routing; install still succeeds).
  Mirror of startup.ts:910-916 try/catch posture.
- bundlePath-only signature per the 5f410eee B1 decoupling lesson
  (routing fires on the dual-override path where installMeta is null).
- Canonical unpack trust boundary; parses bundle.yaml; filters
  manifest workflow_specs[] to strings; delegates to routeWorkflowSpecs
  (which enforces both-sides containment internally).

workflowSpecsRouterFsOps (bundles.ts +4): real-fs wrapper —
fs.existsSync / fs.readFileSync utf-8 / fs.writeFileSync utf-8 /
fs.mkdirSync recursive.

/install completed branch integration: third try/catch block alongside
skills + plugins routings; workflowSpecsRouting key conditionally
merged into the extras object (omitted if null).

bundle-routes.test.ts (+214): 3 new tests:
1. POST /install routes declared workflow_specs after successful
   bootstrap (completed branch) — basic end-to-end. Env-overrides
   OPENRIG_WORKSPACE_SPECS_ROOT to a tmp specsRoot; bundle tampered
   with workflow_specs: [packages/test-pkg/workflows/onboarding.yaml]
   (verbatim package path, same shape as the skills test pattern);
   asserts workflowSpecsRouting.routedCount=1 + records[0].status=
   "routed" + file at <specsRoot>/workflows/packages/test-pkg/workflows/
   onboarding.yaml with content preserved.
2. POST /install routes declared workflow_specs even when both
   --skip-version-check and --force are set (B1 mirror) — PROACTIVELY
   covers the 5f410eee dual-override decoupling lesson this time.
3. POST /install does NOT include workflowSpecsRouting when bundle
   has no workflow_specs[] — null-branch / extras-merge correctness.

Discriminator-flip evidence BEFORE this commit (PROACTIVELY this time,
per banked commitment):

Flip A — insert `return null;` at routeWorkflowSpecsAfterBootstrap
entry. Result: 2/44 fail — exactly the basic with-workflow_specs +
B1-mirror dual-override tests (both fail at
expect(body.workflowSpecsRouting).toBeDefined()). The 42 other tests
stay green including the no-workflow_specs null-branch test (because
extras-key stays absent in both "function returns null" and "no
workflow_specs[] declared" cases). Restored.

Flip B — wrap the call site in /install with `if (installMeta)
workflowSpecsRouting = ...;` (simulated 5f410eee re-coupling).
Result: 1/44 fail — ONLY the B1-mirror dual-override test fails;
the basic test stays green because its installMeta is non-null when
the dual-override flags are absent. PROVES the B1-mirror test
specifically catches the dual-override decoupling regression, not the
general integration path (which Flip A covered). Restored.

Final: 170/170 across the full slice-05 daemon suite
(bundle-routes 44 + bundle-workflow-specs-router 8 + bundle-skills-
router 8 + bundle-plugins-router 9 + bundle-types 49 + pod-bundle-
assembler 51).

Mirror of cont.18 ratification: workflow_specs is the 3rd cross-primitive
kind; the source primitive (WorkflowSpecCache + scanner) is on main per
guard-verified reachability. The integration writes to the live
scanner-reachable location so installed specs become operator-visible
through the existing Library UI on the next scan.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-31 19:09:23 -07:00
1 parent d43b7729f9
commit 9f9ebe0a51
2 files changed
+287

No files matched your search

+73
View File
@@ -23,6 +23,8 @@ import { BundleAuditReader, BundleAuditWriter, type BundleAuditFsOps, type Bundl
import { getDefaultOpenRigPath } from "../openrig-compat.js";
import { routeSkills, type SkillsRouterFsOps, type RouteSkillsResult } from "../domain/bundle-skills-router.js";
import { routePlugins, type PluginsRouterFsOps, type RoutePluginsResult, type PluginRoutingInput } from "../domain/bundle-plugins-router.js";
import { routeWorkflowSpecs, type WorkflowSpecsRouterFsOps, type RouteWorkflowSpecsResult } from "../domain/bundle-workflow-specs-router.js";
import { SettingsStore as ContextPackSettingsStore } from "../domain/user-settings/settings-store.js";
/**
* Read the daemon's own package.json version at call time (Item 1 / slice-05).
@@ -288,6 +290,70 @@ function pluginsRouterFsOps(): PluginsRouterFsOps {
};
}
/** Item 6 / slice-05 Checkpoint 7.3e step 3: real WorkflowSpecsRouterFsOps backed by node:fs. */
function workflowSpecsRouterFsOps(): WorkflowSpecsRouterFsOps {
return {
exists: (p) => fs.existsSync(p),
readFile: (p) => fs.readFileSync(p, "utf-8"),
writeFile: (p, c) => fs.writeFileSync(p, c, "utf-8"),
mkdirp: (p) => fs.mkdirSync(p, { recursive: true }),
};
}
/**
* Item 6 / slice-05 Checkpoint 7.3e step 3: extract the bundle safely
* (banked unpack trust boundary) and route any declared workflow_specs to
* the operator workflow-specs library. Returns null when bundle has no
* workflow_specs[] (no-op), no manifest, or workspaceSpecsRoot unresolved.
*
* Target resolution per the CALLER CONTRACT in
* bundle-workflow-specs-router.ts: SettingsStore is the sole authority.
* Resolved as nodePath.join(workspaceSpecsRoot, "workflows") — the path
* that spec-library-workflow-scanner actually reads (startup.ts:903-916).
* If SettingsStore cannot resolve workspaceSpecsRoot (settings not yet
* initialized / config error), we return null and the install lifecycle
* proceeds without workflow_specs routing — mirror of startup.ts:910-916
* try/catch posture.
*
* Mirror of routeSkillsAfterBootstrap / routePluginsAfterBootstrap pattern:
* takes bundlePath only (decoupled from installMeta per the 5f410eee B1
* lesson; routing must fire on the dual-override path too).
*/
async function routeWorkflowSpecsAfterBootstrap(bundlePath: string): Promise<RouteWorkflowSpecsResult | null> {
let workspaceSpecsRoot: string | undefined;
try {
const settingsStore = new ContextPackSettingsStore();
workspaceSpecsRoot = settingsStore.resolveConfig().workspaceSpecsRoot;
} catch {
return null; // settings unresolvable; no-op routing
}
if (!workspaceSpecsRoot) return null;
const targetWorkflowSpecsDir = nodePath.join(workspaceSpecsRoot, "workflows");
const tmpDir = fs.mkdtempSync(nodePath.join(os.tmpdir(), "bundle-workflow-specs-route-"));
try {
await unpack(bundlePath, tmpDir);
const manifestPath = nodePath.join(tmpDir, "bundle.yaml");
if (!fs.existsSync(manifestPath)) return null;
const manifestYaml = fs.readFileSync(manifestPath, "utf-8");
const manifest = parsePodBundleManifest(manifestYaml) as Record<string, unknown>;
const rawWorkflowSpecs = manifest["workflow_specs"];
if (!Array.isArray(rawWorkflowSpecs) || rawWorkflowSpecs.length === 0) return null;
const declaredWorkflowSpecs = rawWorkflowSpecs.filter((s): s is string => typeof s === "string" && s.length > 0);
if (declaredWorkflowSpecs.length === 0) return null;
return routeWorkflowSpecs(
{
bundleRoot: tmpDir,
declaredWorkflowSpecs,
targetWorkflowSpecsDir,
},
workflowSpecsRouterFsOps(),
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}
/**
* Item 6 / slice-05 Checkpoint 7.3d: extract the bundle safely (banked unpack
* trust boundary) and route any declared plugin references to the operator
@@ -815,6 +881,7 @@ bundleRoutes.post("/install", async (c) => {
// so operators see what landed.
let skillsRouting: RouteSkillsResult | null = null;
let pluginsRouting: RoutePluginsResult | null = null;
let workflowSpecsRouting: RouteWorkflowSpecsResult | null = null;
try {
skillsRouting = await routeSkillsAfterBootstrap(bundlePath);
} catch {
@@ -825,9 +892,15 @@ bundleRoutes.post("/install", async (c) => {
} catch {
// Side-channel failure; install already succeeded
}
try {
workflowSpecsRouting = await routeWorkflowSpecsAfterBootstrap(bundlePath);
} catch {
// Side-channel failure; install already succeeded
}
const extras: Record<string, unknown> = {};
if (skillsRouting) extras.skillsRouting = skillsRouting;
if (pluginsRouting) extras.pluginsRouting = pluginsRouting;
if (workflowSpecsRouting) extras.workflowSpecsRouting = workflowSpecsRouting;
return c.json(Object.keys(extras).length > 0 ? { ...result, ...extras } : result, 201);
}
if (result.status === "partial") {
+214
View File
@@ -1533,6 +1533,220 @@ describe("Bundle API routes", () => {
}
});
// Item 6 / Checkpoint 7.3e step 3: /install routes declared workflow_specs
// after successful bootstrap. Target = SettingsStore-resolved
// <workspaceSpecsRoot>/workflows. Bundle has workflow_specs[] with a path
// pointing to a workflow YAML file in the bundle tree. Verifies the file
// lands at <env-overridden specs root>/workflows/<basename>.
it("POST /api/bundles/install routes declared workflow_specs after successful bootstrap (completed branch)", async () => {
const origHome = process.env.OPENRIG_HOME;
const origSpecsRoot = process.env.OPENRIG_WORKSPACE_SPECS_ROOT;
const auditHome = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-workflow-specs-route-test-"));
const specsRoot = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-workflow-specs-route-target-"));
process.env.OPENRIG_HOME = auditHome;
process.env.OPENRIG_WORKSPACE_SPECS_ROOT = specsRoot;
const origBootstrap = setup.bootstrapOrchestrator.bootstrap.bind(setup.bootstrapOrchestrator);
try {
const { specPath } = seedPackage();
const bundlePath = path.join(tmpDir, "with-workflow-specs.rigbundle");
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "wflow-target-"));
try {
// Put a workflow_spec YAML inside the bundle's package source
const workflowSrcDir = path.join(tmpDir, "test-pkg", "workflows");
fs.mkdirSync(workflowSrcDir, { recursive: true });
fs.writeFileSync(path.join(workflowSrcDir, "onboarding.yaml"), "name: onboarding\nversion: '1.0'\nsteps: []\n");
await app.request("/api/bundles/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ specPath, bundleName: "with-workflow-specs", bundleVersion: "0.1.0", outputPath: bundlePath }),
});
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "wflow-stage-"));
try {
const tar = await import("tar");
await tar.extract({ file: bundlePath, cwd: stagingDir });
const bundleYamlPath = path.join(stagingDir, "bundle.yaml");
const original = fs.readFileSync(bundleYamlPath, "utf-8");
// Declare the verbatim package path (no leading "workflows/"), mirror of
// the skills-test pattern at line 1258. File is already in the bundle via
// the test-pkg source; integrity manifest unchanged.
const tampered = `${original}\nworkflow_specs:\n - packages/test-pkg/workflows/onboarding.yaml\n`;
fs.writeFileSync(bundleYamlPath, tampered);
const { pack } = await import("../src/domain/bundle-archive.js");
await pack(stagingDir, bundlePath);
} finally {
fs.rmSync(stagingDir, { recursive: true, force: true });
}
const stub = vi.fn().mockResolvedValue({
status: "completed",
runId: "test-run-workflow-specs",
rigId: "01H000000000000000WSPEC01",
stages: [{ stage: "resolve_spec", status: "ok" }],
errors: [],
});
(setup.bootstrapOrchestrator as unknown as { bootstrap: typeof stub }).bootstrap = stub;
const installRes = await app.request("/api/bundles/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bundlePath, targetRoot, autoApprove: true }),
});
const body = await installRes.json();
expect(body.workflowSpecsRouting).toBeDefined();
expect(body.workflowSpecsRouting.routedCount).toBe(1);
expect(body.workflowSpecsRouting.records[0].status).toBe("routed");
// Declared path has no leading "workflows/" so routes verbatim;
// file lands at <specsRoot>/workflows/<verbatim-path>
const expectedTarget = path.join(specsRoot, "workflows", "packages", "test-pkg", "workflows", "onboarding.yaml");
expect(fs.existsSync(expectedTarget)).toBe(true);
expect(fs.readFileSync(expectedTarget, "utf-8")).toBe("name: onboarding\nversion: '1.0'\nsteps: []\n");
} finally {
fs.rmSync(targetRoot, { recursive: true, force: true });
}
} finally {
(setup.bootstrapOrchestrator as unknown as { bootstrap: typeof origBootstrap }).bootstrap = origBootstrap;
if (origHome === undefined) delete process.env.OPENRIG_HOME;
else process.env.OPENRIG_HOME = origHome;
if (origSpecsRoot === undefined) delete process.env.OPENRIG_WORKSPACE_SPECS_ROOT;
else process.env.OPENRIG_WORKSPACE_SPECS_ROOT = origSpecsRoot;
fs.rmSync(auditHome, { recursive: true, force: true });
fs.rmSync(specsRoot, { recursive: true, force: true });
}
});
// Item 6 / Checkpoint 7.3e step 3 / B1-mirror discipline (banked 5f410eee
// decoupling lesson): workflow_specs routing must fire even when operator
// uses BOTH --skip-version-check and --force pre-check overrides. The
// routing is independent of the pre-check extraction gate. Discriminator:
// re-coupling routeWorkflowSpecsAfterBootstrap to installMeta would make
// this test fail (workflowSpecsRouting undefined when both flags set on a
// bundle that declares workflow_specs[]).
it("POST /api/bundles/install routes declared workflow_specs even when both --skip-version-check and --force are set (B1 mirror)", async () => {
const origHome = process.env.OPENRIG_HOME;
const origSpecsRoot = process.env.OPENRIG_WORKSPACE_SPECS_ROOT;
const auditHome = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-wflow-dual-override-"));
const specsRoot = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-wflow-dual-override-target-"));
process.env.OPENRIG_HOME = auditHome;
process.env.OPENRIG_WORKSPACE_SPECS_ROOT = specsRoot;
const origBootstrap = setup.bootstrapOrchestrator.bootstrap.bind(setup.bootstrapOrchestrator);
try {
const { specPath } = seedPackage();
const bundlePath = path.join(tmpDir, "dual-override-wflow.rigbundle");
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "dual-override-wflow-target-"));
try {
const workflowSrcDir = path.join(tmpDir, "test-pkg", "workflows");
fs.mkdirSync(workflowSrcDir, { recursive: true });
fs.writeFileSync(path.join(workflowSrcDir, "dualflow.yaml"), "name: dualflow\nversion: '2.0'\nsteps: []\n");
await app.request("/api/bundles/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ specPath, bundleName: "dual-override-wflow", bundleVersion: "0.1.0", outputPath: bundlePath }),
});
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "dual-override-wflow-stage-"));
try {
const tar = await import("tar");
await tar.extract({ file: bundlePath, cwd: stagingDir });
const bundleYamlPath = path.join(stagingDir, "bundle.yaml");
const original = fs.readFileSync(bundleYamlPath, "utf-8");
const tampered = `${original}\nworkflow_specs:\n - packages/test-pkg/workflows/dualflow.yaml\n`;
fs.writeFileSync(bundleYamlPath, tampered);
const { pack } = await import("../src/domain/bundle-archive.js");
await pack(stagingDir, bundlePath);
} finally {
fs.rmSync(stagingDir, { recursive: true, force: true });
}
const stub = vi.fn().mockResolvedValue({
status: "completed",
runId: "test-run-wflow-dual",
rigId: "01H000000000000000WDUAL01",
stages: [{ stage: "resolve_spec", status: "ok" }],
errors: [],
});
(setup.bootstrapOrchestrator as unknown as { bootstrap: typeof stub }).bootstrap = stub;
// Critical: both override flags set — installMeta is null at the pre-check
// site in this path; the workflow_specs-routing wrapper must STILL fire
const installRes = await app.request("/api/bundles/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
bundlePath, targetRoot, autoApprove: true,
skipVersionCheck: true, force: true,
}),
});
const body = await installRes.json();
expect(body.workflowSpecsRouting).toBeDefined();
expect(body.workflowSpecsRouting.routedCount).toBe(1);
expect(body.workflowSpecsRouting.records[0].status).toBe("routed");
const expectedTarget = path.join(specsRoot, "workflows", "packages", "test-pkg", "workflows", "dualflow.yaml");
expect(fs.existsSync(expectedTarget)).toBe(true);
expect(fs.readFileSync(expectedTarget, "utf-8")).toBe("name: dualflow\nversion: '2.0'\nsteps: []\n");
} finally {
fs.rmSync(targetRoot, { recursive: true, force: true });
}
} finally {
(setup.bootstrapOrchestrator as unknown as { bootstrap: typeof origBootstrap }).bootstrap = origBootstrap;
if (origHome === undefined) delete process.env.OPENRIG_HOME;
else process.env.OPENRIG_HOME = origHome;
if (origSpecsRoot === undefined) delete process.env.OPENRIG_WORKSPACE_SPECS_ROOT;
else process.env.OPENRIG_WORKSPACE_SPECS_ROOT = origSpecsRoot;
fs.rmSync(auditHome, { recursive: true, force: true });
fs.rmSync(specsRoot, { recursive: true, force: true });
}
});
it("POST /api/bundles/install does NOT include workflowSpecsRouting when bundle has no workflow_specs[]", async () => {
const origHome = process.env.OPENRIG_HOME;
const origSpecsRoot = process.env.OPENRIG_WORKSPACE_SPECS_ROOT;
const auditHome = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-no-wflow-test-"));
const specsRoot = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-no-wflow-target-"));
process.env.OPENRIG_HOME = auditHome;
process.env.OPENRIG_WORKSPACE_SPECS_ROOT = specsRoot;
const origBootstrap = setup.bootstrapOrchestrator.bootstrap.bind(setup.bootstrapOrchestrator);
try {
const { specPath } = seedPackage();
const bundlePath = path.join(tmpDir, "no-wflow.rigbundle");
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "no-wflow-target-"));
try {
await app.request("/api/bundles/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ specPath, bundleName: "no-wflow-installer", bundleVersion: "0.1.0", outputPath: bundlePath }),
});
const stub = vi.fn().mockResolvedValue({
status: "completed", runId: "test-run-no-wflow",
rigId: "01H000000000000000NOWFLW",
stages: [], errors: [],
});
(setup.bootstrapOrchestrator as unknown as { bootstrap: typeof stub }).bootstrap = stub;
const installRes = await app.request("/api/bundles/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bundlePath, targetRoot, autoApprove: true }),
});
const body = await installRes.json();
expect(body.workflowSpecsRouting).toBeUndefined();
} finally {
fs.rmSync(targetRoot, { recursive: true, force: true });
}
} finally {
(setup.bootstrapOrchestrator as unknown as { bootstrap: typeof origBootstrap }).bootstrap = origBootstrap;
if (origHome === undefined) delete process.env.OPENRIG_HOME;
else process.env.OPENRIG_HOME = origHome;
if (origSpecsRoot === undefined) delete process.env.OPENRIG_WORKSPACE_SPECS_ROOT;
else process.env.OPENRIG_WORKSPACE_SPECS_ROOT = origSpecsRoot;
fs.rmSync(auditHome, { recursive: true, force: true });
fs.rmSync(specsRoot, { recursive: true, force: true });
}
});
it("POST /api/bundles/install does NOT include pluginsRouting when bundle has no plugins[]", async () => {
const origHome = process.env.OPENRIG_HOME;
const auditHome = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-no-plugins-test-"));