# Phase Graders, Company Modes and the Evidence Ledger

> **For agentic workers:** Tasks are grouped into three independent lanes that may run in parallel — see "Lanes" below. Steps use checkbox (`- [ ]`) syntax.

**Goal:** Complete the grading backend for all eight phases, the company-mode configuration, syllabus demonstration tracking, and the evidence ledger.

**Binding specs:**
- `docs/specs/2026-08-18-standard-and-syllabus.md` — the anchored rubric and the 34 items. **The authority for every score in this plan.**
- `docs/specs/2026-08-18-deterministic-contract.md` — `INVALID` vs `no-hire`, and why
- `docs/specs/2026-08-18-lld-dojo-design.md` — the eight phases and their exit criteria
- `docs/specs/2026-08-18-learning-design.md` — failure tags route to lessons

**Available and verified:** `server/lib/{paths,db,corpus,java,junit,diff,stress,grader-llm,grade-phase1,vocab,http,validate-corpus}.mjs`, `server/index.mjs`, `harness/DojoConcurrency.java`, `.toolchain/jdk-21`. **82 tests pass.**

**Conventions:** no git (`node tools/snapshot.mjs "<label>"`), zero runtime dependencies, ESM `.mjs`, bare `node --test` only, strict TDD.

## Lanes

| Lane | Tasks | Owns |
|---|---|---|
| **A** | 1–4 | `grade-phase2/3/4/6.mjs`, `modes.mjs` + their tests |
| **B** | 5–6 | `rubric.mjs`, `grade-phase5.mjs`, `grade-phase7.mjs` + their tests |
| **C** | 7–9 | `syllabus.mjs`, `ledger.mjs`, `readiness.mjs`, db migration + their tests |

Lane C touches `db.mjs`. Lanes A and B must not.

---

## Task 1: Company modes

**Files:** create `server/lib/modes.mjs`, `server/test/modes.test.mjs`

- [ ] **Step 1: Write the failing test**

```javascript
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { MODES, modeFor, curveballDueAt, isSuitable } from '../lib/modes.mjs';

test('every documented company mode exists with a clock and a deliverable', () => {
  for (const id of ['uber', 'atlassian', 'salesforce', 'flipkart', 'meta']) {
    const m = modeFor(id);
    assert.ok(m.clockMs > 0, `${id} needs a clock`);
    assert.ok(m.deliverable, `${id} needs a deliverable`);
  }
});

test('atlassian always injects a curveball, and at T+30', () => {
  const m = modeFor('atlassian');
  assert.equal(m.curveball, 'always');
  assert.equal(curveballDueAt(m), 30 * 60 * 1000);
});

test('meta mode requires no running code', () => {
  const m = modeFor('meta');
  assert.equal(m.mustRun, false);
  assert.equal(m.deliverable, 'requirements+api+outline');
});

test('salesforce scores concurrency, others do not', () => {
  assert.equal(modeFor('salesforce').scoresConcurrency, true);
  assert.equal(modeFor('uber').scoresConcurrency, false);
});

test('curveballDueAt returns null when a mode never injects one', () => {
  assert.equal(curveballDueAt(modeFor('meta')), null);
});

test('modeFor rejects an unknown mode rather than silently defaulting', () => {
  assert.throws(() => modeFor('nope'), /unknown mode/i);
});

test('isSuitable requires concurrency tests for a concurrency-scoring mode', () => {
  const withStress = { tests_concurrency: true };
  assert.equal(isSuitable(modeFor('salesforce'), withStress), true);
  assert.equal(isSuitable(modeFor('salesforce'), {}), false);
  assert.equal(isSuitable(modeFor('uber'), {}), true);
});

test('atlassian mode narrows to its recycled question bank', () => {
  const m = modeFor('atlassian');
  assert.ok(m.problemAllowList.includes('cost-explorer'));
  assert.ok(m.problemAllowList.includes('rating-system'));
  assert.ok(!m.problemAllowList.includes('parking-lot'));
});
```

- [ ] **Step 2** — run it, confirm `Cannot find module`

- [ ] **Step 3: Implement `server/lib/modes.mjs`**

