Files
obelisk/tests/pi-randomized-differential.test.mjs
T

251 lines
7.3 KiB
JavaScript
Raw Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { createPiProvider } from '../packages/core/src/providers/pi.ts';
import {
buildCodingAgentContextEntries,
buildCodingAgentSessionPath,
buildAgentCoreSessionPath,
defaultAgentCoreContextEntryTransform,
PI_CONTEXT_ORACLE_VERSION,
pi083LeafId,
projectCanonicalEvidence,
} from './fixtures/pi/pi-0.83.0-context-oracle.mjs';
const CASES = 512;
const SEED = 0x5eedc0de;
function randomGenerator(seed) {
let state = seed >>> 0;
return () => {
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
return state / 0x1_0000_0000;
};
}
function integer(random, min, max) {
return min + Math.floor(random() * (max - min + 1));
}
function timestamp(caseIndex, entryIndex) {
return new Date(Date.UTC(2026, 7, 2, 12, caseIndex % 30, entryIndex)).toISOString();
}
function actualEvidence(records) {
return records.flatMap((record) => {
if (record.kind === 'message' && record.visibility === 'visible') {
return [JSON.stringify({
kind: 'message',
source: null,
role: record.role,
content: record.text,
})];
}
if (record.kind === 'summary' && record.visibility === 'visible') {
return [JSON.stringify({
kind: 'summary',
source: record.source,
role: null,
content: record.content,
})];
}
return [];
});
}
function drain(generator) {
const records = [];
for (;;) {
const step = generator.next();
if (step.done) return records;
records.push(step.value);
}
}
function createCase(random, caseIndex, totals) {
const entries = [];
const ids = [];
let headId = null;
const entryCount = integer(random, 8, 64);
for (let entryIndex = 0; entryIndex < entryCount; entryIndex++) {
const id = `case-${caseIndex}-entry-${entryIndex}`;
const time = timestamp(caseIndex, entryIndex);
const roll = random();
let entry;
if (roll < 0.08 && ids.length > 0) {
const targetId = random() < 0.12 ? null : ids[integer(random, 0, ids.length - 1)];
entry = { type: 'leaf', id, parentId: headId, timestamp: time, targetId };
headId = targetId;
totals.leaves++;
if (targetId === null) totals.nullLeaves++;
} else {
let parentId = headId;
if (random() < 0.06) {
parentId = `omitted-${caseIndex}-${entryIndex}`;
totals.orphanParents++;
} else if (ids.length > 0 && random() < 0.28) {
parentId = ids[integer(random, 0, ids.length - 1)];
}
if (roll < 0.72) {
entry = {
type: 'message',
id,
parentId,
timestamp: time,
message: {
role: 'user',
content: `entry:${id}`,
timestamp: Date.parse(time),
},
};
totals.messages++;
} else if (roll < 0.84) {
entry = {
type: 'branch_summary',
id,
parentId,
timestamp: time,
fromId: parentId ?? id,
summary: `branch:${id}`,
};
totals.branchSummaries++;
} else if (roll < 0.96) {
const retained = random() < 0.5;
entry = {
type: 'compaction',
id,
parentId,
timestamp: time,
summary: `compaction:${id}`,
tokensBefore: integer(random, 0, 100_000),
...(retained
? {
retainedTail: [{
role: 'user',
content: `tail:${id}`,
timestamp: Date.parse(time),
}],
}
: { firstKeptEntryId: parentId }),
};
totals.compactions++;
if (retained) totals.retainedTailCompactions++;
} else {
entry = {
type: 'model_change',
id,
parentId,
timestamp: time,
provider: 'probe',
modelId: 'probe',
};
}
headId = id;
}
entries.push(entry);
ids.push(id);
totals.entries++;
}
return {
header: {
type: 'session',
version: 3,
id: `differential-${caseIndex}`,
timestamp: timestamp(caseIndex, 0),
cwd: `/tmp/obelisk-pi-differential/project-${caseIndex}`,
},
entries,
};
}
test('fixed-seed randomized differential matches vendored Pi 0.83.0 context oracles', () => {
assert.equal(PI_CONTEXT_ORACLE_VERSION, '0.83.0');
const random = randomGenerator(SEED);
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-randomized-differential-'));
const generated = [];
const totals = {
cases: CASES,
entries: 0,
messages: 0,
leaves: 0,
nullLeaves: 0,
compactions: 0,
retainedTailCompactions: 0,
branchSummaries: 0,
orphanParents: 0,
retainedCheckpointPathCases: 0,
mixedCheckpointLegacyCases: 0,
};
for (let caseIndex = 0; caseIndex < CASES; caseIndex++) {
const generatedCase = createCase(random, caseIndex, totals);
generated.push(generatedCase);
const path = join(root, `case-${caseIndex}`, 'session.jsonl');
mkdirSync(dirname(path), { recursive: true });
writeFileSync(
path,
`${[generatedCase.header, ...generatedCase.entries].map(record => JSON.stringify(record)).join('\n')}\n`,
);
}
const provider = createPiProvider({ rootDir: root });
const units = provider.discover({ lastCursor: () => null });
assert.equal(units.length, CASES);
for (const unit of units) {
const caseIndex = Number(/case-(\d+)/.exec(unit.key)?.[1]);
const entries = generated[caseIndex].entries;
const leafId = pi083LeafId(entries);
const fullPath = buildCodingAgentSessionPath(entries, leafId);
const codingAgentContext = buildCodingAgentContextEntries(entries, leafId);
const agentCoreContext = defaultAgentCoreContextEntryTransform(
buildAgentCoreSessionPath(entries, leafId),
);
// Pi 0.83's CLI owns legacy firstKeptEntryId semantics. retainedTail is the
// agent-core storage checkpoint format, so mixed/new chains use its bounded
// storage path before the context transform.
let checkpointIndex = -1;
for (let index = 0; index < fullPath.length; index++) {
const entry = fullPath[index];
if (entry.type === 'compaction' && entry.retainedTail !== undefined) {
checkpointIndex = index;
}
}
if (checkpointIndex >= 0) {
totals.retainedCheckpointPathCases++;
if (fullPath.slice(checkpointIndex + 1).some(entry => (
entry.type === 'compaction' && entry.retainedTail === undefined
))) {
totals.mixedCheckpointLegacyCases++;
}
}
const expectedContext = checkpointIndex >= 0
? agentCoreContext
: codingAgentContext;
assert.deepEqual(
actualEvidence(drain(provider.parse(unit, null))),
projectCanonicalEvidence(entries, expectedContext),
`seed 0x${SEED.toString(16)}, case ${caseIndex}`,
);
}
assert.deepEqual(totals, {
cases: 512,
entries: 18124,
messages: 11586,
leaves: 1457,
nullLeaves: 167,
compactions: 2230,
retainedTailCompactions: 1137,
branchSummaries: 2191,
orphanParents: 1060,
retainedCheckpointPathCases: 175,
mixedCheckpointLegacyCases: 32,
});
});