practice slice 1 — contracts, the chain bijection, coverage

src/core/contracts/practice.ts (new domain): ReasoningChain (group by
conclusion, id = conclusion node id), PracticeParams (three 0..1 dials),
ProblemSet/Problem lifecycle, feedback-only ProblemGrade. src/core/
practice.ts: chainsForSelection (follows-only, both-ends-in-selection,
group-by-conclusion, singletons for edge-less nodes), chainsToGraph
(the exact inverse — bijection pinned), chainCoverage (the completeness
law: union of problems' chains = the set's chains; duplicated allowed).
13-test suite: diamond, premise-only, singleton, supports-invisible,
outside-endpoint, dedupe, determinism, round-trips, coverage cases.
This commit is contained in:
amosblomqvist
2026-09-29 17:48:20 +03:00
parent f12489fe49
commit 05dcaafc68
4 changed files with 386 additions and 3 deletions
+137
View File
@@ -0,0 +1,137 @@
/**
* Practice-problem contracts — the practice arc (feat/practice-problems,
* plan locked 2026-09-29).
*
* PURPOSE (owner, 2026-09-29): strengthening, NOT assessment — maximize
* strengthening per unit of mental effort. Teaching sessions install the
* graph; practice widens the set of situations in which held propositions
* come to mind. The old vision doc's exam machinery (assessment records,
* understanding writes) is PARKED: grading is feedback-only and never
* touches the graph.
*
* THE CHAIN MODEL (owner, 2026-09-29 — two rulings):
* - Chains group by CONCLUSION: every follows-edge into a node forms ONE
* chain (its premises → it). A conclusion of one chain can be a premise
* of later chains; nodes may repeat across chains. Edge-less nodes are
* singleton chains.
* - FOLLOWS ONLY: supports edges are invisible to practice ("no edges")
* — they neither chain nor cover.
* The translation is a bijection: the union of premises→conclusion pairs
* reconstructs exactly the selection's follows-edges (core/practice.ts).
*
* GENERATION is two-stage: a partitioner agent partitions the chains into
* the smallest covering set of problems (dedup-aware, confusables grouped);
* then ONE generator agent per problem builds it (the problem-engineering
* framework as its doctrine). Numbered problems appear in the UI the
* moment the partition lands — loading until their generator finishes —
* and NEVER show which propositions they test. Each problem commits to its
* full formulation, model solution, and grading rubric up front
* (commit-before-pose, the sycophancy mitigation). GRADING is one
* background job per problem, against the pre-written solution + rubric,
* feedback-only.
*
* Part of the domain-split contracts (src/core/contracts/): this file is
* owned by the practice stream. Pure types + protocol docs only.
*/
/** One reasoning chain — the practice module's agent-facing unit: all
* follows-edges into one conclusion, as premises → that conclusion.
* Singleton chains (premises: []) carry edge-less nodes. */
export interface ReasoningChain {
/** The chain's identity — the CONCLUSION node's id (chains group by
* conclusion, so the two are one). */
id: string;
/** Premise node ids — every follows-edge source into the conclusion,
* sorted. Empty = a singleton chain (an edge-less node). */
premises: string[];
/** The conclusion node id (=== id; mirrored so agent-facing renderings
* read "premises → conclusion" without indirection). */
conclusion: string;
}
/** The three dials every problem set is configured with (owner, 2026-09-29)
* — all 0..1, interpreted by the agents against the framework's axes. */
export interface PracticeParams {
/** Search-to-solution ratio — the main axis: flashcard recall (0) to
* competition problem (1). */
searchSolutionRatio: number;
/** Cue distance from every situation already used (0 = near the
* lesson, 1 = far transfer — still inside the recognizable band). */
cueDistance: number;
/** Presentation axis: pure theory (0) to real-world application (1). */
presentation: number;
}
/** Where a set stands in the two-stage run. The set is PERSISTED at
* "partitioning" (before the partitioner runs), its problem stubs land
* (still "generating") the moment the partition submits, and it flips to
* "ready" when every generator has settled — errored problems included
* (per-problem failure isolation: they carry their own status). */
export type ProblemSetStatus = "partitioning" | "generating" | "ready" | "error";
/** One problem's own lifecycle — a stub from the partition until its
* generator submits. */
export type ProblemStatus = "generating" | "ready" | "error";
/** The grader's three-level verdict (feedback-only; nothing derives from
* it — no understanding writes, owner 2026-09-29). */
export type GradeVerdict = "solid" | "partial" | "missed";
/** The grading job's deliverable, stored on the problem record. */
export interface ProblemGrade {
verdict: GradeVerdict;
/** What actually mattered: the failed recall named, the boundary
* taught — against the pre-written solution + rubric. */
feedback: string;
gradedAt: string;
}
/** One practice problem — built by ONE generator agent from its assigned
* chains, fully committed before the learner ever sees it. No name field
* (owner, 2026-09-29): problems are numbered by position, nothing else. */
export interface Problem {
/** "pp_" + ts36 + rand — store-assigned. */
id: string;
/** Chain ids (ReasoningChain.id — the conclusion node ids) this problem
* exercises. The coverage law: the union over all problems must equal
* the set's chains. */
chains: string[];
/** The full problem formulation — world-facing; never names the
* propositions it tests. */
statement: string;
/** The pre-written model solution (commit-before-pose). */
solution: string;
/** The brief free-form grading rubric — bullet points of what actually
* matters. */
rubric: string;
status: ProblemStatus;
/** Set when this problem's generation failed (the set completes
* around it). */
error?: string;
/** The learner's answer, once submitted (dictation-first — thinking
* out loud is the primary answering mode). */
answer?: string;
/** The grading job's verdict, once it lands. */
grade?: ProblemGrade;
}
/** One problem set — the persisted unit (one JSON document per set). */
export interface ProblemSet {
/** "ps_" + ts36 + rand — store-assigned. */
id: string;
/** Display label — auto-generated (the session's title, or
* date-based). */
title: string;
createdAt: string;
params: PracticeParams;
/** The selected node ids — the raw selection record. The chain
* decomposition derives from these + the follows-edges among them at
* creation time. */
nodeIds: string[];
status: ProblemSetStatus;
/** Set when the partition itself (or the run as a whole) failed. */
error?: string;
/** Problem stubs appear here the moment the partitioner submits —
* numbered by position, "generating", propositions invisible. */
problems: Problem[];
}