feat(registry): runtime Standard Schema validation (insert/update/command)#20
Merged
Conversation
9723ad9 to
9a21bc4
Compare
c0e6325 to
9de32dd
Compare
…rfacing (ADR-0014) Wire the per-op schema slots to validate at runtime: insert.schema checks the full-row cols, update.schema the partial patch, a command's schema its args — each in authorize, before the write, throwing to reject (fail loud). A delete carries no cols. It is a gate, not a parser: the original value flows to handlers, so transforms/defaults/coercion are not applied. Validation failures throw `ValidationError` and surface to the client with a `VALIDATION` code. Error surfacing is now uniform for mutations and commands: authorize and validation errors surface (the reason reaches the client), execute errors stay sanitized. handleCall's authorize is split from its execute, revising ADR-0012 D3, which had sanitized a command's authorize too. Tests drive a `validated` collection and a `requireBody` command via a hand-rolled Standard Schema (no validator dep): bad insert/update/args are rejected with the reason + VALIDATION code, good ones commit; a command execute throw stays generic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DxmkLhbtb5w7oHHiJKprr
recipes/zod-standard-schema-collections.md: how to attach a schema to a collection (insert plus a partial update) and a command, what is checked, and the gate-not-parser rule. Written in the project's plain style. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DxmkLhbtb5w7oHHiJKprr
end-to-end-types.md was authored on feat/sync-schema, where the schema only infers types. On this branch runtime Standard Schema validation is wired (5031f7c), so the "runtime check is a follow-up" note no longer holds: the schema infers the type and validates the value. Re-link the validation recipe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DxmkLhbtb5w7oHHiJKprr
9de32dd to
8142df3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #19 (base branch is
feat/sync-schema, notmain). Review #19 first; this PR's diff is only the validation layer.What
The base PR ships the object schema with the Standard Schema slots typed but not enforced at runtime (a tombstone). This PR wires the runtime gate:
insert.schemachecks the full-rowcols. It also infers the collection'sRow, so you drop the<Row>generic.update.schemachecks the partial patch. You pass a partial schema (e.g.Message.partial()) because an update sends only the changed fields. This is the "go further" piece — a full-row schema would reject every valid partial.schemachecksargs.Each runs in
authorize, before the write, and throws to reject the frame. It is a gate, not a parser (ADR-0014): the original value flows to handlers, so transforms/defaults/coercion are not applied — rewriting an optimistically-applied row would manufacture divergence.Any
~standardlibrary works (zod 3.24+, valibot, arktype); the framework adds no validator dependency.Schema home
The row schema lives on
mutations.insert.schema(it validates inserts and typesRow), per the discussion on #19 — the collection's "default" schema was only ever used by insert. Inference from the nestedinsert.schemawas proven with atsc --strictspike before implementing.Tests
tests/schema-validation.test.tsdrives avalidatedcollection and arequireBodycommand through a hand-rolled Standard Schema (no validator dep): bad insert/update/args are rejected (mutation rejections carry the validation message; command rejections are generic per ADR-0012), good ones commit.Verification
tsc --noEmit: 0 errors.vitest run: 177/177 (171 base + 6 new).Recipe
recipes/zod-standard-schema-collections.mddocuments it (insert + partial update + command, the gate rule), in the project's plain style.🤖 Generated with Claude Code