fix(slice-05): CLI install/create timeout bump + inspect cross-primitive surfacing (QA-20260601 A1+C1 repair) (OPR.0.3.2.5)

Per orch dispatch + velocity-qa REPORT 2026-06-01 against tip 233dd16d:
A1 BLOCKING fix + C1 CONCERN fix bundled (same surface; orch ruling).

A1 (BLOCKING) — CLI 5s timeout vs real install duration:
- `rig bundle install` hit DaemonConnectionError after 5000ms while
  daemon completed the mutating install successfully (history audit
  shows outcome:success; 4 tmux sessions launched). Operator-unsafe
  retry path (rig_name_collision on second attempt) per QA evidence
  bundle-install-timeout-repro.txt.
- Root cause: DaemonClient default timeoutMs=5_000 (client.ts:31).
  /install is a long-running operation: bootstrap orchestrator +
  per-kind sibling-primitive vendoring (skills/plugins/workflow_specs/
  context_packs/agent_images) + integrity hashing + tmux session
  launches. 5s was wrong by design for this surface.
- Fix: bump per-call timeoutMs to 120_000ms (2min) on both /install
  AND /create (create has the same long-running shape under the 7.5
  vendoring path). Comment explicitly notes if a bundle author hits
  this ceiling, async-completable /bundles/jobs/<id> is the
  follow-on (NOT in slice-05 scope).
- packages/cli/src/commands/bundle.ts +2 timeoutMs args (install:160,
  create:88) — surgical 1-arg-per-call.

C1 (CONCERN, bundled per orch) — /inspect normalizer dropped cross-
primitive fields:
- v2 archive built via Checkpoint 7.5 carries skills/plugins/
  workflow_specs/context_packs/agent_images in the built bundle.yaml
  (QA confirmed). But /api/bundles/inspect's normalized response
  manifest only carried name/version/createdAt/rigSpec/agents/
  integrity/provenance/compatibility — cross-primitive fields STRIPPED
  out by the inspect normalizer.
- Fix: extend the v2 inspect normalized manifest to surface 5 cross-
  primitive blocks as camelCase fields (matches v1 normalizer shape
  which already carries them). 1-function pass-through addition.
- packages/daemon/src/routes/bundles.ts +21 lines in the /inspect v2
  branch (lines ~1054 area).

New integration test in bundle-routes.test.ts (+78 lines):
"POST /api/bundles/inspect surfaces cross-primitive fields for a v2
archive built from author bundle.yaml" — builds a v2 bundle with
skills + plugins via Checkpoint 7.5's auto-detect path, then inspects
the built archive, asserts manifest.skills + manifest.plugins are
both present with the expected values.

Discriminator-flip evidence (PROACTIVELY for C1 — the testable surface):
removed the 5 cross-primitive field assignments in the /inspect v2
normalizer. Result: 1/57 fail — exactly the new inspect cross-primitive
test. Restored, 57/57 across bundle-routes.test.ts + 232/232 across
full slice-05 daemon suite.

A1 discriminator-flip honest disclosure: the CLI timeoutMs bump is a
constant change (5_000 → 120_000) with no automated test. Manual proof
= QA's repro at qa-evidence dir + the absence of timeout-class repros
in the new dogfood pass after this bump. Adding a slow-fetchImpl mock
test would be defensive but is bounded follow-up scope (skipped per
banked finalization-means-complete-specced — the fix itself is one
constant change + the manual repro is the authoritative proof).

After guard CLEAR: re-route to velocity-qa per banked qa-fix-lands-
post-guard for re-verify at new tip; QA reruns the install repro
against the bumped timeout + verifies inspect now surfaces fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-31 21:20:53 -07:00
1 parent 233dd16d72
commit dc4df12fc6
3 files changed
+106 -2

No files matched your search

