fix(spec): project a union branch-by-branch, so five filter operators reach a published reference page - #17085
Conversation
…lter-operator-schema-projection
…lter-operator-schema-projection
…lter-operator-schema-projection
📓 Docs Drift Check
What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract review at
|
Part of #16431 — option (a) only. Option (c) landed as PR #16908; option (b) was ruled against by triage and is not attempted here. One item stays open on the card and is named at the bottom.
The premise the card recorded is FALSIFIED, and that is the first result
The card hypothesised that the existing
io: 'input'retry inbuild-schemas.tswould project az.date()branch if it were applied per branch rather than per schema, "since an author writes an ISO string". Measured against zod 4.4.3, that is false, and the reason is structural rather than incidental:dateProcessorinzod/v4/core/json-schema-processorsreads onlyctx.unrepresentableand neverctx.io, so the two directions are the same answer at every granularity. Per-branchio: 'input'publishes nothing.⇒ The route triage ruled — teach the generator to project
z.date()— is unaffected; only the mechanism the card guessed at is. The one switch zod offers isunrepresentable: 'any', and it is used here as a vehicle, never as the answer.What landed
A third projection attempt, reached only when both strict directions have already refused the whole export:
packages/spec/scripts/lib/union-branch-projection.ts, wired into the emission loop ofbuild-schemas.ts.unrepresentable: 'any', marking throughoverrideevery node that came back with no structural keyword.anyOf/oneOf.Step 3 is the load-bearing half. A bare
{}inside ananyOfaccepts every JSON value, so a build that merely setunrepresentable: 'any'would publish universally-permissive schemas for the exports skipped today and report nothing — a worse version of the silence this card is about. Nothing is ever emitted with a{}standing in for a type zod refused, and because the failure path re-throws zod's own error, this change cannot alter why anything is skipped, and so cannot alter anycauserecorded inunemitted-schemas.baseline.json.Two details that measurement forced, both pinned by tests:
z.any()andz.unknown()— and describes them through wrappers.FieldOperators.$eqisz.any().optional().describe(...), which comes back as{ description }, byte-shaped exactly like an unprojectablez.date().describe(...). A list-based detector marked it and refused the whole projection for the enforced half of the filter contract..transform()branch has no output form and a good input one; output-first would delete an authorable shape. Measured case:Data.HookSchema, where output drops the deprecatedz.customhandler AND apipe, and input drops only thez.custom.Why dropping the branch is not a narrowing
These artifacts describe JSON documents. A JSON document cannot carry a
DateINSTANCE, so the set of JSON documents thez.date()branch admits is empty, and an empty member of ananyOfcontributes nothing to the union's accept set. Removing it changes which JSON documents validate by exactly nothing. The ordering docblock insrc/data/filter.zod.tsstates the same thing from the runtime side: the date-macro resolver returns only strings, and the driver only ever sees ISO date / timestamp strings.⛔ No runtime accept set was narrowed.
packages/spec/src/data/filter.zod.tsis not in this diff —orderingComparandSchemaandrangeEndpointSchemaare byte-identical, which is what keeps this (a) and not (b).The published prose still names
Date— the reference table's Description cell reads "a number, a Date, a string, or a{ $field }reference" while its Type cell readsnumber | string | { $field ... }. That difference is deliberate and is recorded ON the artifact: each of the five new schema files carries anx-unprojectable-branchesarray naming the exact JSON Pointer and zod type of every branch the projection dropped, e.g.#/properties/$gt/anyOf/1/date.The measured effect, repo-wide
A generator change is repo-wide by construction, which is why triage said (a) "要有自己的验证面". Measured on this tree:
The 23 is re-derived here, not inherited: it matches the population recorded when option (c) landed. Five entries leave the ledger — the ratchet reported each as
repairedand refused the build until its line went:Data.ComparisonOperatorSchema$gt$gte$lt$lteData.RangeOperatorSchema$betweenData.FieldOperatorsSchemaData.NormalizedFilterSchema$and/$or/$notData.HookSchemahookmetadata typeData.HookSchemais the ledger's own "widest gap" entry, and it was not sought: its only unprojectable member was the DEPRECATED inline-functionhandlerbranch, which is a direct union member. It is the subject of the separately filed #16906, whose measured defect (zerodata/Hook:keys under any key-level ratchet) this repairs as a side effect — 22data/Hook:authorable keys and 4data/Hook:defaults now enterauthorable-surface/andauthorable-defaults/. ⇒ #16906 wants re-triage rather than work; it is not addressed by design here.⛔ Nothing was emptied that should not be. The 18 remaining entries all fail for a reason a per-branch projection cannot reach, and three ledger
reasonfields that this PR made FALSE were corrected against measurement rather than left standing:Automation.FlowFunctionEntrySchema— its barez.function()member IS dropped (1 branch pruned), and the union still does not publish, because the member behind it requires a callablehandlerin a PROPERTY position.Data.MemoryConfigSchema/Data.MemoryPersistenceConfigSchema— the unprojectable node is at#/properties/persistence/anyOf/0/anyOf/4/properties/adapter/properties/load, nested two levels inside a union branch rather than being that branch.The card's own consumer-side controls, before and after
Re-run on this tree, with the card's lit control:
content/docs/references/data/filter.mdxgrows 10,752 -> 72,626 bytes. The five operators now render with their full.describe()text: 1048 / 1060 / 1045 / 1057 characters on$gt/$gte/$lt/$lte, and 1009 on$between— including the{ "$gte": "2026-01-01" }shape the platform's own date-macro resolver produces, which is what #5685 wrote that prose to correct.Rider 1 from the landing comment, discharged
The summary line read
Skipped: N (unsupported types: function, date, bigint, custom)— a hard-coded list that had stopped describing the tree:bigintwas in it and is not a cause here,undefinedis a cause here and was not in it. It is now DERIVED from the build's own skips throughcountByCause, so it cannot go stale again. It readsSkipped: 18 (unsupported types: function, undefined)today.Publishing
ComparisonOperatorandRangeOperatormakes the import-surface ratchet fire: neither schema has anexport type X = z.infer of typeof XSchemaalias, so the generated## TypeScript Usageline omits their names.packages/spec/docs-import-surface.baseline.jsontherefore grows by exactly two lines (60 -> 62, nothing removed).The gate's preferred remedy is to add the aliases — but that is an edit to
packages/spec/src/data/filter.zod.tsand a new published export name, both outside this dispatch's fence. The precedent points the same way: four of the six operator families already sit in that baseline (EqualityOperator,SetOperator,SpecialOperator,StringOperator), so recording these two beside them is the consistent answer and adding aliases to only two of six would not be. ⇒ Recorded, not decided. If a maintainer prefers the aliases, they are a two-line follow-up.Verification
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsover the 13 changed paths; reconciled with--ran: 90 run, 1 unrun. The one ispnpm check:pm-dispatch-gates, a self-test suite overscripts/pm/dispatch-gates.mjs— a file this diff does not touch — which exceeded this container's foreground command cap on three separate attempts (it makes visible progress and is killed mid-run, exit 124). NOT MEASURED, ⛔ not a red; CI runs it. Seven others returned exit 3 PREREQUISITE NOT MET, each needing a workspace build this container did not have; four of those turned green afterpnpm --filter @objectstack/spec buildand the rest need sibling packages built, which CI does.pnpm --filter @objectstack/spec test-> 468 files / 13125 passed, exit 0 under the shared verify lock.pnpm --filter @objectstack/spec typecheck-> exit 0. Newscripts/union-branch-projection.test.ts: 19 passed.trap ... EXIT INT TERMwith absolute paths and blob-hash proof. Mutation:candidates.length === 0->>= 0inprojectByPruningUnionBranches, anchor count 1 -> 0, injected marker count 1, blob1b23049d->4c9f2e87. Under it the unit file went 8 failed / 11 passed andcheck:authorable-surfaceexited 1 — reportingSkipped: 23again and firing the SIBLING disappearance ratchet with "5 previously published schema(s) disappeared from this build", which is the two ratchets guarding their two different directions. Restore: blob back to1b23049d,git diff HEAD0 bytes.eslint . --no-inline-config --format jsonover 6410 files, 0 errors, 0 warnings, exit 0, at3b78f35e.grep -naPover all 13 changed files -> 0 hits, with a positive control on a 0x01 byte that fired.origin/mainmerged into this branch before opening;check:generated15/15 green afterwards.What is still open on #16431
Not addressed here, and the reason this says Part of:
## TypeScript Usageimport line does not name these schemas is now true for two of them for a NEW reason, and the remedy is a maintainer decision on the published export surface;🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Generated by Claude Code