| import assert from 'node:assert/strict'; |
| import { test } from 'node:test'; |
| |
| import { |
| buildImmutableRuntimePrefix, |
| createRuntimeBoundaryCursor, |
| runtimePrefixSegment, |
| type ImmutableRuntimePrefixV1, |
| } from '@maka/core'; |
| import type { RuntimeEvent } from '@maka/core'; |
| import type { AgentRunHeader } from '@maka/core'; |
| |
| import type { FlowInput } from '../agent-flow.js'; |
| import type { InvocationContext } from '../invocation-context.js'; |
| import { createLocalContinuationSafetyInspector } from '../continuation-safety.js'; |
| import { buildContinuationReplayPlan, digestProviderReplay } from '../continuation-replay.js'; |
| import { |
| buildRuntimeEventModelReplayPlan, |
| PROVIDER_REPLAY_PROJECTION_VERSION, |
| } from '../model-history.js'; |
| import { |
| RuntimeContinuationPlanner, |
| buildSafeBoundaryContinuationPlan, |
| type RuntimeContinuation, |
| } from '../runtime-resume.js'; |
| import { |
| issueRuntimeContinuationAdmissionReceipt, |
| RuntimeRunner, |
| runAdmittedRuntimeContinuation, |
| } from '../runtime-runner.js'; |
| import { createRuntimeContinuationStartAdmissionProof } from '../runtime-continuation-admission.js'; |
| |
| test('local continuation safety inspector returns current authoritative workspace facts', async () => { |
| const inspect = createLocalContinuationSafetyInspector({ |
| readSessionCwd: async () => '/workspace/repo-link', |
| resolveWorkspaceIdentity: async () => ({ |
| workspaceIdentity: 'workspace:v1:123e4567-e89b-42d3-a456-426614174000', |
| canonicalPath: '/workspace/repo', |
| }), |
| listAvailableToolNames: async () => ['Write', 'Read', 'Read'], |
| hasPendingBackgroundOperations: async () => false, |
| }); |
| |
| assert.deepEqual(await inspect('session-1'), { |
| workspaceIdentity: 'workspace:v1:123e4567-e89b-42d3-a456-426614174000', |
| workspacePath: '/workspace/repo', |
| backgroundOperationsSettled: true, |
| availableToolNames: ['Read', 'Write'], |
| }); |
| }); |
| |
| test('RuntimeRunner continues from replay context without synthesizing another user event', async () => { |
| const sourceEvents = [ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'run tests' }, |
| }), |
| event({ |
| id: 'source-call', |
| role: 'model', |
| author: 'agent', |
| content: { kind: 'function_call', id: 'tool-1', name: 'Bash', args: { command: 'npm test' } }, |
| }), |
| event({ |
| id: 'source-result', |
| role: 'tool', |
| author: 'tool', |
| content: { kind: 'function_response', id: 'tool-1', name: 'Bash', result: { exitCode: 0 } }, |
| }), |
| ]; |
| const plan = buildSafeBoundaryContinuationPlan(sourceEvents, { |
| ledgerReadable: true, |
| terminalRepairSucceeded: true, |
| sourceCwd: '/workspace/repo', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: ['Bash'], |
| continuationIdentity: { |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| }, |
| }); |
| assert.equal(plan.disposition, 'continue'); |
| assert.ok(plan.continuation); |
| const sourcePrefix = immutablePrefix(sourceEvents); |
| const replay = buildContinuationReplayPlan({ |
| prefixes: [sourcePrefix], |
| providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION, |
| }); |
| assert.equal(replay.kind, 'replayable'); |
| if (replay.kind !== 'replayable') throw new Error('test replay must be admitted'); |
| const continuation: RuntimeContinuation = { |
| ...plan.continuation, |
| claimId: 'claim-2', |
| boundary: replay.plan.boundary, |
| providerProjectionVersion: replay.plan.providerProjectionVersion, |
| providerReplayDigest: replay.plan.providerReplayDigest, |
| }; |
| |
| let capturedContext: InvocationContext | undefined; |
| let capturedInput: FlowInput | undefined; |
| const runner = new RuntimeRunner({ |
| flow: { |
| async *run(context, input) { |
| capturedContext = context; |
| capturedInput = input; |
| yield event({ |
| id: 'continued-complete', |
| invocationId: context.invocationId, |
| runId: context.runId, |
| turnId: context.turnId, |
| role: 'system', |
| author: 'system', |
| status: 'completed', |
| actions: { endInvocation: true }, |
| }); |
| }, |
| }, |
| providers: { newId: () => 'continuation-start', now: () => 20 }, |
| }); |
| |
| const receipt = issueRuntimeContinuationAdmissionReceipt( |
| runner, |
| continuation, |
| startAdmissionFor(continuation, 'ancestor-continuation-start'), |
| ); |
| const result = await runAdmittedRuntimeContinuation(runner, receipt, { |
| source: 'test', |
| }); |
| |
| assert.equal(result.invocationId, 'invocation-2'); |
| assert.equal(result.runId, 'run-2'); |
| assert.equal(result.turnId, 'turn-2'); |
| assert.deepEqual( |
| result.events.map((candidate) => candidate.id), |
| ['continued-complete'], |
| ); |
| assert.equal(capturedContext?.request.continuation?.sourceRunId, 'run-1'); |
| assert.deepEqual(capturedInput?.runtimeContext, sourceEvents); |
| assert.equal(capturedInput?.continuation?.sourceRuntimeEventHighWater, 3); |
| }); |
| |
| test('RuntimeRunner preserves the immediate source segment when replay includes continuation ancestors', async () => { |
| const ancestorEvents = [ |
| event({ |
| id: 'ancestor-user', |
| invocationId: 'ancestor-invocation', |
| runId: 'ancestor-run', |
| turnId: 'ancestor-turn', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'original request' }, |
| }), |
| event({ |
| id: 'ancestor-terminal', |
| invocationId: 'ancestor-invocation', |
| runId: 'ancestor-run', |
| turnId: 'ancestor-turn', |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]; |
| const sourceRuntimeContext = [ |
| event({ |
| id: 'source-continuation-start', |
| role: 'system', |
| author: 'system', |
| actions: { stateDelta: { continuation: true } }, |
| }), |
| event({ |
| id: 'source-terminal', |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]; |
| const runtimeContext = [...ancestorEvents, ...sourceRuntimeContext]; |
| let capturedInput: FlowInput | undefined; |
| const runner = new RuntimeRunner({ |
| flow: { |
| async *run(context, input) { |
| capturedInput = input; |
| yield event({ |
| id: 'continued-text', |
| invocationId: context.invocationId, |
| runId: context.runId, |
| turnId: context.turnId, |
| role: 'model', |
| author: 'agent', |
| content: { kind: 'text', text: 'continued' }, |
| }); |
| yield event({ |
| id: 'continued-terminal', |
| invocationId: context.invocationId, |
| runId: context.runId, |
| turnId: context.turnId, |
| role: 'system', |
| author: 'system', |
| status: 'completed', |
| actions: { endInvocation: true }, |
| }); |
| }, |
| }, |
| providers: { newId: () => 'new-event', now: () => 20 }, |
| }); |
| |
| const sourcePrefix = immutablePrefix(sourceRuntimeContext); |
| const boundary = createRuntimeBoundaryCursor([runtimePrefixSegment(sourcePrefix)]); |
| const continuation: RuntimeContinuation = { |
| sessionId: 'session-1', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| sourceInvocationId: 'invocation-1', |
| sourceRunId: 'run-1', |
| sourceTurnId: 'turn-1', |
| sourceRuntimeEventHighWater: sourceRuntimeContext.length, |
| claimId: 'claim-2', |
| sourceRuntimeContext, |
| runtimeContext, |
| boundary, |
| providerProjectionVersion: 1 as const, |
| providerReplayDigest: digestProviderReplay( |
| PROVIDER_REPLAY_PROJECTION_VERSION, |
| buildRuntimeEventModelReplayPlan(runtimeContext).items, |
| ), |
| safetySnapshot: { |
| workspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }, |
| }; |
| const receipt = issueRuntimeContinuationAdmissionReceipt( |
| runner, |
| continuation, |
| startAdmissionFor(continuation, 'continuation-start'), |
| ); |
| const result = await runAdmittedRuntimeContinuation(runner, receipt, { source: 'test' }); |
| |
| assert.equal(result.status, 'completed'); |
| assert.deepEqual(capturedInput?.runtimeContext, runtimeContext); |
| assert.equal('sourceRuntimeContext' in (capturedInput?.continuation ?? {}), false); |
| }); |
| |
| test('RuntimeContinuationPlanner reads the durable source boundary and allocates fresh identities', async () => { |
| const sourceEvents = [ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| event({ |
| id: 'source-terminal', |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]; |
| const sourcePrefix = immutablePrefix(sourceEvents); |
| const ids = ['invocation-2', 'run-2', 'turn-2', 'claim-2']; |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async () => runHeader('run-1'), |
| readImmutableRuntimePrefix: async () => sourcePrefix, |
| newId: () => ids.shift() ?? 'unexpected-id', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'continue'); |
| assert.deepEqual(plan.continuation, { |
| sessionId: 'session-1', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| sourceInvocationId: 'invocation-1', |
| sourceRunId: 'run-1', |
| sourceTurnId: 'turn-1', |
| sourceRuntimeEventHighWater: 2, |
| claimId: 'claim-2', |
| runtimeContext: sourceEvents, |
| boundary: { |
| protocol: 'runtime_boundary_cursor_v1', |
| segments: [ |
| { |
| protocol: 'runtime_prefix_segment_v1', |
| identity: sourcePrefix.identity, |
| position: sourcePrefix.position, |
| prefixDigest: sourcePrefix.prefixDigest, |
| }, |
| ], |
| manifestDigest: plan.continuation?.boundary?.manifestDigest, |
| }, |
| providerReplayDigest: plan.continuation?.providerReplayDigest, |
| providerProjectionVersion: 1, |
| safetySnapshot: { |
| workspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }, |
| }); |
| }); |
| |
| test('RuntimeRunner rejects a continuation envelope whose high-water is behind its replay context', async () => { |
| const runner = new RuntimeRunner({ |
| flow: { |
| async *run() { |
| throw new Error('flow must not start'); |
| }, |
| }, |
| }); |
| |
| const invalidContinuation: RuntimeContinuation = { |
| sessionId: 'session-1', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| sourceInvocationId: 'invocation-1', |
| sourceRunId: 'run-1', |
| sourceTurnId: 'turn-1', |
| sourceRuntimeEventHighWater: 0, |
| claimId: 'claim-2', |
| runtimeContext: [ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| ], |
| boundary: createRuntimeBoundaryCursor([ |
| runtimePrefixSegment( |
| immutablePrefix([ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| ]), |
| ), |
| ]), |
| providerProjectionVersion: 1 as const, |
| providerReplayDigest: `sha256:${'b'.repeat(64)}` as const, |
| safetySnapshot: { |
| workspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }, |
| }; |
| assert.throws( |
| () => |
| issueRuntimeContinuationAdmissionReceipt( |
| runner, |
| invalidContinuation, |
| startAdmissionFor(invalidContinuation, 'invalid-continuation-start'), |
| ), |
| /high-water/i, |
| ); |
| }); |
| |
| test('RuntimeContinuationPlanner parks with a stable reason when the ledger cannot be read', async () => { |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async () => runHeader('run-1'), |
| readImmutableRuntimePrefix: async () => { |
| throw new Error('corrupt ledger'); |
| }, |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_ledger_unreadable']); |
| }); |
| |
| test('RuntimeContinuationPlanner derives terminal repair from durable run and event facts', async () => { |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async () => runHeader('run-1', { status: 'running' }), |
| readImmutableRuntimePrefix: async () => |
| immutablePrefix([ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| ]), |
| newId: () => 'fresh-id', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['terminal_repair_failed']); |
| }); |
| |
| test('RuntimeContinuationPlanner parks when the terminal run header disagrees with the ledger fact', async () => { |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async () => runHeader('run-1', { status: 'completed' }), |
| readImmutableRuntimePrefix: async () => |
| immutablePrefix([ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| event({ |
| id: 'source-terminal', |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]), |
| newId: () => 'fresh-id', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['terminal_repair_failed']); |
| }); |
| |
| test('RuntimeContinuationPlanner rejects immutable output after the source terminal fact', async () => { |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async () => runHeader('run-1'), |
| readImmutableRuntimePrefix: async () => |
| immutablePrefix([ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| event({ |
| id: 'source-terminal', |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| event({ |
| id: 'post-terminal-output', |
| ts: 3, |
| role: 'model', |
| author: 'agent', |
| content: { kind: 'text', text: 'must invalidate the boundary' }, |
| }), |
| ]), |
| newId: () => 'fresh-id', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['terminal_repair_failed']); |
| }); |
| |
| test('RuntimeContinuationPlanner uses canonical provider items for composite head and tail gates', async () => { |
| let nextId = 0; |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async () => runHeader('run-1'), |
| readImmutableRuntimePrefix: async () => |
| immutablePrefix([ |
| event({ |
| id: 'source-user', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| event({ |
| id: 'empty-model-text', |
| ts: 2, |
| role: 'model', |
| author: 'agent', |
| content: { kind: 'text', text: '' }, |
| }), |
| event({ |
| id: 'source-terminal', |
| ts: 3, |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]), |
| newId: () => `fresh-id-${++nextId}`, |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'continue'); |
| assert.ok(plan.continuation); |
| assert.deepEqual(plan.rejectionReasons, []); |
| }); |
| |
| test('RuntimeContinuationPlanner rejects a ledger returned for another source run', async () => { |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async () => runHeader('run-1'), |
| readImmutableRuntimePrefix: async () => |
| immutablePrefix([ |
| event({ |
| id: 'wrong-user', |
| runId: 'run-other', |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| event({ |
| id: 'wrong-terminal', |
| runId: 'run-other', |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]), |
| newId: () => 'fresh-id', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_identity_mismatch']); |
| }); |
| |
| test('RuntimeContinuationPlanner fails a cyclic continuation lineage closed', async () => { |
| const runs = { |
| 'run-1': runHeader('run-1', { |
| continuationSource: { |
| sourceInvocationId: 'invocation-2', |
| sourceRunId: 'run-2', |
| sourceTurnId: 'turn-2', |
| sourceRuntimeEventHighWater: 1, |
| }, |
| }), |
| 'run-2': runHeader('run-2', { |
| continuationSource: { |
| sourceInvocationId: 'invocation-1', |
| sourceRunId: 'run-1', |
| sourceTurnId: 'turn-1', |
| sourceRuntimeEventHighWater: 1, |
| }, |
| }), |
| } as const; |
| const prefixes = new Map([ |
| ['run-1', prefixForIdentity('invocation-1', 'run-1', 'turn-1')], |
| ['run-2', prefixForIdentity('invocation-2', 'run-2', 'turn-2')], |
| ]); |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => runs[runId as keyof typeof runs], |
| readImmutableRuntimePrefix: async ({ runId }) => prefixes.get(runId)!, |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_lineage_cycle']); |
| }); |
| |
| test('RuntimeContinuationPlanner parks when a continuation ancestor is unavailable', async () => { |
| const source = prefixForIdentity('invocation-2', 'run-2', 'turn-2'); |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => { |
| if (runId === 'run-2') { |
| return runHeader('run-2', { |
| continuationSource: { |
| sourceInvocationId: 'invocation-1', |
| sourceRunId: 'run-missing', |
| sourceTurnId: 'turn-1', |
| sourceRuntimeEventHighWater: 1, |
| }, |
| }); |
| } |
| throw new Error('missing ancestor'); |
| }, |
| readImmutableRuntimePrefix: async ({ runId }) => { |
| if (runId === 'run-2') return source; |
| throw new Error('missing ancestor prefix'); |
| }, |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-2', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_lineage_missing']); |
| }); |
| |
| test('RuntimeContinuationPlanner caps continuation lineage at 64 segments', async () => { |
| const runs = new Map<string, AgentRunHeader>(); |
| const prefixes = new Map<string, ImmutableRuntimePrefixV1>(); |
| for (let index = 1; index <= 64; index += 1) { |
| const runId = `run-${index}`; |
| runs.set( |
| runId, |
| runHeader(runId, { |
| continuationSource: { |
| sourceInvocationId: `invocation-${index + 1}`, |
| sourceRunId: `run-${index + 1}`, |
| sourceTurnId: `turn-${index + 1}`, |
| sourceRuntimeEventHighWater: 1, |
| }, |
| }), |
| ); |
| prefixes.set(runId, prefixForIdentity(`invocation-${index}`, runId, `turn-${index}`)); |
| } |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => { |
| const run = runs.get(runId); |
| if (!run) throw new Error('unexpected lineage read'); |
| return run; |
| }, |
| readImmutableRuntimePrefix: async ({ runId }) => { |
| const prefix = prefixes.get(runId); |
| if (!prefix) throw new Error('unexpected lineage prefix read'); |
| return prefix; |
| }, |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-1', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_lineage_depth_exceeded']); |
| }); |
| |
| test('RuntimeContinuationPlanner verifies a v2 lineage edge prefix digest', async () => { |
| const ancestor = prefixForIdentity('invocation-1', 'run-1', 'turn-1'); |
| const source = immutablePrefix([ |
| event({ |
| id: 'run-2-start', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| role: 'system', |
| author: 'system', |
| actions: { |
| continuationStart: { |
| protocol: 'continuation_start_v2', |
| provenance: 'runtime_admission', |
| claimId: 'claim-1', |
| boundaryDigest: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', |
| immediateSource: { |
| sessionId: 'session-1', |
| invocationId: 'invocation-1', |
| runId: 'run-1', |
| turnId: 'turn-1', |
| highWater: 1, |
| prefixDigest: 'sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', |
| }, |
| replayManifestDigest: |
| 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', |
| providerProjectionVersion: 1, |
| providerReplayDigest: |
| 'sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', |
| }, |
| }, |
| }), |
| ]); |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => |
| runId === 'run-2' |
| ? runHeader('run-2', { |
| continuationSource: { |
| protocol: 'continuation_source_v2', |
| claimId: 'claim-1', |
| boundaryDigest: |
| 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', |
| sourceInvocationId: 'invocation-1', |
| sourceRunId: 'run-1', |
| sourceTurnId: 'turn-1', |
| sourceRuntimeEventHighWater: 1, |
| sourcePrefixDigest: |
| 'sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', |
| replayManifestDigest: |
| 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', |
| }, |
| }) |
| : runHeader('run-1'), |
| readImmutableRuntimePrefix: async ({ runId }) => (runId === 'run-2' ? source : ancestor), |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-2', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['source_prefix_digest_mismatch']); |
| }); |
| |
| test('RuntimeContinuationPlanner binds every v2 lineage edge to its continuation-start T1', async () => { |
| const ancestor = prefixForIdentity('invocation-1', 'run-1', 'turn-1'); |
| const ancestorBoundary = createRuntimeBoundaryCursor([runtimePrefixSegment(ancestor)]); |
| const sourceIdentity = { |
| sessionId: 'session-1', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| }; |
| const source = immutablePrefix([ |
| event({ |
| id: 'continuation-start-2', |
| ...sourceIdentity, |
| role: 'system', |
| author: 'system', |
| actions: { |
| continuationStart: { |
| protocol: 'continuation_start_v2', |
| provenance: 'runtime_admission', |
| claimId: 'forged-claim', |
| boundaryDigest: ancestorBoundary.manifestDigest, |
| immediateSource: { |
| ...ancestor.identity, |
| highWater: ancestor.position.lastEventSeq, |
| prefixDigest: ancestor.prefixDigest, |
| }, |
| replayManifestDigest: ancestorBoundary.manifestDigest, |
| providerProjectionVersion: 1, |
| providerReplayDigest: |
| 'sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', |
| }, |
| }, |
| }), |
| event({ |
| id: 'run-2-terminal', |
| ...sourceIdentity, |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]); |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => |
| runId === 'run-2' |
| ? runHeader('run-2', { |
| continuationSource: { |
| protocol: 'continuation_source_v2', |
| claimId: 'claim-expected', |
| boundaryDigest: ancestorBoundary.manifestDigest, |
| sourceInvocationId: ancestor.identity.invocationId, |
| sourceRunId: ancestor.identity.runId, |
| sourceTurnId: ancestor.identity.turnId, |
| sourceRuntimeEventHighWater: ancestor.position.lastEventSeq, |
| sourcePrefixDigest: ancestor.prefixDigest, |
| replayManifestDigest: ancestorBoundary.manifestDigest, |
| }, |
| }) |
| : runHeader('run-1'), |
| readImmutableRuntimePrefix: async ({ runId }) => (runId === 'run-2' ? source : ancestor), |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: 'run-2', |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_lineage_start_mismatch']); |
| }); |
| |
| test('RuntimeContinuationPlanner rejects downgrading a canonical v2 start to legacy lineage', async () => { |
| const ancestor = prefixForIdentity('invocation-1', 'run-1', 'turn-1'); |
| const ancestorReplay = buildContinuationReplayPlan({ |
| prefixes: [ancestor], |
| providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION, |
| }); |
| assert.equal(ancestorReplay.kind, 'replayable'); |
| if (ancestorReplay.kind !== 'replayable') return; |
| const sourceIdentity = { |
| sessionId: 'session-1', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| }; |
| const source = immutablePrefix([ |
| event({ |
| id: 'continuation-start-2', |
| ...sourceIdentity, |
| role: 'system', |
| author: 'system', |
| actions: { |
| continuationStart: { |
| protocol: 'continuation_start_v2', |
| provenance: 'runtime_admission', |
| claimId: 'claim-1', |
| boundaryDigest: ancestorReplay.plan.boundary.manifestDigest, |
| immediateSource: { |
| ...ancestor.identity, |
| highWater: ancestor.position.lastEventSeq, |
| prefixDigest: ancestor.prefixDigest, |
| }, |
| replayManifestDigest: ancestorReplay.plan.boundary.manifestDigest, |
| providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION, |
| providerReplayDigest: ancestorReplay.plan.providerReplayDigest, |
| }, |
| }, |
| }), |
| event({ |
| id: 'run-2-terminal', |
| ...sourceIdentity, |
| ts: 2, |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]); |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => |
| runId === sourceIdentity.runId |
| ? runHeader(sourceIdentity.runId, { |
| continuationSource: { |
| sourceInvocationId: ancestor.identity.invocationId, |
| sourceRunId: ancestor.identity.runId, |
| sourceTurnId: ancestor.identity.turnId, |
| sourceRuntimeEventHighWater: ancestor.position.lastEventSeq, |
| }, |
| }) |
| : runHeader('run-1'), |
| readImmutableRuntimePrefix: async ({ runId }) => |
| runId === sourceIdentity.runId ? source : ancestor, |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: sourceIdentity.runId, |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_lineage_start_mismatch']); |
| }); |
| |
| test('RuntimeContinuationPlanner authenticates every v2 edge provider replay digest', async () => { |
| const ancestor = prefixForIdentity('invocation-1', 'run-1', 'turn-1'); |
| const ancestorReplay = buildContinuationReplayPlan({ |
| prefixes: [ancestor], |
| providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION, |
| }); |
| assert.equal(ancestorReplay.kind, 'replayable'); |
| if (ancestorReplay.kind !== 'replayable') return; |
| const sourceIdentity = { |
| sessionId: 'session-1', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| }; |
| const source = immutablePrefix([ |
| event({ |
| id: 'continuation-start-2', |
| ...sourceIdentity, |
| role: 'system', |
| author: 'system', |
| actions: { |
| continuationStart: { |
| protocol: 'continuation_start_v2', |
| provenance: 'runtime_admission', |
| claimId: 'claim-1', |
| boundaryDigest: ancestorReplay.plan.boundary.manifestDigest, |
| immediateSource: { |
| ...ancestor.identity, |
| highWater: ancestor.position.lastEventSeq, |
| prefixDigest: ancestor.prefixDigest, |
| }, |
| replayManifestDigest: ancestorReplay.plan.boundary.manifestDigest, |
| providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION, |
| providerReplayDigest: |
| 'sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', |
| }, |
| }, |
| }), |
| event({ |
| id: 'run-2-terminal', |
| ...sourceIdentity, |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true }, |
| }), |
| ]); |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => |
| runId === sourceIdentity.runId |
| ? runHeader(sourceIdentity.runId, { |
| continuationSource: { |
| protocol: 'continuation_source_v2', |
| claimId: 'claim-1', |
| boundaryDigest: ancestorReplay.plan.boundary.manifestDigest, |
| sourceInvocationId: ancestor.identity.invocationId, |
| sourceRunId: ancestor.identity.runId, |
| sourceTurnId: ancestor.identity.turnId, |
| sourceRuntimeEventHighWater: ancestor.position.lastEventSeq, |
| sourcePrefixDigest: ancestor.prefixDigest, |
| replayManifestDigest: ancestorReplay.plan.boundary.manifestDigest, |
| }, |
| }) |
| : runHeader('run-1'), |
| readImmutableRuntimePrefix: async ({ runId }) => |
| runId === sourceIdentity.runId ? source : ancestor, |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: sourceIdentity.runId, |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_lineage_replay_mismatch']); |
| }); |
| |
| test('RuntimeContinuationPlanner rejects a v2 lineage edge whose durable claim is missing', async () => { |
| const ancestor = prefixForIdentity('invocation-1', 'run-1', 'turn-1'); |
| const ancestorReplay = buildContinuationReplayPlan({ |
| prefixes: [ancestor], |
| providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION, |
| }); |
| assert.equal(ancestorReplay.kind, 'replayable'); |
| if (ancestorReplay.kind !== 'replayable') return; |
| const sourceIdentity = { |
| sessionId: 'session-1', |
| invocationId: 'invocation-2', |
| runId: 'run-2', |
| turnId: 'turn-2', |
| }; |
| const source = immutablePrefix([ |
| event({ |
| id: 'continuation-start-2', |
| ...sourceIdentity, |
| role: 'system', |
| author: 'system', |
| actions: { |
| continuationStart: { |
| protocol: 'continuation_start_v2', |
| provenance: 'runtime_admission', |
| claimId: 'claim-1', |
| boundaryDigest: ancestorReplay.plan.boundary.manifestDigest, |
| immediateSource: { |
| ...ancestor.identity, |
| highWater: ancestor.position.lastEventSeq, |
| prefixDigest: ancestor.prefixDigest, |
| }, |
| replayManifestDigest: ancestorReplay.plan.boundary.manifestDigest, |
| providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION, |
| providerReplayDigest: ancestorReplay.plan.providerReplayDigest, |
| }, |
| }, |
| }), |
| event({ |
| id: 'run-2-terminal', |
| ...sourceIdentity, |
| ts: 2, |
| role: 'system', |
| author: 'system', |
| status: 'failed', |
| actions: { endInvocation: true, stateDelta: { failureClass: 'test_failure' } }, |
| }), |
| ]); |
| const sourceRun = runHeader(sourceIdentity.runId, { |
| continuationSource: { |
| protocol: 'continuation_source_v2', |
| claimId: 'claim-1', |
| boundaryDigest: ancestorReplay.plan.boundary.manifestDigest, |
| sourceInvocationId: ancestor.identity.invocationId, |
| sourceRunId: ancestor.identity.runId, |
| sourceTurnId: ancestor.identity.turnId, |
| sourceRuntimeEventHighWater: ancestor.position.lastEventSeq, |
| sourcePrefixDigest: ancestor.prefixDigest, |
| replayManifestDigest: ancestorReplay.plan.boundary.manifestDigest, |
| }, |
| }); |
| const planner = new RuntimeContinuationPlanner({ |
| readSourceRun: async (_sessionId, runId) => |
| runId === sourceIdentity.runId ? sourceRun : runHeader('run-1'), |
| readImmutableRuntimePrefix: async ({ runId }) => |
| runId === sourceIdentity.runId ? source : ancestor, |
| readContinuationClaimStateByBoundary: async () => undefined, |
| newId: () => 'unused', |
| }); |
| |
| const plan = await planner.plan({ |
| sessionId: 'session-1', |
| sourceRunId: sourceIdentity.runId, |
| currentCwd: '/workspace/repo', |
| sourceWorkspaceIdentity: 'workspace-1', |
| currentWorkspaceIdentity: 'workspace-1', |
| backgroundOperationsSettled: true, |
| availableToolNames: [], |
| }); |
| |
| assert.equal(plan.disposition, 'park'); |
| assert.deepEqual(plan.rejectionReasons, ['runtime_lineage_claim_mismatch']); |
| }); |
| |
| function runHeader(runId: string, overrides: Partial<AgentRunHeader> = {}): AgentRunHeader { |
| const ordinal = runId.match(/(\d+)$/)?.[1] ?? '1'; |
| const status = overrides.status ?? 'failed'; |
| return { |
| runId, |
| invocationId: `invocation-${ordinal}`, |
| sessionId: 'session-1', |
| turnId: `turn-${ordinal}`, |
| status, |
| backendKind: 'fake', |
| llmConnectionSlug: 'test', |
| modelId: 'test-model', |
| cwd: '/workspace/repo', |
| permissionMode: 'ask', |
| ...(status === 'failed' ? { failureClass: 'test_failure' } : {}), |
| createdAt: 1, |
| updatedAt: 1, |
| ...overrides, |
| }; |
| } |
| |
| function event(overrides: Partial<RuntimeEvent>): RuntimeEvent { |
| return { |
| id: 'event', |
| invocationId: 'invocation-1', |
| runId: 'run-1', |
| sessionId: 'session-1', |
| turnId: 'turn-1', |
| ts: 1, |
| partial: false, |
| role: 'model', |
| author: 'agent', |
| ...overrides, |
| }; |
| } |
| |
| function immutablePrefix(events: readonly RuntimeEvent[]): ImmutableRuntimePrefixV1 { |
| const first = events[0]; |
| if (!first) throw new Error('test immutable prefix requires at least one event'); |
| return buildImmutableRuntimePrefix( |
| { |
| sessionId: first.sessionId, |
| invocationId: first.invocationId, |
| runId: first.runId, |
| turnId: first.turnId, |
| }, |
| events.map((runtimeEvent, index) => ({ |
| eventSeq: index + 1, |
| event: runtimeEvent, |
| })), |
| ); |
| } |
| |
| function prefixForIdentity( |
| invocationId: string, |
| runId: string, |
| turnId: string, |
| ): ImmutableRuntimePrefixV1 { |
| return immutablePrefix([ |
| event({ |
| id: `${runId}-user`, |
| invocationId, |
| runId, |
| turnId, |
| role: 'user', |
| author: 'user', |
| content: { kind: 'text', text: 'continue' }, |
| }), |
| ]); |
| } |
| |
| function startAdmissionFor(continuation: RuntimeContinuation, startEventId: string) { |
| if ( |
| !continuation.claimId || |
| !continuation.boundary || |
| !continuation.providerReplayDigest || |
| continuation.providerProjectionVersion !== 1 |
| ) { |
| throw new Error('test continuation is missing durable admission identity'); |
| } |
| return createRuntimeContinuationStartAdmissionProof({ |
| startEventId, |
| claimId: continuation.claimId, |
| boundaryDigest: continuation.boundary.manifestDigest, |
| providerProjectionVersion: continuation.providerProjectionVersion, |
| providerReplayDigest: continuation.providerReplayDigest, |
| target: { |
| sessionId: continuation.sessionId, |
| invocationId: continuation.invocationId, |
| runId: continuation.runId, |
| turnId: continuation.turnId, |
| }, |
| }); |
| } |