fix(compliance): release 3.0.16 storyboard fixes#5533
Conversation
There was a problem hiding this comment.
Request changes on one phase. The past-start rewrite in schema-validation.yaml ships a breaking conformance MUST as a 3.0.x patch — and conformance.mdx is explicit that the storyboards are the spec, so this is a normative tightening, not a test fix. The other four storyboard fixes are the right shape and can ship as-is.
Things I checked
- index.json delta is correct for a path-versioned patch. Only
adcp_version3.0.15 → 3.0.16 (static/schemas/source/index.json:7); the file's ownversioning.noteconfirms path-based versioning with no per-schema version field. No wire-shape change in any source schema.ad-tech-protocol-expert: sound. - Webhook HMAC-loophole closure aligns the suite with an existing baseline. Removing
skip_if: agent.webhook_auth_mode == 'hmac_legacy'fromsignature_validityand adding thesigning_keys_publishedprecheck (webhook-emission.yaml, diff L1704-1766) matchessecurity.mdx:66— 9421 signing is baseline-required in 3.0, and the HMAC fallback is a buyer-side opt-in, never a seller exemption. Legacy-HMAC-no-keys sellers were already non-conformant; this closes a suite gap, not a new bar.ad-tech-protocol-expert: sound. - New idempotency/webhook phases are additive and gated.
concurrent_retryandrate_limit_replay_invariantsit behindrequires_contract: parallel_dispatch_runner/rate_limit_trip_runnerand gradenot_applicableon runners without the contract;operation_id_echoand the sync-completion branch set are contract-gated orany_of. None newly-fail an existing agent on an existing runner. - Storyboard wiring is clean (
code-reviewer): removedidempotency_key_acapture has zero downstream refs; all fiveexpect_webhooktrigger/filteroperation_idliterals match their paired triggers (op_webhook_presence_explicit,op_storyboard_echo_123,op_webhook_idempotency_replay,op_webhook_retry_explicit,op_webhook_signed_explicit); fixtures align with everytest-product/test-pricingreference;no_duplicate_webhooks_on_replayis fully excised from idempotency.yaml and re-homed in webhook-emission.yaml with no dangling reference. - state-machine.yaml is the right shape.
sync_creativesbeforecreate_media_buy+creative_assignments+status == activeassertion +requires_capability: media_buy.creative_approval_mode == auto_approveis exactly what it takes to reach a pausable buy. Clean.
MUST FIX (blocking)
1. Past-start tightening is a breaking conformance change shipped on a patch — and it contradicts the wire spec. schema-validation.yaml deletes the any_of(past_start_reject_path | past_start_adjust_path) branch_set and the past_start_enforcement assertion, replacing them with a single mandatory past_start_rejection phase requiring INVALID_REQUEST. The deleted narrative stated the reason for the either-or directly: the spec "is silent on past start_time, which is why either-or is the current conformant stance." Confirmed at create_media_buy.mdx:981 — Flight date validation is SHOULD and covers only in-range dates and start_time < end_time; nothing about start_time vs. now.
What breaks for adopters: a seller that accepts-and-adjusts a past start_time is wire-conformant under that SHOULD, passed the 3.0.15 suite via the now-deleted adjust branch, and hard-fails the new 3.0.16 past_start_rejection MUST. conformance.mdx makes this normative, not cosmetic — "the storyboards ARE the conformance specification" — so a 3.0.x patch newly-fails a previously-conformant agent while the prose spec still says SHOULD. Breaking conformance change without a minor/major, plus a suite-vs-spec contradiction.
Pick one to unblock:
- (a) Restore the
any_of(reject | adjust)branch_set on the 3.0.x line and defer the tightening to a 3.1 minor with an RFC; or - (b) Re-rev this as a minor and update
create_media_buy.mdx§Flight date validation to commit past-start to a hardINVALID_REQUESTMUST, so suite and spec agree.
The other four fixes are unaffected by either path.
Follow-ups (non-blocking — file as issues)
- Orphan capture. idempotency.yaml's new
idempotency_supportedcontext_output (~L297, pathadcp.idempotency.supported) is never consumed — thesupported: falseskip is handled by thepermanent_advisorychecks at the capability phase, not this capture. Drop it, or wire it into the #3919 precondition gate when that lands. (code-reviewer.) - CHANGELOG traceability. The conformance-affecting changes are rolled under one hashless "Patch Changes" bullet. Once the versioning in MUST-FIX #1 is settled, the bullet should name the past-start and HMAC-loophole changes explicitly so the conformance delta is auditable.
- dist regeneration. Couldn't byte-diff
dist/schemas/3.0.16/**against source from the PR diff. PR saysbuild:compliance --releasewas run — confirm in CI that the tree is build-regenerated, not hand-edited. - Tarball signing.
sign:protocol-tarballwas skipped locally (SIGN_PROTOCOL_TARBALLunset).dist/protocol/3.0.16.tgz.sha256is committed; CI must produce the signature.
Minor nits (non-blocking)
- Duplicate correlation_id. webhook-emission.yaml's
synchronous_completion_success_pathand_rejection_pathreuse the sameoperation_id: op_sync_get_products_no_webhookandcontext.correlation_id: webhook_emission--sync_get_products_no_webhookacross two distinct step ids. Not a wiring bug — both areany_ofbranches, neither is anexpect_webhook, so no runtime collision — but rename the reject branch's correlation_id for log traceability.
Restore the branch_set on 3.0.x, or re-rev to minor and commit the spec to the MUST. Either flips me to approve — the rest is clean.
There was a problem hiding this comment.
Two new webhook-emission phases ship broken — both false-fail conformant agents against the bundle's own schemas. Block is on the release artifact, not the release mechanics, which are clean. The fixes are small.
MUST FIX (blocking)
-
generate: uuid_v4undercontext_outputsis an unsupported form —webhook_replay_keynever populates. Instatic/compliance/source/universal/webhook-emission.yaml,capability_discoveryadds:context_outputs: - name: webhook_replay_key generate: uuid_v4
The
ContextOutputcontract instatic/compliance/source/universal/storyboard-schema.yaml:391-419is strictly{name, path}(plus thetask_completion.<inner>path prefix). There is nogenerate:key. Every one of the ~40$generate:uuid_v4usages instatic/compliance/source/**is an inline string placeholder on a request field — zero precedent for it undercontext_outputs. With nopath, the capture never resolves, so the two consumers inidempotency_replay_side_effects(idempotency_key: \"\$context.webhook_replay_key\"ontrigger_idempotent_webhook_initialandtrigger_idempotent_webhook_replay) resolve tounresolved_substitutionand grade the phase failed for every agent. What breaks for adopters: the new idempotency-replay-no-duplicate-webhook phase false-fails 100% of runs. Fix: use a shared inline$generate:uuid_v4#webhook_replay_keyacross both steps and adduniversal/webhook-emission.yamlto the alias-reuse exemption inscripts/build-compliance.cjs(the same exemptionuniversal/idempotency.yamlalready has), or extendContextOutput+ the runner to supportgenerate:. Do not ship the silently-ignored key. -
status == completedon aget-products-responsethat has no top-levelstatus. Inwebhook-emission.yaml,synchronous_completion_success_path→sync_get_products_with_webhook_config_success:- check: field_value path: \"status\" value: \"completed\"
static/schemas/source/media-buy/get-products-response.jsondefines top-levelproducts,proposals,errors,property_list_applied,catalog_applied,refinement_applied— no top-levelstatus(thestatusfields are nested insiderefinement_applied[].oneOf[]scope objects, lines 51/75/99). A conformant synchronousget_productsreturns{products: [...]}, so this check can never resolve. What breaks for adopters: a seller that correctly keeps wholesaleget_productssynchronous fails the success branch, and unless it also rejects the registration it failssynchronous_completion_assertion— the branch set penalizes the exact behavior it is meant to reward. The companionfield_absent: task_idalready captures the real intent. Fix: drop thestatusassertion (or replace with aproductspresence check).
Neither bug is caught by the storyboard schema/contradiction tests in the test plan — those validate request/response shape, not runner-time \$context resolution or field_value paths against live responses. That is the gap that put these in the bundle.
Things I checked
- Version triple is coherent.
package.json3.0.16,static/schemas/source/index.jsonadcp_version3.0.16, CHANGELOG 3.0.16 patch section all agree.index.jsonis the onlystatic/schemas/source/**file touched and it is a version-string bump — no wire field, type, enum, or required-flag change. No spec drift, no oneOf regression. - Changeset gate is satisfied. This is a
changeset-release/PR: it consumes (deletes)backport-supports-proposals-gate.mdandtmp-context-match-seller-agent-url.mdand rolls them into CHANGELOG. Release PRs consume changesets — no missing-changeset block. idempotency_key_aremoval is clean — it was acontext_outputsdefinition only, referenced nowhere as\$context.idempotency_key_a.- Date logic is right. Reversed-dates uses
2099-12-31→2099-01-01(end-before-start, still exercises reversal); past-start uses2020→2099(past start, future end, exercises auto-adjust);asap/2099windows keep buys non-terminal. The 2026/2027 hardcoded dates were the reported runner failure — this is the right fix shape. - Security probe change does not weaken the bar (
security-reviewer: no regression, highest finding Low). The negative probes (probe_unauth,probe_invalid_api_key,probe_invalid_basic,probe_invalid_oauth_token) are untouched and still assert rejection; the positive-path change correctly models MCP session auth (initialize handshake beforetools/call) and a non-conformant server cannot newly pass because enforcement is proven by the positive+negative pairing, not the positive step alone. - Closing the
skip_if: webhook_auth_mode == 'hmac_legacy'loophole is a security improvement — it removed a self-exemption from signature grading, andsignature_validitystill gradesnot_applicable(not pass) when the client 9421 verifier isn't shipped, so no false-pass. Tests an already-normative 3.0 baseline (security.mdx:64,66) — patch-appropriate, not a new wire requirement.
Follow-ups (non-blocking — file as issues)
rule 9 (first-insert-wins)does not exist insecurity.mdx.ad-tech-protocol-expertconfirmed the idempotency normative list runs 1-8;idempotency.yamlcites a "rule 9" in the summary line, narrative bullet 6, theconcurrent_retrynarrative, and thecross_response_count_distinctdescription. (Therate_limit_replay_invariant"rule 3" citation is accurate —security.mdx:289,300.) Re-cite to rule 2's(authenticated_agent, account_id, idempotency_key)canonical-insert model, or add the rule tosecurity.mdxin its own change — not silently in a backport.- Dead doc link introduced by this PR.
security.yamlrepoints the authentication link from/docs/building/integration/authentication(which exists) to/docs/building/by-layer/L2/authentication(which exists nowhere in the repo on this branch). Point it back, or land the target doc. - Confirm the runner treats unrecognized
check:verbs under a missingrequires_contractasnot_applicable, not a hard parse error. The newcross_response_count_distinct,cross_response_field_equal, andreplay_not_cached_rate_limitchecks are assertion verbs, not contract-gated tasks — if an older runner hard-errors on an unknown check, the concurrent-retry and rate-limit phases fail closed on every current runner. - Cross-tenant idempotency isolation is a
reviewer_check, not an automated assertion (idempotency.yamlconcurrent_retry, and the rate-limit budget-scoping check). Fine as human guidance — but don't describe it as automated cross-tenant testing in release notes. A real second-principal step would close the gap. - Positive auth probes no longer assert any success signal (
security-reviewerLow). Consider asserting a success envelope / absence oferror_codeso a server that 5xx's on a valid credential is still caught on the positive path.
Minor nits (non-blocking)
- Copy-paste ids in the sync-completion branches. Both
sync_get_products_with_webhook_config_successand..._rejectuseoperation_id: \"op_sync_get_products_no_webhook\"andcorrelation_id: \"webhook_emission--sync_get_products_no_webhook\"despite distinct step ids. Harmless — noexpect_webhookkeys off them — but the_no_webhooktoken reads as stale on the success branch. Give each its own ids for trace clarity.
Fix the two false-fails and I'll re-review. The dates/security/state-machine fixes are the right shape — it's the two new webhook-emission phases that aren't ready.
There was a problem hiding this comment.
Requesting changes. The release mechanics are right, but the storyboard fixes ship graded MUST-pass assertions that contradict the 3.0 wire spec — false negatives in the stable 3.0.16 grading bundle, which is the exact failure class this release says it fixes.
code-reviewer: clean. No dangling $context refs, the filter.body.operation_id shape change is consistent across all five expect_webhook* steps, the synchronous_completion branch_set wiring is valid, and the no_duplicate_webhooks_on_replay / idempotency_key_a removals left zero references. ad-tech-protocol-expert: unsound — multiple new graded assertions test fields, error codes, and capability paths that do not exist on the 3.0.x branch this bundle ships against.
MUST FIX (blocking)
-
operation_id_echogrades against a config field that does not exist, and inverts the documented mechanism.static/compliance/source/universal/webhook-emission.yaml:225-289sendspush_notification_config.operation_id: "op_storyboard_echo_123"and asserts (L24, L231) the seller MUST treat the URL as opaque and MUST NOT parse it. Butpush-notification-config.json:7-46has exactly three properties —url,token,authentication— nooperation_id. And the spec mechanism is the opposite:mcp-webhook-payload.json:17says operation_id "was embedded in the webhook URL by the buyer," andwebhooks.mdx:118shows it in the URL path (.../{operation_id}), echoed "from your URL" (webhooks.mdx:38). This is a MUST-pass phase (webhook-emission.yaml:728), andidempotency_key_presencenow depends on the same echo. What breaks for adopters: a spec-conformant 3.0 seller reads operation_id from the URL perwebhooks.mdx, is sent a non-schemapush_notification_config.operation_id, and gets graded as failing the first two MUST-pass webhook phases — a false negative shipped in the stable bundle. -
State-machine gate keys on a capability that does not exist → fail-open dead test.
static/compliance/source/protocols/media-buy/state-machine.yaml:14-16gates the entire storyboard onmedia_buy.creative_approval_mode == auto_approve. There is nocreative_approval_modefield anywhere instatic/schemas/source/**. No agent can populate it, sorequires_capabilitynever matches and every agent skips the storyboard. The state-machine conformance area silently goes dark — the opposite of the "stop manual-review sellers from false-failing" intent. (Thestatus: activeassertion at the create step is correct —activeis valid perenums/media-buy-status.json.) -
IDEMPOTENCY_IN_FLIGHTis not in the error enum, and "rule 9" does not exist.idempotency.yamlblessesIDEMPOTENCY_IN_FLIGHTas a conformant concurrent-retry response and citessecurity.mdx#idempotency"rule 9 (first-insert-wins)."enums/error-code.jsonhas onlyIDEMPOTENCY_CONFLICTandIDEMPOTENCY_EXPIRED;security.mdxidempotency rules are numbered 1-8. Lower blast radius than 1-2 (the phase is gated on aparallel_dispatch_runnercontract and the code isMAY, notMUST), but it tells sellers an out-of-enum error code is conformant. Either add the code to the enum onmainfirst, or relabel this assertion advisory.
These flow from ad-tech-protocol-expert's unsound verdict with cited divergence. The fix is to land the wire additions (push_notification_config.operation_id, creative_approval_mode, IDEMPOTENCY_IN_FLIGHT) on main as a minor and backport the storyboard grading after the schema, or mark these assertions advisory so 3.0.16 stays a true patch.
Things I checked
- Release mechanics are sound:
changeset-release/branch prefix, two consumedpatchchangesets (backport-supports-proposals-gate,tmp-context-match-seller-agent-url) deleted,package.jsonandstatic/schemas/source/index.jsonboth 3.0.15→3.0.16.index.jsonis a cleanadcp_versionbump — no wire-shape change. Existingdist/.../3.0.15artifacts untouched; 3.0.16 is additive. - ~778 of 781 files are generated
dist/compliance/3.0.16/**— confirmed generated, reviewed the 8 hand-edited source files instead. scripts/build-compliance.cjs:276-410: the duplicate-generated-key lint allowlist widening touniversal/webhook-emission.yamlis correct (relispath.relative(sourceDir, p), matching the Set entries) and harmless —code-reviewernotes it is inert because those steps carry a$test_kitschema_ref that never matchesmutatingRefs.security.yaml: moving static-credential positive probes onto the initialized MCP session (handshake beforetools/call) is the correct MCP Streamable HTTP model — removing the raw-bearerhttp_status: 200probe is right.idempotency.yaml: therate_limit_replay_invariantphase is spec-grounded (security.mdx:300—RATE_LIMITEDMUST NOT be cached as the canonical replay). Moving the webhook side-effect assertion out towebhook-emission.yamlto keep core idempotency runnable without a receiver is the right seam.
Follow-ups (non-blocking — file as issues)
- HMAC-legacy exemption removal. Dropping
skip_if: agent.webhook_auth_mode == 'hmac_legacy'onsignature_validityand adding thesigning_keys_publishedprecheck is a real conformance tightening.push-notification-config.json:20says the baseline 9421 key "is always discoverable" even when HMAC is selected, so requiring publication is defensible — but failing live-HMAC agents on signing flips a fallback the wire spec blesses through 4.0. If intentional, it wants a deprecation note, not a backport bullet. (ad-tech-protocol-expertflagged this; I'm downgrading it from the block because the "always discoverable" language gives it cover andadcp_use: webhook-signingdoes exist inpush-notification-config.json/get-adcp-capabilities-response.json— the expert overreached calling that fabricated.) - Storyboard fixes ride in without a discrete changeset. The five storyboard edits are introduced in this release PR and documented only by a hand-authored, hash-less CHANGELOG bullet. Defensible for a manual backport cut, but the graded-conformance tightening above is exactly what a changeset is supposed to surface.
Minor nits (non-blocking)
- Broken backported doc link.
security.yaml:18repoints the auth link to/docs/building/by-layer/L2/authentication. That path does not exist on 3.0.x — the doc isdocs/building/integration/authentication.mdx. Theby-layer/L2reorg never landed on this branch; the backport carried the main-line path.
Land the wire-schema additions first, or mark the three assertions advisory, and this is a clean patch. Holding until operation_id_echo and the state-machine gate grade against fields that exist on 3.0.x.
Summary
Release
3.0.16on the 3.0.x line with compliance storyboard fixes for the runner failures reported against the stable bundle.This backports the fixed storyboard source for:
3.0.15already exists as released artifacts and a remote tag, so this ships the next patch release instead of mutating3.0.15.This replacement PR uses the
changeset-release/branch prefix so the changeset gate correctly treats it as a Version Packages-style release PR with consumed changesets.Validation
npm run build:compliance -- --releasenpm run build:protocol-tarball -- --releasenpm run sign:protocol-tarball(skipped locally;SIGN_PROTOCOL_TARBALL=truenot set)npm run test:schemasnpm run test:storyboard-sample-request-schemanpm run test:storyboard-response-schemanpm run test:storyboard-context-entitynpm run test:storyboard-contradictionsnpm run test:storyboard-test-kitsgit diff --check && git diff --cached --checknpm run test:unit && npm run test:test-dynamic-imports && npm run typecheckNotes
No existing
dist/compliance/3.0.15,dist/schemas/3.0.15, ordist/protocol/3.0.15.*files are modified. The release adds new3.0.16artifacts.