4e38b5a791
Full Next.js App Router application with: - AI-graded lesson checkpoints (BKT/Elo mastery tracking) - Auth.js v5: credentials, Google, GitHub, generic OIDC - Anonymous-session-first with migrate-on-signin - Admin panel: users, blueprints, reports, site settings - Password reset + email verification (nodemailer/SMTP) - Site config: require_auth + signups_enabled flags - server-only guards on all DB/generation/verification modules - PostgreSQL 16 + pgvector, Redis cache, Drizzle ORM - 271 unit tests (Vitest), golden-eval harness, Playwright e2e stubs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
177 lines
7.8 KiB
TypeScript
177 lines
7.8 KiB
TypeScript
/**
|
|
* Frozen golden eval cases for content verification (T1 + T2 cascade).
|
|
*
|
|
* Each case provides a synthetic segment + checkpoint + source chunks.
|
|
* The verifier should catch bad content (unsupported claims, wrong reference answers)
|
|
* and pass good content (well-grounded, correct reference answer).
|
|
*
|
|
* expectedT1Verdict: what T1 (cheap model) should return.
|
|
* isBadContent: true → verifier MUST return fail (false-pass = catastrophic).
|
|
* isGoodContent: true → verifier should return pass or uncertain (false-fail = trust erosion).
|
|
*/
|
|
|
|
export interface ContentCase {
|
|
id: string;
|
|
label: string;
|
|
segmentText: string;
|
|
sourceChunks: Array<{ docRef: string; text: string }>;
|
|
checkpointPrompt: string;
|
|
/** Only provided to T2, not T1. */
|
|
referenceAnswer: string;
|
|
rubric: Array<{ id: string; criterion: string; weight: number }>;
|
|
expectedT1Verdict: 'pass' | 'fail' | 'uncertain';
|
|
isBadContent: boolean;
|
|
isGoodContent: boolean;
|
|
}
|
|
|
|
const CLOSURE_CHUNK = {
|
|
docRef: 'js-closures/definition',
|
|
text: `A closure is a function that retains access to its lexical scope even after the outer function has returned. When a function is created, it captures a reference to its surrounding scope. This means variables defined in the outer function remain accessible to the inner function — they are not garbage collected as long as the inner function exists.
|
|
|
|
Example:
|
|
\`\`\`js
|
|
function makeGreeter(name) {
|
|
return function() {
|
|
return 'Hello, ' + name;
|
|
};
|
|
}
|
|
const greetAlice = makeGreeter('Alice');
|
|
greetAlice(); // 'Hello, Alice'
|
|
\`\`\`
|
|
|
|
In this example, the inner function closes over the \`name\` variable. Even after \`makeGreeter\` returns, the inner function can still access \`name\`.`,
|
|
};
|
|
|
|
const LOOP_CHUNK = {
|
|
docRef: 'js-closures/loop-bug',
|
|
text: `A common pitfall: closures in loops with \`var\`.
|
|
|
|
\`\`\`js
|
|
for (var i = 0; i < 3; i++) {
|
|
setTimeout(() => console.log(i), 0);
|
|
}
|
|
// logs: 3, 3, 3
|
|
\`\`\`
|
|
|
|
Because \`var\` is function-scoped (not block-scoped), all three arrow functions share the same \`i\` variable. When the callbacks run, the loop has finished and \`i\` is 3.
|
|
|
|
Fix: use \`let\`, which creates a new binding per iteration:
|
|
\`\`\`js
|
|
for (let i = 0; i < 3; i++) {
|
|
setTimeout(() => console.log(i), 0);
|
|
}
|
|
// logs: 0, 1, 2
|
|
\`\`\``,
|
|
};
|
|
|
|
export const CONTENT_CASES: ContentCase[] = [
|
|
// ── GOOD CONTENT ────────────────────────────────────────────────────────────
|
|
|
|
{
|
|
id: 'content-good-001',
|
|
label: 'well-grounded segment — should pass T1',
|
|
segmentText: `A closure is a function that keeps a reference to the variables in its outer scope, even after the outer function has finished running. This is possible because JavaScript functions retain a reference to the scope in which they were created — their "lexical environment."
|
|
|
|
When you call \`makeGreeter('Alice')\`, the returned function closes over the \`name\` variable. The outer function's stack frame is gone, but \`name\` lives on because the inner function holds a reference to it.
|
|
|
|
\`\`\`js
|
|
function makeGreeter(name) {
|
|
return function() {
|
|
return 'Hello, ' + name;
|
|
};
|
|
}
|
|
const greetAlice = makeGreeter('Alice');
|
|
greetAlice(); // 'Hello, Alice'
|
|
\`\`\`
|
|
|
|
This pattern is the foundation of data privacy in JavaScript: variables declared in the outer function are inaccessible from outside, reachable only through the returned function.`,
|
|
sourceChunks: [CLOSURE_CHUNK],
|
|
checkpointPrompt: 'In your own words, explain what happens to the `name` variable after `makeGreeter` returns.',
|
|
referenceAnswer: 'The name variable is retained in memory because the returned inner function closes over it. Even though makeGreeter has returned, name is not garbage-collected — the inner function holds a reference to the lexical scope where name was defined.',
|
|
rubric: [
|
|
{ id: 'r1', criterion: 'Explains that name persists because the inner function holds a reference', weight: 0.6 },
|
|
{ id: 'r2', criterion: 'Notes that makeGreeter has returned but name survives', weight: 0.4 },
|
|
],
|
|
expectedT1Verdict: 'pass',
|
|
isBadContent: false,
|
|
isGoodContent: true,
|
|
},
|
|
|
|
{
|
|
id: 'content-good-002',
|
|
label: 'loop bug explanation — well-grounded, should pass T1',
|
|
segmentText: `The classic closure-in-loop bug catches many developers by surprise. When you use \`var\` in a for loop and create functions inside the loop body, all those functions share a single \`i\` variable — because \`var\` is function-scoped, not block-scoped.
|
|
|
|
\`\`\`js
|
|
for (var i = 0; i < 3; i++) {
|
|
setTimeout(() => console.log(i), 0);
|
|
}
|
|
// Logs: 3, 3, 3
|
|
\`\`\`
|
|
|
|
By the time the callbacks fire, the loop has run to completion and \`i\` equals 3. The fix is straightforward: replace \`var\` with \`let\`. Because \`let\` is block-scoped, each iteration gets its own separate \`i\` binding.`,
|
|
sourceChunks: [LOOP_CHUNK],
|
|
checkpointPrompt: 'Why does the loop log "3" three times, and how do you fix it?',
|
|
referenceAnswer: 'var is function-scoped so all callbacks share the same i variable. By the time they run, i is 3. Fix: replace var with let to get a new i binding per iteration.',
|
|
rubric: [
|
|
{ id: 'r1', criterion: 'Identifies var scoping as the root cause', weight: 0.5 },
|
|
{ id: 'r2', criterion: 'Explains that all callbacks see the final value of i', weight: 0.3 },
|
|
{ id: 'r3', criterion: 'Proposes let as the fix', weight: 0.2 },
|
|
],
|
|
expectedT1Verdict: 'pass',
|
|
isBadContent: false,
|
|
isGoodContent: true,
|
|
},
|
|
|
|
// ── BAD CONTENT ─────────────────────────────────────────────────────────────
|
|
|
|
{
|
|
id: 'content-bad-001',
|
|
label: 'unsupported claim — closures copy values (not references)',
|
|
segmentText: `A closure captures a copy of the variables in its outer scope at the moment the inner function is created. This means the inner function has a snapshot of the values, not a live reference to them.
|
|
|
|
\`\`\`js
|
|
function makeCounter() {
|
|
let count = 0;
|
|
return {
|
|
increment: () => ++count,
|
|
getValue: () => count,
|
|
};
|
|
}
|
|
\`\`\`
|
|
|
|
When the returned methods run, they use the snapshot copy of \`count\` that was captured at creation time.`,
|
|
sourceChunks: [CLOSURE_CHUNK],
|
|
checkpointPrompt: 'Does a closure capture a copy of variables or a live reference?',
|
|
referenceAnswer: 'A closure captures a live reference to the variable, not a copy. Changes to the variable are reflected when the inner function reads it.',
|
|
rubric: [
|
|
{ id: 'r1', criterion: 'States it is a live reference, not a copy', weight: 1.0 },
|
|
],
|
|
expectedT1Verdict: 'fail',
|
|
isBadContent: true,
|
|
isGoodContent: false,
|
|
},
|
|
|
|
{
|
|
id: 'content-bad-002',
|
|
label: 'wrong reference answer — claims var is block-scoped',
|
|
segmentText: `The loop bug occurs because JavaScript's \`var\` is block-scoped, so each iteration of the loop has its own \`i\` variable. However, the asynchronous nature of \`setTimeout\` means the callbacks run after the loop completes, reading the final value of \`i\`.`,
|
|
sourceChunks: [LOOP_CHUNK],
|
|
checkpointPrompt: 'What is the scoping behavior of var in a for loop?',
|
|
referenceAnswer: 'var is block-scoped in for loops, so each iteration creates its own i binding.',
|
|
rubric: [
|
|
{ id: 'r1', criterion: 'Correctly identifies var scoping (block vs function)', weight: 1.0 },
|
|
],
|
|
expectedT1Verdict: 'fail',
|
|
isBadContent: true,
|
|
isGoodContent: false,
|
|
},
|
|
];
|
|
|
|
export const CONTENT_VERIFY_THRESHOLDS = {
|
|
/** Max fraction of bad-content cases that T1 passes (false-pass rate). */
|
|
maxFalsePassRate: 0.15,
|
|
/** Max fraction of good-content cases that T1 fails (false-fail rate). */
|
|
maxFalseFailRate: 0.15,
|
|
};
|