Repository navigation
refactor(sections): read every schema through one public-API adapter instead of three private walkers - #442
Merged
Merged
Conversation
…instead of three private walkers
Four walkers each reached into zod's private `_zod.def` through a hand-mirrored def view, carried their own "unhandled schema type" BUG throw and leaf whitelist, and cast through `unknown`: `projectOntoSchema` and `replaceSweep` (snapshot-helpers), `canonicalNode`/`admits` (engine/canonical), and `loosen`/`detectKnobUnion`/`cloneWith` (contract/module). Four test-side copies and the JSON schema generator read the same internals.
One adapter now does the reading, through zod's public classes and `.def`: `schemaNode(schema: z.ZodType): SchemaNode` in src/sections/shared/schema-node.ts. Its closed union:
- object: shape, catchall, open (a catchall other than never; a strip object stays closed to the walks)
- array: element
- record: value
- wrapper: inner plus the kind (optional, nullable, default, prefault, catch, nonoptional, readonly)
- union: options plus knob, the knobbed()/layeredList() pair when the union is one (same detection as the deleted detectKnobUnion)
- leaf: string, number, boolean, null, unknown, never, enum, literal, with an enum's or literal's values (duplicates kept, so a seeded draw is stable)
Every walker consumes SchemaNode: the two snapshot helpers, the two canonical walkers, loosen (which now writes through public `z.clone(node.schema, { ...node.schema.def, patch })`, typed against the narrowed class), the canonical completeness test, the slice identity test, the raw-sibling walk, the fuzz generator, and the generator override. `defOf`, `ZodDef`, `cloneWith`, `detectKnobUnion`, and the four test-side def views are deleted. The one BUG throw and the one leaf whitelist live in the adapter; `loosen` keeps only its own-refinements-on-a-knob BUG.
Behavior on the shipped schemas is unchanged: 2174 tests pass, the e2e suite passes 370/370, and lib/settings.schema.json built from this tree is byte-identical (cmp) to a build at the base branch. The one test edit is the expected text of the moved unhandled-type BUG in test/sections/loosen.test.ts.
Two deliberate readings the old walkers did not share. The "int" leaf entry is gone: `z.int().type` is "number" in zod 4, and a walk over every node of SettingsFile met exactly these types: array, boolean, default, enum, literal, never, null, nullable, number, object, optional, record, string, union, unknown. The four extra wrappers (prefault, catch, nonoptional, readonly), absent from that census, are looked through by every walk where before two walkers threw, two fell through, and the generator looked through.
`_zod` census over src/, .github/scripts/, and test/: 6 hits before, 0 after.
schemaNode() in src/sections/shared/schema-node.ts is now the one place that recognizes the knobbed()/layeredList() union by the wrapper's entries; loosen() and engine/canonical.ts consume its union.knob instead of detecting the pair themselves.
Contributor
File size check0 over a hard cap (fails), 33 warning(s).
Split the file, wrap the line, shorten or exempt the comment, or list the path in 5 managed file(s) skipped; repo-platform owns them. |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The adapter migration is consistent across production, generation, and test paths with no unresolved defects found.
Review effort: Balanced
Findings: None
What changed in this PR
Centralizes Zod schema traversal behind the public schemaNode() adapter while preserving schema generation and runtime behavior.
Changes:
- Adds a closed schema-node representation.
- Migrates projection, canonicalization, loosening, generation, and test walkers.
- Removes private
_zodaccess.
| File | Description |
|---|---|
.github/scripts/gen-settings-schema.ts |
Uses the adapter during JSON Schema generation. |
src/engine/canonical.ts |
Migrates canonical traversal. |
src/sections/contract/module.ts |
Reworks loosen() around public cloning. |
src/sections/shared/schema-helpers.ts |
Updates adapter-related documentation. |
src/sections/shared/schema-node.ts |
Adds the centralized schema adapter. |
src/sections/shared/snapshot-helpers.ts |
Migrates projection and replace-sweep traversal. |
test/e2e/gen-support.ts |
Migrates slice-based generators. |
test/engine/canonical.test.ts |
Migrates canonical schema census helpers. |
test/schema-slices.test.ts |
Validates slice composition through the adapter. |
test/sections/loosen.test.ts |
Updates the centralized invariant expectation. |
test/sections/raw-sibling.test.ts |
Migrates raw-sibling schema walkers. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Before
After
How
schemaNode(), maps every schema into a closed union (object, array, record, wrapper, union, leaf) from zod's public.defand class checks; the one unhandled-type invariant and the one leaf whitelist live there.loosen(), which now writes through publicz.clonewith a typed def.Proof
_zodreferences 6 to 0 across source, scripts, and tests.Technical details
open()replacingloosen()) follow..catch()and the old walkers disagreed on them;openmeans a catchall is declared and notnever, which every old walker tested; the unreachable"int"leaf entry is dropped (z.int().typeis"number"), with the slice walk census in the commit body.unknown.toJSONSchemacallbacks instead of the generator override's null arm.src/sections/shared/{schema-node,snapshot-helpers,schema-helpers}.ts,src/engine/canonical.ts,src/sections/contract/module.ts,.github/scripts/gen-settings-schema.ts,test/{engine/canonical,schema-slices,sections/raw-sibling,sections/loosen}.test.ts,test/e2e/gen-support.ts.