```javascript
/**
 * Company flavour is configuration, not content — the same problems and the same
 * rubric, with different clocks and pressures. Sourced from the 2025-2026 research
 * in docs/research/2026-08-18-lld-round-landscape.md.
 */
export const MODES = {
  uber: {
    id: 'uber', label: 'Uber', clockMs: 50 * 60 * 1000,
    mustRun: true, curveball: 'optional', curveballAtMs: 30 * 60 * 1000,
    scoresConcurrency: false, deliverable: 'code+main',
    note: 'Code is compiled and executed. Clean OOP and extensibility.',
  },
  atlassian: {
    id: 'atlassian', label: 'Atlassian', clockMs: 55 * 60 * 1000,
    mustRun: true, curveball: 'always', curveballAtMs: 30 * 60 * 1000,
    scoresConcurrency: false, deliverable: 'code+edge-cases',
    // Atlassian recycles a small bank; targeted drilling genuinely pays here.
    problemAllowList: ['cost-explorer', 'rating-system', 'middleware-router',
                       'customer-satisfaction', 'snake-and-ladder'],
    note: 'A scale-up requirement lands mid-round. Edge-case validation is scored.',
  },
  salesforce: {
    id: 'salesforce', label: 'Salesforce', clockMs: 60 * 60 * 1000,
    mustRun: true, curveball: 'optional', curveballAtMs: 35 * 60 * 1000,
    scoresConcurrency: true, deliverable: 'code+thread-safety',
    note: 'Java, multithreading and thread safety are first-class. Prompts are ambiguous.',
  },
  flipkart: {
    id: 'flipkart', label: 'Flipkart / Swiggy', clockMs: 100 * 60 * 1000,
    mustRun: true, curveball: 'always', curveballAtMs: 60 * 60 * 1000,
    scoresConcurrency: false, deliverable: 'code+main+review',
    note: 'Long build, then a code review you must defend.',
  },
  meta: {
    id: 'meta', label: 'Meta (product architecture)', clockMs: 45 * 60 * 1000,
    mustRun: false, curveball: 'never', curveballAtMs: null,
    scoresConcurrency: false, deliverable: 'requirements+api+outline',
    note: 'No running code. Requirements, API surface and a component outline.',
  },
};

export function modeFor(id) {
  const m = MODES[id];
  if (!m) throw new Error(`unknown mode: ${id}`);
  return m;
}

export function curveballDueAt(mode) {
  return mode.curveball === 'never' ? null : mode.curveballAtMs;
}

/** A concurrency-scoring mode cannot use a problem with no stress suite. */
export function isSuitable(mode, problem) {
  if (mode.scoresConcurrency && !problem.tests_concurrency) return false;
  if (mode.problemAllowList && problem.id && !mode.problemAllowList.includes(problem.id)) return false;
  return true;
}
```

- [ ] **Step 4** — confirm 8 tests pass. **Step 5** — snapshot `phases-1-modes`

---

## Task 2: Phase 2 and Phase 3 graders

**Files:** create `server/lib/grade-phase2.mjs`, `server/lib/grade-phase3.mjs`, and their tests.

Phase 2 = skeleton sprint (types and seams only, must compile, 12 min).
Phase 3 = core build (base suite green, `main` mandatory, 30 min).

- [ ] **Step 1: Write the failing tests**

```javascript
// server/test/grade-phase2.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gradeSkeleton, missingFrom } from '../lib/grade-phase2.mjs';

const SPEC = { required_types: ['ParkingLot', 'Ticket'], required_seams: ['PricingPolicy'] };

test('missingFrom finds declarations regardless of declaration keyword', () => {
  const src = 'class ParkingLot {} record Ticket() {} interface PricingPolicy {}';
  assert.deepEqual(missingFrom(src, ['ParkingLot', 'Ticket', 'PricingPolicy']), []);
});

test('missingFrom ignores names that appear only in comments or strings', () => {
  const src = '// interface PricingPolicy\nString s = "class ParkingLot";';
  assert.deepEqual(missingFrom(src, ['ParkingLot', 'PricingPolicy']).sort(),
    ['ParkingLot', 'PricingPolicy']);
});

test('gradeSkeleton passes only when compiled, complete and inside the clock', () => {
  const ok = gradeSkeleton({ spec: SPEC, compiled: true,
    source: 'class ParkingLot{} record Ticket(){} interface PricingPolicy{}',
    elapsedMs: 60_000, limitMs: 720_000 });
  assert.equal(ok.passed, true);
  assert.deepEqual(ok.tags, []);
});

test('gradeSkeleton fails and tags a missing seam distinctly from a missing type', () => {
  const r = gradeSkeleton({ spec: SPEC, compiled: true,
    source: 'class ParkingLot{} record Ticket(){}', elapsedMs: 60_000, limitMs: 720_000 });
  assert.equal(r.passed, false);
  assert.deepEqual(r.missingSeams, ['PricingPolicy']);
  assert.ok(r.tags.includes('missing-seam'));
  assert.ok(!r.tags.includes('missing-type'));
});

test('gradeSkeleton tags an overrun clock without discarding the work', () => {
  const r = gradeSkeleton({ spec: SPEC, compiled: true,
    source: 'class ParkingLot{} record Ticket(){} interface PricingPolicy{}',
    elapsedMs: 900_000, limitMs: 720_000 });
  assert.equal(r.passed, false);
  assert.ok(r.tags.includes('ran-out-of-clock'));
  assert.equal(r.withinClock, false);
  assert.deepEqual(r.missingSeams, [], 'the design was complete — only the clock failed');
});

test('gradeSkeleton reports INVALID when it did not compile', () => {
  const r = gradeSkeleton({ spec: SPEC, compiled: false, source: '', elapsedMs: 1, limitMs: 720_000 });
  assert.equal(r.verdict, 'INVALID');
  assert.equal(r.passed, false);
});
```

