chore: release package#828
Merged
Merged
Conversation
869c234 to
273f0ad
Compare
3025b56 to
87e8e52
Compare
87e8e52 to
ec2502c
Compare
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.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@adcp/client@5.14.0
Minor Changes
36b920c: Dogfooding follow-ups from reference training agent (adcp#2889) plus
four fixes surfaced while running the
media_buy+sales-non-guaranteedcompliance bundle against a freshSpotify-shaped seller built on the new patterns. Nine bundled fixes
across the server + testing surfaces:
registerTestControllerauto-emits thecompliance_testingcapability block on
AdcpServer— per AdCP 3.0, comply_test_controllersupport is declared via
capabilities.compliance_testing.scenarios,NOT as a value in
supported_protocols.registerTestControllernowwrites that block onto the server's capabilities object (new
ADCP_CAPABILITIESinternal symbol) usingfactory.scenariosorinferring from plain-store method presence.
parseCapabilitiesResponseadditionally normalizes the client-side
AdcpCapabilities.protocolslist from the block when declared by a peer. The misleading
augmenter log that told sellers to "declare compliance_testing" was
rewritten to point at the correct declaration (the capability block,
not
supported_protocols).TaskExecutorpreserves structured tool-error payloads on failedtasks — previously the FAILED branch dropped
result.dataunlessextractAdcpErrorInforecognized anadcp_errororerrorsenvelope. Tool-level error shapes like
comply_test_controller's{ success: false, error: 'UNKNOWN_SCENARIO', error_detail }don'tmatch that extractor, so storyboard validators checking
success: false/error_codeon controller error paths sawundefined. The branch now retains any non-empty structuredpayload so validators can read tool-specific envelopes the SDK
doesn't model explicitly.
createIdempotencyStore()throws a helpful error when calledwithout
{ backend }— the zero-arg path previously crashed withCannot read properties of undefined (reading 'ttlSeconds'). Theerror now names
memoryBackend()andpgBackend(pool)as the twooptions.
registerTestControllerechoes requestcontextinto the responseenvelope —
createAdcpServerauto-echoescontextfor domain toolhandlers, but
registerTestControllerwirescomply_test_controlleron the raw MCP surface and bypassed that pipeline. Every
controller_validation+deterministic_*storyboard step failsfield_present: contextwithout this echo. The wrapper now attachesinput.contextonto the response when the handler didn't set oneitself (handler-supplied context still wins, matching the
domain-handler rule). Surfaced by a mini-seller migration exercise
against the full
media_buy+sales-non-guaranteedstoryboardbundle.
bridgeFromSessionStore({ loadSession, selectSeededProducts, productDefaults? })(adcp-client#824) — new helper for sellers whose seed store is
session-scoped (one Map per tenant / brand.domain / account_id, loaded
per request). The existing
bridgeFromTestControllerStoretakes asingle Map at construction time and doesn't compose with per-request
session loading; the new helper takes an options object with two
callbacks. Both
loadSessionandselectSeededProductsmay be async,so lazy-loaded seed collections don't force eager hydration inside
the loader.
loadSessionrejections propagate to the dispatcher —silent seed loss under DB failure would be worse than a loud error.
BridgeFromSessionStoreOptions<TSession>is exported.mcpAcceptHeaderMiddlewarenow rewritesreq.rawHeaders(adcp-client#825) — the MCP SDK's
StreamableHTTPServerTransportrebuilds its Fetch
Headersfromreq.rawHeadersvia@hono/node-server, ignoringreq.headers. Patching only the parsedmap was a silent no-op for the transport the middleware's name
implies. Both surfaces now move in lockstep (case-insensitive on the
rawHeaders name, all duplicate entries rewritten, no phantom entry
added when
rawHeaderslacks Accept — see JSDoc for the proxy-divergence tradeoff).
adcpError()+ dispatcher consult a per-code inside-adcp_errorallowlist(adcp-client#826) — new
ADCP_ERROR_FIELD_ALLOWLISTmap in@adcp/client/server(parallel to the existingERROR_ENVELOPE_FIELD_ALLOWLISTfor siblings). The builder filtersits output against the allowlist for the given code.
IDEMPOTENCY_CONFLICTis the canonical strict case:
recovery,retry_after,field,suggestion, anddetailsall drop from the wire shape so theenvelope can't become a stolen-key read oracle. The dispatcher
(
create-adcp-server.ts) re-applies the same allowlist asdefence-in-depth for handlers that hand-roll an envelope outside
adcpError(). Codes without an entry pass through unchanged.Legacy
CONFLICT_ADCP_ERROR_ALLOWLISTstays exported as an alias forthe
IDEMPOTENCY_CONFLICTentry. The allowlist is scoped to standarderror codes on purpose — vendor codes need
recoveryper the spec'sgraceful-degradation contract, so don't extend this pattern there.
Breaking (narrow surface): remove
createDefaultTestControllerStore/createDefaultSession/DefaultSessionShape(adcp-client#827) — the"collapse 300 lines to 10" default factory shipped in 5.11.0 only held
for sellers whose session IS
DefaultSessionShape(a bag of genericMaps). Every real seller has typed domain state (
MediaBuyStatewithpackages, history, revision;
CreativeStatewith format_id, manifest,pricing_option_id;
GovernancePlanStatewith budget allocations,flight, policy categories). The default handlers wrote into a
parallel bag of seed Maps those sellers' production tools don't
read — so
seed_media_buypopulatedsession.seededMediaBuyswhileget_media_buyread fromsession.mediaBuys, and subsequent storyboardsteps silently saw empty state. Sellers either overrode every handler
(net savings: zero) or silently drifted. The helper had been on npm
for ~24 hours with zero documented adopters.
The replacement is documentation, not another helper.
@adcp/client/testingnow re-exports the full integration surface in one place —
registerTestController,TestControllerStore,TestControllerStoreFactory,enforceMapCap,createSeedFixtureCache,SESSION_ENTRY_CAP,TestControllerError,CONTROLLER_SCENARIOS,SEED_SCENARIOS— andexamples/seller-test-controller.tsshows thereal pattern (typed
MediaBuyState+CreativeState, session-scopedfactory, seed writes into the same records production readers use,
~200 LOC). Sellers with simpler domains should keep using
createComplyController(adapter surface, unchanged).Type change
AdcpErrorPayload.recoveryis now optional (previously required).Reflects the filtered wire shape — consumers that destructure
recoveryoff a parsed conflict response MUST tolerateundefined.If you hit a TS strict-null complaint, use
payload.recovery ?? 'terminal'or re-derive from
STANDARD_ERROR_CODES[payload.code]?.recovery.Who is affected / upgrade path
No action required for:
adcpError()— the builder now quietly filters disallowedfields per code; the wire shape is what the spec expects.
bridgeFromTestControllerStore— unchanged.CONFLICT_ADCP_ERROR_ALLOWLIST— it still exports asan alias for
ADCP_ERROR_FIELD_ALLOWLIST.IDEMPOTENCY_CONFLICT.Action required for:
recovery/retry_after/detailson anIDEMPOTENCY_CONFLICTenvelope via a hand-rolled response (notthrough
adcpError()): the dispatcher now strips those fields beforethey reach the wire. Either switch to
adcpError('IDEMPOTENCY_CONFLICT', {...})(recommended) or drop the disallowed fields from your envelope.
AdcpErrorPayload.recoverynon-optionally: guard with
??or re-derive from the code.createDefaultTestControllerStore/createDefaultSessionor importers of the
DefaultSessionShape/DefaultLoadSessionInput/CreateDefaultTestControllerStoreOptions/DefaultTestControllerStoreResult/
SeedFixture/BudgetSpendRecord/DeliverySimulationRecord/SessionTerminalStatustypes from@adcp/client/testing: these areremoved. Replace with a
TestControllerStore/TestControllerStoreFactoryimplementation against your own domain types — see
examples/seller-test-controller.tsfor the pattern. The scopeprimitives you need (
enforceMapCap,createSeedFixtureCache,SESSION_ENTRY_CAP,TestControllerError) are now reachable from@adcp/client/testingalongsideregisterTestController.get_adcp_capabilitiesresponse JSON for a server with
registerTestControllerwired:the response now includes a top-level
compliance_testing.scenariosblock per AdCP 3.0. This is the spec-correct wire shape (previously
missing), but snapshot tests pinning the old omitted-block shape
will need regeneration. Mutating-tool responses on the fresh-exec
path also now include
replayed: falseexplicitly (was omitted) —same implication for pinned-snapshot tests.
6061973: Creative-agent ergonomics follow-ups from scope3 agentic-adapters#100 review (feat: creative-agent docs polish + CLI warns on removed flags #844 follow-up):
displayRender/parameterizedRenderfactories forFormat.renders[](closes feat(sdk): add audioRender / displayRender / videoRender factories #846)The
Format.renders[]item schema'soneOfforces each entry to satisfy exactly one branch —dimensions(width + height) ORparameters_from_format_id: true. A render with only{ role }or{ role, duration_seconds }fails strict validation. Two new named exports from@adcp/client:Also corrects a spec-non-conformant audio example that shipped in feat: creative-agent docs polish + CLI warns on removed flags #844 — audio
renders[]must useparameterizedRenderand encode duration/codec informat_id.parametersviaaccepts_parameters, not in the render entry.--strict-flagsonadcp storyboard run(closes feat(cli): --strict-flags to fail CI when removed flags are passed #847)Removed-flag warnings (added in feat: creative-agent docs polish + CLI warns on removed flags #844) stay advisory by default.
--strict-flagsupgrades them to a hard exit 2 so CI pipelines can catch stale scripts as build-breakers:detectShapeDriftHintonbuild_creativeresponses (closes feat(testing): hint at build_creative shape drift in strict-validation warnings #845)When a
build_creativeresponse has platform-native fields (tag_url,creative_id,media_type,tag_type) at the top level instead of{ creative_manifest }, the storyboard runner now attaches an actionable fix-recipe toValidationResult.warning— namingbuildCreativeResponse/buildCreativeMultiResponsefrom@adcp/client/serverand pointing at thecreative-templateskill section. Fires on both Zod-fail (common — platform-native shape) and Zod-pass-AJV-fail paths. No change to pass/fail logic —warningis advisory.e2f9ea9: Storyboard runner: fixture-authoritative request construction (closes Storyboard runner: demote builder to enricher, hard-fail on missing fixture (extends #801) #820).
The runner's request-construction priority is inverted.
sample_requestis now the authoritative base payload — when authored, every top-level
key the author wrote reaches the wire verbatim. The per-task enricher
(formerly "request builder") runs alongside, filling fields the fixture
left unset — typically discovery-derived identifiers, envelope fields,
or context-substituted placeholders.
The previous behavior silently fabricated payloads and discarded author
fixtures on ~20 tasks whose enrichers didn't opt into a fixture-honoring
early return. That false-green failure mode produced five consecutive
fallback-shape bugs (storyboard request-builder: list_creative_formats() ignores step.sample_request, drops format_ids filter #780 / fix(testing): honor sample_request in get_rights builder (closes adcp#2846) #792 / Storyboard request-builders emit out-of-spec shapes for log_event / create_media_buy (drops packages[1+], wrong field names) #793 / fix(testing): storyboard builders emit spec-violating shapes for si_* + sync_governance #802 / Storyboard fallback shapes: check_governance caller must be URI; creative_approval needs creative_url #805) before anyone
noticed the architecture was backward.
New contract
sample_request(authored) — base payload. Context placeholders(
$context.*,$generate:uuid_v4,{{runner.*}}) resolve as before.Fixture wins every top-level conflict.
create_media_buy,comply_test_controller) —declared in
FIXTURE_AWARE_ENRICHERSbecause they splicediscovery-derived fields INTO nested fixture structures (array-level
merges the generic overlay can't express). The runner passes their
output verbatim; envelope fields from the fixture (
context,ext,push_notification_config,idempotency_key) still flow through.Load-time hard-fail
Mutating tasks (per
MUTATING_TASKS) now throw at storyboard load whensample_requestis absent andexpect_error !== true. The runner nolonger fabricates write payloads. Error messages point at the task,
step id, storyboard id, and suggest the concrete author action. Synthesized
phases (request-signing, controller seeding) are unaffected — their
runtime-generated steps don't pass through
parseStoryboard.Rename (compat preserved)
buildRequest→enrichRequest(old name kept as deprecated alias)hasRequestBuilder→hasRequestEnricher(old name kept)REQUEST_BUILDERS→REQUEST_ENRICHERS(internal)External consumers pinned to the old names continue to work for one
release. Migrate to the new names at your own pace.
Observable-behavior changes
sample_requestfail loudly at loadinstead of silently shipping fabricated payloads. This is the
intentional correctness improvement.
accountnow wins on four tasks whose pre-inversionbuilders injected
context.accountOVER the fixture's authoredaccountvia the hybrid{ ...sample_request, account: context.account }pattern:
sync_catalogs,sync_creatives,report_usage,sync_audiences. Storyboards that relied on the runner silentlysubstituting
context.accountover their authored value will now sendthe authored value. Audit these fixtures if your tests depend on a
specific account on these tasks.
options.brief→signal_spec) now coexist with authored fields(
sample_request.signal_ids) instead of replacing them. A storyboardauthoring signal_ids and being invoked with
--brief Xnow sendsboth; agents receive a richer query. Schema-valid under
anyOf: [signal_spec | signal_ids].get_rights.brand_idfromresolveBrand(options)) gap-fill when fixture omits them. Astoryboard that specifically needs an identity field absent must
author it explicitly or opt out via
expect_error: true.Strict-vs-lenient run reporting (the fourth proposal in Storyboard runner: demote builder to enricher, hard-fail on missing fixture (extends #801) #820) is
deferred to a separate issue — it's a reporting-subsystem concern
orthogonal to the request-construction flow.
122aaf5: Add response helpers and shape-drift detection for governance list tools (closes feat(server): add response helpers for governance list tools (list_property_lists, list_collection_lists, list_content_standards) + shape-drift detection #854):
New response helpers in
@adcp/client/server:listPropertyListsResponse(data)— wraps{ lists: PropertyList[] }listCollectionListsResponse(data)— wraps{ lists: CollectionList[] }listContentStandardsResponse(data)— handles the union type (success{ standards }/ error{ errors })All three follow the existing list-response pattern (
listCreativesResponse/listAccountsResponse): default summary names the count and singular/plural handling, pass-through of the typed payload intostructuredContent.Shape-drift detection —
list_property_lists,list_collection_lists, andlist_content_standardsnow join theLIST_WRAPPER_TOOLStable in the storyboard runner'sdetectShapeDriftHint. A handler that returns a bare array at the top level gets a pointed hint naming the correct wrapper key and the new helper.Brings the shape-drift detector's coverage of list tools to nine:
list_creatives,list_creative_formats,list_accounts,get_products,get_media_buys,get_signals, and now the three governance tools. 34 shape-drift tests + 3 new response-helper tests covering count-formatting and the error-branch split on content standards.42debb0: Catch stale CLI installs before users spend hours debugging phantom behavior.
@latestin every documentednpx @adcp/clientinvocation. Unpinnednpxreuses whatever version is cached in~/.npm/_npx/— users can have six different versions co-existing and not know which one runs.@latestforces npx to re-resolve against the registry each invocation.registry.npmjs.org/@adcp/client/latest(cached for 24h at~/.adcp/version-check.json, 800ms timeout, fire-and-forget) and prints a one-time stderr warning if the running version is behind the published latest. Catches every stale-install path, not just the npx copy-paste one: global installs, pinnedpackage.json, corporate forks,pnpm dlxcaches.CI=true), non-TTY stderr,--jsonmode, andADCP_SKIP_VERSION_CHECK=1.a2ec3c0: Add
resolvePerStoryboardcallback torunAgainstLocalAgentrunAgainstLocalAgentnow accepts aresolvePerStoryboard(storyboard, defaultAgentUrl)callback that returns optional per-storyboard overrides. Callers can redirect a single storyboard to a different URL (e.g. routesigned_requestsat/mcp-strictwhile the rest stay on/mcp) and shallow-mergeStoryboardRunOptionsfields liketest_kit,brand,contracts, orauthper storyboard without giving up the helper's single-serve / single-seed lifecycle. The override shape is flat —{ agentUrl?, ...StoryboardRunOptions }— andwebhook_receiverstays helper-owned (typed out of the shape; re-applied after the merge). The callback may return aPromisefor async work such as loading a test-kit YAML or minting a scoped token. Returningundefinedkeeps the run-level defaults, so existing callers are unaffected. Resolves RFC: per-storyboard config on runAgainstLocalAgent (endpoint + test_kit + brand) #810.94b1ebd: Storyboard steps can now opt out of a default invariant for that step
only. New
StoryboardStep.invariants.disable: string[]mirrors theexisting storyboard-level
invariants.disablebut scoped to one step:the runner skips calling the named invariants'
onStepfor that stepand leaves every other invariant (and every other step) untouched.
Motivating case: storyboards that exercise buyer recovery from a
check_governance200status: denied. Theexpect_error: trueescape introduced in 5.12.1 only covers wire-error denials
(
adcp_errorresponses). A 200 withstatus: deniedis not a wireerror, so the flag was semantically inapplicable — the invariant would
anchor and flag every subsequent mutation in the run as a silent
bypass.
invariants.disablecovers both shapes uniformly.Validation is fail-fast at runner start (matches the storyboard-level
precedent):
invariants.disablethrows;disabled) throws.The
governance.denial_blocks_mutationfailure message now names thisfield and renders the exact YAML snippet to paste, for every anchor
shape. The previous message branched on anchor kind and suppressed the
hint for 200-status denials — that suppression pointed authors at
nothing. Unified under the one escape that works for both.
expect_error: true's implicit skip is unchanged. It remains thezero-ceremony path for expected-error contracts;
invariants.disableis the explicit surface for everything else.
Closes governance.denial_blocks_mutation: no escape hatch for check_governance 200 status:denied recovery paths #815.
c61ac15: Storyboard runner: strict/lenient response-schema reporting (closes the
final proposal from Storyboard runner: demote builder to enricher, hard-fail on missing fixture (extends #801) #820).
response_schemavalidations now run the strict AJV path alongside theexisting lenient Zod check and record the strict verdict on each
ValidationResult.strict(new optional field). The step's pass/fail isunchanged — it remains Zod-driven so existing tests and downstream
reporting stay backward-compatible. The strict verdict is additive
signal.
Per-run summary
Every
StoryboardResultnow carries astrict_validation_summary:strict_only_failuresis the actionable number. Responses that clearedZod passthrough but strict AJV rejected — typically
format: uriorpattern violations Zod's generated
z.string()doesn't enforce. A greenlenient run with
strict_only_failures > 0tells the developer theiragent isn't production-ready for strict dispatchers.
observable: falsewith zeroed counters signals "run had nostrict-eligible checks" (distinct from strict-clean). Dashboards and
JUnit formatters MUST check
observablebefore rendering counts.New helpers exported from
@adcp/client/testingsummarizeStrictValidation(phases)— compute the summary over afiltered subset of phases (e.g. render per-phase rollups in a
dashboard without re-running validation).
listStrictOnlyFailures(phases)— flat drill-down list of everystrict_only_failurewith{phase_id, step_id, task, variant, issues}for triage. Direct path fromstrict_only_failures: 7tothe seven offending responses without walking four levels of nested
arrays.
AJV coverage extended to flat-tree domains
The AJV schema loader now indexes
governance/,brand/,content-standards/,account/,property/, andcollection/alongside
bundled/. This closes a coverage gap wherestrict_validation_summarysystematically under-reported for mutatingtasks whose schemas ship outside the bundled tree —
check_governance,acquire_rights,creative_approval,sync_governance,sync_plans, CRUD on property_list / collection_list,etc. Previously those validations returned
strict: undefinedanddidn't count toward
checked; now they grade strict-eligible, soformat: uriviolations oncallerandidempotency_keypatternmismatches (protocol-wide requirements per AdCP 3.0 GA) surface in the
strictness delta where they belong.
CLI summary line
adcp storyboard runnow prints a single human-readable line under thelenient pass/fail tally when
observable: true:Silent when the run had no strict-eligible checks. The multi-storyboard
local-agent mode aggregates across results before printing.
ValidationResult.warningon strict-only failuresWhen Zod passes and AJV rejects, the step stays
passed: truebut a newwarningfield carries the top AJV issue:This closes the loop for LLM-driven self-correction and CI graphs that
scan
error/warningfields — they can act on the strict signalwithout the runner flipping step pass/fail and breaking existing tests.
Async variant fallback signal
When the agent's response advertises an async variant (
status: submitted/working/input-required) but the tool schema doesn'tship a variant schema, validation falls back to the sync response
schema. The fallback is now surfaced:
StrictValidationVerdict.variant_fallback_applied: trueStrictValidationVerdict.requested_variant: 'working'ValidationResult.warningnames the gapA conformance signal that was previously invisible (the tool accepts
sync-shaped validation even though the agent sent an async shape) now
tells the author: "this tool doesn't schema the variant your agent is
using." AJV acceptance of the sync fallback doesn't mask the signal.
Out of scope — tracked follow-ups (Strict-validation reporting follow-ups: envelope per-field check, --strict CLI gate #832)
Per-field envelope validation (
replayed,operation_id,context,extvalue shapes) as a separate check type — needs a speccontribution for
core/envelope.json.Opt-in
--strictCLI flag that gates CI onstrict_only_failures == 0— waiting for real-world delta telemetrybefore calibrating the gating policy.
Patch Changes
e2f9ea9: Fix storyboard request-builder fallback shapes: every fallback now
satisfies the upstream JSON schema it pairs with, unblocking strict-mode
agents that reject non-conforming payloads at the MCP boundary.
Builder fixes (all only take effect when
step.sample_requestisabsent — authored fixtures are unaffected):
check_governance—callernow emitshttps://${brand.domain}instead of a bare domain. Schema declares
caller: format: uri. (Storyboard fallback shapes: check_governance caller must be URI; creative_approval needs creative_url #805)build_creative,preview_creative,sync_creatives— theformat_idplaceholder for a missing format now carries aURI-formatted
agent_url(https://unknown.example.com/) instead ofthe string
"unknown". Schema (core/format-id.json) declaresagent_url: format: uri.update_media_buy— fallback now injectsaccount: context.account ?? resolveAccount(options); schema listsaccountas required. Matches the pattern peer builders(
sync_creatives,sync_catalogs,report_usage) already use.get_signals— when neitheroptions.briefnorsample_request.signal_idsis present, fallback now emits{ signal_spec: 'E2E fallback signal discovery' }instead of{}.Schema
anyOf: [signal_spec | signal_ids].create_content_standards— fallback now emits a minimal inlinebespoke policy (
policies: [{policy_id, enforcement: 'must', policy}])alongside
scope. SchemaanyOf: [policies | registry_policy_ids].New test:
test/lib/request-builder-jsonschema-roundtrip.test.js—AJV round-trip invariant that validates every builder fallback against
the upstream JSON schema. Complements the existing Zod round-trip test
(
request-builder-schema-roundtrip.test.js), which does not enforceformatkeywords or strictadditionalProperties.KNOWN_NONCONFORMINGallowlist is empty; self-pruning guard tests fire if a new fallback
regresses or an allowlisted task starts passing.
Observable-behavior notes:
buildRequestwho asserted onget_signalsreturning{}will need to update — it now returns{ signal_spec }.update_media_buyfallback now carries anaccount. Storyboardsrelying on a seller resolving account from
media_buy_idalone via thefallback will now send a canonical account; if the seller is strict
about account consistency across lifecycle, this is the correct signal.
No shipping first-party storyboards hit this path (all author
sample_request.account).Closes Storyboard fallback shapes: check_governance caller must be URI; creative_approval needs creative_url #805.
9e588bf: cli: warn on removed flags instead of silently ignoring
--platform-typewas removed from the SDK in 5.1 (comply()throws when it's passed programmatically), but the CLI was still capturing and silently dropping the flag. Third-party CI scripts that pass it today believe they're filtering agent selection when they aren't.adcp storyboard run(and itsadcp complydeprecated alias) now emits a stderr warning naming the flag, the version it was removed in, and the migration path:Non-breaking — execution continues. Warnings are suppressed under
--jsonto keep stdout as pure JSON. Detection covers both space-separated (--platform-type value) and equals (--platform-type=value) forms.The
REMOVED_FLAGSmap inbin/adcp.jsis a single location to extend as we deprecate additional flags.9e588bf: docs(creative-agent): louder build_creative response-shape callouts, add audio creative-template example
Makes discoverability of existing SDK surface better for creative agents:
docs/llms.txt— new "Watch out:" blocks onbuild_creative,preview_creative, andlist_creative_formatsthat point atbuildCreativeResponse/buildCreativeMultiResponse/typed asset factories and flag the audio-formatsrendersgotcha. Driven by a data map inscripts/generate-agent-docs.ts.skills/build-creative-agent/SKILL.md— cross-cutting pitfalls now mentionaudioAssetand spell out that platform-native top-level fields (tag_url,creative_id,media_type) are invalid responses. Adds an Audio subsection undercreative-templatecovering format declaration (type: 'audio',renders: [{ role, duration_seconds }]), async render pipelines, and a handler example usingbuildCreativeResponse+audioAsset.No library code changes — the factories and response helpers already shipped in prior releases.
48096a7: fix(testing): honor
step.sample_requeston add-shaped payloads in storyboardsync_audiencesbuilderThe storyboard request builder for
sync_audiencesonly delegated tostep.sample_requestfor delete or discovery shapes. Add-shaped payloads — where a storyboard authorsaudience_idwithadd: [...]identifiers — fell through to the generated fallback, which overwrote the authored id withtest-audience-${Date.now()}. Downstream steps that referenced the authored id (e.g.,delete_audiencein theaudience_syncspecialism, or$context.audience_idsubstitutions) then hitAUDIENCE_NOT_FOUNDbecause sync had registered a different id.The builder now delegates to
step.sample_requestwhenever it's present (matchingsync_event_sources,sync_catalogs,sync_creatives, and peers), falling back to the generated payload only when nosample_requestis authored.d3bd569: Two independent envelope-layer fixes for mutating responses:
1. Omit
replayedon fresh execution (align withprotocol-envelope.json)The SDK used to stamp
replayed: falseon every fresh-path mutating response. The envelope spec explicitly permits the field to be "omitted when the request was executed fresh" — absence now signals fresh execution, presence signals replay. Replay responses still carryreplayed: true.2. Mirror the replay marker into L2
content[0].text(A2A/REST parity)The replay-path stamp previously only touched MCP
structuredContent(L3). A2A and REST transports that consumecontent[0].text(L2) never sawreplayed: trueon replay — replay detection was silently broken on those transports.stampReplayednow updates both layers, matching the lockstep pattern used byinjectContextIntoResponseandsanitizeAdcpErrorEnvelope. This is orthogonal to the envelope-semantics change and a bona fide bug fix.What you'll see
createAdcpServer): fresh mutating responses no longer carryreplayed: false. Snapshot / contract tests assertingreplayed === falseon fresh need to be updated toreplayed !== true(orreplayed === undefined).replayed: trueon the text body where they previously didn't. Replay detection on non-MCP transports starts working.replayed === falsego silent on this version. Switch toreplayed !== true(fresh = absent or false) or key off a different signal (e.g. tool handler invocation count).@adcp/clientSDK): no change.ProtocolResponseParser.getReplayedalready treats absence andfalseidentically, and the envelope schema's"default": falsemeans schema-aware parsers materialize the same value either way.wrapEnvelopehelper: unchanged. Sellers callingwrapEnvelope({replayed: false})directly still round-trip the explicit marker. The asymmetry between the framework path (omits on fresh) and wrapEnvelope callers (honors explicitfalse) is intentional and documented on the option.Upstream coordination
Filed
adcp-client#857against thecompliance/cache/latest/universal/idempotency.yamlstoryboard: itsfield_value allowed_values: [false]assertion on the fresh-path step conflicts with its own prose ("Initial execution sets replayed: false (or omits the field)") — a literal-match bug whereany_of: [field_absent, field_value: false]was intended. Until the storyboard fix lands, thereplay_same_payloadphase will fail on the fresh-path step for sellers on this SDK version; all other phases (key_reuse_conflict,fresh_key_new_resource, webhook dedup) still pass. PerCLAUDE.md's storyboard-failure triage rule, the storyboard is the bug here — the envelope spec unambiguously permits omission.Internal
create-adcp-server.ts:injectReplayed(response, value)renamed tostampReplayed(response); fresh-path call site dropped; replay-path stamp mirrors into both L2 text and L3 structuredContent.wrap-envelope.ts,envelope-allowlist.ts,validation/schema-loader.ts: documentation only.wrap-envelope.tspicks up a note that the framework/helper asymmetry on freshreplayedis intentional.test/server-idempotency.test.js: fresh-path assertion relaxed from=== falseto!== true.0cc6f98: docs(seller-skill): define the baseline explicitly
Follow-up to refactor(testing): delete createDefaultTestControllerStore; ship real pattern #843. The seller-agent skill referenced "the baseline" 25+ times without enumerating it. A reader (or a coding agent like Claude) hitting the skill could not find an authoritative list of tools every
sales-*agent must implement, which is the gap that let an adapter update removeget_productsandcreate_media_buyon the read thatsales-socialis "walled-garden-only."Adds a new top-level "The baseline: what every sales-* agent MUST implement" section to
skills/build-seller-agent/SKILL.mdwith the full 11-tool table (get_adcp_capabilities,sync_accounts,list_accounts,get_products,list_creative_formats,create_media_buy,update_media_buy,get_media_buys,sync_creatives,list_creatives,get_media_buy_delivery), thecreateAdcpServerhandler group each belongs to, a minimum handler skeleton, and an explicit "if a specialism's storyboard doesn't exercise a baseline tool, the tool is not optional" note.Also anchors the section and wires cross-refs from the "Specialisms are additive" intro paragraph and the
sales-social"Baseline tools still apply" block so readers have a single source of truth for the baseline surface.No code changes; skill is shipped under
files[]so a patch bump surfaces the doc update to downstream consumers who ship CLAUDE.md-linked skill packs.42e43d4: Extend
detectShapeDriftHintin the storyboard runner to coversync_creativesandpreview_creativealongside the existingbuild_creativedetection (closes feat(testing): extend detectShapeDriftHint to sync_creatives and preview_creative #849).Both tools share the same drift pattern as
build_creative: a handler returns a single inner shape at the top level instead of wrapping it in the tool's required array/discriminator envelope. A bare schema error ("must have required property X") doesn't tell the developer they've inverted the response shape — this hint does.sync_creatives— top-levelcreative_id/platform_id/actionwithout acreativesarray (orerrors/task_idfor the other two valid branches) → hint namessyncCreativesResponse()from@adcp/client/server.preview_creative— top-levelpreview_url/preview_htmlwithout thepreviews[].renders[]nesting andresponse_typediscriminator → hint namespreviewCreativeResponse().interactive_urlalone doesn't trigger (it's a legal top-level sibling on the single-variant branch).Scoped per-tool so cross-tool field names can't bleed across branches (e.g.
build_creative-specifictag_urldoesn't trip thepreview_creativebranch).11 new tests covering positive detection, each valid branch that must stay silent, and cross-tool scoping.
908786e: Extend
detectShapeDriftHintin the storyboard runner to cover list-shaped tools (closes feat(testing): extend detectShapeDriftHint to list_creatives + peer list-shaped tools #852):list_creatives— handler returns bare[{...}]instead of{ creatives, query_summary, pagination }list_creative_formats— bare array instead of{ formats: [...] }list_accounts— bare array instead of{ accounts: [...] }get_products— bare array instead of{ products: [...] }The detector now accepts
unknownrather thanRecord<string, unknown>so it can recognize bare-array responses at the root — a common drift class where AJV's error ("expected object, got array") doesn't name the required wrapper key. Each known list tool gets a pointed hint naming the wrapper and the response helper (listCreativesResponse,listCreativeFormatsResponse,listAccountsResponse,productsResponse) from@adcp/client/server.Bare arrays for unknown task names pass through silently — the detector only fires on registered list tools to avoid false positives on APIs that legitimately return top-level arrays.
8 new tests covering each tool, the wrapper-present negative case, unknown-task pass-through, empty-array handling, and null/primitive defensive cases.