fix(codegen): pre-merge allOf[$ref] siblings to unbreak jsts union emission#1783
Conversation
|
Coordinator review (3-lens sweep: code / protocol / testing). One real concern worth surfacing; the rest are nits or follow-ups. Not blocking — flagging for your call before merge. Real concern:
|
|
Addressed the reviewer's What changed
Recursion verified safe: Diff scope (tight, as expected):
Full test suite passes, format/build clean. The two pre-existing lint errors ( The three "nice-to-have" items you flagged (collision-precedence pin, nested-base allOf test, etc.) are out of scope for this fix — happy to file follow-ups if useful. |
…ission (#1756) JSON Schema objects that mix sibling `properties`/`required` with `allOf: [{ $ref }]` make `json-schema-to-typescript` emit a broken union `( Base | { variant + duplicated base fields } )` where the intent is `Base & { variant }`. Most visible inside `oneOf` discriminator variants — `GetContentStandardsResponse` collapsed its success arm to bare `ContentStandards`, silently dropping the variant's own `context` and `ext` fields. `enforceStrictSchema` now pre-merges any `allOf` member that is a single `$ref` into the parent shape when the parent already declares its own `properties`/`required` — variant-level fields win on collision; the base's `additionalProperties` is inherited only when the variant didn't override it. Local `#/$defs/...` fragments are left in place (existing post-processors handle the Individual*Asset alias case). `vendor-pricing-option`-style allOf-only roots stay untouched. Tests cover the three required scenarios: allOf-only pass-through, single-variant merge, and a two-variant `oneOf` ending in a clean discriminated union. Knock-on type changes: - `BriefAsset` / `CatalogAsset` flatten from `Base & { ... }` to merged interfaces (option 2 in adcp#4510 acceptance criteria). - `GetContentStandardsResponse` success arm regains its `context?` / `ext?` fields. - `CreativeBrief` no longer transitively reaches `tools.generated.ts`; re-exported from `core.generated` via `src/lib/index.ts`. Unblocks adcp#4510 (schema dedup spike). Closes #1756 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nalProperties widening
`resolveAllOfRefForMerge` previously returned the raw resolved base with only
`removeArrayLengthConstraints` applied. Because `creative-brief.json` and
`catalog.json` declare top-level `additionalProperties: true`, that flag
propagated into the merged shape and emitted a
`[k: string]: unknown | undefined` index signature on `BriefAsset` and
`CatalogAsset` — wider than either the pre-PR intersection form
(`BaseAsset & { asset_type }`) or the standalone `CreativeBrief` interface.
Apply `enforceStrictSchema` to the resolved base inside
`resolveAllOfRefForMerge` so `additionalProperties: true` at the base's top
level gets stripped before the merge. Recursion terminates: `loadCachedSchema`
reads a fresh JSON document per call (no shared mutable state) and AdCP
schemas aren't cyclic at the `allOf:[{ $ref }]` sibling level.
Adds a regression test pinning that `additionalProperties: true` on the
resolved base does not propagate after the merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eab4000 to
b5cc211
Compare
Closes #1756. Unblocks adcp#4510 (the spec-side schema dedup spike that reverted on this codegen bug).
Summary
JSON Schema objects that mix sibling
properties/requiredwithallOf: [{ $ref }]makejson-schema-to-typescriptemit a broken union( Base | { variant + duplicated base fields } )where the intent isBase & { variant }. This is most visible insideoneOfdiscriminator variants —GetContentStandardsResponsecollapsed its success arm to bareContentStandards, silently dropping the variant's owncontextandextfields.enforceStrictSchemanow pre-merges anyallOfmember that is a single$refinto the parent shape when the parent already declares its ownproperties/required— variant-level fields win on collision; the base'sadditionalPropertiesis inherited only when the variant didn't override it. Local#/$defs/...fragments are left in place (existing Individual*Asset post-processor handles them).vendor-pricing-option-style allOf-only roots stay untouched (jsts handles those correctly today).This is paired with the sibling PR for #1745 (JSDoc constraint injection) — independent edits to different parts of
scripts/generate-types.ts, mergeable in either order.Knock-on type changes (intentional)
BriefAsset/CatalogAssetflatten fromBase & { ... }to merged interfaces. Field set is identical; the named base type is no longer referenced via intersection. This matches option 2 in the issue's acceptance criteria ("flattens the allOf into a single merged shape — less ideal but acceptable"). Recovering theBase & { variant }intersection form would be a follow-up polish pass and is out of scope here.GetContentStandardsResponsesuccess arm regains itscontext?/ext?fields — the bug this PR fixes.CreativeBriefno longer transitively reachestools.generated.ts;src/lib/index.tsre-exports it fromcore.generatedinstead. Existing import sites are unaffected.Test plan
enforceStrictSchemacover three scenarios —vendor-pricing-option-style pass-through, single-variant merge, and a two-variantoneOfending in a clean discriminated union (test/type-generator-allof-ref-merge.test.js).npm run generate-typesregenerates cleanly; diff confined to the four type changes listed above plus their downstream Zod schemas.npm testpasses (1 known-flaky perf-test unrelated to this change:request-signing-replay-perf.test.js'shas() sub-linearassertion — passes in isolation).npm run format:checkpasses.tsc --noEmit+build:lib) passes.🤖 Generated with Claude Code