```javascript
// server/test/grade-phase3.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gradeCoreBuild, hasMain, functionalityLevel } from '../lib/grade-phase3.mjs';

test('hasMain detects a real main and ignores a mention in a comment', () => {
  assert.equal(hasMain('public static void main(String[] args) {}'), true);
  assert.equal(hasMain('public static void main(String... args){}'), true);
  assert.equal(hasMain('// public static void main(String[] args)'), false);
  assert.equal(hasMain('void mainMenu() {}'), false);
});

test('functionalityLevel follows the STANDARD v1.0 anchors', () => {
  // 0 — did not compile, OR no main
  assert.equal(functionalityLevel({ compiled: false, hasMain: true, pass: 9, total: 9, withinClock: true }), 0);
  assert.equal(functionalityLevel({ compiled: true, hasMain: false, pass: 9, total: 9, withinClock: true }), 0);
  // 1 — compiles, under half green
  assert.equal(functionalityLevel({ compiled: true, hasMain: true, pass: 3, total: 10, withinClock: true }), 1);
  // 2 — all green
  assert.equal(functionalityLevel({ compiled: true, hasMain: true, pass: 10, total: 10, withinClock: false }), 2);
  // 3 — all green inside the clock
  assert.equal(functionalityLevel({ compiled: true, hasMain: true, pass: 10, total: 10, withinClock: true }), 3);
});

test('an empty suite can never reach a passing level', () => {
  assert.equal(functionalityLevel({ compiled: true, hasMain: true, pass: 0, total: 0, withinClock: true }), 0);
});

test('gradeCoreBuild returns INVALID rather than no-hire when compilation failed', () => {
  const r = gradeCoreBuild({ compiled: false, diagnostics: [{ line: 3 }], summary: { pass: 0, total: 0 },
                             source: '', elapsedMs: 1, limitMs: 1800_000 });
  assert.equal(r.verdict, 'INVALID');
  assert.ok(/did not compile/i.test(r.reason));
});

test('gradeCoreBuild tags a missing driver, because interviewers run it first', () => {
  const r = gradeCoreBuild({ compiled: true, diagnostics: [], summary: { pass: 9, total: 9 },
                             source: 'class A {}', elapsedMs: 1000, limitMs: 1800_000 });
  assert.equal(r.level, 0);
  assert.ok(r.tags.includes('no-main'));
});

test('gradeCoreBuild passes a green suite with a driver inside the clock', () => {
  const r = gradeCoreBuild({ compiled: true, diagnostics: [], summary: { pass: 9, total: 9 },
                             source: 'class A { public static void main(String[] a){} }',
                             elapsedMs: 1000, limitMs: 1800_000 });
  assert.equal(r.verdict, 'scored');
  assert.equal(r.level, 3);
  assert.equal(r.passed, true);
});
```

- [ ] **Step 2** — run both, confirm module-not-found

- [ ] **Step 3: Implement `server/lib/grade-phase2.mjs`**

```javascript
/** Comments and string literals must never satisfy a structural check. */
export function stripNoise(src) {
  return String(src)
    .replace(/\/\*[\s\S]*?\*\//g, ' ')
    .replace(/\/\/[^\n]*/g, ' ')
    .replace(/"(?:\\.|[^"\\])*"/g, '""');
}

export function missingFrom(source, names) {
  const src = stripNoise(source);
  return names.filter((n) => !new RegExp(`\\b(class|interface|enum|record)\\s+${n}\\b`).test(src));
}

const LIMIT_MS = 12 * 60 * 1000;

export function gradeSkeleton({ spec, compiled, source, elapsedMs, limitMs = LIMIT_MS }) {
  if (!compiled) {
    return { verdict: 'INVALID', passed: false, reason: 'did not compile',
             missingTypes: [], missingSeams: [], withinClock: elapsedMs <= limitMs, tags: [] };
  }

  const missingTypes = missingFrom(source, spec.required_types ?? []);
  const missingSeams = missingFrom(source, spec.required_seams ?? []);
  const withinClock = elapsedMs <= limitMs;

  const tags = [];
  if (missingSeams.length) tags.push('missing-seam');
  if (missingTypes.length) tags.push('missing-type');
  if (!withinClock) tags.push('ran-out-of-clock');

  return {
    verdict: 'scored',
    passed: tags.length === 0,
    missingTypes, missingSeams, withinClock, elapsedMs, tags,
  };
}
```

