Skip to content

WP6 E2 Ratifier — Implementation Compliance Verdict

WP6 E2 Ratifier — Implementation Compliance Verdict

Section titled “WP6 E2 Ratifier — Implementation Compliance Verdict”

Sub-agent: WP3 E2 Implementation Ratifier (S238). Inputs verified:

  • Spec — docs/specs/wp6-ontology-harness/TECH.md §5 (file-by-file plan), §6 (frontmatter Zod shape), §7 (test plan), §9 (acceptance criteria), §10 (implementer instructions).
  • Source-of-truth contract — docs/ontology/04-content-type.md (15 baseline_values), docs/ontology/README.md (frontmatter shape).
  • Implementation (5 NEW + 4 EDIT) — lib/ontology/{schemas,loader,content-type-registry}.ts, lib/ontology/content-type-values.generated.ts, scripts/generate-content-type-values.ts, __tests__/lib/ontology/markdown-parity.test.ts, scripts/ontology-sync/parse-flow.py, lib/validation/schemas.ts, lib/validation/ingest-schemas.ts, scripts/generate-codebase-stats.ts, .knip-baseline.json, package.json.
  • Live exercise — bun run test __tests__/lib/ontology/markdown-parity.test.ts (6 of 6 passing), bun run test __tests__/validation (1107 of 1107 passing), bun run test __tests__/scripts/generate-codebase-stats.test.ts (31 of 31 passing), bun run tsc --noEmit filtered to WP6 D1 scope (zero errors), Bun smoke load of loadOntologyCVs() (29 CVs, 15 content_type keys), schema rejection of malformed frontmatter (7 issues surfaced cleanly).
  • Branch context — content-items-investigation @ 57ad8dcc. Implementer commits: a258d144 (initial D1), 13f30993 (CI fix — Turbopack node:fs leak), 04684565 (stats + knip follow-up).

Method: Programmatic execution of the test suite, runtime smoke of the loader against the live MD register, schema-rejection probe with a hand-crafted malformed fixture, manual trace of every VALID_CONTENT_TYPES / CONTENT_TYPE_VALUES call site (8 production + 6 test), tsc filter to the WP6 D1 file scope, and adversarial read of the parity test against docs/reference/test-philosophy.md six audit criteria.


PASS-WITH-NOTES.

The WP6 D1 implementation ships the spec’s contract — Zod schema + loader + parity test + downstream consumer swap — and every behaviour invariant TECH.md §7 specified is exercised by a passing test against real markdown + the live DB snapshot. Six of six parity assertions pass deterministically; 1107 of 1107 validation tests pass. No silent-strip or mocked-the-thing-it-was-supposed-to-verify antipatterns present. The bidirectional set-equality assertion is implemented correctly (both directions) and the schema’s .strict() mode is wired and rejects unknown keys at load time.

Three deviations from TECH.md as authored warrant a NOTE rather than a FAIL:

  1. Registry architecture diverged from TECH.md §5.3 — the registry now re-exports a build-time-generated tuple (content-type-values.generated.ts written by scripts/generate-content-type-values.ts) rather than reading via loadOntologyCVs() at module import. Rationale (per commit 13f30993): Turbopack rejects external node:fs modules in client chunks; the chain loader → registry → validation/schemas → bid-context-provider leaked node:fs into the browser bundle. The codegen approach is sound, mirrors the existing generate:skills pattern, is wired into predev + prebuild + build:vercel, and the parity test still imports CONTENT_TYPE_VALUES to assert it matches both the markdown register AND the live DB snapshot — so end-to-end parity is preserved. But this is an architectural change the spec doesn’t yet acknowledge, and the spec (and ADR-grade documentation) should be updated to reflect it.
  2. Two reasonable D1 follow-up findings, both already absorbed by 04684565 — the content_types stats counter was correctly switched to - key: pattern counting in the MD register (no longer counting array entries in the soon-to-be-shim VALID_CONTENT_TYPES), and the knip baseline raised 47→52 to absorb the harness’s public surface. Both are documented in the commit message with a clear rationale; no further fix needed.
  3. loader.ts existsSync import is unused — the import statement import { readdirSync, readFileSync, existsSync } from 'node:fs'; lists existsSync but only existsSync(ONTOLOGY_DIR) is referenced (line 46), which IS a use. Re-checking: existsSync IS used at line 46. Withdrawing this note. (Retained in the report for transparency that the verifier looked.)

