Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/lib/machineToken.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { createHash, createHmac } from "node:crypto";
import { createLogger } from "@/shared/utils/logger";

const log = createLogger("auth:machine-token");
let fallbackLogged = false;

let machineIdSync: (original?: boolean) => string;
try {
// Use require() to bypass webpack static analysis that breaks the default export
const mod = require("node-machine-id");
machineIdSync = mod.machineIdSync || mod.default?.machineIdSync;
} catch {
} catch (err) {
if (!fallbackLogged) {
fallbackLogged = true;
log.error(
{ err },
"machineToken: node-machine-id unavailable — HMAC salt falls back to empty string (security-relevant, all tokens become constant-keyed)"
);
}
machineIdSync = () => "";
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/resilience/anomalyHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* telemetry failures
*/

import { isFeatureFlagEnabled } from "@/lib/featureFlags";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
import { resolveSelfHealingSettings } from "./selfHealingSettings";
import { SelfHealingManager } from "./selfHealingManager";
import { createAnomalyDetector } from "./anomalyDetector";
Expand Down
11 changes: 9 additions & 2 deletions src/lib/versionManager/processManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import fsSync from "fs";
import path from "path";
import os from "os";
import { setToolStatus, getVersionManagerTool } from "@/lib/db/versionManager";
import { createLogger } from "@/shared/utils/logger";

const log = createLogger("version-manager:process-manager");

const DEFAULT_PORT = 8317;
const GRACEFUL_TIMEOUT_MS = 5000;
Expand Down Expand Up @@ -149,7 +152,11 @@ export async function getProcessInfo(pid: number): Promise<{
}
}
return { pid, alive: true };
} catch {
return { pid, alive: true };
} catch (err) {
log.error(
{ err, pid, platform: process.platform },
"processManager.getProcessInfo: failed to read process info"
);
return { pid, alive: false };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: alive: false conflates "dead" with "unreadable"

The catch now returns { pid, alive: false } on any read failure. This is correct for the documented race-condition case (process died between isProcessRunning and the /proc/ps read), but it also maps transient failures (e.g. permissions, platform quirks) to alive: false. If future callers use getProcessInfo to drive restart decisions, a temporarily unreadable alive process could be restarted unnecessarily. Consider adding an unknown state or a separate readable flag so callers can distinguish "confirmed dead" from "state unreadable".


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}
}
4 changes: 2 additions & 2 deletions src/server-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ async function startServer() {
// first request after restart. Gated on the feature flag so the
// default-off behaviour is unchanged.
try {
const { isFeatureFlagEnabled } = await import("./lib/featureFlags");
const { isFeatureFlagEnabled } = await import("./shared/utils/featureFlags");
const { getSelfHealingManager } = await import("./lib/resilience/anomalyHook");
if (isFeatureFlagEnabled("OMNIROUTE_SELF_HEALING_ENABLED")) {
const mgr = getSelfHealingManager();
Expand All @@ -135,7 +135,7 @@ async function startServer() {
void mgr; // referenced to ensure the singleton is constructed
}
} catch (err) {
startupLog.warn({ err }, "Self-healing hydration skipped (non-fatal)");
startupLog.error({ err }, "Self-healing hydration skipped (non-fatal)");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Log severity says "error" but message says "non-fatal"

The startupLog.error call carries the text "Self-healing hydration skipped (non-fatal)". If your monitoring/alerting treats error as actionable, this will page on-call for a condition that explicitly does not require human intervention. Either downgrade to warn to match the message, or update the message to reflect that this is treated as an error-level event.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}

startupLog.info("Server started with cloud sync initialized");
Expand Down
10 changes: 9 additions & 1 deletion src/server/ws/liveServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { WebSocketServer, WebSocket } from "ws";
import { jwtVerify } from "jose";
import { createServer, type IncomingMessage, type ServerResponse } from "http";
import { randomUUID } from "crypto";
import { createLogger } from "@/shared/utils/logger";

const log = createLogger("ws:live-server");

// ── Types ─────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -476,7 +479,12 @@ export async function startLiveDashboardServer(
// does not block the event loop on a cold import — which would starve concurrent
// WebSocket handshakes (see loadAuthModule). A failed warm is non-fatal: the
// handler retries the import lazily.
await loadAuthModule().catch(() => {});
await loadAuthModule().catch((err) =>
log.error(
{ err },
"liveServer: failed to warm auth module — clients may experience cold-import latency"
)
);
Comment on lines +482 to +487

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The warm-up catches the rejected dynamic import, but loadAuthModule memoizes that rejected promise in authModulePromise. Consequently, every later API-key authorization receives the same rejection and returns Auth system unavailable; the documented lazy retry never occurs after a transient startup failure. Clear the cached promise when the import rejects, or avoid caching rejected imports. [logic error]

Severity Level: Major ⚠️
- ❌ API-key WebSocket clients remain unauthorized after transient startup recovery.
- ⚠️ Live dashboard authentication requires process restart.
- ⚠️ Cookie-authenticated clients may continue while API-key clients fail.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/server/ws/liveServer.ts
**Line:** 482:487
**Comment:**
	*Logic Error: The warm-up catches the rejected dynamic import, but `loadAuthModule` memoizes that rejected promise in `authModulePromise`. Consequently, every later API-key authorization receives the same rejection and returns `Auth system unavailable`; the documented lazy retry never occurs after a transient startup failure. Clear the cached promise when the import rejects, or avoid caching rejected imports.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


wss.on("connection", async (ws, request) => {
const pendingMessages: string[] = [];
Expand Down
88 changes: 88 additions & 0 deletions tests/unit/quota/keyvQuotaStoreExtras.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// @vitest-environment node
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import {
getKeyvQuotaStore,
__resetKeyvQuotaStoreForTests,
} from "../../../src/lib/quota/keyvQuotaStore";
import type { ProviderPlan, QuotaPool } from "../../../src/lib/quota/dimensions";

/**
* Sanity tests for the "extras" surface of KeyvQuotaStore:
* recordPlanUsage, upsertProviderPlan, listProviderPlans, setPools, getPool.
*
* These methods were originally scoped for a KeyvQuotaStoreExtras class per
* plans/quota-keystore-type-drift-spec.md §8.2, but were folded directly into
* KeyvQuotaStore before the spec landed. We exercise them here against the
* in-memory backing to guarantee the surface stays wired correctly.
*/
describe("KeyvQuotaStoreExtras", () => {
let store: ReturnType<typeof getKeyvQuotaStore>;

beforeEach(() => {
__resetKeyvQuotaStoreForTests();
store = getKeyvQuotaStore({ uri: "memory://" });
});

afterEach(async () => {
await store.dispose();
__resetKeyvQuotaStoreForTests();
});

it("recordPlanUsage returns a PlanPoolUsage shape with totalConsumed and lastUpdatedAt populated", async () => {
const rollup = await store.recordPlanUsage(
"conn-1",
"openai",
"pool-1",
[{ unit: "tokens", window: "hourly" }],
42,
);
expect(rollup).toBeDefined();
expect(rollup.totalConsumed).toBe(42);
expect(typeof rollup.lastUpdatedAt).toBe("number");
expect(rollup.lastUpdatedAt).toBeGreaterThan(0);
});

it("upsertProviderPlan writes the plan without throwing", async () => {
const plan: ProviderPlan = {
connectionId: "conn-1",
provider: "openai",
dimensions: [{ unit: "tokens", window: "hourly", limit: 1000 }],
source: "manual",
};
await expect(store.upsertProviderPlan(plan)).resolves.toBeUndefined();
});

it("listProviderPlans returns [] even after an upsert (independent surface)", async () => {
// upsertProviderPlan and listProviderPlans are independent: the in-memory
// store does not enumerate provider plans, so the list stays empty.
await store.upsertProviderPlan({
connectionId: "conn-1",
provider: "openai",
dimensions: [{ unit: "tokens", window: "hourly", limit: 1000 }],
source: "manual",
});
const plans = await store.listProviderPlans();
expect(plans).toEqual([]);
});

it("setPools persists a QuotaPool retrievable via getPool", async () => {
const pool: QuotaPool = {
id: "pool-1",
connectionId: "conn-1",
name: "Primary",
createdAt: new Date().toISOString(),
allocations: [],
};
await store.setPools([pool]);
const retrieved = await store.getPool("pool-1");
expect(retrieved).toBeDefined();
expect(retrieved?.id).toBe("pool-1");
expect(retrieved?.name).toBe("Primary");
expect(retrieved?.allocations).toEqual([]);
});

it("getPool returns undefined for an unknown poolId", async () => {
const result = await store.getPool("nonexistent-pool");
expect(result).toBeUndefined();
});
});
Loading