Merge pull request 'refactor(daemon): rename reflex-scheduler → sense-scheduler' (#203) from refactor/rename-reflex-to-sense-scheduler into main

This commit was merged in pull request #203.
This commit is contained in:
2026-04-27 12:14:09 +00:00
16 changed files with 81 additions and 74 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ Three extension points for **what / when / multi-step action** — reflexes neve
|---------|-------------|
| [`@uncaged/nerve-core`](./packages/core) | Shared types, config parser, Sense→workflow routing, daemon IPC protocol |
| [`@uncaged/nerve-store`](./packages/store) | Append-only log SQLite, JSONL archive, CAS blob store, workflow run rows |
| [`@uncaged/nerve-daemon`](./packages/daemon) | Kernel, workers, signal bus, reflex scheduler, workflow manager, file watcher, IPC |
| [`@uncaged/nerve-daemon`](./packages/daemon) | Kernel, workers, signal bus, sense scheduler, workflow manager, file watcher, IPC |
| [`@uncaged/nerve-cli`](./packages/cli) | CLI (`nerve`) — init, validate, daemon, dev, logs, sense, store, workflow |
## Quick Start
+1 -1
View File
@@ -473,7 +473,7 @@ Sense 的运行时属性(`group`、`throttle`、`timeout`)在 `nerve.yaml`
```sql
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL, -- "reflex", "workflow", "system"
source TEXT NOT NULL, -- "sense_scheduler", "sense", "workflow", "system"
type TEXT NOT NULL, -- "run_start", "run_complete", "error", "state_change"
ref_id TEXT, -- 关联的 reflex name / workflow run_id
payload TEXT, -- JSON
+5 -5
View File
@@ -350,12 +350,12 @@ export type WorkflowManager = {
};
```
### 7.2 Reflex Scheduler 扩展
### 7.2 Sense Scheduler 扩展
当前 `ReflexScheduler` 只处理 `kind: "sense"` 的 reflex。扩展为同时处理 `kind: "workflow"`
当前 `SenseScheduler` 只处理 `kind: "sense"` 的 reflex。扩展为同时处理 `kind: "workflow"`
```typescript
// reflex-scheduler.ts 扩展
// sense-scheduler.ts 扩展
if (reflex.kind === "workflow") {
const workflowName = reflex.workflow;
if (reflex.on !== null && reflex.on.length > 0) {
@@ -393,8 +393,8 @@ if (reflex.kind === "workflow") {
### Phase 2:Kernel 集成
- [ ] `packages/daemon/src/kernel.ts` — 集成 WorkflowManager,处理 workflow worker 的生命周期
- [ ] `packages/daemon/src/reflex-scheduler.ts` — 扩展支持 `kind: "workflow"` 的 reflex
- [ ] 集成测试:Sense signal → Reflex → Workflow 全链路
- [ ] `packages/daemon/src/sense-scheduler.ts` — 扩展支持 `kind: "workflow"` 的 reflex
- [ ] 集成测试:Sense signal → SenseScheduler → Workflow 全链路
### Phase 3:崩溃恢复与热更新
+1 -1
View File
@@ -203,7 +203,7 @@ api:
expect(result.value.api).toEqual({ port: 9800, token: "secret", host: "0.0.0.0" });
});
it("accepts inline interval and on only (no reflexes key)", () => {
it("accepts per-sense interval and on only (no top-level reflexes key)", () => {
const yaml = `
senses:
cpu:
+1
View File
@@ -301,6 +301,7 @@ export function parseNerveConfig(raw: string): Result<NerveConfig> {
if (!sensesResult.ok) return sensesResult;
const { senses } = sensesResult.value;
// Legacy top-level `reflexes` is rejected; each sense carries `interval` / `on` for the sense scheduler.
if (Object.hasOwn(obj, "reflexes")) {
return err(
new Error(
+1 -1
View File
@@ -11,7 +11,7 @@ export type SenseInfo = {
group: string;
throttle: number | null;
timeout: number | null;
/** Declarative reflex lines that schedule this sense (derived from nerve.yaml). */
/** Declarative schedule (`interval` / `on`) for this sense (derived from nerve.yaml). */
triggers: string[];
lastSignalTimestamp: number | null;
};
+4 -4
View File
@@ -1,18 +1,18 @@
# @uncaged/nerve-daemon
The observation engine runtime for [nerve](../../README.md) — runs senses, routes signals, schedules reflexes, and manages workflows.
The observation engine runtime for [nerve](../../README.md) — runs senses, routes signals, runs the sense scheduler, and manages workflows.
## Architecture
| Module | Source (indicative) | Responsibility |
|--------|---------------------|----------------|
| **Kernel** | `kernel.ts` | Orchestrator — worker pool, signal bus, reflex scheduler, workflow manager, optional file watcher and daemon IPC, config reload hooks |
| **Kernel** | `kernel.ts` | Orchestrator — worker pool, signal bus, sense scheduler, workflow manager, optional file watcher and daemon IPC, config reload hooks |
| **Worker pool** | `worker-pool.ts` | Fork and supervise one child process per sense group; restart/shutdown; crash cleanup hooks for scheduler state |
| **Kernel sense groups** | `kernel-sense-groups.ts` | Derive sense groups from config; list senses per group for scheduling |
| **Sense runtime** | sense worker + Drizzle | Per-sense SQLite (`node:sqlite`), migrations, peer DB reads |
| **Sense worker** | `sense-worker.ts` (fork target) | Child process entry — runs `compute()` per sense in a group |
| **Signal bus** | `signal-bus.ts` | In-memory pub/sub for sense signals |
| **Reflex scheduler** | `reflex-scheduler.ts` | Interval + `on` subscriptions, throttle/coalesce |
| **Sense scheduler** | `sense-scheduler.ts` | Interval + `on` subscriptions, throttle/coalesce |
| **Workflow manager** | `workflow-manager.ts` | One worker per workflow name, concurrency (drop/queue), queue caps |
| **Workflow worker** | `workflow-worker.ts` | Child process — runs RFC-002 threads (`start-thread`, `resume-thread` IPC) |
| **IPC (parent ↔ workers)** | `ipc.ts` | Typed messages for sense and workflow workers (includes `resume-thread` for recovery) |
@@ -37,7 +37,7 @@ Hot reload (`drainAndRespawn`) uses a controlled drain: in-flight runs may be ma
- **One worker process per sense group** — isolation between groups, shared compute within a group
- **`node:sqlite` (DatabaseSync)** — zero native addons, WAL mode, built into Node.js ≥ 22.5
- **Throttle + coalesce** — if compute is in-flight, at most one pending trigger is queued (no unbounded accumulation)
- **Log ≠ Signal** — logs are queryable data assets but cannot trigger reflexes (prevents feedback loops)
- **Log ≠ Signal** — logs are queryable data assets but cannot trigger the sense scheduler or workflows (prevents feedback loops)
## Usage
@@ -6,10 +6,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { NerveConfig, Signal } from "@uncaged/nerve-core";
import { createLogStore } from "@uncaged/nerve-store";
import type { LogStore } from "@uncaged/nerve-store";
import { createReflexScheduler } from "../reflex-scheduler.js";
import { createSenseScheduler } from "../sense-scheduler.js";
import { createSignalBus } from "../signal-bus.js";
describe("LogStore + ReflexScheduler integration", () => {
describe("LogStore + SenseScheduler integration", () => {
let tmpDir: string;
let logStore: LogStore;
@@ -42,14 +42,14 @@ describe("LogStore + ReflexScheduler integration", () => {
};
const bus = createSignalBus();
const triggered: string[] = [];
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name), {
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name), {
logStore,
});
const signal: Signal = { id: 1, senseId: "cpu-usage", payload: 42, timestamp: Date.now() };
bus.emit(signal);
const logs = logStore.query({ source: "reflex", type: "run_start" });
const logs = logStore.query({ source: "sense_scheduler", type: "run_start" });
expect(logs).toHaveLength(1);
expect(logs[0].refId).toBe("cpu-usage");
expect(triggered).toHaveLength(1);
@@ -77,8 +77,8 @@ describe("LogStore + ReflexScheduler integration", () => {
api: { port: null, token: null, host: "127.0.0.1" },
};
const bus = createSignalBus();
const ref: { scheduler: ReturnType<typeof createReflexScheduler> | null } = { scheduler: null };
const scheduler = createReflexScheduler(
const ref: { scheduler: ReturnType<typeof createSenseScheduler> | null } = { scheduler: null };
const scheduler = createSenseScheduler(
config,
bus,
(name) => {
@@ -90,7 +90,7 @@ describe("LogStore + ReflexScheduler integration", () => {
vi.advanceTimersByTime(3000);
const logs = logStore.query({ source: "reflex", type: "run_start" });
const logs = logStore.query({ source: "sense_scheduler", type: "run_start" });
expect(logs.length).toBeGreaterThanOrEqual(3);
expect(logs.every((l) => l.refId === "cpu-usage")).toBe(true);
@@ -117,7 +117,7 @@ describe("LogStore + ReflexScheduler integration", () => {
};
const bus = createSignalBus();
const triggered: string[] = [];
const scheduler = createReflexScheduler(
const scheduler = createSenseScheduler(
config,
bus,
(name) => {
@@ -128,7 +128,7 @@ describe("LogStore + ReflexScheduler integration", () => {
);
logStore.append({
source: "reflex",
source: "sense_scheduler",
type: "run_complete",
refId: "cpu-usage",
payload: '{"v":99}',
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { NerveConfig, Signal } from "@uncaged/nerve-core";
import { createReflexScheduler } from "../reflex-scheduler.js";
import { createSenseScheduler } from "../sense-scheduler.js";
import { createSignalBus } from "../signal-bus.js";
function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
@@ -28,7 +28,7 @@ function makeSignal(senseId: string, payload: unknown = 1): Signal {
return { id: 1, senseId, payload, timestamp: Date.now() };
}
describe("ReflexScheduler — throttle + pending deferred trigger", () => {
describe("SenseScheduler — throttle + pending deferred trigger", () => {
beforeEach(() => {
vi.useFakeTimers();
});
@@ -53,7 +53,7 @@ describe("ReflexScheduler — throttle + pending deferred trigger", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
// First trigger fires immediately (outside throttle window)
bus.emit(makeSignal("cpu-usage"));
@@ -89,7 +89,7 @@ describe("ReflexScheduler — throttle + pending deferred trigger", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
// First trigger fires
bus.emit(makeSignal("cpu-usage"));
@@ -129,7 +129,7 @@ describe("ReflexScheduler — throttle + pending deferred trigger", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
bus.emit(makeSignal("cpu-usage"));
scheduler.onComputeComplete("cpu-usage");
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { NerveConfig, Signal } from "@uncaged/nerve-core";
import { createReflexScheduler } from "../reflex-scheduler.js";
import { createSenseScheduler } from "../sense-scheduler.js";
import { createSignalBus } from "../signal-bus.js";
// ---------------------------------------------------------------------------
@@ -54,7 +54,7 @@ function makeSignal(senseId: string, payload: unknown = 1): Signal {
// Tests
// ---------------------------------------------------------------------------
describe("ReflexScheduler — interval reflex", () => {
describe("SenseScheduler — interval schedule", () => {
beforeEach(() => {
vi.useFakeTimers();
});
@@ -73,10 +73,10 @@ describe("ReflexScheduler — interval reflex", () => {
});
const bus = createSignalBus();
// Use a ref so the triggerFn can call back into the scheduler
const ref: { scheduler: ReturnType<typeof createReflexScheduler> | null } = {
const ref: { scheduler: ReturnType<typeof createSenseScheduler> | null } = {
scheduler: null,
};
const scheduler = createReflexScheduler(config, bus, (name) => {
const scheduler = createSenseScheduler(config, bus, (name) => {
triggered.push(name);
// Immediately complete compute so the scheduler is not blocked by in-flight state
ref.scheduler?.onComputeComplete(name);
@@ -101,10 +101,10 @@ describe("ReflexScheduler — interval reflex", () => {
},
});
const bus = createSignalBus();
const ref: { scheduler: ReturnType<typeof createReflexScheduler> | null } = {
const ref: { scheduler: ReturnType<typeof createSenseScheduler> | null } = {
scheduler: null,
};
const scheduler = createReflexScheduler(config, bus, (name) => {
const scheduler = createSenseScheduler(config, bus, (name) => {
triggered.push(name);
ref.scheduler?.onComputeComplete(name);
});
@@ -128,7 +128,7 @@ describe("ReflexScheduler — interval reflex", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
// Only advance 500ms — should be 0 triggers (not catching up)
vi.advanceTimersByTime(500);
@@ -138,7 +138,7 @@ describe("ReflexScheduler — interval reflex", () => {
});
});
describe("ReflexScheduler — event (on) reflex", () => {
describe("SenseScheduler — event (on) schedule", () => {
it("triggers target sense when watched sense emits a signal", () => {
const triggered: string[] = [];
const base = makeConfig();
@@ -153,7 +153,7 @@ describe("ReflexScheduler — event (on) reflex", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
bus.emit(makeSignal("cpu-usage"));
scheduler.onComputeComplete("system-health");
@@ -176,7 +176,7 @@ describe("ReflexScheduler — event (on) reflex", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
bus.emit(makeSignal("disk-usage"));
@@ -195,7 +195,7 @@ describe("ReflexScheduler — event (on) reflex", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
scheduler.stop();
bus.emit(makeSignal("cpu-usage"));
@@ -204,7 +204,7 @@ describe("ReflexScheduler — event (on) reflex", () => {
});
});
describe("ReflexScheduler — throttle", () => {
describe("SenseScheduler — throttle", () => {
beforeEach(() => {
vi.useFakeTimers();
});
@@ -228,7 +228,7 @@ describe("ReflexScheduler — throttle", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
bus.emit(makeSignal("cpu-usage"));
scheduler.onComputeComplete("cpu-usage");
@@ -257,7 +257,7 @@ describe("ReflexScheduler — throttle", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
bus.emit(makeSignal("cpu-usage"));
scheduler.onComputeComplete("cpu-usage");
@@ -272,7 +272,7 @@ describe("ReflexScheduler — throttle", () => {
});
});
describe("ReflexScheduler — merge/coalesce", () => {
describe("SenseScheduler — merge/coalesce", () => {
it("concurrent triggers collapse to at most one pending run", () => {
const triggered: string[] = [];
const base = makeConfig();
@@ -283,7 +283,7 @@ describe("ReflexScheduler — merge/coalesce", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
// First trigger starts compute
bus.emit(makeSignal("cpu-usage"));
@@ -316,7 +316,7 @@ describe("ReflexScheduler — merge/coalesce", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
bus.emit(makeSignal("cpu-usage"));
expect(triggered.length).toBe(1);
@@ -329,7 +329,7 @@ describe("ReflexScheduler — merge/coalesce", () => {
});
});
describe("ReflexScheduler — interval + on combined", () => {
describe("SenseScheduler — interval + on combined", () => {
beforeEach(() => {
vi.useFakeTimers();
});
@@ -351,7 +351,7 @@ describe("ReflexScheduler — interval + on combined", () => {
},
});
const bus = createSignalBus();
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
const scheduler = createSenseScheduler(config, bus, (name) => triggered.push(name));
// Event trigger
bus.emit(makeSignal("cpu-usage"));
+6 -6
View File
@@ -1,5 +1,5 @@
/**
* Kernel — ties sense workers, signal bus, reflex scheduler, workflow manager,
* Kernel — ties sense workers, signal bus, sense scheduler, workflow manager,
* optional file watcher, and daemon IPC.
*/
@@ -34,8 +34,8 @@ import {
senseNamesInGroup,
senseNamesInGroupAsSet,
} from "./kernel-sense-groups.js";
import { createReflexScheduler } from "./reflex-scheduler.js";
import type { ReflexScheduler } from "./reflex-scheduler.js";
import { createSenseScheduler } from "./sense-scheduler.js";
import type { SenseScheduler } from "./sense-scheduler.js";
import { createSignalBus } from "./signal-bus.js";
import type { SignalBus } from "./signal-bus.js";
import { createSenseWorkerPool, resolveWorkerScript } from "./worker-pool.js";
@@ -142,7 +142,7 @@ export function createKernel(
let stopped = false;
/** Assigned before workers start; `handleWorkerMessage` only runs after this is set. */
let scheduler!: ReflexScheduler;
let scheduler!: SenseScheduler;
let readyResolve: (() => void) | undefined;
const ready = new Promise<void>((resolve) => {
@@ -247,7 +247,7 @@ export function createKernel(
senseWorkerPool.sendCompute(group, senseName);
}
scheduler = createReflexScheduler(config, bus, triggerFn, {
scheduler = createSenseScheduler(config, bus, triggerFn, {
logStore,
});
@@ -283,7 +283,7 @@ export function createKernel(
const oldWorkflows = config.workflows;
config = newConfig;
scheduler.stop();
scheduler = createReflexScheduler(config, bus, triggerFn, {
scheduler = createSenseScheduler(config, bus, triggerFn, {
logStore,
});
workflowManager.updateConfig(newConfig);
@@ -1,5 +1,5 @@
/**
* Reflex Scheduler drives sense compute cycles from each sense's `interval` / `on`.
* Sense Scheduler drives sense compute cycles from each sense's `interval` / `on`.
*
* Supports:
* - interval: periodic setInterval-based triggering
@@ -24,8 +24,8 @@ type SenseState = {
deferredTimer: ReturnType<typeof setTimeout> | null;
};
/** Handle returned by createReflexScheduler — call stop() for graceful shutdown. */
export type ReflexScheduler = {
/** Handle returned by createSenseScheduler — call stop() for graceful shutdown. */
export type SenseScheduler = {
/** Notify scheduler that a compute cycle finished. Drains the pending flag. */
onComputeComplete: (senseName: string) => void;
stop: () => void;
@@ -35,25 +35,25 @@ function makeSenseState(): SenseState {
return { lastComputeAt: 0, inFlight: false, pending: false, deferredTimer: null };
}
export type ReflexSchedulerOptions = {
export type SenseSchedulerOptions = {
logStore?: LogStore;
};
/**
* Create and start a reflex scheduler.
* Create and start a sense scheduler.
*
* @param config Full NerveConfig (senses for schedule + throttle).
* @param bus SignalBus to subscribe for event-driven triggers.
* @param triggerFn Called with the sense name when a compute should be dispatched.
* @param opts Optional: logStore for structured logging.
* @returns ReflexScheduler with stop() and onComputeComplete() methods.
* @returns SenseScheduler with stop() and onComputeComplete() methods.
*/
export function createReflexScheduler(
export function createSenseScheduler(
config: NerveConfig,
bus: SignalBus,
triggerFn: TriggerFn,
opts?: ReflexSchedulerOptions,
): ReflexScheduler {
opts?: SenseSchedulerOptions,
): SenseScheduler {
const logStore = opts?.logStore;
const intervals: ReturnType<typeof setInterval>[] = [];
const unsubscribers: Unsubscribe[] = [];
@@ -74,7 +74,7 @@ export function createReflexScheduler(
state.pending = false;
state.lastComputeAt = Date.now();
logStore?.append({
source: "reflex",
source: "sense_scheduler",
type: "run_start",
refId: senseName,
payload: null,
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* In-memory signal bus for routing signals between sense workers and reflex subscribers.
* In-memory signal bus for routing signals between sense workers and sense-scheduler subscribers.
* Synchronous dispatch — no persistence, no async queuing.
*
* If a handler throws, the error is logged and remaining handlers still run.
@@ -31,7 +31,13 @@ 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}', timestamp: ts });
store.append({ source: "reflex", type: "y", refId: "z", payload: null, timestamp: ts + 1 });
store.append({
source: "sense_scheduler",
type: "y",
refId: "z",
payload: null,
timestamp: ts + 1,
});
const now = nowForLastArchivableFeb1();
const result = store.archiveLogs({ now, retentionMs: 30 * DAY_MS });
@@ -63,14 +63,14 @@ describe("LogStore", () => {
timestamp: 1000,
});
store.append({
source: "reflex",
source: "sense_scheduler",
type: "run_start",
refId: "cpu",
payload: null,
timestamp: 2000,
});
store.append({
source: "reflex",
source: "sense_scheduler",
type: "run_complete",
refId: "cpu",
payload: '{"v":42}',
@@ -92,14 +92,14 @@ describe("LogStore", () => {
timestamp: 1000,
});
store.append({
source: "reflex",
source: "sense_scheduler",
type: "run_start",
refId: "cpu",
payload: null,
timestamp: 2000,
});
store.append({
source: "reflex",
source: "sense_scheduler",
type: "run_complete",
refId: "cpu",
payload: '{"v":42}',
@@ -116,9 +116,9 @@ describe("LogStore", () => {
});
it("filters by source", () => {
const results = store.query({ source: "reflex" });
const results = store.query({ source: "sense_scheduler" });
expect(results).toHaveLength(2);
expect(results.every((r) => r.source === "reflex")).toBe(true);
expect(results.every((r) => r.source === "sense_scheduler")).toBe(true);
});
it("filters by type", () => {
@@ -178,7 +178,7 @@ describe("LogStore", () => {
timestamp: 5000,
});
store.append({
source: "reflex",
source: "sense_scheduler",
type: "run_start",
refId: "a",
payload: null,
@@ -231,7 +231,7 @@ describe("LogStore", () => {
it("preserves JSON payload", () => {
const payload = JSON.stringify({ cpu: 95, host: "node-1" });
store.append({
source: "reflex",
source: "sense_scheduler",
type: "run_complete",
refId: "cpu",
payload,
+2 -2
View File
@@ -1,8 +1,8 @@
/**
* Log Store — append-only structured log storage backed by SQLite.
*
* Stores system, reflex, and workflow log entries in a single table.
* Logs are data assets for audit/analysis — they MUST NOT trigger reflexes.
* Stores system, sense-scheduler (`sense_scheduler` source), sense, and workflow log entries in a single table.
* Logs are data assets for audit/analysis — they MUST NOT feed back into scheduling or workflows as triggers.
*
* Also provides a `meta` key-value table for bookkeeping (e.g. archive watermarks).
*/