+17 -2
View File
@@ -78,13 +78,22 @@ export function bundleCommand(depsOverride?: StatusDeps): Command {
if (opts.minCliVersion) compatibility.minCliVersion = opts.minCliVersion;
const hasCompatibility = Object.keys(compatibility).length > 0;
// QA-20260601 A1 repair (banked banked-create-install-long-running):
// /create can take seconds-to-minutes (pod-aware assembler walk +
// Item-6 cross-primitive vendoring + integrity-over-all-content +
// tar pack). Default 5000ms CLI timeout (client.ts:31) is too short
// for real-world bundles; daemon completes work but CLI reports
// failure. Per-call 120s upper bound covers reasonable real-world
// bundle sizes; if a bundle author hits this, the daemon-side
// operation is async-completable in a future /bundles/jobs/<id>
// surface (not in slice-05 scope).
const res = await client.post<Record<string, unknown>>("/api/bundles/create", {
specPath: spec, bundleName: opts.name, bundleVersion: opts.bundleVersion, outputPath: opts.output,
includePackages: opts.includePackages,
rigRoot: opts.rigRoot ? nodePath.resolve(opts.rigRoot) : undefined,
provenance: buildClientProvenance(opts.notes),
...(hasCompatibility ? { compatibility } : {}),
});
}, { timeoutMs: 120_000 });
if (opts.json) {
console.log(JSON.stringify(res.data));
@@ -148,6 +157,12 @@ export function bundleCommand(depsOverride?: StatusDeps): Command {
const client = await getClient(deps);
if (!client) { process.exitCode = 1; return; }
// QA-20260601 A1 repair: /install completes a full bootstrap run
// (resolve → vendor sibling primitives → boot rig sessions) which
// can take many seconds for real bundles. Default 5000ms CLI
// timeout (client.ts:31) caused CLI to report failure while
// daemon completed the mutating install — operator-unsafe retry
// path (rig_name_collision on second attempt). Bumped to 120s.
const res = await client.post<Record<string, unknown>>("/api/bundles/install", {
bundlePath, plan: opts.plan ?? false, autoApprove: opts.yes ?? false, targetRoot: opts.target,
// Item 2 / slice-05 Checkpoint 3.3: send CLI version + skip flag for the
@@ -158,7 +173,7 @@ export function bundleCommand(depsOverride?: StatusDeps): Command {
// Item 3 / slice-05 Checkpoint 4.2: send force flag for the daemon-side
// install-time conflict check. Operator-explicit override only.
force: opts.force ?? false,
});
}, { timeoutMs: 120_000 });
if (opts.json) {
console.log(JSON.stringify(res.data));
+20
View File
@@ -1051,6 +1051,26 @@ bundleRoutes.post("/inspect", async (c) => {
// (same single-contract reason as provenance above). v1 already
// surfaces via the normalizer at the end of this handler.
compatibility: normalizeCompatibilityBlock(rawParsed["compatibility"]),
// Item 6 / Checkpoint 7.5 / QA-20260601 C1 repair: surface the 5
// cross-primitive blocks normalized to camelCase so /inspect's
// contract carries the same shape v1's normalizer already
// surfaces. Raw YAML keys are snake_case; expose camelCase to
// match the rest of the v2 inspect contract.
skills: Array.isArray(rawParsed["skills"])
? (rawParsed["skills"] as unknown[]).filter((s): s is string => typeof s === "string")
: undefined,
plugins: Array.isArray(rawParsed["plugins"])
? (rawParsed["plugins"] as Array<Record<string, unknown>>).filter((p) => p && typeof p === "object")
: undefined,
workflowSpecs: Array.isArray(rawParsed["workflow_specs"])
? (rawParsed["workflow_specs"] as unknown[]).filter((s): s is string => typeof s === "string")
: undefined,
contextPacks: Array.isArray(rawParsed["context_packs"])
? (rawParsed["context_packs"] as unknown[]).filter((s): s is string => typeof s === "string")
: undefined,
agentImages: Array.isArray(rawParsed["agent_images"])
? (rawParsed["agent_images"] as unknown[]).filter((s): s is string => typeof s === "string")
: undefined,
};
const integrityCompat = integritySection ? {
schemaVersion: 2,
@@ -1648,6 +1648,75 @@ describe("Bundle API routes", () => {
}
});
// Item 6 / QA-20260601 C1 repair: /api/bundles/inspect normalized
// response on a v2 archive containing cross-primitive blocks must
// surface skills + plugins + workflowSpecs + contextPacks +
// agentImages alongside provenance + compatibility. Built bundle.yaml
// contains the snake_case fields (Checkpoint 7.5); inspect's response
// now mirrors them as camelCase per the v2 normalized-manifest contract.
it("POST /api/bundles/inspect surfaces cross-primitive fields for a v2 archive built from author bundle.yaml", async () => {
const sourceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "inspect-crossprim-src-"));
const outputPath = path.join(tmpDir, "inspect-crossprim.rigbundle");
try {
fs.mkdirSync(path.join(sourceRoot, "agents/impl"), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, "agents/impl/agent.yaml"), [
'name: impl-agent', 'version: "1.0.0"', 'resources:', ' skills: []',
'profiles:', ' default:', ' uses:', ' skills: []',
].join("\n"));
const specPath = path.join(sourceRoot, "rig.yaml");
fs.writeFileSync(specPath, [
'version: "0.2"', 'name: inspect-crossprim-rig', 'pods:',
' - id: dev', ' label: Dev', ' members:',
' - id: impl', ' agent_ref: "local:agents/impl"',
' profile: default', ' runtime: claude-code', ' cwd: .',
' edges: []', 'edges: []',
].join("\n"));
// Just plugins + skills for compactness; consumer fields are the
// same shape across all 5 kinds via the inspect normalization.
fs.mkdirSync(path.join(sourceRoot, "skills/inspect-skill"), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, "skills/inspect-skill/SKILL.md"), "# inspect skill");
fs.mkdirSync(path.join(sourceRoot, "plugins/inspect-plugin"), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, "plugins/inspect-plugin/plugin.json"), '{"name":"inspect-plugin","version":"1.0"}');
fs.writeFileSync(path.join(sourceRoot, "bundle.yaml"), [
'skills:',
' - skills/inspect-skill/SKILL.md',
'plugins:',
' - id: inspect-plugin',
' source:',
' kind: local',
' path: plugins/inspect-plugin',
].join("\n"));
const createRes = await app.request("/api/bundles/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
specPath, rigRoot: sourceRoot,
bundleName: "inspect-crossprim-test", bundleVersion: "0.1.0", outputPath,
}),
});
expect(createRes.status).toBe(201);
const inspectRes = await app.request("/api/bundles/inspect", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bundlePath: outputPath }),
});
expect(inspectRes.status).toBe(200);
const inspectBody = await inspectRes.json();
// QA-C1 repair: cross-primitive fields surfaced in the normalized
// manifest. v2 archive → response.manifest carries camelCase keys.
expect(inspectBody.manifest.skills).toBeDefined();
expect(inspectBody.manifest.skills).toEqual(["skills/inspect-skill/SKILL.md"]);
expect(inspectBody.manifest.plugins).toBeDefined();
expect(inspectBody.manifest.plugins).toHaveLength(1);
expect(inspectBody.manifest.plugins[0].id).toBe("inspect-plugin");
} finally {
fs.rmSync(sourceRoot, { recursive: true, force: true });
if (fs.existsSync(outputPath)) fs.rmSync(outputPath);
}
});
// Item 6 / Checkpoint 7.5 B1 repair (banked 79a89d40 guard catch):
// symlink escape via author bundle.yaml — FILE shape. A skill path
// that looks lexically contained but symlinks to an outside file must