test: cover trajectory rows in sqlite flip proof

This commit is contained in:
Josh Lehman
2026-07-03 10:44:48 -07:00
parent ef87572f55
commit 6d943104b4
4 changed files with 127 additions and 1 deletions

View File

@@ -1746,3 +1746,8 @@
{"type":"comment","timestamp":"2026-07-03T17:36:01.634601Z","issue_id":"oc-08c.8","payload":{"body":"Final proof: focused trajectory/cleanup/doctor/budget test set passed (6 Vitest shards, 142 tests), pnpm tsgo:core passed, pnpm docs:list passed, and git diff --check passed. Autoreview was attempted but blocked before review by the local codex wrapper pointing at a missing vendored binary: /Users/josh/.nvm/versions/node/v22.22.1/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex/codex ENOENT."}}
{"type":"close","timestamp":"2026-07-03T17:36:05.80934Z","issue_id":"oc-08c.8","payload":{}}
{"type":"close","timestamp":"2026-07-03T17:36:06.39963Z","issue_id":"oc-08c","payload":{}}
{"type":"create","timestamp":"2026-07-03T17:41:59.889705Z","issue_id":"oc-a85","payload":{"description":"Extend the SQLite sessions/transcripts flip proof harness with trajectory runtime-row evidence: append a marker-backed trajectory event through the SDK/runtime seam, assert it lands in trajectory_runtime_events, and assert no JSONL or pointer sidecar appears during the flip proof.","priority":"1","title":"AN-0009: add trajectory coverage to SQLite flip E2E","type":"task"}}
{"type":"status_update","timestamp":"2026-07-03T17:42:03.5015Z","issue_id":"oc-a85","payload":{"status":"in_progress"}}
{"type":"rename","timestamp":"2026-07-03T17:42:03.5015Z","issue_id":"oc-a85","payload":{"new_id":"oc-08c.9"}}
{"type":"dep_add","timestamp":"2026-07-03T17:42:03.5015Z","issue_id":"oc-08c.9","payload":{"dep_type":"parent-child","depends_on":"oc-08c"}}
{"type":"close","timestamp":"2026-07-03T17:44:47.739182Z","issue_id":"oc-08c.9","payload":{}}

View File

@@ -107,10 +107,14 @@ Each step snapshots before and after state and writes a structured assertion
record:
- SQLite row counts advance only where expected.
- Trajectory runtime rows advance for marker-backed proof sessions that record
runtime events.
- The proof session row has the expected `session_id`, status, timestamps,
metadata, and route rows.
- Gateway history/session projection matches the SQLite transcript tail.
- No proof-session JSONL file is created or modified.
- No proof-session `.trajectory.jsonl`, `.trajectory-path.json`, or
marker-derived `trajectory/<session>.jsonl` sidecar is created.
- Existing legacy JSONL files and `sessions.json` remain unchanged unless the
step is explicitly an offline migration or archive operation.
- The Gateway process does not open `.jsonl` or `sessions.json` handles.

View File

