DR-138 — A runtime validator is minted for untrusted input, not for symmetry
DR-138 — A runtime validator is minted for untrusted input, not for symmetry
Section titled “DR-138 — A runtime validator is minted for untrusted input, not for symmetry”Context
Section titled “Context”lib/validation/owner-kind.ts (ID-151, bl-412) shipped a TypeScript union and
a Zod enum for each of three discriminator domains. Two of the three Zod enums
had no consumer at all.
The reason is legible in the task, not in the code. ID-151’s goal line reads: “Introduce a shared OwnerKind/CitedKind type + Zod enum and re-point call sites for compile-time safety.” The deliverable was specified as “type + Zod enum” uniformly, so all three domains got both halves — while the stated purpose, compile-time safety, is delivered entirely by the unions. bl-412’s triage rationale is explicitly “Bare string literals at ~20 call sites with DB CHECKs as backstop … explicitly non-regression.” The authoring commit is a crash rescue (“executor parked pre-finalize”), and the finalisation that would have pruned or wired the unused halves never ran.
S544 swept app/api/**, lib/**, scripts/**, the Python pipeline,
migrations, seeds, .rpc( sites, e2e/** and mcp-apps/ for a path where an
owner_kind or cited_kind value reaches a write without being one of our own
literals. None exists. No SQL function takes such a parameter. The single
client-supplied kind field on the API surface is ReviewActionBodySchema.owner_kind
— whose validator, FacetOwnerKindSchema, is wired and stays.
Mutation confirmed the unions are load-bearing: typo’ing a literal at
lib/intelligence/pipeline.ts:176 fails bun run typecheck, even though the
generated Insert type declares the column as bare string.
Decision
Section titled “Decision”A Zod enum is minted for a discriminator column when a value can reach it from outside our own source — a request body, a query param, an MCP or tool-call argument, a file import. Where every call site writes a literal, the TypeScript union is the whole guard and no runtime validator is added.
Deleted: RecordEmbeddingsOwnerKindSchema, CitedKindSchema. Retained:
FacetOwnerKindSchema (request-body input), and both TS unions.
Alternatives Considered
Section titled “Alternatives Considered”Keep them as defence in depth. Rejected: an unreachable validator is not depth, it is a claim of protection that no test exercises and no caller invokes. It also invites the inverse error — a reviewer seeing a Zod enum in the module may assume the column is validated at some boundary when it is not.
Keep them for symmetry across the three domains. Rejected: symmetry is what produced them. The three domains differ in the way that matters — one takes untrusted input and two do not — so uniform treatment obscures the distinction the module exists to draw.
Consequences
Section titled “Consequences”- Adding an untrusted-input path to
record_embeddings.owner_kindorcitations.cited_kindin future requires re-minting the validator at that boundary. Neither column is validated at runtime today, by design. - A related claim in the same module was measured and found false, and is
corrected in place rather than relied upon:
satisfies readonly CitedKind[]was documented as making a futureALTER TYPE … ADD VALUE“drift loudly”. It does not — adding a bogus label fails, but removing a real one passes silently, becausesatisfiesis a subset check, not an exhaustiveness one. Do not treat it as a drift alarm. - The exhaustiveness that does hold is at the consumer:
citedTargetForDraftItem’sneverarm stops compiling if the drafting-grain union grows a third member (verified — adding'concept'errors atdraft-response.ts:195). - ID-151 is
archived; this amends its output without reopening it.