Skip to content

Commit 3015374

Browse files
committed
fix(runtime): validate canonical prefix anchors
Generated-by: Codex
1 parent 2d46692 commit 3015374

4 files changed

Lines changed: 112 additions & 11 deletions

File tree

packages/runtime/src/__tests__/context-diagnostics.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,34 @@ test('a failed call does not replace the last good snapshot', async () => {
247247
}
248248
});
249249

250+
test('an identity-mismatched canonical event cannot become the latest request', async () => {
251+
const root = await mkdtemp(join(tmpdir(), 'maka-context-diagnostics-'));
252+
try {
253+
const writer = createSqliteAgentRunStore(root);
254+
await writer.createRun(runHeader('run-1', 1));
255+
await writer.appendEvent(
256+
'session-1',
257+
'run-1',
258+
meteringEvent('run-1', 'attempt-1', 10, 'model-valid', 40, 200),
259+
);
260+
await writer.appendEvent(
261+
'session-1',
262+
'run-1',
263+
meteringEvent('run-1', 'attempt-2', 20, 'model-mismatched', 40, 200, {
264+
runId: 'another-run',
265+
}),
266+
);
267+
268+
const diagnostics = await readLatestContextDiagnostics(writer, 'session-1');
269+
270+
assert.equal(diagnostics.status, 'available');
271+
if (diagnostics.status !== 'available') return;
272+
assert.equal(diagnostics.modelId, 'model-valid');
273+
} finally {
274+
await rm(root, { recursive: true, force: true });
275+
}
276+
});
277+
250278
test("a subagent's run never becomes the session's context", async () => {
251279
const root = await mkdtemp(join(tmpdir(), 'maka-context-diagnostics-'));
252280
try {
@@ -1138,7 +1166,7 @@ function meteringEvent(
11381166
const turnId = `turn-${runId}`;
11391167
return {
11401168
type: 'model_call_attempt_recorded',
1141-
id: `metering-${attemptId}`,
1169+
id: attemptId,
11421170
runId,
11431171
sessionId: 'session-1',
11441172
turnId,

packages/runtime/src/__tests__/semantic-prefix-continuity.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,58 @@ test('reports the first removed earlier segment', () => {
8181
});
8282
});
8383

84+
test('reports the current segment at the first middle deletion', () => {
85+
const previous = observation([
86+
message(0, 'system'),
87+
message(1, 'user-1'),
88+
message(2, 'assistant-1'),
89+
]);
90+
const current = observation([message(0, 'system'), message(2, 'assistant-1')]);
91+
92+
assert.deepEqual(deriveSemanticPrefixContinuity(current, previous), {
93+
status: 'diverged',
94+
previousSegmentCount: 3,
95+
preservedSegmentCount: 1,
96+
firstDivergentSegment: { kind: 'message', index: 2 },
97+
});
98+
});
99+
100+
test('reports an inserted segment as the first divergence', () => {
101+
const previous = observation([message(0, 'system'), message(2, 'assistant-1')]);
102+
const current = observation([
103+
message(0, 'system'),
104+
message(1, 'inserted-user'),
105+
message(2, 'assistant-1'),
106+
]);
107+
108+
assert.deepEqual(deriveSemanticPrefixContinuity(current, previous), {
109+
status: 'diverged',
110+
previousSegmentCount: 2,
111+
preservedSegmentCount: 1,
112+
firstDivergentSegment: { kind: 'message', index: 1 },
113+
});
114+
});
115+
116+
test('reports the first moved segment after a reorder', () => {
117+
const previous = observation([
118+
message(0, 'system'),
119+
message(1, 'user-1'),
120+
message(2, 'assistant-1'),
121+
]);
122+
const current = observation([
123+
message(0, 'system'),
124+
message(2, 'assistant-1'),
125+
message(1, 'user-1'),
126+
]);
127+
128+
assert.deepEqual(deriveSemanticPrefixContinuity(current, previous), {
129+
status: 'diverged',
130+
previousSegmentCount: 3,
131+
preservedSegmentCount: 1,
132+
firstDivergentSegment: { kind: 'message', index: 2 },
133+
});
134+
});
135+
84136
test('uses the preceding physical retry as its durable baseline', async () => {
85137
const previous = attempt({ attemptId: 'attempt-0', attempt: 0 });
86138
const current = attempt({ attemptId: 'attempt-1', attempt: 1 });

packages/runtime/src/context-diagnostics.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
} from './history-compact-checkpoint.js';
4343
import {
4444
deriveAttemptSemanticPrefixContinuity,
45+
isCanonicalAttemptForRun,
4546
type SemanticPrefixContinuity,
4647
} from './semantic-prefix-continuity.js';
4748

@@ -419,7 +420,13 @@ function meteringAnchor(event: AgentRunEvent, run: AgentRunHeader): MeteringAnch
419420
} catch {
420421
return undefined;
421422
}
422-
if (attempt.callKind !== 'main' || attempt.status !== 'completed') return undefined;
423+
if (
424+
attempt.callKind !== 'main' ||
425+
attempt.status !== 'completed' ||
426+
!isCanonicalAttemptForRun(event, attempt, run)
427+
) {
428+
return undefined;
429+
}
423430
const composition = attempt.requestObservation
424431
? foldPromptComposition(attempt.requestObservation.segments)
425432
: undefined;

packages/runtime/src/semantic-prefix-continuity.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ import type {
2323
PreparedRequestObservationSegment,
2424
} from '@maka/core/model-call-attempt';
2525
import { decodeModelCallAttempt } from '@maka/core/model-call-attempt';
26-
import { isSessionInlineRun, type AgentRunHeader, type AgentRunStore } from '@maka/core/agent-run';
26+
import {
27+
isSessionInlineRun,
28+
type AgentRunEvent,
29+
type AgentRunHeader,
30+
type AgentRunStore,
31+
} from '@maka/core/agent-run';
2732

2833
export type SemanticPrefixContinuity =
2934
| {
@@ -249,14 +254,7 @@ async function attemptsFor(store: PrefixStore, run: AgentRunHeader): Promise<Mod
249254
if (event.type !== 'model_call_attempt_recorded') continue;
250255
try {
251256
const attempt = decodeModelCallAttempt(event.data);
252-
if (
253-
attempt.callKind === 'main' &&
254-
attempt.sessionId === run.sessionId &&
255-
attempt.runId === run.runId &&
256-
attempt.turnId === run.turnId &&
257-
event.turnId === run.turnId &&
258-
attempt.attemptId === event.id
259-
) {
257+
if (attempt.callKind === 'main' && isCanonicalAttemptForRun(event, attempt, run)) {
260258
attempts.push(attempt);
261259
}
262260
} catch {
@@ -266,6 +264,22 @@ async function attemptsFor(store: PrefixStore, run: AgentRunHeader): Promise<Mod
266264
return attempts;
267265
}
268266

267+
export function isCanonicalAttemptForRun(
268+
event: AgentRunEvent,
269+
attempt: ModelCallAttempt,
270+
run: AgentRunHeader,
271+
): boolean {
272+
return (
273+
event.sessionId === run.sessionId &&
274+
event.runId === run.runId &&
275+
event.turnId === run.turnId &&
276+
attempt.sessionId === run.sessionId &&
277+
attempt.runId === run.runId &&
278+
attempt.turnId === run.turnId &&
279+
attempt.attemptId === event.id
280+
);
281+
}
282+
269283
function latestAttempt(
270284
run: AgentRunHeader,
271285
attempts: readonly ModelCallAttempt[],

0 commit comments

Comments
 (0)