practice slice 2 — store, paths, self-contained sets

Contract amendment (planning ruling): ProblemSet gains propositions
(nodeId → {name, statement}) and chains (the frozen derivation) — sets
are documents of what WAS selected; grading/dedup/coverage never touch
the live graph. config.ts PATHS.practice → data/practice/. PracticeStore
port (createSet assigns ps_<ts36>_<rand>, getSet, listSets createdAt-desc,
saveSet replace-whole, deleteSet). src/store/practice.ts: one JSON per
set, hand-written isProblemSet read guard (types-only; corrupt/missing →
null/skipped, never thrown; saveSet refuses invalid records). Wired in
src/server/stores.ts. 10-test suite: round-trip, corrupt-tolerance,
missing-dir, validator garbage-reject + optional-tolerate + verdict
union, ps_ id shape.
This commit is contained in:
amosblomqvist
2026-09-29 17:53:36 +03:00
parent 05dcaafc68
commit b7a19609cb
6 changed files with 373 additions and 0 deletions
+10
View File
@@ -128,6 +128,16 @@ export interface ProblemSet {
* decomposition derives from these + the follows-edges among them at
* creation time. */
nodeIds: string[];
/** Snapshot of every selected proposition at creation — nodeId →
* {name, statement}. The set is SELF-CONTAINED (planning ruling,
* 2026-09-29): rendering, generation, grading, and dedup never
* re-read the live graph, which keeps moving (extraction continues,
* nodes get deleted); a set is a document of what WAS selected. */
propositions: Record<string, { name: string; statement: string }>;
/** The frozen chain derivation (chainsForSelection at creation time).
* Coverage checks and the problems' chain tags resolve against THIS,
* never against live edges. */
chains: ReasoningChain[];
status: ProblemSetStatus;
/** Set when the partition itself (or the run as a whole) failed. */
error?: string;
+28
View File
@@ -21,6 +21,7 @@ import type {
ExtractionRunUsage,
PlanTree,
Position,
ProblemSet,
CoverageEntry,
SessionCheckpoint,
TurnUsage,
@@ -157,6 +158,33 @@ export interface JobStore {
deleteJobsForChat(chatId: string): number;
}
// ─── PracticeStore — problem-set documents (data/practice) ──────────────────
/** Everything a new set needs except the store-assigned "ps_" id. */
export type CreateProblemSetInit = Omit<ProblemSet, "id">;
/**
* The practice module's persistence: one JSON document per problem set
* (src/store/practice.ts adapter). Sets are bounded, SELF-CONTAINED
* snapshots — propositions + chains frozen at creation (contracts/
* practice.ts) — so reads never join the live graph. Replace-whole
* writes like settings.json, one-file-per-record like data/jobs.
* Concurrent same-set writes are the pipeline's serialization duty
* (slice 4), not the store's — this seam owns file integrity only.
*/
export interface PracticeStore {
/** Persist a new set; the store assigns the id ("ps_" + ts36 + rand). */
createSet(init: CreateProblemSetInit): ProblemSet;
/** The full set document, or null when unknown id / corrupt file. */
getSet(id: string): ProblemSet | null;
/** Every set, createdAt descending (the practice page's list view). */
listSets(): ProblemSet[];
/** Persist the full set document (replace-whole). */
saveSet(set: ProblemSet): void;
/** Delete a set file; false when unknown id. */
deleteSet(id: string): boolean;
}
// ─── SettingsStore — THE user settings record (settings-storage wave, 2026-09-19) ──
/**