refactor(core): remove unnecessary | null, unify timestamp naming
- SenseReflexConfig.on: string[] | null → string[] (empty = no conditions)
- NerveConfig.workflows: Record | null → Record (empty = no workflows)
- Signal.ts → Signal.timestamp
- SenseInfo.lastSignalTs → SenseInfo.lastSignalTimestamp
- All consumers across daemon/cli/store updated
- parseNerveConfig: on defaults to [], workflows defaults to {}
Fixes #108
This commit is contained in:
@@ -29,10 +29,22 @@ const SAMPLE_SENSES: SenseInfo[] = [
|
||||
group: "system",
|
||||
throttle: 5000,
|
||||
timeout: 3000,
|
||||
lastSignalTs: 1_700_000_000_000,
|
||||
lastSignalTimestamp: 1_700_000_000_000,
|
||||
},
|
||||
{
|
||||
name: "disk-usage",
|
||||
group: "system",
|
||||
throttle: 30000,
|
||||
timeout: null,
|
||||
lastSignalTimestamp: null,
|
||||
},
|
||||
{
|
||||
name: "active-tasks",
|
||||
group: "tasks",
|
||||
throttle: 10000,
|
||||
timeout: 30000,
|
||||
lastSignalTimestamp: null,
|
||||
},
|
||||
{ name: "disk-usage", group: "system", throttle: 30000, timeout: null, lastSignalTs: null },
|
||||
{ name: "active-tasks", group: "tasks", throttle: 10000, timeout: 30000, lastSignalTs: null },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -100,14 +112,14 @@ describe("formatSenseList", () => {
|
||||
expect(output).toContain("—");
|
||||
});
|
||||
|
||||
it("shows '(never)' when lastSignalTs is null", () => {
|
||||
it("shows '(never)' when lastSignalTimestamp is null", () => {
|
||||
const output = formatSenseList(SAMPLE_SENSES);
|
||||
expect(output).toContain("(never)");
|
||||
});
|
||||
|
||||
it("shows ISO timestamp when lastSignalTs is set", () => {
|
||||
it("shows ISO timestamp when lastSignalTimestamp is set", () => {
|
||||
const output = formatSenseList(SAMPLE_SENSES);
|
||||
// cpu-usage has lastSignalTs = 1_700_000_000_000
|
||||
// cpu-usage has lastSignalTimestamp = 1_700_000_000_000
|
||||
expect(output).toContain(new Date(1_700_000_000_000).toISOString());
|
||||
});
|
||||
});
|
||||
@@ -157,11 +169,19 @@ reflexes: []
|
||||
);
|
||||
const result = sensesFromConfig(path);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toMatchObject({ name: "cpu-usage", group: "system", lastSignalTs: null });
|
||||
expect(result[1]).toMatchObject({ name: "disk-usage", group: "system", lastSignalTs: null });
|
||||
expect(result[0]).toMatchObject({
|
||||
name: "cpu-usage",
|
||||
group: "system",
|
||||
lastSignalTimestamp: null,
|
||||
});
|
||||
expect(result[1]).toMatchObject({
|
||||
name: "disk-usage",
|
||||
group: "system",
|
||||
lastSignalTimestamp: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("always sets lastSignalTs to null (static fallback)", () => {
|
||||
it("always sets lastSignalTimestamp to null (static fallback)", () => {
|
||||
const path = join(tmpDir, "nerve.yaml");
|
||||
writeFileSync(
|
||||
path,
|
||||
@@ -173,7 +193,7 @@ reflexes: []
|
||||
`.trim(),
|
||||
);
|
||||
const result = sensesFromConfig(path);
|
||||
expect(result[0].lastSignalTs).toBeNull();
|
||||
expect(result[0].lastSignalTimestamp).toBeNull();
|
||||
});
|
||||
|
||||
it("populates throttle and timeout from config", () => {
|
||||
@@ -238,7 +258,13 @@ describe("listSensesViaDaemon", () => {
|
||||
|
||||
it("resolves with populated senses array", async () => {
|
||||
const senses: SenseInfo[] = [
|
||||
{ name: "cpu-usage", group: "system", throttle: 5000, timeout: 3000, lastSignalTs: 12345 },
|
||||
{
|
||||
name: "cpu-usage",
|
||||
group: "system",
|
||||
throttle: 5000,
|
||||
timeout: 3000,
|
||||
lastSignalTimestamp: 12345,
|
||||
},
|
||||
];
|
||||
const server = createServer((s) => {
|
||||
s.on("data", () => {
|
||||
|
||||
@@ -43,7 +43,8 @@ export function formatSenseList(senses: SenseInfo[]): string {
|
||||
lines.push(` group: ${s.group}\n`);
|
||||
lines.push(` throttle: ${formatDuration(s.throttle)}\n`);
|
||||
lines.push(` timeout: ${formatDuration(s.timeout)}\n`);
|
||||
const lastSignal = s.lastSignalTs !== null ? new Date(s.lastSignalTs).toISOString() : "(never)";
|
||||
const lastSignal =
|
||||
s.lastSignalTimestamp !== null ? new Date(s.lastSignalTimestamp).toISOString() : "(never)";
|
||||
lines.push(` last signal: ${lastSignal}\n`);
|
||||
}
|
||||
return lines.join("");
|
||||
@@ -64,7 +65,7 @@ export function sensesFromConfig(configPath: string): SenseInfo[] {
|
||||
group: cfg.group,
|
||||
throttle: cfg.throttle,
|
||||
timeout: cfg.timeout,
|
||||
lastSignalTs: null,
|
||||
lastSignalTimestamp: null,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export const validateCommand = defineCommand({
|
||||
const config = result.value;
|
||||
const senseCount = Object.keys(config.senses).length;
|
||||
const reflexCount = config.reflexes.length;
|
||||
const workflowCount = config.workflows ? Object.keys(config.workflows).length : 0;
|
||||
const workflowCount = Object.keys(config.workflows).length;
|
||||
|
||||
process.stdout.write(
|
||||
`✅ nerve.yaml is valid — ${senseCount} sense(s), ${reflexCount} reflex(es), ${workflowCount} workflow(s)\n`,
|
||||
|
||||
@@ -28,7 +28,7 @@ function isSenseInfo(value: unknown): value is SenseInfo {
|
||||
typeof value.group === "string" &&
|
||||
(value.throttle === null || typeof value.throttle === "number") &&
|
||||
(value.timeout === null || typeof value.timeout === "number") &&
|
||||
(value.lastSignalTs === null || typeof value.lastSignalTs === "number")
|
||||
(value.lastSignalTimestamp === null || typeof value.lastSignalTimestamp === "number")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("parseNerveConfig", () => {
|
||||
kind: "sense",
|
||||
sense: "cpu",
|
||||
interval: 30_000,
|
||||
on: null,
|
||||
on: [],
|
||||
});
|
||||
expect(result.value.reflexes[1]).toEqual({
|
||||
kind: "sense",
|
||||
@@ -58,7 +58,7 @@ describe("parseNerveConfig", () => {
|
||||
interval: null,
|
||||
on: ["high_usage"],
|
||||
});
|
||||
expect(result.value.workflows?.alert).toEqual({
|
||||
expect(result.value.workflows.alert).toEqual({
|
||||
concurrency: 2,
|
||||
overflow: "queue",
|
||||
maxQueue: 10,
|
||||
@@ -85,11 +85,12 @@ senses:
|
||||
group: system
|
||||
reflexes:
|
||||
- sense: cpu
|
||||
interval: 1s
|
||||
`;
|
||||
const result = parseNerveConfig(yaml);
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.value.workflows).toBeNull();
|
||||
expect(result.value.workflows).toEqual({});
|
||||
});
|
||||
|
||||
it("sense config has null for omitted throttle/timeout/gracePeriod", () => {
|
||||
@@ -142,11 +143,11 @@ workflows:
|
||||
const result = parseNerveConfig(yaml);
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.value.workflows?.alert).toEqual({
|
||||
expect(result.value.workflows.alert).toEqual({
|
||||
concurrency: 1,
|
||||
overflow: "drop",
|
||||
});
|
||||
expect("maxQueue" in (result.value.workflows?.alert ?? {})).toBe(false);
|
||||
expect("maxQueue" in result.value.workflows.alert).toBe(false);
|
||||
});
|
||||
|
||||
it("overflow: queue defaults maxQueue to 100", () => {
|
||||
@@ -163,7 +164,7 @@ workflows:
|
||||
const result = parseNerveConfig(yaml);
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.value.workflows?.alert).toEqual({
|
||||
expect(result.value.workflows.alert).toEqual({
|
||||
concurrency: 1,
|
||||
overflow: "queue",
|
||||
maxQueue: 100,
|
||||
|
||||
@@ -76,8 +76,8 @@ function validateSenseConfig(name: string, raw: unknown): Result<SenseConfig> {
|
||||
});
|
||||
}
|
||||
|
||||
function parseOnField(index: number, obj: Record<string, unknown>): Result<string[] | null> {
|
||||
if (obj.on === undefined || obj.on === null) return ok(null);
|
||||
function parseOnField(index: number, obj: Record<string, unknown>): Result<string[]> {
|
||||
if (obj.on === undefined || obj.on === null) return ok([]);
|
||||
if (!Array.isArray(obj.on) || !obj.on.every((item): item is string => typeof item === "string")) {
|
||||
return err(new Error(`reflexes[${index}].on: must be an array of strings`));
|
||||
}
|
||||
@@ -88,7 +88,7 @@ function parseSenseReflex(
|
||||
index: number,
|
||||
obj: Record<string, unknown>,
|
||||
senseNames: Set<string>,
|
||||
on: string[] | null,
|
||||
on: string[],
|
||||
): Result<ReflexConfig> {
|
||||
if (typeof obj.sense !== "string") {
|
||||
return err(new Error(`reflexes[${index}].sense: must be a string`));
|
||||
@@ -100,7 +100,7 @@ function parseSenseReflex(
|
||||
const intervalResult = parseDurationField(obj.interval, `reflexes[${index}].interval`);
|
||||
if (!intervalResult.ok) return intervalResult;
|
||||
|
||||
if (intervalResult.value === null && on !== null && on.length === 0) {
|
||||
if (intervalResult.value === null && on.length === 0) {
|
||||
return err(
|
||||
new Error(`reflexes[${index}]: sense reflex must have at least one of "interval" or "on"`),
|
||||
);
|
||||
@@ -245,10 +245,8 @@ function parseReflexes(
|
||||
return ok(reflexes);
|
||||
}
|
||||
|
||||
function parseWorkflows(
|
||||
obj: Record<string, unknown>,
|
||||
): Result<Record<string, WorkflowConfig> | null> {
|
||||
if (obj.workflows === undefined || obj.workflows === null) return ok(null);
|
||||
function parseWorkflows(obj: Record<string, unknown>): Result<Record<string, WorkflowConfig>> {
|
||||
if (obj.workflows === undefined || obj.workflows === null) return ok({});
|
||||
|
||||
if (!isPlainRecord(obj.workflows)) {
|
||||
return err(new Error("workflows: must be an object if provided"));
|
||||
|
||||
@@ -2,7 +2,7 @@ export type Signal = {
|
||||
id: number;
|
||||
senseId: string;
|
||||
payload: unknown;
|
||||
ts: number;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
export type SenseConfig = {
|
||||
@@ -18,14 +18,14 @@ export type SenseInfo = {
|
||||
group: string;
|
||||
throttle: number | null;
|
||||
timeout: number | null;
|
||||
lastSignalTs: number | null;
|
||||
lastSignalTimestamp: number | null;
|
||||
};
|
||||
|
||||
export type SenseReflexConfig = {
|
||||
kind: "sense";
|
||||
sense: string;
|
||||
interval: number | null;
|
||||
on: string[] | null;
|
||||
on: string[];
|
||||
};
|
||||
|
||||
/** Reflexes only schedule Senses; workflow launches come from Sense return values. */
|
||||
@@ -49,7 +49,7 @@ export type NerveConfig = {
|
||||
maxRounds: number;
|
||||
senses: Record<string, SenseConfig>;
|
||||
reflexes: ReflexConfig[];
|
||||
workflows: Record<string, WorkflowConfig> | null;
|
||||
workflows: Record<string, WorkflowConfig>;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -200,8 +200,20 @@ describe("daemon-ipc — list-senses", () => {
|
||||
|
||||
it("responds ok:true with senses populated from listSenses", async () => {
|
||||
const sensesData = [
|
||||
{ name: "cpu-usage", group: "system", throttle: 5000, timeout: 3000, lastSignalTs: 1000 },
|
||||
{ name: "disk-usage", group: "system", throttle: 30000, timeout: null, lastSignalTs: null },
|
||||
{
|
||||
name: "cpu-usage",
|
||||
group: "system",
|
||||
throttle: 5000,
|
||||
timeout: 3000,
|
||||
lastSignalTimestamp: 1000,
|
||||
},
|
||||
{
|
||||
name: "disk-usage",
|
||||
group: "system",
|
||||
throttle: 30000,
|
||||
timeout: null,
|
||||
lastSignalTimestamp: null,
|
||||
},
|
||||
];
|
||||
const listSenses = vi.fn(() => sensesData);
|
||||
server = createDaemonIpcServer(sockPath, makeMockWorkflowManager() as never, {
|
||||
|
||||
@@ -250,7 +250,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
|
||||
const logStore = makeLogStore();
|
||||
const config: NerveConfig = {
|
||||
senses: {},
|
||||
reflexes: [{ kind: "workflow", workflow: "my-wf", on: null } as any],
|
||||
reflexes: [],
|
||||
workflows: { "my-wf": { concurrency: 1, overflow: "drop" } },
|
||||
maxRounds: 10,
|
||||
};
|
||||
@@ -285,7 +285,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
|
||||
const logStore = makeLogStore();
|
||||
const initialConfig: NerveConfig = {
|
||||
senses: {},
|
||||
reflexes: [{ kind: "workflow", workflow: "old-wf", on: null } as any],
|
||||
reflexes: [],
|
||||
workflows: { "old-wf": { concurrency: 1, overflow: "drop" } },
|
||||
maxRounds: 10,
|
||||
};
|
||||
@@ -307,7 +307,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
|
||||
const newConfig: NerveConfig = {
|
||||
senses: {},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
kernel.reloadConfig(newConfig);
|
||||
@@ -328,7 +328,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
|
||||
const logStore = makeLogStore();
|
||||
const initialConfig: NerveConfig = {
|
||||
senses: {},
|
||||
reflexes: [{ kind: "workflow", workflow: "my-wf", on: null } as any],
|
||||
reflexes: [],
|
||||
workflows: { "my-wf": { concurrency: 1, overflow: "drop" } },
|
||||
maxRounds: 10,
|
||||
};
|
||||
@@ -344,7 +344,7 @@ describe("Kernel — workflow hot reload via file-watcher (Phase 3)", () => {
|
||||
// Reload with updated concurrency — should NOT spawn a new workflow worker
|
||||
const newConfig: NerveConfig = {
|
||||
senses: {},
|
||||
reflexes: [{ kind: "workflow", workflow: "my-wf", on: null } as any],
|
||||
reflexes: [],
|
||||
workflows: { "my-wf": { concurrency: 5, overflow: "queue", maxQueue: 50 } },
|
||||
maxRounds: 10,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
@@ -73,7 +73,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
@@ -180,7 +180,7 @@ describe("kernel — reloadConfig", () => {
|
||||
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
});
|
||||
|
||||
@@ -197,7 +197,7 @@ describe("kernel — reloadConfig", () => {
|
||||
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
const kernel = createKernel(config, "/tmp/nerve-test");
|
||||
@@ -212,7 +212,7 @@ describe("kernel — reloadConfig", () => {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
});
|
||||
|
||||
@@ -235,7 +235,7 @@ describe("kernel — reloadConfig", () => {
|
||||
"disk-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
});
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
@@ -298,7 +298,7 @@ describe("kernel + workflowManager integration", () => {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
});
|
||||
|
||||
@@ -366,7 +366,7 @@ describe("kernel + workflowManager integration", () => {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
kernel.reloadConfig(newConfig);
|
||||
|
||||
@@ -59,7 +59,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
@@ -200,7 +200,7 @@ describe("kernel — groupForSense mapping", () => {
|
||||
"net-usage": { group: "network", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
const kernel = createKernel(config, "/tmp/nerve-test");
|
||||
@@ -215,7 +215,7 @@ describe("kernel — groupForSense mapping", () => {
|
||||
senses: {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: null }],
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: [] }],
|
||||
});
|
||||
createKernel(config, "/tmp/nerve-test");
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("LogStore + ReflexScheduler integration", () => {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: null, on: ["cpu-usage"] }],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
const bus = createSignalBus();
|
||||
@@ -38,7 +38,7 @@ describe("LogStore + ReflexScheduler integration", () => {
|
||||
logStore,
|
||||
});
|
||||
|
||||
const signal: Signal = { id: 1, senseId: "cpu-usage", payload: 42, ts: Date.now() };
|
||||
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" });
|
||||
@@ -56,8 +56,8 @@ describe("LogStore + ReflexScheduler integration", () => {
|
||||
senses: {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: null }],
|
||||
workflows: null,
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: [] }],
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
const bus = createSignalBus();
|
||||
@@ -88,7 +88,7 @@ describe("LogStore + ReflexScheduler integration", () => {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: null, on: ["cpu-usage"] }],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
const bus = createSignalBus();
|
||||
|
||||
@@ -23,7 +23,7 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
@@ -136,7 +136,7 @@ describe("phase6 — reloadConfig", () => {
|
||||
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
|
||||
@@ -156,7 +156,7 @@ describe("phase6 — reloadConfig", () => {
|
||||
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
kernel = createKernel(config, "/tmp/nerve-phase6-test", {
|
||||
@@ -171,7 +171,7 @@ describe("phase6 — reloadConfig", () => {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
|
||||
@@ -202,7 +202,7 @@ describe("phase6 — error isolation", () => {
|
||||
"bad-sense": { group: "mixed", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
|
||||
@@ -306,7 +306,7 @@ describe("phase6 — getHealth", () => {
|
||||
"net-rx": { group: "network", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
};
|
||||
kernel.reloadConfig(newConfig);
|
||||
|
||||
@@ -10,14 +10,14 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"cpu-usage": { group: "system", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeSignal(senseId: string, payload: unknown = 1): Signal {
|
||||
return { id: 1, senseId, payload, ts: Date.now() };
|
||||
return { id: 1, senseId, payload, timestamp: Date.now() };
|
||||
}
|
||||
|
||||
describe("ReflexScheduler — throttle + pending deferred trigger", () => {
|
||||
|
||||
@@ -16,14 +16,14 @@ function makeConfig(overrides: Partial<NerveConfig> = {}): NerveConfig {
|
||||
"system-health": { group: "derived", throttle: null, timeout: null, gracePeriod: null },
|
||||
},
|
||||
reflexes: [],
|
||||
workflows: null,
|
||||
workflows: {},
|
||||
maxRounds: 10,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeSignal(senseId: string, payload: unknown = 1): Signal {
|
||||
return { id: 1, senseId, payload, ts: Date.now() };
|
||||
return { id: 1, senseId, payload, timestamp: Date.now() };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -41,7 +41,7 @@ describe("ReflexScheduler — interval reflex", () => {
|
||||
it("fires triggerFn on schedule", () => {
|
||||
const triggered: string[] = [];
|
||||
const config = makeConfig({
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: null }],
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: [] }],
|
||||
});
|
||||
const bus = createSignalBus();
|
||||
// Use a ref so the triggerFn can call back into the scheduler
|
||||
@@ -66,7 +66,7 @@ describe("ReflexScheduler — interval reflex", () => {
|
||||
it("stops firing after stop() is called", () => {
|
||||
const triggered: string[] = [];
|
||||
const config = makeConfig({
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: null }],
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 500, on: [] }],
|
||||
});
|
||||
const bus = createSignalBus();
|
||||
const ref: { scheduler: ReturnType<typeof createReflexScheduler> | null } = {
|
||||
@@ -89,7 +89,7 @@ describe("ReflexScheduler — interval reflex", () => {
|
||||
it("starts from current time — does not compensate for past intervals", () => {
|
||||
const triggered: string[] = [];
|
||||
const config = makeConfig({
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: null }],
|
||||
reflexes: [{ kind: "sense", sense: "cpu-usage", interval: 1000, on: [] }],
|
||||
});
|
||||
const bus = createSignalBus();
|
||||
const scheduler = createReflexScheduler(config, bus, (name) => triggered.push(name));
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Signal } from "@uncaged/nerve-core";
|
||||
import { createSignalBus } from "../signal-bus.js";
|
||||
|
||||
function makeSignal(senseId: string, payload: unknown = 1): Signal {
|
||||
return { id: 1, senseId, payload, ts: Date.now() };
|
||||
return { id: 1, senseId, payload, timestamp: Date.now() };
|
||||
}
|
||||
|
||||
describe("createSignalBus", () => {
|
||||
|
||||
@@ -161,14 +161,14 @@ export function createKernel(
|
||||
id: nextSignalId(),
|
||||
senseId: msg.sense,
|
||||
payload: route.payload,
|
||||
ts: Date.now(),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
logStore.append({
|
||||
source: "sense",
|
||||
type: "signal",
|
||||
refId: msg.sense,
|
||||
payload: JSON.stringify(route.payload),
|
||||
ts: signal.ts,
|
||||
ts: signal.timestamp,
|
||||
});
|
||||
bus.emit(signal);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ export function createKernel(
|
||||
function reloadConfig(newConfig: NerveConfig): void {
|
||||
const oldGroups = collectSenseGroups(config);
|
||||
const oldConfig = config;
|
||||
const oldWorkflows = config.workflows ?? {};
|
||||
const oldWorkflows = config.workflows;
|
||||
config = newConfig;
|
||||
scheduler.stop();
|
||||
scheduler = createReflexScheduler(config, bus, triggerFn, {
|
||||
@@ -247,7 +247,7 @@ export function createKernel(
|
||||
});
|
||||
workflowManager.updateConfig(newConfig);
|
||||
|
||||
const newWorkflows = newConfig.workflows ?? {};
|
||||
const newWorkflows = newConfig.workflows;
|
||||
|
||||
for (const workflowName of Object.keys(oldWorkflows)) {
|
||||
if (!(workflowName in newWorkflows)) {
|
||||
@@ -327,7 +327,7 @@ export function createKernel(
|
||||
group: senseConfig.group,
|
||||
throttle: senseConfig.throttle,
|
||||
timeout: senseConfig.timeout,
|
||||
lastSignalTs: lastEntry !== null ? lastEntry.ts : null,
|
||||
lastSignalTimestamp: lastEntry !== null ? lastEntry.ts : null,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -164,7 +164,7 @@ export function createReflexScheduler(
|
||||
intervals.push(id);
|
||||
}
|
||||
|
||||
if (senseReflex.on !== null && senseReflex.on.length > 0) {
|
||||
if (senseReflex.on.length > 0) {
|
||||
const watchedSenses = new Set(senseReflex.on);
|
||||
const unsub = bus.subscribe((signal) => {
|
||||
if (watchedSenses.has(signal.senseId)) {
|
||||
|
||||
@@ -217,7 +217,7 @@ export function createWorkflowManager(
|
||||
}
|
||||
|
||||
function workflowConfig(workflowName: string): WorkflowConfig | null {
|
||||
return config.workflows?.[workflowName] ?? null;
|
||||
return config.workflows[workflowName] ?? null;
|
||||
}
|
||||
|
||||
function toWorkflowRunStatus(eventType: string): WorkflowRunStatus | null {
|
||||
|
||||
Reference in New Issue
Block a user