mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 22:58:42 -10:00
fix(cli): Restore-Packet M2c-CLI R2 — --source-jsonl provenance from parsed session_meta
Closes guard BLOCK on M2c-CLI ate5de3ab. Per dispatch qitem-20260502014948-af2d4713 + BLOCK artifact at substrate/rigs/openrig-velocity/state/orch/restore-packet-M2c-cli-guard-checkpoint-block-2026-05-02.md. Bug (R1 /e5de3ab): - `--source-jsonl` adapter wrote the JSONL FILE PATH into restore-summary.json.source_session_id (not the parsed session id). - packet-writer.ts:174-175 silently derived source_rig from sourceSessionId via @ split, masking the bug as `source_rig: "unknown"` for any value without @ shape. - Schema-valid but semantically WRONG; exactly the source-provenance drift the restore-packet standard exists to prevent. Writer-internal tests bypassed the parse-to-write provenance path entirely. Fix: 1. CLI adapter (commands/restore-packet.ts): after parseCodexJsonl / parseClaudeTranscript, derive canonical (source_session_id, source_rig) honestly via new deriveProvenance() helper. - For --source-jsonl: prefer parsed structured.sessionMeta?.sessionId over the file path; fail with explicit operator guidance if absent and no override flag supplied. - source_rig: <seat>@<rig> regex split; fail if shape doesn't match and no override flag supplied. No silent "unknown" fallback. 2. New flags --source-session-id-override and --source-rig-override give operators an explicit honest path when source JSONL has no session_meta (e.g., raw test fixtures, mid-session captures) or session id is bare. 3. packet-writer.ts: WritePacketOptions adds required `sourceRig: string`. Writer no longer parses sourceSessionId — that derivation moved to the adapter where it belongs. Tests (TDD red-then-green; 5 new R2 cases at the CLI surface using createProgram().parseAsync with synthetic JSONL fixtures, exercising the parse-to-writePacket adapter path that 75/75 was bypassing): - guard reproducer: --source-jsonl with session_meta sets source_session_id from parsed id and source_rig from <seat>@<rig> split. - --source-jsonl with NO session_meta and NO overrides fails with explicit operator guidance referencing both override flags. - --source-jsonl with NO session_meta + both overrides succeeds with overridden provenance. - --source-jsonl with bare-id session_meta fails without --source-rig-override (no silent unknown). - --source-jsonl with --source-session-id-override overrides parsed session_meta id (override-wins precedence). Updates to existing tests: - M2c-CLI atomic-emission, Velocity-shape round-trip, M2b R2 nested-content fixtures pass `sourceRig:` explicitly to writePacket() (now required). - Mock-daemon --source-session round-trip uses --source-rig-override for bare "src-session" fixture and asserts source_rig is honored. Re-verification (all green; honest baseline match per Quality Lesson v8): - npx vitest run test/restore-packet.test.ts → 80/80 pass (75 baseline + 5 R2). - npx tsc --noEmit → exit 0. - npm run build → exit 0; dist/schemas/restore-summary.schema.json present. - Compiled-output guard reproducer (BLOCK artifact verbatim) → source_session_id="velocity-driver@openrig-velocity", source_rig="openrig-velocity", validation_valid=true. - Full CLI suite → 720/724; 4 failures match named flake baseline (chatroom wait timing, config-store env pollution, two preflight host-port). No regression vs 715/719 + same 4 flakes pre-R2. M2c-Daemon surfaces (packages/daemon/src/routes/transcripts.ts + packages/daemon/test/transcript-routes.test.ts) NOT touched per orch sub-chunking. Velocity prior-art .mjs UNCHANGED. M3 read/validate stubs preserved. No auth tokens or credentials in fixtures (Quality Lesson v9). Quality Lesson v12 (NEW; filed at this dispatch): writer-internal round trips are not enough for CLI source adapters — slices that claim adapter functionality MUST include at least one compiled-output CLI-path provenance check that exercises the actual command surface (createProgram().parseAsync against synthetic JSONL with realistic session_meta). Filed inline at this commit; will be formalized at M2 marshal-acceptance close. Posture: full QA/testing window (matches M2 declared posture). Dispatch: qitem-20260502014948-af2d4713. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
e5de3abd12
commit
a8401f6181
3 files changed
+350
-5
No files matched your search
@@ -30,6 +30,19 @@ interface WriteOptions {
|
||||
sourceSession?: string;
|
||||
sourceJsonl?: string;
|
||||
sourceRuntime?: string;
|
||||
/**
|
||||
* R2: explicit operator override for source_session_id. Wins over parsed
|
||||
* session_meta and over the --source-session arg. Required when JSONL
|
||||
* has no session_meta record.
|
||||
*/
|
||||
sourceSessionIdOverride?: string;
|
||||
/**
|
||||
* R2: explicit operator override for source_rig. Wins over <seat>@<rig>
|
||||
* derivation. Required when the canonical source_session_id is bare
|
||||
* (no @) and the operator has not supplied --source-session-id-override
|
||||
* with @ shape.
|
||||
*/
|
||||
sourceRigOverride?: string;
|
||||
target: string;
|
||||
targetRig?: string;
|
||||
targetRuntime?: string;
|
||||
@@ -44,6 +57,53 @@ interface WriteOptions {
|
||||
generatorVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* R2: derive canonical (source_session_id, source_rig) per honest-fallback
|
||||
* policy. Override flags > parsed session_meta (jsonl) > supplied --source-session.
|
||||
*
|
||||
* Returns either the derived pair (with no exception) OR throws an Error
|
||||
* containing the explicit operator-facing guidance message. The action
|
||||
* caller catches and routes to reportFailure().
|
||||
*/
|
||||
function deriveProvenance(
|
||||
opts: WriteOptions,
|
||||
parsedSessionId: string | null,
|
||||
hasJsonl: boolean,
|
||||
): { sourceSessionId: string; sourceRig: string } {
|
||||
// Step 1: source_session_id — override > parsed > --source-session arg.
|
||||
let sourceSessionId: string;
|
||||
if (typeof opts.sourceSessionIdOverride === "string" && opts.sourceSessionIdOverride.length > 0) {
|
||||
sourceSessionId = opts.sourceSessionIdOverride;
|
||||
} else if (hasJsonl) {
|
||||
if (!parsedSessionId) {
|
||||
throw new Error(
|
||||
"no session_meta record in source JSONL; supply --source-session-id-override <id> AND --source-rig-override <rig> to set provenance explicitly. Silent fallback to file-path-as-session-id is not allowed (M2c-CLI R2 honest-provenance policy).",
|
||||
);
|
||||
}
|
||||
sourceSessionId = parsedSessionId;
|
||||
} else {
|
||||
// hasSession path: operator-named session is authoritative.
|
||||
sourceSessionId = opts.sourceSession!;
|
||||
}
|
||||
|
||||
// Step 2: source_rig — override > <seat>@<rig> split.
|
||||
let sourceRig: string;
|
||||
if (typeof opts.sourceRigOverride === "string" && opts.sourceRigOverride.length > 0) {
|
||||
sourceRig = opts.sourceRigOverride;
|
||||
} else {
|
||||
const m = /^[^@]+@([^@]+)$/.exec(sourceSessionId);
|
||||
const captured = m?.[1];
|
||||
if (!captured) {
|
||||
throw new Error(
|
||||
`could not derive source_rig from session id '${sourceSessionId}' (does not match <seat>@<rig> shape); supply --source-rig-override <rig> explicitly. Silent fallback to source_rig:"unknown" is not allowed (M2c-CLI R2 honest-provenance policy).`,
|
||||
);
|
||||
}
|
||||
sourceRig = captured;
|
||||
}
|
||||
|
||||
return { sourceSessionId, sourceRig };
|
||||
}
|
||||
|
||||
function reportFailure(message: string): void {
|
||||
console.error(`rig restore-packet write: ${message}`);
|
||||
process.exitCode = 2;
|
||||
@@ -92,6 +152,7 @@ function buildWritePacketOptions(
|
||||
sourceRuntime: SourceRuntime,
|
||||
sourceCwdFallback: string,
|
||||
sourceSessionId: string,
|
||||
sourceRig: string,
|
||||
): WritePacketOptions {
|
||||
const trustRankingDefault = "rig_whoami,bounded_latest_transcript";
|
||||
const trustRankingRaw = opts.sourceTrustRanking ?? trustRankingDefault;
|
||||
@@ -115,6 +176,7 @@ function buildWritePacketOptions(
|
||||
authorityBoundaries: opts.authorityBoundaries ?? "Restored seat authority per source session role.",
|
||||
sourceTrustRanking: trustRanking,
|
||||
sourceSessionId,
|
||||
sourceRig,
|
||||
sourceCwd: structured.sessionMeta?.cwd ?? sourceCwdFallback,
|
||||
generatorVersion: opts.generatorVersion ?? "[email protected]",
|
||||
includeFullTranscript: structured.messages.length > 0,
|
||||
@@ -187,6 +249,14 @@ export function restorePacketCommand(depsOverride?: RestorePacketDeps): Command
|
||||
"--generator-version <version>",
|
||||
'Generator version identifier (default: "[email protected]")',
|
||||
)
|
||||
.option(
|
||||
"--source-session-id-override <id>",
|
||||
"Operator override for source_session_id (R2; required when JSONL has no session_meta record)",
|
||||
)
|
||||
.option(
|
||||
"--source-rig-override <rig>",
|
||||
"Operator override for source_rig (R2; required when canonical session id is bare with no <seat>@<rig> shape)",
|
||||
)
|
||||
.action(async (opts: WriteOptions) => {
|
||||
const hasSession = typeof opts.sourceSession === "string" && opts.sourceSession.length > 0;
|
||||
const hasJsonl = typeof opts.sourceJsonl === "string" && opts.sourceJsonl.length > 0;
|
||||
@@ -206,18 +276,15 @@ export function restorePacketCommand(depsOverride?: RestorePacketDeps): Command
|
||||
try {
|
||||
let content: string;
|
||||
let sourceCwdFallback: string;
|
||||
let sourceSessionId: string;
|
||||
if (hasJsonl) {
|
||||
const jsonlPath = resolvePath(opts.sourceJsonl!);
|
||||
content = readFileSync(jsonlPath, "utf-8");
|
||||
sourceCwdFallback = "/";
|
||||
sourceSessionId = jsonlPath;
|
||||
} else {
|
||||
const session = opts.sourceSession!;
|
||||
const fetched = await fetchSourceTranscriptViaDaemon(session, depsOverride);
|
||||
content = fetched.content;
|
||||
sourceCwdFallback = fetched.sourceCwd ?? "/";
|
||||
sourceSessionId = session;
|
||||
}
|
||||
|
||||
let runtime: SourceRuntime | null = null;
|
||||
@@ -242,7 +309,26 @@ export function restorePacketCommand(depsOverride?: RestorePacketDeps): Command
|
||||
? parseCodexJsonl(content)
|
||||
: parseClaudeTranscript(content);
|
||||
|
||||
const writeOpts = buildWritePacketOptions(opts, structured, runtime, sourceCwdFallback, sourceSessionId);
|
||||
// R2: derive (source_session_id, source_rig) honestly. Override
|
||||
// flags > parsed session_meta (jsonl) > supplied --source-session
|
||||
// arg. Throws an Error with explicit operator guidance when the
|
||||
// honest-provenance policy can't be satisfied.
|
||||
let provenance: { sourceSessionId: string; sourceRig: string };
|
||||
try {
|
||||
provenance = deriveProvenance(opts, structured.sessionMeta?.sessionId ?? null, hasJsonl);
|
||||
} catch (err) {
|
||||
reportFailure((err as Error).message);
|
||||
return;
|
||||
}
|
||||
|
||||
const writeOpts = buildWritePacketOptions(
|
||||
opts,
|
||||
structured,
|
||||
runtime,
|
||||
sourceCwdFallback,
|
||||
provenance.sourceSessionId,
|
||||
provenance.sourceRig,
|
||||
);
|
||||
const result = await writePacket(writeOpts);
|
||||
|
||||
// Per Quality Lesson v9: log only metadata, NOT transcript content.
|
||||
|
||||
@@ -57,6 +57,13 @@ export interface WritePacketOptions {
|
||||
sourceTrustRanking: string[];
|
||||
/** Source session id. */
|
||||
sourceSessionId: string;
|
||||
/**
|
||||
* Source rig name. Per M2c-CLI R2: passed explicitly by the CLI adapter
|
||||
* (computed from parsed session_meta `<seat>@<rig>` split or operator
|
||||
* --source-rig-override). The writer does NOT derive this from
|
||||
* sourceSessionId — that hidden derivation was the M2c-CLI R1 bug.
|
||||
*/
|
||||
sourceRig: string;
|
||||
/** Source cwd (absolute path). */
|
||||
sourceCwd: string;
|
||||
/** Generator version identifier (e.g., "[email protected]"). */
|
||||
@@ -172,7 +179,7 @@ function buildSummary(opts: WritePacketOptions): Record<string, unknown> {
|
||||
const messageCount = Math.min(opts.structured.messages.length, TRANSCRIPT_BOUND);
|
||||
const summary: Record<string, unknown> = {
|
||||
source_session_id: opts.sourceSessionId,
|
||||
source_rig: opts.sourceSessionId.includes("@") ? opts.sourceSessionId.split("@")[1] ?? "unknown" : "unknown",
|
||||
source_rig: opts.sourceRig,
|
||||
source_runtime: opts.sourceRuntime,
|
||||
source_cwd: opts.sourceCwd,
|
||||
target_rig: opts.targetRig,
|
||||
|
||||
@@ -861,6 +861,7 @@ describe("M2c-CLI packet-writer atomic emission", () => {
|
||||
authorityBoundaries: "May edit packages/cli/ and packages/daemon/ within M2c boundary; no M3+ surfaces.",
|
||||
sourceTrustRanking: ["rig_whoami", "bounded_latest_transcript"],
|
||||
sourceSessionId: "velocity-driver@openrig-velocity",
|
||||
sourceRig: "openrig-velocity",
|
||||
sourceCwd: "/Users/wrandom/code/projects/openrig-hub",
|
||||
generatorVersion: "[email protected]",
|
||||
includeFullTranscript: false,
|
||||
@@ -1046,6 +1047,7 @@ describe("M2c-CLI Velocity-shape round-trip (synthetic 4-role fixtures)", () =>
|
||||
authorityBoundaries: `${role} authority for Velocity-replay context.`,
|
||||
sourceTrustRanking: ["rig_whoami", "bounded_latest_transcript"],
|
||||
sourceSessionId: `velocity-${role}@openrig-velocity-code`,
|
||||
sourceRig: "openrig-velocity-code",
|
||||
sourceCwd: "/Users/wrandom/code/projects/openrig-hub",
|
||||
generatorVersion: "[email protected]",
|
||||
includeFullTranscript: true,
|
||||
@@ -1127,6 +1129,11 @@ describe("M2c-CLI redaction + omitted-record round-trip end-to-end", () => {
|
||||
"--target", targetDir,
|
||||
"--target-rig", "openrig-velocity-claude",
|
||||
"--target-runtime", "claude-code",
|
||||
// src-session is bare (no @); operator must supply --source-rig-override
|
||||
// per R2 honest-fallback policy.
|
||||
"--source-rig-override", "openrig-test",
|
||||
"--current-work-summary", "mock-daemon round-trip with bare session id + rig override.",
|
||||
"--authority-boundaries", "test-only.",
|
||||
]);
|
||||
|
||||
// Daemon was queried at the new full-read route; no mutation methods called.
|
||||
@@ -1139,6 +1146,8 @@ describe("M2c-CLI redaction + omitted-record round-trip end-to-end", () => {
|
||||
const validation = validateRestoreSummary(summary);
|
||||
expect(validation.valid, JSON.stringify(validation.errors)).toBe(true);
|
||||
expect(summary.source_session_id).toBe("src-session");
|
||||
// R2: operator-supplied --source-rig-override is honored.
|
||||
expect(summary.source_rig).toBe("openrig-test");
|
||||
});
|
||||
|
||||
it("M2b R2 nested Claude content fixture: round-trips with non-zero omittedCounts in summary", async () => {
|
||||
@@ -1185,6 +1194,7 @@ describe("M2c-CLI redaction + omitted-record round-trip end-to-end", () => {
|
||||
authorityBoundaries: "Velocity authority.",
|
||||
sourceTrustRanking: ["rig_whoami"],
|
||||
sourceSessionId: "claude-session-id",
|
||||
sourceRig: "openrig-velocity-claude",
|
||||
sourceCwd: "/Users/wrandom/code/projects/openrig-hub",
|
||||
generatorVersion: "[email protected]",
|
||||
includeFullTranscript: false,
|
||||
@@ -1198,3 +1208,245 @@ describe("M2c-CLI redaction + omitted-record round-trip end-to-end", () => {
|
||||
p.path === "/Users/wrandom/code/projects/openrig-hub")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// M2c-CLI R2 — `--source-jsonl` provenance fix.
|
||||
//
|
||||
// Guard BLOCKED M2c-CLI at openrig e5de3ab because the `--source-jsonl`
|
||||
// adapter wrote the JSONL FILE PATH into `restore-summary.json.source_session_id`
|
||||
// and forced `source_rig: "unknown"`, EVEN WHEN the parsed Codex JSONL had
|
||||
// `session_meta.payload.id = "<seat>@<rig>"` available.
|
||||
//
|
||||
// These tests exercise the CLI command surface end-to-end (Quality Lesson
|
||||
// v12 candidate) — they parseAsync through createProgram() against synthetic
|
||||
// JSONL fixtures with realistic session_meta records, so the bug between
|
||||
// parse and writePacket call IS exercised. Writer-internal tests bypass
|
||||
// this path by passing manually-constructed sourceSessionId.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("M2c-CLI R2 --source-jsonl provenance from parsed session_meta", () => {
|
||||
let tmpRoot: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
const fs = await import("node:fs");
|
||||
const os = await import("node:os");
|
||||
const path = await import("node:path");
|
||||
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "restore-packet-r2-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const fs = await import("node:fs");
|
||||
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
// The exact reproducer fixture from the guard's BLOCK artifact.
|
||||
function jsonlWithSessionMeta(): string {
|
||||
return [
|
||||
JSON.stringify({
|
||||
type: "session_meta",
|
||||
payload: {
|
||||
cwd: "/Users/wrandom/code/projects/openrig-hub",
|
||||
id: "velocity-driver@openrig-velocity",
|
||||
},
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "response_item",
|
||||
payload: {
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: "hello direct source",
|
||||
},
|
||||
}),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function jsonlWithoutSessionMeta(): string {
|
||||
return [
|
||||
JSON.stringify({
|
||||
type: "response_item",
|
||||
payload: {
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: "no session_meta in this JSONL",
|
||||
},
|
||||
}),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function jsonlWithBareSessionMetaId(): string {
|
||||
// session_meta exists but the id is a bare token, not <seat>@<rig> shape.
|
||||
return [
|
||||
JSON.stringify({
|
||||
type: "session_meta",
|
||||
payload: { cwd: "/x", id: "bare-id-no-at" },
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "response_item",
|
||||
payload: { type: "message", role: "user", content: "hi" },
|
||||
}),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
it("guard reproducer: --source-jsonl with session_meta sets source_session_id from parsed id and source_rig from <seat>@<rig> split", async () => {
|
||||
const { createProgram } = await import("../src/index.js");
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const src = path.join(tmpRoot, "source.jsonl");
|
||||
fs.writeFileSync(src, jsonlWithSessionMeta(), "utf8");
|
||||
const target = path.join(tmpRoot, "packet");
|
||||
|
||||
const program = createProgram();
|
||||
program.exitOverride();
|
||||
await program.parseAsync([
|
||||
"node", "rig", "restore-packet", "write",
|
||||
"--source-jsonl", src,
|
||||
"--target", target,
|
||||
"--target-rig", "openrig-velocity-claude",
|
||||
"--target-runtime", "claude-code",
|
||||
"--current-work-summary", "R2 guard reproducer: provenance from parsed session_meta.",
|
||||
"--authority-boundaries", "R2 provenance check only.",
|
||||
]);
|
||||
|
||||
expect(fs.existsSync(target)).toBe(true);
|
||||
const summary = JSON.parse(fs.readFileSync(path.join(target, "restore-summary.json"), "utf-8"));
|
||||
const validation = validateRestoreSummary(summary);
|
||||
expect(validation.valid, JSON.stringify(validation.errors)).toBe(true);
|
||||
// Bug reproducer's expected values per BLOCK artifact:
|
||||
expect(summary.source_session_id).toBe("velocity-driver@openrig-velocity");
|
||||
expect(summary.source_rig).toBe("openrig-velocity");
|
||||
expect(summary.source_cwd).toBe("/Users/wrandom/code/projects/openrig-hub");
|
||||
// session_session_id MUST NOT be the file path.
|
||||
expect(summary.source_session_id).not.toContain("/source.jsonl");
|
||||
expect(summary.source_session_id).not.toContain(tmpRoot);
|
||||
// transcript.md emitted (messageCount > 0 → includeFullTranscript true).
|
||||
expect(fs.existsSync(path.join(target, "transcript.md"))).toBe(true);
|
||||
});
|
||||
|
||||
it("--source-jsonl with NO session_meta and NO override flags fails with explicit guidance", async () => {
|
||||
const { createProgram } = await import("../src/index.js");
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const src = path.join(tmpRoot, "no-session.jsonl");
|
||||
fs.writeFileSync(src, jsonlWithoutSessionMeta(), "utf8");
|
||||
const target = path.join(tmpRoot, "packet-no-session");
|
||||
|
||||
const stderr: string[] = [];
|
||||
const origConsoleError = console.error;
|
||||
const origExitCode = process.exitCode;
|
||||
console.error = (...args: unknown[]) => stderr.push(args.join(" "));
|
||||
process.exitCode = undefined;
|
||||
try {
|
||||
const program = createProgram();
|
||||
program.exitOverride();
|
||||
await program.parseAsync([
|
||||
"node", "rig", "restore-packet", "write",
|
||||
"--source-jsonl", src,
|
||||
"--target", target,
|
||||
"--target-rig", "openrig-velocity-claude",
|
||||
"--target-runtime", "claude-code",
|
||||
"--current-work-summary", "no-session-meta error path.",
|
||||
"--authority-boundaries", "R2 provenance check only.",
|
||||
]);
|
||||
} finally {
|
||||
console.error = origConsoleError;
|
||||
}
|
||||
expect(process.exitCode).not.toBe(0);
|
||||
process.exitCode = origExitCode;
|
||||
const errStr = stderr.join("\n");
|
||||
expect(errStr).toMatch(/session.meta|--source-session-id-override|--source-rig-override|provenance/i);
|
||||
// No partial packet written.
|
||||
expect(fs.existsSync(target)).toBe(false);
|
||||
});
|
||||
|
||||
it("--source-jsonl with NO session_meta + both overrides succeeds with overridden provenance", async () => {
|
||||
const { createProgram } = await import("../src/index.js");
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const src = path.join(tmpRoot, "no-session-with-overrides.jsonl");
|
||||
fs.writeFileSync(src, jsonlWithoutSessionMeta(), "utf8");
|
||||
const target = path.join(tmpRoot, "packet-overrides");
|
||||
|
||||
const program = createProgram();
|
||||
program.exitOverride();
|
||||
await program.parseAsync([
|
||||
"node", "rig", "restore-packet", "write",
|
||||
"--source-jsonl", src,
|
||||
"--target", target,
|
||||
"--target-rig", "openrig-velocity-claude",
|
||||
"--target-runtime", "claude-code",
|
||||
"--source-session-id-override", "manual-driver@manual-rig",
|
||||
"--source-rig-override", "manual-rig",
|
||||
"--current-work-summary", "override path; honest provenance.",
|
||||
"--authority-boundaries", "R2 override path.",
|
||||
]);
|
||||
|
||||
const summary = JSON.parse(fs.readFileSync(path.join(target, "restore-summary.json"), "utf-8"));
|
||||
expect(summary.source_session_id).toBe("manual-driver@manual-rig");
|
||||
expect(summary.source_rig).toBe("manual-rig");
|
||||
const validation = validateRestoreSummary(summary);
|
||||
expect(validation.valid, JSON.stringify(validation.errors)).toBe(true);
|
||||
});
|
||||
|
||||
it("--source-jsonl with bare-id session_meta fails without --source-rig-override (no silent unknown)", async () => {
|
||||
const { createProgram } = await import("../src/index.js");
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const src = path.join(tmpRoot, "bare-id.jsonl");
|
||||
fs.writeFileSync(src, jsonlWithBareSessionMetaId(), "utf8");
|
||||
const target = path.join(tmpRoot, "packet-bare");
|
||||
|
||||
const stderr: string[] = [];
|
||||
const origConsoleError = console.error;
|
||||
const origExitCode = process.exitCode;
|
||||
console.error = (...args: unknown[]) => stderr.push(args.join(" "));
|
||||
process.exitCode = undefined;
|
||||
try {
|
||||
const program = createProgram();
|
||||
program.exitOverride();
|
||||
await program.parseAsync([
|
||||
"node", "rig", "restore-packet", "write",
|
||||
"--source-jsonl", src,
|
||||
"--target", target,
|
||||
"--target-rig", "openrig-velocity-claude",
|
||||
"--target-runtime", "claude-code",
|
||||
"--current-work-summary", "bare-id no-rig-override path.",
|
||||
"--authority-boundaries", "R2 honest fallback.",
|
||||
]);
|
||||
} finally {
|
||||
console.error = origConsoleError;
|
||||
}
|
||||
expect(process.exitCode).not.toBe(0);
|
||||
process.exitCode = origExitCode;
|
||||
const errStr = stderr.join("\n");
|
||||
expect(errStr).toMatch(/source.rig|--source-rig-override|<seat>@<rig>|derive/i);
|
||||
expect(fs.existsSync(target)).toBe(false);
|
||||
});
|
||||
|
||||
it("--source-jsonl with --source-session-id-override overrides parsed session_meta id", async () => {
|
||||
const { createProgram } = await import("../src/index.js");
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
// JSONL has session_meta.id = velocity-driver@openrig-velocity
|
||||
const src = path.join(tmpRoot, "override-wins.jsonl");
|
||||
fs.writeFileSync(src, jsonlWithSessionMeta(), "utf8");
|
||||
const target = path.join(tmpRoot, "packet-override-wins");
|
||||
|
||||
const program = createProgram();
|
||||
program.exitOverride();
|
||||
await program.parseAsync([
|
||||
"node", "rig", "restore-packet", "write",
|
||||
"--source-jsonl", src,
|
||||
"--target", target,
|
||||
"--target-rig", "openrig-velocity-claude",
|
||||
"--target-runtime", "claude-code",
|
||||
"--source-session-id-override", "operator-renamed@operator-rig",
|
||||
"--source-rig-override", "operator-rig",
|
||||
"--current-work-summary", "override-wins path.",
|
||||
"--authority-boundaries", "operator-supplied provenance.",
|
||||
]);
|
||||
|
||||
const summary = JSON.parse(fs.readFileSync(path.join(target, "restore-summary.json"), "utf-8"));
|
||||
expect(summary.source_session_id).toBe("operator-renamed@operator-rig");
|
||||
expect(summary.source_rig).toBe("operator-rig");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user