- [ ] **Step 4: Implement `server/lib/grade-phase3.mjs`**

```javascript
import { stripNoise } from './grade-phase2.mjs';

export function hasMain(source) {
  return /\bpublic\s+static\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s*\w+\s*\)/.test(stripNoise(source));
}

/**
 * STANDARD v1.0, dimension D2. Deterministic and never overridden by a model.
 *
 * A missing driver caps this at 0 even with a green suite: interviewers run main
 * first, and an unrunnable submission forfeits the functionality dimension.
 * An empty suite is likewise never a pass — zero tests usually means a broken
 * classpath, and that must not read as success.
 */
export function functionalityLevel({ compiled, hasMain: main, pass, total, withinClock }) {
  if (!compiled || !main) return 0;
  if (total === 0) return 0;
  if (pass < total) return pass / total < 0.5 ? 1 : 1;
  return withinClock ? 3 : 2;
}

const LIMIT_MS = 30 * 60 * 1000;

export function gradeCoreBuild({ compiled, diagnostics, summary, source, elapsedMs, limitMs = LIMIT_MS }) {
  if (!compiled) {
    return { verdict: 'INVALID', passed: false, level: null, diagnostics,
             reason: 'did not compile — nothing was scored and nothing is recorded against you',
             tags: [] };
  }

  const main = hasMain(source);
  const withinClock = elapsedMs <= limitMs;
  const level = functionalityLevel({ compiled, hasMain: main, pass: summary.pass, total: summary.total, withinClock });

  const tags = [];
  if (!main) tags.push('no-main');
  if (summary.total > 0 && summary.pass < summary.total) tags.push('suite-not-green');
  if (!withinClock) tags.push('ran-out-of-clock');

  return {
    verdict: 'scored', level, passed: level >= 2,
    hasMain: main, withinClock, elapsedMs, summary, diagnostics: [], tags,
  };
}
```

- [ ] **Step 5** — confirm both suites pass. **Step 6** — snapshot `phases-2-skeleton-core`

---

## Task 3: Phase 4 — extension under fire

The Atlassian discriminator. **Files:** `server/lib/grade-phase4.mjs`, test.

- [ ] **Step 1: Write the failing test**

```javascript
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { extensibilityLevel, gradeExtension } from '../lib/grade-phase4.mjs';

test('extensibilityLevel follows the STANDARD v1.0 D4 anchors', () => {
  // 0 — curveball suite fails
  assert.equal(extensibilityLevel({ green: false, diffLines: 0, budget: 6, touchedFiles: 0 }), 0);
  // 3 — passed by adding files only
  assert.equal(extensibilityLevel({ green: true, diffLines: 0, budget: 6, touchedFiles: 0 }), 3);
  // 2 — within budget
  assert.equal(extensibilityLevel({ green: true, diffLines: 5, budget: 6, touchedFiles: 2 }), 2);
  // 1 — over budget but under 2x
  assert.equal(extensibilityLevel({ green: true, diffLines: 10, budget: 6, touchedFiles: 3 }), 1);
  // 1 -> still 1 at exactly 2x, 0 beyond
  assert.equal(extensibilityLevel({ green: true, diffLines: 12, budget: 6, touchedFiles: 3 }), 1);
  assert.equal(extensibilityLevel({ green: true, diffLines: 13, budget: 6, touchedFiles: 4 }), 0);
});

test('gradeExtension reports the raw number so the verdict is inspectable', () => {
  const r = gradeExtension({ green: true, change: { diffLines: 4, touchedFiles: 1, newFiles: 1, perFile: {} }, budget: 6 });
  assert.equal(r.diffLines, 4);
  assert.equal(r.budget, 6);
  assert.equal(r.level, 2);
  assert.equal(r.passed, true);
});

test('gradeExtension tags an if-chain rewrite for lesson routing', () => {
  const r = gradeExtension({ green: true, change: { diffLines: 31, touchedFiles: 3, newFiles: 0, perFile: {} }, budget: 6 });
  assert.equal(r.level, 0);
  assert.ok(r.tags.includes('rewrote-existing-files'));
});

test('gradeExtension is INVALID when the curveball did not compile', () => {
  const r = gradeExtension({ compiled: false, green: false, change: { diffLines: 0 }, budget: 6 });
  assert.equal(r.verdict, 'INVALID');
});

test('a zero-change pass is celebrated as the exemplary case', () => {
  const r = gradeExtension({ green: true, change: { diffLines: 0, touchedFiles: 0, newFiles: 1, perFile: {} }, budget: 6 });
  assert.equal(r.level, 3);
  assert.match(r.headline, /adding|added/i);
});
```

