feat: 3.1.0 follow-ups — impairment_propagation, swap recovery, cardinality#4685
Merged
Conversation
…ts on get_media_buys response (#4683) Two additive spec changes: 1. New capability `capabilities.media_buy.impairment_propagation` with three postures — `snapshot` (default), `webhook_only`, `out_of_band` — so sellers can honestly declare how they surface dependency impairments. Sellers declaring `webhook_only` or `out_of_band` are not graded by the impairment.coherence storyboards; their compliance bar is the webhook contract or the offline agreement. Each posture is a legitimate operational pattern (premium guaranteed → snapshot; high-throughput SSPs → webhook_only; long-tail/enterprise-bundled → out_of_band). 2. Adds typed `health` (media-buy-health enum, default ok) and `impairments` (array of impairment.json) to get_media_buys response media_buys[] items. These fields existed on core/media-buy.json but weren't surfaced on the response schema, so storyboards referencing media_buys[0].health failed the validations-path lint. Brings the response schema in line with the published contract. Runtime gating on the capability (storyboard runner grades not_applicable based on the declared posture) is the adcp-client follow-up; spec-side declaration ships here. Closes #4683. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y_impairment (#4682) Rewrite phase 5 of media_buy_seller/dependency_impairment to use update_media_buy with packages[].creative_assignments replacement semantics instead of force_creative_status approved on the same id. In production, rejected → approved on the same creative_id is uncommon; buyers ship a corrected asset under a new id and update the package's assignments. Modeling that uncommon path penalized sellers whose review flow treats rejected as a hard wall (a legitimate design). New phase 5 sequence: - sync replacement creative B (approved baseline) - force B to approved via comply_test_controller - update_media_buy: packages[$package].creative_assignments = [B] - get_media_buys: health: ok, impairments[] empty (creative A still rejected in library but no longer a buy dependency) Same-id re-approval is filed as opt-in sibling scenario media_buy_seller/dependency_impairment_reapprove_recovery for sellers whose review flow supports the reinstatement path. Scenario version bumped 1.0.0 → 2.0.0; required_tools adds update_media_buy. Closes #4682. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pressure-test the impairment.coherence inverse rule under cardinality.
Two creatives on two packages, sequential rejection 0 → 1 → 2 → 1 → 0
via swap-assignment recovery. Catches failure modes the single-creative
scenario can't:
- wrong resource_id (right cardinality, wrong target)
- over-scoped package_ids when only one creative is rejected
- failure to decrement impairments[] on partial recovery
- merged-entry sellers (one impairment with both creatives' package_ids
when both are rejected)
Wired into protocols/media-buy/index.yaml#requires_scenarios. Sellers
without comply_test_controller force_creative_status, multi-package
support, or update_media_buy with replacement-semantic creative_assignments
grade not_applicable. Same impairment_propagation capability gating as
the base scenario.
Closes #4681.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three rounds of expert review (protocol, docs, product). Must-fix items: - Cardinality scenario: drop unsound `field_value_or_absent value: null` cardinality assertions. The check kind passes when the field is absent OR equals the value, so a seller emitting a literal-null pad in the array silently passes (protocol expert flag). Cardinality enforcement falls back to the cross-resource invariant (over-emission produces an impairment pointing at a non-offline resource, failing the forward rule). A direct `array_length` check kind in the runner is filed as a follow-up. - Capability description (get-adcp-capabilities-response.json): make it explicit that `snapshot` is compatible with webhooks — the field is about whether the buy snapshot mirrors the impairment, not whether webhooks fire. The webhook channel is orthogonal. Mirror the clarification on the lifecycle.mdx paragraph. - Docs voice: replace "declare honestly" / "tend toward" with neutral observational framing. - Cross-link: lifecycle.mdx capability paragraph now links to the schema URL so an agent reader reaches the enum definition in one hop. Deferred to follow-ups (filed separately on this PR's branch before push): - snapshot_and_webhook combinator (common case missing from current taxonomy) - out_of_band semantic split (API-mapping-gap vs no-API-at-all) - rejection_reason + same-gap audit on get_media_buys response schema - Materiality SHOULD vs MUST tension on impairment.json - array_length check kind in the compliance runner (adcp-client) - Cardinality gating: 3.1 vs 3.2 vs separate premium tier - webhook_only minimum recovery surface - Swap-recovery uneven across SSP categories (programmatic SSPs) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 17, 2026
bokelley
added a commit
to adcontextprotocol/adcp-client
that referenced
this pull request
May 17, 2026
…tions (#1830) (#1831) * feat(storyboard): add `array_length` check kind for cardinality assertions (#1830) Adds a dedicated cardinality check so storyboards can assert "exactly N entries" without the unsound `field_present arr[N-1]` + `field_value_or_absent arr[N] value: null` workaround — the second clause passes when a seller emits a literal `null` pad at `arr[N]`. Supports exact-count (`value: N`) and inclusive bounds (`min` / `max`, either or both). Mutually exclusive — specifying both forms or neither fails the check as misconfigured. Non-array resolutions fail with a type error rather than passing silently. Wired through both the runtime validator and the in-process `conformance-replay` harness. Follow-up to adcontextprotocol/adcp#4685 protocol review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(storyboard): tighten array_length operand validation + replay symmetry Code-review follow-ups for #1830: - Reject NaN, fractional, and negative `value` / `min` / `max` at the config gate. Previously these slipped through `typeof === 'number'` and silently failed every real input (NaN === N is always false; fractional `value` never matches `array.length`). - Reject impossible ranges (`min > max`) at the config gate. - `conformance-replay` was missing the canonical's `value` + `min`/`max` mutual-exclusion guard — a misconfigured storyboard could silently pass the replay harness while failing the runtime validator. Add the symmetric guard plus the same operand validation so replay tracks canonical semantics. - Align replay's not-an-array message to distinguish `null` (which `typeof` mislabels as `'object'`) from real objects. - JSDoc on the union variant now states this check is non-optional by design. Adds four tests for NaN, fractional, negative-min, and impossible-range misconfig paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 18, 2026
…#4686, #4687) (#4690) * feat(media-buy): restructure impairment_propagation as propagation_surfaces array (#4686) Replace the single-value enum capabilities.media_buy.impairment_propagation with the non-exclusive array capabilities.media_buy.propagation_surfaces. The enum couldn't express the common case where a seller propagates on both the buy snapshot AND fires webhooks — premium guaranteed sellers (GAM, FreeWheel, CM360) do both, but the previous enum forced a choice between 'snapshot' and 'webhook_only', creating an incentive to declare 'webhook_only' and dodge the snapshot-coherence bar. Surface values: snapshot — media_buy.health + impairments[] mirror on get_media_buys webhook — notification-type: impairment fires via push_notification_config out_of_band — channels outside AdCP entirely (email, dashboard, partner-specific feeds) Default when absent: ['snapshot']. Storyboard gating: scenarios grade not_applicable when propagation_surfaces does not include 'snapshot'. Pre-release breaking change — the original enum landed in #4685 but 3.1.0 hasn't released yet (GA 2026-05-29), so no deprecation cycle. Also documents the out_of_band semantic split (proper out-of-band vs mapping-gap-disguised-as-out-of-band) and prescribes documenting mappings rather than claiming out_of_band when the seller does have the data under a non-AdCP field name. Closes #4686. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(media-buy): surface rejection_reason on get_media_buys response items (#4687) The field existed on core/media-buy.json but wasn't typed on the get-media-buys-response.json items — same gap-class as health + impairments[] fixed in #4685. Completed the full audit of core/media-buy.json against the response schema: every other field (media_buy_id, account, status, health, impairments, confirmed_at, cancellation, total_budget, packages, invoice_recipient, creative_deadline, revision, created_at, updated_at, ext) is already mirrored. With rejection_reason added, the response is complete with respect to the canonical media-buy fields. Response-only fields (currency, start_time, end_time, valid_actions, available_actions, history, augmented packages with delivery snapshots) remain — deliberately response-side, not on core/media-buy.json. Closes #4687. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(compliance): expert review feedback on #4690 Three expert reviews (protocol, docs, product). Must-fix items: - Drop real-brand names from schema description and lifecycle.mdx (GAM/FreeWheel/CM360). Replaced with 'premium guaranteed sellers' generic descriptor. Playbook rule (no real brands in examples). - Add explicit 'empty array invalid' clarification to the propagation_surfaces description — minItems: 1 already enforces it, but readers don't always check the constraint block. - Fix stale adcp#4683 referent in dependency_impairment.yaml — the capability is no longer in #4683's shape; lifecycle.mdx is the live cross-reference and is sufficient on its own. Deferred (filed separately): - proprietary_field guidance follow-up so the description's SHOULD directive doesn't get lost. - Release notes callout for the value rename webhook_only → webhook in 3.1.0 GA notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 18, 2026
Closed
bokelley
added a commit
that referenced
this pull request
May 18, 2026
… (#4733) When a creative referenced by a media buy's package transitions to rejected, the buy now surfaces `health: impaired` and an `impairments[]` entry. When the buyer recovers via assignment swap (update_media_buy replacing the offline creative with an approved sibling), the impairment clears and health returns to ok. Closes the dependency_impairment + dependency_impairment_cardinality storyboards added in #4677/#4685. - types.ts: Impairment interface + MediaBuyState.impairments - comply-test-controller.ts: propagateCreativeImpairment walks session.mediaBuys on forceCreativeStatus, appending on approved→rejected and removing on the inverse; idempotent on re-emission - task-handlers.ts: - get_media_buys emits health + impairments mapped to wire shape - update_media_buy recomputes impairments after creative_assignments replacement; drops any whose resource_id is no longer referenced - tenants/comply.ts: wire force_creative_status adapter on /sales (was missing, storyboards reported force_scenario_unsupported) Scenario fixes: - Both dependency_impairment storyboards restructure sync_creatives + inline assignments into sync_creatives + update_media_buy. v6 SalesPlatform.syncCreatives doesn't expose request-level assignments to the platform (filed upstream at adcp-client#1842). Both surfaces are spec-canonical; this is a v6-SDK compatibility choice, not a deprecation. - dependency_impairment_cardinality also needs bid_price: 10.0 on its packages because its brief lands on an auction pricing_option as products[0].pricing_options[0] (test-design issue filed at #4732). Sales floor lifts from 72:340 to 74:380. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Three 3.1.0 follow-ups from the #4677 expert-review batch. All in the
impairment.coherencecluster.Issues closed
media_buy.impairment_propagationcapability +health/impairmentstyped onget_media_buysresponsedependency_impairmentscenariodependency_impairment_cardinalityscenario — pressure-tests inverse rule with two creativesCommits
feat(media-buy): impairment_propagation capability + health/impairments on get_media_buys response (#4683)capabilities.media_buy.impairment_propagationenum:snapshot(default),webhook_only,out_of_band. Sellers honestly declare how they propagate dependency impairments;webhook_only/out_of_bandsellers gradenot_applicablefor impairment.coherence storyboards.health(media-buy-health enum, defaultok) andimpairments[]onget_media_buysresponsemedia_buys[]items. The fields existed oncore/media-buy.jsonbut weren't typed on the response schema — storyboards referencingmedia_buys[0].healthpreviously failed the validations-path lint (the original storyboard: dependency-impact end-to-end scenario #2860 scenario slipped through admin-merge; this closes the gap).feat(compliance): swap-assignment as canonical recovery for dependency_impairment (#4682)media_buy_seller/dependency_impairmentrewritten to useupdate_media_buywith replacement-semanticscreative_assignments— sync replacement creative B, swap the package's binding from A (rejected) to B (approved), verifyhealth: okandimpairments[]cleared. Creative A's library status staysrejectedbut is no longer a dependency.required_toolsaddsupdate_media_buy.feat(compliance): dependency_impairment_cardinality scenario (#4681)resource_id(right cardinality, wrong target).package_idswhen only one creative is rejected.protocols/media-buy/index.yaml#requires_scenarios.Three changesets, all
minor.Test plan
build:schemas✓ /build:compliance✓test:schemas✓ /test:examples✓test:storyboard-validations-paths✓ for the new scenarios (the dependency_impairment.yaml violations introduced by docs+compliance: terminal-buy carve-out (#4635) + dependency-impairment storyboard (#2860) #4677 are also fixed by the get-media-buys-response schema additions in this PR)test:storyboard-{doc-parity,check-enum,branch-sets,test-kits,sample-request-schema,response-schema,context-entity,contradictions,provides-state-for,auth-shape,advisory-expiry,raw-mode-required}✓typecheck/test:json-schema/test:composed/test:docs-nav✓test:storyboard-scopingonsync_audiences,test:storyboard-context-output-pathsonsales-guaranteed/index.yaml,test:storyboard-validations-pathsonbrand-rights/scenarios/governance_denied.yaml.Rollout
impairment_propagation: snapshot(current behavior). The newhealth/impairmentsfields on the response are optional. The cardinality scenario is new and gracefully gradesnot_applicablefor sellers without multi-package or controller support.not_applicableaccordingly) is the adcp-client follow-up; spec-side declaration ships here.dependency_impairmentbase,dependency_impairment_cardinality) cleanly grade against the spec contract.🤖 Generated with Claude Code