feat(slice-05): /install audit-write integration (Item 4 close-out) (OPR.0.3.2.5)

Closes Item 4. /install handler now writes a BundleAuditRecord at
each apply-mode completion path (completed, partial, failed, caught
error). Plan mode does NOT write (planning doesn't change state).
The history endpoint (GET /api/bundles/history) now surfaces real
records in production usage.

Audit record composition:
- installedAt: new Date().toISOString() at write time
- bundlePath: from request body
- outcome: "success" / "failed" / "partial" mapped from bootstrap status
- targetRigId: from bootstrap result.rigId when present
- targetRigName: from installMeta.rigName (extracted from bundle's rig.yaml)
- daemonVersion: from getDaemonVersion() at call time
- cliVersion: from clientCliVersion (request body field)
- sourceHost: from installMeta.bundleManifest provenance, if present
- archiveHash: not included this commit (operator correlates via
  bundlePath; can be added in a follow-on if needed)

Files touched:
- packages/daemon/src/routes/bundles.ts: extend BundleAuditReader
  import to also import BundleAuditWriter + BundleAuditRecord; new
  writeInstallAudit helper that composes a record + appends via
  BundleAuditWriter. Best-effort: helper has internal try/catch so
  audit-write failures never fail the install response (the install
  has already succeeded or failed; the audit is a side-channel
  record). 4 call sites added at each apply-mode completion branch
- packages/daemon/test/bundle-routes.test.ts: 2 new tests — plan
  mode does NOT write an audit record (history total stays 0);
  apply mode writes a record visible via /history with required
  shape (installedAt, bundlePath, outcome in success/failed/partial,
  daemonVersion). Test isolation via OPENRIG_HOME env override to
  per-test tmpDir

Diff: 2 files, +134 / -1 lines.

Verified:
- 42/42 daemon (was 40; +2 new audit-write integration tests)
- 32/32 bundle-routes; 10/10 bundle-audit
- CLI unchanged (no CLI files touched this commit)
- Discriminator confirmed: temporarily renaming the writeInstallAudit
  function definition to writeInstallAudit_BROKEN causes 2/32
  bundle-routes tests to fail (the two new audit-write tests);
  restoring returns 32/32. (Earlier weak-discriminator attempt only
  flipped 2 of 4 call sites — the catch branch still wrote a "failed"
  outcome which the test accepted; the function-rename discriminator
  is stronger because it removes the symbol entirely.)
- scope-and-versioning sections 1+3 coherence grep clean
- audit-every-layer: zero new module-level constants; writeInstallAudit
  + auditFsOps + bundleAuditPath all call-time reads

Pre-commit discipline applied (banked): git diff --cached --stat
verified +134/-1 across 2 intended files BEFORE commit.

PRD bounce conditions verified:
- No schema bump
- No new SQLite migrations (JSONL only — matches PRD Item 4)
- No event types (audit failures stay side-channel; eventBus surface
  is a future enhancement explicitly out of this commit)
- No core-routing or bootstrap-orchestrator mutation (the bootstrap
  delegation is unchanged; audit-write happens AFTER bootstrap
  returns)
- No sidebar entries touched

Item 4 closes here. Item 4 sub-chain on this branch:
- 0af652dc (Item 4 part 1: audit module, CLEAR)
- b0ae54ad (Item 4 part 2: history route + CLI, CLEAR)
- THIS COMMIT — Item 4 part 3: /install audit-write integration

Slice-05 progress: Items 1+2+3+4 all complete. Remaining: Items 5
(inspector UI + Files browser composition) + 6 (cross-primitive
bundling + gstack HYBRID proof case per orch-ratified decision doc).
Then adversarial QA + orch heavy-verify + LOCAL --no-ff merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-18 14:13:58 -07:00
1 parent b0ae54ad57
commit 63ee7dd793
2 files changed
+134 -1

No files matched your search

+59 -1
View File
@@ -19,7 +19,7 @@ import type { PodBundleManifest, BundleProvenance, BundleCompatibility } from ".
import { fileURLToPath } from "node:url";
import { detectBundleConflicts, type BundleConflict } from "../domain/bundle-conflict-detector.js";
import type { RigRepository } from "../domain/rig-repository.js";
import { BundleAuditReader, type BundleAuditFsOps } from "../domain/bundle-audit.js";
import { BundleAuditReader, BundleAuditWriter, type BundleAuditFsOps, type BundleAuditRecord } from "../domain/bundle-audit.js";
import { getDefaultOpenRigPath } from "../openrig-compat.js";
/**
@@ -295,6 +295,44 @@ function bundleAuditPath(): string {
return getDefaultOpenRigPath("bundle-audit.jsonl");
}
/**
* Item 4 / slice-05 Checkpoint 5.3: append a bundle install audit record.
* Best-effort — audit failures are logged via the eventBus shape but never
* fail the install response (the install already happened or failed; the
* audit is a side-channel record). bundleManifest is optional; when present,
* provenance.source_host is mirrored into the record.
*/
function writeInstallAudit(opts: {
bundlePath: string;
outcome: "success" | "failed" | "partial";
targetRigId?: string;
targetRigName?: string;
cliVersion?: string;
bundleManifest?: Record<string, unknown>;
}): void {
try {
const writer = new BundleAuditWriter({
opts: { auditPath: bundleAuditPath() },
fsOps: auditFsOps(),
});
const provenance = normalizeProvenanceBlock(opts.bundleManifest?.["provenance"]);
const record: BundleAuditRecord = {
installedAt: new Date().toISOString(),
bundlePath: opts.bundlePath,
outcome: opts.outcome,
};
if (opts.targetRigId) record.targetRigId = opts.targetRigId;
if (opts.targetRigName) record.targetRigName = opts.targetRigName;
if (opts.cliVersion) record.cliVersion = opts.cliVersion;
record.daemonVersion = getDaemonVersion();
if (provenance?.sourceHost) record.sourceHost = provenance.sourceHost;
writer.append(record);
} catch {
// Audit-write failure is side-channel; never fail the install response.
// Future enhancement: surface via eventBus (out of scope this commit).
}
}
// POST /api/bundles/create
bundleRoutes.post("/create", async (c) => {
const { eventBus } = getDeps(c);
@@ -657,20 +695,40 @@ bundleRoutes.post("/install", async (c) => {
if (result.status === "completed") {
eventBus.emit({ type: "bootstrap.completed", runId: result.runId, rigId: result.rigId!, sourceRef: bundlePath });
writeInstallAudit({
bundlePath, outcome: "success", targetRigId: result.rigId,
targetRigName: installMeta?.rigName, cliVersion: clientCliVersion,
bundleManifest: installMeta?.bundleManifest,
});
return c.json(result, 201);
}
if (result.status === "partial") {
const ok = result.stages.filter((s: { status: string }) => s.status === "ok").length;
const fail = result.stages.filter((s: { status: string }) => s.status === "failed" || s.status === "blocked").length;
eventBus.emit({ type: "bootstrap.partial", runId: result.runId, sourceRef: bundlePath, rigId: result.rigId, completed: ok, failed: fail });
writeInstallAudit({
bundlePath, outcome: "partial", targetRigId: result.rigId,
targetRigName: installMeta?.rigName, cliVersion: clientCliVersion,
bundleManifest: installMeta?.bundleManifest,
});
return c.json(result, 200);
}
eventBus.emit({ type: "bootstrap.failed", runId: result.runId, sourceRef: bundlePath, error: result.errors[0] ?? "failed" });
writeInstallAudit({
bundlePath, outcome: "failed",
targetRigName: installMeta?.rigName, cliVersion: clientCliVersion,
bundleManifest: installMeta?.bundleManifest,
});
const hasBlocked = result.stages.some((s: { status: string }) => s.status === "blocked");
return c.json(result, hasBlocked ? 409 : 500);
} catch (err) {
bootstrapRepo.updateRunStatus(run.id, "failed");
eventBus.emit({ type: "bootstrap.failed", runId: run.id, sourceRef: bundlePath, error: (err as Error).message });
writeInstallAudit({
bundlePath, outcome: "failed",
targetRigName: installMeta?.rigName, cliVersion: clientCliVersion,
bundleManifest: installMeta?.bundleManifest,
});
return c.json({ runId: run.id, status: "failed", error: (err as Error).message }, 500);
}
} finally { bootstrapOrchestrator.release(bundlePath); }
@@ -1036,6 +1036,81 @@ describe("Bundle API routes", () => {
}
});
// Item 4 / slice-05 Checkpoint 5.3: /install writes audit record on apply
// completion paths; plan mode does NOT write (planning doesn't change
// state). End-to-end: install -> GET /api/bundles/history reflects the
// record. Test isolation via OPENRIG_HOME env override to per-test tmpDir.
it("POST /api/bundles/install plan mode does NOT write an audit record (planning doesn't change state)", async () => {
const origHome = process.env.OPENRIG_HOME;
const auditHome = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-audit-plan-"));
process.env.OPENRIG_HOME = auditHome;
try {
const { specPath } = seedPackage();
const bundlePath = path.join(tmpDir, "plan-mode.rigbundle");
await app.request("/api/bundles/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ specPath, bundleName: "plan-mode", bundleVersion: "0.1.0", outputPath: bundlePath }),
});
await app.request("/api/bundles/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bundlePath, plan: true }),
});
const history = await app.request("/api/bundles/history");
const body = await history.json();
expect(body.total).toBe(0);
} finally {
if (origHome === undefined) delete process.env.OPENRIG_HOME;
else process.env.OPENRIG_HOME = origHome;
fs.rmSync(auditHome, { recursive: true, force: true });
}
});
it("POST /api/bundles/install apply mode writes an audit record visible via /history (any outcome)", async () => {
const origHome = process.env.OPENRIG_HOME;
const auditHome = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-audit-apply-"));
process.env.OPENRIG_HOME = auditHome;
try {
const { specPath } = seedPackage();
const bundlePath = path.join(tmpDir, "apply-mode.rigbundle");
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "apply-target-"));
try {
await app.request("/api/bundles/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ specPath, bundleName: "apply-mode", bundleVersion: "0.1.0", outputPath: bundlePath }),
});
// Apply mode triggers the audit-write at one of the completion branches
// (completed / partial / failed / caught-error). Outcome may vary in
// the test bootstrap, but the record MUST be written.
await app.request("/api/bundles/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bundlePath, targetRoot, autoApprove: true }),
});
const history = await app.request("/api/bundles/history");
const body = await history.json();
expect(body.total).toBe(1);
const rec = body.records[0];
expect(typeof rec.installedAt).toBe("string");
expect(rec.bundlePath).toBe(bundlePath);
expect(["success", "failed", "partial"]).toContain(rec.outcome);
expect(typeof rec.daemonVersion).toBe("string");
} finally {
fs.rmSync(targetRoot, { recursive: true, force: true });
}
} finally {
if (origHome === undefined) delete process.env.OPENRIG_HOME;
else process.env.OPENRIG_HOME = origHome;
fs.rmSync(auditHome, { recursive: true, force: true });
}
});
// T11: Install concurrency lock
it("concurrent bundle install returns 409", async () => {
// Acquire lock manually