- [ ] **Step 2** — confirm failure. **Step 3: Implement**

```javascript
/**
 * STANDARD v1.0, dimension D4 — deterministic.
 *
 * The question: when a new requirement landed, how much of the code that already
 * existed did you have to edit? Level 3 is reserved for absorbing the change by
 * ADDING files only, because that is what a correctly-seamed design achieves.
 */
export function extensibilityLevel({ green, diffLines, budget, touchedFiles }) {
  if (!green) return 0;
  if (diffLines === 0 && touchedFiles === 0) return 3;
  if (diffLines <= budget) return 2;
  if (diffLines <= budget * 2) return 1;
  return 0;
}

export function gradeExtension({ compiled = true, green, change, budget }) {
  if (!compiled) {
    return { verdict: 'INVALID', passed: false, level: null,
             reason: 'the extension did not compile — nothing was scored', tags: [] };
  }

  const { diffLines, touchedFiles = 0, newFiles = 0, perFile = {} } = change;
  const level = extensibilityLevel({ green, diffLines, budget, touchedFiles });

  const tags = [];
  if (!green) tags.push('curveball-suite-failed');
  if (green && diffLines > budget * 2) tags.push('rewrote-existing-files');
  else if (green && diffLines > budget) tags.push('over-diff-budget');

  const headline = !green
    ? 'The new requirement is not satisfied yet.'
    : level === 3
      ? `Absorbed by adding ${newFiles} file${newFiles === 1 ? '' : 's'} and changing nothing that already existed.`
      : `${diffLines} line${diffLines === 1 ? '' : 's'} changed across ${touchedFiles} existing file${touchedFiles === 1 ? '' : 's'} (budget ${budget}).`;

  return { verdict: 'scored', level, passed: level >= 2, green,
           diffLines, budget, touchedFiles, newFiles, perFile, headline, tags };
}
```

- [ ] **Step 4** — confirm pass. **Step 5** — snapshot `phases-3-extension`

---

## Task 4: Phase 6 — concurrency

**Files:** `server/lib/grade-phase6.mjs`, test. Wraps `stress.mjs`'s `classifyStress`/`runStress`.

- [ ] **Step 1: Test**

```javascript
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gradeConcurrency } from '../lib/grade-phase6.mjs';

test('gradeConcurrency passes only when the stress suite survived every repetition', () => {
  const r = gradeConcurrency({ compiled: true, survived: true, kind: 'clean', repetitions: 5,
                               verdict: 'no lost updates across 5 repetitions' });
  assert.equal(r.passed, true);
  assert.equal(r.level, 2);
});

test('gradeConcurrency maps each failure kind to its own lesson tag', () => {
  assert.ok(gradeConcurrency({ compiled: true, survived: false, kind: 'lost-update', repetitions: 1 })
    .tags.includes('lost-update'));
  assert.ok(gradeConcurrency({ compiled: true, survived: false, kind: 'deadlock', repetitions: 1 })
    .tags.includes('deadlock'));
  assert.ok(gradeConcurrency({ compiled: true, survived: false, kind: 'race', repetitions: 1 })
    .tags.includes('race'));
});

test('a not-run suite is INVALID, never a pass', () => {
  const r = gradeConcurrency({ compiled: true, survived: false, kind: 'not-run', repetitions: 0 });
  assert.equal(r.verdict, 'INVALID');
  assert.equal(r.passed, false);
});

test('gradeConcurrency is INVALID when the code did not compile', () => {
  const r = gradeConcurrency({ compiled: false, survived: false, kind: 'not-run', repetitions: 0 });
  assert.equal(r.verdict, 'INVALID');
});

test('surviving more repetitions than required reaches the exemplary level', () => {
  const r = gradeConcurrency({ compiled: true, survived: true, kind: 'clean', repetitions: 20 });
  assert.equal(r.level, 3);
});
```

- [ ] **Step 2–4: Implement, confirm, snapshot `phases-4-concurrency`**