No fix-agent dispatch is required pre-merge. The NOTE in §1 (1) is documentation-debt for a later spec-edit wave or ADR; the parity test continues to enforce the load-bearing invariant.


Check 1 — Bidirectional parity assertion

Section titled “Check 1 — Bidirectional parity assertion”

Requirement (TECH.md §7 + §5.4 case 2): “every CV with editable_via=database_migration matches the live DB CHECK both ways” — set equality between markdown baseline_values[].key and taxonomy_snapshot.json entries, with both missingFromMD and missingFromDB arrays asserted empty. Plus a §5.4 case 3 spot-check that CONTENT_TYPE_VALUES.length === 15.

Observed: __tests__/lib/ontology/markdown-parity.test.ts lines 83-124 implement the bidirectional check exactly. Both missingFromMD = dbValues.filter(v => !mdSet.has(v)) and missingFromDB = mdKeys.filter(k => !dbSet.has(k)) accumulate into an errors array and the assertion is expect(errors).toHaveLength(0) with a structured-error preamble that names which direction is failing and which CV. The 15-value spot-check at lines 76-81 reads [...CONTENT_TYPE_VALUES].sort() vs [...(snapshot!.content_types ?? [])].sort() and asserts both equality AND length. Both directions are real assertions, not vacuous tautologies; both are exercised by the snapshot fixture at runtime.

Verdict: PASS. The 15 values cover both directions and the snapshot oracle is the live-DB-derived scripts/tests/fixtures/taxonomy_snapshot.json. Confidence: HIGH.

Requirement (TECH.md §5.3 + §10 build order step 6): Both lib/validation/schemas.ts:264 (ItemCreateBodySchema.content_type) and lib/validation/ingest-schemas.ts:11 (IngestUrlBodySchema.content_type) must swap z.enum(VALID_CONTENT_TYPES) to z.enum(VALID_CONTENT_TYPES as readonly [string, ...string[]]). The VALID_CONTENT_TYPES declaration itself must become a re-export of CONTENT_TYPE_VALUES.

Observed:

  • lib/validation/schemas.ts:6 imports CONTENT_TYPE_VALUES from @/lib/ontology/content-type-registry.
  • lib/validation/schemas.ts:52 defines export const VALID_CONTENT_TYPES = CONTENT_TYPE_VALUES; (re-export shim, behaviour-preserving).
  • lib/validation/schemas.ts:259 carries the cast: content_type: z.enum(VALID_CONTENT_TYPES as readonly [string, ...string[]]),.
  • lib/validation/ingest-schemas.ts:12-13 carries the cast: content_type: z.enum(VALID_CONTENT_TYPES as readonly [string, ...string[]]).optional(),.
  • 12 downstream consumers traced (app/item/new/create-content-client.tsx, app/api/items/[id]/route.ts, components/review/assignment-manager.tsx, lib/taxonomy/taxonomy.ts shim, __tests__/mcp/plugin-taxonomy-consistency.test.ts, __tests__/lib/content-templates.test.ts, __tests__/lib/intelligence/content-type-update.test.ts, __tests__/validation/schema-db-consistency.test.ts, __tests__/validation/validation.test.ts). All use the existing import path @/lib/validation/schemas and continue to compile / pass.
  • lib/taxonomy/taxonomy.ts:18 (export const CONTENT_TYPES = VALID_CONTENT_TYPES) re-exports cleanly.
  • No stale array literal of content-type values remains anywhere on the production path.

Verdict: PASS. Confidence: HIGH.

Requirement (TECH.md §9 acceptance criteria): bun lint zero new errors; full suite + typecheck green.

