feat(slice-05): provenance capture in v1+v2 bundle assemblers (Item 1 part 2) (OPR.0.3.2.5)

Extends AssembleOptions and PodAssembleOptions with optional
provenance input; assemblers populate manifest.provenance at create
time. provenance.createdAt mirrors root manifest.createdAt unless
caller pre-sets it (caller value wins for test determinism). Missing
opts.provenance keeps backward compat: no provenance recorded on the
manifest.

Builds on 82d39715 (Item 1 part 1: type layer). Route + CLI plumbing
land in next commit (Item 1 close-out).

Files touched:
- packages/daemon/src/domain/bundle-assembler.ts (v1) — AssembleOptions
  extended with optional provenance; capture at the assemble()
  manifest-construction site
- packages/daemon/src/domain/pod-bundle-assembler.ts (v2) — same shape
  for PodAssembleOptions and the v2 manifest-construction site
- packages/daemon/test/bundle-assembler.test.ts — 3 new tests covering
  full capture, caller-set-createdAt-wins, omits-when-missing
- packages/daemon/test/pod-bundle-assembler.test.ts — 3 mirrored tests
  for v2 with the existing mockFs harness

Diff: 4 files, +188 / -5 lines. 4572 tests pass post-change (no
regressions).

PRD bounce conditions verified:
- No schema bump (consumes optional manifest field added in 82d39715)
- No new SQLite migrations
- No event types
- No core-routing or bootstrap mutation
- No sidebar entries touched
- scope-and-versioning sections 1 and 3 coherence re-audit clean

Watched risk applied — audit-every-layer (function-level call sites
AND module-level constants): the opts-injection pattern means
assemblers READ provenance only from opts at assemble() call time.
NO module-level constants read os.hostname, process.env, or daemon
or cli versions at import time in either assembler. Verified via
import grep: bundle-assembler imports node:path + 3 internal types;
pod-bundle-assembler imports node:path + node:crypto + 5 internal
types. Caller-side reads (route handler, CLI) land in the next
commit where call-time injection is the correct boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-18 12:29:53 -07:00
1 parent 82d397156f
commit 3b7432e41c
4 files changed
+188 -5

No files matched your search