```javascript
const TAG_FOR = { 'lost-update': 'lost-update', deadlock: 'deadlock', race: 'race' };

export function gradeConcurrency({ compiled, survived, kind, repetitions, verdict = '' }) {
  if (!compiled || kind === 'not-run') {
    return { verdict: 'INVALID', passed: false, level: null, kind, repetitions,
             reason: compiled
               ? 'no concurrency tests ran — the suite or classpath is wrong, which is never a pass'
               : 'the code did not compile, so no race could be tested',
             tags: [] };
  }

  if (survived) {
    return { verdict: 'scored', passed: true, level: repetitions >= 10 ? 3 : 2,
             kind, repetitions, headline: verdict, tags: [] };
  }

  return { verdict: 'scored', passed: false, level: kind === 'deadlock' ? 0 : 1,
           kind, repetitions, headline: verdict, tags: [TAG_FOR[kind] ?? 'race'] };
}
```

---

## Task 5: The rubric engine with citation validation

**Lane B.** This is the anti-arbitrariness machinery from STANDARD v1.0. **Files:** `server/lib/rubric.mjs`, test.

- [ ] **Step 1: Write the failing test**

```javascript
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
  DIMENSIONS, validateCitations, validateScore, weightedLevel, verdictFor, buildRubricPrompt,
} from '../lib/rubric.mjs';

const FILES = [{ path: 'Lot.java', content: 'a\nb\nc\nd\ne' }, { path: 'PricingPolicy.java', content: 'x\ny' }];

test('the five dimensions carry the documented weights summing to 1', () => {
  assert.equal(DIMENSIONS.length, 5);
  const sum = DIMENSIONS.reduce((n, d) => n + d.weight, 0);
  assert.ok(Math.abs(sum - 1) < 1e-9, `weights sum to ${sum}`);
  assert.equal(DIMENSIONS.find((d) => d.id === 'D1').weight, 0.30);
  assert.equal(DIMENSIONS.find((d) => d.id === 'D4').weight, 0.15);
});

test('validateCitations accepts a real file and an in-range line', () => {
  assert.deepEqual(validateCitations(['Lot.java:3 — policy injected'], FILES).invalid, []);
});

test('validateCitations rejects a nonexistent file', () => {
  const r = validateCitations(['Ghost.java:1 — invented'], FILES);
  assert.equal(r.ok, false);
  assert.match(r.invalid[0], /Ghost\.java/);
});

test('validateCitations rejects an out-of-range line number', () => {
  const r = validateCitations(['Lot.java:99 — beyond the file'], FILES);
  assert.equal(r.ok, false);
  assert.match(r.invalid[0], /99/);
});

test('validateCitations accepts a bare type-name citation with no line', () => {
  assert.equal(validateCitations(['PricingPolicy — a single-method interface'], FILES).ok, true);
});

test('validateScore requires counter_evidence at levels 2 and 3', () => {
  const base = { dimension: 'D1', level: 2, citations: ['Lot.java:1 — x'], rationale: 'ok' };
  assert.equal(validateScore({ ...base, counter_evidence: ['none found'] }, FILES).ok, true);
  assert.equal(validateScore(base, FILES).ok, false);
  assert.match(validateScore(base, FILES).errors.join(' '), /counter_evidence/);
});

test('validateScore requires two citations for level 3', () => {
  const one = { dimension: 'D1', level: 3, citations: ['Lot.java:1 — x'],
                counter_evidence: ['none found'], rationale: 'ok' };
  assert.equal(validateScore(one, FILES).ok, false);
  const two = { ...one, citations: ['Lot.java:1 — x', 'PricingPolicy — y'] };
  assert.equal(validateScore(two, FILES).ok, true);
});

test('validateScore rejects a level outside 0-3 and an unknown dimension', () => {
  assert.equal(validateScore({ dimension: 'D1', level: 5, citations: [] }, FILES).ok, false);
  assert.equal(validateScore({ dimension: 'D9', level: 1, citations: [] }, FILES).ok, false);
});

test('validateScore does not demand citations at level 0', () => {
  assert.equal(validateScore({ dimension: 'D1', level: 0, citations: [], rationale: 'absent' }, FILES).ok, true);
});

test('weightedLevel combines dimension levels by weight', () => {
  const all2 = { D1: 2, D2: 2, D3: 2, D4: 2, D5: 2 };
  assert.equal(weightedLevel(all2), 2);
  const mixed = { D1: 3, D2: 2, D3: 1, D4: 2, D5: 2 };
  assert.ok(weightedLevel(mixed) > 1.9 && weightedLevel(mixed) < 2.2);
});

test('verdictFor is no-hire when any single dimension is zero, whatever the total', () => {
  assert.equal(verdictFor({ D1: 3, D2: 3, D3: 3, D4: 0, D5: 3 }).verdict, 'no-hire');
  assert.match(verdictFor({ D1: 3, D2: 3, D3: 3, D4: 0, D5: 3 }).reason, /D4/);
});

test('verdictFor is hire at level 2 across the board', () => {
  assert.equal(verdictFor({ D1: 2, D2: 2, D3: 2, D4: 2, D5: 2 }).verdict, 'hire');
});

test('verdictFor is unscored when a dimension could not be scored', () => {
  assert.equal(verdictFor({ D1: null, D2: 2, D3: 2, D4: 2, D5: 2 }).verdict, 'unscored');
});

test('buildRubricPrompt embeds the anchors and demands the JSON shape', () => {
  const p = buildRubricPrompt({ dimension: 'D3', files: FILES, problemTitle: 'Parking Lot' });
  assert.match(p, /D3/);
  assert.match(p, /counter_evidence/);
  assert.match(p, /Parking Lot/);
  assert.match(p, /Lot\.java/);
  assert.match(p, /over-abstraction|premature|speculative/i,
    'D3 level 3 must penalise speculative interfaces, not just reward patterns');
});
```

