Repository navigation
fix(codegen): follow source additionalProperties instead of asserting closure - #3678
Merged
Merged
Conversation
… closure AdCP 3.x opens the schemas that were closed by accident (adcontextprotocol/adcp#8866). The Zod generator required `additionalProperties: false` on four get_reporting_status structures and hard-coded `.strict()` or unknown-key rejection in several other post-processors, so it either failed or stayed stricter than the source. Read closure from the synced source cache everywhere a post-processor restores strictness. Still-closed sources keep their strict output, and the generated output for the current 3.3.0-beta.5 bundle is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… neighbours The privacy-boundary pass rewrote everything from ContextMatchRequestSchema to OfferPriceSchema, which also closed TargetingKvsSchema, a context-match response shape. It matched the source only while targeting_kvs items were closed; adcontextprotocol/adcp#8866 opens them. Close just the request export, and give TargetingKvsSchema its own source-driven policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inject inline source fixtures through the readSource parameters so the tests never depend on schemas/cache/latest. For each changed post-processor cover the closed source (previous strict output), the open source (no unknown-key check, no throw, runtime accept/reject of emitted Zod), and the new failure modes: title mismatch, closed root with open inline object, changed closed key sets, and targeting_kvs disagreement. Also assert the privacy-boundary pass closes only the request export. Update the three tests that hard-coded the old assertion or read the cache. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The file spawns a tsx harness per post-processor and runs about 25s alone, close to the fast lane's 60s file limit on a cold shard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — Zod generator now follows each source schema's additionalProperties (.strict() only where closed, .passthrough() otherwise) instead of asserting closure, preparing for AdCP 3.3.0-rc.0; generated output for the current beta.5 bundle is unchanged.
What was checked
- Witness-not-translator: passthrough is more faithful than asserting closure — no fabrication or re-shaping at the seam.
- Load-bearing fix: the new single-export boundary (
source.indexOf('\n\nexport const ', start + 1)) prevents the context-match pass from accidentally closingTargetingKvsSchema; a missing next export throws (fail-closed). - Fail-closed drift guards (missing file/pointer, title mismatch, open inline under closed root, key-set drift) all throw at generation; RFC 6901 pointer escape order verified.
- Changeset present and correctly typed as
patch— no wire-shape change ships. - 977-line test suite compiles and runtime-exercises emitted Zod across closed/open/mixed/drift scenarios including the
everyStructureClosedmixed-mode gate.
No blocking or medium findings. No author-team gate, gated_paths false, high_risk false. Falls through to row 9.
This was referenced Nov 9, 2026
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.
Summary
This prepares the code generator for AdCP 3.3.0-rc.0's opened schemas. On Nov 9 the AdCP WG decided that 3.x opens the schemas that were closed by accident. adcontextprotocol/adcp#8866 does that for 3.3: about 744 locations open, and about 115 stay closed for stated structural reasons. The SDK has to regenerate cleanly from that bundle before the 3.3.0-rc.0 SDK regeneration (RC cut Mon Nov 16).
Today the generator assumes closure. #8866's non-required "Generate TypeScript SDK" check fails with
get_reporting_status source schema must close scope. Several other post-processors don't fail but stay stricter than the source. This PR makes every post-processor that restores strictness readadditionalPropertiesfrom the synced source cache:Three new helpers do this.
sourceSchemaAt(file, pointer)reads one object schema fromschemas/cache/latest, andsourceUnknownKeyPolicy()returns the matching policy. The third,openInlineObjectPointers(), guards the passes that close a whole generated expression: if a closed root contains an open inline object, generation fails rather than closing it by accident.Audit: every place the generator assumed closure
All of these are in
scripts/generate-zod-from-ts.ts.reportingStatusClosedStructures+postProcessGetReportingStatusEvidenceStrictnessscope,scope.delivery_config_generations[],obligation_countsandpaginationwere closed; superRefine rejected unknown keys on all fourmedia-buy/get-reporting-status-response.json(bundled)postProcessReportingEvidenceStrictness.strict()on 14 hard-coded evidence schemas and every inline object inside themcore/reporting-{coverage,status-issue,canonical-content-digest,control-total,resource,verification,schedule,receipt,revision,obligation,materialization}.jsonpostProcessReportingConsumerStatusConstraintsperiodand thesync_reporting_statusrequestcore/reporting-consumer-status.json,media-buy/sync-reporting-status-request.jsonpostProcessTrustedMatchResponseSchemas.strict()onTMPXChunk,TMPProviderRegistration,PublisherTMPXMacroMappingand the routertmpx_providersentrytrusted-match/{tmpx-chunk,provider-registration,publisher-tmpx-config,identity-match-response}.jsonpostProcessCompatibilityPurchaseCoordinatorInput.strict()on the restored schemamedia-buy/legacy-purchase-continuation-input.jsonpostProcessTrustedMatchPrivacyBoundaryStrictness.strict()on the whole context/identity match request exceptprooftrusted-match/{context,identity}-match-request.jsonContextMatchRequestSchematoOfferPriceSchema, so it also closed the neighbouringTargetingKvsSchema, a context-match response shapetrusted-match/{,provider-}context-match-response.json#/definitions/targeting_kvs/itemsTargetingKvsSchemagets its own policy from the response sources (and generation fails if the two disagree)postProcessPlacementPresentationRuntimeConstraints.strict()on 8 presentation objectscore/placement-presentation.jsonpostProcessBeta6ReportingConstraints.strict()onCanonicalMetricQualifiercore/canonical-metric-qualifier.jsonreportingFileManifestClosedStructuresadditionalProperties: falsefrom the sourcecore/reporting-file-manifest.json+ refsThese do not assume closure, so they are unchanged:
scripts/generate-types.ts(TypeScript types):enforceStrictSchemadeletesadditionalProperties: trueand compiles withadditionalProperties: false, so TS types never gained index signatures for open objects. That is a typing policy, not runtime validation. The size-mode and mutual-exclusion rewrites emitadditionalProperties: falseonly for the TS projection.CanonicalProposaland the canonical-format passes turn.strict()into.passthrough()on purpose (the SDK's extension policy). These open schemas rather than close them, so they are out of scope here. Note that this also loosens the commercial terms that #8866 keeps closed.not.anyOf required), the postal-area key checks (propertyNamesand explicit keys) andpropertyNamesrecords are key constraints, not closure.generate-zod-schemas.ts,generate-wellknown-schemas.tsandschema-utils.tsonly use open fallbacks.schema-diff.tsreportsadditionalPropertiesflips.Verification
6c716e67…): no change.npm run generate-typeswith the final generator reports every generated file up to date, andgit statusis clean. Still-closed structures produce byte-identical output.9c1c52de, digest785b85a9…): I replayed the adcp "Generate TypeScript SDK" job step for step. The bundle was served locally withADCP_BASE_URL, thensync-schemas,generate-typesandvalidate-schemasran, thenbuild:lib, thenprobe-generated-sdk-creative-assets.cjsfrom the PR head, thenci:codegen-strict. All pass, and the probe reports 57 Zod shape checks and 157 canonical checks. Before this PR, generation failed atmust close scope.test/generate-zod-reporting-status.test.jschecks generatedGetReportingStatusResponsevalidation against Ajv on the authoritative source over 30 cases. Generated and authoritative agree on both bundles. Under #8866, all 17 unknown-key cases (scope, generations, obligation counts, pagination and every evidence object) move from rejected to accepted in the source, and the generated Zod moves with them. That test also pins the closed-beta.5 outcome array, so the rc.0 regeneration PR will need to update it.TMPXChunk,TargetingKvsitems, an evidence digest andCompatibilityPurchaseCoordinatorInputaccept an unknown key.ContextMatchRequest,CanonicalMetricQualifierand the placementRectanglestill reject one.demographicshas aRequired/Supportedarm, and #8866 annotates that$refas deprecated, so jsts inlines it astrue. The type is the same..strict()becomes.passthrough()on the 14 evidence schemas and their inlineperiod/limitations/native_commit_evidenceobjects, onReportingFileEntry/ReportingFileManifest, the TMP chunk, registration, macro mapping andtmpx_providersentry, onTargetingKvsitems, and onCompatibilityPurchaseCoordinatorInput.json-schema-to-zod:TargetingOverlay,TargetingOverlayInputandMediaBuyDeliveryWebhookResultalready follow the source, so they open with no generator change (30 → 0 and 45 → 5.strict()calls).ReportingConsumerStatus(item andperiod) andSyncReportingStatusRequest. TheGetReportingStatusResponserefinement keeps its required-field checks but no longer rejects unknown keys onscope, the generation items,obligation_countsorpagination.RecordedReportingReceipt/UnchangedReportingReceiptcollapse.and()to.merge()now that the receipt is no longer strict..strict().test/generate-zod-source-closure.test.jshas 11 tests and uses injected readers and inline fixtures, so it never depends on the synced cache. It covers the closed source, the open source, mixed sources and each new failure mode for every changed post-processor, compiling the emitted Zod where practical. Existing generator tests now inject sources instead of reading the cache. Across the 7 generator test files: 64/64 pass on the published cache.This PR does not regenerate types: the checked-in output stays on 3.3.0-beta.5. The 3.3.0-rc.0 regeneration will pick up the opened schemas.
Out of scope: the opposite gap
The SDK's global passthrough policy means the Zod output does not enforce most of the locations #8866 keeps closed. Examples are the credential-free reporting destinations, the signed commercial terms (which
CanonicalProposalturns from strict to passthrough on purpose), theforecast-dimension-placementjoin key, and the closedoneOfbranches. This PR doesn't change that; it only stops the generator from being stricter than the source.Refs adcontextprotocol/adcp#8866
🤖 Generated with Claude Code