Skip to content

Commit 8c5c474

Browse files
committed
feat: version runtime observability snapshots
1 parent bd79b92 commit 8c5c474

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

lib/runtime/runtime-observability.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface RuntimeMetricsSnapshot {
3434
}
3535

3636
export interface RuntimeObservabilitySnapshot {
37+
version: number;
3738
updatedAt: number;
3839
currentRequestId: string | null;
3940
responsesRequests: number;
@@ -46,6 +47,7 @@ export interface RuntimeObservabilitySnapshot {
4647

4748
const SNAPSHOT_FILE_NAME = "runtime-observability.json";
4849
const PERSIST_RUNTIME_SNAPSHOT = process.env.VITEST !== "true";
50+
const RUNTIME_OBSERVABILITY_SNAPSHOT_VERSION = 1;
4951

5052
let snapshotState: RuntimeObservabilitySnapshot | null = null;
5153
let pendingWrite: Promise<void> | null = null;
@@ -56,6 +58,7 @@ function getSnapshotPath(): string {
5658

5759
function createDefaultSnapshot(): RuntimeObservabilitySnapshot {
5860
return {
61+
version: RUNTIME_OBSERVABILITY_SNAPSHOT_VERSION,
5962
updatedAt: 0,
6063
currentRequestId: null,
6164
responsesRequests: 0,
@@ -148,7 +151,26 @@ export async function loadPersistedRuntimeObservabilitySnapshot(): Promise<Runti
148151
}
149152
try {
150153
const raw = await fs.readFile(path, "utf-8");
151-
return JSON.parse(raw) as RuntimeObservabilitySnapshot;
154+
const parsed = JSON.parse(raw) as Partial<RuntimeObservabilitySnapshot> | null;
155+
if (!parsed || typeof parsed !== "object") {
156+
return null;
157+
}
158+
if (
159+
typeof parsed.version === "number" &&
160+
parsed.version !== RUNTIME_OBSERVABILITY_SNAPSHOT_VERSION
161+
) {
162+
return null;
163+
}
164+
const base = createDefaultSnapshot();
165+
return {
166+
...base,
167+
...parsed,
168+
version: RUNTIME_OBSERVABILITY_SNAPSHOT_VERSION,
169+
runtimeMetrics: {
170+
...base.runtimeMetrics,
171+
...(parsed.runtimeMetrics ?? {}),
172+
},
173+
};
152174
} catch {
153175
return null;
154176
}

test/runtime-observability.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
const readFileMock = vi.fn();
4+
vi.mock("node:fs", () => ({
5+
existsSync: vi.fn(() => true),
6+
promises: {
7+
readFile: readFileMock,
8+
writeFile: vi.fn(async () => undefined),
9+
rename: vi.fn(async () => undefined),
10+
unlink: vi.fn(async () => undefined),
11+
mkdir: vi.fn(async () => undefined),
12+
},
13+
}));
14+
15+
vi.mock("../lib/runtime-paths.js", () => ({
16+
getCodexMultiAuthDir: () => "/mock/.codex/multi-auth",
17+
}));
18+
19+
describe("runtime observability snapshot versioning", () => {
20+
beforeEach(() => {
21+
vi.resetModules();
22+
});
23+
24+
afterEach(() => {
25+
readFileMock.mockReset();
26+
});
27+
28+
it("normalizes legacy unversioned snapshots", async () => {
29+
readFileMock.mockResolvedValueOnce(
30+
JSON.stringify({
31+
updatedAt: 1,
32+
responsesRequests: 2,
33+
runtimeMetrics: { totalRequests: 3 },
34+
}),
35+
);
36+
37+
const { loadPersistedRuntimeObservabilitySnapshot } = await import(
38+
"../lib/runtime/runtime-observability.js"
39+
);
40+
const snapshot = await loadPersistedRuntimeObservabilitySnapshot();
41+
42+
expect(snapshot?.version).toBe(1);
43+
expect(snapshot?.responsesRequests).toBe(2);
44+
expect(snapshot?.runtimeMetrics.totalRequests).toBe(3);
45+
expect(snapshot?.runtimeMetrics.failedRequests).toBe(0);
46+
});
47+
48+
it("drops unknown future snapshot versions safely", async () => {
49+
readFileMock.mockResolvedValueOnce(JSON.stringify({ version: 99 }));
50+
51+
const { loadPersistedRuntimeObservabilitySnapshot } = await import(
52+
"../lib/runtime/runtime-observability.js"
53+
);
54+
const snapshot = await loadPersistedRuntimeObservabilitySnapshot();
55+
56+
expect(snapshot).toBeNull();
57+
});
58+
});

0 commit comments

Comments
 (0)