- [ ] **Step 2** — confirm failure

- [ ] **Step 3: Implement `server/lib/rubric.mjs`**

Transcribe the anchors from `docs/specs/2026-08-18-standard-and-syllabus.md` Part 1 **verbatim** into
an `anchors` field per dimension. Structure:

```javascript
export const STANDARD_VERSION = '1.0';

export const DIMENSIONS = [
  { id: 'D1', title: 'Code design & structure', weight: 0.30, scoredBy: 'llm',
    anchors: { 0: '...', 1: '...', 2: '...', 3: '...' } },
  { id: 'D2', title: 'Working functionality', weight: 0.25, scoredBy: 'deterministic', anchors: {...} },
  { id: 'D3', title: 'Design patterns & SOLID', weight: 0.20, scoredBy: 'llm', anchors: {...} },
  { id: 'D4', title: 'Extensibility', weight: 0.15, scoredBy: 'deterministic', anchors: {...} },
  { id: 'D5', title: 'Edge cases & error handling', weight: 0.10, scoredBy: 'llm', anchors: {...} },
];
```

Then:

- `validateCitations(citations, files)` — parse `Path.java:N` or a bare `TypeName`; verify the file
  exists among `files` and `1 <= N <= lineCount`. Return `{ok, invalid[]}`.
- `validateScore(score, files)` — dimension known, level in 0..3, citations valid, **`counter_evidence`
  non-empty for levels 2–3**, **two citations minimum at level 3**, citations not required at level 0.
- `weightedLevel(levels)` — weighted mean over the five dimensions.
- `verdictFor(levels)` — `unscored` if any level is `null`; `no-hire` if any level is `0`, naming the
  dimension; `hire` if weighted ≥ 2; otherwise `no-hire`.
- `buildRubricPrompt({dimension, files, problemTitle})` — embeds the dimension's four anchors verbatim,
  the submission with line numbers, and the required JSON shape including `counter_evidence`.

- [ ] **Step 4** — confirm 14 tests pass. **Step 5** — snapshot `phases-5-rubric`

---

## Task 6: Phase 5 (defence) and Phase 7 (full round)

**Files:** `server/lib/grade-phase5.mjs`, `server/lib/grade-phase7.mjs`, tests.

**Phase 5** — four question archetypes, graded against the problem's `DECISION_LOG.md`:
`why-this-seam`, `how-would-you-add-X`, `what-breaks-under-concurrency`, `where-is-the-race`.
Requires T1/T2 (prose). Falls back to self-marking against the decision log, labelled `reduced`.

**Phase 7** — assembles a full verdict:
1. D2 from `grade-phase3`, D4 from `grade-phase4` — deterministic, never overridden
2. D1, D3, D5 from `askJson` with `buildRubricPrompt`, each validated by `validateScore`
3. **On invalid citations, re-request that dimension exactly once**, then set it `null` → `unscored`
4. `verdictFor()` produces hire / no-hire / unscored
5. **Drift sampling:** if `attemptId % 6 === 0`, grade D1/D3/D5 twice and record both

Tests must cover: deterministic dimensions are never overridden by a contradicting model score; one
retry then `unscored`; a single 0 forces no-hire; drift rows are written on sampled attempts only.

- [ ] Snapshot `phases-6-defence-round`

---

## Task 7: Syllabus tracking — demonstrated and held

**Lane C.** **Files:** `server/lib/syllabus.mjs`, test, plus a db migration.

