diff --git a/src/daemon/runtime/codebase/query.ts b/src/daemon/runtime/codebase/query.ts index 92e6569d..8db855e3 100644 --- a/src/daemon/runtime/codebase/query.ts +++ b/src/daemon/runtime/codebase/query.ts @@ -192,7 +192,21 @@ function parsePath(path: string): { command: string; remainder: string } { const cleaned = path.replace(/^\/+/, "").replace(/^graph\//, "").replace(/\.md$/, ""); const slash = cleaned.indexOf("/"); if (slash < 0) return { command: cleaned.toLowerCase(), remainder: "" }; - return { command: cleaned.slice(0, slash).toLowerCase(), remainder: decodeURIComponent(cleaned.slice(slash + 1)) }; + return { command: cleaned.slice(0, slash).toLowerCase(), remainder: safeDecode(cleaned.slice(slash + 1)) }; +} + +/** + * URL-decode a remainder, falling back to the RAW string on a malformed `%` escape. + * `decodeURIComponent` throws `URIError` on a lone/short `%` (e.g. a `find/100%` pattern); + * {@link handleGraphVfs} promises it "never throws", so an undecodable remainder is passed + * through verbatim rather than blowing up the whole render. + */ +function safeDecode(s: string): string { + try { + return decodeURIComponent(s); + } catch { + return s; + } } // ════════════════════════════════════════════════════════════════════════════ diff --git a/tests/cli/daemon-service.test.ts b/tests/cli/daemon-service.test.ts index c21e6da6..eb6ed98f 100644 --- a/tests/cli/daemon-service.test.ts +++ b/tests/cli/daemon-service.test.ts @@ -530,6 +530,7 @@ describe("PRD-064h schtasks controller, register/restart/status argv (injected r ), ).toThrow(); }, + 30_000, ); it("isRegistered is true when /Query succeeds, false when it throws (task absent)", () => { diff --git a/tests/cli/health-probes.test.ts b/tests/cli/health-probes.test.ts index a4ec5c45..556e548e 100644 --- a/tests/cli/health-probes.test.ts +++ b/tests/cli/health-probes.test.ts @@ -36,7 +36,8 @@ describe("PRD-021b b-AC-5 — status reports the real D1–D5 health", () => { const lines = await health.evaluate(); const ids = lines.map((l) => l.id); expect(ids).toEqual(["D1", "D2", "D3", "D4", "D5"]); - }); + }, + 30_000); it("D5 (capture wired) reports HEALTHY when the Claude Code plugin is installed + enabled", async () => { const probes = buildHealthProbes(createFakeDaemonClient({ alive: true }), fakePluginRunner(true)); diff --git a/tests/daemon/runtime/codebase/query.test.ts b/tests/daemon/runtime/codebase/query.test.ts index 65f032e8..46c9d05f 100644 --- a/tests/daemon/runtime/codebase/query.test.ts +++ b/tests/daemon/runtime/codebase/query.test.ts @@ -303,6 +303,14 @@ describe("d-AC-5 zero network — handleGraphVfs reads only the local snapshot", expect(() => handleGraphVfs("graph/bogus/thing", buildFixture())).not.toThrow(); expect(handleGraphVfs("graph/bogus/thing", buildFixture())).toContain("unknown"); }); + + it("d-AC-5 a remainder with a malformed % escape does not throw (never throws contract)", () => { + // `decodeURIComponent` throws URIError on a lone/short `%`; the renderer must not. + for (const path of ["graph/find/100%", "graph/show/a%zz", "graph/neighborhood/src/x%.ts"]) { + expect(() => handleGraphVfs(path, buildFixture())).not.toThrow(); + expect(typeof handleGraphVfs(path, buildFixture())).toBe("string"); + } + }); }); // ── Supporting endpoints (FR-4 / FR-7) ────────────────────────────────────────── diff --git a/tests/daemon/runtime/logs/log-store.test.ts b/tests/daemon/runtime/logs/log-store.test.ts index 290f96be..4622a5ba 100644 --- a/tests/daemon/runtime/logs/log-store.test.ts +++ b/tests/daemon/runtime/logs/log-store.test.ts @@ -63,13 +63,15 @@ afterEach(() => { describe("PRD-043a openLogStore persists to disk", () => { it("AC-1: records written before a restart are queryable after re-opening the same logs.db", () => { // Write three records, then CLOSE (simulating the daemon stopping). - const first = openLogStore({ baseDir: dir }); + // Mock clock so test records (2026-06-20) don't get pruned as "too old" by the startup sweep. + const testClock = { now: () => Date.parse("2026-06-20T00:00:00.000Z") }; + const first = openLogStore({ baseDir: dir, clock: testClock }); expect(first.persistent).toBe(true); for (let i = 0; i < 3; i++) first.appendRequest(rec(i)); first.close(); // A FRESH store opens the SAME on-disk file (the daemon restarting) and reads the records back. - const second = openLogStore({ baseDir: dir }); + const second = openLogStore({ baseDir: dir, clock: testClock }); const page = second.queryRequests(resolveHistoryQuery({})); expect(page.records).toHaveLength(3); // Newest first. @@ -79,7 +81,8 @@ describe("PRD-043a openLogStore persists to disk", () => { }); it("AC-6: the request_log schema carries ONLY the record fields — no header/token/body column", () => { - const store = openLogStore({ baseDir: dir }); + const testClock = { now: () => Date.parse("2026-06-20T00:00:00.000Z") }; + const store = openLogStore({ baseDir: dir, clock: testClock }); store.appendRequest(rec(0)); const page = store.queryRequests(resolveHistoryQuery({})); const record = page.records[0] as Record; @@ -95,7 +98,8 @@ describe("PRD-043a openLogStore persists to disk", () => { }); it("the db file lands under .daemon/logs.db (mirrors the secrets .daemon pattern)", () => { - const store = openLogStore({ baseDir: dir }); + const testClock = { now: () => Date.parse("2026-06-20T00:00:00.000Z") }; + const store = openLogStore({ baseDir: dir, clock: testClock }); store.appendRequest(rec(0)); store.close(); // The file exists at the documented path. diff --git a/tests/daemon/runtime/pipeline/memory-redrive.test.ts b/tests/daemon/runtime/pipeline/memory-redrive.test.ts index 4c62bb26..ab2066e1 100644 --- a/tests/daemon/runtime/pipeline/memory-redrive.test.ts +++ b/tests/daemon/runtime/pipeline/memory-redrive.test.ts @@ -187,7 +187,8 @@ describe("b-AC-4: readTerminalControlledWriteJobs reads only TERMINAL memory_con const jobs = readTerminalControlledWriteJobs({ baseDir: dir }); expect(jobs).toHaveLength(1); expect(jobs[0]!.content).toBe("terminal fact"); - }); + }, + 30_000); it("returns [] when the local-queue.db does not exist yet (read-through fail-soft)", () => { // The temp dir has no `.daemon/local-queue.db` — the reader must NOT fabricate one.