@@ -22,6 +22,8 @@ import {
getSessionEntry as getSdkSessionEntry,
listSessionEntries as listSdkSessionEntries,
loadTranscriptEventsSync as loadSdkTranscriptEventsSync,
appendTrajectoryRuntimeEvents,
type SessionStoreTrajectoryEvent,
} from "../../src/plugin-sdk/session-store-runtime.js";
import {
appendSessionTranscriptMessageByIdentity,
@@ -74,6 +76,7 @@ type SqliteSessionEntryEvidence = {
entry?: Record<string, unknown>;
sessionId: string;
sessionKey: string;
trajectoryEvents: number;
transcriptEvents: number;
};
@@ -82,6 +85,7 @@ type SqliteEvidence = {
path: string;
sessionEntries: number;
sessions: number;
trajectoryRuntimeEvents: number;
transcriptEvents: number;
trackedEntries: SqliteSessionEntryEvidence[];
};
@@ -98,6 +102,9 @@ type ProofCheckpoint = {
type PluginSdkConsumerEvidence = {
activeJsonlForSessionExists: boolean;
activeTrajectoryPointerForSessionExists: boolean;
activeTrajectoryRuntimeSidecarForSessionExists: boolean;
activeTrajectorySessionSidecarForSessionExists: boolean;
appendedMessageId: string;
identityMemoryKey: string;
latestAssistantTextBeforeAppend: string;
@@ -107,6 +114,9 @@ type PluginSdkConsumerEvidence = {
sessionId: string;
sessionKey: string;
storeTranscriptEvents: number;
trajectoryEventsAfterAppend: number;
trajectoryEventsBeforeAppend: number;
trajectoryEventType: string;
transcriptEventsAfterAppend: number;
transcriptEventsBeforeAppend: number;
};
@@ -1262,10 +1272,43 @@ async function runPluginSdkConsumerProbe(
const transcriptEventsBeforeAppend = (await readSessionTranscriptEvents(scope)).length;
const storeTranscriptEvents = loadSdkTranscriptEventsSync(scope).length;
const activeJsonlPath = path.join(context.activeSessionsDir, `${sessionId}.jsonl`);
const activeTrajectorySessionSidecarPath = path.join(
context.activeSessionsDir,
`${sessionId}.trajectory.jsonl`,
);
const activeTrajectoryPointerPath = path.join(
context.activeSessionsDir,
`${sessionId}.trajectory-path.json`,
);
const activeTrajectoryRuntimeSidecarPath = path.join(
context.activeSessionsDir,
"trajectory",
`${sessionId}.jsonl`,
);
const activeJsonlForSessionExists = fsSync.existsSync(activeJsonlPath);
if (activeJsonlForSessionExists) {
throw new Error(`SDK probe found active JSONL for SQLite session at ${activeJsonlPath}`);
}
const activeTrajectorySessionSidecarForSessionExists = fsSync.existsSync(
activeTrajectorySessionSidecarPath,
);
const activeTrajectoryPointerForSessionExists = fsSync.existsSync(activeTrajectoryPointerPath);
const activeTrajectoryRuntimeSidecarForSessionExists = fsSync.existsSync(
activeTrajectoryRuntimeSidecarPath,
);
if (
activeTrajectorySessionSidecarForSessionExists ||
activeTrajectoryPointerForSessionExists ||
activeTrajectoryRuntimeSidecarForSessionExists
) {
throw new Error(
`SDK trajectory probe found active sidecar paths: ${JSON.stringify({
pointer: activeTrajectoryPointerPath,
runtime: activeTrajectoryRuntimeSidecarPath,
session: activeTrajectorySessionSidecarPath,
})}`,
);
}
const appended = await appendSessionTranscriptMessageByIdentity({
...scope,
@@ -1293,9 +1336,44 @@ async function runPluginSdkConsumerProbe(
`SDK transcript append did not increase event count for ${context.pluginSdkSessionKey}`,
);
}
const trajectoryEventsBeforeAppend = countSqliteTrajectoryRuntimeEvents(
context.agentDbPath,
sessionId,
);
const trajectoryEventType = "e2e.sdk.trajectory";
const trajectoryEvent: SessionStoreTrajectoryEvent = {
traceSchema: "openclaw-trajectory",
schemaVersion: 1,
traceId: sessionId,
source: "runtime",
type: trajectoryEventType,
ts: new Date().toISOString(),
seq: 1,
sourceSeq: 1,
sessionId,
sessionKey: context.pluginSdkSessionKey,
runId: "sqlite-flip-e2e-sdk",
data: { proof: "sqlite trajectory runtime row" },
};
appendTrajectoryRuntimeEvents({
...scope,
events: [trajectoryEvent],
});
const trajectoryEventsAfterAppend = countSqliteTrajectoryRuntimeEvents(
context.agentDbPath,
sessionId,
);
if (trajectoryEventsAfterAppend <= trajectoryEventsBeforeAppend) {
throw new Error(
`SDK trajectory append did not increase event count for ${context.pluginSdkSessionKey}`,
);
}
return {
activeJsonlForSessionExists,
activeTrajectoryPointerForSessionExists,
activeTrajectoryRuntimeSidecarForSessionExists,
activeTrajectorySessionSidecarForSessionExists,
appendedMessageId: appended.messageId,
identityMemoryKey: identity.memoryKey,
latestAssistantTextBeforeAppend: latestBefore.text,
@@ -1305,6 +1383,9 @@ async function runPluginSdkConsumerProbe(
sessionId,
sessionKey: context.pluginSdkSessionKey,
storeTranscriptEvents,
trajectoryEventsAfterAppend,
trajectoryEventsBeforeAppend,
trajectoryEventType,
transcriptEventsAfterAppend,
transcriptEventsBeforeAppend,
};
@@ -2069,6 +2150,22 @@ function countSqliteTranscriptEvents(dbPath: string, sessionId: string): number
}
}
function countSqliteTrajectoryRuntimeEvents(dbPath: string, sessionId: string): number {
if (!fsSync.existsSync(dbPath)) {
return 0;
}
const db = new DatabaseSync(dbPath, { readOnly: true });
try {
return scalarNumber(
db,
"SELECT COUNT(*) AS count FROM trajectory_runtime_events WHERE session_id = ?",
[sessionId],
);
} finally {
db.close();
}
}
async function captureCheckpoint(
context: ProofContext,
label: string,
@@ -2221,6 +2318,7 @@ function readSqliteEvidence(dbPath: string, trackedSessionKeys: readonly string[
sessionEntries: 0,
sessions: 0,
trackedEntries: [],
trajectoryRuntimeEvents: 0,
transcriptEvents: 0,
};
}
@@ -2232,6 +2330,10 @@ function readSqliteEvidence(dbPath: string, trackedSessionKeys: readonly string[
path: dbPath,
sessionEntries: scalarNumber(db, "SELECT COUNT(*) AS count FROM session_entries"),
sessions: scalarNumber(db, "SELECT COUNT(*) AS count FROM sessions"),
trajectoryRuntimeEvents: scalarNumber(
db,
"SELECT COUNT(*) AS count FROM trajectory_runtime_events",
),
trackedEntries,
transcriptEvents: scalarNumber(db, "SELECT COUNT(*) AS count FROM transcript_events"),
};
@@ -2262,6 +2364,11 @@ function readTrackedEntries(
const result: SqliteSessionEntryEvidence = {
sessionId,
sessionKey: typeof row.sessionKey === "string" ? row.sessionKey : "",
trajectoryEvents: scalarNumber(
db,
"SELECT COUNT(*) AS count FROM trajectory_runtime_events WHERE session_id = ?",
[sessionId],
),
transcriptEvents: scalarNumber(
db,
"SELECT COUNT(*) AS count FROM transcript_events WHERE session_id = ?",

View File

@@ -157,22 +157,32 @@ describe("SQLite sessions/transcripts flip proof harness", () => {
).toBe(true);
expect(report.pluginSdkConsumer).toMatchObject({
activeJsonlForSessionExists: false,
activeTrajectoryPointerForSessionExists: false,
activeTrajectoryRuntimeSidecarForSessionExists: false,
activeTrajectorySessionSidecarForSessionExists: false,
latestAssistantTextBeforeAppend: report.fullTurnAssistantText,
latestAssistantTextAfterAppend: "sqlite sdk consumer appended by identity",
sessionKey: report.pluginSdkSessionKey,
trajectoryEventType: "e2e.sdk.trajectory",
});
expect(report.pluginSdkConsumer?.sessionFileMarker.startsWith("sqlite:")).toBe(true);
expect(report.pluginSdkConsumer?.listedSessionKeys).toContain(report.pluginSdkSessionKey);
expect(report.pluginSdkConsumer?.transcriptEventsAfterAppend).toBeGreaterThan(
report.pluginSdkConsumer?.transcriptEventsBeforeAppend ?? 0,
);
expect(report.pluginSdkConsumer?.trajectoryEventsAfterAppend).toBeGreaterThan(
report.pluginSdkConsumer?.trajectoryEventsBeforeAppend ?? 0,
);
expect(
report.checkpoints.some(
(checkpoint) =>
checkpoint.label === "after-plugin-sdk-consumer" &&
checkpoint.sqlite.trajectoryRuntimeEvents >= 1 &&
checkpoint.sqlite.trackedEntries.some(
(entry) =>
entry.sessionKey === report.pluginSdkSessionKey && entry.transcriptEvents >= 3,
entry.sessionKey === report.pluginSdkSessionKey &&
entry.trajectoryEvents >= 1 &&
entry.transcriptEvents >= 3,
),
),
).toBe(true);