- [ ] **Step 1: Test**

```javascript
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { stateOf, HELD_GAP_MS } from '../lib/syllabus.mjs';

const DAY = 24 * 60 * 60 * 1000;

test('an item with no demonstrations is not yet started', () => {
  assert.equal(stateOf([]), 'not-yet');
});

test('a single level-2 demonstration is demonstrated, not held', () => {
  assert.equal(stateOf([{ level: 2, at: 0 }]), 'demonstrated');
});

test('a level-1 demonstration does not count at all', () => {
  assert.equal(stateOf([{ level: 1, at: 0 }]), 'not-yet');
});

test('two level-2 demonstrations at least 3 days apart are held', () => {
  assert.equal(stateOf([{ level: 2, at: 0 }, { level: 2, at: 3 * DAY }]), 'held');
});

test('two demonstrations on the same evening are not held — that is luck, not retention', () => {
  assert.equal(stateOf([{ level: 2, at: 0 }, { level: 3, at: 2 * 60 * 60 * 1000 }]), 'demonstrated');
});

test('HELD_GAP_MS is three days', () => {
  assert.equal(HELD_GAP_MS, 3 * DAY);
});
```

- [ ] **Step 2–3:** implement `stateOf(demonstrations)`; add tables `syllabus_items`, `problem_syllabus`,
  `demonstrations`, `drift`, and extend `grades` with `level`, `citations`, `counter_evidence`,
  `standard_version`. Use `CREATE TABLE IF NOT EXISTS` and `ALTER TABLE ... ADD COLUMN` guarded by a
  `PRAGMA table_info` check, so existing databases migrate without loss.

- [ ] **Step 4** — snapshot `phases-7-syllabus`

---

## Task 8: The evidence ledger

**Files:** `server/lib/ledger.mjs`, test.

Every claim is computed from stored attempts with an inspectable number — **never asserted**.

- `timeToFirstCompilingSkeleton(attempts)` — the headline metric; returns a trend, first-5 vs last-5 median
- `curveballDiffTrend(attempts)` — median `diff_lines` over time
- `finishRate(attempts)` — green inside the clock, first-5 vs last-5
- `failureModeLedger(tags)` — counts per tag, with first-half vs second-half deltas
- `claims(...)` — an array of `{claim, evidence, numbers}` ready for the UI
- `invalidRuns(attempts)` — counted **separately** and excluded from every failure statistic, per the
  deterministic-contract spec: a mechanical problem is never a judgement about ability

Tests must assert: an empty history produces **no naked zeros** but an actionable next step; INVALID
attempts never appear in failure counts; a trend needs at least 3 points before it is reported at all.

- [ ] Snapshot `phases-8-ledger`

## Task 9: Readiness

**Files:** `server/lib/readiness.mjs`, test.

Per company mode: rounds attempted, hire verdicts, consecutive hires, whether the mode's requirements
were exercised (concurrency for Salesforce, curveball absorbed for Atlassian). Readiness requires
**four consecutive hire verdicts across at least three different modes on sealed problems**, plus every
syllabus item at `held`. Honest about gaps — a mode with zero attempts says so plainly.

- [ ] Snapshot `phases-9-readiness`

---

## Self-review

**Spec coverage.** All five STANDARD dimensions are implemented with their documented weights; D2 and D4
are deterministic (Tasks 2, 3) and structurally cannot be overridden by a model, because Task 6 assembles
them from the deterministic graders rather than asking for them. Citation validation, mandatory
`counter_evidence`, the two-citation rule at level 3, one retry then `unscored`, and drift sampling are
all in Tasks 5–6. The 34-item syllabus with demonstrated/held is Task 7. All five company modes are
Task 1.

**The invariant repeated in four places on purpose.** `INVALID` is never `no-hire`. It appears in
`gradeSkeleton`, `gradeCoreBuild`, `gradeExtension` and `gradeConcurrency`, and `ledger.mjs` excludes
INVALID runs from every failure statistic. A compile or binding problem must never become a judgement
about his ability — that is the whole basis of trusting a deterministic judge.

**One anchor deliberately asymmetric.** D3 level 3 requires a *minimal* seam set — a speculative
interface with one implementation and no foreseeable second scores lower, not higher. A test asserts the
prompt says so, because a rubric that only rewards patterns would train exactly the over-abstraction the
standard means to penalise.

**Known gap.** `functionalityLevel` returns 1 for any partially-green suite, so the 0.5 threshold in the
anchor text is currently cosmetic. Kept because the anchor's *intent* is "partial credit is level 1"; if
finer resolution is wanted later it is a one-line change with a new test.
