feat: update AdCP beta.7 schema support#2086
Conversation
There was a problem hiding this comment.
LGTM. Clean beta.5 → beta.7 sync; the witness-not-translator invariant holds across every new scenario dispatcher.
Things I checked
- Changeset is present, marked
minor, and the narrative names every behavior delta (force_creative_purge,seed_measurement_catalog,query_provenance_audit_observations,compliance_testing.scenarioswidening, and the beta-as-primary-pin restore guard). Wire impact matches. - Exhaustiveness guard at
src/lib/server/test-controller.ts:211-216still resolves tonever—CONTROLLER_SCENARIOS(12) ∪SeedScenario(8) covers the widenedListScenariosSuccess.scenariosunion, so the build fails if the next beta widens it again without aCONTROLLER_SCENARIOSupdate. compliance_testing.scenarioswidening is spec-driven, not SDK fiat — verified againststatic/schemas/source/protocol/get-adcp-capabilities-response.jsonupstream. The prior "deliberately not advertised" comment was correct at beta.5; beta.7 explicitly enumerates seeds + new force/query scenarios excludinglist_scenarios.deriveScenariosFromAdapters()andadvertisedScenarios()are 1:1 consistent.CreateMediaBuySuccess/UpdateMediaBuySuccessrequired-field additions (confirmed_at: string|null,revision: integer) reflect the upstream schema'srequiredblock. Theconfirmed_at: null×media_buy_status: activeallOf guard exists upstream — fixtures honor it.decisioning.type-checks.tsfactoring into_createBuyPayload/_updateBuyPayloadis applied at every call site that previously inlined the literal.- Auto-seed projection move at
src/lib/server/decisioning/runtime/from-platform.ts:1097-1130—autoSeedStorecomputed before the projection block now letsseed_product/seed_pricing_optionflow throughcompliance_testing.scenarioswhen the framework wires the no-op seed adapters. Consistent withlist_scenariosoutput. sync-3-1-beta-schemas.ts:97guardif (primary !== BETA_VERSION)is right — when the beta IS the primary pin (today's state),syncSchemasalready wrote the authoritative tracked artifacts; restoring from HEAD would clobber them. The latest-symlink restore still runs unconditionally, which is also correct.seed_measurement_catalogvendor-only cache key is fine: divergence check at line 991-998 runsfixturesEquivalentagainst{ ...params, fixture }(which includesmetrics), so same-vendor + different-metrics getsINVALID_PARAMSrather than a silent collision.- New scenario dispatchers (
force_creative_purge,seed_measurement_catalog,query_provenance_audit_observations) are pure routers — destructure, validate, forward. No fabrication, no normalization, no placeholder substitution. Test coverage matches attest/server-test-controller.test.jsandtest/comply-controller.test.js.
Follow-ups (non-blocking — file as issues)
-
GetMediaBuysResponse.media_buys: unknown[]is codegen drift, not a spec regression. Upstreamstatic/schemas/source/media-buy/get-media-buys-response.jsondefinesmedia_buys.itemsas a structured object with seven required properties. Beta.7 codegen drops them atsrc/lib/types/tools.generated.ts:10229. The bridge's local narrowing atsrc/lib/server/test-controller-bridge.ts:138is a reasonable workaround but masks the regression for adopters who importedGetMediaBuysResponse['media_buys'][number]. File againstscripts/generate-3-1-beta-types.ts+scripts/generate-zod-from-ts.ts— the items+additionalProperties handling likely drops the sibling object schema. Worth landing before any consumer pins^@adcp/sdkagainst 3.1-beta.7 type imports. -
postProcessRecordSizeConstraintshas two latent gaps. Atscripts/generate-zod-from-ts.ts:284-321: (a) only the outermostz.record(is post-processed, so a nestedz.record(z.record(...).max(5))leaves the inner.max(5)to break Zod v4 at runtime; (b) the trailing-constraint regex^\.(?:min|max|length)\(\d+\)matches once, soz.record(...).min(1).max(5)strips only.min(1). Neither triggers on current beta.7 output. Wrap the regex in awhileand extend the test fixture intest/generate-zod-object-intersections.test.jswith both shapes — regression-lock today, not when an upstream schema actually emits them. -
summarize()fallthrough onProvenanceAuditObservationsSuccess.src/lib/server/test-controller.ts:1388-1406has no branch foraudit_observations, so the response's human-readable text content is"Scenario succeeded". Asymmetric with the siblingrecorded_callsbranch one line above. Adopters consumestructuredContent, so wire impact is nil — cosmetic only.
Safe to merge.
93232c3 to
95c5262
Compare
There was a problem hiding this comment.
LGTM. Follow-ups noted below. Clean schema bump — synced enum (inline-enums.generated.ts:329) backs the four new scenarios, auto-seed projection wires real adapters into autoSeedStore rather than fabricating, and the SeededMediaBuy local widening at test-controller-bridge.ts:138 is defensive narrowing against a generator regression, not wire fabrication.
Things I checked
- Changeset
.changeset/adcp-3-1-beta-7.mdpresent, markedminor. Correct: additive scenarios + permissive type widening. No exported-API removal, no required→optional flips.package.jsonversion untouched. ControllerScenariowidening (test-controller.ts:137-141) brings the type into line with the syncedListScenariosSuccess['scenarios']union and the runtimeSCENARIO_MAPthat already emitted seeds. The exhaustiveness guard at:212-217enforces the union — codegen drift fails the build.advertisedScenarios()(comply-controller.ts:603-633) andderiveScenariosFromAdapters()(from-platform.ts:2207-2231) now advertise the same set. Two paths converged.force_creative_purgevalidatespurge_kindagainstCreativePurgeKindSchemaandreason_codeagainstCreativeEventReasonCodeSchema(test-controller.ts:1234-1248). Right shape.force_upstream_unavailablerequiresparams.toolto be a non-empty string before invoking the adapter (test-controller.ts:1351). Fail-closed.dispatchSeedprototype-pollution guard (test-controller.ts:831-838) extended cleanly toseed_measurement_catalog(:931-946) andseed_buyer_agent(:970-977).postProcessRecordSizeConstraints(generate-zod-from-ts.ts:284) uses balanced-paren scanning consistent with the other post-processors, handles quoted literals. Strips spec-irrelevant.min/.max/.lengthfromz.record(...)only.sync-3-1-beta-schemas.ts:36-41restoresRESTORE_PATHS(registry.yaml + protocol skills) from HEAD after the beta sync runs — opt-in beta doesn't bump the primary pin's tracked artifacts. Correct.compliance_testing.scenariostype narrowed via_ComplianceTestingScenario = NonNullable<...['scenarios']>[number](capabilities.ts:33-36) instead of the hand-maintained string-literal union. Removes the prior drift surface.- Auto-seed advertising (
from-platform.ts:1100-1119): default() => undefinedadapters are placeholders that flag the discovery surface — the actual seeded fixtures are persisted intoautoSeedStoreand served on subsequentget_productsvia the bridge. Not fabrication; not overreach.
Follow-ups (non-blocking — file as issues)
-
seed_measurement_catalogcache key omitsmetrics.test-controller.ts:941keys only oncanonicalJson(params.vendor ?? 'default'), but the stored fixture is{ ...params, fixture }— which includesmetrics. Two seeds from the same vendor with different metric arrays will hit the cache, failfixturesEquivalent, and surfaceINVALID_PARAMSeven though they're legitimately distinct catalogs. TheSeedMeasurementCatalogParamsdocstring oncomply-controller.ts:123-128calls out the vendor+metrics composite key. Fix:seed_measurement_catalog:${canonicalJson(params.vendor)}:${canonicalJson(params.metrics)}. -
postProcessRecordSizeConstraintsonly strips one trailing call.generate-zod-from-ts.ts:316matches^\.(?:min|max|length)\(\d+\)once. If ts-to-zod ever emits a chain like.max(10).min(1), the second call survives and breaks the Zod v4 build silently. Loop until no match. -
query_provenance_audit_observationstype-launderscreative_id.test-controller.ts:1334castsparams as { creative_id: string }after a truthy check only; a numericcreative_id: 123reaches the adopter typed as a string. Addtypeof params.creative_id !== 'string'rejection to match the guardforce_upstream_unavailablehas at:1351. -
scripts/sync-3-1-beta-schemas.ts:24pulls frommain/dist, not a release tag.GITHUB_DIST_BASE_URL = '.../adcp/main/dist'means the cached bundle is whatever was onadcontextprotocol/adcpmainat sync time. Confirmv3.1.0-beta.7is a published Git tag — otherwise the pin is to a moving snapshot and re-runs could pull a different bundle under the same version directory. -
GetMediaBuysResponse.media_buys: unknown[]is an upstream regression.core.generated.ts:17441types the array asunknown[]rather than the structured row type, which is whytest-controller-bridge.ts:138had to widenSeededMediaBuylocally. The defensive widening is right for this PR; file upstream onadcontextprotocol/adcpso the next sync restores the structured item type.
Minor nits (non-blocking)
-
Redundant fixture spread in
comply-controller.ts:524-532.seedMeasurementCatalogadapter constructs{ ...params, vendor: params.vendor, metrics: params.metrics, fixture: params.fixture ?? {} }. The spread already placesvendorandmetrics; the explicit re-assignments are pure no-ops. Either drop them or comment why they're load-bearing. -
Parity divergence between
deriveScenariosFromAdapters()andadvertisedScenarios(). The former dropsforce_audience_status,force_catalog_item_status,seed_buyer_agent(not in the wire enum yet); the latter includes them as extension casts. The divergence is intentional —from-platform.tsadvertises only spec-canonical scenarios on the wire; the comply-controller layer accepts extensions inlist_scenariosper the open-extension contract. A one-line// extensions excluded — see comply-controller.ts for the open-for-extension listnearfrom-platform.ts:2207would save the next maintainer a grep.
Approving on the strength of the protocol-coherence verdict (synced enum matches, auto-seed wires real fulfillment) plus the absence of any wire-shape fabrication or removed surface. The seed_measurement_catalog cache-key bug is the only thing I'd want fixed promptly, but it only surfaces under multi-metric seeding from the same vendor — opt-in scenario, no data-corruption path.
There was a problem hiding this comment.
Schema bump tracks the upstream beta.7 contracts cleanly. One open question about the changeset grading; the rest are follow-ups.
Things I checked
- Changeset present at
.changeset/adcp-3-1-beta-7.md, descriptive, named consistently with prioradcp-3-1-beta-Nfiles. ADCP_VERSIONbumped atADCP_VERSION:1,package.jsonadcp_version:449,src/lib/version.ts:12, withCOMPATIBLE_ADCP_VERSIONSextended additively (beta.5 still present).list_scenariossemantics change confirmed against the spec:ListScenariosSuccess_ScenariosValuesandcompliance_testing.scenariosenums insrc/lib/types/v3-1-beta/tools.generated.tshave always carried seeds; the oldExclude<..., SeedScenario>was the SDK lone-holdout. The new behavior is spec-aligned, not an SDK opinion.- New scenario names (
force_creative_purge,seed_measurement_catalog,query_provenance_audit_observations,force_upstream_unavailable) match the beta.7 spec exactly.purge_kindvalidated against the literal'soft' | 'hard';reason_coderuns throughCreativeEventReasonCodeSchemaatsrc/lib/server/test-controller.ts:1240-1248. Witness, not translator. complyTestForProjectionatsrc/lib/server/decisioning/runtime/from-platform.ts:1109-1119correctly stays scoped to capability projection — the runtimecomplyTestflows through...optsand never sees the() => undefinedstubs. The auto-seed dispatch path runs throughtestController: makeAutoSeedBridge(autoSeedStore)at L1221.mergeSeededMediaBuysIntoResponseatsrc/lib/server/test-controller-bridge.ts:885-905defensively handles the now-unknown[]media_buysshape with explicittypeof mb === 'object'checks.postProcessRecordSizeConstraintsclaim ("JSON-schema validation remains the source of truth") holds: no beta.7 record-typed field usesmaxPropertiestoday, and the Ajv compile path catches it if a future rev adds one.
Open question — please respond before merge
- Changeset grade.
confirmed_at: string | nullandrevision: integerare now REQUIRED onCreateMediaBuySuccess(src/lib/types/v3-1-beta/tools.generated.tsconfirmed via diff);revisionis required onUpdateMediaBuySuccess. Server-side adopters whosecreateMediaBuy/updateMediaBuyhandlers don't return these fields fail type-check. Prior art for the same shape — #1902, the 8.0-beta.2 cut — shipped asmajor. The current changeset isminorand the body is silent on this surface. Either re-grade tomajor, or at minimum amend the changeset to call out the required-field flip so adopters audit before bumping. Ifminoris intentional because we treat schema-pin bumps on the beta line as additive-by-convention, say so in the changeset prose — silence is the part I want to fix.
Follow-ups (non-blocking — file as issues)
GetMediaBuysResponse.media_buys: unknown[]is a codegen quirk in beta.7. The schema docstring clearly intends a structured shape but ts-to-zod fails to resolve the items$ref. The SDK'sSeededMediaBuy = {media_buy_id: string; [key: string]: unknown}widening is the right witness-style choice locally; the upstream codegen bug is worth filing onadcontextprotocol/adcpso every downstream codegen consumer stops hitting it.- Auto-seed comment regression at
src/lib/server/decisioning/runtime/from-platform.ts:1211-1217. The old ~25-line block documented the multi-tenant isolation contract (autoSeedStorekeyed byaccount_id) and the adopter's escape hatch (wirebridgeFromSessionStorefor tighter scoping). The new short comment drops both. The stale process-wideSeedFixtureCachecaveat was correctly removed (per-account scoping landed in #1215), but the isolation guarantee is the load-bearing part adopters audit. Restore a one-paragraph version near the wiring at L1542. seed_measurement_catalogcache key keys by vendor only.src/lib/server/test-controller.ts:941usesseed_measurement_catalog:\${canonicalJson(params.vendor ?? 'default')}. Two distinct catalogs for the same vendor (differentmetricsarrays) collide and the second seed returnsINVALID_PARAMS. May be intentional (one catalog per vendor), but the dispatch comment doesn't say so. Either widen the key to include a metrics hash or add a one-line invariant comment.deriveScenariosFromAdaptersvsadvertisedScenariosare out of sync on extension scenarios.src/lib/server/decisioning/runtime/from-platform.ts:2209-2233does not advertiseseed_buyer_agent,force_audience_status,force_catalog_item_status;src/lib/testing/comply-controller.ts:622-626does. Pre-existing, but newly visible now that seeds are advertised. Bring the two into sync.
Minor nits (non-blocking)
- Prototype pollution claim on
seed_measurement_catalog— worth a sanity check, not a block.src/lib/server/test-controller.ts:939buildsmeasurementCatalogFixture = { ...params, fixture }and forwards the whole params object to the adapter, while other seed scenarios extract specific scalars. Spread operator semantics since ES2018 use[[DefineOwnProperty]], so a JSON-parsed__proto__arrives as a regular own property rather than walking the prototype chain — the actual exploit path is narrow. Still, extractingvendor+metrics+fixtureexplicitly like the other seed branches is cheaper than reasoning about every adopter's downstream merging. - Chained-call regression test missing.
test/generate-zod-object-intersections.test.js:163-175covers the four basic strip/preserve cases but doesn't lock thez.record(...).optional().max(5)chained case the comment inscripts/generate-zod-from-ts.tsimplicitly promises to handle. One assertion would close the loop. hello_seller_adapter_guaranteed.ts:1058—revision: localBuyRevisions.get(o.order_id) ?? 1silently reportsrevision: 1for any buy loaded from upstream state without going through this process'screate_media_buy. Fine for a worked example; one-line comment would prevent buyers from copy-pasting it into production.
LGTM modulo the changeset question — happy to flip to approve once that's either re-graded or explained.
95c5262 to
fdef881
Compare
There was a problem hiding this comment.
LGTM. Faithful beta.7 reflection — SDK correctly tracks the widened compliance_testing.scenarios enum (which now admits seeds per spec), the four new controller scenarios, and the new field-required shape on CreateMediaBuyResponse. "Witness, not translator" — the previous beta.5 hand-narrowing was the bug; deriving from the generated union in capabilities.ts:34-36 is the correct fix.
Things I checked
force_creative_purge/force_upstream_unavailable/query_provenance_audit_observations/seed_measurement_catalogare all in beta.7'sComplyTestControllerRequest.scenarioandListScenariosSuccess.scenariosenums persrc/lib/types/v3-1-beta/tools.generated.ts:22481,23184. SDK is not fabricating scenarios.deriveScenariosFromAdapters()projection infrom-platform.ts:2208-2231now correctly pushes seed scenarios — beta.7'sGetAdCPCapabilitiesResponse.compliance_testing.scenariosenum (tools.generated.ts:20552-20572) enumerates the seed values. The dropped "spec narrowed to forces+simulates" comment was stale.- New dispatch arms in
test-controller.ts: Zod validations short-circuit before adapter invocation (force_creative_purgeL1226-L1250,force_upstream_unavailableL1346-L1360).paramsstripping (creative_id/tool) is clean before spread. postProcessRecordSizeConstraints(generate-zod-from-ts.ts:284-321) — regex/^\.(?:min|max|length)\(\d+\)/is anchored and tight, won't eat.minLength(or.max(z.unknown()). Wired beforepostProcessRecordIntersectionsat L1681 — correct order.- Pre-release mode is active (
.changeset/pre.jsonmode=beta), so theminorchangeset rolls into the next beta. Appropriate.
Follow-ups (non-blocking — file as issues)
- Changeset undersells wire impact.
CreateMediaBuyResponse.confirmed_at: string | nullandCreateMediaBuyResponse.revision: numberare now non-optional on the success branch (v3-1-beta/tools.generated.ts:12274,12282);UpdateMediaBuyResponse.revision: numberlikewise at:12726. Buyer agents deserializing into typed clients will hard-fail on responses that omit these. The example-file additions are spec-required, not cosmetic. Recommend a "Wire-impact" header in the changeset before primary pin moves. force_upstream_unavailablemissing from changeset prose. The body at.changeset/adcp-3-1-beta-7.md:10-12lists three new scenarios but the PR ships four.STALE_RESPONSEerror code addition (enums.generated.ts:80) deserves a callout —force_upstream_unavailabledoc-comment mandateserror.details.upstream.nameecho. Adopters who wrap controller errors need to know.GetMediaBuysResponse.media_buys: unknown[]codegen regression. Spec doc-comment attools.generated.ts:12883describes a typed element shape ("media buys with status, creative approval state, and optional delivery snapshots") but noMediaBuyinterface is emitted — element widened tounknown[]. Likely a$refresolution miss injson-schema-to-typescript. The bridge's localSeededMediaBuyretype attest-controller-bridge.ts:138is a reasonable contention strip, but don't ship beta.7 to GA withunknown[]on a primary read path. Track as a codegen issue (or file againstadcontextprotocol/adcpif the schema source isn't$ref-disciplined).SeededMediaBuypublic-type widening. Type changed fromGetMediaBuysResponse['media_buys'][number](the strong lookup) to{ media_buy_id: string; [key: string]: unknown }. Adopters who importedSeededMediaBuyfrom@adcp/sdk/serverlose autocomplete onstatus,packages, etc. Public-surface change worth one line in the changeset.
Minor nits (non-blocking)
CreativePurgeKindSchemahand-authored.test-controller.ts:603declaresz.enum(['soft', 'hard'])at module scope, but the generated artifacts already carry this —inline-enums.generated.ts:168exportsCreativePurgedWebhook_PurgeKindValues = ["soft", "hard"] as const. Replace withz.enum(CreativePurgedWebhook_PurgeKindValues)so a future beta widening (e.g.,'tombstone') flows through automatically instead of silently rejecting valid purge kinds at L1236.seed_measurement_catalogpresence check is inconsistent with peers.test-controller.ts:932usesObject.hasOwn(params, 'vendor')which letsvendor: null/vendor: undefinedpass while every other seed branch (seed_productL847,seed_planL890, etc.) uses truthyparams?.X. Either tighten for symmetry or add a one-line comment that null-vendor is intentional pass-through.wrapStoreSuccessdouble cast.test-controller.ts:674-677as unknown as ComplyTestControllerResponsemasks the per-arm narrowing the generated union provides. Tightening the signature to<T extends StateTransitionSuccess | UpstreamTrafficSuccessResponse | SeedSuccess | ForcedDirectiveSuccess>would let callers keep the arm type. Works at runtime; throwing away type info.force_upstream_unavailableexplicit-undefined spread.test-controller.ts:1357builds{ tool, upstream_name: undefined }which is fine under the current tsconfig but breaks the moment someone enablesexactOptionalPropertyTypes. Conditional spread...(typeof forceParams.upstream_name === 'string' ? { upstream_name: forceParams.upstream_name } : {})is forward-compatible.sync-3-1-beta-schemas.ts:97-102comment. Gate logic is correct, but the comment block still reads as ifrestoreFromHeadruns unconditionally. Reword: "When the beta is also the primary pin, the synced tarball already represents the primary state — skip the HEAD restore."
The seeds-now-advertised behavior change is interesting on its own — last beta the SDK type was the conservative one and the spec was the broad one; this one corrects in the right direction.
Approving on the strength of the spec alignment plus the new dispatch arms being properly Zod-gated. Ship it; address the changeset prose in a follow-up before the primary pin moves.
fdef881 to
85f8faf
Compare
There was a problem hiding this comment.
LGTM. Clean schema bump — additive scenarios, generated surfaces regenerated, codegen workarounds scoped narrowly enough that Ajv remains the runtime source of truth.
Things I checked
- Changeset present, typed
minor. Right call — four new scenarios (force_creative_purge,query_provenance_audit_observations,force_upstream_unavailable,seed_measurement_catalog) are additive; the existing dispatcher arms didn't change shape. package.jsonversion untouched.LIBRARY_VERSIONstays at8.1.0-beta.14;ADCP_VERSIONmoves to3.1.0-beta.7.COMPATIBLE_ADCP_VERSIONS(src/lib/version.ts:38) extends the list — beta.4 / beta.6 absent is intentional (never pinned).- Exhaustiveness guard at
test-controller.ts:216still type-checks against the regeneratedControllerScenariounion.SCENARIO_MAP(test-controller.ts:606-626) registers the four new entries againstCONTROLLER_SCENARIOS/SEED_SCENARIOS. - Envelope
statusinvariant holds. Success paths route throughwrapStoreSuccess()(test-controller.ts:674) stampingstatus: 'completed'; error paths route throughcontrollerError()(L645-662,comply-controller.ts:477-481) stampingstatus: 'failed'. MatchesComplyTestControllerResponse.status: TaskStatussince beta.2. - Witness-not-translator preserved.
force_creative_purge(test-controller.ts:1226-1250) validatespurge_kindagainstCreativePurgeKindSchema(z.enum(['soft','hard'])) andreason_codeagainst the spec-shippedCreativeEventReasonCodeSchema— no fabrication, no silent defaulting.force_upstream_unavailableuses explicittypeof === 'string'narrowing (L1351). Prototype-pollution guard at L831-838 covers the newseed_measurement_catalogpath via the shareddispatchSeed. applyCodegenSchemaWorkaroundsforGetMediaBuysResponse(scripts/generate-types.ts:1022-1060) is scoped by name and only dropsallOfmembers whose keys are exclusively{$comment, if, then, else}— won't over-strip a mixed-content member, and Ajv still validates against the unstripped cached JSON. Legitimate jsts-emitter workaround.postProcessRecordSizeConstraints(scripts/generate-zod-from-ts.ts:284-321) only fires afterz.record(literally;z.array(z.string()).max(5)is left untouched. Balanced-paren scanner handles quoted strings and escapes. Pipeline placement (afterpostProcessUndefinedUnions, beforepostProcessRecordIntersections) is right.sync-3-1-beta-schemas.tsbumpsBETA_VERSIONto3.1.0-beta.7.RESTORE_PATHSstill matches whatsyncSchemasoverwrites as a side effect. When primary IS the beta (current state),restoreFromHeadis correctly skipped.- Official MCP SDK used throughout.
registerTestController(test-controller.ts:1499-1543) goes throughmcp.registerTool; no custom HTTP / SSE. - Test plan in PR body lists
build:lib/typecheck/ targeted controller / version / postprocessor suites +git diff --check. Nothing unchecked.
Follow-ups (non-blocking — file as issues)
src/lib/server/test-controller.ts:941— theseed_measurement_catalogcache key isseed_measurement_catalog:${canonicalJson(params.vendor ?? 'default')}, ignoringmetrics. Two storyboards seeding the same vendor with different metric sets will hit the cache, tripfixturesEquivalent, and surfaceINVALID_PARAMS. Failure mode is loud, not silent, but it forces adopters into one-vendor-per-metric-catalog. Either widen the cache key to(vendor, metric_id-set)or document the constraint in the store JSDoc.src/lib/server/test-controller.ts:1330—QUERY_PROVENANCE_AUDIT_OBSERVATIONSuses truthiness (!params?.creative_id) and then unsafe-casts. The newforce_upstream_unavailablearm at L1351 already uses explicittypeof === 'string'narrowing — tighten this one to match the new pattern.scripts/sync-3-1-beta-schemas.ts:97— whenprimary === BETA_VERSION(true today),restoreFromHead(RESTORE_PATHS)is correctly skipped, but a one-line comment at the conditional would keep a future refactor from dropping the guard.
Minor nits (non-blocking)
force_creative_purgeparams passthrough.test-controller.ts:1233rest-spreadspurgeParamsto the adapter after validating onlypurge_kindandreason_code. Consistent withsimulate_delivery's open-ended signature ([key: string]: unknown) and not a regression — just noting that the prototype-pollution guard at L831-838 isfixture-scoped, notparams-scoped, across allforce_*arms.
Safe to merge.
85f8faf to
739f6f9
Compare
There was a problem hiding this comment.
LGTM. Schema pin bump is clean, the new scenarios are wired correctly, and the codegen workarounds (applyCodegenSchemaWorkarounds for the new media_buys.items if/then/else guard, postProcessRecordSizeConstraints for unsupported Zod v4 record size calls) are scoped to the TS/Zod emit path — Ajv still loads the un-stripped schemas, so the conditional invariants remain enforced at runtime. package.json's adcp_version field moved; the library version field was correctly left alone for changesets to own.
Things I checked
- Changeset present and correctly sized:
.changeset/adcp-3-1-beta-7.mdisminor— additive scenarios + new beta opt-in pin. Nomajorremovals on the public surface;SeededMediaBuywas widened, not narrowed. - Spec conformance on seeds-now-advertised:
dist/schemas/3.1.0-beta.7/compliance/comply-test-controller-response.jsonlist_scenarios.scenarios[]enum includes all six original seeds plusseed_measurement_catalog. The SDK was following the spec, not driving it. Interesting wrinkle: the same enum already included seeds in beta.5, so the old "deliberately NOT advertised" comment was drifting one minor ago — this PR catches up rather than aligning with a fresh beta.7 break. applyCodegenSchemaWorkaroundsatscripts/generate-types.ts:1015-1055only deletes the inlineif/then/elseallOf when EVERY allOf member has keys ⊆{$comment, if, then, else}. A future beta that mixespropertiesinto the same allOf will no-op the strip and surface the jsts collapse loudly at codegen — fail-loud, not fail-silent.postProcessRecordSizeConstraintsquote handling atscripts/generate-zod-from-ts.ts:284-321correctly handles'/\"with\\\\escapes. No backticks emitted by ts-to-zod.sync-3-1-beta-schemas.ts:97-103guardif (primary !== BETA_VERSION) restoreFromHead(...)is symmetric with howlatestsymlinks are handled — restore the HEAD artifacts only when the beta is the opt-in track, not the primary pin.- New scenario validators are tight:
force_creative_purgevalidatespurge_kindagainstz.enum(['soft','hard'])andreason_codeagainstCreativeEventReasonCodeSchema.seed_measurement_catalogrequires bothvendorandmetrics.query_provenance_audit_observationsrequirescreative_id. Tests attest/server-test-controller.test.js:516-678andtest/comply-controller.test.js:152-274cover the happy path plus invalid-param branches. BACKWARD_COMPAT_OPTIONAL_FIELDSentries forCreateMediaBuyResponse: ['confirmed_at', 'revision']andUpdateMediaBuyResponse: ['revision']— right posture. Buyer parsers stay tolerant of legacy v3 sellers.- Examples updated to emit the new required fields:
examples/hello_seller_adapter_guaranteed.tsand siblings now setconfirmed_at+revision.
Follow-ups (non-blocking — file as issues)
mergeSeededMediaBuysIntoResponsedoesn't backfill the new beta.7 required fields.src/lib/server/test-controller-bridge.ts:903castsseeded as readonly GetMediaBuysResponseMediaBuy[]and splats. WithSeededMediaBuywidened toPick<…,'media_buy_id'> & Partial<…> & Record<string, unknown>, pre-beta.7 seed fixtures will produce wire responses missingconfirmed_at/revisionand fail Ajv at the seller seam. Either backfill defaults that match the provisional-buy contract (confirmed_at: null,revision: 0) or run the merged response through wire validation with a loud warning.seed_buyer_agentand theforce_audience_status/force_catalog_item_statusextension claims.comply-controller.ts:624casts theseas unknown as ControllerScenariobecause the beta.7 wire enum is closed and doesn't include them. Pre-existing for the two force_* scenarios; new forseed_buyer_agent. File onadcontextprotocol/adcpto either add anx-extension convention or open the response enum so the SDK isn't fabricating wire claims. Same problem affects strict-Ajv-running counterparties.- PR description language. The summary frames the seeds-advertised change as a beta.7 alignment, but the response enum already carried seeds in beta.5 — the SDK was the one out of step. Worth correcting in the PR body so the audit trail is accurate.
complyTestForProjectionindirection atsrc/lib/server/decisioning/runtime/from-platform.ts:1109-1119plants() => undefinedno-op seed stubs solely to makederiveScenariosFromAdaptersadvertiseseed_product/seed_pricing_optionwhen the framework wires auto-seed on the adopter's behalf. The real dispatcher istestController: makeAutoSeedBridgeat line 1221. One short comment at 1109 would prevent the next reader from chasing a phantom adapter.
Minor nits (non-blocking)
force_creative_purgereason_detailis unbounded. No length cap on the free-text field. Consistent with the rest of the controller surface — but worth a follow-up bound across the lot.seed_measurement_catalogfixture-equivalence comparison atsrc/lib/server/test-controller.ts:945-964includes the full params object (vendor + metrics + anything else), so unrelated params drift triggerINVALID_PARAMSon replay. Matches every other seed; not a new wart, but a one-line doc note on the seed-equivalence semantics would help adopters key replays correctly.
Notable: the SDK now emits seed_buyer_agent as a list_scenarios value despite the beta.7 response enum being closed against it. That's a pre-existing extension claim, not new in this PR — but worth surfacing alongside the other extensions so it lands as one spec issue rather than three.
Approving.
739f6f9 to
717978d
Compare
There was a problem hiding this comment.
Approving. Clean additive bump — new optional wire fields, widened scenario enum, codegen workarounds for two emit-shape mismatches; the seed-bridge merge defaults sit on the SDK side of the seam so witness-not-translator holds.
Things I checked
- Changeset
.changeset/adcp-3-1-beta-7.mdpresent andminormatches the wire impact (additiveconfirmed_at/revisionmade optional in TS viaBACKWARD_COMPAT_OPTIONAL_FIELDS;compliance_testing.scenarioswidened to match the beta.7 enum). package.jsonversion stays at8.1.0-beta.14; only theadcp_versionmetadata field bumped to3.1.0-beta.7. No manualversionedit.- Wire enum widening (
from-platform.ts:2216-2234deriveScenariosFromAdapters) matchesListScenariosSuccess_ScenariosValuesandComplyTestControllerRequest_ScenarioValuesininline-enums.generated.ts:151/329. The previously-load-bearing "seeds aren't on the wire" comment no longer holds in beta.7 — removing it is correct, not drift. force_creative_purgereason_codevalidates againstCreativeEventReasonCodeSchema(test-controller.ts:1259-1267) — same schema beta.7 wires intocomply_test_controller.params.seed_measurement_catalogcache key composes(vendor, sorted distinct metric_ids)(test-controller.ts:744-756) —INVALID_PARAMSon divergent replay still fires viafixturesEquivalent. Regression test attest/server-test-controller.test.js:632.mergeSeededMediaBuysIntoResponsespread order attest-controller-bridge.ts:903-906is{ confirmed_at: null, revision: 1, ...mb }— fixture-provided values win.confirmed_at: nullis the canonical "provisional buy" wire value percore.generated.ts:681-684; defaulting seed fixtures to provisional is the right floor.applyCodegenSchemaWorkaroundspredicate atscripts/generate-types.ts:1025-1040is tight (allOfonly stripped when every member has only$comment/if/then/elsekeys); a future schema rev adding structuralallOfmembers keeps the conditional intact.sync-3-1-beta-schemas.tscorrectly conditions therestoreFromHeadstep onprimary !== BETA_VERSION— explains the registry.yaml prose changes flowing through.- CI: typecheck, build, format, security, eslint, codeql, library build, pack runner, schema/strict 3.0 reference seller all green. Unit tests + adopter typechecks + reference seller still in flight at review time.
Follow-ups (non-blocking)
scripts/generate-zod-from-ts.ts:319postProcessRecordSizeConstraintsstrips only a single trailing.max()/.min()/.length()perz.record(...). If a future codegen output chains two constraints (.max(1000).min(1)), the second one leaks through and Zod v4 records will reject the chain. Wrap the strip in a loop or use/^(?:\.(?:min|max|length)\(\d+\))+/. No chained constraints in this PR's emit, so it doesn't bite today.src/lib/server/test-controller-bridge.ts:907— theSeededMediaBuywidening toPick<...,'media_buy_id'> & Partial<...> & Record<string, unknown>plus theas GetMediaBuysResponseMediaBuy[]cast hides genuine schema gaps from TS when a fixture omitsstatus/currency. Risk is bounded to test/sandbox paths but worth tightening with a runtime invariant at the wire-emit boundary.examples/hello_seller_adapter_social.ts:617— the identifier-collection loop overwritesidentifierTypeon every row; a mixed-identifieradd[](hashed_email + hashed_phone in the same batch) silently uploads everything under whichever identifier the last row used. Either bucket per type or reject mixed inputs.from-platform.ts:1116comment "makeAutoSeedBridge handles the real writes" is slightly off — the writes flow through inlineautoSeed.product/autoSeed.pricing_optionadapters;makeAutoSeedBridgeis the read side. Comment-only.
Safe to merge.
Summary
Bumps the SDK AdCP schema pin and generated surfaces to 3.1.0-beta.7 while keeping the package metadata at 8.1.0-beta.14.
Adds beta.7 compliance-controller support for creative purge, measurement-catalog seeding, provenance audit queries, upstream-unavailable forcing, and brings SDK seed/auto-seed scenario projection in line with the existing beta compliance response enum.
Hardens beta.7 codegen for unsupported z.record size constraints and updates media-buy seed/type fixtures for required confirmed_at/revision lifecycle fields.
Validation
npm run build:lib; npm run typecheck; npm run format:check; targeted node --test suites for controller/seed wiring, version/schema, and zod postprocessors; git diff --check.