Observed: bun run tsc --noEmit on the project tsconfig reports zero errors filtered to lib/ontology/**, lib/validation/schemas.ts, lib/validation/ingest-schemas.ts. Pre-existing tsc errors in unrelated files (__tests__/lib/supabase/safe.test.ts, __tests__/lib/ai-parse.test.ts, two integration tests) are out of scope for WP6 D1 and pre-date the implementation. The as readonly [string, ...string[]] cast is applied at both call sites exactly as the spec requires; this is the canonical KH idiom (per lib/validation/layer-schemas.ts:53 precedent) and matches the Zod constructor’s [string, ...string[]] requirement.

Verdict: PASS. Confidence: HIGH.

Requirement (TECH.md §9): bun run test __tests__/lib/ontology — all parity tests pass; test must NOT pass vacuously (per docs/reference/test-philosophy.md criteria 1 + 5 + antipattern 3 conditional-false-pass).

Observed:

  • bun run test __tests__/lib/ontology/markdown-parity.test.ts → 6 of 6 tests pass; 393ms duration.
  • bun run test __tests__/validation → 20 files, 1107 of 1107 pass; 2.32s duration (proves the re-export chain wires end-to-end — __tests__/validation/schema-db-consistency.test.ts continues to pass via VALID_CONTENT_TYPES, per TECH.md §8 risk mitigation).
  • bun run test __tests__/scripts/generate-codebase-stats.test.ts → 31 of 31 pass (repairs the two assertions the stats follow-up commit addressed).
  • No mocks. No vi.mock. No createMockSupabaseClient. The test reads docs/ontology/*.md via real node:fs + real gray-matter, parses via the real Zod schema, and oracles against the real taxonomy_snapshot.json. The boundary crossings are intentional — the markdown files + the snapshot ARE what the test exists to prove are in sync.
  • Test titles read as product specs per criterion 5 (e.g. “every CV file parses and validates against the ontology schema”, “content_items.content_type lists exactly 15 values matching the live DB CHECK”).
  • The accumulate-and-report pattern (build a failures: string[] array, assert toHaveLength(0) with the joined error string as the preamble) mirrors __tests__/validation/schema-db-consistency.test.ts:72-88 per the spec instruction.
  • Honest-fail probe: hand-crafted malformed-frontmatter fixture (bad layer: 99, bad provenance_model: invalid, bad client_extensible: maybe, bad editable_via: rocket, bad key: 9bad-key, empty label, bad provenance: garbage) is rejected by the schema with all 7 issues enumerated cleanly. This proves the schema would FAIL the build on Drafter regression — the test is not a tautology.

Verdict: PASS. Confidence: HIGH.

Requirement (TECH.md §5.2 + §8 risk “Loader runs on every test file’s import → slow”): Synchronous read; throws fatally on schema violation with offending file path; caches across imports; throws clear error if ONTOLOGY_DIR is missing; one-pass module-load semantics.

Observed:

  • lib/ontology/loader.ts:46-51existsSync(ONTOLOGY_DIR) guard throws an explicit error with the absolute path and a Drafter-wave hint if the directory is missing. NOT silent fallback to [].
  • Lines 33, 44, 74 — cache flag let cached: readonly OntologyCV[] | null = null; set once and Object.freeze-d before return. The cached array is the SAME reference across all imports, eliminating repeat disk reads (per spec).
  • Lines 53-55 — readdirSync(ONTOLOGY_DIR).filter(name => name.endsWith('.md') && name !== 'README.md').sort() correctly excludes README and yields a deterministic ordering.
  • Lines 62-70 — Zod safeParse result drives an Error with the offending filename AND a formatted issue list (i.path.join('.') + i.message). Verified with smoke-test malformed fixture; messages surface cleanly.
  • Path resolution uses fileURLToPath(import.meta.url) + dirname() to anchor REPO_ROOT regardless of CWD — works under Vitest + direct Bun runs.

Verdict: PASS. Confidence: HIGH.

Requirement (TECH.md §5.3): Export CONTENT_TYPE_VALUES: readonly string[] derived from the MD register; throw if content_type CV is missing.

Observed: lib/ontology/content-type-registry.ts is now an 18-line re-export shim sourcing CONTENT_TYPE_VALUES from @/lib/ontology/content-type-values.generated. The generated file is built at predev / prebuild time by scripts/generate-content-type-values.ts, which (a) reads docs/ontology/04-content-type.md, (b) asserts cv_name === 'content_type' (throws otherwise), (c) requires non-empty baseline_values (throws otherwise), (d) writes the readonly tuple. The runtime export is readonly string[] per Object.freeze([…] as const) — wider than a literal-union but compatible with the two z.enum(... as readonly [string, ...string[]]) consumers.

The “find the content_type CV; throw if not found” check that TECH.md §5.3 originally located in the registry’s runtime-load path now lives in the codegen script (lines 50-54 of generate-content-type-values.ts). Equivalent guarantee, shifted left to build time. The parity test (case 1 + case 3) continues to assert the markdown register and the registry agree, so the codegen output is independently re-verified per test run.

Verdict: PASS-WITH-NOTES. The architecture diverges from TECH.md §5.3 (registry no longer reads via loadOntologyCVs()) because of the CI-unblock fix. The divergence is sound but unspecified — TECH.md should be amended to reflect the codegen approach. Concrete fix-agent dispatch criterion if the spec needs updating: amend TECH.md §5.3 to (a) declare the registry is a generated-file re-export, (b) point to scripts/generate-content-type-values.ts as the runtime, (c) acknowledge the Turbopack node:fs constraint as the rationale, (d) document the predev / prebuild wiring. Confidence: HIGH on the implementation correctness; HIGH on the spec-debt observation.

Requirement (TECH.md §6 verbatim): OntologyCVSchema.strict() covering cv_name + layer + provenance_model + client_extensible + editable_via + core_seed_path + status + baseline_values + related_layers; tuples as const; explicit z.literal union for layer; .min(1) on baseline_values; key regex accepts snake_case + kebab + TBD.

Observed: lib/ontology/schemas.ts:16-86 matches §6 verbatim:

  • PROVENANCE_VALUES, PROVENANCE_MODEL_VALUES, EDITABLE_VIA_VALUES, STATUS_VALUES, LAYER_VALUES all as const tuples per spec.
  • BaselineValueSchema.key regex ^([a-z][a-z0-9_-]*|TBD)$ accepts snake_case, kebab-case, AND the TBD sentinel. Verified against the live corpus (CV 19’s kebab keys + CV 14’s BID_STATES cv_name, see E1 ratifier §4.4 enumeration).
  • OntologyCVSchema.strict() is wired on line 83 — silently-strip-unknown-keys is the most subtle landmine in the spec and the implementer applied it.
  • Layer + related_layers use explicit z.union([z.literal(1)..z.literal(6)]) per spec (preserves literal-union narrowing for downstream switch consumers).
  • core_seed_path: z.string().min(1).nullable() (line 67) — .nullable(), NOT .optional() per spec; load-bearing distinction (missing-from-doc vs intentionally-null).
  • All 29 MD files in the live corpus parse + validate via loadOntologyCVs() smoke run (bun -e "..." reported count: 29, content_type keys: 15).
  • Type export OntologyCV derived via z.infer<typeof OntologyCVSchema> per spec.

Verdict: PASS. Confidence: HIGH.

Check 8 — Stats follow-up (commit 04684565)

Section titled “Check 8 — Stats follow-up (commit 04684565)”

Requirement (continuation prompt + commit message): scripts/generate-codebase-stats.ts content_types counter switches from countArrayEntries(VALID_CONTENT_TYPES) (which became a re-export shim and would return 0) to counting - key: patterns in docs/ontology/04-content-type.md. Repairs two failing assertions in __tests__/scripts/generate-codebase-stats.test.ts.

Observed: scripts/generate-codebase-stats.ts:171-173 reads:

content_types: countPatternsInFiles('docs/ontology/04-content-type.md', [
'- key:',
]),
  • Pattern count: head -56 docs/ontology/04-content-type.md | grep -c '^ - key:' returns 15. Matches the MD register and the snapshot.
  • bun run test __tests__/scripts/generate-codebase-stats.test.ts → 31 of 31 pass.
  • The assertion at __tests__/scripts/generate-codebase-stats.test.ts:173-175 (expect(stats.content_types).toBeGreaterThanOrEqual(10)) is satisfied (15 ≥ 10).
  • The MD file at docs/ontology/04-content-type.md IS the source-of-truth for this counter per CLAUDE.md S237 Critical Rule 1 — correct architectural alignment.

Verdict: PASS. Confidence: HIGH.

Check 9 — Knip baseline (commit 04684565)

Section titled “Check 9 — Knip baseline (commit 04684565)”

Requirement: Exports baseline 47→52 to absorb the harness’s 5 new public surfaces. Each addition must be intentional.

Observed: .knip-baseline.json:counts.exports = 52. The commit message names the five surfaces explicitly:

  • OntologyCVSchema in lib/ontology/schemas.ts.
  • loadOntologyCVs in lib/ontology/loader.ts.
  • CONTENT_TYPE_VALUES in lib/ontology/content-type-registry.ts.
  • VALID_CONTENT_TYPES re-export shim at lib/validation/schemas.ts:52.
  • One Zod-side strict-detection edge knip surfaces.

All five are wired end-to-end (consumers traced in Check 2). Knip flags cross-module re-exports as a false-positive shape under its strict detection; the baseline raise is the documented procedure per docs/runbooks/ci.md knip baseline note. No accidental cruft.

The newly-added content-type-values.generated.ts is NOT mentioned in the baseline note, but it does ship a CONTENT_TYPE_VALUES export that the registry re-exports — knip likely consolidates this into the same export-count bucket. NOT a defect; just an observation. If knip starts flagging the generated file as an additional unused surface in future CI runs, the baseline raise rationale already covers it.

Verdict: PASS-WITH-NOTES. The baseline raise is correct AND documented. Minor note: the codegen architecture (commit 13f30993) added content-type-values.generated.ts after the knip baseline was set; if CI ever flags it, the existing 47→52 rationale still holds. No fix-agent dispatch required. Confidence: HIGH.

Requirement (S237 Critical Rule 1 + CLAUDE.md “Taxonomy dual-source”): docs/ontology/04-content-type.md is source-of-truth; downstream consumers derive from it.

Observed:

  • lib/validation/schemas.ts:52 re-exports VALID_CONTENT_TYPES = CONTENT_TYPE_VALUES (which is generated from the MD).
  • lib/taxonomy/taxonomy.ts:18 re-exports CONTENT_TYPES = VALID_CONTENT_TYPES (transitively derived from the MD).
  • No file hardcodes the 15 values outside docs/ontology/04-content-type.md AND scripts/tests/fixtures/taxonomy_snapshot.json (which is itself derived from the live DB CHECK — the parity test asserts MD and DB agree, closing the loop).
  • The parity test imports CONTENT_TYPE_VALUES from the registry AND directly re-reads 04-content-type.md (line 170-174) to assert the loader actually crossed the fs boundary. Belt-and-braces guard against fixture-stub leaks.
  • The codegen file content-type-values.generated.ts carries a // AUTO-GENERATED ... Do not edit. header citing the MD source path — clear signal to future engineers.

Verdict: PASS. Confidence: HIGH.


CheckVerdictConfidence
1. Bidirectional parity assertionPASSHIGH
2. Downstream wiringPASSHIGH
3. TypeScript compilationPASSHIGH
4. Test execution + honestyPASSHIGH
5. Loader robustnessPASSHIGH
6. Registry shapePASS-WITH-NOTESHIGH
7. Schema validationPASSHIGH
8. Stats follow-up (04684565)PASSHIGH
9. Knip baseline (04684565)PASS-WITH-NOTESHIGH
10. CLAUDE.md alignmentPASSHIGH

Aggregate: 8 PASS, 2 PASS-WITH-NOTES, 0 FAIL. Overall verdict PASS-WITH-NOTES.


No fix-agent dispatch required pre-merge. Both PASS-WITH-NOTES findings are documentation-debt items, not defects:

  1. Spec amendment (Check 6 / Check 9 — combined fix): A later spec-edit wave (or a paired ADR) should update TECH.md §5.3 to record the codegen architecture chosen in commit 13f30993 (the Turbopack node:fs client-bundle leak that forced the loader→generated-file split). Concrete dispatch criteria if Liam wants this done now:
    • Edit docs/specs/wp6-ontology-harness/TECH.md §5.3 to declare the registry as a build-time-generated re-export shim, with the rationale (Turbopack rejects external node:fs in client chunks) cited.
    • Cross-link to scripts/generate-content-type-values.ts as the new build-time runtime.
    • Cross-link to the predev / prebuild / build:vercel wiring in package.json scripts.
    • Add an ADR-grade entry in docs/reference/ (or similar) capturing the architectural decision: “ontology registries that need to reach client bundles get codegen-inlined; loader stays server-only for tests + parity guard.”
    • Acceptance: future engineers reading TECH.md §5 alone can rebuild the registry from spec without surprise.

The parity test continues to enforce the load-bearing invariant (markdown ↔ DB CHECK ↔ registry) so the spec-debt does not put a downstream consumer at risk. Treating it as “doc reconciliation in a later wave” is the lowest-risk path.


FileLinesStatusCommit
lib/ontology/schemas.ts85NEWa258d144
lib/ontology/loader.ts76NEWa258d144
lib/ontology/content-type-registry.ts18NEW (modified)a258d144 (32 lines initial); refactored to re-export shim in 13f30993
lib/ontology/content-type-values.generated.ts27NEW (generated)13f30993
scripts/generate-content-type-values.ts90NEW13f30993
__tests__/lib/ontology/markdown-parity.test.ts177NEWa258d144
scripts/ontology-sync/parse-flow.py147NEW (stub)a258d144
lib/validation/schemas.ts1+ edited lines (imports + L52 + L259)EDITa258d144
lib/validation/ingest-schemas.ts1+ edited lines (L11-13)EDITa258d144
scripts/generate-codebase-stats.ts3 lines edited (counter swap)EDIT04684565
.knip-baseline.jsonexports 47→52EDIT04684565
package.json+ gray-matter dep + generate:content-type-values + predev/prebuild + build:vercel wiringEDITa258d144 + 13f30993
bun.lockdep tree updateEDITa258d144

Total new code: ~620 lines across 7 new files. Total edited surface: 6 existing files, small targeted changes.


Per-check confidence levels (HIGH / MEDIUM / LOW):

  • Check 1 (bidirectional parity) — HIGH. Direct read of the test file + live execution + manual trace of both missingFromMD and missingFromDB filters.
  • Check 2 (downstream wiring) — HIGH. Grep across the project surfaced all 12 consumer call sites; each traced.
  • Check 3 (TypeScript) — HIGH. tsc --noEmit filtered to WP6 D1 scope returned zero errors; pre-existing errors elsewhere unrelated.
  • Check 4 (test execution + honesty) — HIGH. Live test execution (1107 + 6 + 31 passing) + adversarial probe with a hand-crafted malformed fixture proved the schema would surface real failures cleanly.
  • Check 5 (loader robustness) — HIGH. Direct read of loader.ts + smoke bun -e runtime load + malformed-fixture probe.
  • Check 6 (registry shape) — HIGH on implementation, HIGH on architectural divergence observation. The codegen approach is correct; the spec drift is real.
  • Check 7 (schema validation) — HIGH. Direct read against TECH.md §6 verbatim + live exercise of all 29 MD files via the loader.
  • Check 8 (stats follow-up) — HIGH. Verified pattern count returns 15; test suite passes.
  • Check 9 (knip baseline) — HIGH on the count (52); MEDIUM on whether content-type-values.generated.ts would surface as an extra count under stricter knip detection in future. Not a current defect.
  • Check 10 (CLAUDE.md alignment) — HIGH. All consumers traced back to either 04-content-type.md directly or via the documented derivation chain.

End of E2 ratifier report. Verifier: WP3 E2 sub-agent (S238). Method: live test execution + tsc filter + Bun runtime smoke + adversarial schema-rejection probe + manual call-site trace + spec-vs-implementation diff. UK English. Branch content-items-investigation @ 57ad8dcc.