wave 2 (platform stream): settings storage & plumbing foundation (P2)

- src/core/settings.ts: versioned DELTA record schema + sanitizer (unknown keys preserved)
  — general/chat/agent{models,tools,prompts}/appearance/graph sections
- src/core/settings-resolution.ts: pure resolveSettings (factory defaults → user delta),
  extraction/graph-context channel predicates
- src/store/settings.ts + prompt-overrides.ts: SettingsStore/PromptOverrideStore adapters
  wired through stores.ts (effectiveSettings() fresh per call); lazy migration of
  default-settings.json into record.chat
- app/api/settings/** routes (record, section bodies, prompt overrides per segment)
- chat-handler: graph gates at trigger call-sites + toolToggles injection seam
- config.ts: SETTINGS_FILE + PROMPTS_OVERRIDE_DIR paths

587/587 tests; typecheck/lint/build clean
This commit is contained in:
amosblomqvist
2026-09-19 18:05:30 +03:00
parent 36c5d19437
commit cb4fe25c89
24 changed files with 2096 additions and 101 deletions
+129
View File
@@ -0,0 +1,129 @@
/**
* Tests for the prompt-override store (settings-storage wave, 2026-09-19):
* src/store/prompt-overrides.ts — override file CRUD, the mirror-layout
* path discipline (traversal guard), and the SOURCE-DERIVED segment list
* (the loader mirrors teacher-agent.ts's composeInstructions discovery;
* these tests pin that the mirror tracks the real src/agent/prompts tree).
* Writes under the repo data/prompts/ (disposable scratch) and cleans up.
*/
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { after, describe, it } from "node:test";
import { PROMPTS_OVERRIDE_DIR } from "../config.ts";
import {
allSegmentPaths,
effectivePromptSegments,
isValidSegmentPath,
promptOverrides,
} from "../src/store/prompt-overrides.ts";
after(() => {
fs.rmSync(PROMPTS_OVERRIDE_DIR, { recursive: true, force: true });
});
describe("isValidSegmentPath (the mirror-layout discipline)", () => {
it("accepts the shared base and numeric fragments, rejects everything else", () => {
assert.equal(isValidSegmentPath("system-prompt.md"), true);
assert.equal(isValidSegmentPath("teacher/20-teaching.md"), true);
assert.equal(isValidSegmentPath("prober/10-probe-doctrine.md"), true);
for (const bad of [
"", // empty
"README.md", // no agent dir
"teacher/README.md", // not numeric-prefixed
"teacher/20-teaching.txt", // not .md
"../system-prompt.md", // traversal
"agent/../teacher/20-teaching.md", // traversal
"/etc/passwd", // absolute
"teacher", // bare dir
"teacher/", // bare dir
"teacher/sub/10-x.md", // nested (not the mirror layout)
"prober\\10-x.md", // backslash
]) {
assert.equal(isValidSegmentPath(bad), false, bad);
}
});
});
describe("the override store", () => {
it("listOverrides is empty with no dir, then lists written mirrors sorted", () => {
fs.rmSync(PROMPTS_OVERRIDE_DIR, { recursive: true, force: true });
assert.deepEqual(promptOverrides.listOverrides(), []);
promptOverrides.writeOverride("teacher/20-teaching.md", "custom teaching");
promptOverrides.writeOverride("system-prompt.md", "custom base");
assert.deepEqual(promptOverrides.listOverrides(), ["system-prompt.md", "teacher/20-teaching.md"]);
});
it("readOverride round-trips; delete removes idempotently; invalid paths no-op", () => {
promptOverrides.writeOverride("prober/30-probe-done.md", "custom probe-done");
assert.equal(promptOverrides.readOverride("prober/30-probe-done.md"), "custom probe-done");
promptOverrides.deleteOverride("prober/30-probe-done.md");
assert.equal(promptOverrides.readOverride("prober/30-probe-done.md"), null);
promptOverrides.deleteOverride("prober/30-probe-done.md"); // again: no throw
// traversal junk never reaches disk
promptOverrides.writeOverride("../evil.md", "nope");
assert.equal(fs.existsSync(path.join(PROMPTS_OVERRIDE_DIR, "..", "evil.md")), false);
});
it("switches are NOT the store's concern — files alone never enable", () => {
promptOverrides.writeOverride("system-prompt.md", "custom base");
const off = effectivePromptSegments("teacher", {}) // no switches
.find((s) => s.path === "system-prompt.md");
assert.ok(off);
assert.equal(off.overrideText, "custom base"); // file exists...
assert.equal(off.enabled, false); // ...but no switch → source default
promptOverrides.deleteOverride("system-prompt.md"); // leave clean for the sibling tests
});
});
describe("the source-derived segment list", () => {
it("mirrors composeInstructions' discovery: base first, numeric fragments sorted, per agent", () => {
const prober = effectivePromptSegments("prober", {});
const teacher = effectivePromptSegments("teacher", {});
// The shared base composes both prompts, SAME path.
assert.equal(prober[0]?.path, "system-prompt.md");
assert.equal(teacher[0]?.path, "system-prompt.md");
assert.equal(prober[0]?.shared, true);
assert.equal(teacher[0]?.shared, true);
assert.equal(prober[0]?.sourceText.length > 0, true, "base prompt text loads");
// Fragments track the REAL source tree (numeric order, right agent dir).
assert.deepEqual(
prober.map((s) => s.path),
["system-prompt.md", "prober/10-probe-doctrine.md", "prober/20-teaching-in-brief.md", "prober/30-probe-done.md"],
);
assert.equal(teacher.some((s) => s.path === "teacher/10-planning.md"), true);
assert.equal(teacher.some((s) => s.path === "teacher/50-probing-in-brief.md"), true);
assert.equal(teacher.every((s) => s.path === "system-prompt.md" || s.path.startsWith("teacher/")), true);
// README and other non-numeric files never load.
assert.equal(allSegmentPaths().some((p) => p.includes("README")), false);
});
it("effective segments: switch+file → override; either missing → source text", () => {
promptOverrides.writeOverride("teacher/20-teaching.md", "MY WAY");
const switches = { "teacher/20-teaching.md": true, "system-prompt.md": false };
const teacher = effectivePromptSegments("teacher", switches);
const teaching = teacher.find((s) => s.path === "teacher/20-teaching.md");
assert.ok(teaching);
assert.equal(teaching.overrideText, "MY WAY");
assert.equal(teaching.enabled, true);
// base: switch OFF with file ABSENT → source, disabled
const base = teacher.find((s) => s.path === "system-prompt.md");
assert.ok(base);
assert.equal(base.overrideText, null);
assert.equal(base.enabled, false);
// a switch for a segment with NO override file never enables it
const weird = effectivePromptSegments("teacher", { "teacher/10-planning.md": true });
const planning = weird.find((s) => s.path === "teacher/10-planning.md");
assert.ok(planning);
assert.equal(planning.enabled, false);
assert.equal(planning.overrideText, null);
});
it("adapts to source changes by construction: the list is computed, not stored", () => {
// If a fragment vanishes upstream, its switch/override simply stop
// composing — nothing here can go stale. Pinned by absence:
const before = allSegmentPaths().length;
assert.equal(before > 5, true, "base + prober fragments + teacher fragments");
});
});