+18 -2
View File
@@ -2,7 +2,7 @@ import nodePath from "node:path";
import { LegacyRigSpecCodec as RigSpecCodec } from "./rigspec-codec.js"; // TODO: AS-T08b — migrate to pod-aware RigSpec
import { LegacyRigSpecSchema as RigSpecSchema } from "./rigspec-schema.js"; // TODO: AS-T08b — migrate to pod-aware RigSpec
// TODO: AS-T12 — migrate to pod-aware bundle types
import { serializeLegacyBundleManifest as serializeBundleManifest, type LegacyBundleManifest as BundleManifest } from "./bundle-types.js";
import { serializeLegacyBundleManifest as serializeBundleManifest, type LegacyBundleManifest as BundleManifest, type BundleProvenance } from "./bundle-types.js";
export interface AssemblerFsOps {
readFile: (path: string) => string;
@@ -26,6 +26,15 @@ export interface AssembleOptions {
outputDir: string;
bundleName: string;
bundleVersion: string;
/**
* Optional Item-1 provenance input. Caller fills the fields it knows
* (sourceHost, authorSession, daemonVersion, cliVersion, sourceRigId,
* sourceRigName, notes). The assembler mirrors root createdAt into
* provenance.createdAt unless caller pre-set it (caller value wins
* for test determinism). Missing block = no provenance recorded
* (backward compat).
*/
provenance?: BundleProvenance;
}
/**
@@ -104,14 +113,21 @@ export class LegacyBundleAssembler {
}
// Generate manifest (no integrity — P7-T02 adds it)
const createdAt = new Date().toISOString();
const manifest: BundleManifest = {
schemaVersion: 1,
name: opts.bundleName,
version: opts.bundleVersion,
createdAt: new Date().toISOString(),
createdAt,
rigSpec: "rig.yaml",
packages: packageEntries,
};
if (opts.provenance) {
manifest.provenance = {
...opts.provenance,
createdAt: opts.provenance.createdAt ?? createdAt,
};
}
// Write bundle.yaml
this.fs.writeFile(
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
import { RigSpecCodec } from "./rigspec-codec.js";
import { RigSpecSchema } from "./rigspec-schema.js";
import { resolveAgentRef, type AgentResolverFsOps } from "./agent-resolver.js";
import { serializePodBundleManifest, type PodBundleManifest, type PodBundleAgentEntry, type PodBundleAgentImportEntry } from "./bundle-types.js";
import { serializePodBundleManifest, type PodBundleManifest, type PodBundleAgentEntry, type PodBundleAgentImportEntry, type BundleProvenance } from "./bundle-types.js";
import type { RigSpec, StartupBlock } from "./types.js";
export interface PodAssemblerFsOps extends AgentResolverFsOps {
@@ -19,6 +19,15 @@ export interface PodAssembleOptions {
outputDir: string;
bundleName: string;
bundleVersion: string;
/**
* Optional Item-1 provenance input. Caller fills the fields it knows
* (sourceHost, authorSession, daemonVersion, cliVersion, sourceRigId,
* sourceRigName, notes). The assembler mirrors root createdAt into
* provenance.createdAt unless caller pre-set it (caller value wins
* for test determinism). Missing block = no provenance recorded
* (backward compat).
*/
provenance?: BundleProvenance;
}
export interface PodAssembleResult {
@@ -163,15 +172,22 @@ export class PodBundleAssembler {
this.fs.writeFile(nodePath.join(opts.outputDir, "rig.yaml"), rewrittenRigYaml);
// 4. Build manifest
const createdAt = new Date().toISOString();
const manifest: PodBundleManifest = {
schemaVersion: 2,
name: opts.bundleName,
version: opts.bundleVersion,
createdAt: new Date().toISOString(),
createdAt,
rigSpec: "rig.yaml",
agents: agentEntries,
cultureFile: rigSpec.cultureFile,
};
if (opts.provenance) {
manifest.provenance = {
...opts.provenance,
createdAt: opts.provenance.createdAt ?? createdAt,
};
}
// Write manifest
this.fs.writeFile(
+77 -1
View File
@@ -5,7 +5,7 @@ import os from "node:os";
// TODO: AS-T12 — migrate to pod-aware bundle assembler
import { LegacyBundleAssembler as BundleAssembler, type AssemblerFsOps } from "../src/domain/bundle-assembler.js";
// TODO: AS-T12 — migrate to pod-aware bundle types
import { parseLegacyBundleManifest as parseBundleManifest, validateLegacyBundleManifest as validateBundleManifest, normalizeLegacyBundleManifest as normalizeBundleManifest } from "../src/domain/bundle-types.js";
import { parseLegacyBundleManifest as parseBundleManifest, validateLegacyBundleManifest as validateBundleManifest, normalizeLegacyBundleManifest as normalizeBundleManifest, type BundleProvenance } from "../src/domain/bundle-types.js";
const VALID_SPEC = `
schema_version: 1
@@ -241,4 +241,80 @@ describe("BundleAssembler", () => {
const normalized = normalizeBundleManifest(parsed);
expect(normalized.packages[0]!.originalSources).toEqual(["local:./a", "local:./b"]);
});
// Item 1 — provenance capture (slice-05 Checkpoint 2 part 2)
it("captures full provenance from opts into manifest", () => {
const specPath = writeSpec();
const pkgDir = writePkg("pkg", { "package.yaml": "name: pkg" });
const outputDir = path.join(tmpDir, "staging");
const assembler = new BundleAssembler({ fsOps: realFsOps() });
const provenance: BundleProvenance = {
sourceHost: "test-host.local",
authorSession: "velocity-driver@openrig-velocity",
sourceRigId: "01KQEQPN4MQJN0DHBM5CQ0N8D7",
sourceRigName: "openrig-velocity",
daemonVersion: "0.3.2",
cliVersion: "0.3.2",
notes: "checkpoint-2-part-2 fixture",
};
const manifest = assembler.assemble({
specPath, outputDir, bundleName: "test", bundleVersion: "1.0",
packages: [{ name: "pkg", version: "1.0", sourcePath: pkgDir, originalSource: "local:./pkg", manifestHash: "h1" }],
provenance,
});
expect(manifest.provenance).toBeDefined();
expect(manifest.provenance?.sourceHost).toBe("test-host.local");
expect(manifest.provenance?.authorSession).toBe("velocity-driver@openrig-velocity");
expect(manifest.provenance?.daemonVersion).toBe("0.3.2");
expect(manifest.provenance?.cliVersion).toBe("0.3.2");
expect(manifest.provenance?.notes).toBe("checkpoint-2-part-2 fixture");
// createdAt mirrors root when opts.provenance.createdAt unset
expect(manifest.provenance?.createdAt).toBe(manifest.createdAt);
// Round-trip via the on-disk bundle.yaml
const diskYaml = fs.readFileSync(path.join(outputDir, "bundle.yaml"), "utf-8");
const parsed = parseBundleManifest(diskYaml);
const validation = validateBundleManifest(parsed, { requireIntegrity: false });
expect(validation.valid).toBe(true);
const normalized = normalizeBundleManifest(parsed);
expect(normalized.provenance?.sourceHost).toBe("test-host.local");
expect(normalized.provenance?.notes).toBe("checkpoint-2-part-2 fixture");
});
it("respects opts.provenance.createdAt when caller pre-sets it (test determinism)", () => {
const specPath = writeSpec();
const pkgDir = writePkg("pkg", { "package.yaml": "name: pkg" });
const outputDir = path.join(tmpDir, "staging");
const assembler = new BundleAssembler({ fsOps: realFsOps() });
const fixedCreatedAt = "2026-01-01T00:00:00Z";
const manifest = assembler.assemble({
specPath, outputDir, bundleName: "test", bundleVersion: "1.0",
packages: [{ name: "pkg", version: "1.0", sourcePath: pkgDir, originalSource: "local:./pkg", manifestHash: "h1" }],
provenance: { createdAt: fixedCreatedAt, sourceHost: "h" },
});
expect(manifest.provenance?.createdAt).toBe(fixedCreatedAt);
// root createdAt is independent (real-time stamp)
expect(manifest.createdAt).not.toBe(fixedCreatedAt);
});
it("omits provenance when opts.provenance not provided (backward compat)", () => {
const specPath = writeSpec();
const pkgDir = writePkg("pkg", { "package.yaml": "name: pkg" });
const outputDir = path.join(tmpDir, "staging");
const assembler = new BundleAssembler({ fsOps: realFsOps() });
const manifest = assembler.assemble({
specPath, outputDir, bundleName: "test", bundleVersion: "1.0",
packages: [{ name: "pkg", version: "1.0", sourcePath: pkgDir, originalSource: "local:./pkg", manifestHash: "h1" }],
});
expect(manifest.provenance).toBeUndefined();
// on-disk yaml must not contain provenance section
const diskYaml = fs.readFileSync(path.join(outputDir, "bundle.yaml"), "utf-8");
expect(diskYaml).not.toContain("provenance:");
});
});
@@ -505,6 +505,81 @@ describe("PodBundleAssembler", () => {
// Deferred: full golden-path integration (assemble -> validate -> preflight -> instantiate)
// will be verified at Checkpoint 2 when AS-T11 + AS-T08b land
// Item 1 — provenance capture (slice-05 Checkpoint 2 part 2)
it("v2: captures provenance from opts into manifest", () => {
const spec = makeRigSpec();
const files: Record<string, string> = {
[`${RIG_ROOT}/rig.yaml`]: rigSpecYaml(spec),
[`${RIG_ROOT}/agents/impl/agent.yaml`]: validAgentYaml("impl"),
};
const fs = mockFs(files);
const assembler = new PodBundleAssembler({ fsOps: fs });
const result = assembler.assemble({
rigRoot: RIG_ROOT,
rigSpecPath: `${RIG_ROOT}/rig.yaml`,
outputDir: "/tmp/bundle-staging-prov",
bundleName: "test-bundle",
bundleVersion: "1.0.0",
provenance: {
sourceHost: "test-host",
authorSession: "velocity-driver@openrig-velocity",
daemonVersion: "0.3.2",
cliVersion: "0.3.2",
notes: "v2 capture fixture",
},
});
expect(result.manifest.provenance).toBeDefined();
expect(result.manifest.provenance?.sourceHost).toBe("test-host");
expect(result.manifest.provenance?.authorSession).toBe("velocity-driver@openrig-velocity");
expect(result.manifest.provenance?.daemonVersion).toBe("0.3.2");
expect(result.manifest.provenance?.notes).toBe("v2 capture fixture");
// createdAt mirrors root
expect(result.manifest.provenance?.createdAt).toBe(result.manifest.createdAt);
});
it("v2: respects opts.provenance.createdAt when pre-set (test determinism)", () => {
const spec = makeRigSpec();
const files: Record<string, string> = {
[`${RIG_ROOT}/rig.yaml`]: rigSpecYaml(spec),
[`${RIG_ROOT}/agents/impl/agent.yaml`]: validAgentYaml("impl"),
};
const fs = mockFs(files);
const assembler = new PodBundleAssembler({ fsOps: fs });
const fixedCreatedAt = "2026-01-01T00:00:00Z";
const result = assembler.assemble({
rigRoot: RIG_ROOT,
rigSpecPath: `${RIG_ROOT}/rig.yaml`,
outputDir: "/tmp/bundle-staging-fixed-createdat",
bundleName: "test", bundleVersion: "1.0",
provenance: { createdAt: fixedCreatedAt, sourceHost: "h" },
});
expect(result.manifest.provenance?.createdAt).toBe(fixedCreatedAt);
expect(result.manifest.createdAt).not.toBe(fixedCreatedAt);
});
it("v2: omits provenance when opts.provenance not provided (backward compat)", () => {
const spec = makeRigSpec();
const files: Record<string, string> = {
[`${RIG_ROOT}/rig.yaml`]: rigSpecYaml(spec),
[`${RIG_ROOT}/agents/impl/agent.yaml`]: validAgentYaml("impl"),
};
const fs = mockFs(files);
const assembler = new PodBundleAssembler({ fsOps: fs });
const result = assembler.assemble({
rigRoot: RIG_ROOT,
rigSpecPath: `${RIG_ROOT}/rig.yaml`,
outputDir: "/tmp/bundle-staging-no-prov",
bundleName: "test", bundleVersion: "1.0",
});
expect(result.manifest.provenance).toBeUndefined();
});
});
describe("PodBundleManifest validation", () => {