Repository navigation
Commit 5e5ec9f
feat(spec)!: publish the two named refinement patterns the runtime already enforces (#18952)
Part of #18670 (item 2 only — see "What is left" below; item 1 landed as
#18729).
Clause-②: yes (narrowing)
Director ruling batch #154 item 3, letter **C** (comment 5725370614,
maintainer 「同意」): 「the projection emits a refinement only where the rule
is a complete, mechanically derivable JSON Schema pattern — banned keys,
required-one-of, non-blank — one ledger row at a time; everything else
stays annotated as `x-dropped-refinements`」.
## What this does
`z.toJSONSchema()` has no arm for a `custom` check. On zod 4.4.3 — the
version `packages/spec` resolves — a plain record, the same record with
a `.refine()`, and the same record with an **aborting** `.refine()`
project byte-identically. So every rule written as a refinement reached
the runtime and not `packages/spec/json-schema/**`: the published file
was **wider** than the contract it is generated from, which is the
direction where an author's (or an AI's) validator answers PASS right up
to the moment the platform answers NO.
Two of the ruling's four named patterns now project, and only those two:
| pattern | emitted as | sites | why it is EXACT, not approximate |
|:---|:---|--:|:---|
| `required-one-of` | `anyOf` of one `required` per key, conjoined
through `allOf` | **137** | A key absent from a JSON object is the only
way for its value to read `undefined`, so `required` and `!== undefined`
name the same set of documents. A key present with any JSON value,
`null` included, satisfies both. |
| `non-blank-string` | `minLength: 1` plus the pattern `\S` | **60** |
`String.prototype.trim` removes exactly ECMA-262 WhiteSpace ∪
LineTerminator, and `\S` is the complement of that same set. |
`system/TraceSamplingConfig.json` — the card's own named specimen — now
carries no `x-dropped-refinements` at all, and `shared/Expression.json`
states the source-or-ast rule, so `{ "dialect": "cel" }` is refused by
the published file exactly as the runtime already refused it.
## Proof of work: the shrink-only ledger
`packages/spec/dropped-refinements.baseline.json`, measured by the
generator itself:
| reading | before | after |
|:---|--:|--:|
| `publishedSchemasWithDroppedRefinements` | **246** | **201** |
| `droppedRefinementSites` | **750** | **553** |
| `refinementSitesThatDidProject` | **0** | **197** |
| `refinementSitesWithNoJsonFormToCompare` | 3 | 3 |
45 rows deleted outright, 75 rows shrunk, **197 sites closed, 0 sites
added anywhere**. The edit was not typed by hand: a throwaway auditor
parsed the gate's own "corrected entries, in full" output and refused to
write unless the file round-tripped byte-identically through
`JSON.stringify(obj, null, 2)`, every removed site was one the
post-change census classifies `projected`, no entry gained a site, and
the per-entry arithmetic closed. Independently re-proved at JSON level
against `git show HEAD:...`: 0 keys added, 45 keys deleted, 0 sites
added, 197 sites removed, header equals body.
The generator also prints the closed population **per pattern** on every
run, with a line of its own for a site that projects with no declared
pattern — that bucket reads **0**:
```
🔇 553 refinement site(s) across 201 published schema(s) reach the RUNTIME and not the published JSON Schema
Also measured this run: 197 refinement site(s) DID reach the file, 3 had no JSON form on either side to compare.
📣 197 refinement site(s) DO reach the published JSON Schema, by declared pattern:
137 required-one-of
60 non-blank-string
```
## The contract: nothing the runtime accepts becomes refused, MEASURED
The ruling is explicit that this is a correction of the machine-readable
declaration and ⛔ not a behaviour change. That is a reading here, not an
assertion. A probe parses one shared corpus of **6027 documents** across
**12 schemas** — `ExpressionSchema`, `EvaluatedExpressionSchema`, the
four input unions, `PredicateSchema` / `PredicateInputSchema`,
`UpdateAiConversationRequestSchema`, and three deep composers
(`FlowSchema.edges[].condition`, `ObjectSchema.titleFormat`,
`CronScheduleSchema.expression`) — recording per document the success
bit and every issue as a sorted `code@path`. Run in two worktrees, at
the merge base and at this head, over the same corpus file:
```
merge-base 46559f6 cases=12 documents=6027 accepted=1873 refused=4154
this head eae168e cases=12 documents=6027 accepted=1873 refused=4154
cmp exit 0 · sha256 15a714e471f1ba43ec0c0c8773ac345c5c0d03974855ed15116ae2014119008a (both files)
```
Byte-identical, so the accept set, the refusal set and every refusal
message and path are unchanged.
⭐ **And the probe can fire.** Under a lit control that weakens
`NON_BLANK_STRING` from `source.trim().length > 0` to `source.length >
0` — one token — **732 of the 6027 documents move**. So the zero above
is a measurement, not a vacuous pass.
## The two `packages/spec/src/**` files, and why each had to move
The projection cannot READ a predicate's meaning: `.superRefine()` and
`.check()` carry no readable function at all (their check def holds only
`{ check: 'custom' }`, where `.refine()`'s holds `{ type, check, fn }`),
and a projection turning on `fn.toString()` would be a source-text
parser. So the pattern is **declared at the refinement's own call
site**, and `requiredOneOf` builds its predicate **from** that
declaration, so the published `anyOf` and the enforced rule cannot name
different keys.
Each change replaces the predicate expression handed to an **existing**
`.refine()` and nothing else — no schema shape, no key, no message, no
`.strict()`, no optionality, nothing added or removed:
- `src/shared/expression.zod.ts` — `e => e.source !== undefined || e.ast
!== undefined` becomes `requiredOneOf(['source', 'ast'])`; three copies
of `(source) => source.trim().length > 0` become the shared
`NON_BLANK_STRING` (one is inside `typedExpressionStringArm`, so it
covers both the cron and the template slot).
- `src/api/protocol.zod.ts` — `p => p.title !== undefined || p.metadata
!== undefined` becomes `requiredOneOf(['title', 'metadata'])`, plus its
import.
The parse-equivalence reading above is the evidence that both
substitutions are behaviour-preserving.
## 1 parent 106717c commit 5e5ec9f
11 files changed
Lines changed: 899 additions & 481 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
48 | 53 | | |
49 | 54 | | |
50 | 55 | | |
| |||
495 | 500 | | |
496 | 501 | | |
497 | 502 | | |
| 503 | + | |
498 | 504 | | |
499 | 505 | | |
500 | 506 | | |
| |||
503 | 509 | | |
504 | 510 | | |
505 | 511 | | |
| 512 | + | |
506 | 513 | | |
507 | 514 | | |
508 | 515 | | |
| |||
519 | 526 | | |
520 | 527 | | |
521 | 528 | | |
522 | | - | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
523 | 533 | | |
524 | 534 | | |
525 | 535 | | |
| |||
550 | 560 | | |
551 | 561 | | |
552 | 562 | | |
553 | | - | |
| 563 | + | |
554 | 564 | | |
555 | 565 | | |
556 | 566 | | |
557 | 567 | | |
558 | 568 | | |
559 | 569 | | |
560 | | - | |
561 | | - | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
562 | 578 | | |
563 | 579 | | |
564 | 580 | | |
| |||
3421 | 3437 | | |
3422 | 3438 | | |
3423 | 3439 | | |
3424 | | - | |
3425 | | - | |
3426 | | - | |
| 3440 | + | |
| 3441 | + | |
| 3442 | + | |
| 3443 | + | |
| 3444 | + | |
| 3445 | + | |
| 3446 | + | |
| 3447 | + | |
| 3448 | + | |
| 3449 | + | |
3427 | 3450 | | |
3428 | 3451 | | |
3429 | 3452 | | |
| |||
3544 | 3567 | | |
3545 | 3568 | | |
3546 | 3569 | | |
3547 | | - | |
3548 | | - | |
3549 | | - | |
3550 | | - | |
| 3570 | + | |
| 3571 | + | |
| 3572 | + | |
| 3573 | + | |
| 3574 | + | |
3551 | 3575 | | |
3552 | 3576 | | |
3553 | 3577 | | |
3554 | 3578 | | |
3555 | 3579 | | |
3556 | 3580 | | |
3557 | 3581 | | |
| 3582 | + | |
| 3583 | + | |
| 3584 | + | |
| 3585 | + | |
| 3586 | + | |
| 3587 | + | |
| 3588 | + | |
| 3589 | + | |
| 3590 | + | |
| 3591 | + | |
| 3592 | + | |
| 3593 | + | |
| 3594 | + | |
| 3595 | + | |
| 3596 | + | |
| 3597 | + | |
| 3598 | + | |
| 3599 | + | |
| 3600 | + | |
| 3601 | + | |
| 3602 | + | |
| 3603 | + | |
| 3604 | + | |
| 3605 | + | |
3558 | 3606 | | |
3559 | 3607 | | |
3560 | 3608 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
225 | 225 | | |
226 | 226 | | |
227 | 227 | | |
228 | | - | |
| 228 | + | |
229 | 229 | | |
230 | 230 | | |
231 | 231 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
34 | | - | |
35 | | - | |
| 33 | + | |
| 34 | + | |
36 | 35 | | |
37 | | - | |
38 | | - | |
39 | | - | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
40 | 44 | | |
41 | 45 | | |
42 | 46 | | |
| |||
77 | 81 | | |
78 | 82 | | |
79 | 83 | | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
80 | 97 | | |
81 | 98 | | |
82 | 99 | | |
83 | 100 | | |
84 | 101 | | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
90 | 106 | | |
91 | | - | |
| 107 | + | |
92 | 108 | | |
93 | 109 | | |
94 | 110 | | |
| |||
122 | 138 | | |
123 | 139 | | |
124 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
125 | 152 | | |
126 | 153 | | |
127 | 154 | | |
| |||
160 | 187 | | |
161 | 188 | | |
162 | 189 | | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
167 | | - | |
168 | | - | |
169 | | - | |
170 | | - | |
171 | | - | |
172 | | - | |
173 | 190 | | |
174 | 191 | | |
175 | 192 | | |
176 | 193 | | |
177 | 194 | | |
178 | | - | |
179 | | - | |
180 | | - | |
181 | | - | |
182 | | - | |
183 | | - | |
184 | | - | |
185 | 195 | | |
186 | 196 | | |
187 | 197 | | |
| |||
196 | 206 | | |
197 | 207 | | |
198 | 208 | | |
199 | | - | |
| 209 | + | |
200 | 210 | | |
201 | | - | |
| 211 | + | |
202 | 212 | | |
203 | 213 | | |
204 | 214 | | |
| |||
207 | 217 | | |
208 | 218 | | |
209 | 219 | | |
210 | | - | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
211 | 232 | | |
212 | 233 | | |
213 | 234 | | |
214 | | - | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
215 | 238 | | |
216 | 239 | | |
217 | 240 | | |
| |||
241 | 264 | | |
242 | 265 | | |
243 | 266 | | |
244 | | - | |
| 267 | + | |
245 | 268 | | |
246 | 269 | | |
247 | 270 | | |
| |||
375 | 398 | | |
376 | 399 | | |
377 | 400 | | |
378 | | - | |
| 401 | + | |
379 | 402 | | |
380 | 403 | | |
381 | 404 | | |
| 405 | + | |
382 | 406 | | |
383 | 407 | | |
384 | 408 | | |
| |||
0 commit comments