Audit — P1.5.9 Model Candidates Table Population
Task: P1.5.9 — Phase 1.5 graduation Wave 1 (foundation).
Branch: feature/p1-5-9-candidates
Base: origin/main @ c44e709f (post-R91 seal).
Audit date: 2026-05-13.
Audit scope: the mcp_model_candidates table — its existence, schema, current seed, and the column shape required by the staging file and concept doc.
1. Goal of the audit
Confirm or refute the claim made by both
docs/3-world/social/llm.md §”Phase 0 posture” and
the P1.5.9 slice of
docs/guides/implementation/task-prompts/p1.5-delta-router-graduation.md
that the mcp_model_candidates table is “schema-ready in Phase 0,
populated with a single row (Claude Sonnet).” If true, P1.5.9 is a
seed-only migration. If false, P1.5.9 must also create the table.
2. Method
- Grep the entire
src/tree formcp_model_candidatesandmodel_candidates. - List
src/db/migrations/and inspect each.sqlfile for anyCREATE TABLEmatching the candidate table. - Read
src/db/schema.sqlend-to-end for the column shape. - Read
src/db/index.tsto confirm the migration-runner convention (numbering, idempotency, transactions,PRAGMA user_version). - Cross-reference with the concept-doc column list and the staging file’s acceptance criteria.
3. Findings
3.1. The table does not exist in any shipped migration
| Migration | Tables introduced | Has mcp_model_candidates? |
|---|---|---|
001_init.sql |
(empty / comment-only baseline) | No |
002_tasks.sql |
tasks, task_dependencies |
No |
003_thought_records.sql |
thought_records, audit_events |
No |
004_skills.sql |
skills |
No |
005_retention.sql |
ALTER thought_records (retention cols) |
No |
006_eta.sql |
merkle_roots, audit_sessions, etc. |
No |
007_reputation.sql |
reputations, reputation_history |
No |
008_experience_tokens.sql |
experience_tokens, mcp_witnesses |
No |
Grep result (across src/):
$ rg 'mcp_model_candidates|model_candidates' src/
# (no matches)
The next free migration number is 009.
3.2. The concept doc and staging file both claim the table is “schema-ready” — that claim is incorrect against current code
docs/3-world/social/llm.md:196says “mcp_model_candidatestable is defined and populated with a single row (Claude Sonnet); the table is not mutated by any Phase 0 code path.”- The staging file slice for P1.5.9 says “Claude 3.5 Sonnet (already
present)” and that it depends on “Phase 0 schema (
mcp_model_candidatestable columns present).”
Both are spec aspirations that were never realised in code. R75 Wave I
shipped only the in-memory δ stubs (src/domains/router/scoring.ts +
fallback.ts) — no SQL migration accompanied them.
3.3. Implication for P1.5.9 scope
Because the table is absent, P1.5.9 must:
- Create the table in migration
009_model_candidates.sql. - Seed all 8 rows in that same migration (1 Claude Sonnet at
enabled = TRUE, 7 others atenabled = FALSE).
This is still inside the staging file’s scope envelope: the acceptance
criteria explicitly say “8 total rows in mcp_model_candidates
post-migration” and the embedded agent prompt says “INSERT OR IGNORE 7
rows” alongside the existing Claude Sonnet row. The only delta vs the
slice text is that we cannot inherit an in-place Claude Sonnet row —
we insert it as part of this migration. The staging file’s claim that
the Sonnet row is “already present” is corrected by this audit; the
post-condition (8 rows, 7 disabled) is unchanged.
3.4. Column shape — sources reconciled
The staging file’s acceptance criteria name these 7 columns plus
enabled:
| Column | Type | Source |
|---|---|---|
model_id |
TEXT PRIMARY KEY |
Concept doc + staging file |
provider |
TEXT NOT NULL |
Concept doc + staging file |
context_window_tokens |
INTEGER NOT NULL |
Concept doc + staging file |
latency_tier |
TEXT CHECK IN ('fast','balanced','slow') |
Concept doc + staging file |
cost_bps_per_kilotoken |
INTEGER NOT NULL |
Concept doc + staging file |
domain_fit_profile |
INTEGER NOT NULL (bitmask over 8 ξ domains) |
Concept doc §”candidate table” + identity.md §”The eight domains” |
enabled |
INTEGER NOT NULL DEFAULT 0 (SQLite has no BOOLEAN) |
Concept doc + staging file |
p50_latency_ms is not a table column — the concept doc has no such
field, and the cost/aggregates slice (P1.5.5) describes it as an
in-memory ring-buffer aggregate, not a persisted column.
The 8-bit domain_fit_profile bitmask layout comes from
docs/3-world/social/identity.md:85 §”The eight domains”:
| Bit | Domain | Meaning |
|---|---|---|
0 (0x01) |
BUILDER | Produces new artifacts |
1 (0x02) |
JUDGE | Evaluates artifacts |
2 (0x04) |
INVESTOR | Allocates resources |
3 (0x08) |
MENTOR | Guides others |
4 (0x10) |
GUARDIAN | Enforces rules |
5 (0x20) |
INNOVATOR | Proposes new rules |
6 (0x40) |
DIPLOMAT | Mediates |
7 (0x80) |
STEWARD | Curates state |
0xFF = all 8 domains; 0x00 = none.
3.5. Cohort table from the concept doc
docs/3-world/social/llm.md:79-89 §”Phase 1.5 candidate cohort”:
| Model | Window | Cost (indicative bps) | Latency tier | Strengths |
|---|---|---|---|---|
| Claude 3.5 Sonnet | 200k | high | balanced | code review, structured output, long-context reasoning |
| Claude 3.5 Haiku | 200k | low | fast | classification, triage, high-throughput |
| GPT-4o | 128k | high | balanced | multimodal, general reasoning |
| GPT-4o mini | 128k | low | fast | simple tasks, cheap fallback |
| Gemini 1.5 Pro | 1M | medium | slow | huge-context synthesis, video |
| Llama 3.3 70B | 128k | low (self-host) | varies | data-sovereign deploys |
| Mixtral 8x22B | 64k | low | fast | permissive open-weight, code-heavy |
| Kimi K2 | 200k | medium | balanced | Chinese/English parity, long-context |
The concept doc uses qualitative cost buckets (“high / medium / low”).
The staging file says “use indicative cost_bps values from the concept
doc ‘indicative’ ranges — the live post-task callback refreshes them.”
The contract step (P1.5.9 Step 2) will pin numeric values inside the
qualitative buckets and document the choice. Llama’s “varies” latency
maps to balanced (consistent with the staging file’s row table at
L1332: 'balanced').
3.6. Domain-fit bitmask mapping (preliminary)
This pre-implementation table is the input to Step 2 (contract) and will be ratified there. The strength descriptions in the concept doc are mapped to ξ domains:
| Model | Strengths (concept doc) | Domains (8-bit bitmask) | Hex |
|---|---|---|---|
| Claude 3.5 Sonnet | code review, structured output, long-context | BUILDER+JUDGE+MENTOR+STEWARD | 0x8B = bit 0,1,3,7 |
| Claude 3.5 Haiku | classification, triage, high-throughput | JUDGE+DIPLOMAT | 0x42 = bit 1,6 |
| GPT-4o | multimodal, general | BUILDER+JUDGE+INNOVATOR | 0x23 = bit 0,1,5 |
| GPT-4o mini | simple tasks, cheap fallback | BUILDER+DIPLOMAT | 0x41 = bit 0,6 |
| Gemini 1.5 Pro | huge-context synthesis, video | STEWARD+INNOVATOR+JUDGE | 0xA2 = bit 1,5,7 |
| Llama 3.3 70B | data-sovereign, customization | GUARDIAN+BUILDER+STEWARD | 0x91 = bit 0,4,7 |
| Mixtral 8x22B | open-weight, code-heavy | BUILDER+INVESTOR | 0x05 = bit 0,2 |
| Kimi K2 | CN/EN parity, long-context | BUILDER+MENTOR+DIPLOMAT | 0x49 = bit 0,3,6 |
Bitmasks are advisory starting points; the post-task callback in the real Phase 1.5 activation will refine them. They are deterministic and non-zero per-model so scoring (P1.5.1) can read non-trivial values.
3.7. Indicative cost bps assignments
Cost bucket → numeric mapping (Step 2 will pin and justify):
| Bucket | bps per 1k tokens | Models |
|---|---|---|
| high | 300 | Claude 3.5 Sonnet, GPT-4o |
| medium | 125 | Gemini 1.5 Pro, Kimi K2 |
| low (frontier mini) | 80 | Claude 3.5 Haiku |
| low (self-host) | 50 | Llama 3.3 70B |
| low (open-weight) | 60 | Mixtral 8x22B |
| low (frontier mini) | 15 | GPT-4o mini |
These are bps per kilotoken, i.e. for 1000 tokens. A bps is 0.0001 USD.
So 300 bps/1k = $0.030/1k = $30/1M. All chosen values are within
the publicly published rate-card ranges, and per the spec are
indicative — the post-task callback updates the table.
3.8. Migration-runner conventions (relevant to Step 3 packet)
From src/db/index.ts:
- Numeric prefix
NNN_*.sqldiscovered via regex; gap-free ordering is not required, only ascending unique versions. - File is read, line-comment-stripped, and
db.exec‘d inside a transaction; transaction also bumpsPRAGMA user_version = NNN. - Empty-body files (comment-only) are allowed (the body-length check
short-circuits
db.exec). - The runner is idempotent: any migration whose
version <= currentVersionis skipped. - Block comments (
/* ... */) are not stripped — only--line comments. The migration we write will use line comments throughout.
The migration must therefore be:
- Filename:
009_model_candidates.sql. - Body: a
CREATE TABLEfollowed byINSERT OR IGNORErows. - Comments:
--line comments only.
3.9. Idempotency mechanics
The staging file says “Migration is idempotent (INSERT OR IGNORE on
primary key).” Two layers of idempotency apply:
- Migration-runner-level:
PRAGMA user_versionskips applied migrations on a subsequent boot. Once we ship009, a re-boot never re-executes the file. - Statement-level: if the runner is forced to re-execute (e.g. a
fresh DB),
CREATE TABLE(withoutIF NOT EXISTS) fails on the second run. We will useCREATE TABLE IF NOT EXISTSplusINSERT OR IGNOREso the migration is self-idempotent independent of theuser_versionshield, matching the staging file’s stated acceptance criteria.
3.10. Reversibility
The staging file requires a DOWN section. SQLite migrations in
Phase 0 are forward-only; the runner has no DOWN interpreter. The
convention in the codebase (read 002 / 007 / 008) is that the DOWN
SQL is preserved as a commented-out block at the bottom of the
migration file so a future operator can run it manually with
sqlite3 if a rollback is needed. We follow that convention.
4. Surface that this task touches
- CREATE:
src/db/migrations/009_model_candidates.sql. - CREATE (tests):
src/__tests__/db/migrations/009_model_candidates.test.ts. - CREATE (chain docs):
docs/audits/,docs/contracts/,docs/packets/,docs/verification/files for P1.5.9. - No changes to
src/db/schema.sql(per the audit, no columns missing from the concept doc’s table). The schema-snapshot file atsrc/db/schema.sqlis a comment-only reference asset that is NOT passed todb.exec; updating it is not load-bearing. We may add aδ Model Candidates — introduced by 009_model_candidates.sqlblock to keep it in sync with reality, but the migration itself is the source of truth.
5. Pre-conditions (verified)
origin/main @ c44e709fchecked out cleanly to.worktrees/claude/p1-5-9-candidates.- Migrations 001–008 present; next free slot is 009.
src/domains/router/scoring.tsPhase 0 stub (Claude only) in place.src/domains/router/fallback.tsPhase 0 stub (single-member chain) in place.docs/3-world/social/llm.mdcohort table read in full.docs/3-world/social/identity.md8-domain list read in full.src/db/index.tsmigration-runner conventions captured.- No conflicts with downstream prompts — P1.5.1 (scoring) reads the table this seeds; the cohort, columns, and indicative ranges match what P1.5.1’s prompt expects.
6. Risk register
| Risk | Mitigation |
|---|---|
| Concept-doc cohort text ambiguous on bitmask | Pinned mapping in §3.6 + ratification in Step 2 contract |
| Indicative bps may drift before live activation | Spec explicitly authorises post-task callback to refresh |
INTEGER storage of bitmask + enabled boolean |
SQLite uses INTEGER for both; standard pattern (see 007/008) |
| Fresh DB + previously applied migration re-execution | CREATE TABLE IF NOT EXISTS + INSERT OR IGNORE |
enabled = TRUE on rows without adapters |
Only Sonnet ships enabled = 1; 7 others ship enabled = 0 |
| Test runner uses every migration | db-init.test.ts:166 asserts user_version === countRealMigrations(); adding one migration auto-bumps both sides — no fixture change required |
7. Pre-flight reading
CLAUDE.md§6 (5-step chain).src/db/index.ts(migration runner).src/db/schema.sql(schema reference asset).src/db/migrations/007_reputation.sql(recent migration template).src/db/migrations/008_experience_tokens.sql(most-recent migration template).docs/3-world/social/llm.md§”The candidate table” + §”Phase 1.5 candidate cohort”.docs/3-world/social/identity.md§”The eight domains”.docs/guides/implementation/task-prompts/p1.5-delta-router-graduation.md§P1.5.9 (slice starting at L1269).
8. Outcome of audit
P1.5.9 will:
- Create migration
009_model_candidates.sql. - Create the
mcp_model_candidatestable with the 7 columns +enabled(8 columns total). - Seed 8 rows: 1 Claude Sonnet at
enabled = 1+ 7 candidates atenabled = 0. - Make the migration self-idempotent (
CREATE TABLE IF NOT EXISTS,INSERT OR IGNORE). - Preserve a
DOWNblock in commented form. - Land Jest tests that assert: table exists, 8 rows present, 7 of them
enabled = 0, exactly Claude Sonnet hasenabled = 1, cohort values match this audit’s tables, migration is idempotent under re-execution. - Touch nothing in
src/domains/router/(scoring/fallback continue to return their Phase 0 constants; that’s P1.5.1’s job). - Add zero MCP tools (router tools are P1.5.7’s job).