You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Minor releases sometimes break older buyers, and our versioning rules disagree about whether that's allowed #8501
We say a minor release only adds things, so a buyer built for 3.0 keeps working against a 3.2 seller. In practice it sometimes doesn't.
Some of our schemas are published as closed. A closed schema tells a validator to reject any field it doesn't recognize. When a later minor adds a field to a closed schema, a buyer that validates against its own release rejects the newer response. That's a breaking change, even though the field is optional.
This has happened in every minor so far. Two examples:
The brand reference was closed in 3.0. It gained brand_kit_override in 3.1 and countries in 3.2.
The account reference (the brand + operator form) was closed in 3.0. It gained currency, timezone and operator_unit in 3.2.
A check that compares each release with the previous one (#8502) counts the changes that aren't additive. Experimental features are left out, because they're allowed to change:
From → to
Changes that aren't additive
3.0 → 3.1
79
3.1 → 3.2
70
3.2 → 3.3 beta
41
Why it keeps happening: two rules that disagree
DR-0009 says schemas are open by default, so that minors can add optional fields "without invalidating existing traffic". The versioning docs add that readers must ignore unknown fields.
Each rule makes sense on its own. Together they allow minor releases that break older buyers, and implementers can't tell which rule to rely on. The cost shows up elsewhere. Sellers have to shape responses to each buyer's release (DR-0024). Requests that only say "version 3" now get the oldest release (DR-0034). And DR-0032 proposes translation code in every SDK.
Proposal
One rule: a minor release never breaks a buyer that validates against its own release. A closed schema stays as it is until the next major. New fields go into an open schema, a new schema, or the next major.
Update DR-0019 to match. Its idea of closed schemas can stay; closed just means frozen within a major.
WG Session C (2026-10-26) accepted the problem. Adding a property to a schema published with additionalProperties: false breaks older strict validators, exactly as removing a field does.
One rule: a minor never adds a property to, or removes one from, a closed schema. A closed schema is frozen until the next major.
New schemas default to open (DR-0009) unless there's a stated reason to close them.
Owner:@KonstantinMirin owns the triage of the 3.3 additions to closed schemas (move it, send it only to 3.3 buyers, or defer it to 4.0), due before 3.3 GA.
DR-0032 and DR-0033 are parked until this is settled.
Thanks for taking this up at Session C. Here's the triage I owe for the 3.3 additions to closed schemas.
The rule I applied: a minor release only adds, and it never adds to a closed schema (additionalProperties: false). A closed schema stays exactly as it is until 4.0. I didn't make exceptions for fields sent only to 3.3 peers, or for experimental fields, because either one would still break a strict 3.2 validator.
For each of the 57 new fields on closed schemas there are two outcomes. Either the field moves to the nearest open object that contains its closed parent, keeping its meaning, or it waits for 4.0. 15 move and 42 wait. The evaluator value removed from the x-entity registry comes back.
Most of the 42 share one cause. In 3.2, list_products, request_proposals, refine_proposals, buy_products, accept_proposal, decline_proposals and control_media_buy were published closed from the request or response object down. So proposals, purchases and commercial terms have no open place to grow in 3.x. DR-0019 asks for roots and containers to stay open, so I'd treat that as something to reopen in 4.0, together with declared extension points, rather than as anyone's mistake today.
Holds, installment selection, purchase rights, definition pins, the list_products version echo, product_ids_exclusive
Wait for 4.0
The full per-field list, with the open object each field moves to and the reason for each deferral, is in this gist.
Follow-ups I'll open:
DR-0036: a minor adds only to open schemas; a closed schema stays as it is until the next major, including its closedness, so opening it doesn't allow later additions; new schemas default to open. It replaces DR-0019's "additions to closed schemas are minor" and keeps its two classes.
A fix to the build(schemas): fail CI when a minor release makes a non-additive schema change #8502 check: treat a schema as closed if any stable release of the major closed it. Today the check compares only with 3.2.7, which is how one case slipped through: the capabilities identity block was closed in 3.0.0, opened in 3.0.5, and gained brand_json_url in 3.1.0.
The changes themselves: restore evaluator, remove the deferred fields with the capability flags of the features that wait, and one PR per moved feature, agreed with its owner first.
@bokelley, I know this costs 3.3 features the group already agreed on, and I'm sorry to bring it this late. Could you check the list before we cut 3.3.0-beta.1? Six features wait for 4.0, and the moves change the wire form of #8025 and #8468, so their owners should see it too. I'm happy to walk through any of it.
Thanks, Konstantin. This is careful work. I'd accept the analysis method, the 15 moves, the narrowings, the evaluator restore and the check fix (closed anywhere in the major) as written. The identity case shows why the fix matters.
Before we accept the 42 deferrals, I'd like to question their main cause rather than live with it until 4.0. Most of them, and all six features that slip (holds, installment selection, purchase rights, definition pins, the list_products version echo, product_ids_exclusive), come from one thing: the 3.2 proposal flow was published closed from the root down. As far as I can find, nobody decided that, and it contradicts our own records.
What the records say:
DR-0009: schemas are open by default, so that a minor can add optional fields.
DR-0019: allows additionalProperties: false only on row schemas: join-key objects, or row envelopes that keep an ext escape hatch. It also says outright that "response roots, capability blocks, and container objects stay open."
What 3.2 shipped:
Schema
Closed
ext
request_proposals request (root)
yes
no
request_proposals response (root)
yes
yes
list_products request (root)
yes
no
buy_products request (root)
yes
yes
canonical-proposal (container)
yes
no
commercial-terms
yes
no
canonical-product
yes
yes
The roots and containers are closed, which DR-0019 says they must not be. Several of them also lack the ext slot DR-0019 requires of a closed envelope. None carries a note explaining why. I read this as a 3.2 defect, not a deliberate design.
One exception is deliberate in substance: commercial-terms. A buyer must not be bound by terms it can't read, and those terms are what terms_digest signs. If the object were open, a 3.2 buyer could accept a 3.3 term (rights, pins, installments) it silently ignored. Exhaustive keys there fit DR-0019's join-key class, and I'd keep it closed.
Proposal:
Fix the defect in 3.2.x and 3.3. Open the proposal-flow roots, rows and containers (the canonical discovery and proposal task roots, canonical-proposal, canonical-product, product-discovery-criteria, proposal-refinement, package-control), and keep commercial-terms closed. Loosening is non-breaking under DR-0019: anything valid before stays valid. These tasks are new in 3.2, so the strict validators that exist for them are almost all the SDKs, and we'll patch those.
3.3 fields on the opened objects go only to peers that negotiated 3.3. DR-0024 already requires responses, webhooks included, to conform to the served release. Every counterpart on these tasks must advertise supported_versions (the 3.2 grader blocks otherwise), so negotiation is reliable here. An un-updated strict 3.2.0–3.2.7 validator therefore never sees a 3.3 field from a conformant seller.
Term-level 3.3 features (definition pins, purchase rights, installments in purchases[]) are offered only to buyers that negotiated 3.3. A seller never serves a pre-3.3 peer a proposal whose commercial-terms uses them. It returns VERSION_UNSUPPORTED for that proposal instead of stripping terms, which would also break terms_digest. This keeps the reason the object is closed: nobody receives terms they can't read.
It contests the "opening a closed schema permits nothing within the major" part of the rule. That needs the group's agreement and probably an amendment to the DR-0036 you're drafting.
A third-party strict validator pinned to 3.2.0–3.2.7 that never takes the errata could still reject a field, but only if a non-conformant seller ignores the served release.
What it buys:
All six features stay in 3.3. Definition pins matter most, because the 3.3 docs list them as required behavior.
The proposal flow complies with DR-0019 now instead of carrying the defect to 4.0.
Your M1 point still applies to everything else: a moved field on an open object is ignored rather than rejected, so negotiation-aware senders are needed either way.
If the group prefers the strict rule without this exception, I'll take your list as is. Either way, can we settle it at the next session, or async this week, so 3.3.0-beta.1 isn't blocked? I'm happy to write the errata PR and the #8502 changes if we go this way.
Thanks, Brian. You're right about the cause. DR-0019 says roots and containers stay open, and nobody decided to close the 3.2 proposal flow. #8518 shows the same closure even blocks the version echo the versioning page requires. So I'd treat it as a defect to correct, not a rule to live with.
Where I land on your four steps:
Reopen the proposal-flow roots, rows and containers: yes, as a one-time correction. DR-0036 will record it as exactly that, with the list of reopened schemas and the reason (they contradicted DR-0019). The general rule stays as accepted on Monday: a minor adds only to open schemas, and a closed schema stays closed until the next major.
3.3 fields on the reopened objects go only to 3.3 peers: yes. DR-0024 already requires it, and DR-0034 covers requests that only say "version 3". I accept that this relies on sellers shaping responses for the reopened objects. That's the price of correcting the defect inside 3.x.
Adding to commercial-terms in 3.3: no. It's the one object closed on purpose, for the reason you gave: a buyer must not be bound by terms it can't read. Adding fields to it in a minor, and relying on negotiation to keep that safe, brings back the exception we decided against on Monday, for exactly the object where it matters most. So definition pins, purchase rights, and choosing installments inside purchases[] wait for 4.0. In 4.0, commercial-terms gets a declared extension point with must-understand, which is what binding terms need.
With that, holds, the list_products version echo, product_ids_exclusive, and the installment parts on products and refinement all stay in 3.3. Pins, rights, and installment selection move to 4.0.
I'll do the work: the reopening PR, DR-0036, and the check change. I'll link them here.
build(schemas): keep closed schemas closed within a major, with recorded corrections #8530: DR-0036 and the check change. It adds the rule and a narrow correction rule. A minor or patch release may change a closed schema only to make it accept what the published spec text already required, and each correction names that text. It records two corrections: (a) reopening the 3.2 proposal flow, and (b) the adcp_version echo.
Two updates to my earlier comment. Installment selection waits for 4.0 as a whole: its refinement fields are checked against the installments in purchases[], which wait, so they can't ship alone. The installment fields on products stay. Later-proposal consent does ship: reopening opportunity-context lets it through. @bokelley, can you confirm you're OK with that?
On #8523 and #8524: they implement correction (b), so they add their entries to the corrections file that #8530 introduces. Once #8531 lands, #8523 may need fewer entries, because (a) already covers the proposal-flow responses.
Staying in 3.3, as you listed: holds, the list_products version echo, product_ids_exclusive, and the installment fields on products and in refinement.
feat(media-buy): seller offers on accepted buys #8102 splits: the offer lane ships where its fields land on reopened, non-commercial-terms objects. The seller-held allowance lane (holder/purchase_index on change-term, inside commercial terms) moves to 4.0.
4.0 work list from this thread, async (the Nov 2 session is cancelled; objections and owner volunteers by Fri 2026-11-06):
commercial-terms gets a declared extension point with must-understand. It brings back definition pins, purchase rights, installment selection in purchases[], and seller-held adjustable terms (holder/purchase_index). Proposed owner: @KonstantinMirin.
Reopen closed roots and containers under DR-0019, beyond the DR-0036 corrections, together with declared extension points.
Enforce maxItems/maxLength on stable fields from the bounds inventory. 3.3 enforces bounds only on new and experimental fields.
Property and collection lists are always passed by reference, never inline.
Remove the deprecated surfaces:adcp_major_version, the natural-key account reference after provisioning, the identity-rekey machinery, principal_kind, and the RFC 9421 signing profiles.
Tracked in the v4 compatibility registry. Reply with objections or to take an item.
@KonstantinMirin: one ask on the GA path for 3.3 (#8600). scripts/lint-additive-minor-baseline.json still has 26 entries marked "pending WG decision". Could you post a disposition table here: each entry with one of (a) move to the nearest open parent, (b) record a DR-0036 correction, with the spec text it restores, or (c) defer to 4.0 and withdraw from 3.3? We'll approve it by consent, with objections by Fri 2026-11-13, and land the moves before the GA beta (target: week of Nov 16). Happy for you to own the PRs, or tell us which ones you'd like taken off your plate.
I'd like to do it as a module, per #8359, rather than as an extension point added to a closed schema. So commercial-terms would get its own identity and version, and core would declare the extension point it attaches to, so core doesn't change when the module does.
On must-understand, I'd read it as one thing here: a receiver that doesn't recognize a declared extension refuses the terms. That follows from what you've already set up, since terms_digest signs these terms and a buyer shouldn't be bound by terms it can't read.
I'll put it up as its own PR so you can see how it holds together. If it works, I think it could be a template for later modules. Let me know what you think.
A correction on item 1. I said I'd do commercial-terms as a module, and I built it that way, but an audit against #8359 showed it isn't one. It's an extension point with four extensions. I don't think we can create a real module until the schemas themselves are layered, and I've written that up on #8359: #8359 (comment)
I'm still happy to own item 1 as an extension point and four extensions, and I'd sequence it behind a clear boundary around what terms_digest covers.
Before I post the disposition table, there's one thing I'd like to raise, because it affects most of the table.
Deprecated surfaces are where I'd like to disagree. I don't think we should add fields to them. It keeps the old surface growing while we're asking people to move off it, and every field that exists only there is a new gap for anyone migrating. 3.3 currently adds these, compared with 3.2.8:
I'd suggest withdrawing all of these from the deprecated surfaces. 3.3 is still in beta, so that breaks no stable release, and the collections, readiness and target_audiences fields are experimental anyway. Where a replacement doesn't have a field yet, I'd add it there, so the new surface is where the new capability lives.
Opening the closed 3.2 schemas is what makes this possible, and with so few 3.2 implementers so far, I don't think closure should stop us, as long as each one is recorded in DR-0036. The additive-minor guard covers this going forward, and I'd keep commercial-terms closed because terms_digest signs it. The get_products page says target_audiences waits for 4.0 on buy_products and list_products because their schemas are closed, so today closure is what pushes new capability onto the old tasks. Readiness looks straightforward: list_products already takes account and returns execution_requirements, and its root is open. The exact-package forecast is the one I can't place. I don't see an obvious home for it on the discovery tasks, so I'd really like your view on where it should go.
I'm not asking to fork shared schemas. A field that reaches a deprecated task through a type it shares with live tasks, like core/targeting.json or core/reporting-webhook.json, should stay, because a second copy would be worse.
3.2 and 3.3 need to stay interoperable. When I raised #7403 in September, #7444 covered most of it, and the compatibility doc now says a facade preserves a field or rejects it, but never drops it. I'd like that to stay true through 3.3. The doc doesn't mention 3.3 yet, and some fields aren't covered: filters.audience_activation_methods, for example, has no compact member and no obligation. So I'd suggest that every field a buyer can send to get_products at 3.2 or 3.3 has a stated outcome in that doc: a compact field, a facade obligation, or a rejection at its path. I also still owe you the scenarios you asked me to open as a PR on #7403, and I'll do that.
For the GA criteria in #8600, I'd propose adding two things: no 3.3 field exists only on a deprecated surface, and the compatibility doc covers every get_products field.
The table for the 26 entries will follow from this. Does this seem reasonable to you? Please tell me if there's a reason some of these need to stay on the deprecated surfaces.
Here's the disposition table you asked for on Oct 31. It follows the position in my earlier comment: no new fields on deprecated tasks, and the commercial-terms root stays closed. Everything else stays in 3.3 where you put it.
Result: 11 entries stay, 2 move, 12 are withdrawn and 1 is restored.
I decided each entry with four rules, in this order:
A published enum value that 3.3 removed comes back.
A field on a deprecated task leaves it. If the need is real and no live task has the field, it goes on the replacement.
A field that changes the meaning of a signed field leaves. The digest proves the bytes didn't change, but it can't stop a buyer from misreading them.
Everything else stays, and DR-0036 records the reopening.
How the 11 entries stay. Nine of them sit on schemas that DR-0019 says should be open. It says "Response roots, capability blocks, and container objects stay open", and "A Class B schema without an escape hatch is a defect." That's the same basis as the proposal-flow correction we agreed on Oct 27, so I'd record these as one more correction of the same kind. The other two are the trackers and landing_page_url fields on assign. No published text required them, and DR-0019 allows a discriminated-union branch to stay closed. So I'd keep that branch closed and record just those two names as a named exception. sync_creatives is where assignment settings belong, and #8695 builds on them.
Minute dayparts, the spot capability and the overlay requirement filters stay where you put them. The triage moved them to new fields, so this saves a wire change.
The assign fields stay. The triage withdrew them.
The exact-package forecast leaves get_products. Where it goes is the open question from my earlier comment.
Lineage, collection cards and the creative start policy leave the deprecated tasks and stay on the live ones. The triage let them ship wherever the schema was open.
Everything else in the triage stands. All of this is in the 3.3 betas, so no stable release breaks.
Entries 1 to 5 are narrowings you already accepted, so they go unless you'd like to keep one. Entry 1 is the one I'd give back first, because #8696 asserts on it.
The controller returns the proposal ID in simulated, which holds "Values injected or applied by this call". hold_unavailable_batch.yaml reads it there. forced.arm also loses the unable value 3.3 added.
It stays on push_notification_config and reporting_webhook. A per-row profile only matters until 4.0 removes the RFC 9421 profile. This row has ext, so DR-0019 lets it stay closed.
It's part of a requirements container, and DR-0019 keeps containers open.
10
core/targeting-overlay-support.json, daypart support time_granularity
Stay, branch opens
It's a capability block, and DR-0019 keeps those open.
11
core/sla-window.json, clock
Move to core/creative-review-sla.json
creative_policy.review_sla keeps business time. sla-window goes back to its 3.2.8 form, and escalation-sla loses clock. The signed terms use sla-window through change_terms[].processing_sla, and 3.2.8 says "Sellers include weekends and non-working periods in the maximum."
One catch: in 3.2, a product's demographics: true means age, but it matches a gender requirement. The schema description needs an exception for that, and seller matching has to follow it. The training agent needs the same fix.
media-buy/package-forecast-input.json goes too, because nothing else uses it. Its home is the open question. Wherever it lands, it has to carry the goal, because #8448 says "A guaranteed forecast depends on the goal".
Withdraw, add to the 4.0 list. The same registry already names offer_filters for 4.0. core/product-offer-filters.json has ext, so DR-0019 gives no reason to open it.
include_collections, collections
get_products
Withdraw. list_products, request_proposals and refine_proposals keep them.
include_execution_readiness, execution_readiness
get_products
Move to the list_products request and response, which are open.
trackers, landing_page_url on core/creative-assignment.json
create_media_buy, update_media_buy
Withdraw. assign writes them, and get_media_buys reads them.
metric_id on core/performance-standard.json and core/product-offer-filters.json
Withdraw with entry 6. Otherwise the filter matches on a value canonical products don't carry.
counting_mechanisms (experimental)
create_media_buy, update_media_buy, get_products
Withdraw. The registry already has it for 4.0 as canonical_counting_mechanisms_required_in_4_0.
price_adjustments (experimental)
get_products pricing options
Withdraw, add to the 4.0 list on the canonical pricing option. It needs a registry entry.
available_installment_statuses (experimental)
get_products
Move to core/canonical-reporting-capabilities.json, a capability block that opens with the other nine.
expected_confirmation_at stays everywhere, and my earlier comment was wrong to list it. It belongs on core/task-submitted.json, which every submitted response uses, create_media_buy included.
One definition per field. When these land, I'd give each field one definition:
Field
Defined today in
One definition
price_adjustments
7 pricing options
A parent schema they all use
trackers
5 places, 3 of them with minItems
core/assignment-trackers.json, with minItems on the requests
signing_profile
2 webhooks
core/webhook-signing.json, with its rule against sending authentication too
predecessor_media_buy_id, lineage_reason
2 tasks
core/media-buy-lineage.json
include_collections, collections
3 tasks
One request and one response schema
start_requires_all_creatives_approved
5 places
core/creative-start-policy.json
collection_lists, collection_lists_exclude
2 identical arrays on targeting
core/collection-list-refs.json
Daypart start_time, end_time
2 identical patterns
core/civil-time-hhmm.json
expected_confirmation_at
5 submitted responses
core/task-submitted.json
None of these changes what validates, except expected_confirmation_at. A task that doesn't declare it today starts rejecting a non-date value.
A correction to my earlier comment. I said filters.audience_activation_methods has no obligation in the compatibility doc. The doc's general rule, preserve the field or reject it, does cover it. What's missing is an outcome for that specific field.
PRs. I'll take DR-0036, the correction and the named exception, the baseline, and entries 2, 3, 6 and 26. Could you take 1, 11, 12 and 20 to 25? They touch your storyboards and the training agent.
@KonstantinMirin: thanks for the table. Yes to the split. I'll take entries 1, 11, 12 and 20–25, and I'll also take the deprecated-task fields table, since it touches the same files.
PRs, all drafts that reference this issue:
Entry 1.forced.proposal_id and the unable arm leave comply-test-controller-response.json. hold_unavailable_batch.yaml and the training agent read the proposal ID from simulated.
Entries 11–12.sla-window goes back to its 3.2.8 shape. clock and business_calendar_id move to core/creative-review-sla.json, and escalation-sla loses clock.
Entries 20–25, plus the get_products rows of the deprecated-task table. This covers assignments[]trackers/landing_page_url; refine[].package, package-forecast-input.json and refinement_applied[]; filters.target_audiences; include_collections/collections; include_execution_readiness/execution_readiness moving to list_products; and the experimental price_adjustments and counting_mechanisms.
The create_media_buy / update_media_buy rows. This covers lineage, artifact_webhook.signing_profile, start_requires_all_creatives_approved, target_audiences, trackers/landing_page_url on creative-assignment, and counting_mechanisms.
Coordination:
metric_id on performance-standard and product-offer-filters goes with your entry 6, so I'll leave those two rows to your PR unless you'd rather I take them.
available_installment_statuses moves into canonical-reporting-capabilities, which opens with your capability-block set. I'll make that move once your opening has landed.
Each PR drops its own rows from scripts/lint-additive-minor-baseline.json. If your baseline PR lands first, I'll rebase.
Timing: drafts up by Thu Nov 12. Nothing merges until the consent window closes Fri Nov 13.
The create_media_buy / update_media_buy rows, plus counting_mechanisms on the shared measurement-terms
Entries 4 and 5 (review_sla on canonical-media-buy-action-fields and media-buy-available-action) didn't have an owner, so I'm folding them into #8762, next to the review-SLA change they depend on.
fix(schema): keep 3.3 additions off the deprecated create_media_buy and update_media_buy (#8501) #8767 leaves a gap. Removing trackers and landing_page_url from update_media_buy assignments takes away the only atomic way to clear one override while keeping rotation, group, sequence and placements, because assign can't carry those settings. The PR documents a non-atomic replace-then-assign workaround. We either accept that for 3.3, or let assign carry those controls.
Once all four land, media_buy.audience_evidence_targets and core/audience-evidence-target.json have nothing left in 3.3 that uses them. I'll remove them in whichever PR merges last.
Activity
bokelley commentedon Oct 26, 2026
WG Session C (2026-10-26) accepted the problem. Adding a property to a schema published with
additionalProperties: falsebreaks older strict validators, exactly as removing a field does.DR-0032 and DR-0033 are parked until this is settled.
KonstantinMirin commentedon Oct 26, 2026
Thanks for taking this up at Session C. Here's the triage I owe for the 3.3 additions to closed schemas.
The rule I applied: a minor release only adds, and it never adds to a closed schema (
additionalProperties: false). A closed schema stays exactly as it is until 4.0. I didn't make exceptions for fields sent only to 3.3 peers, or for experimental fields, because either one would still break a strict 3.2 validator.For each of the 57 new fields on closed schemas there are two outcomes. Either the field moves to the nearest open object that contains its closed parent, keeping its meaning, or it waits for 4.0. 15 move and 42 wait. The
evaluatorvalue removed from thex-entityregistry comes back.Most of the 42 share one cause. In 3.2,
list_products,request_proposals,refine_proposals,buy_products,accept_proposal,decline_proposalsandcontrol_media_buywere published closed from the request or response object down. So proposals, purchases and commercial terms have no open place to grow in 3.x. DR-0019 asks for roots and containers to stay open, so I'd treat that as something to reopen in 4.0, together with declared extension points, rather than as anyone's mistake today.daypart_windowson targeting, with the granularity onoverlay_supportandget_productspackage_forecasts[]andpackage_forecast_results[]onget_productsreporting_capabilitiesget_productsrequestexpected_confirmation_at, the creative start policy,metric_id, assignment landing page and trackers, webhooksigning_profilelist_productsversion echo,product_ids_exclusiveThe full per-field list, with the open object each field moves to and the reason for each deferral, is in this gist.
Follow-ups I'll open:
identityblock was closed in 3.0.0, opened in 3.0.5, and gainedbrand_json_urlin 3.1.0.evaluator, remove the deferred fields with the capability flags of the features that wait, and one PR per moved feature, agreed with its owner first.@bokelley, I know this costs 3.3 features the group already agreed on, and I'm sorry to bring it this late. Could you check the list before we cut 3.3.0-beta.1? Six features wait for 4.0, and the moves change the wire form of #8025 and #8468, so their owners should see it too. I'm happy to walk through any of it.
feat(schema): selection-dependent price_adjustments on fixed-price pr…
docs(governance): ratify DR-0034; split DR-0032/0033 out to #8519
feat(schema): selection-dependent price_adjustments on fixed-price pr…
docs(governance): ratify DR-0034 (major-only requests serve the oldes…
bokelley commentedon Oct 27, 2026
Thanks, Konstantin. This is careful work. I'd accept the analysis method, the 15 moves, the narrowings, the
evaluatorrestore and the check fix (closed anywhere in the major) as written. Theidentitycase shows why the fix matters.Before we accept the 42 deferrals, I'd like to question their main cause rather than live with it until 4.0. Most of them, and all six features that slip (holds, installment selection, purchase rights, definition pins, the
list_productsversion echo,product_ids_exclusive), come from one thing: the 3.2 proposal flow was published closed from the root down. As far as I can find, nobody decided that, and it contradicts our own records.What the records say:
additionalProperties: falseonly on row schemas: join-key objects, or row envelopes that keep anextescape hatch. It also says outright that "response roots, capability blocks, and container objects stay open."What 3.2 shipped:
extrequest_proposalsrequest (root)request_proposalsresponse (root)list_productsrequest (root)buy_productsrequest (root)canonical-proposal(container)commercial-termscanonical-productThe roots and containers are closed, which DR-0019 says they must not be. Several of them also lack the
extslot DR-0019 requires of a closed envelope. None carries a note explaining why. I read this as a 3.2 defect, not a deliberate design.One exception is deliberate in substance:
commercial-terms. A buyer must not be bound by terms it can't read, and those terms are whatterms_digestsigns. If the object were open, a 3.2 buyer could accept a 3.3 term (rights, pins, installments) it silently ignored. Exhaustive keys there fit DR-0019's join-key class, and I'd keep it closed.Proposal:
canonical-proposal,canonical-product,product-discovery-criteria,proposal-refinement,package-control), and keepcommercial-termsclosed. Loosening is non-breaking under DR-0019: anything valid before stays valid. These tasks are new in 3.2, so the strict validators that exist for them are almost all the SDKs, and we'll patch those.supported_versions(the 3.2 grader blocks otherwise), so negotiation is reliable here. An un-updated strict 3.2.0–3.2.7 validator therefore never sees a 3.3 field from a conformant seller.purchases[]) are offered only to buyers that negotiated 3.3. A seller never serves a pre-3.3 peer a proposal whosecommercial-termsuses them. It returnsVERSION_UNSUPPORTEDfor that proposal instead of stripping terms, which would also breakterms_digest. This keeps the reason the object is closed: nobody receives terms they can't read.What this costs:
What it buys:
If the group prefers the strict rule without this exception, I'll take your list as is. Either way, can we settle it at the next session, or async this week, so 3.3.0-beta.1 isn't blocked? I'm happy to write the errata PR and the #8502 changes if we go this way.
KonstantinMirin commentedon Oct 27, 2026
Thanks, Brian. You're right about the cause. DR-0019 says roots and containers stay open, and nobody decided to close the 3.2 proposal flow. #8518 shows the same closure even blocks the version echo the versioning page requires. So I'd treat it as a defect to correct, not a rule to live with.
Where I land on your four steps:
commercial-termsin 3.3: no. It's the one object closed on purpose, for the reason you gave: a buyer must not be bound by terms it can't read. Adding fields to it in a minor, and relying on negotiation to keep that safe, brings back the exception we decided against on Monday, for exactly the object where it matters most. So definition pins, purchase rights, and choosing installments insidepurchases[]wait for 4.0. In 4.0,commercial-termsgets a declared extension point with must-understand, which is what binding terms need.With that, holds, the
list_productsversion echo,product_ids_exclusive, and the installment parts on products and refinement all stay in 3.3. Pins, rights, and installment selection move to 4.0.I'll do the work: the reopening PR, DR-0036, and the check change. I'll link them here.
KonstantinMirin commentedon Oct 27, 2026
The three PRs are up:
adcp_versionecho.main, stacked on build(schemas): keep closed schemas closed within a major, with recorded corrections #8530. It reopens 24 locations and still rejects the three legacy keys the closure used to reject.3.2.x, with no 3.3 fields.Two updates to my earlier comment. Installment selection waits for 4.0 as a whole: its refinement fields are checked against the installments in
purchases[], which wait, so they can't ship alone. The installment fields on products stay. Later-proposal consent does ship: reopeningopportunity-contextlets it through. @bokelley, can you confirm you're OK with that?On #8523 and #8524: they implement correction (b), so they add their entries to the corrections file that #8530 introduces. Once #8531 lands, #8523 may need fewer entries, because (a) already covers the proposal-flow responses.
bokelley commentedon Oct 27, 2026
Maintainer decisions (2026-10-27): aligned with your plan, @KonstantinMirin.
DR-0036 one-time correction: yes. Reopen the 3.2 proposal flow (build(schemas): keep closed schemas closed within a major, with recorded corrections #8530, fix(schemas): reopen the 3.2 proposal-flow schemas closed against DR-0019 #8531, fix(schemas): reopen the proposal-flow schemas closed against DR-0019 on 3.2.x (#8501) #8532). I'll merge them as they go green; they touch gated paths.
Later-proposal consent ships in 3.3: confirmed (
opportunity-contextreopened).commercial-termsstays closed: agreed. Moving to 4.0, where commercial-terms gets a declared extension point with must-understand:purchases[].Staying in 3.3, as you listed: holds, the
list_productsversion echo,product_ids_exclusive, and the installment fields on products and in refinement.feat(media-buy): seller offers on accepted buys #8102 splits: the offer lane ships where its fields land on reopened, non-commercial-terms objects. The seller-held allowance lane (
holder/purchase_indexonchange-term, inside commercial terms) moves to 4.0.Capabilities byte cap (Spec tension: creative.supported_formats pushes capabilities past the 64 KiB that gates signing-key discovery #8198/spec(capabilities): structural bounds are the limit; the byte cap is a deployment backstop #8526): see Spec tension: creative.supported_formats pushes capabilities past the 64 KiB that gates signing-key discovery #8198. It stays as a backstop only.
build(schemas): keep closed schemas closed within a major, with recor…
fix(schemas): reopen the proposal-flow schemas closed against DR-0019…
bokelley commentedon Oct 30, 2026
4.0 work list from this thread, async (the Nov 2 session is cancelled; objections and owner volunteers by Fri 2026-11-06):
commercial-termsgets a declared extension point with must-understand. It brings back definition pins, purchase rights, installment selection inpurchases[], and seller-held adjustable terms (holder/purchase_index). Proposed owner: @KonstantinMirin.maxItems/maxLengthon stable fields from the bounds inventory. 3.3 enforces bounds only on new and experimental fields.adcp_major_version, the natural-key account reference after provisioning, the identity-rekey machinery,principal_kind, and the RFC 9421 signing profiles.Tracked in the v4 compatibility registry. Reply with objections or to take an item.
bokelley commentedon Oct 31, 2026
@KonstantinMirin: one ask on the GA path for 3.3 (#8600).
scripts/lint-additive-minor-baseline.jsonstill has 26 entries marked "pending WG decision". Could you post a disposition table here: each entry with one of (a) move to the nearest open parent, (b) record a DR-0036 correction, with the spec text it restores, or (c) defer to 4.0 and withdraw from 3.3? We'll approve it by consent, with objections by Fri 2026-11-13, and land the moves before the GA beta (target: week of Nov 16). Happy for you to own the PRs, or tell us which ones you'd like taken off your plate.docs(governance): propose DR-0032 and DR-0033 (parked pending #8501)
docs(governance): revise DR-0032 and DR-0033 against DR-0036
KonstantinMirin commentedon Nov 2, 2026
Happy to take item 1, thanks for offering it.
I'd like to do it as a module, per #8359, rather than as an extension point added to a closed schema. So
commercial-termswould get its own identity and version, and core would declare the extension point it attaches to, so core doesn't change when the module does.On must-understand, I'd read it as one thing here: a receiver that doesn't recognize a declared extension refuses the terms. That follows from what you've already set up, since
terms_digestsigns these terms and a buyer shouldn't be bound by terms it can't read.I'll put it up as its own PR so you can see how it holds together. If it works, I think it could be a template for later modules. Let me know what you think.
KonstantinMirin commentedon Nov 3, 2026
A correction on item 1. I said I'd do
commercial-termsas a module, and I built it that way, but an audit against #8359 showed it isn't one. It's an extension point with four extensions. I don't think we can create a real module until the schemas themselves are layered, and I've written that up on #8359: #8359 (comment)I'm still happy to own item 1 as an extension point and four extensions, and I'd sequence it behind a clear boundary around what
terms_digestcovers.KonstantinMirin commentedon Nov 3, 2026
Hi Brian,
Before I post the disposition table, there's one thing I'd like to raise, because it affects most of the table.
Deprecated surfaces are where I'd like to disagree. I don't think we should add fields to them. It keeps the old surface growing while we're asking people to move off it, and every field that exists only there is a new gap for anyone migrating. 3.3 currently adds these, compared with 3.2.8:
create_media_buypredecessor_media_buy_id,lineage_reasonbuy_products,accept_proposalcreate_media_buyexpected_confirmation_at(response)buy_products,accept_proposalcreate_media_buyartifact_webhook.signing_profileartifact_webhookitself isn't on themcreate_media_buy,update_media_buystart_requires_all_creatives_approvedon packagescontrol_media_buyyes,buy_productsnocreate_media_buy,update_media_buytarget_audienceson packagesget_productsinclude_collections,collections(response)list_products,request_proposals,refine_proposalsget_productsfilters.target_audiencesget_productsinclude_execution_readiness,execution_readiness(response)get_productsrefine[].package,refinement_applied[].result_product_id,resolved_start_time,resolved_end_timesync_creativesassignments[]trackers,landing_page_urlassignment_operationsI'd suggest withdrawing all of these from the deprecated surfaces. 3.3 is still in beta, so that breaks no stable release, and the collections, readiness and
target_audiencesfields are experimental anyway. Where a replacement doesn't have a field yet, I'd add it there, so the new surface is where the new capability lives.Opening the closed 3.2 schemas is what makes this possible, and with so few 3.2 implementers so far, I don't think closure should stop us, as long as each one is recorded in DR-0036. The additive-minor guard covers this going forward, and I'd keep
commercial-termsclosed becauseterms_digestsigns it. Theget_productspage saystarget_audienceswaits for 4.0 onbuy_productsandlist_productsbecause their schemas are closed, so today closure is what pushes new capability onto the old tasks. Readiness looks straightforward:list_productsalready takesaccountand returnsexecution_requirements, and its root is open. The exact-package forecast is the one I can't place. I don't see an obvious home for it on the discovery tasks, so I'd really like your view on where it should go.I'm not asking to fork shared schemas. A field that reaches a deprecated task through a type it shares with live tasks, like
core/targeting.jsonorcore/reporting-webhook.json, should stay, because a second copy would be worse.3.2 and 3.3 need to stay interoperable. When I raised #7403 in September, #7444 covered most of it, and the compatibility doc now says a facade preserves a field or rejects it, but never drops it. I'd like that to stay true through 3.3. The doc doesn't mention 3.3 yet, and some fields aren't covered:
filters.audience_activation_methods, for example, has no compact member and no obligation. So I'd suggest that every field a buyer can send toget_productsat 3.2 or 3.3 has a stated outcome in that doc: a compact field, a facade obligation, or a rejection at its path. I also still owe you the scenarios you asked me to open as a PR on #7403, and I'll do that.For the GA criteria in #8600, I'd propose adding two things: no 3.3 field exists only on a deprecated surface, and the compatibility doc covers every
get_productsfield.The table for the 26 entries will follow from this. Does this seem reasonable to you? Please tell me if there's a reason some of these need to stay on the deprecated surfaces.
KonstantinMirin commentedon Nov 3, 2026
Hi Brian,
Here's the disposition table you asked for on Oct 31. It follows the position in my earlier comment: no new fields on deprecated tasks, and the
commercial-termsroot stays closed. Everything else stays in 3.3 where you put it.Result: 11 entries stay, 2 move, 12 are withdrawn and 1 is restored.
I decided each entry with four rules, in this order:
How the 11 entries stay. Nine of them sit on schemas that DR-0019 says should be open. It says "Response roots, capability blocks, and container objects stay open", and "A Class B schema without an escape hatch is a defect." That's the same basis as the proposal-flow correction we agreed on Oct 27, so I'd record these as one more correction of the same kind. The other two are the
trackersandlanding_page_urlfields onassign. No published text required them, and DR-0019 allows a discriminated-union branch to stay closed. So I'd keep that branch closed and record just those two names as a named exception.sync_creativesis where assignment settings belong, and #8695 builds on them.What differs from the triage you accepted on Oct 27:
assignfields stay. The triage withdrew them.get_products. Where it goes is the open question from my earlier comment.Everything else in the triage stands. All of this is in the 3.3 betas, so no stable release breaks.
Entries 1 to 5 are narrowings you already accepted, so they go unless you'd like to keep one. Entry 1 is the one I'd give back first, because #8696 asserts on it.
Paths are under
static/schemas/source/.compliance/comply-test-controller-response.json,forced.proposal_idsimulated, which holds "Values injected or applied by this call".hold_unavailable_batch.yamlreads it there.forced.armalso loses theunablevalue 3.3 added.core/agent-notification-config.json,signing_profilepush_notification_configandreporting_webhook. A per-row profile only matters until 4.0 removes the RFC 9421 profile. This row hasext, so DR-0019 lets it stay closed.core/notification-config.json,signing_profilecore/canonical-media-buy-action-fields.json,review_slacreative_policy.review_slaalready carries this value, and a buy doesn't change it.core/media-buy-available-action.json,review_slaget_media_buysaccepts either row, so 4 and 5 go together.core/canonical-performance-standard.json,metric_idcore/daypart-target.json,start_timeexta defect. TheoneOfstill rejects a window that mixes hours and minutes.core/daypart-target.json,end_timecore/targeting-overlay-requirements.json, daypart requirementtime_granularitycore/targeting-overlay-support.json, daypart supporttime_granularitycore/sla-window.json,clockcore/creative-review-sla.jsoncreative_policy.review_slakeeps business time.sla-windowgoes back to its 3.2.8 form, andescalation-slalosesclock. The signed terms usesla-windowthroughchange_terms[].processing_sla, and 3.2.8 says "Sellers include weekends and non-working periods in the maximum."core/sla-window.json,business_calendar_idcore/spot-reporting-capability.json,available_statusesext. Canonical products keep it as well.core/spot-reporting-capability.json,supports_break_positioncore/targeting-overlay-requirements.json,collection_listscore/targeting-overlay-requirements.json,collection_lists_excludecore/targeting-overlay-requirements.json, demographics requirementgenderdemographics: truemeans age, but it matches a gender requirement. The schema description needs an exception for that, and seller matching has to follow it. The training agent needs the same fix.creative/sync-creatives-request.json,assignlanding_page_urlsupports_assignment_landing_page.creative/sync-creatives-request.json,assigntrackerssupports_assignment_trackers.creative/sync-creatives-request.json,assignments[]landing_page_urlassignments[]is deprecated, andassignhas the field. No storyboard sends it onassignments[].creative/sync-creatives-request.json,assignments[]trackersmedia-buy/get-products-request.json,refine[].packageget_productsmedia-buy/package-forecast-input.jsongoes too, because nothing else uses it. Its home is the open question. Wherever it lands, it has to carry the goal, because #8448 says "A guaranteed forecast depends on the goal".media-buy/get-products-response.json,refinement_applied[].result_product_idrefine[].package.media-buy/get-products-response.json,refinement_applied[].resolved_start_timemedia-buy/get-products-response.json,refinement_applied[].resolved_end_timecore/x-entity-types.json, valueevaluatorevaluator_idremoval stands. 3.2.8 says "Existing values are never removed or renamed."Deprecated-task fields outside the 26. The baseline doesn't list these, because their schemas are open. The same rules apply.
predecessor_media_buy_id,lineage_reasoncreate_media_buybuy_productsandaccept_proposalkeep them.artifact_webhook.signing_profilecreate_media_buyartifact_webhook.start_requires_all_creatives_approvedcreate_media_buy,update_media_buypackagescontrol_media_buykeeps it, and it doesn't need to be on the purchase, because it "does not change accepted commercial terms".target_audiencescreate_media_buy,update_media_buypackagespurchase_target_audiences_required_in_4_0says a purchase must record it, and the purchase is signed. My earlier comment said reopening makes room for this field. It doesn't.filters.target_audiencesget_productsoffer_filtersfor 4.0.core/product-offer-filters.jsonhasext, so DR-0019 gives no reason to open it.include_collections,collectionsget_productslist_products,request_proposalsandrefine_proposalskeep them.include_execution_readiness,execution_readinessget_productslist_productsrequest and response, which are open.trackers,landing_page_urloncore/creative-assignment.jsoncreate_media_buy,update_media_buyassignwrites them, andget_media_buysreads them.metric_idoncore/performance-standard.jsonandcore/product-offer-filters.jsoncreate_media_buy,update_media_buy,get_products, offer filterscounting_mechanisms(experimental)create_media_buy,update_media_buy,get_productscanonical_counting_mechanisms_required_in_4_0.price_adjustments(experimental)get_productspricing optionsavailable_installment_statuses(experimental)get_productscore/canonical-reporting-capabilities.json, a capability block that opens with the other nine.expected_confirmation_atstays everywhere, and my earlier comment was wrong to list it. It belongs oncore/task-submitted.json, which every submitted response uses,create_media_buyincluded.One definition per field. When these land, I'd give each field one definition:
price_adjustmentstrackersminItemscore/assignment-trackers.json, withminItemson the requestssigning_profilecore/webhook-signing.json, with its rule against sendingauthenticationtoopredecessor_media_buy_id,lineage_reasoncore/media-buy-lineage.jsoninclude_collections,collectionsstart_requires_all_creatives_approvedcore/creative-start-policy.jsoncollection_lists,collection_lists_excludecore/collection-list-refs.jsonstart_time,end_timecore/civil-time-hhmm.jsonexpected_confirmation_atcore/task-submitted.jsonNone of these changes what validates, except
expected_confirmation_at. A task that doesn't declare it today starts rejecting a non-date value.A correction to my earlier comment. I said
filters.audience_activation_methodshas no obligation in the compatibility doc. The doc's general rule, preserve the field or reject it, does cover it. What's missing is an outcome for that specific field.PRs. I'll take DR-0036, the correction and the named exception, the baseline, and entries 2, 3, 6 and 26. Could you take 1, 11, 12 and 20 to 25? They touch your storyboards and the training agent.
Objections by Fri 2026-11-13, as you proposed.
bokelley commentedon Nov 5, 2026
@KonstantinMirin: thanks for the table. Yes to the split. I'll take entries 1, 11, 12 and 20–25, and I'll also take the deprecated-task fields table, since it touches the same files.
PRs, all drafts that reference this issue:
forced.proposal_idand theunablearm leavecomply-test-controller-response.json.hold_unavailable_batch.yamland the training agent read the proposal ID fromsimulated.sla-windowgoes back to its 3.2.8 shape.clockandbusiness_calendar_idmove tocore/creative-review-sla.json, andescalation-slalosesclock.get_productsrows of the deprecated-task table. This coversassignments[]trackers/landing_page_url;refine[].package,package-forecast-input.jsonandrefinement_applied[];filters.target_audiences;include_collections/collections;include_execution_readiness/execution_readinessmoving tolist_products; and the experimentalprice_adjustmentsandcounting_mechanisms.create_media_buy/update_media_buyrows. This covers lineage,artifact_webhook.signing_profile,start_requires_all_creatives_approved,target_audiences,trackers/landing_page_urloncreative-assignment, andcounting_mechanisms.Coordination:
metric_idonperformance-standardandproduct-offer-filtersgoes with your entry 6, so I'll leave those two rows to your PR unless you'd rather I take them.available_installment_statusesmoves intocanonical-reporting-capabilities, which opens with your capability-block set. I'll make that move once your opening has landed.scripts/lint-additive-minor-baseline.json. If your baseline PR lands first, I'll rebase.Timing: drafts up by Thu Nov 12. Nothing merges until the consent window closes Fri Nov 13.
🤖 Generated with Claude Code
bokelley commentedon Nov 6, 2026
@KonstantinMirin: my half is up as drafts. None of them merges before the consent window closes on Fri Nov 13.
simulated;forced.proposal_idandarm: unableare withdrawnsla-windowgoes back to its 3.2.8 shape;clockandbusiness_calendar_idmove tocreative-review-slaget_productsrows of the deprecated-task table (execution_readinessmoves tolist_products)create_media_buy/update_media_buyrows, pluscounting_mechanismson the sharedmeasurement-termsEntries 4 and 5 (
review_slaoncanonical-media-buy-action-fieldsandmedia-buy-available-action) didn't have an owner, so I'm folding them into #8762, next to the review-SLA change they depend on.Three things for Monday:
creative-review-slaschema is open, by default under DR-0036. It was effectively closed before, through its$refto the closedsla-window. If you read it as DR-0019 class A, say so on fix(schema): keep sla-window at its 3.2 shape; review SLA stays on the product (#8501 entries 4, 5, 11, 12) #8762.refine[].packageleavesget_products. Per RFC: check a proposed package's availability before create_media_buy (structured package on refine) #8448, wherever it goes has to carry the goal.trackersandlanding_page_urlfromupdate_media_buyassignments takes away the only atomic way to clear one override while keeping rotation, group, sequence and placements, becauseassigncan't carry those settings. The PR documents a non-atomic replace-then-assignworkaround. We either accept that for 3.3, or letassigncarry those controls.Once all four land,
media_buy.audience_evidence_targetsandcore/audience-evidence-target.jsonhave nothing left in 3.3 that uses them. I'll remove them in whichever PR merges last.🤖 Generated with Claude Code
fix(schema): keep sla-window at its 3.2 shape; business time moves to…
fix(schema): review SLA stays on the product; withdraw review_sla fro…