diff --git a/packages/cli/src/__tests__/workflow.test.ts b/packages/cli/src/__tests__/workflow.test.ts index 7609750..b731f16 100644 --- a/packages/cli/src/__tests__/workflow.test.ts +++ b/packages/cli/src/__tests__/workflow.test.ts @@ -44,7 +44,7 @@ function upsertRun( ts: number, ): void { store.upsertWorkflowRun( - { source: "workflow", type: status, refId: runId, payload: null, ts }, + { source: "workflow", type: status, refId: runId, payload: null, timestamp: ts }, { runId, workflow, status, ts }, ); } @@ -252,7 +252,7 @@ describe("buildInspectOutput", () => { }); it("shows event lines with type and ts", () => { - const logs = [{ ts: 1_700_000_001_000, type: "started", payload: null }]; + const logs = [{ timestamp: 1_700_000_001_000, type: "started", payload: null }]; const { eventLines } = buildInspectOutput(baseRun, logs, 0, 20); const text = eventLines.join(""); expect(text).toContain("type=started"); @@ -260,7 +260,7 @@ describe("buildInspectOutput", () => { it("truncates long payloads to 200 chars with ellipsis", () => { const longPayload = "x".repeat(250); - const logs = [{ ts: 1000, type: "step_complete", payload: longPayload }]; + const logs = [{ timestamp: 1000, type: "step_complete", payload: longPayload }]; const { eventLines } = buildInspectOutput(baseRun, logs, 0, 20); const text = eventLines.join(""); expect(text).toContain("…"); @@ -268,14 +268,14 @@ describe("buildInspectOutput", () => { }); it("shows short payloads in full", () => { - const logs = [{ ts: 1000, type: "step_complete", payload: '{"count":5}' }]; + const logs = [{ timestamp: 1000, type: "step_complete", payload: '{"count":5}' }]; const { eventLines } = buildInspectOutput(baseRun, logs, 0, 20); expect(eventLines.join("")).toContain('{"count":5}'); }); it("paginates events with a hint", () => { const logs = Array.from({ length: 5 }, (_, i) => ({ - ts: 1000 + i, + timestamp: 1000 + i, type: "step_complete", payload: null, })); @@ -287,7 +287,7 @@ describe("buildInspectOutput", () => { }); it("no pagination hint when all events fit on one page", () => { - const logs = [{ ts: 1000, type: "started", payload: null }]; + const logs = [{ timestamp: 1000, type: "started", payload: null }]; const { paginationHint } = buildInspectOutput(baseRun, logs, 0, 20); expect(paginationHint).toBeNull(); }); diff --git a/packages/cli/src/commands/workflow.ts b/packages/cli/src/commands/workflow.ts index 105a8a0..7a24e8e 100644 --- a/packages/cli/src/commands/workflow.ts +++ b/packages/cli/src/commands/workflow.ts @@ -139,7 +139,7 @@ export type InspectOutput = { export function buildInspectOutput( run: WorkflowRun, - allLogs: Array<{ ts: number; type: string; payload: string | null }>, + allLogs: Array<{ timestamp: number; type: string; payload: string | null }>, offset: number, limit: number, ): InspectOutput { @@ -167,7 +167,7 @@ export function buildInspectOutput( : entry.payload.length <= 200 ? ` payload=${entry.payload}` : ` payload=${entry.payload.slice(0, 200)}…`; - eventLines.push(` [${formatTs(entry.ts)}] type=${entry.type}${payloadStr}\n`); + eventLines.push(` [${formatTs(entry.timestamp)}] type=${entry.type}${payloadStr}\n`); } } diff --git a/packages/daemon/src/__tests__/hot-reload.test.ts b/packages/daemon/src/__tests__/hot-reload.test.ts index 4bdb52f..5a7d356 100644 --- a/packages/daemon/src/__tests__/hot-reload.test.ts +++ b/packages/daemon/src/__tests__/hot-reload.test.ts @@ -10,6 +10,9 @@ */ import { EventEmitter } from "node:events"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import type { NerveConfig, WorkflowConfig } from "@uncaged/nerve-core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -236,14 +239,18 @@ describe("WorkflowManager — drainAndRespawn (Phase 3 hot reload)", () => { }); describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-hot-reload-")); }); afterEach(async () => { vi.useRealTimers(); vi.clearAllMocks(); + rmSync(nerveRoot, { recursive: true, force: true }); }); it("handleWorkflowFileChange logs workflow_reload system event", async () => { @@ -255,7 +262,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => { maxRounds: 10, }; - const kernel = createKernel(config, "/tmp/nerve-hot-reload-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -290,7 +297,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => { maxRounds: 10, }; - const kernel = createKernel(initialConfig, "/tmp/nerve-hot-reload-test", { + const kernel = createKernel(initialConfig, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -333,7 +340,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => { maxRounds: 10, }; - const kernel = createKernel(initialConfig, "/tmp/nerve-hot-reload-test", { + const kernel = createKernel(initialConfig, nerveRoot, { workerScript: "fake-worker.js", logStore, }); diff --git a/packages/daemon/src/__tests__/kernel-integration.test.ts b/packages/daemon/src/__tests__/kernel-integration.test.ts index 9bd868e..c916e12 100644 --- a/packages/daemon/src/__tests__/kernel-integration.test.ts +++ b/packages/daemon/src/__tests__/kernel-integration.test.ts @@ -6,12 +6,14 @@ * artifacts are required. */ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import type { Signal } from "@uncaged/nerve-core"; import type { NerveConfig } from "@uncaged/nerve-core"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createKernel } from "../kernel.js"; import type { Kernel } from "../kernel.js"; @@ -55,12 +57,18 @@ async function pollUntil( describe("kernel integration — real child processes", () => { let kernel: Kernel | null = null; + let nerveRoot: string; + + beforeEach(() => { + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-integration-")); + }); afterEach(async () => { if (kernel !== null) { await kernel.stop(); kernel = null; } + rmSync(nerveRoot, { recursive: true, force: true }); }); it("returns correct groups and senseCount", () => { @@ -71,7 +79,7 @@ describe("kernel integration — real child processes", () => { "net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null }, }, }); - kernel = createKernel(config, "/tmp/nerve-integration-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); @@ -83,7 +91,7 @@ describe("kernel integration — real child processes", () => { it("workers start and respond to compute messages with signals", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-integration-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); @@ -115,7 +123,7 @@ describe("kernel integration — real child processes", () => { "net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null }, }, }); - kernel = createKernel(config, "/tmp/nerve-integration-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); @@ -131,7 +139,7 @@ describe("kernel integration — real child processes", () => { it("compute round-trip: worker receives compute and sends signal back through bus", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-integration-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); @@ -158,7 +166,7 @@ describe("kernel integration — real child processes", () => { it("crash recovery: kernel respawns worker after unexpected exit and new worker is functional", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-integration-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); diff --git a/packages/daemon/src/__tests__/kernel-phase6.test.ts b/packages/daemon/src/__tests__/kernel-phase6.test.ts index 8eab8d4..2dd285a 100644 --- a/packages/daemon/src/__tests__/kernel-phase6.test.ts +++ b/packages/daemon/src/__tests__/kernel-phase6.test.ts @@ -4,6 +4,9 @@ */ import { EventEmitter } from "node:events"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import type { NerveConfig } from "@uncaged/nerve-core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -84,13 +87,17 @@ function makeConfig(overrides: Partial = {}): NerveConfig { // --------------------------------------------------------------------------- describe("kernel — getHealth", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-kernel-p6-health-")); }); afterEach(() => { vi.useRealTimers(); + rmSync(nerveRoot, { recursive: true, force: true }); }); it("returns correct health shape", async () => { @@ -101,7 +108,7 @@ describe("kernel — getHealth", () => { "net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null }, }, }); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); const health = kernel.getHealth(); expect(health.activeSenses).toBe(3); @@ -115,18 +122,22 @@ describe("kernel — getHealth", () => { }); describe("kernel — restartGroup", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-kernel-p6-restart-")); }); afterEach(() => { vi.useRealTimers(); + rmSync(nerveRoot, { recursive: true, force: true }); }); it("sends shutdown to old worker and spawns new one", async () => { const config = makeConfig(); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); expect(mockChildren.length).toBe(1); const oldChild = mockChildren[0]; @@ -146,7 +157,7 @@ describe("kernel — restartGroup", () => { it("restartGroup on unknown group does nothing", async () => { const config = makeConfig(); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); expect(mockChildren.length).toBe(1); await kernel.restartGroup("nonexistent"); @@ -158,18 +169,22 @@ describe("kernel — restartGroup", () => { }); describe("kernel — reloadConfig", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-kernel-p6-reload-")); }); afterEach(() => { vi.useRealTimers(); + rmSync(nerveRoot, { recursive: true, force: true }); }); it("adds new group worker when new sense group appears", async () => { const config = makeConfig(); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); expect(mockChildren.length).toBe(1); // only system group expect(kernel.groups.has("network")).toBe(false); @@ -200,7 +215,7 @@ describe("kernel — reloadConfig", () => { workflows: {}, maxRounds: 10, }; - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); expect(mockChildren.length).toBe(2); expect(kernel.groups.has("network")).toBe(true); @@ -225,7 +240,7 @@ describe("kernel — reloadConfig", () => { it("health reflects updated sense count after reloadConfig", async () => { const config = makeConfig(); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); expect(kernel.getHealth().activeSenses).toBe(1); diff --git a/packages/daemon/src/__tests__/kernel-trigger-sense.test.ts b/packages/daemon/src/__tests__/kernel-trigger-sense.test.ts index ecdc458..9fad4be 100644 --- a/packages/daemon/src/__tests__/kernel-trigger-sense.test.ts +++ b/packages/daemon/src/__tests__/kernel-trigger-sense.test.ts @@ -6,6 +6,9 @@ */ import { EventEmitter } from "node:events"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import type { NerveConfig } from "@uncaged/nerve-core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -103,18 +106,22 @@ function makeConfig(overrides: Partial = {}): NerveConfig { // --------------------------------------------------------------------------- describe("kernel.triggerSense()", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-trigger-sense-")); }); afterEach(() => { vi.useRealTimers(); + rmSync(nerveRoot, { recursive: true, force: true }); }); it("throws for an unknown sense name", async () => { const config = makeConfig(); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: null, logStore: makeMockLogStore() as never, }); @@ -132,7 +139,7 @@ describe("kernel.triggerSense()", () => { }, reflexes: [], }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: null, logStore: makeMockLogStore() as never, }); @@ -162,7 +169,7 @@ describe("kernel.triggerSense()", () => { }, reflexes: [], }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: null, logStore: makeMockLogStore() as never, }); @@ -185,7 +192,7 @@ describe("kernel.triggerSense()", () => { vi.useRealTimers(); const config = makeConfig(); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: null, logStore: makeMockLogStore() as never, }); diff --git a/packages/daemon/src/__tests__/kernel-workflow-integration.test.ts b/packages/daemon/src/__tests__/kernel-workflow-integration.test.ts index 4966041..9e1b646 100644 --- a/packages/daemon/src/__tests__/kernel-workflow-integration.test.ts +++ b/packages/daemon/src/__tests__/kernel-workflow-integration.test.ts @@ -9,6 +9,9 @@ */ import { EventEmitter } from "node:events"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import type { NerveConfig } from "@uncaged/nerve-core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -106,14 +109,18 @@ function makeConfig(overrides: Partial = {}): NerveConfig { // --------------------------------------------------------------------------- describe("kernel + workflowManager integration", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-kernel-wf-")); }); afterEach(async () => { vi.useRealTimers(); vi.clearAllMocks(); + rmSync(nerveRoot, { recursive: true, force: true }); }); describe("sense compute triggers workflow via return value", () => { @@ -127,7 +134,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "my-workflow": { concurrency: 2, overflow: "drop" } }, }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -171,7 +178,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "alert-workflow": { concurrency: 1, overflow: "drop" } }, }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -221,7 +228,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "my-workflow": { concurrency: 1, overflow: "drop" } }, }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -264,7 +271,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "log-test-workflow": { concurrency: 2, overflow: "drop" } }, }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -302,7 +309,7 @@ describe("kernel + workflowManager integration", () => { maxRounds: 10, }); - const kernel = createKernel(initialConfig, "/tmp/nerve-test", { + const kernel = createKernel(initialConfig, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -355,7 +362,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "old-workflow": { concurrency: 1, overflow: "drop" } }, }); - const kernel = createKernel(initialConfig, "/tmp/nerve-test", { + const kernel = createKernel(initialConfig, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -414,7 +421,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "shutdown-test": { concurrency: 1, overflow: "drop" } }, }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -440,7 +447,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "my-wf": { concurrency: 1, overflow: "drop" } }, }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); @@ -463,7 +470,7 @@ describe("kernel + workflowManager integration", () => { workflows: { "health-wf": { concurrency: 2, overflow: "drop" } }, }); - const kernel = createKernel(config, "/tmp/nerve-test", { + const kernel = createKernel(config, nerveRoot, { workerScript: "fake-worker.js", logStore, }); diff --git a/packages/daemon/src/__tests__/kernel.test.ts b/packages/daemon/src/__tests__/kernel.test.ts index f758f10..5b2b277 100644 --- a/packages/daemon/src/__tests__/kernel.test.ts +++ b/packages/daemon/src/__tests__/kernel.test.ts @@ -70,13 +70,17 @@ function makeConfig(overrides: Partial = {}): NerveConfig { // --------------------------------------------------------------------------- describe("kernel — message routing", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-kernel-msg-")); }); afterEach(() => { vi.useRealTimers(); + rmSync(nerveRoot, { recursive: true, force: true }); }); it("routes signal message to bus without throwing", async () => { @@ -86,7 +90,7 @@ describe("kernel — message routing", () => { }, reflexes: [], }); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); expect(mockChildren.length).toBe(1); const child = mockChildren[0]; @@ -129,7 +133,7 @@ describe("kernel — message routing", () => { }, reflexes: [], }); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); const child = mockChildren[0]; child.emit("message", { type: "error", sense: "cpu-usage", error: "compute failed" }); @@ -150,7 +154,7 @@ describe("kernel — message routing", () => { }, reflexes: [], }); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); const child = mockChildren[0]; const callsBefore = stderrSpy.mock.calls.length; @@ -170,7 +174,7 @@ describe("kernel — message routing", () => { }, reflexes: [], }); - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); const child = mockChildren[0]; expect(() => child.emit("message", { type: "unknown-type" })).not.toThrow(); @@ -183,13 +187,17 @@ describe("kernel — message routing", () => { }); describe("kernel — groupForSense mapping", () => { + let nerveRoot: string; + beforeEach(() => { mockChildren.length = 0; vi.useFakeTimers({ shouldAdvanceTime: true }); + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-kernel-groups-")); }); afterEach(() => { vi.useRealTimers(); + rmSync(nerveRoot, { recursive: true, force: true }); }); it("spawns one worker per unique group", async () => { @@ -203,7 +211,7 @@ describe("kernel — groupForSense mapping", () => { workflows: {}, maxRounds: 10, }; - const kernel = createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); // system and network = 2 unique groups expect(mockChildren.length).toBe(2); @@ -217,7 +225,7 @@ describe("kernel — groupForSense mapping", () => { }, reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: [] }], }); - createKernel(config, "/tmp/nerve-test"); + const kernel = createKernel(config, nerveRoot); const child = mockChildren[0]; vi.advanceTimersByTime(500); @@ -225,5 +233,7 @@ describe("kernel — groupForSense mapping", () => { expect(child.send).toHaveBeenCalledWith( expect.objectContaining({ type: "compute", sense: "cpu-usage" }), ); + + await kernel.stop(); }); }); diff --git a/packages/daemon/src/__tests__/log-store-integration.test.ts b/packages/daemon/src/__tests__/log-store-integration.test.ts index e627208..8323abb 100644 --- a/packages/daemon/src/__tests__/log-store-integration.test.ts +++ b/packages/daemon/src/__tests__/log-store-integration.test.ts @@ -108,7 +108,7 @@ describe("LogStore + ReflexScheduler integration", () => { type: "run_complete", refId: "cpu-usage", payload: '{"v":99}', - ts: Date.now(), + timestamp: Date.now(), }); // Writing to the log store should NOT trigger any reflex. diff --git a/packages/daemon/src/__tests__/phase6-integration.test.ts b/packages/daemon/src/__tests__/phase6-integration.test.ts index 6915542..6c9fd10 100644 --- a/packages/daemon/src/__tests__/phase6-integration.test.ts +++ b/packages/daemon/src/__tests__/phase6-integration.test.ts @@ -2,12 +2,14 @@ * Phase 6 integration tests — hot reload, error isolation, grace period, health. */ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import type { Signal } from "@uncaged/nerve-core"; import type { NerveConfig } from "@uncaged/nerve-core"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createKernel } from "../kernel.js"; import type { Kernel } from "../kernel.js"; @@ -55,17 +57,23 @@ async function pollUntil( describe("phase6 — restartGroup", () => { let kernel: Kernel | null = null; + let nerveRoot: string; + + beforeEach(() => { + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-phase6-restart-")); + }); afterEach(async () => { if (kernel !== null) { await kernel.stop(); kernel = null; } + rmSync(nerveRoot, { recursive: true, force: true }); }); it("restartGroup stops old worker and spawns a new one", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); @@ -97,7 +105,7 @@ describe("phase6 — restartGroup", () => { it("restartGroup on nonexistent group does nothing", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); await kernel.ready; @@ -113,17 +121,23 @@ describe("phase6 — restartGroup", () => { describe("phase6 — reloadConfig", () => { let kernel: Kernel | null = null; + let nerveRoot: string; + + beforeEach(() => { + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-phase6-reload-")); + }); afterEach(async () => { if (kernel !== null) { await kernel.stop(); kernel = null; } + rmSync(nerveRoot, { recursive: true, force: true }); }); it("adds new group when new sense group is introduced", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); await kernel.ready; @@ -159,7 +173,7 @@ describe("phase6 — reloadConfig", () => { workflows: {}, maxRounds: 10, }; - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); await kernel.ready; @@ -187,12 +201,18 @@ describe("phase6 — reloadConfig", () => { describe("phase6 — error isolation", () => { let kernel: Kernel | null = null; + let nerveRoot: string; + + beforeEach(() => { + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-phase6-err-")); + }); afterEach(async () => { if (kernel !== null) { await kernel.stop(); kernel = null; } + rmSync(nerveRoot, { recursive: true, force: true }); }); it("error from one sense does not crash the worker — other senses still work", async () => { @@ -206,7 +226,7 @@ describe("phase6 — error isolation", () => { maxRounds: 10, }; - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); await kernel.ready; @@ -238,7 +258,7 @@ describe("phase6 — error isolation", () => { process.stderr.write = stderrSpy as typeof process.stderr.write; const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: ERROR_WORKER, }); await kernel.ready; @@ -261,12 +281,18 @@ describe("phase6 — error isolation", () => { describe("phase6 — getHealth", () => { let kernel: Kernel | null = null; + let nerveRoot: string; + + beforeEach(() => { + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-phase6-health-")); + }); afterEach(async () => { if (kernel !== null) { await kernel.stop(); kernel = null; } + rmSync(nerveRoot, { recursive: true, force: true }); }); it("returns health snapshot with correct shape", async () => { @@ -277,7 +303,7 @@ describe("phase6 — getHealth", () => { "net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null }, }, }); - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); await kernel.ready; @@ -293,7 +319,7 @@ describe("phase6 — getHealth", () => { it("health reflects config changes after reloadConfig", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); await kernel.ready; @@ -322,17 +348,23 @@ describe("phase6 — getHealth", () => { describe("phase6 — auto-respawn on worker crash", () => { let kernel: Kernel | null = null; + let nerveRoot: string; + + beforeEach(() => { + nerveRoot = mkdtempSync(join(tmpdir(), "nerve-phase6-crash-")); + }); afterEach(async () => { if (kernel !== null) { await kernel.stop(); kernel = null; } + rmSync(nerveRoot, { recursive: true, force: true }); }); it("kernel auto-respawns worker and new worker is functional", async () => { const config = makeConfig(); - kernel = createKernel(config, "/tmp/nerve-phase6-test", { + kernel = createKernel(config, nerveRoot, { workerScript: MOCK_WORKER, }); await kernel.ready; diff --git a/packages/daemon/src/kernel-file-watch.ts b/packages/daemon/src/kernel-file-watch.ts index 3bb8c1b..f94b124 100644 --- a/packages/daemon/src/kernel-file-watch.ts +++ b/packages/daemon/src/kernel-file-watch.ts @@ -38,7 +38,7 @@ export function createKernelFileWatchHandlers(deps: KernelFileWatchDeps): Kernel type: "sense_reload", refId: senseName, payload: null, - ts: Date.now(), + timestamp: Date.now(), }); deps.restartGroup(sc.group).catch((e) => { const msg = e instanceof Error ? e.message : String(e); @@ -55,7 +55,7 @@ export function createKernelFileWatchHandlers(deps: KernelFileWatchDeps): Kernel type: "workflow_reload", refId: workflowName, payload: null, - ts: Date.now(), + timestamp: Date.now(), }); deps.workflowManager.drainAndRespawn(workflowName).catch((e) => { const msg = e instanceof Error ? e.message : String(e); @@ -70,7 +70,7 @@ export function createKernelFileWatchHandlers(deps: KernelFileWatchDeps): Kernel type: "config_reload", refId: null, payload: null, - ts: Date.now(), + timestamp: Date.now(), }); try { const raw = readFileSync(join(deps.nerveRoot, "nerve.yaml"), "utf8"); diff --git a/packages/daemon/src/kernel.ts b/packages/daemon/src/kernel.ts index 8d7fe21..d089e53 100644 --- a/packages/daemon/src/kernel.ts +++ b/packages/daemon/src/kernel.ts @@ -81,7 +81,7 @@ export function createKernel( type: "start", refId: null, payload: null, - ts: startTime, + timestamp: startTime, }); let config = initialConfig; @@ -138,7 +138,7 @@ export function createKernel( type: "error", refId: msg.sense, payload: JSON.stringify({ error: msg.error }), - ts: Date.now(), + timestamp: Date.now(), }); scheduler.onComputeComplete(msg.sense); return; @@ -154,7 +154,7 @@ export function createKernel( type: "workflow-launch", refId: msg.sense, payload: JSON.stringify(route.launch), - ts: Date.now(), + timestamp: Date.now(), }); } else { const signal: Signal = { @@ -168,7 +168,7 @@ export function createKernel( type: "signal", refId: msg.sense, payload: JSON.stringify(route.payload), - ts: signal.timestamp, + timestamp: signal.timestamp, }); bus.emit(signal); } @@ -327,7 +327,7 @@ export function createKernel( group: senseConfig.group, throttle: senseConfig.throttle, timeout: senseConfig.timeout, - lastSignalTimestamp: lastEntry !== null ? lastEntry.ts : null, + lastSignalTimestamp: lastEntry !== null ? lastEntry.timestamp : null, }; }); }, @@ -352,7 +352,7 @@ export function createKernel( type: "stop", refId: null, payload: null, - ts: Date.now(), + timestamp: Date.now(), }); logStore.close(); } diff --git a/packages/daemon/src/reflex-scheduler.ts b/packages/daemon/src/reflex-scheduler.ts index ac61845..589f313 100644 --- a/packages/daemon/src/reflex-scheduler.ts +++ b/packages/daemon/src/reflex-scheduler.ts @@ -78,7 +78,7 @@ export function createReflexScheduler( type: "run_start", refId: senseName, payload: null, - ts: Date.now(), + timestamp: Date.now(), }); triggerFn(senseName); } diff --git a/packages/daemon/src/workflow-manager.ts b/packages/daemon/src/workflow-manager.ts index f030915..156e142 100644 --- a/packages/daemon/src/workflow-manager.ts +++ b/packages/daemon/src/workflow-manager.ts @@ -245,7 +245,7 @@ export function createWorkflowManager( if (status !== null) { logStore.upsertWorkflowRun( - { source: "workflow", type: eventType, refId: runId, payload: serialised, ts }, + { source: "workflow", type: eventType, refId: runId, payload: serialised, timestamp: ts }, { runId, workflow: workflowName, status, ts }, ); } else { @@ -254,7 +254,7 @@ export function createWorkflowManager( type: eventType, refId: runId, payload: serialised, - ts, + timestamp: ts, }); } } @@ -440,7 +440,7 @@ export function createWorkflowManager( type: "thread_workflow_message", refId: msg.runId, payload: JSON.stringify(msg.message), - ts: Date.now(), + timestamp: Date.now(), }); return; } diff --git a/packages/store/src/__tests__/log-store-archive.test.ts b/packages/store/src/__tests__/log-store-archive.test.ts index d3cf8e3..e63d9fd 100644 --- a/packages/store/src/__tests__/log-store-archive.test.ts +++ b/packages/store/src/__tests__/log-store-archive.test.ts @@ -30,8 +30,8 @@ describe("LogStore — cold archive (RFC-001 §5.4)", () => { it("exports one UTC day to JSONL, deletes rows, advances archived_up_to", () => { const ts = Date.UTC(2026, 1, 1, 10, 0, 0); - store.append({ source: "system", type: "x", refId: null, payload: '{"a":1}', ts }); - store.append({ source: "reflex", type: "y", refId: "z", payload: null, ts: ts + 1 }); + store.append({ source: "system", type: "x", refId: null, payload: '{"a":1}', timestamp: ts }); + store.append({ source: "reflex", type: "y", refId: "z", payload: null, timestamp: ts + 1 }); const now = nowForLastArchivableFeb1(); const result = store.archiveLogs({ now, retentionMs: 30 * DAY_MS }); @@ -61,7 +61,7 @@ describe("LogStore — cold archive (RFC-001 §5.4)", () => { it("does nothing when all logs are inside the hot window", () => { const now = Date.UTC(2026, 3, 23, 12, 0, 0); const ts = now - 5 * DAY_MS; - store.append({ source: "system", type: "warm", refId: null, payload: null, ts }); + store.append({ source: "system", type: "warm", refId: null, payload: null, timestamp: ts }); const r = store.archiveLogs({ now, retentionMs: 30 * DAY_MS }); expect(r.days).toHaveLength(0); expect(store.query()).toHaveLength(1); @@ -69,7 +69,7 @@ describe("LogStore — cold archive (RFC-001 §5.4)", () => { it("second archive with same clock is a no-op (watermark already caught up)", () => { const ts = Date.UTC(2026, 1, 1, 10, 0, 0); - store.append({ source: "system", type: "x", refId: null, payload: null, ts }); + store.append({ source: "system", type: "x", refId: null, payload: null, timestamp: ts }); const now = nowForLastArchivableFeb1(); store.archiveLogs({ now, retentionMs: 30 * DAY_MS }); const path = join(tmpDir, "data", "archive", "logs", "2026-02-01.jsonl"); @@ -82,11 +82,11 @@ describe("LogStore — cold archive (RFC-001 §5.4)", () => { it("overwrites JSONL when the same UTC day is archived again after watermark rewind", () => { const ts = Date.UTC(2026, 1, 1, 10, 0, 0); - store.append({ source: "a", type: "1", refId: null, payload: null, ts }); + store.append({ source: "a", type: "1", refId: null, payload: null, timestamp: ts }); const now = nowForLastArchivableFeb1(); store.archiveLogs({ now, retentionMs: 30 * DAY_MS }); store.setMeta(LOG_ARCHIVE_META_KEY, "2026-01-31"); - store.append({ source: "b", type: "2", refId: null, payload: null, ts: ts + 100 }); + store.append({ source: "b", type: "2", refId: null, payload: null, timestamp: ts + 100 }); store.archiveLogs({ now, retentionMs: 30 * DAY_MS }); const path = join(tmpDir, "data", "archive", "logs", "2026-02-01.jsonl"); @@ -98,8 +98,8 @@ describe("LogStore — cold archive (RFC-001 §5.4)", () => { it("respects maxDays across invocations", () => { const t1 = Date.UTC(2026, 1, 1, 10, 0, 0); const t2 = Date.UTC(2026, 1, 2, 10, 0, 0); - store.append({ source: "system", type: "a", refId: null, payload: null, ts: t1 }); - store.append({ source: "system", type: "b", refId: null, payload: null, ts: t2 }); + store.append({ source: "system", type: "a", refId: null, payload: null, timestamp: t1 }); + store.append({ source: "system", type: "b", refId: null, payload: null, timestamp: t2 }); const now = Date.UTC(2027, 0, 1, 12, 0, 0); const r1 = store.archiveLogs({ now, retentionMs: 30 * DAY_MS, maxDays: 1 }); @@ -116,7 +116,7 @@ describe("LogStore — cold archive (RFC-001 §5.4)", () => { it("starts from earliest log day when it is before watermark+1", () => { store.setMeta(LOG_ARCHIVE_META_KEY, "2026-01-10"); const ts = Date.UTC(2026, 1, 1, 10, 0, 0); - store.append({ source: "x", type: "p", refId: null, payload: null, ts }); + store.append({ source: "x", type: "p", refId: null, payload: null, timestamp: ts }); const result = store.archiveLogs({ now: nowForLastArchivableFeb1(), retentionMs: 30 * DAY_MS }); expect(result.days.map((d) => d.day)).toContain("2026-02-01"); }); @@ -128,7 +128,7 @@ describe("LogStore — cold archive (RFC-001 §5.4)", () => { it("runs VACUUM when vacuum: true", () => { const ts = Date.UTC(2026, 1, 1, 10, 0, 0); - store.append({ source: "system", type: "x", refId: null, payload: null, ts }); + store.append({ source: "system", type: "x", refId: null, payload: null, timestamp: ts }); const r = store.archiveLogs({ now: nowForLastArchivableFeb1(), retentionMs: 30 * DAY_MS, diff --git a/packages/store/src/__tests__/log-store-crash-recovery.test.ts b/packages/store/src/__tests__/log-store-crash-recovery.test.ts index 979a82e..a751174 100644 --- a/packages/store/src/__tests__/log-store-crash-recovery.test.ts +++ b/packages/store/src/__tests__/log-store-crash-recovery.test.ts @@ -39,7 +39,7 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "started", refId: "run-1", payload: JSON.stringify({ triggerPayload: payload }), - ts: 1000, + timestamp: 1000, }, { runId: "run-1", workflow: "my-wf", status: "started", ts: 1000 }, ); @@ -55,7 +55,7 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "started", refId: "run-2", payload: null, - ts: 1000, + timestamp: 1000, }, { runId: "run-2", workflow: "my-wf", status: "started", ts: 1000 }, ); @@ -72,14 +72,14 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "started", refId: "run-3", payload: JSON.stringify({ triggerPayload: payloadA }), - ts: 100, + timestamp: 100, }); store.append({ source: "workflow", type: "started", refId: "run-3", payload: JSON.stringify({ triggerPayload: payloadB }), - ts: 200, + timestamp: 200, }); const result = store.getTriggerPayload("run-3"); @@ -106,7 +106,7 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "thread_command_event", refId: "run-4", payload: JSON.stringify(event), - ts: Date.now(), + timestamp: Date.now(), }); } @@ -123,14 +123,14 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "thread_command_event", refId: "run-5", payload: null, - ts: 1000, + timestamp: 1000, }); store.append({ source: "workflow", type: "thread_command_event", refId: "run-5", payload: JSON.stringify({ type: "valid_event" }), - ts: 1001, + timestamp: 1001, }); const result = store.getThreadEvents("run-5"); @@ -146,7 +146,7 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "started", refId: "run-6", payload: JSON.stringify({ triggerPayload: {} }), - ts: 1000, + timestamp: 1000, }, { runId: "run-6", workflow: "my-wf", status: "started", ts: 1000 }, ); @@ -155,14 +155,14 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "thread_command_event", refId: "run-6", payload: JSON.stringify({ type: "step_one" }), - ts: 1001, + timestamp: 1001, }); store.append({ source: "workflow", type: "step_complete", refId: "run-6", payload: JSON.stringify({ message: "done step" }), - ts: 1002, + timestamp: 1002, }); const result = store.getThreadEvents("run-6"); @@ -176,14 +176,14 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "thread_command_event", refId: "run-7", payload: JSON.stringify({ type: "event_for_7" }), - ts: 1000, + timestamp: 1000, }); store.append({ source: "workflow", type: "thread_command_event", refId: "run-8", payload: JSON.stringify({ type: "event_for_8" }), - ts: 1001, + timestamp: 1001, }); const result7 = store.getThreadEvents("run-7"); @@ -203,7 +203,7 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "thread_command_event", refId: "run-tr", payload: JSON.stringify({ type: "thread_start", triggerPayload: { x: 1 } }), - ts: 100, + timestamp: 100, }); store.append({ source: "workflow", @@ -215,14 +215,14 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { content: "hello", meta: 1, }), - ts: 101, + timestamp: 101, }); store.append({ source: "workflow", type: "thread_command_event", refId: "run-tr", payload: JSON.stringify({ type: "step_b", role: "beta", content: "world" }), - ts: 102, + timestamp: 102, }); expect(store.getThreadRoundCount("run-tr")).toBe(2); @@ -241,7 +241,7 @@ describe("LogStore — crash recovery helpers (Phase 3)", () => { type: "thread_command_event", refId: "run-b4", payload: JSON.stringify({ type: `ev_${i}`, role: "r", content: String(i) }), - ts: 200 + i, + timestamp: 200 + i, }); } diff --git a/packages/store/src/__tests__/log-store-workflow.test.ts b/packages/store/src/__tests__/log-store-workflow.test.ts index 41bdcc6..e53bb49 100644 --- a/packages/store/src/__tests__/log-store-workflow.test.ts +++ b/packages/store/src/__tests__/log-store-workflow.test.ts @@ -34,7 +34,7 @@ describe("LogStore — workflow_runs", () => { }; const entry = store.upsertWorkflowRun( - { source: "workflow", type: "started", refId: "run-1", payload: null, ts: 1000 }, + { source: "workflow", type: "started", refId: "run-1", payload: null, timestamp: 1000 }, run, ); @@ -52,12 +52,12 @@ describe("LogStore — workflow_runs", () => { it("updates existing workflow_runs row on upsert (status transition)", () => { store.upsertWorkflowRun( - { source: "workflow", type: "started", refId: "run-2", payload: null, ts: 1000 }, + { source: "workflow", type: "started", refId: "run-2", payload: null, timestamp: 1000 }, { runId: "run-2", workflow: "cleanup", status: "started", ts: 1000 }, ); store.upsertWorkflowRun( - { source: "workflow", type: "completed", refId: "run-2", payload: null, ts: 2000 }, + { source: "workflow", type: "completed", refId: "run-2", payload: null, timestamp: 2000 }, { runId: "run-2", workflow: "cleanup", status: "completed", ts: 2000 }, ); @@ -78,7 +78,7 @@ describe("LogStore — workflow_runs", () => { ["completed", "completed", 1005], ] as const) { store.upsertWorkflowRun( - { source: "workflow", type, refId: "run-3", payload: null, ts }, + { source: "workflow", type, refId: "run-3", payload: null, timestamp: ts }, { runId: "run-3", workflow: "cleanup", status, ts }, ); } @@ -97,11 +97,11 @@ describe("LogStore — workflow_runs", () => { it("returns the latest state after multiple upserts", () => { store.upsertWorkflowRun( - { source: "workflow", type: "queued", refId: "run-4", payload: null, ts: 100 }, + { source: "workflow", type: "queued", refId: "run-4", payload: null, timestamp: 100 }, { runId: "run-4", workflow: "code-review", status: "queued", ts: 100 }, ); store.upsertWorkflowRun( - { source: "workflow", type: "started", refId: "run-4", payload: null, ts: 200 }, + { source: "workflow", type: "started", refId: "run-4", payload: null, timestamp: 200 }, { runId: "run-4", workflow: "code-review", status: "started", ts: 200 }, ); @@ -114,19 +114,19 @@ describe("LogStore — workflow_runs", () => { describe("getActiveWorkflowRuns", () => { beforeEach(() => { store.upsertWorkflowRun( - { source: "workflow", type: "queued", refId: "r1", payload: null, ts: 100 }, + { source: "workflow", type: "queued", refId: "r1", payload: null, timestamp: 100 }, { runId: "r1", workflow: "cleanup", status: "queued", ts: 100 }, ); store.upsertWorkflowRun( - { source: "workflow", type: "started", refId: "r2", payload: null, ts: 200 }, + { source: "workflow", type: "started", refId: "r2", payload: null, timestamp: 200 }, { runId: "r2", workflow: "cleanup", status: "started", ts: 200 }, ); store.upsertWorkflowRun( - { source: "workflow", type: "completed", refId: "r3", payload: null, ts: 300 }, + { source: "workflow", type: "completed", refId: "r3", payload: null, timestamp: 300 }, { runId: "r3", workflow: "cleanup", status: "completed", ts: 300 }, ); store.upsertWorkflowRun( - { source: "workflow", type: "failed", refId: "r4", payload: null, ts: 400 }, + { source: "workflow", type: "failed", refId: "r4", payload: null, timestamp: 400 }, { runId: "r4", workflow: "deploy", status: "queued", ts: 400 }, ); }); @@ -176,7 +176,7 @@ describe("LogStore — workflow_runs", () => { (status) => { const runId = `run-${status}`; store.upsertWorkflowRun( - { source: "workflow", type: status, refId: runId, payload: null, ts: 1 }, + { source: "workflow", type: status, refId: runId, payload: null, timestamp: 1 }, { runId, workflow: "test", status, ts: 1 }, ); expect(store.getWorkflowRun(runId)?.status).toBe(status); diff --git a/packages/store/src/__tests__/log-store.test.ts b/packages/store/src/__tests__/log-store.test.ts index 7e75b72..a8bea69 100644 --- a/packages/store/src/__tests__/log-store.test.ts +++ b/packages/store/src/__tests__/log-store.test.ts @@ -27,7 +27,7 @@ describe("LogStore", () => { type: "start", refId: null, payload: null, - ts: 1000, + timestamp: 1000, }); expect(entry.id).toBe(1); @@ -41,28 +41,40 @@ describe("LogStore", () => { type: "start", refId: null, payload: null, - ts: 1000, + timestamp: 1000, }); const e2 = store.append({ source: "system", type: "stop", refId: null, payload: null, - ts: 2000, + timestamp: 2000, }); expect(e2.id).toBe((e1.id ?? 0) + 1); }); it("returns all entries when queried with no filter", () => { - store.append({ source: "system", type: "start", refId: null, payload: null, ts: 1000 }); - store.append({ source: "reflex", type: "run_start", refId: "cpu", payload: null, ts: 2000 }); + store.append({ + source: "system", + type: "start", + refId: null, + payload: null, + timestamp: 1000, + }); + store.append({ + source: "reflex", + type: "run_start", + refId: "cpu", + payload: null, + timestamp: 2000, + }); store.append({ source: "reflex", type: "run_complete", refId: "cpu", payload: '{"v":42}', - ts: 3000, + timestamp: 3000, }); const all = store.query(); @@ -72,23 +84,35 @@ describe("LogStore", () => { describe("query filters", () => { beforeEach(() => { - store.append({ source: "system", type: "start", refId: null, payload: null, ts: 1000 }); - store.append({ source: "reflex", type: "run_start", refId: "cpu", payload: null, ts: 2000 }); + store.append({ + source: "system", + type: "start", + refId: null, + payload: null, + timestamp: 1000, + }); + store.append({ + source: "reflex", + type: "run_start", + refId: "cpu", + payload: null, + timestamp: 2000, + }); store.append({ source: "reflex", type: "run_complete", refId: "cpu", payload: '{"v":42}', - ts: 3000, + timestamp: 3000, }); store.append({ source: "system", type: "error", refId: "disk", payload: '{"error":"fail"}', - ts: 4000, + timestamp: 4000, }); - store.append({ source: "system", type: "stop", refId: null, payload: null, ts: 5000 }); + store.append({ source: "system", type: "stop", refId: null, payload: null, timestamp: 5000 }); }); it("filters by source", () => { @@ -111,7 +135,7 @@ describe("LogStore", () => { it("filters by since (inclusive)", () => { const results = store.query({ since: 3000 }); expect(results).toHaveLength(3); - expect(results[0].ts).toBe(3000); + expect(results[0].timestamp).toBe(3000); }); it("filters by until (inclusive)", () => { @@ -146,12 +170,24 @@ describe("LogStore", () => { describe("query ordering", () => { it("returns entries in insertion order (ascending id)", () => { - store.append({ source: "system", type: "start", refId: null, payload: null, ts: 5000 }); - store.append({ source: "reflex", type: "run_start", refId: "a", payload: null, ts: 1000 }); + store.append({ + source: "system", + type: "start", + refId: null, + payload: null, + timestamp: 5000, + }); + store.append({ + source: "reflex", + type: "run_start", + refId: "a", + payload: null, + timestamp: 1000, + }); const all = store.query(); - expect(all[0].ts).toBe(5000); - expect(all[1].ts).toBe(1000); + expect(all[0].timestamp).toBe(5000); + expect(all[1].timestamp).toBe(1000); }); }); @@ -182,7 +218,7 @@ describe("LogStore", () => { describe("append-only semantics", () => { it("ids are always increasing", () => { const entries = Array.from({ length: 10 }, (_, i) => - store.append({ source: "system", type: "test", refId: null, payload: null, ts: i }), + store.append({ source: "system", type: "test", refId: null, payload: null, timestamp: i }), ); for (let i = 1; i < entries.length; i++) { @@ -194,7 +230,13 @@ describe("LogStore", () => { describe("payload JSON round-trip", () => { it("preserves JSON payload", () => { const payload = JSON.stringify({ cpu: 95, host: "node-1" }); - store.append({ source: "reflex", type: "run_complete", refId: "cpu", payload, ts: 1000 }); + store.append({ + source: "reflex", + type: "run_complete", + refId: "cpu", + payload, + timestamp: 1000, + }); const results = store.query({ refId: "cpu" }); expect(results).toHaveLength(1); @@ -206,7 +248,13 @@ describe("LogStore", () => { it("creates nested directory structure for db path", () => { const deepPath = join(tmpDir, "a", "b", "c", "test.db"); const deepStore = createLogStore(deepPath); - deepStore.append({ source: "system", type: "start", refId: null, payload: null, ts: 1000 }); + deepStore.append({ + source: "system", + type: "start", + refId: null, + payload: null, + timestamp: 1000, + }); expect(deepStore.query()).toHaveLength(1); deepStore.close(); }); diff --git a/packages/store/src/log-store.ts b/packages/store/src/log-store.ts index c025db9..8ec94f3 100644 --- a/packages/store/src/log-store.ts +++ b/packages/store/src/log-store.ts @@ -35,7 +35,7 @@ export type LogEntry = { type: string; refId: string | null; payload: string | null; - ts: number; + timestamp: number; }; export type LogQuery = { @@ -174,16 +174,16 @@ export type LogStore = { const SCHEMA_SQL = ` CREATE TABLE IF NOT EXISTS logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - source TEXT NOT NULL, - type TEXT NOT NULL, - ref_id TEXT, - payload TEXT, - ts INTEGER NOT NULL + id INTEGER PRIMARY KEY AUTOINCREMENT, + source TEXT NOT NULL, + type TEXT NOT NULL, + ref_id TEXT, + payload TEXT, + timestamp INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_logs_source_type ON logs(source, type); -CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs(ts); +CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp); CREATE INDEX IF NOT EXISTS idx_logs_ref_id ON logs(ref_id); CREATE TABLE IF NOT EXISTS meta ( @@ -208,7 +208,7 @@ type SqlLogRow = { type: string; ref_id: string | null; payload: string | null; - ts: number; + timestamp: number; }; function buildJsonlBody(rows: SqlLogRow[]): string { @@ -220,7 +220,7 @@ function buildJsonlBody(rows: SqlLogRow[]): string { type: r.type, refId: r.ref_id, payload: r.payload, - ts: r.ts, + timestamp: r.timestamp, }), ); return `${lines.join("\n")}\n`; @@ -333,7 +333,7 @@ export function createLogStore(dbPath: string): LogStore { sqlite.exec(SCHEMA_SQL); const insertStmt = sqlite.prepare( - "INSERT INTO logs (source, type, ref_id, payload, ts) VALUES (@source, @type, @refId, @payload, @ts)", + "INSERT INTO logs (source, type, ref_id, payload, timestamp) VALUES (@source, @type, @refId, @payload, @timestamp)", ); const getMetaStmt = sqlite.prepare("SELECT value FROM meta WHERE key = ?"); @@ -371,7 +371,7 @@ export function createLogStore(dbPath: string): LogStore { const getThreadRoundsStmt = sqlite.prepare( `WITH numbered AS ( - SELECT id, ts, payload, + SELECT id, timestamp, payload, ROW_NUMBER() OVER (ORDER BY id ASC) AS rn FROM logs WHERE source = 'workflow' AND type IN ('thread_command_event', 'thread_workflow_message') AND ref_id = @runId @@ -379,7 +379,7 @@ export function createLogStore(dbPath: string): LogStore { AND COALESCE(json_extract(payload, '$.type'), '') != 'thread_start' AND COALESCE(json_extract(payload, '$.role'), '') != '__start__' ) - SELECT id, ts, payload, rn FROM numbered + SELECT id, timestamp, payload, rn FROM numbered WHERE (@before = 0 OR rn < @before) ORDER BY rn DESC LIMIT @lim`, @@ -401,12 +401,12 @@ export function createLogStore(dbPath: string): LogStore { "SELECT run_id, workflow, status, ts FROM workflow_runs WHERE workflow = ? ORDER BY ts DESC", ); - const minLogTsStmt = sqlite.prepare("SELECT MIN(ts) AS m FROM logs"); + const minLogTsStmt = sqlite.prepare("SELECT MIN(timestamp) AS m FROM logs"); const selectLogsForDayStmt = sqlite.prepare( - "SELECT id, source, type, ref_id, payload, ts FROM logs WHERE ts >= @start AND ts < @endExclusive ORDER BY id ASC", + "SELECT id, source, type, ref_id, payload, timestamp FROM logs WHERE timestamp >= @start AND timestamp < @endExclusive ORDER BY id ASC", ); const deleteLogsForDayStmt = sqlite.prepare( - "DELETE FROM logs WHERE ts >= @start AND ts < @endExclusive", + "DELETE FROM logs WHERE timestamp >= @start AND timestamp < @endExclusive", ); function upsertWorkflowRunTx(entry: Omit, run: WorkflowRun): LogEntry { @@ -416,7 +416,7 @@ export function createLogStore(dbPath: string): LogStore { type: entry.type, refId: entry.refId, payload: entry.payload, - ts: entry.ts, + timestamp: entry.timestamp, }); upsertWorkflowRunStmt.run({ runId: run.runId, @@ -434,7 +434,7 @@ export function createLogStore(dbPath: string): LogStore { type: entry.type, refId: entry.refId, payload: entry.payload, - ts: entry.ts, + timestamp: entry.timestamp, }); return { ...entry, id: Number(info.lastInsertRowid) }; } @@ -456,17 +456,17 @@ export function createLogStore(dbPath: string): LogStore { params.refId = filter.refId; } if (filter.since !== undefined) { - conditions.push("ts >= @since"); + conditions.push("timestamp >= @since"); params.since = filter.since; } if (filter.until !== undefined) { - conditions.push("ts <= @until"); + conditions.push("timestamp <= @until"); params.until = filter.until; } const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const limit = filter.limit !== undefined ? `LIMIT ${filter.limit}` : ""; - const sql = `SELECT id, source, type, ref_id, payload, ts FROM logs ${where} ORDER BY id ASC ${limit}`; + const sql = `SELECT id, source, type, ref_id, payload, timestamp FROM logs ${where} ORDER BY id ASC ${limit}`; const rows = sqlite.prepare(sql).all(params) as Array<{ id: number; @@ -474,7 +474,7 @@ export function createLogStore(dbPath: string): LogStore { type: string; ref_id: string | null; payload: string | null; - ts: number; + timestamp: number; }>; return rows.map((r) => ({ @@ -483,7 +483,7 @@ export function createLogStore(dbPath: string): LogStore { type: r.type, refId: r.ref_id, payload: r.payload, - ts: r.ts, + timestamp: r.timestamp, })); } @@ -650,14 +650,14 @@ export function createLogStore(dbPath: string): LogStore { runId, before: params.before, lim: params.limit, - }) as Array<{ id: number; ts: number; payload: string | null; rn: number }>; + }) as Array<{ id: number; timestamp: number; payload: string | null; rn: number }>; const out: ThreadRoundRow[] = []; for (const row of rows) { if (row.payload === null) continue; - const message = parseRoundPayload(row.payload, row.ts); + const message = parseRoundPayload(row.payload, row.timestamp); if (message !== null) { - out.push({ round: row.rn, logId: row.id, ts: row.ts, message }); + out.push({ round: row.rn, logId: row.id, ts: row.timestamp, message }); } } return out;