Contracts: LadderRung 0-4, CueDistance 0-2, Presentation theory|
application; PracticeParams carries the three named levels. Doc
comments hold the three definition tables verbatim (rung names +
definitions from the ladder doctrine; cue levels; presentation
levels).
Core: LADDER_RUNGS/CUE_LEVELS/PRESENTATIONS tables in src/core/
practice.ts (definitions verbatim) — ONE source spoken by the agents'
prompts, the dialog's stepped controls, and the exam header; core
barrel re-exports practice.ts (pure domain, browser-safe).
Agents: Part I's ladder intro now 'five rungs, numbered; the request
arrives as a rung' with bullets numbered 0-4 (definitions untouched);
Part III's parameters block replaced with the named-level doctrine
(rung = exact class, no continuum; cue counts lesson examples AND
previous problems' situations; presentation dresses, never changes);
partitioner's RESPECT THE PARAMETERS replaced (low rungs = many small
assignments, high = larger; far cue makes confusables-together MORE
important; presentation does not shape the partition). renderParams
outputs the exact named-level block. Anchor/0..1 language swept dead.
Service + store: strict union validation — floats between levels,
out-of-range, wrong strings, legacy dial shapes all 400 at the API
and fail isProblemSet (legacy sets skipped from lists; no migration —
data/practice is disposable scratch, owner wipes before testing).
UI: dialog = 5-detent ladder slider + 3-detent cue slider (each shows
the level NAME + one-line definition) + Theory|Application segmented
toggle; defaults rung 2 / cue 1 / theory; Create posts the new shape.
Exam header gains the levels line ('Direct exercise · Adjacent
context · Theory').
Tests: renderParams exact-block pin; strict-validation pins (new
tests/practice-service.test.ts — bad unions 400, legacy shape 400);
store pins (legacy floats + 2.5 rung fail); render pins (detents +
names + definitions, defaults, detent-swap, toggle active segment,
levels line); every params fixture moved off floats. 1138/1138, tsc
0, lint 0, build 0.
Docs: architecture.md — discretization paragraph (compact tables,
named-level invocation, defaults, one-table source), UI mentions;
README axes sentence.
44 lines
2.0 KiB
TypeScript
44 lines
2.0 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createPracticeSetResponse } from "../src/server/practice.ts";
|
|
|
|
/**
|
|
* The practice create-route's parameter validation (owner, 2026-09-29):
|
|
* the three axes are NAMED LEVELS — exact unions, nothing between. A
|
|
* rung is a rung; a float between rungs, a wrong string, or a missing
|
|
* field is a 400 BEFORE the pipeline is ever reached (these pins only
|
|
* exercise the rejecting paths — the accepting path runs the real
|
|
* pipeline and belongs to the pipeline suite).
|
|
*/
|
|
|
|
const NODE_IDS = { nodeIds: ["n1", "n2", "n3"], title: "Ethics cluster" };
|
|
|
|
async function statusOf(params: unknown): Promise<number> {
|
|
const res = await createPracticeSetResponse({ ...NODE_IDS, params });
|
|
return res.status;
|
|
}
|
|
|
|
describe("createPracticeSetResponse — strict named-level params", () => {
|
|
it("floats between levels → 400", async () => {
|
|
assert.equal(await statusOf({ ladderRung: 2.5, cueDistance: 1, presentation: "theory" }), 400);
|
|
assert.equal(await statusOf({ ladderRung: 2, cueDistance: 1.5, presentation: "theory" }), 400);
|
|
});
|
|
|
|
it("out-of-range integers → 400", async () => {
|
|
assert.equal(await statusOf({ ladderRung: 5, cueDistance: 1, presentation: "theory" }), 400);
|
|
assert.equal(await statusOf({ ladderRung: -1, cueDistance: 1, presentation: "theory" }), 400);
|
|
assert.equal(await statusOf({ ladderRung: 2, cueDistance: 3, presentation: "theory" }), 400);
|
|
});
|
|
|
|
it("wrong presentation → 400", async () => {
|
|
assert.equal(await statusOf({ ladderRung: 2, cueDistance: 1, presentation: "real-world" }), 400);
|
|
assert.equal(await statusOf({ ladderRung: 2, cueDistance: 1, presentation: 1 }), 400);
|
|
});
|
|
|
|
it("missing / legacy float fields → 400", async () => {
|
|
assert.equal(await statusOf({ ladderRung: 2, cueDistance: 1 }), 400);
|
|
assert.equal(await statusOf({ searchSolutionRatio: 0.7, cueDistance: 0.4, presentation: 0.6 }), 400, "legacy dials are dead");
|
|
assert.equal(await statusOf("high"), 400);
|
|
});
|
|
});
|