blob: 07cb84cf98919dae9e0cd48e0c7ad702f9b49f9d [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
buildImmutableRuntimePrefix,
createRuntimeBoundaryCursor,
runtimePrefixSegment,
type ImmutableRuntimePrefixV1,
} from '@maka/core/runtime-boundary';
import type { RuntimeEvent, RuntimeEventInvocationOpenedContent } from '@maka/core/runtime-event';
import type { RuntimeInvocationRecord } from '@maka/core/runtime-invocation';
import { createLocalContinuationSafetyInspector } from '../continuation-safety.js';
import { buildContinuationReplayPlan } from '../continuation-replay.js';
import {
buildRuntimeEventModelReplayPlan,
PROVIDER_REPLAY_PROJECTION_VERSION,
} from '../model-history.js';
import {
RuntimeContinuationPlanner,
buildSafeBoundaryContinuationPlan,
type RuntimeContinuation,
} from '../runtime-resume.js';
import { testInvocationRecord } from './invocation-fixture.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('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);
// Run and invocation are one identity, so the planner mints three ids, not four.
const ids = ['invocation-2', 'turn-2', 'claim-2'];
const planner = new RuntimeContinuationPlanner({
readSourceInvocation: async () => runInvocation('run-1'),
readImmutableRuntimePrefix: async () => sourcePrefix,
newId: () => ids.shift() ?? 'unexpected-id',
});
const plan = await planner.plan({
sessionId: 'session-1',
sourceRunId: 'run-1',
admissionRoute: sameRouteAdmission(),
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: 'invocation-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: PROVIDER_REPLAY_PROJECTION_VERSION,
safetySnapshot: {
workspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
},
});
});
test('RuntimeContinuationPlanner parks with a stable reason when the ledger cannot be read', async () => {
const planner = new RuntimeContinuationPlanner({
readSourceInvocation: async () => runInvocation('run-1'),
readImmutableRuntimePrefix: async () => {
throw new Error('corrupt ledger');
},
newId: () => 'unused',
});
const plan = await planner.plan({
sessionId: 'session-1',
sourceRunId: 'run-1',
admissionRoute: sameRouteAdmission(),
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({
readSourceInvocation: async () => runInvocation('run-1', { outcome: 'open' }),
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',
admissionRoute: sameRouteAdmission(),
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 source ledger does not end on its terminal fact', async () => {
const planner = new RuntimeContinuationPlanner({
readSourceInvocation: async () => runInvocation('run-1', { outcome: 'completed' }),
readImmutableRuntimePrefix: async () =>
immutablePrefix([
event({
id: 'source-terminal',
role: 'system',
author: 'system',
status: 'failed',
actions: { endInvocation: true },
}),
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',
admissionRoute: sameRouteAdmission(),
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({
readSourceInvocation: async () => runInvocation('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',
admissionRoute: sameRouteAdmission(),
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({
readSourceInvocation: async () => runInvocation('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',
admissionRoute: sameRouteAdmission(),
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({
readSourceInvocation: async () => runInvocation('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',
admissionRoute: sameRouteAdmission(),
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': runInvocation('run-1', {
source: {
kind: 'continuation' as const,
sourceInvocationId: 'invocation-2',
sourceRunId: 'run-2',
sourceTurnId: 'turn-2',
sourceRuntimeEventHighWater: 1,
},
}),
'run-2': runInvocation('run-2', {
source: {
kind: 'continuation' as const,
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({
readSourceInvocation: 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',
admissionRoute: sameRouteAdmission(),
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({
readSourceInvocation: async (_sessionId, runId) => {
if (runId === 'run-2') {
return runInvocation('run-2', {
source: {
kind: 'continuation' as const,
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',
admissionRoute: sameRouteAdmission(),
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, RuntimeInvocationRecord>();
const prefixes = new Map<string, ImmutableRuntimePrefixV1>();
for (let index = 1; index <= 64; index += 1) {
const runId = `run-${index}`;
runs.set(
runId,
runInvocation(runId, {
source: {
kind: 'continuation' as const,
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({
readSourceInvocation: 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',
admissionRoute: sameRouteAdmission(),
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',
},
providerProjectionVersion: 1,
providerReplayDigest:
'sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
replayManifestDigest:
'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
},
},
}),
]);
const planner = new RuntimeContinuationPlanner({
readSourceInvocation: async (_sessionId, runId) =>
runId === 'run-2'
? runInvocation('run-2', {
source: {
kind: 'continuation' as const,
claimId: 'claim-1',
boundaryDigest:
'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
sourceInvocationId: 'invocation-1',
sourceRunId: 'run-1',
sourceTurnId: 'turn-1',
sourceRuntimeEventHighWater: 1,
},
})
: runInvocation('run-1'),
readImmutableRuntimePrefix: async ({ runId }) => (runId === 'run-2' ? source : ancestor),
newId: () => 'unused',
});
const plan = await planner.plan({
sessionId: 'session-1',
sourceRunId: 'run-2',
admissionRoute: sameRouteAdmission(),
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({
readSourceInvocation: async (_sessionId, runId) =>
runId === 'run-2'
? runInvocation('run-2', {
source: {
kind: 'continuation' as const,
claimId: 'claim-expected',
boundaryDigest: ancestorBoundary.manifestDigest,
sourceInvocationId: ancestor.identity.invocationId,
sourceRunId: ancestor.identity.runId,
sourceTurnId: ancestor.identity.turnId,
sourceRuntimeEventHighWater: ancestor.position.lastEventSeq,
},
})
: runInvocation('run-1'),
readImmutableRuntimePrefix: async ({ runId }) => (runId === 'run-2' ? source : ancestor),
newId: () => 'unused',
});
const plan = await planner.plan({
sessionId: 'session-1',
sourceRunId: 'run-2',
admissionRoute: sameRouteAdmission(),
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,
admissionRoute: sameRouteAdmission(),
});
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({
readSourceInvocation: async (_sessionId, runId) =>
runId === sourceIdentity.runId
? runInvocation(sourceIdentity.runId, {
source: {
kind: 'continuation' as const,
sourceInvocationId: ancestor.identity.invocationId,
sourceRunId: ancestor.identity.runId,
sourceTurnId: ancestor.identity.turnId,
sourceRuntimeEventHighWater: ancestor.position.lastEventSeq,
},
})
: runInvocation('run-1'),
readImmutableRuntimePrefix: async ({ runId }) =>
runId === sourceIdentity.runId ? source : ancestor,
newId: () => 'unused',
});
const plan = await planner.plan({
sessionId: 'session-1',
sourceRunId: sourceIdentity.runId,
admissionRoute: sameRouteAdmission(),
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 requires a durable target before authenticating edge replay', async () => {
const ancestor = prefixForIdentity('invocation-1', 'run-1', 'turn-1');
const ancestorReplay = buildContinuationReplayPlan({
prefixes: [ancestor],
providerProjectionVersion: PROVIDER_REPLAY_PROJECTION_VERSION,
admissionRoute: sameRouteAdmission(),
});
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({
readSourceInvocation: async (_sessionId, runId) =>
runId === sourceIdentity.runId
? runInvocation(sourceIdentity.runId, {
source: {
kind: 'continuation' as const,
claimId: 'claim-1',
boundaryDigest: ancestorReplay.plan.boundary.manifestDigest,
sourceInvocationId: ancestor.identity.invocationId,
sourceRunId: ancestor.identity.runId,
sourceTurnId: ancestor.identity.turnId,
sourceRuntimeEventHighWater: ancestor.position.lastEventSeq,
},
})
: runInvocation('run-1'),
readImmutableRuntimePrefix: async ({ runId }) =>
runId === sourceIdentity.runId ? source : ancestor,
newId: () => 'unused',
});
const plan = await planner.plan({
sessionId: 'session-1',
sourceRunId: sourceIdentity.runId,
admissionRoute: sameRouteAdmission(),
currentCwd: '/workspace/repo',
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
assert.equal(plan.disposition, 'park');
assert.deepEqual(plan.rejectionReasons, ['continuation_authority_unavailable']);
});
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,
admissionRoute: sameRouteAdmission(),
});
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 = runInvocation(sourceIdentity.runId, {
source: {
kind: 'continuation' as const,
claimId: 'claim-1',
boundaryDigest: ancestorReplay.plan.boundary.manifestDigest,
sourceInvocationId: ancestor.identity.invocationId,
sourceRunId: ancestor.identity.runId,
sourceTurnId: ancestor.identity.turnId,
sourceRuntimeEventHighWater: ancestor.position.lastEventSeq,
},
});
const planner = new RuntimeContinuationPlanner({
readSourceInvocation: async (_sessionId, runId) =>
runId === sourceIdentity.runId ? sourceRun : runInvocation('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,
admissionRoute: sameRouteAdmission(),
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 sameRouteAdmission() {
return {
invocations: ['run-1', 'run-2', 'run-3'].map((runId) => runInvocation(runId)),
targetProviderStateIdentity: undefined,
targetModelId: 'test-model',
};
}
interface RunFacts {
source?: RuntimeEventInvocationOpenedContent['source'];
outcome?: 'completed' | 'failed' | 'aborted' | 'open';
failureClass?: string;
providerStateIdentity?: `sha256:${string}`;
modelId?: string;
cwd?: string;
}
/** One source invocation as the planner reads it back off the spine. */
function runInvocation(runId: string, facts: RunFacts = {}): RuntimeInvocationRecord {
const ordinal = runId.match(/(\d+)$/)?.[1] ?? '1';
const outcome = facts.outcome ?? 'failed';
const failureClass = outcome === 'failed' ? (facts.failureClass ?? 'test_failure') : undefined;
return testInvocationRecord({
sessionId: 'session-1',
invocationId: `invocation-${ordinal}`,
runId,
turnId: `turn-${ordinal}`,
openedAt: 1,
closedAt: 1,
...(outcome === 'open' ? {} : { outcome }),
...(failureClass ? { failureClass } : {}),
opening: {
route: {
provenance: 'runtime',
backendKind: 'fake',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'test',
modelId: facts.modelId ?? 'test-model',
...(facts.providerStateIdentity
? { providerStateIdentity: facts.providerStateIdentity }
: {}),
},
configuration: { cwd: facts.cwd ?? '/workspace/repo' },
...(facts.source ? { source: facts.source } : {}),
},
});
}
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' },
}),
]);
}