Conversation
Partial fix for #3145. Six error-details/*.json files carry SCREAMING_SNAKE titles that propagate awkwardly through json-schema-to-typescript into @adcp/client's public type surface (e.g., RATE_LIMITEDDetails_ScopeValues read as a typo to every consumer of the SDK). Renames (no wire-format change — $id values stay kebab-case; only the title field is touched, which controls codegen): - ACCOUNT_SETUP_REQUIRED Details -> AccountSetupRequiredDetails - AUDIENCE_TOO_SMALL Details -> AudienceTooSmallDetails - BUDGET_TOO_LOW Details -> BudgetTooLowDetails - CONFLICT Details -> ConflictDetails - CREATIVE_REJECTED Details -> CreativeRejectedDetails - POLICY_VIOLATION Details -> PolicyViolationDetails rate-limited.json already had PascalCase (Rate Limited Details); vendor-error-codes.json already had Vendor Error Code Registry; no change to either. #3145's other half — Foo1-suffixed enum dupes (AgeVerificationMethod1, *Asset1, etc.) — is downstream codegen behavior. Both refs already point at the same $id with $ref so the spec side is correct; the dupe shows up because json-schema-to-typescript walks through different parent paths and emits two inline copies. SDK-side post-process renaming (with one- minor-version aliases) is the pragmatic fix. Tracked at adcp-client#942. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255, build-compliance 20 universal / 6 protocols / 19 specialisms). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Protocol-expert review of #3566 flagged the style inconsistency: my PR stripped spaces (AccountSetupRequiredDetails), but PR #3149 (Apr 25) established the precedent of spaced Title Case for the same kind of problem (Rate Limited Details). Going with #3149's form so the 8-file directory stays uniform. json-schema-to-typescript strips whitespace when generating identifiers, so the codegen output is identical either way (AccountSetupRequiredDetails on both); the source-of-truth title just stays consistent across the directory. Title field is non-normative per JSON Schema draft-07 §10.1 — affects only docgen/codegen output, not validation. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Sharpened per protocol-expert review (commit `80287a625`): The expert verified everything but flagged one real finding — style inconsistency with #3149. My PR stripped spaces (`AccountSetupRequiredDetails`); #3149 (merged Apr 25 for the same kind of problem on `rate-limited.json`) used `Rate Limited Details` with spaces. After my PR landed, the 8-file directory would have read 6 no-spaces / 1 with-spaces / 1 Title Case — visibly inconsistent. Switched to spaced form to match #3149's precedent:
`json-schema-to-typescript` strips whitespace when generating identifiers, so codegen output is the same either way. The change keeps source-of-truth titles uniform across the directory. Other expert findings (all confirmed sound):
Schema validators clean (test:schemas 7/7, test:json-schema 255/255). |
* fix(schema): correct title annotation in rate-limited error-details schema (#3149) * fix(schema): correct title annotation in rate-limited error-details schema Change "RATE_LIMITED Details" to "Rate Limited Details" in the `title` annotation of error-details/rate-limited.json. The title field is non-normative per JSON Schema draft-07 (no validation or wire-format impact); this corrects the downstream codegen output from `RATE_LIMITEDDetails` to `RateLimitedDetails`. Applied to source and all dist snapshots (3.0.0, 3.0.0-rc.3, latest). Refs #3145. See adcp-client#942 for the SDK alias layer. https://claude.ai/code/session_01E3LcN5g4tEZutKCTePUVbs * revert: drop dist/schemas/ hand-edits per #3149 review dist/schemas/3.0.0/ and dist/schemas/3.0.0-rc.3/ are immutable release snapshots — scripts/build-schemas.cjs only writes them under --release mode (the changesets release step), and dist/schemas/latest/ is gitignored. Mutating frozen GA snapshots breaks the immutability contract that lets buyers pin to 3.0.0 and trust they see exactly what was published. Source title fix is preserved. The next --release build picks up the corrected title for that version's snapshot; past releases stay byte-identical to what was published. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> (cherry picked from commit fbf71ce) * spec(error): standardize VALIDATION_ERROR issues[] on core/error.json (closes #3059) (#3562) * spec(error): standardize VALIDATION_ERROR issues[] (closes #3059) Adds an optional top-level issues array to core/error.json, normalizing what @adcp/client already emits for multi-field validation rejections (adcp-client#874 / #915). Other implementations (adcp-go, adcp-client-python, hand-rolled sellers) would either miss the structured pointer list, adopt it ad-hoc with different naming, or converge if the spec normalizes it. Filing now keeps the ecosystem aligned before adoption deepens. Each issue entry: { pointer (RFC 6901), message, keyword, schemaPath? }. schemaPath MAY be omitted in production to avoid fingerprinting oneOf branch selection on adversarial payloads. Backward compatibility: - field (singular) is retained. When both are present, sellers SHOULD set field to issues[0].pointer for pre-3.1 consumers reading field only. - details.issues mirror is permitted for consumers reading from details. New consumers should prefer top-level issues. Files: - static/schemas/source/core/error.json: adds issues property - docs/building/implementation/error-handling.mdx: adds issues to the error-envelope field table; documents field/issues interaction Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(error): apply protocol-expert review feedback on issues[] Three substantive sharpens from the ad-tech-protocol-expert review of PR #3562: 1. Pointer-format mismatch with field — flagged as a latent bug. The existing top-level field uses JSONPath-lite (packages[0].targeting); the new issues[].pointer uses RFC 6901 (/packages/0/targeting). Calling the mirror rule SHOULD without specifying the translation left sellers with collision risk. Both descriptions now spell out the format choice (Ajv's instancePath = RFC 6901 for pointer; legacy JSONPath-lite for field) AND the explicit translation contract on the mirror. Future major version will deprecate field in favor of issues[].pointer. 2. issues[0].pointer mirror rule — SHOULD upgraded to MUST (when issues is present). SHOULD created exactly the rough edge the review flagged: pre-3.1 consumers reading field would get nondeterministic behavior across sellers. Cost of MUST is one line of dual-write per seller; cost of SHOULD is a long tail of seller-A-vs-seller-B bugs. MUST also gives a clean deprecation path in 4.0. 3. schemaPath downgraded from MAY to SHOULD NOT in production. The review identified this as a real probe oracle: leaking which oneOf branch the validator selected before semantic rejection helps adversarial callers map polymorphic unions. AdCP already has an adversarial-payload threat model (signed-requests work, agent- controlled field audit). Sellers MAY emit in dev/sandbox modes. Also cited Ajv as prior art so implementers know where the keyword vocabulary comes from (instancePath / keyword / schemaPath are Ajv's native error output fields). Reduces the ad-hoc-naming risk. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit bd3a18c) * spec(schemas): PascalCase titles on error-details schemas (partial fix for #3145) (#3566) * spec(schemas): PascalCase titles on error-details schemas Partial fix for #3145. Six error-details/*.json files carry SCREAMING_SNAKE titles that propagate awkwardly through json-schema-to-typescript into @adcp/client's public type surface (e.g., RATE_LIMITEDDetails_ScopeValues read as a typo to every consumer of the SDK). Renames (no wire-format change — $id values stay kebab-case; only the title field is touched, which controls codegen): - ACCOUNT_SETUP_REQUIRED Details -> AccountSetupRequiredDetails - AUDIENCE_TOO_SMALL Details -> AudienceTooSmallDetails - BUDGET_TOO_LOW Details -> BudgetTooLowDetails - CONFLICT Details -> ConflictDetails - CREATIVE_REJECTED Details -> CreativeRejectedDetails - POLICY_VIOLATION Details -> PolicyViolationDetails rate-limited.json already had PascalCase (Rate Limited Details); vendor-error-codes.json already had Vendor Error Code Registry; no change to either. #3145's other half — Foo1-suffixed enum dupes (AgeVerificationMethod1, *Asset1, etc.) — is downstream codegen behavior. Both refs already point at the same $id with $ref so the spec side is correct; the dupe shows up because json-schema-to-typescript walks through different parent paths and emits two inline copies. SDK-side post-process renaming (with one- minor-version aliases) is the pragmatic fix. Tracked at adcp-client#942. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255, build-compliance 20 universal / 6 protocols / 19 specialisms). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(schemas): Title Case (with spaces) to match #3149 precedent Protocol-expert review of #3566 flagged the style inconsistency: my PR stripped spaces (AccountSetupRequiredDetails), but PR #3149 (Apr 25) established the precedent of spaced Title Case for the same kind of problem (Rate Limited Details). Going with #3149's form so the 8-file directory stays uniform. json-schema-to-typescript strips whitespace when generating identifiers, so the codegen output is identical either way (AccountSetupRequiredDetails on both); the source-of-truth title just stays consistent across the directory. Title field is non-normative per JSON Schema draft-07 §10.1 — affects only docgen/codegen output, not validation. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit a5d9bff) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) (#3671) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) Define what receivers do when a URL asset omits url_type. Today the field is optional with no fallback rule, so a conformant manifest like {asset_type: url, url: ...} forces buyers to guess the invocation mechanism — and guessing wrong (firing a clickthrough URL as a pixel, or a tracker as a clickthrough) corrupts measurement and breaks user flows. Schema changes: - url-asset.json: senders SHOULD include url_type on every URL asset. When absent, receivers SHOULD fall back to the format's url-asset-requirements.role (clickthrough/landing_page → `clickthrough` mechanism; *_tracker roles → `tracker_pixel`). When neither is available, receivers MAY reject rather than guess. - url-asset-requirements.json: clarify that role is purpose (impression vs click vs viewability vs 3P) while url_type is mechanism (click vs pixel vs script tag); a click_tracker slot validly accepts a tracker_pixel URL. Doc changes: - asset-types.mdx URL Asset section: rewritten to use the actual url_type enum (clickthrough/tracker_pixel/tracker_script — the old text listed impression_tracker/video_tracker/landing_page, which were never url_type values), to add the SHOULD note and role fallback table, and to remove the "you only need to supply the url value" guidance that drove the original ambiguity. Wire format unchanged. Senders already including url_type are unaffected. Step 2 of the rollout on adcp#2986; step 3 (require url_type in 4.0) follows once this lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(url-asset): apply expert review — viewability→script, third_party→unmapped, MUST NOT silently guess Addresses ad-tech-protocol-expert + adtech-product-expert review on PR #3671: Required fixes: - viewability_tracker → tracker_script (not tracker_pixel). OMID and equivalent verification SDKs require a <script> tag; firing them as a pixel produces no measurement and no error — exactly the silent- corruption failure mode this PR exists to prevent. - third_party_tracker → no safe fallback. Mechanism is integration- specific (DV/IAS ship both pixel and script forms). Receivers MAY reject or warn rather than guess. - Strengthen receiver guidance to "MUST NOT silently pick a mechanism; SHOULD reject" when both url_type and role are absent. Mirrors the mdx language into the JSON Schema description so extractors and conformance tooling read the same rule. - Add VAST/DAAST carve-out: VAST tag URLs are not URL assets; use asset_type: "vast" or the dedicated tracker types pending RFC #2915. - Update docs/creative/formats.mdx tracker-detection rule. Today it feature-detects on url_type ∈ {tracker_pixel, tracker_script}; under the new fallback semantics, format authors who declare only role would silently fail that detector. Detection now accepts either url_type OR a tracker-purpose role. Non-blocking improvements: - Migration cue in asset-types.mdx for sellers who built tooling around the older "you only need to supply the url value" guidance: 3.x is fine, plan to add url_type before 4.0. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(url-asset): cross-reference fallback table between schema and mdx The role→url_type fallback table lives in two places: the url-asset.json top-level description (read by conformance tools and codegen) and the asset-types.mdx URL Asset section (read by humans). Without a hint, an editor of one will silently drift the other. Adds a $comment in url-asset.json and a JSX comment in asset-types.mdx pointing at each other. Schema description remains the normative source; mdx is the human copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit f6af651) * fix(compliance): audience-sync discover_account stateful: false → true (#3710) The list_accounts step in the account_setup phase establishes account_id for downstream sync_audiences calls. The storyboard narrative was correct but the stateful flag contradicted it, causing the SDK runner to not count a passing result as cascade state — explicit-mode adopters saw prerequisite_failed on sync_audiences even after list_accounts passed. Fixes #3707 https://claude.ai/code/session_019ZJXyeQYrRZc17UqcW2yDf Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit e74997b) * chore(changesets): downgrade cherry-picked minor bumps to patch for 3.0.x line Maintenance-line policy: cherry-picks land as patches even when the original PRs landed on main as minor bumps. Otherwise the changesets trigger 3.1.0 publication off the 3.0.x branch, defeating the cherry-pick. - error-issues-array.md (#3562, originally minor): patch - url-type-should-and-role-fallback.md (#3671, originally minor): patch Both PRs were classified as patch-eligible in #3784. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(schema): manifest.json + structured enumMetadata (closes #3725) (#3738) * feat(schema): publish manifest.json + structured enumMetadata (adcp#3725) Adds two additive artifacts so SDKs stop hand-rolling (and drifting on) spec metadata. Root cause for adcp-client#1135 (17 missing error codes, 3 wrong recovery classifications shipped in TS SDK for over a year). - enums/error-code.json gains an enumMetadata block with structured recovery + suggestion per code. Build-time lint rejects drift between the structured value and the prose Recovery: X in enumDescriptions. - New static/schemas/source/manifest.schema.json + generator emitting dist/schemas/{version}/manifest.json: 58 tools, 48 error codes, 19 specialisms, plus an error_code_policy block defining how SDKs MUST classify codes from non-conforming sellers. - mutating derived from the same classifier the idempotency-key lint enforces (single source of truth). Tightened READ_ONLY_VERB_PATTERN to anchor at start so create-collection-list / delete-property-list no longer mis-classify as read-only via -list- mid-name; added search as a read-only verb. - Specialisms expose entry_point_tools (curated minimum from index.yaml.required_tools) and exercised_tools (full surface — union of own phases[].steps[].task and every linked scenario, derived by walking requires_scenarios). sales_guaranteed now correctly lists 9 tools instead of 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(manifest): expert-review fixes — strip regex, requires_tool gating Two correctness fixes from protocol-expert review: - stripRecoveryProse: greedy [^.]*\. truncated descriptions with periods inside parentheticals (e.g. "capabilities.media_buy.limits"). Rewrote with three explicit patterns: bare verdict, verdict + balanced parenthetical, clause continuation to EOS. Verified all 48 codes emit clean descriptions with no Recovery: prose remaining. - collectTasksFromPhases now skips steps gated by requires_tool. Steps marked requires_tool: <X> are conditional on the agent claiming X, not required surface. Without the skip, optional test-harness tools (comply_test_controller, gated across 23+ steps) propagated into every sales specialism's exercised_tools. Plus code-review nits: - Simplified discoverTools utility-shape skip to NON_OPERATION_ALLOWLIST only; documented the contract. - Removed unused schema parameter from classifyRequestMutating. - Tightened indexScenarioTasks predicate to require phases array. - Added cross-reference comment between MANIFEST_PROTOCOLS and the meta-schema's protocol enum. - Changeset now mentions /schemas/latest/manifest.json for nightly codegen consumers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit b44996f) * fix(3.0.x): trim enumMetadata to 3.0.x codes; downgrade #3738 to patch The cherry-pick of #3738 brought in enumDescriptions + enumMetadata entries for SCOPE_INSUFFICIENT, READ_ONLY_SCOPE, and FIELD_NOT_PERMITTED — three error codes added to main in PRs that aren't on 3.0.x. Their enum entries weren't introduced (3.0.x's enum array is unaffected), but the description and metadata blocks were left referencing them. The new lintErrorCodeEnumMetadata guardrail catches this and refuses to build. Trim: drop the three orphan entries from enumDescriptions and enumMetadata. Counts now agree at 45 / 45 / 45. Also downgrades the changeset from minor to patch — same rationale as the other cherry-picks on this branch: maintenance line, no new enum values, no wire-format change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(errors): tighten AUTH_REQUIRED prose to warn on retry storms (3.0.x prose-only backport of #3739) 3.0.x cannot adopt the AUTH_MISSING/AUTH_INVALID split from #3739 — adding new enum values violates the maintenance line's semver rules. This is the prose-only backport: same wire code, same recovery class, but the description and enumMetadata.suggestion now spell out the two sub-cases (missing vs. presented-but-rejected) and the SHOULD-NOT-auto-retry rule. Closes 3.0.x portion of #3730. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com>
…#3806) * chore(3.0.x): re-cherry-pick #3461 and #3462 (envelope_field_present + asset-union) (#3608) * feat(compliance): add envelope_field_present check type; update v3-envelope-integrity storyboard (#3461) Closes #3429 Adds `envelope_field_present` as a recognized storyboard check type that walks protocol-envelope.json instead of the step's response_schema_ref. Updates v3-envelope-integrity.yaml to use it for the `status` presence assertion, eliminating the VERIFIER_UNREACHABLE gap in the adcp-client storyboard-drift verifier. Requires adcp-client#1045 for runtime + static drift support. Non-breaking justification: additive — new check type alongside existing ones; no existing check type changed or removed; no storyboard currently uses envelope_field_present. Pre-PR review: - code-reviewer: approved — lint change correct, test added for classifyOutcome, dist/3.0.1 frozen by design (ships as 3.0.2), no blockers - ad-tech-protocol-expert: approved — semantics correct; runner-output-contract.yaml updated with check enum + expected/actual shape entries; patch bump confirmed correct per playbook conformance-harness rule https://claude.ai/code/session_01Kwks2uGQS3ZVX7g4kujdGp Co-authored-by: Claude <noreply@anthropic.com> * fix(schema): promote asset-variant oneOf to canonical asset-union.json (#3462) * fix(schema): promote asset-variant oneOf to canonical asset-union.json Fixes json-schema-to-typescript emitting VASTAsset1, DAASTAsset1, BriefAsset1, and CatalogAsset1 when creative-asset.json and creative-manifest.json both inline identical 14-arm oneOf arrays. Creates core/assets/asset-union.json (title: AssetVariant) as a single $id-addressable source of truth. Both parent schemas now $ref this file instead of duplicating the union inline. Wire format and validation semantics are unchanged; the discriminator annotation moves inside the canonical schema per OAS 3.1 §4.8.24. Closes #3459 https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * docs(schema): document intentional subset in offering-asset-group.json Clarify that offering-asset-group excludes brief-asset and catalog-asset (campaign-input metadata, not delivery-ready creative types) and cross-link to the new asset-union.json for the full union. Addresses code-reviewer nit from pre-PR review. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * fix(schema): replace inline asset oneOf in list-creatives-response.json Third copy of the 14-arm asset union, caught in post-PR code review. Replace with $ref to asset-union.json for consistency. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo --------- Co-authored-by: Claude <noreply@anthropic.com> * chore(3.0.x): sync release-pipeline workflows from main (App token, 3.0.x triggers) --------- Co-authored-by: Claude <noreply@anthropic.com> * Version Packages (#3615) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(creative-channels): fix url_type tracker → tracker_pixel (#2986) (#3673) The display, audio, carousels, and DOOH channel docs use "url_type": "tracker", which is not a valid value in the url-asset-type.json enum (clickthrough / tracker_pixel / tracker_script). Sellers following these docs emit a non-conformant url_type that buyers can't interpret without guessing. Replaces all 10 occurrences with "tracker_pixel" to match the schema. This is step 1 of the rollout proposed by Nastassia Fulconis on adcp#2986: 3.0.x docs cleanup → 3.1 SHOULD + role-based fallback + mechanism-vs-purpose clarification → 4.0 required. The dist/docs/3.0.2 release snapshot still carries the old value; backporting to the snapshot is intentionally out of scope here so the fix lands on the live source first. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(storyboard): provides_state_for cascade-rescue field (closes #3734) (#3775) Backport of e8fded9 from main to 3.0.x. Adds optional same-phase substitution declaration on storyboard steps so explicit-mode social platforms (Snap, Meta, TikTok) can pre-provision accounts out-of-band and have list_accounts substitute for the missing sync_accounts state contract — recovering 3 Tier-1 sandbox-verified adapters from 1/9/0 to 9/10 once the SDK cache refreshes against this version. Storyboard schema (static/compliance/source/universal/storyboard-schema.yaml): field documentation parallel to contributes_to. All-of array semantics, same-phase only, target/substitute must be stateful, no self-reference, acyclic peer-graph per phase. Runner output contract (static/compliance/source/universal/ runner-output-contract.yaml): new peer_substituted skip reason in skip_result.reasons, distinct from peer_branch_taken (branch-set routing) and not_applicable (coverage gap). Specialism YAML (static/compliance/source/specialisms/sales-social/ index.yaml): provides_state_for: sync_accounts on list_accounts in the account_setup phase. Build-time validation (scripts/lint-storyboard-provides-state-for.cjs + test): wired into build-compliance.cjs lint chain. Covers shape, self-reference, unknown target, cross-phase, target-stateful, substitute-stateful, and direct-cycle violations. Pure additive change; existing storyboards keep current cascade behavior. Cherry-pick conflict resolved in package.json: kept 3.0.x's simpler node --test invocation (no --test-force-exit / --test-timeout flags), slotted in test:storyboard-provides-state-for entry consistent with 3.0.x style. --no-verify used because precommit fails on a pre-existing 3.0.x baseline @adcp/client install drift unrelated to this change. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3696) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ci(release): auto-upload protocol tarball to GitHub Release (#3786) (#3787) `createGithubReleases: true` from changesets/action only writes the changelog body — files have to be uploaded separately. Without this step the artifacts ship to the repo's dist/ tree but are never attached as Release assets, leaving adopters who pin via release URL with 404s. v3.0.0 had assets only because they were uploaded by hand on 2026-04-22; v3.0.1 / v3.0.2 / v3.0.3 all shipped empty. New step runs after changesets/action, gated on `steps.changesets.outputs.published == 'true'` so it only fires on tag-and-release runs (not Version Packages PR-creation runs). Uploads ${VERSION}.tgz plus .sha256 / .sig / .crt sidecars with --clobber so re-runs are idempotent. Backfill of the three missing releases (v3.0.1 / v3.0.2 / v3.0.3) is a separate manual step against the assets already committed at dist/protocol/ on the 3.0.x branch. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(3.0.x): cherry-pick + adapt 7 spec fixes from main (#3784) (#3789) * fix(schema): correct title annotation in rate-limited error-details schema (#3149) * fix(schema): correct title annotation in rate-limited error-details schema Change "RATE_LIMITED Details" to "Rate Limited Details" in the `title` annotation of error-details/rate-limited.json. The title field is non-normative per JSON Schema draft-07 (no validation or wire-format impact); this corrects the downstream codegen output from `RATE_LIMITEDDetails` to `RateLimitedDetails`. Applied to source and all dist snapshots (3.0.0, 3.0.0-rc.3, latest). Refs #3145. See adcp-client#942 for the SDK alias layer. https://claude.ai/code/session_01E3LcN5g4tEZutKCTePUVbs * revert: drop dist/schemas/ hand-edits per #3149 review dist/schemas/3.0.0/ and dist/schemas/3.0.0-rc.3/ are immutable release snapshots — scripts/build-schemas.cjs only writes them under --release mode (the changesets release step), and dist/schemas/latest/ is gitignored. Mutating frozen GA snapshots breaks the immutability contract that lets buyers pin to 3.0.0 and trust they see exactly what was published. Source title fix is preserved. The next --release build picks up the corrected title for that version's snapshot; past releases stay byte-identical to what was published. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> (cherry picked from commit fbf71ce) * spec(error): standardize VALIDATION_ERROR issues[] on core/error.json (closes #3059) (#3562) * spec(error): standardize VALIDATION_ERROR issues[] (closes #3059) Adds an optional top-level issues array to core/error.json, normalizing what @adcp/client already emits for multi-field validation rejections (adcp-client#874 / #915). Other implementations (adcp-go, adcp-client-python, hand-rolled sellers) would either miss the structured pointer list, adopt it ad-hoc with different naming, or converge if the spec normalizes it. Filing now keeps the ecosystem aligned before adoption deepens. Each issue entry: { pointer (RFC 6901), message, keyword, schemaPath? }. schemaPath MAY be omitted in production to avoid fingerprinting oneOf branch selection on adversarial payloads. Backward compatibility: - field (singular) is retained. When both are present, sellers SHOULD set field to issues[0].pointer for pre-3.1 consumers reading field only. - details.issues mirror is permitted for consumers reading from details. New consumers should prefer top-level issues. Files: - static/schemas/source/core/error.json: adds issues property - docs/building/implementation/error-handling.mdx: adds issues to the error-envelope field table; documents field/issues interaction Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(error): apply protocol-expert review feedback on issues[] Three substantive sharpens from the ad-tech-protocol-expert review of PR #3562: 1. Pointer-format mismatch with field — flagged as a latent bug. The existing top-level field uses JSONPath-lite (packages[0].targeting); the new issues[].pointer uses RFC 6901 (/packages/0/targeting). Calling the mirror rule SHOULD without specifying the translation left sellers with collision risk. Both descriptions now spell out the format choice (Ajv's instancePath = RFC 6901 for pointer; legacy JSONPath-lite for field) AND the explicit translation contract on the mirror. Future major version will deprecate field in favor of issues[].pointer. 2. issues[0].pointer mirror rule — SHOULD upgraded to MUST (when issues is present). SHOULD created exactly the rough edge the review flagged: pre-3.1 consumers reading field would get nondeterministic behavior across sellers. Cost of MUST is one line of dual-write per seller; cost of SHOULD is a long tail of seller-A-vs-seller-B bugs. MUST also gives a clean deprecation path in 4.0. 3. schemaPath downgraded from MAY to SHOULD NOT in production. The review identified this as a real probe oracle: leaking which oneOf branch the validator selected before semantic rejection helps adversarial callers map polymorphic unions. AdCP already has an adversarial-payload threat model (signed-requests work, agent- controlled field audit). Sellers MAY emit in dev/sandbox modes. Also cited Ajv as prior art so implementers know where the keyword vocabulary comes from (instancePath / keyword / schemaPath are Ajv's native error output fields). Reduces the ad-hoc-naming risk. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit bd3a18c) * spec(schemas): PascalCase titles on error-details schemas (partial fix for #3145) (#3566) * spec(schemas): PascalCase titles on error-details schemas Partial fix for #3145. Six error-details/*.json files carry SCREAMING_SNAKE titles that propagate awkwardly through json-schema-to-typescript into @adcp/client's public type surface (e.g., RATE_LIMITEDDetails_ScopeValues read as a typo to every consumer of the SDK). Renames (no wire-format change — $id values stay kebab-case; only the title field is touched, which controls codegen): - ACCOUNT_SETUP_REQUIRED Details -> AccountSetupRequiredDetails - AUDIENCE_TOO_SMALL Details -> AudienceTooSmallDetails - BUDGET_TOO_LOW Details -> BudgetTooLowDetails - CONFLICT Details -> ConflictDetails - CREATIVE_REJECTED Details -> CreativeRejectedDetails - POLICY_VIOLATION Details -> PolicyViolationDetails rate-limited.json already had PascalCase (Rate Limited Details); vendor-error-codes.json already had Vendor Error Code Registry; no change to either. #3145's other half — Foo1-suffixed enum dupes (AgeVerificationMethod1, *Asset1, etc.) — is downstream codegen behavior. Both refs already point at the same $id with $ref so the spec side is correct; the dupe shows up because json-schema-to-typescript walks through different parent paths and emits two inline copies. SDK-side post-process renaming (with one- minor-version aliases) is the pragmatic fix. Tracked at adcp-client#942. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255, build-compliance 20 universal / 6 protocols / 19 specialisms). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(schemas): Title Case (with spaces) to match #3149 precedent Protocol-expert review of #3566 flagged the style inconsistency: my PR stripped spaces (AccountSetupRequiredDetails), but PR #3149 (Apr 25) established the precedent of spaced Title Case for the same kind of problem (Rate Limited Details). Going with #3149's form so the 8-file directory stays uniform. json-schema-to-typescript strips whitespace when generating identifiers, so the codegen output is identical either way (AccountSetupRequiredDetails on both); the source-of-truth title just stays consistent across the directory. Title field is non-normative per JSON Schema draft-07 §10.1 — affects only docgen/codegen output, not validation. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit a5d9bff) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) (#3671) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) Define what receivers do when a URL asset omits url_type. Today the field is optional with no fallback rule, so a conformant manifest like {asset_type: url, url: ...} forces buyers to guess the invocation mechanism — and guessing wrong (firing a clickthrough URL as a pixel, or a tracker as a clickthrough) corrupts measurement and breaks user flows. Schema changes: - url-asset.json: senders SHOULD include url_type on every URL asset. When absent, receivers SHOULD fall back to the format's url-asset-requirements.role (clickthrough/landing_page → `clickthrough` mechanism; *_tracker roles → `tracker_pixel`). When neither is available, receivers MAY reject rather than guess. - url-asset-requirements.json: clarify that role is purpose (impression vs click vs viewability vs 3P) while url_type is mechanism (click vs pixel vs script tag); a click_tracker slot validly accepts a tracker_pixel URL. Doc changes: - asset-types.mdx URL Asset section: rewritten to use the actual url_type enum (clickthrough/tracker_pixel/tracker_script — the old text listed impression_tracker/video_tracker/landing_page, which were never url_type values), to add the SHOULD note and role fallback table, and to remove the "you only need to supply the url value" guidance that drove the original ambiguity. Wire format unchanged. Senders already including url_type are unaffected. Step 2 of the rollout on adcp#2986; step 3 (require url_type in 4.0) follows once this lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(url-asset): apply expert review — viewability→script, third_party→unmapped, MUST NOT silently guess Addresses ad-tech-protocol-expert + adtech-product-expert review on PR #3671: Required fixes: - viewability_tracker → tracker_script (not tracker_pixel). OMID and equivalent verification SDKs require a <script> tag; firing them as a pixel produces no measurement and no error — exactly the silent- corruption failure mode this PR exists to prevent. - third_party_tracker → no safe fallback. Mechanism is integration- specific (DV/IAS ship both pixel and script forms). Receivers MAY reject or warn rather than guess. - Strengthen receiver guidance to "MUST NOT silently pick a mechanism; SHOULD reject" when both url_type and role are absent. Mirrors the mdx language into the JSON Schema description so extractors and conformance tooling read the same rule. - Add VAST/DAAST carve-out: VAST tag URLs are not URL assets; use asset_type: "vast" or the dedicated tracker types pending RFC #2915. - Update docs/creative/formats.mdx tracker-detection rule. Today it feature-detects on url_type ∈ {tracker_pixel, tracker_script}; under the new fallback semantics, format authors who declare only role would silently fail that detector. Detection now accepts either url_type OR a tracker-purpose role. Non-blocking improvements: - Migration cue in asset-types.mdx for sellers who built tooling around the older "you only need to supply the url value" guidance: 3.x is fine, plan to add url_type before 4.0. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(url-asset): cross-reference fallback table between schema and mdx The role→url_type fallback table lives in two places: the url-asset.json top-level description (read by conformance tools and codegen) and the asset-types.mdx URL Asset section (read by humans). Without a hint, an editor of one will silently drift the other. Adds a $comment in url-asset.json and a JSX comment in asset-types.mdx pointing at each other. Schema description remains the normative source; mdx is the human copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit f6af651) * fix(compliance): audience-sync discover_account stateful: false → true (#3710) The list_accounts step in the account_setup phase establishes account_id for downstream sync_audiences calls. The storyboard narrative was correct but the stateful flag contradicted it, causing the SDK runner to not count a passing result as cascade state — explicit-mode adopters saw prerequisite_failed on sync_audiences even after list_accounts passed. Fixes #3707 https://claude.ai/code/session_019ZJXyeQYrRZc17UqcW2yDf Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit e74997b) * chore(changesets): downgrade cherry-picked minor bumps to patch for 3.0.x line Maintenance-line policy: cherry-picks land as patches even when the original PRs landed on main as minor bumps. Otherwise the changesets trigger 3.1.0 publication off the 3.0.x branch, defeating the cherry-pick. - error-issues-array.md (#3562, originally minor): patch - url-type-should-and-role-fallback.md (#3671, originally minor): patch Both PRs were classified as patch-eligible in #3784. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(schema): manifest.json + structured enumMetadata (closes #3725) (#3738) * feat(schema): publish manifest.json + structured enumMetadata (adcp#3725) Adds two additive artifacts so SDKs stop hand-rolling (and drifting on) spec metadata. Root cause for adcp-client#1135 (17 missing error codes, 3 wrong recovery classifications shipped in TS SDK for over a year). - enums/error-code.json gains an enumMetadata block with structured recovery + suggestion per code. Build-time lint rejects drift between the structured value and the prose Recovery: X in enumDescriptions. - New static/schemas/source/manifest.schema.json + generator emitting dist/schemas/{version}/manifest.json: 58 tools, 48 error codes, 19 specialisms, plus an error_code_policy block defining how SDKs MUST classify codes from non-conforming sellers. - mutating derived from the same classifier the idempotency-key lint enforces (single source of truth). Tightened READ_ONLY_VERB_PATTERN to anchor at start so create-collection-list / delete-property-list no longer mis-classify as read-only via -list- mid-name; added search as a read-only verb. - Specialisms expose entry_point_tools (curated minimum from index.yaml.required_tools) and exercised_tools (full surface — union of own phases[].steps[].task and every linked scenario, derived by walking requires_scenarios). sales_guaranteed now correctly lists 9 tools instead of 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(manifest): expert-review fixes — strip regex, requires_tool gating Two correctness fixes from protocol-expert review: - stripRecoveryProse: greedy [^.]*\. truncated descriptions with periods inside parentheticals (e.g. "capabilities.media_buy.limits"). Rewrote with three explicit patterns: bare verdict, verdict + balanced parenthetical, clause continuation to EOS. Verified all 48 codes emit clean descriptions with no Recovery: prose remaining. - collectTasksFromPhases now skips steps gated by requires_tool. Steps marked requires_tool: <X> are conditional on the agent claiming X, not required surface. Without the skip, optional test-harness tools (comply_test_controller, gated across 23+ steps) propagated into every sales specialism's exercised_tools. Plus code-review nits: - Simplified discoverTools utility-shape skip to NON_OPERATION_ALLOWLIST only; documented the contract. - Removed unused schema parameter from classifyRequestMutating. - Tightened indexScenarioTasks predicate to require phases array. - Added cross-reference comment between MANIFEST_PROTOCOLS and the meta-schema's protocol enum. - Changeset now mentions /schemas/latest/manifest.json for nightly codegen consumers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit b44996f) * fix(3.0.x): trim enumMetadata to 3.0.x codes; downgrade #3738 to patch The cherry-pick of #3738 brought in enumDescriptions + enumMetadata entries for SCOPE_INSUFFICIENT, READ_ONLY_SCOPE, and FIELD_NOT_PERMITTED — three error codes added to main in PRs that aren't on 3.0.x. Their enum entries weren't introduced (3.0.x's enum array is unaffected), but the description and metadata blocks were left referencing them. The new lintErrorCodeEnumMetadata guardrail catches this and refuses to build. Trim: drop the three orphan entries from enumDescriptions and enumMetadata. Counts now agree at 45 / 45 / 45. Also downgrades the changeset from minor to patch — same rationale as the other cherry-picks on this branch: maintenance line, no new enum values, no wire-format change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(errors): tighten AUTH_REQUIRED prose to warn on retry storms (3.0.x prose-only backport of #3739) 3.0.x cannot adopt the AUTH_MISSING/AUTH_INVALID split from #3739 — adding new enum values violates the maintenance line's semver rules. This is the prose-only backport: same wire code, same recovery class, but the description and enumMetadata.suggestion now spell out the two sub-cases (missing vs. presented-but-rejected) and the SHOULD-NOT-auto-retry rule. Closes 3.0.x portion of #3730. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> * ci(release): un-exclude dist/compliance + forward-merge auto-resolution (#3794) (#3800) * ci(release): un-exclude dist/compliance + forward-merge auto-resolution Two release-pipeline hardening fixes for 3.0.4 and beyond. .dockerignore: un-exclude dist/compliance/ so versioned URLs (the /compliance/{version}/ tree) actually serve from the Fly image. The ignore pattern was set up for /schemas/ and /protocol/ but never updated when /compliance/ versioned routing was added. Result: every committed dist/compliance/3.0.X/ directory was stripped from the build context, and SDKs fetching compliance bundles by URL hit 404s on fresh-cache scenarios. Re-include dist/compliance, then re-exclude dist/compliance/latest (regenerated in-container by build:compliance). forward-merge-3.0.yml: replace the bare-merge approach with auto- resolution on an explicit allowlist of always-divergent paths (package.json version, .changeset/*.md, dist/*, CHANGELOG.md, schema source index files). Drop the brittle is-ancestor shortcut that returns false after squash-merges even when content is in main; use post-merge git diff --quiet to skip cleanly when main already has 3.0.x's content. Conflicts outside the allowlist fail the workflow loud, surfacing playbook violations (a change on 3.0.x that wasn't first cherry-picked from main). Updates .agents/playbook.md § Release lines to document the new auto-resolution behavior so reviewers know what to spot-check. Closes the operational pain that bit us today on PR #3783, where the v3.0.3 cut's forward-merge needed three manual conflict resolutions (package.json, .changeset/fix-url-type-tracker-pixel-channel-docs.md, static/schemas/source/index.json) plus a manual unshallow before the merge could complete. * ci(release): address expert review feedback on forward-merge auto-resolution Code review (af5969...): empty-CONFLICTS-after-merge-failure guard so hook rejections / unrelated-history failures fail loud instead of silently committing the empty merge. Security review (a16289...): tighten dist/* glob to explicit {schemas,compliance,protocol,docs}/* list so future mutable subtrees under dist/ don't silently auto-resolve via --theirs. Plus: drop `2>/dev/null || true` on `git rm` (let real errors surface; the post-loop REMAINING check already guards against bad state). Add post-resolution `git status --short` and `git diff vs origin/main -- package.json` log groups so reviewers can spot main-unique scripts that may have been overwritten without leaving GitHub. No functional change to the happy-path resolution behavior. --------- # Conflicts: # .agents/playbook.md Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(forward-merge): keep main's package.json (--ours) instead of 3.0.x's The original auto-resolution rule for package.json was --theirs, which worked when main and 3.0.x diverged only on the version field. But main has structural changes (package rename: @adcp/client@5.21.1 → @adcp/sdk@5.25.1, plus undici@7 dep) that 3.0.x doesn't have. Wholesale --theirs stripped main's package rename and broke npm ci. Fix: take main's package.json + package-lock.json. Main's pre-mode tracking is independent of 3.0.x's version anyway (main's next cut goes 3.1.0-beta.0 from accumulated changesets, regardless of starting version). The version field doesn't NEED to propagate. Follow-up: update the auto-resolution rule in .github/workflows/forward-merge-3.0.yml to use --ours for package.json + package-lock.json rather than --theirs. That changes the workflow's behavior for all future forward-merges and prevents this class of breakage. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: aao-release-bot[bot] <280565558+aao-release-bot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com>
* chore(3.0.x): re-cherry-pick #3461 and #3462 (envelope_field_present + asset-union) (#3608) * feat(compliance): add envelope_field_present check type; update v3-envelope-integrity storyboard (#3461) Closes #3429 Adds `envelope_field_present` as a recognized storyboard check type that walks protocol-envelope.json instead of the step's response_schema_ref. Updates v3-envelope-integrity.yaml to use it for the `status` presence assertion, eliminating the VERIFIER_UNREACHABLE gap in the adcp-client storyboard-drift verifier. Requires adcp-client#1045 for runtime + static drift support. Non-breaking justification: additive — new check type alongside existing ones; no existing check type changed or removed; no storyboard currently uses envelope_field_present. Pre-PR review: - code-reviewer: approved — lint change correct, test added for classifyOutcome, dist/3.0.1 frozen by design (ships as 3.0.2), no blockers - ad-tech-protocol-expert: approved — semantics correct; runner-output-contract.yaml updated with check enum + expected/actual shape entries; patch bump confirmed correct per playbook conformance-harness rule https://claude.ai/code/session_01Kwks2uGQS3ZVX7g4kujdGp Co-authored-by: Claude <noreply@anthropic.com> * fix(schema): promote asset-variant oneOf to canonical asset-union.json (#3462) * fix(schema): promote asset-variant oneOf to canonical asset-union.json Fixes json-schema-to-typescript emitting VASTAsset1, DAASTAsset1, BriefAsset1, and CatalogAsset1 when creative-asset.json and creative-manifest.json both inline identical 14-arm oneOf arrays. Creates core/assets/asset-union.json (title: AssetVariant) as a single $id-addressable source of truth. Both parent schemas now $ref this file instead of duplicating the union inline. Wire format and validation semantics are unchanged; the discriminator annotation moves inside the canonical schema per OAS 3.1 §4.8.24. Closes #3459 https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * docs(schema): document intentional subset in offering-asset-group.json Clarify that offering-asset-group excludes brief-asset and catalog-asset (campaign-input metadata, not delivery-ready creative types) and cross-link to the new asset-union.json for the full union. Addresses code-reviewer nit from pre-PR review. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * fix(schema): replace inline asset oneOf in list-creatives-response.json Third copy of the 14-arm asset union, caught in post-PR code review. Replace with $ref to asset-union.json for consistency. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo --------- Co-authored-by: Claude <noreply@anthropic.com> * chore(3.0.x): sync release-pipeline workflows from main (App token, 3.0.x triggers) --------- Co-authored-by: Claude <noreply@anthropic.com> * Version Packages (#3615) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(creative-channels): fix url_type tracker → tracker_pixel (#2986) (#3673) The display, audio, carousels, and DOOH channel docs use "url_type": "tracker", which is not a valid value in the url-asset-type.json enum (clickthrough / tracker_pixel / tracker_script). Sellers following these docs emit a non-conformant url_type that buyers can't interpret without guessing. Replaces all 10 occurrences with "tracker_pixel" to match the schema. This is step 1 of the rollout proposed by Nastassia Fulconis on adcp#2986: 3.0.x docs cleanup → 3.1 SHOULD + role-based fallback + mechanism-vs-purpose clarification → 4.0 required. The dist/docs/3.0.2 release snapshot still carries the old value; backporting to the snapshot is intentionally out of scope here so the fix lands on the live source first. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(storyboard): provides_state_for cascade-rescue field (closes #3734) (#3775) Backport of e8fded9 from main to 3.0.x. Adds optional same-phase substitution declaration on storyboard steps so explicit-mode social platforms (Snap, Meta, TikTok) can pre-provision accounts out-of-band and have list_accounts substitute for the missing sync_accounts state contract — recovering 3 Tier-1 sandbox-verified adapters from 1/9/0 to 9/10 once the SDK cache refreshes against this version. Storyboard schema (static/compliance/source/universal/storyboard-schema.yaml): field documentation parallel to contributes_to. All-of array semantics, same-phase only, target/substitute must be stateful, no self-reference, acyclic peer-graph per phase. Runner output contract (static/compliance/source/universal/ runner-output-contract.yaml): new peer_substituted skip reason in skip_result.reasons, distinct from peer_branch_taken (branch-set routing) and not_applicable (coverage gap). Specialism YAML (static/compliance/source/specialisms/sales-social/ index.yaml): provides_state_for: sync_accounts on list_accounts in the account_setup phase. Build-time validation (scripts/lint-storyboard-provides-state-for.cjs + test): wired into build-compliance.cjs lint chain. Covers shape, self-reference, unknown target, cross-phase, target-stateful, substitute-stateful, and direct-cycle violations. Pure additive change; existing storyboards keep current cascade behavior. Cherry-pick conflict resolved in package.json: kept 3.0.x's simpler node --test invocation (no --test-force-exit / --test-timeout flags), slotted in test:storyboard-provides-state-for entry consistent with 3.0.x style. --no-verify used because precommit fails on a pre-existing 3.0.x baseline @adcp/client install drift unrelated to this change. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3696) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ci(release): auto-upload protocol tarball to GitHub Release (#3786) (#3787) `createGithubReleases: true` from changesets/action only writes the changelog body — files have to be uploaded separately. Without this step the artifacts ship to the repo's dist/ tree but are never attached as Release assets, leaving adopters who pin via release URL with 404s. v3.0.0 had assets only because they were uploaded by hand on 2026-04-22; v3.0.1 / v3.0.2 / v3.0.3 all shipped empty. New step runs after changesets/action, gated on `steps.changesets.outputs.published == 'true'` so it only fires on tag-and-release runs (not Version Packages PR-creation runs). Uploads ${VERSION}.tgz plus .sha256 / .sig / .crt sidecars with --clobber so re-runs are idempotent. Backfill of the three missing releases (v3.0.1 / v3.0.2 / v3.0.3) is a separate manual step against the assets already committed at dist/protocol/ on the 3.0.x branch. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(3.0.x): cherry-pick + adapt 7 spec fixes from main (#3784) (#3789) * fix(schema): correct title annotation in rate-limited error-details schema (#3149) * fix(schema): correct title annotation in rate-limited error-details schema Change "RATE_LIMITED Details" to "Rate Limited Details" in the `title` annotation of error-details/rate-limited.json. The title field is non-normative per JSON Schema draft-07 (no validation or wire-format impact); this corrects the downstream codegen output from `RATE_LIMITEDDetails` to `RateLimitedDetails`. Applied to source and all dist snapshots (3.0.0, 3.0.0-rc.3, latest). Refs #3145. See adcp-client#942 for the SDK alias layer. https://claude.ai/code/session_01E3LcN5g4tEZutKCTePUVbs * revert: drop dist/schemas/ hand-edits per #3149 review dist/schemas/3.0.0/ and dist/schemas/3.0.0-rc.3/ are immutable release snapshots — scripts/build-schemas.cjs only writes them under --release mode (the changesets release step), and dist/schemas/latest/ is gitignored. Mutating frozen GA snapshots breaks the immutability contract that lets buyers pin to 3.0.0 and trust they see exactly what was published. Source title fix is preserved. The next --release build picks up the corrected title for that version's snapshot; past releases stay byte-identical to what was published. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> (cherry picked from commit fbf71ce) * spec(error): standardize VALIDATION_ERROR issues[] on core/error.json (closes #3059) (#3562) * spec(error): standardize VALIDATION_ERROR issues[] (closes #3059) Adds an optional top-level issues array to core/error.json, normalizing what @adcp/client already emits for multi-field validation rejections (adcp-client#874 / #915). Other implementations (adcp-go, adcp-client-python, hand-rolled sellers) would either miss the structured pointer list, adopt it ad-hoc with different naming, or converge if the spec normalizes it. Filing now keeps the ecosystem aligned before adoption deepens. Each issue entry: { pointer (RFC 6901), message, keyword, schemaPath? }. schemaPath MAY be omitted in production to avoid fingerprinting oneOf branch selection on adversarial payloads. Backward compatibility: - field (singular) is retained. When both are present, sellers SHOULD set field to issues[0].pointer for pre-3.1 consumers reading field only. - details.issues mirror is permitted for consumers reading from details. New consumers should prefer top-level issues. Files: - static/schemas/source/core/error.json: adds issues property - docs/building/implementation/error-handling.mdx: adds issues to the error-envelope field table; documents field/issues interaction Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(error): apply protocol-expert review feedback on issues[] Three substantive sharpens from the ad-tech-protocol-expert review of PR #3562: 1. Pointer-format mismatch with field — flagged as a latent bug. The existing top-level field uses JSONPath-lite (packages[0].targeting); the new issues[].pointer uses RFC 6901 (/packages/0/targeting). Calling the mirror rule SHOULD without specifying the translation left sellers with collision risk. Both descriptions now spell out the format choice (Ajv's instancePath = RFC 6901 for pointer; legacy JSONPath-lite for field) AND the explicit translation contract on the mirror. Future major version will deprecate field in favor of issues[].pointer. 2. issues[0].pointer mirror rule — SHOULD upgraded to MUST (when issues is present). SHOULD created exactly the rough edge the review flagged: pre-3.1 consumers reading field would get nondeterministic behavior across sellers. Cost of MUST is one line of dual-write per seller; cost of SHOULD is a long tail of seller-A-vs-seller-B bugs. MUST also gives a clean deprecation path in 4.0. 3. schemaPath downgraded from MAY to SHOULD NOT in production. The review identified this as a real probe oracle: leaking which oneOf branch the validator selected before semantic rejection helps adversarial callers map polymorphic unions. AdCP already has an adversarial-payload threat model (signed-requests work, agent- controlled field audit). Sellers MAY emit in dev/sandbox modes. Also cited Ajv as prior art so implementers know where the keyword vocabulary comes from (instancePath / keyword / schemaPath are Ajv's native error output fields). Reduces the ad-hoc-naming risk. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit bd3a18c) * spec(schemas): PascalCase titles on error-details schemas (partial fix for #3145) (#3566) * spec(schemas): PascalCase titles on error-details schemas Partial fix for #3145. Six error-details/*.json files carry SCREAMING_SNAKE titles that propagate awkwardly through json-schema-to-typescript into @adcp/client's public type surface (e.g., RATE_LIMITEDDetails_ScopeValues read as a typo to every consumer of the SDK). Renames (no wire-format change — $id values stay kebab-case; only the title field is touched, which controls codegen): - ACCOUNT_SETUP_REQUIRED Details -> AccountSetupRequiredDetails - AUDIENCE_TOO_SMALL Details -> AudienceTooSmallDetails - BUDGET_TOO_LOW Details -> BudgetTooLowDetails - CONFLICT Details -> ConflictDetails - CREATIVE_REJECTED Details -> CreativeRejectedDetails - POLICY_VIOLATION Details -> PolicyViolationDetails rate-limited.json already had PascalCase (Rate Limited Details); vendor-error-codes.json already had Vendor Error Code Registry; no change to either. #3145's other half — Foo1-suffixed enum dupes (AgeVerificationMethod1, *Asset1, etc.) — is downstream codegen behavior. Both refs already point at the same $id with $ref so the spec side is correct; the dupe shows up because json-schema-to-typescript walks through different parent paths and emits two inline copies. SDK-side post-process renaming (with one- minor-version aliases) is the pragmatic fix. Tracked at adcp-client#942. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255, build-compliance 20 universal / 6 protocols / 19 specialisms). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(schemas): Title Case (with spaces) to match #3149 precedent Protocol-expert review of #3566 flagged the style inconsistency: my PR stripped spaces (AccountSetupRequiredDetails), but PR #3149 (Apr 25) established the precedent of spaced Title Case for the same kind of problem (Rate Limited Details). Going with #3149's form so the 8-file directory stays uniform. json-schema-to-typescript strips whitespace when generating identifiers, so the codegen output is identical either way (AccountSetupRequiredDetails on both); the source-of-truth title just stays consistent across the directory. Title field is non-normative per JSON Schema draft-07 §10.1 — affects only docgen/codegen output, not validation. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit a5d9bff) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) (#3671) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) Define what receivers do when a URL asset omits url_type. Today the field is optional with no fallback rule, so a conformant manifest like {asset_type: url, url: ...} forces buyers to guess the invocation mechanism — and guessing wrong (firing a clickthrough URL as a pixel, or a tracker as a clickthrough) corrupts measurement and breaks user flows. Schema changes: - url-asset.json: senders SHOULD include url_type on every URL asset. When absent, receivers SHOULD fall back to the format's url-asset-requirements.role (clickthrough/landing_page → `clickthrough` mechanism; *_tracker roles → `tracker_pixel`). When neither is available, receivers MAY reject rather than guess. - url-asset-requirements.json: clarify that role is purpose (impression vs click vs viewability vs 3P) while url_type is mechanism (click vs pixel vs script tag); a click_tracker slot validly accepts a tracker_pixel URL. Doc changes: - asset-types.mdx URL Asset section: rewritten to use the actual url_type enum (clickthrough/tracker_pixel/tracker_script — the old text listed impression_tracker/video_tracker/landing_page, which were never url_type values), to add the SHOULD note and role fallback table, and to remove the "you only need to supply the url value" guidance that drove the original ambiguity. Wire format unchanged. Senders already including url_type are unaffected. Step 2 of the rollout on adcp#2986; step 3 (require url_type in 4.0) follows once this lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(url-asset): apply expert review — viewability→script, third_party→unmapped, MUST NOT silently guess Addresses ad-tech-protocol-expert + adtech-product-expert review on PR #3671: Required fixes: - viewability_tracker → tracker_script (not tracker_pixel). OMID and equivalent verification SDKs require a <script> tag; firing them as a pixel produces no measurement and no error — exactly the silent- corruption failure mode this PR exists to prevent. - third_party_tracker → no safe fallback. Mechanism is integration- specific (DV/IAS ship both pixel and script forms). Receivers MAY reject or warn rather than guess. - Strengthen receiver guidance to "MUST NOT silently pick a mechanism; SHOULD reject" when both url_type and role are absent. Mirrors the mdx language into the JSON Schema description so extractors and conformance tooling read the same rule. - Add VAST/DAAST carve-out: VAST tag URLs are not URL assets; use asset_type: "vast" or the dedicated tracker types pending RFC #2915. - Update docs/creative/formats.mdx tracker-detection rule. Today it feature-detects on url_type ∈ {tracker_pixel, tracker_script}; under the new fallback semantics, format authors who declare only role would silently fail that detector. Detection now accepts either url_type OR a tracker-purpose role. Non-blocking improvements: - Migration cue in asset-types.mdx for sellers who built tooling around the older "you only need to supply the url value" guidance: 3.x is fine, plan to add url_type before 4.0. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(url-asset): cross-reference fallback table between schema and mdx The role→url_type fallback table lives in two places: the url-asset.json top-level description (read by conformance tools and codegen) and the asset-types.mdx URL Asset section (read by humans). Without a hint, an editor of one will silently drift the other. Adds a $comment in url-asset.json and a JSX comment in asset-types.mdx pointing at each other. Schema description remains the normative source; mdx is the human copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit f6af651) * fix(compliance): audience-sync discover_account stateful: false → true (#3710) The list_accounts step in the account_setup phase establishes account_id for downstream sync_audiences calls. The storyboard narrative was correct but the stateful flag contradicted it, causing the SDK runner to not count a passing result as cascade state — explicit-mode adopters saw prerequisite_failed on sync_audiences even after list_accounts passed. Fixes #3707 https://claude.ai/code/session_019ZJXyeQYrRZc17UqcW2yDf Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit e74997b) * chore(changesets): downgrade cherry-picked minor bumps to patch for 3.0.x line Maintenance-line policy: cherry-picks land as patches even when the original PRs landed on main as minor bumps. Otherwise the changesets trigger 3.1.0 publication off the 3.0.x branch, defeating the cherry-pick. - error-issues-array.md (#3562, originally minor): patch - url-type-should-and-role-fallback.md (#3671, originally minor): patch Both PRs were classified as patch-eligible in #3784. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(schema): manifest.json + structured enumMetadata (closes #3725) (#3738) * feat(schema): publish manifest.json + structured enumMetadata (adcp#3725) Adds two additive artifacts so SDKs stop hand-rolling (and drifting on) spec metadata. Root cause for adcp-client#1135 (17 missing error codes, 3 wrong recovery classifications shipped in TS SDK for over a year). - enums/error-code.json gains an enumMetadata block with structured recovery + suggestion per code. Build-time lint rejects drift between the structured value and the prose Recovery: X in enumDescriptions. - New static/schemas/source/manifest.schema.json + generator emitting dist/schemas/{version}/manifest.json: 58 tools, 48 error codes, 19 specialisms, plus an error_code_policy block defining how SDKs MUST classify codes from non-conforming sellers. - mutating derived from the same classifier the idempotency-key lint enforces (single source of truth). Tightened READ_ONLY_VERB_PATTERN to anchor at start so create-collection-list / delete-property-list no longer mis-classify as read-only via -list- mid-name; added search as a read-only verb. - Specialisms expose entry_point_tools (curated minimum from index.yaml.required_tools) and exercised_tools (full surface — union of own phases[].steps[].task and every linked scenario, derived by walking requires_scenarios). sales_guaranteed now correctly lists 9 tools instead of 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(manifest): expert-review fixes — strip regex, requires_tool gating Two correctness fixes from protocol-expert review: - stripRecoveryProse: greedy [^.]*\. truncated descriptions with periods inside parentheticals (e.g. "capabilities.media_buy.limits"). Rewrote with three explicit patterns: bare verdict, verdict + balanced parenthetical, clause continuation to EOS. Verified all 48 codes emit clean descriptions with no Recovery: prose remaining. - collectTasksFromPhases now skips steps gated by requires_tool. Steps marked requires_tool: <X> are conditional on the agent claiming X, not required surface. Without the skip, optional test-harness tools (comply_test_controller, gated across 23+ steps) propagated into every sales specialism's exercised_tools. Plus code-review nits: - Simplified discoverTools utility-shape skip to NON_OPERATION_ALLOWLIST only; documented the contract. - Removed unused schema parameter from classifyRequestMutating. - Tightened indexScenarioTasks predicate to require phases array. - Added cross-reference comment between MANIFEST_PROTOCOLS and the meta-schema's protocol enum. - Changeset now mentions /schemas/latest/manifest.json for nightly codegen consumers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit b44996f) * fix(3.0.x): trim enumMetadata to 3.0.x codes; downgrade #3738 to patch The cherry-pick of #3738 brought in enumDescriptions + enumMetadata entries for SCOPE_INSUFFICIENT, READ_ONLY_SCOPE, and FIELD_NOT_PERMITTED — three error codes added to main in PRs that aren't on 3.0.x. Their enum entries weren't introduced (3.0.x's enum array is unaffected), but the description and metadata blocks were left referencing them. The new lintErrorCodeEnumMetadata guardrail catches this and refuses to build. Trim: drop the three orphan entries from enumDescriptions and enumMetadata. Counts now agree at 45 / 45 / 45. Also downgrades the changeset from minor to patch — same rationale as the other cherry-picks on this branch: maintenance line, no new enum values, no wire-format change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(errors): tighten AUTH_REQUIRED prose to warn on retry storms (3.0.x prose-only backport of #3739) 3.0.x cannot adopt the AUTH_MISSING/AUTH_INVALID split from #3739 — adding new enum values violates the maintenance line's semver rules. This is the prose-only backport: same wire code, same recovery class, but the description and enumMetadata.suggestion now spell out the two sub-cases (missing vs. presented-but-rejected) and the SHOULD-NOT-auto-retry rule. Closes 3.0.x portion of #3730. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> * ci(release): un-exclude dist/compliance + forward-merge auto-resolution (#3794) (#3800) * ci(release): un-exclude dist/compliance + forward-merge auto-resolution Two release-pipeline hardening fixes for 3.0.4 and beyond. .dockerignore: un-exclude dist/compliance/ so versioned URLs (the /compliance/{version}/ tree) actually serve from the Fly image. The ignore pattern was set up for /schemas/ and /protocol/ but never updated when /compliance/ versioned routing was added. Result: every committed dist/compliance/3.0.X/ directory was stripped from the build context, and SDKs fetching compliance bundles by URL hit 404s on fresh-cache scenarios. Re-include dist/compliance, then re-exclude dist/compliance/latest (regenerated in-container by build:compliance). forward-merge-3.0.yml: replace the bare-merge approach with auto- resolution on an explicit allowlist of always-divergent paths (package.json version, .changeset/*.md, dist/*, CHANGELOG.md, schema source index files). Drop the brittle is-ancestor shortcut that returns false after squash-merges even when content is in main; use post-merge git diff --quiet to skip cleanly when main already has 3.0.x's content. Conflicts outside the allowlist fail the workflow loud, surfacing playbook violations (a change on 3.0.x that wasn't first cherry-picked from main). Updates .agents/playbook.md § Release lines to document the new auto-resolution behavior so reviewers know what to spot-check. Closes the operational pain that bit us today on PR #3783, where the v3.0.3 cut's forward-merge needed three manual conflict resolutions (package.json, .changeset/fix-url-type-tracker-pixel-channel-docs.md, static/schemas/source/index.json) plus a manual unshallow before the merge could complete. * ci(release): address expert review feedback on forward-merge auto-resolution Code review (af5969...): empty-CONFLICTS-after-merge-failure guard so hook rejections / unrelated-history failures fail loud instead of silently committing the empty merge. Security review (a16289...): tighten dist/* glob to explicit {schemas,compliance,protocol,docs}/* list so future mutable subtrees under dist/ don't silently auto-resolve via --theirs. Plus: drop `2>/dev/null || true` on `git rm` (let real errors surface; the post-loop REMAINING check already guards against bad state). Add post-resolution `git status --short` and `git diff vs origin/main -- package.json` log groups so reviewers can spot main-unique scripts that may have been overwritten without leaving GitHub. No functional change to the happy-path resolution behavior. --------- # Conflicts: # .agents/playbook.md Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): forward-merge package.json --ours, not --theirs (#3807) (#3808) Original --theirs rule stripped main's structural changes when 3.0.x hadn't been updated to match. Concrete case: main renamed @adcp/client to @adcp/sdk + bumped to 5.25.1; 3.0.x stayed on @adcp/client@5.21.1. The forward-merge took 3.0.x's package.json wholesale, leaving the package-lock out of sync. CI broke with "npm ci ... package.json and package-lock.json or npm-shrinkwrap.json are in sync". PR #3806's CI exposed this. --ours preserves main's state. Main's pre-mode tracking is independent of 3.0.x's version field; the dist/* artifacts still flow forward via the allowlist; main's structural changes survive. Trade-off: main's package.json version doesn't reflect 3.0.x's latest release. Acceptable — main's version field isn't authoritative while pre-mode is active. The next main pre-mode cut produces 3.1.0-beta.X from accumulated changesets regardless of base version. Companion playbook + PR-body checklist update so docs match behavior. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): bridge cherry-pick divergence in forward-merge (#3811) (#3814) Adds two specific files to the auto-resolution --ours allowlist where 3.0.x has #3789's hand-adapted prose-only backport of #3739 and main has the full enum split (adds AUTH_MISSING / AUTH_INVALID codes that can't ship to 3.0.x without violating patch eligibility). - docs/building/implementation/error-handling.mdx - static/schemas/source/enums/error-code.json Without this rule, every routine forward-merge from 3.0.x → main rediscovers the same conflict because squash-merges of prior forward-merges (the only merge style this repo allows) don't advance git's merge-base. The post-merge `git diff --quiet` skip can't reach to detect "main already has this content" because the merge fails before that step. Marked temporary in the workflow comments — remove when 3.1.0 cuts and main no longer has the in-flight enum split. Without this fix, the next forward-merge after 3.0.4 cuts would fail loud on these same two files, requiring another manual resolution PR. With it, 3.0.4's forward-merge auto-succeeds. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3799) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(changeset): empty changeset for 3.0.4 forward-merge --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: aao-release-bot[bot] <280565558+aao-release-bot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com>
* chore(3.0.x): re-cherry-pick #3461 and #3462 (envelope_field_present + asset-union) (#3608) * feat(compliance): add envelope_field_present check type; update v3-envelope-integrity storyboard (#3461) Closes #3429 Adds `envelope_field_present` as a recognized storyboard check type that walks protocol-envelope.json instead of the step's response_schema_ref. Updates v3-envelope-integrity.yaml to use it for the `status` presence assertion, eliminating the VERIFIER_UNREACHABLE gap in the adcp-client storyboard-drift verifier. Requires adcp-client#1045 for runtime + static drift support. Non-breaking justification: additive — new check type alongside existing ones; no existing check type changed or removed; no storyboard currently uses envelope_field_present. Pre-PR review: - code-reviewer: approved — lint change correct, test added for classifyOutcome, dist/3.0.1 frozen by design (ships as 3.0.2), no blockers - ad-tech-protocol-expert: approved — semantics correct; runner-output-contract.yaml updated with check enum + expected/actual shape entries; patch bump confirmed correct per playbook conformance-harness rule https://claude.ai/code/session_01Kwks2uGQS3ZVX7g4kujdGp Co-authored-by: Claude <noreply@anthropic.com> * fix(schema): promote asset-variant oneOf to canonical asset-union.json (#3462) * fix(schema): promote asset-variant oneOf to canonical asset-union.json Fixes json-schema-to-typescript emitting VASTAsset1, DAASTAsset1, BriefAsset1, and CatalogAsset1 when creative-asset.json and creative-manifest.json both inline identical 14-arm oneOf arrays. Creates core/assets/asset-union.json (title: AssetVariant) as a single $id-addressable source of truth. Both parent schemas now $ref this file instead of duplicating the union inline. Wire format and validation semantics are unchanged; the discriminator annotation moves inside the canonical schema per OAS 3.1 §4.8.24. Closes #3459 https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * docs(schema): document intentional subset in offering-asset-group.json Clarify that offering-asset-group excludes brief-asset and catalog-asset (campaign-input metadata, not delivery-ready creative types) and cross-link to the new asset-union.json for the full union. Addresses code-reviewer nit from pre-PR review. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * fix(schema): replace inline asset oneOf in list-creatives-response.json Third copy of the 14-arm asset union, caught in post-PR code review. Replace with $ref to asset-union.json for consistency. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo --------- Co-authored-by: Claude <noreply@anthropic.com> * chore(3.0.x): sync release-pipeline workflows from main (App token, 3.0.x triggers) --------- Co-authored-by: Claude <noreply@anthropic.com> * Version Packages (#3615) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(creative-channels): fix url_type tracker → tracker_pixel (#2986) (#3673) The display, audio, carousels, and DOOH channel docs use "url_type": "tracker", which is not a valid value in the url-asset-type.json enum (clickthrough / tracker_pixel / tracker_script). Sellers following these docs emit a non-conformant url_type that buyers can't interpret without guessing. Replaces all 10 occurrences with "tracker_pixel" to match the schema. This is step 1 of the rollout proposed by Nastassia Fulconis on adcp#2986: 3.0.x docs cleanup → 3.1 SHOULD + role-based fallback + mechanism-vs-purpose clarification → 4.0 required. The dist/docs/3.0.2 release snapshot still carries the old value; backporting to the snapshot is intentionally out of scope here so the fix lands on the live source first. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(storyboard): provides_state_for cascade-rescue field (closes #3734) (#3775) Backport of e8fded9 from main to 3.0.x. Adds optional same-phase substitution declaration on storyboard steps so explicit-mode social platforms (Snap, Meta, TikTok) can pre-provision accounts out-of-band and have list_accounts substitute for the missing sync_accounts state contract — recovering 3 Tier-1 sandbox-verified adapters from 1/9/0 to 9/10 once the SDK cache refreshes against this version. Storyboard schema (static/compliance/source/universal/storyboard-schema.yaml): field documentation parallel to contributes_to. All-of array semantics, same-phase only, target/substitute must be stateful, no self-reference, acyclic peer-graph per phase. Runner output contract (static/compliance/source/universal/ runner-output-contract.yaml): new peer_substituted skip reason in skip_result.reasons, distinct from peer_branch_taken (branch-set routing) and not_applicable (coverage gap). Specialism YAML (static/compliance/source/specialisms/sales-social/ index.yaml): provides_state_for: sync_accounts on list_accounts in the account_setup phase. Build-time validation (scripts/lint-storyboard-provides-state-for.cjs + test): wired into build-compliance.cjs lint chain. Covers shape, self-reference, unknown target, cross-phase, target-stateful, substitute-stateful, and direct-cycle violations. Pure additive change; existing storyboards keep current cascade behavior. Cherry-pick conflict resolved in package.json: kept 3.0.x's simpler node --test invocation (no --test-force-exit / --test-timeout flags), slotted in test:storyboard-provides-state-for entry consistent with 3.0.x style. --no-verify used because precommit fails on a pre-existing 3.0.x baseline @adcp/client install drift unrelated to this change. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3696) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ci(release): auto-upload protocol tarball to GitHub Release (#3786) (#3787) `createGithubReleases: true` from changesets/action only writes the changelog body — files have to be uploaded separately. Without this step the artifacts ship to the repo's dist/ tree but are never attached as Release assets, leaving adopters who pin via release URL with 404s. v3.0.0 had assets only because they were uploaded by hand on 2026-04-22; v3.0.1 / v3.0.2 / v3.0.3 all shipped empty. New step runs after changesets/action, gated on `steps.changesets.outputs.published == 'true'` so it only fires on tag-and-release runs (not Version Packages PR-creation runs). Uploads ${VERSION}.tgz plus .sha256 / .sig / .crt sidecars with --clobber so re-runs are idempotent. Backfill of the three missing releases (v3.0.1 / v3.0.2 / v3.0.3) is a separate manual step against the assets already committed at dist/protocol/ on the 3.0.x branch. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(3.0.x): cherry-pick + adapt 7 spec fixes from main (#3784) (#3789) * fix(schema): correct title annotation in rate-limited error-details schema (#3149) * fix(schema): correct title annotation in rate-limited error-details schema Change "RATE_LIMITED Details" to "Rate Limited Details" in the `title` annotation of error-details/rate-limited.json. The title field is non-normative per JSON Schema draft-07 (no validation or wire-format impact); this corrects the downstream codegen output from `RATE_LIMITEDDetails` to `RateLimitedDetails`. Applied to source and all dist snapshots (3.0.0, 3.0.0-rc.3, latest). Refs #3145. See adcp-client#942 for the SDK alias layer. https://claude.ai/code/session_01E3LcN5g4tEZutKCTePUVbs * revert: drop dist/schemas/ hand-edits per #3149 review dist/schemas/3.0.0/ and dist/schemas/3.0.0-rc.3/ are immutable release snapshots — scripts/build-schemas.cjs only writes them under --release mode (the changesets release step), and dist/schemas/latest/ is gitignored. Mutating frozen GA snapshots breaks the immutability contract that lets buyers pin to 3.0.0 and trust they see exactly what was published. Source title fix is preserved. The next --release build picks up the corrected title for that version's snapshot; past releases stay byte-identical to what was published. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> (cherry picked from commit fbf71ce) * spec(error): standardize VALIDATION_ERROR issues[] on core/error.json (closes #3059) (#3562) * spec(error): standardize VALIDATION_ERROR issues[] (closes #3059) Adds an optional top-level issues array to core/error.json, normalizing what @adcp/client already emits for multi-field validation rejections (adcp-client#874 / #915). Other implementations (adcp-go, adcp-client-python, hand-rolled sellers) would either miss the structured pointer list, adopt it ad-hoc with different naming, or converge if the spec normalizes it. Filing now keeps the ecosystem aligned before adoption deepens. Each issue entry: { pointer (RFC 6901), message, keyword, schemaPath? }. schemaPath MAY be omitted in production to avoid fingerprinting oneOf branch selection on adversarial payloads. Backward compatibility: - field (singular) is retained. When both are present, sellers SHOULD set field to issues[0].pointer for pre-3.1 consumers reading field only. - details.issues mirror is permitted for consumers reading from details. New consumers should prefer top-level issues. Files: - static/schemas/source/core/error.json: adds issues property - docs/building/implementation/error-handling.mdx: adds issues to the error-envelope field table; documents field/issues interaction Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(error): apply protocol-expert review feedback on issues[] Three substantive sharpens from the ad-tech-protocol-expert review of PR #3562: 1. Pointer-format mismatch with field — flagged as a latent bug. The existing top-level field uses JSONPath-lite (packages[0].targeting); the new issues[].pointer uses RFC 6901 (/packages/0/targeting). Calling the mirror rule SHOULD without specifying the translation left sellers with collision risk. Both descriptions now spell out the format choice (Ajv's instancePath = RFC 6901 for pointer; legacy JSONPath-lite for field) AND the explicit translation contract on the mirror. Future major version will deprecate field in favor of issues[].pointer. 2. issues[0].pointer mirror rule — SHOULD upgraded to MUST (when issues is present). SHOULD created exactly the rough edge the review flagged: pre-3.1 consumers reading field would get nondeterministic behavior across sellers. Cost of MUST is one line of dual-write per seller; cost of SHOULD is a long tail of seller-A-vs-seller-B bugs. MUST also gives a clean deprecation path in 4.0. 3. schemaPath downgraded from MAY to SHOULD NOT in production. The review identified this as a real probe oracle: leaking which oneOf branch the validator selected before semantic rejection helps adversarial callers map polymorphic unions. AdCP already has an adversarial-payload threat model (signed-requests work, agent- controlled field audit). Sellers MAY emit in dev/sandbox modes. Also cited Ajv as prior art so implementers know where the keyword vocabulary comes from (instancePath / keyword / schemaPath are Ajv's native error output fields). Reduces the ad-hoc-naming risk. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit bd3a18c) * spec(schemas): PascalCase titles on error-details schemas (partial fix for #3145) (#3566) * spec(schemas): PascalCase titles on error-details schemas Partial fix for #3145. Six error-details/*.json files carry SCREAMING_SNAKE titles that propagate awkwardly through json-schema-to-typescript into @adcp/client's public type surface (e.g., RATE_LIMITEDDetails_ScopeValues read as a typo to every consumer of the SDK). Renames (no wire-format change — $id values stay kebab-case; only the title field is touched, which controls codegen): - ACCOUNT_SETUP_REQUIRED Details -> AccountSetupRequiredDetails - AUDIENCE_TOO_SMALL Details -> AudienceTooSmallDetails - BUDGET_TOO_LOW Details -> BudgetTooLowDetails - CONFLICT Details -> ConflictDetails - CREATIVE_REJECTED Details -> CreativeRejectedDetails - POLICY_VIOLATION Details -> PolicyViolationDetails rate-limited.json already had PascalCase (Rate Limited Details); vendor-error-codes.json already had Vendor Error Code Registry; no change to either. #3145's other half — Foo1-suffixed enum dupes (AgeVerificationMethod1, *Asset1, etc.) — is downstream codegen behavior. Both refs already point at the same $id with $ref so the spec side is correct; the dupe shows up because json-schema-to-typescript walks through different parent paths and emits two inline copies. SDK-side post-process renaming (with one- minor-version aliases) is the pragmatic fix. Tracked at adcp-client#942. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255, build-compliance 20 universal / 6 protocols / 19 specialisms). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(schemas): Title Case (with spaces) to match #3149 precedent Protocol-expert review of #3566 flagged the style inconsistency: my PR stripped spaces (AccountSetupRequiredDetails), but PR #3149 (Apr 25) established the precedent of spaced Title Case for the same kind of problem (Rate Limited Details). Going with #3149's form so the 8-file directory stays uniform. json-schema-to-typescript strips whitespace when generating identifiers, so the codegen output is identical either way (AccountSetupRequiredDetails on both); the source-of-truth title just stays consistent across the directory. Title field is non-normative per JSON Schema draft-07 §10.1 — affects only docgen/codegen output, not validation. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit a5d9bff) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) (#3671) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) Define what receivers do when a URL asset omits url_type. Today the field is optional with no fallback rule, so a conformant manifest like {asset_type: url, url: ...} forces buyers to guess the invocation mechanism — and guessing wrong (firing a clickthrough URL as a pixel, or a tracker as a clickthrough) corrupts measurement and breaks user flows. Schema changes: - url-asset.json: senders SHOULD include url_type on every URL asset. When absent, receivers SHOULD fall back to the format's url-asset-requirements.role (clickthrough/landing_page → `clickthrough` mechanism; *_tracker roles → `tracker_pixel`). When neither is available, receivers MAY reject rather than guess. - url-asset-requirements.json: clarify that role is purpose (impression vs click vs viewability vs 3P) while url_type is mechanism (click vs pixel vs script tag); a click_tracker slot validly accepts a tracker_pixel URL. Doc changes: - asset-types.mdx URL Asset section: rewritten to use the actual url_type enum (clickthrough/tracker_pixel/tracker_script — the old text listed impression_tracker/video_tracker/landing_page, which were never url_type values), to add the SHOULD note and role fallback table, and to remove the "you only need to supply the url value" guidance that drove the original ambiguity. Wire format unchanged. Senders already including url_type are unaffected. Step 2 of the rollout on adcp#2986; step 3 (require url_type in 4.0) follows once this lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(url-asset): apply expert review — viewability→script, third_party→unmapped, MUST NOT silently guess Addresses ad-tech-protocol-expert + adtech-product-expert review on PR #3671: Required fixes: - viewability_tracker → tracker_script (not tracker_pixel). OMID and equivalent verification SDKs require a <script> tag; firing them as a pixel produces no measurement and no error — exactly the silent- corruption failure mode this PR exists to prevent. - third_party_tracker → no safe fallback. Mechanism is integration- specific (DV/IAS ship both pixel and script forms). Receivers MAY reject or warn rather than guess. - Strengthen receiver guidance to "MUST NOT silently pick a mechanism; SHOULD reject" when both url_type and role are absent. Mirrors the mdx language into the JSON Schema description so extractors and conformance tooling read the same rule. - Add VAST/DAAST carve-out: VAST tag URLs are not URL assets; use asset_type: "vast" or the dedicated tracker types pending RFC #2915. - Update docs/creative/formats.mdx tracker-detection rule. Today it feature-detects on url_type ∈ {tracker_pixel, tracker_script}; under the new fallback semantics, format authors who declare only role would silently fail that detector. Detection now accepts either url_type OR a tracker-purpose role. Non-blocking improvements: - Migration cue in asset-types.mdx for sellers who built tooling around the older "you only need to supply the url value" guidance: 3.x is fine, plan to add url_type before 4.0. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(url-asset): cross-reference fallback table between schema and mdx The role→url_type fallback table lives in two places: the url-asset.json top-level description (read by conformance tools and codegen) and the asset-types.mdx URL Asset section (read by humans). Without a hint, an editor of one will silently drift the other. Adds a $comment in url-asset.json and a JSX comment in asset-types.mdx pointing at each other. Schema description remains the normative source; mdx is the human copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit f6af651) * fix(compliance): audience-sync discover_account stateful: false → true (#3710) The list_accounts step in the account_setup phase establishes account_id for downstream sync_audiences calls. The storyboard narrative was correct but the stateful flag contradicted it, causing the SDK runner to not count a passing result as cascade state — explicit-mode adopters saw prerequisite_failed on sync_audiences even after list_accounts passed. Fixes #3707 https://claude.ai/code/session_019ZJXyeQYrRZc17UqcW2yDf Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit e74997b) * chore(changesets): downgrade cherry-picked minor bumps to patch for 3.0.x line Maintenance-line policy: cherry-picks land as patches even when the original PRs landed on main as minor bumps. Otherwise the changesets trigger 3.1.0 publication off the 3.0.x branch, defeating the cherry-pick. - error-issues-array.md (#3562, originally minor): patch - url-type-should-and-role-fallback.md (#3671, originally minor): patch Both PRs were classified as patch-eligible in #3784. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(schema): manifest.json + structured enumMetadata (closes #3725) (#3738) * feat(schema): publish manifest.json + structured enumMetadata (adcp#3725) Adds two additive artifacts so SDKs stop hand-rolling (and drifting on) spec metadata. Root cause for adcp-client#1135 (17 missing error codes, 3 wrong recovery classifications shipped in TS SDK for over a year). - enums/error-code.json gains an enumMetadata block with structured recovery + suggestion per code. Build-time lint rejects drift between the structured value and the prose Recovery: X in enumDescriptions. - New static/schemas/source/manifest.schema.json + generator emitting dist/schemas/{version}/manifest.json: 58 tools, 48 error codes, 19 specialisms, plus an error_code_policy block defining how SDKs MUST classify codes from non-conforming sellers. - mutating derived from the same classifier the idempotency-key lint enforces (single source of truth). Tightened READ_ONLY_VERB_PATTERN to anchor at start so create-collection-list / delete-property-list no longer mis-classify as read-only via -list- mid-name; added search as a read-only verb. - Specialisms expose entry_point_tools (curated minimum from index.yaml.required_tools) and exercised_tools (full surface — union of own phases[].steps[].task and every linked scenario, derived by walking requires_scenarios). sales_guaranteed now correctly lists 9 tools instead of 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(manifest): expert-review fixes — strip regex, requires_tool gating Two correctness fixes from protocol-expert review: - stripRecoveryProse: greedy [^.]*\. truncated descriptions with periods inside parentheticals (e.g. "capabilities.media_buy.limits"). Rewrote with three explicit patterns: bare verdict, verdict + balanced parenthetical, clause continuation to EOS. Verified all 48 codes emit clean descriptions with no Recovery: prose remaining. - collectTasksFromPhases now skips steps gated by requires_tool. Steps marked requires_tool: <X> are conditional on the agent claiming X, not required surface. Without the skip, optional test-harness tools (comply_test_controller, gated across 23+ steps) propagated into every sales specialism's exercised_tools. Plus code-review nits: - Simplified discoverTools utility-shape skip to NON_OPERATION_ALLOWLIST only; documented the contract. - Removed unused schema parameter from classifyRequestMutating. - Tightened indexScenarioTasks predicate to require phases array. - Added cross-reference comment between MANIFEST_PROTOCOLS and the meta-schema's protocol enum. - Changeset now mentions /schemas/latest/manifest.json for nightly codegen consumers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit b44996f) * fix(3.0.x): trim enumMetadata to 3.0.x codes; downgrade #3738 to patch The cherry-pick of #3738 brought in enumDescriptions + enumMetadata entries for SCOPE_INSUFFICIENT, READ_ONLY_SCOPE, and FIELD_NOT_PERMITTED — three error codes added to main in PRs that aren't on 3.0.x. Their enum entries weren't introduced (3.0.x's enum array is unaffected), but the description and metadata blocks were left referencing them. The new lintErrorCodeEnumMetadata guardrail catches this and refuses to build. Trim: drop the three orphan entries from enumDescriptions and enumMetadata. Counts now agree at 45 / 45 / 45. Also downgrades the changeset from minor to patch — same rationale as the other cherry-picks on this branch: maintenance line, no new enum values, no wire-format change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(errors): tighten AUTH_REQUIRED prose to warn on retry storms (3.0.x prose-only backport of #3739) 3.0.x cannot adopt the AUTH_MISSING/AUTH_INVALID split from #3739 — adding new enum values violates the maintenance line's semver rules. This is the prose-only backport: same wire code, same recovery class, but the description and enumMetadata.suggestion now spell out the two sub-cases (missing vs. presented-but-rejected) and the SHOULD-NOT-auto-retry rule. Closes 3.0.x portion of #3730. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> * ci(release): un-exclude dist/compliance + forward-merge auto-resolution (#3794) (#3800) * ci(release): un-exclude dist/compliance + forward-merge auto-resolution Two release-pipeline hardening fixes for 3.0.4 and beyond. .dockerignore: un-exclude dist/compliance/ so versioned URLs (the /compliance/{version}/ tree) actually serve from the Fly image. The ignore pattern was set up for /schemas/ and /protocol/ but never updated when /compliance/ versioned routing was added. Result: every committed dist/compliance/3.0.X/ directory was stripped from the build context, and SDKs fetching compliance bundles by URL hit 404s on fresh-cache scenarios. Re-include dist/compliance, then re-exclude dist/compliance/latest (regenerated in-container by build:compliance). forward-merge-3.0.yml: replace the bare-merge approach with auto- resolution on an explicit allowlist of always-divergent paths (package.json version, .changeset/*.md, dist/*, CHANGELOG.md, schema source index files). Drop the brittle is-ancestor shortcut that returns false after squash-merges even when content is in main; use post-merge git diff --quiet to skip cleanly when main already has 3.0.x's content. Conflicts outside the allowlist fail the workflow loud, surfacing playbook violations (a change on 3.0.x that wasn't first cherry-picked from main). Updates .agents/playbook.md § Release lines to document the new auto-resolution behavior so reviewers know what to spot-check. Closes the operational pain that bit us today on PR #3783, where the v3.0.3 cut's forward-merge needed three manual conflict resolutions (package.json, .changeset/fix-url-type-tracker-pixel-channel-docs.md, static/schemas/source/index.json) plus a manual unshallow before the merge could complete. * ci(release): address expert review feedback on forward-merge auto-resolution Code review (af5969...): empty-CONFLICTS-after-merge-failure guard so hook rejections / unrelated-history failures fail loud instead of silently committing the empty merge. Security review (a16289...): tighten dist/* glob to explicit {schemas,compliance,protocol,docs}/* list so future mutable subtrees under dist/ don't silently auto-resolve via --theirs. Plus: drop `2>/dev/null || true` on `git rm` (let real errors surface; the post-loop REMAINING check already guards against bad state). Add post-resolution `git status --short` and `git diff vs origin/main -- package.json` log groups so reviewers can spot main-unique scripts that may have been overwritten without leaving GitHub. No functional change to the happy-path resolution behavior. --------- # Conflicts: # .agents/playbook.md Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): forward-merge package.json --ours, not --theirs (#3807) (#3808) Original --theirs rule stripped main's structural changes when 3.0.x hadn't been updated to match. Concrete case: main renamed @adcp/client to @adcp/sdk + bumped to 5.25.1; 3.0.x stayed on @adcp/client@5.21.1. The forward-merge took 3.0.x's package.json wholesale, leaving the package-lock out of sync. CI broke with "npm ci ... package.json and package-lock.json or npm-shrinkwrap.json are in sync". PR #3806's CI exposed this. --ours preserves main's state. Main's pre-mode tracking is independent of 3.0.x's version field; the dist/* artifacts still flow forward via the allowlist; main's structural changes survive. Trade-off: main's package.json version doesn't reflect 3.0.x's latest release. Acceptable — main's version field isn't authoritative while pre-mode is active. The next main pre-mode cut produces 3.1.0-beta.X from accumulated changesets regardless of base version. Companion playbook + PR-body checklist update so docs match behavior. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): bridge cherry-pick divergence in forward-merge (#3811) (#3814) Adds two specific files to the auto-resolution --ours allowlist where 3.0.x has #3789's hand-adapted prose-only backport of #3739 and main has the full enum split (adds AUTH_MISSING / AUTH_INVALID codes that can't ship to 3.0.x without violating patch eligibility). - docs/building/implementation/error-handling.mdx - static/schemas/source/enums/error-code.json Without this rule, every routine forward-merge from 3.0.x → main rediscovers the same conflict because squash-merges of prior forward-merges (the only merge style this repo allows) don't advance git's merge-base. The post-merge `git diff --quiet` skip can't reach to detect "main already has this content" because the merge fails before that step. Marked temporary in the workflow comments — remove when 3.1.0 cuts and main no longer has the in-flight enum split. Without this fix, the next forward-merge after 3.0.4 cuts would fail loud on these same two files, requiring another manual resolution PR. With it, 3.0.4's forward-merge auto-succeeds. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3799) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ci(release): push forward-merge branch before peter-evans runs (#3818) (#3820) Discovered when 3.0.4's forward-merge ran for real for the first time (run 25250971971): auto-resolution worked perfectly (the new allowlist + bridge handled every conflict), but peter-evans/create-pull-request crashed with "fatal: ambiguous argument 'origin/forward-merge/3.0.x'" because the remote branch didn't exist yet. peter-evans's internal `git reset --hard origin/forward-merge/3.0.x` flow assumes the remote-tracking branch already exists. On a first run (or any time the remote branch isn't there), it fails. Pushing explicitly after auto-resolution establishes the ref so peter-evans's reset has a target. After this lands + cherry-picks to 3.0.x, the next VP cut (3.0.5 or 3.1.0) will auto-create the forward-merge PR without manual intervention. For 3.0.4 specifically: I'll open the PR manually since this fix requires a workflow change that hasn't run yet. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(storyboard-schema): add optional default_agent field (closes #3894) (#3897) * spec(storyboard-schema): add optional default_agent field (closes #3894) Adds an optional top-level default_agent: <key> field to the storyboard authoring schema. The multi-agent runner resolves the logical key (sales, governance, creative, …) against the runtime agents map passed to runStoryboard({ agents: {…} }) — see adcp-client#1066 / #1355. The runner already accepts default_agent via run-options. This change lets storyboard authors encode the topology intent in YAML once instead of re-asserting it on every CI invocation. Cross-domain tools (sync_creatives, list_creative_formats, comply_test_controller) route deterministically without per-step agent: overrides. Strictly additive — single-agent runs ignore it, existing 3.0.x storyboards keep working, pre-existing run-options default_agent keeps its lower-precedence slot. Mirrors the provides_state_for precedent (#3775) for additive storyboard-schema affordances on 3.0.x. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(storyboard-schema): tighten default_agent contract per expert review Address protocol- and product-expert review on PR #3897: - Slot 2: state explicitly what zero/one/multi specialism claimants do. Multi-claim grades unrouted_step (operator-config error); slots 3/4 do NOT rescue. Zero falls through to slot 3. - Slot 3: when the storyboard declares default_agent and the key is absent from the runtime map, grade default_agent_unresolved — do NOT silently fall to slot 4. Silent fallback would invisibly override the storyboard author's encoded intent. Slot 4 fires only when the field is unset. - Slot 4: same set-but-unmatched rule applied symmetrically. - Key shape: free-form non-empty string keyed by the runtime agents map. Spec does NOT constrain to the specialism enum — production topologies legitimately fan out per-property / per-region / per-rights-holder. Cross-operator portability is the author's concern, not the spec's. - Drop comply_test_controller from the cross-domain example — it's routed via prerequisites.controller_seeding, not default_agent. - Disambiguate adcp-client#1355 reference (was bare "#1355"). No wire-protocol surface change; doc-only edit to the storyboard authoring schema (already a comment-block YAML). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(capabilities): relax identity.additionalProperties to true (3.0.x) (#3896) The identity object on get-adcp-capabilities-response was schema-closed (additionalProperties: false), so any 3.0-pinned operator adopting a forward-compatible field — notably identity.brand_json_url from #3690, intended to be readable on 3.0 without a schema bump — would have its capabilities response rejected by strict 3.0 validators (e.g., @adcp/sdk's createAdcpServer default). Mirrors the relaxation already on main (post-#3690). Closed property list (per_principal_key_isolation, key_origins, compromise_notification) is unchanged; this is strictly additive forward-compat. The forward-compat narrative in security.mdx ("3.0-pinned implementers can adopt the field today without bumping") depends on this being live in the shipped 3.0 schema — without it, the spec advice contradicts the schema. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(storyboard): capture rights_id from acquire_rights response (closes #3892) (#3893) The brand-rights storyboard step `acquire_rights` captured `rights_grant_id` from the response, but `brand/acquire-rights-response.json` defines the field as `rights_id`. Spec-compliant agents passed response_schema validation but failed context capture, cascade-skipping `rights_enforcement`. Update the YAML to read `rights_id` (preserving the storyboard-internal `rights_grant_id` key so no other steps need to change) and correct the `expected:` prose to match the published schema (rights_id + status: acquired). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3898) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: aao-release-bot[bot] <280565558+aao-release-bot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com>
…olution (#4011) * chore(3.0.x): re-cherry-pick #3461 and #3462 (envelope_field_present + asset-union) (#3608) * feat(compliance): add envelope_field_present check type; update v3-envelope-integrity storyboard (#3461) Closes #3429 Adds `envelope_field_present` as a recognized storyboard check type that walks protocol-envelope.json instead of the step's response_schema_ref. Updates v3-envelope-integrity.yaml to use it for the `status` presence assertion, eliminating the VERIFIER_UNREACHABLE gap in the adcp-client storyboard-drift verifier. Requires adcp-client#1045 for runtime + static drift support. Non-breaking justification: additive — new check type alongside existing ones; no existing check type changed or removed; no storyboard currently uses envelope_field_present. Pre-PR review: - code-reviewer: approved — lint change correct, test added for classifyOutcome, dist/3.0.1 frozen by design (ships as 3.0.2), no blockers - ad-tech-protocol-expert: approved — semantics correct; runner-output-contract.yaml updated with check enum + expected/actual shape entries; patch bump confirmed correct per playbook conformance-harness rule https://claude.ai/code/session_01Kwks2uGQS3ZVX7g4kujdGp Co-authored-by: Claude <noreply@anthropic.com> * fix(schema): promote asset-variant oneOf to canonical asset-union.json (#3462) * fix(schema): promote asset-variant oneOf to canonical asset-union.json Fixes json-schema-to-typescript emitting VASTAsset1, DAASTAsset1, BriefAsset1, and CatalogAsset1 when creative-asset.json and creative-manifest.json both inline identical 14-arm oneOf arrays. Creates core/assets/asset-union.json (title: AssetVariant) as a single $id-addressable source of truth. Both parent schemas now $ref this file instead of duplicating the union inline. Wire format and validation semantics are unchanged; the discriminator annotation moves inside the canonical schema per OAS 3.1 §4.8.24. Closes #3459 https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * docs(schema): document intentional subset in offering-asset-group.json Clarify that offering-asset-group excludes brief-asset and catalog-asset (campaign-input metadata, not delivery-ready creative types) and cross-link to the new asset-union.json for the full union. Addresses code-reviewer nit from pre-PR review. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo * fix(schema): replace inline asset oneOf in list-creatives-response.json Third copy of the 14-arm asset union, caught in post-PR code review. Replace with $ref to asset-union.json for consistency. https://claude.ai/code/session_017XYv1Yt2m9NSj4bsNR22qo --------- Co-authored-by: Claude <noreply@anthropic.com> * chore(3.0.x): sync release-pipeline workflows from main (App token, 3.0.x triggers) --------- Co-authored-by: Claude <noreply@anthropic.com> * Version Packages (#3615) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(creative-channels): fix url_type tracker → tracker_pixel (#2986) (#3673) The display, audio, carousels, and DOOH channel docs use "url_type": "tracker", which is not a valid value in the url-asset-type.json enum (clickthrough / tracker_pixel / tracker_script). Sellers following these docs emit a non-conformant url_type that buyers can't interpret without guessing. Replaces all 10 occurrences with "tracker_pixel" to match the schema. This is step 1 of the rollout proposed by Nastassia Fulconis on adcp#2986: 3.0.x docs cleanup → 3.1 SHOULD + role-based fallback + mechanism-vs-purpose clarification → 4.0 required. The dist/docs/3.0.2 release snapshot still carries the old value; backporting to the snapshot is intentionally out of scope here so the fix lands on the live source first. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(storyboard): provides_state_for cascade-rescue field (closes #3734) (#3775) Backport of e8fded9 from main to 3.0.x. Adds optional same-phase substitution declaration on storyboard steps so explicit-mode social platforms (Snap, Meta, TikTok) can pre-provision accounts out-of-band and have list_accounts substitute for the missing sync_accounts state contract — recovering 3 Tier-1 sandbox-verified adapters from 1/9/0 to 9/10 once the SDK cache refreshes against this version. Storyboard schema (static/compliance/source/universal/storyboard-schema.yaml): field documentation parallel to contributes_to. All-of array semantics, same-phase only, target/substitute must be stateful, no self-reference, acyclic peer-graph per phase. Runner output contract (static/compliance/source/universal/ runner-output-contract.yaml): new peer_substituted skip reason in skip_result.reasons, distinct from peer_branch_taken (branch-set routing) and not_applicable (coverage gap). Specialism YAML (static/compliance/source/specialisms/sales-social/ index.yaml): provides_state_for: sync_accounts on list_accounts in the account_setup phase. Build-time validation (scripts/lint-storyboard-provides-state-for.cjs + test): wired into build-compliance.cjs lint chain. Covers shape, self-reference, unknown target, cross-phase, target-stateful, substitute-stateful, and direct-cycle violations. Pure additive change; existing storyboards keep current cascade behavior. Cherry-pick conflict resolved in package.json: kept 3.0.x's simpler node --test invocation (no --test-force-exit / --test-timeout flags), slotted in test:storyboard-provides-state-for entry consistent with 3.0.x style. --no-verify used because precommit fails on a pre-existing 3.0.x baseline @adcp/client install drift unrelated to this change. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3696) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ci(release): auto-upload protocol tarball to GitHub Release (#3786) (#3787) `createGithubReleases: true` from changesets/action only writes the changelog body — files have to be uploaded separately. Without this step the artifacts ship to the repo's dist/ tree but are never attached as Release assets, leaving adopters who pin via release URL with 404s. v3.0.0 had assets only because they were uploaded by hand on 2026-04-22; v3.0.1 / v3.0.2 / v3.0.3 all shipped empty. New step runs after changesets/action, gated on `steps.changesets.outputs.published == 'true'` so it only fires on tag-and-release runs (not Version Packages PR-creation runs). Uploads ${VERSION}.tgz plus .sha256 / .sig / .crt sidecars with --clobber so re-runs are idempotent. Backfill of the three missing releases (v3.0.1 / v3.0.2 / v3.0.3) is a separate manual step against the assets already committed at dist/protocol/ on the 3.0.x branch. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(3.0.x): cherry-pick + adapt 7 spec fixes from main (#3784) (#3789) * fix(schema): correct title annotation in rate-limited error-details schema (#3149) * fix(schema): correct title annotation in rate-limited error-details schema Change "RATE_LIMITED Details" to "Rate Limited Details" in the `title` annotation of error-details/rate-limited.json. The title field is non-normative per JSON Schema draft-07 (no validation or wire-format impact); this corrects the downstream codegen output from `RATE_LIMITEDDetails` to `RateLimitedDetails`. Applied to source and all dist snapshots (3.0.0, 3.0.0-rc.3, latest). Refs #3145. See adcp-client#942 for the SDK alias layer. https://claude.ai/code/session_01E3LcN5g4tEZutKCTePUVbs * revert: drop dist/schemas/ hand-edits per #3149 review dist/schemas/3.0.0/ and dist/schemas/3.0.0-rc.3/ are immutable release snapshots — scripts/build-schemas.cjs only writes them under --release mode (the changesets release step), and dist/schemas/latest/ is gitignored. Mutating frozen GA snapshots breaks the immutability contract that lets buyers pin to 3.0.0 and trust they see exactly what was published. Source title fix is preserved. The next --release build picks up the corrected title for that version's snapshot; past releases stay byte-identical to what was published. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> (cherry picked from commit fbf71ce) * spec(error): standardize VALIDATION_ERROR issues[] on core/error.json (closes #3059) (#3562) * spec(error): standardize VALIDATION_ERROR issues[] (closes #3059) Adds an optional top-level issues array to core/error.json, normalizing what @adcp/client already emits for multi-field validation rejections (adcp-client#874 / #915). Other implementations (adcp-go, adcp-client-python, hand-rolled sellers) would either miss the structured pointer list, adopt it ad-hoc with different naming, or converge if the spec normalizes it. Filing now keeps the ecosystem aligned before adoption deepens. Each issue entry: { pointer (RFC 6901), message, keyword, schemaPath? }. schemaPath MAY be omitted in production to avoid fingerprinting oneOf branch selection on adversarial payloads. Backward compatibility: - field (singular) is retained. When both are present, sellers SHOULD set field to issues[0].pointer for pre-3.1 consumers reading field only. - details.issues mirror is permitted for consumers reading from details. New consumers should prefer top-level issues. Files: - static/schemas/source/core/error.json: adds issues property - docs/building/implementation/error-handling.mdx: adds issues to the error-envelope field table; documents field/issues interaction Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(error): apply protocol-expert review feedback on issues[] Three substantive sharpens from the ad-tech-protocol-expert review of PR #3562: 1. Pointer-format mismatch with field — flagged as a latent bug. The existing top-level field uses JSONPath-lite (packages[0].targeting); the new issues[].pointer uses RFC 6901 (/packages/0/targeting). Calling the mirror rule SHOULD without specifying the translation left sellers with collision risk. Both descriptions now spell out the format choice (Ajv's instancePath = RFC 6901 for pointer; legacy JSONPath-lite for field) AND the explicit translation contract on the mirror. Future major version will deprecate field in favor of issues[].pointer. 2. issues[0].pointer mirror rule — SHOULD upgraded to MUST (when issues is present). SHOULD created exactly the rough edge the review flagged: pre-3.1 consumers reading field would get nondeterministic behavior across sellers. Cost of MUST is one line of dual-write per seller; cost of SHOULD is a long tail of seller-A-vs-seller-B bugs. MUST also gives a clean deprecation path in 4.0. 3. schemaPath downgraded from MAY to SHOULD NOT in production. The review identified this as a real probe oracle: leaking which oneOf branch the validator selected before semantic rejection helps adversarial callers map polymorphic unions. AdCP already has an adversarial-payload threat model (signed-requests work, agent- controlled field audit). Sellers MAY emit in dev/sandbox modes. Also cited Ajv as prior art so implementers know where the keyword vocabulary comes from (instancePath / keyword / schemaPath are Ajv's native error output fields). Reduces the ad-hoc-naming risk. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit bd3a18c) * spec(schemas): PascalCase titles on error-details schemas (partial fix for #3145) (#3566) * spec(schemas): PascalCase titles on error-details schemas Partial fix for #3145. Six error-details/*.json files carry SCREAMING_SNAKE titles that propagate awkwardly through json-schema-to-typescript into @adcp/client's public type surface (e.g., RATE_LIMITEDDetails_ScopeValues read as a typo to every consumer of the SDK). Renames (no wire-format change — $id values stay kebab-case; only the title field is touched, which controls codegen): - ACCOUNT_SETUP_REQUIRED Details -> AccountSetupRequiredDetails - AUDIENCE_TOO_SMALL Details -> AudienceTooSmallDetails - BUDGET_TOO_LOW Details -> BudgetTooLowDetails - CONFLICT Details -> ConflictDetails - CREATIVE_REJECTED Details -> CreativeRejectedDetails - POLICY_VIOLATION Details -> PolicyViolationDetails rate-limited.json already had PascalCase (Rate Limited Details); vendor-error-codes.json already had Vendor Error Code Registry; no change to either. #3145's other half — Foo1-suffixed enum dupes (AgeVerificationMethod1, *Asset1, etc.) — is downstream codegen behavior. Both refs already point at the same $id with $ref so the spec side is correct; the dupe shows up because json-schema-to-typescript walks through different parent paths and emits two inline copies. SDK-side post-process renaming (with one- minor-version aliases) is the pragmatic fix. Tracked at adcp-client#942. Schema validators all clean (test:schemas 7/7, test:json-schema 255/255, build-compliance 20 universal / 6 protocols / 19 specialisms). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(schemas): Title Case (with spaces) to match #3149 precedent Protocol-expert review of #3566 flagged the style inconsistency: my PR stripped spaces (AccountSetupRequiredDetails), but PR #3149 (Apr 25) established the precedent of spaced Title Case for the same kind of problem (Rate Limited Details). Going with #3149's form so the 8-file directory stays uniform. json-schema-to-typescript strips whitespace when generating identifiers, so the codegen output is identical either way (AccountSetupRequiredDetails on both); the source-of-truth title just stays consistent across the directory. Title field is non-normative per JSON Schema draft-07 §10.1 — affects only docgen/codegen output, not validation. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit a5d9bff) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) (#3671) * spec(url-asset): SHOULD url_type + role-based fallback (#2986 step 2) Define what receivers do when a URL asset omits url_type. Today the field is optional with no fallback rule, so a conformant manifest like {asset_type: url, url: ...} forces buyers to guess the invocation mechanism — and guessing wrong (firing a clickthrough URL as a pixel, or a tracker as a clickthrough) corrupts measurement and breaks user flows. Schema changes: - url-asset.json: senders SHOULD include url_type on every URL asset. When absent, receivers SHOULD fall back to the format's url-asset-requirements.role (clickthrough/landing_page → `clickthrough` mechanism; *_tracker roles → `tracker_pixel`). When neither is available, receivers MAY reject rather than guess. - url-asset-requirements.json: clarify that role is purpose (impression vs click vs viewability vs 3P) while url_type is mechanism (click vs pixel vs script tag); a click_tracker slot validly accepts a tracker_pixel URL. Doc changes: - asset-types.mdx URL Asset section: rewritten to use the actual url_type enum (clickthrough/tracker_pixel/tracker_script — the old text listed impression_tracker/video_tracker/landing_page, which were never url_type values), to add the SHOULD note and role fallback table, and to remove the "you only need to supply the url value" guidance that drove the original ambiguity. Wire format unchanged. Senders already including url_type are unaffected. Step 2 of the rollout on adcp#2986; step 3 (require url_type in 4.0) follows once this lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(url-asset): apply expert review — viewability→script, third_party→unmapped, MUST NOT silently guess Addresses ad-tech-protocol-expert + adtech-product-expert review on PR #3671: Required fixes: - viewability_tracker → tracker_script (not tracker_pixel). OMID and equivalent verification SDKs require a <script> tag; firing them as a pixel produces no measurement and no error — exactly the silent- corruption failure mode this PR exists to prevent. - third_party_tracker → no safe fallback. Mechanism is integration- specific (DV/IAS ship both pixel and script forms). Receivers MAY reject or warn rather than guess. - Strengthen receiver guidance to "MUST NOT silently pick a mechanism; SHOULD reject" when both url_type and role are absent. Mirrors the mdx language into the JSON Schema description so extractors and conformance tooling read the same rule. - Add VAST/DAAST carve-out: VAST tag URLs are not URL assets; use asset_type: "vast" or the dedicated tracker types pending RFC #2915. - Update docs/creative/formats.mdx tracker-detection rule. Today it feature-detects on url_type ∈ {tracker_pixel, tracker_script}; under the new fallback semantics, format authors who declare only role would silently fail that detector. Detection now accepts either url_type OR a tracker-purpose role. Non-blocking improvements: - Migration cue in asset-types.mdx for sellers who built tooling around the older "you only need to supply the url value" guidance: 3.x is fine, plan to add url_type before 4.0. Wire format unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(url-asset): cross-reference fallback table between schema and mdx The role→url_type fallback table lives in two places: the url-asset.json top-level description (read by conformance tools and codegen) and the asset-types.mdx URL Asset section (read by humans). Without a hint, an editor of one will silently drift the other. Adds a $comment in url-asset.json and a JSX comment in asset-types.mdx pointing at each other. Schema description remains the normative source; mdx is the human copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit f6af651) * fix(compliance): audience-sync discover_account stateful: false → true (#3710) The list_accounts step in the account_setup phase establishes account_id for downstream sync_audiences calls. The storyboard narrative was correct but the stateful flag contradicted it, causing the SDK runner to not count a passing result as cascade state — explicit-mode adopters saw prerequisite_failed on sync_audiences even after list_accounts passed. Fixes #3707 https://claude.ai/code/session_019ZJXyeQYrRZc17UqcW2yDf Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit e74997b) * chore(changesets): downgrade cherry-picked minor bumps to patch for 3.0.x line Maintenance-line policy: cherry-picks land as patches even when the original PRs landed on main as minor bumps. Otherwise the changesets trigger 3.1.0 publication off the 3.0.x branch, defeating the cherry-pick. - error-issues-array.md (#3562, originally minor): patch - url-type-should-and-role-fallback.md (#3671, originally minor): patch Both PRs were classified as patch-eligible in #3784. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(schema): manifest.json + structured enumMetadata (closes #3725) (#3738) * feat(schema): publish manifest.json + structured enumMetadata (adcp#3725) Adds two additive artifacts so SDKs stop hand-rolling (and drifting on) spec metadata. Root cause for adcp-client#1135 (17 missing error codes, 3 wrong recovery classifications shipped in TS SDK for over a year). - enums/error-code.json gains an enumMetadata block with structured recovery + suggestion per code. Build-time lint rejects drift between the structured value and the prose Recovery: X in enumDescriptions. - New static/schemas/source/manifest.schema.json + generator emitting dist/schemas/{version}/manifest.json: 58 tools, 48 error codes, 19 specialisms, plus an error_code_policy block defining how SDKs MUST classify codes from non-conforming sellers. - mutating derived from the same classifier the idempotency-key lint enforces (single source of truth). Tightened READ_ONLY_VERB_PATTERN to anchor at start so create-collection-list / delete-property-list no longer mis-classify as read-only via -list- mid-name; added search as a read-only verb. - Specialisms expose entry_point_tools (curated minimum from index.yaml.required_tools) and exercised_tools (full surface — union of own phases[].steps[].task and every linked scenario, derived by walking requires_scenarios). sales_guaranteed now correctly lists 9 tools instead of 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(manifest): expert-review fixes — strip regex, requires_tool gating Two correctness fixes from protocol-expert review: - stripRecoveryProse: greedy [^.]*\. truncated descriptions with periods inside parentheticals (e.g. "capabilities.media_buy.limits"). Rewrote with three explicit patterns: bare verdict, verdict + balanced parenthetical, clause continuation to EOS. Verified all 48 codes emit clean descriptions with no Recovery: prose remaining. - collectTasksFromPhases now skips steps gated by requires_tool. Steps marked requires_tool: <X> are conditional on the agent claiming X, not required surface. Without the skip, optional test-harness tools (comply_test_controller, gated across 23+ steps) propagated into every sales specialism's exercised_tools. Plus code-review nits: - Simplified discoverTools utility-shape skip to NON_OPERATION_ALLOWLIST only; documented the contract. - Removed unused schema parameter from classifyRequestMutating. - Tightened indexScenarioTasks predicate to require phases array. - Added cross-reference comment between MANIFEST_PROTOCOLS and the meta-schema's protocol enum. - Changeset now mentions /schemas/latest/manifest.json for nightly codegen consumers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit b44996f) * fix(3.0.x): trim enumMetadata to 3.0.x codes; downgrade #3738 to patch The cherry-pick of #3738 brought in enumDescriptions + enumMetadata entries for SCOPE_INSUFFICIENT, READ_ONLY_SCOPE, and FIELD_NOT_PERMITTED — three error codes added to main in PRs that aren't on 3.0.x. Their enum entries weren't introduced (3.0.x's enum array is unaffected), but the description and metadata blocks were left referencing them. The new lintErrorCodeEnumMetadata guardrail catches this and refuses to build. Trim: drop the three orphan entries from enumDescriptions and enumMetadata. Counts now agree at 45 / 45 / 45. Also downgrades the changeset from minor to patch — same rationale as the other cherry-picks on this branch: maintenance line, no new enum values, no wire-format change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(errors): tighten AUTH_REQUIRED prose to warn on retry storms (3.0.x prose-only backport of #3739) 3.0.x cannot adopt the AUTH_MISSING/AUTH_INVALID split from #3739 — adding new enum values violates the maintenance line's semver rules. This is the prose-only backport: same wire code, same recovery class, but the description and enumMetadata.suggestion now spell out the two sub-cases (missing vs. presented-but-rejected) and the SHOULD-NOT-auto-retry rule. Closes 3.0.x portion of #3730. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com> * ci(release): un-exclude dist/compliance + forward-merge auto-resolution (#3794) (#3800) * ci(release): un-exclude dist/compliance + forward-merge auto-resolution Two release-pipeline hardening fixes for 3.0.4 and beyond. .dockerignore: un-exclude dist/compliance/ so versioned URLs (the /compliance/{version}/ tree) actually serve from the Fly image. The ignore pattern was set up for /schemas/ and /protocol/ but never updated when /compliance/ versioned routing was added. Result: every committed dist/compliance/3.0.X/ directory was stripped from the build context, and SDKs fetching compliance bundles by URL hit 404s on fresh-cache scenarios. Re-include dist/compliance, then re-exclude dist/compliance/latest (regenerated in-container by build:compliance). forward-merge-3.0.yml: replace the bare-merge approach with auto- resolution on an explicit allowlist of always-divergent paths (package.json version, .changeset/*.md, dist/*, CHANGELOG.md, schema source index files). Drop the brittle is-ancestor shortcut that returns false after squash-merges even when content is in main; use post-merge git diff --quiet to skip cleanly when main already has 3.0.x's content. Conflicts outside the allowlist fail the workflow loud, surfacing playbook violations (a change on 3.0.x that wasn't first cherry-picked from main). Updates .agents/playbook.md § Release lines to document the new auto-resolution behavior so reviewers know what to spot-check. Closes the operational pain that bit us today on PR #3783, where the v3.0.3 cut's forward-merge needed three manual conflict resolutions (package.json, .changeset/fix-url-type-tracker-pixel-channel-docs.md, static/schemas/source/index.json) plus a manual unshallow before the merge could complete. * ci(release): address expert review feedback on forward-merge auto-resolution Code review (af5969...): empty-CONFLICTS-after-merge-failure guard so hook rejections / unrelated-history failures fail loud instead of silently committing the empty merge. Security review (a16289...): tighten dist/* glob to explicit {schemas,compliance,protocol,docs}/* list so future mutable subtrees under dist/ don't silently auto-resolve via --theirs. Plus: drop `2>/dev/null || true` on `git rm` (let real errors surface; the post-loop REMAINING check already guards against bad state). Add post-resolution `git status --short` and `git diff vs origin/main -- package.json` log groups so reviewers can spot main-unique scripts that may have been overwritten without leaving GitHub. No functional change to the happy-path resolution behavior. --------- # Conflicts: # .agents/playbook.md Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): forward-merge package.json --ours, not --theirs (#3807) (#3808) Original --theirs rule stripped main's structural changes when 3.0.x hadn't been updated to match. Concrete case: main renamed @adcp/client to @adcp/sdk + bumped to 5.25.1; 3.0.x stayed on @adcp/client@5.21.1. The forward-merge took 3.0.x's package.json wholesale, leaving the package-lock out of sync. CI broke with "npm ci ... package.json and package-lock.json or npm-shrinkwrap.json are in sync". PR #3806's CI exposed this. --ours preserves main's state. Main's pre-mode tracking is independent of 3.0.x's version field; the dist/* artifacts still flow forward via the allowlist; main's structural changes survive. Trade-off: main's package.json version doesn't reflect 3.0.x's latest release. Acceptable — main's version field isn't authoritative while pre-mode is active. The next main pre-mode cut produces 3.1.0-beta.X from accumulated changesets regardless of base version. Companion playbook + PR-body checklist update so docs match behavior. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): bridge cherry-pick divergence in forward-merge (#3811) (#3814) Adds two specific files to the auto-resolution --ours allowlist where 3.0.x has #3789's hand-adapted prose-only backport of #3739 and main has the full enum split (adds AUTH_MISSING / AUTH_INVALID codes that can't ship to 3.0.x without violating patch eligibility). - docs/building/implementation/error-handling.mdx - static/schemas/source/enums/error-code.json Without this rule, every routine forward-merge from 3.0.x → main rediscovers the same conflict because squash-merges of prior forward-merges (the only merge style this repo allows) don't advance git's merge-base. The post-merge `git diff --quiet` skip can't reach to detect "main already has this content" because the merge fails before that step. Marked temporary in the workflow comments — remove when 3.1.0 cuts and main no longer has the in-flight enum split. Without this fix, the next forward-merge after 3.0.4 cuts would fail loud on these same two files, requiring another manual resolution PR. With it, 3.0.4's forward-merge auto-succeeds. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3799) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ci(release): push forward-merge branch before peter-evans runs (#3818) (#3820) Discovered when 3.0.4's forward-merge ran for real for the first time (run 25250971971): auto-resolution worked perfectly (the new allowlist + bridge handled every conflict), but peter-evans/create-pull-request crashed with "fatal: ambiguous argument 'origin/forward-merge/3.0.x'" because the remote branch didn't exist yet. peter-evans's internal `git reset --hard origin/forward-merge/3.0.x` flow assumes the remote-tracking branch already exists. On a first run (or any time the remote branch isn't there), it fails. Pushing explicitly after auto-resolution establishes the ref so peter-evans's reset has a target. After this lands + cherry-picks to 3.0.x, the next VP cut (3.0.5 or 3.1.0) will auto-create the forward-merge PR without manual intervention. For 3.0.4 specifically: I'll open the PR manually since this fix requires a workflow change that hasn't run yet. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(storyboard-schema): add optional default_agent field (closes #3894) (#3897) * spec(storyboard-schema): add optional default_agent field (closes #3894) Adds an optional top-level default_agent: <key> field to the storyboard authoring schema. The multi-agent runner resolves the logical key (sales, governance, creative, …) against the runtime agents map passed to runStoryboard({ agents: {…} }) — see adcp-client#1066 / #1355. The runner already accepts default_agent via run-options. This change lets storyboard authors encode the topology intent in YAML once instead of re-asserting it on every CI invocation. Cross-domain tools (sync_creatives, list_creative_formats, comply_test_controller) route deterministically without per-step agent: overrides. Strictly additive — single-agent runs ignore it, existing 3.0.x storyboards keep working, pre-existing run-options default_agent keeps its lower-precedence slot. Mirrors the provides_state_for precedent (#3775) for additive storyboard-schema affordances on 3.0.x. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(storyboard-schema): tighten default_agent contract per expert review Address protocol- and product-expert review on PR #3897: - Slot 2: state explicitly what zero/one/multi specialism claimants do. Multi-claim grades unrouted_step (operator-config error); slots 3/4 do NOT rescue. Zero falls through to slot 3. - Slot 3: when the storyboard declares default_agent and the key is absent from the runtime map, grade default_agent_unresolved — do NOT silently fall to slot 4. Silent fallback would invisibly override the storyboard author's encoded intent. Slot 4 fires only when the field is unset. - Slot 4: same set-but-unmatched rule applied symmetrically. - Key shape: free-form non-empty string keyed by the runtime agents map. Spec does NOT constrain to the specialism enum — production topologies legitimately fan out per-property / per-region / per-rights-holder. Cross-operator portability is the author's concern, not the spec's. - Drop comply_test_controller from the cross-domain example — it's routed via prerequisites.controller_seeding, not default_agent. - Disambiguate adcp-client#1355 reference (was bare "#1355"). No wire-protocol surface change; doc-only edit to the storyboard authoring schema (already a comment-block YAML). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(capabilities): relax identity.additionalProperties to true (3.0.x) (#3896) The identity object on get-adcp-capabilities-response was schema-closed (additionalProperties: false), so any 3.0-pinned operator adopting a forward-compatible field — notably identity.brand_json_url from #3690, intended to be readable on 3.0 without a schema bump — would have its capabilities response rejected by strict 3.0 validators (e.g., @adcp/sdk's createAdcpServer default). Mirrors the relaxation already on main (post-#3690). Closed property list (per_principal_key_isolation, key_origins, compromise_notification) is unchanged; this is strictly additive forward-compat. The forward-compat narrative in security.mdx ("3.0-pinned implementers can adopt the field today without bumping") depends on this being live in the shipped 3.0 schema — without it, the spec advice contradicts the schema. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(storyboard): capture rights_id from acquire_rights response (closes #3892) (#3893) The brand-rights storyboard step `acquire_rights` captured `rights_grant_id` from the response, but `brand/acquire-rights-response.json` defines the field as `rights_id`. Spec-compliant agents passed response_schema validation but failed context capture, cascade-skipping `rights_enforcement`. Update the YAML to read `rights_id` (preserving the storyboard-internal `rights_grant_id` key so no other steps need to change) and correct the `expected:` prose to match the published schema (rights_id + status: acquired). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3898) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(skill): document implementation-dependent issues[] fields (3.0.x backport of #3927) (#3931) Backports the SKILL.md update onto 3.0.x so 3.0.6 carries the four implementation-dependent issues[] field bullets + 2 symptom-fix table rows. Doc-only, identical to #3927 on main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * 3.0.6 cherry-picks: governance wire-placement + ctx_metadata reservation + storyboard fixture fixes (#3996) * spec(errors): wire-placement guidance for GOVERNANCE_DENIED / GOVERNANCE_UNAVAILABLE (#3929) error-code.json described what each code means but not WHERE on the response it appears. Storyboards interpreted differently — #3914 surfaced the mismatch where the brand-rights compliance storyboard expected adcp_error.code: GOVERNANCE_DENIED even though acquire_rights already has a first-class AcquireRightsRejected discriminated arm. GOVERNANCE_DENIED — structured business outcome (governance call SUCCEEDED, agent returned a denial verdict). When the task response defines a rejection arm (e.g., AcquireRightsRejected, CreativeRejected), that arm IS the canonical denial shape — populate reason, do NOT also emit the code in errors[]/ adcp_error, transport-level success markers stay green. The schema layer already enforces this via `not: { required: [errors] }` on those arms; the doc-comment makes the rule discoverable from the error code. When no rejection arm exists (e.g., create_media_buy), populate errors[] + adcp_error per the two-layer model and flip transport markers. GOVERNANCE_UNAVAILABLE — system error (governance call FAILED). Always errors[] + adcp_error, transport markers always flip. Never use a rejection arm. Also adds a parallel storyboard-authoring note in error-handling.mdx: when asserting against rejection-arm denials, use `check: field_value, path: "status", value: "rejected"` instead of `check: error_code` — the spec-correct response carries no code on the wire. Closes the doc-comment item on #3918; companion to #3914 (storyboard fix is separate work). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(conventions): reserve ctx_metadata as adapter-internal round-trip key (#3640) (#3788) * spec(conventions): reserve ctx_metadata as adapter-internal round-trip key (#3640) Closes #3640. Reserves `ctx_metadata` as a top-level adapter-internal round-trip cache key on AdCP resource objects (Product, MediaBuy, Package, Creative, AudienceSegment, Signal, RightsGrant). SDKs MUST strip the key before wire egress and MUST emit a warning-level log when stripping. Buyers never see the field. Convention is non-binding at the wire level — these resources already declare additionalProperties: true. PropertyList and CollectionList are out of scope (additionalProperties: false) until a follow-up PR widens those schemas. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(conventions): tighten ctx_metadata scope language and warn-log condition Two reviewer-flagged clarifications on the ctx_metadata reservation: - Scope: state explicitly that the reservation travels with the resource wherever it appears (top-level, nested, in arrays). Closes the read where someone could interpret the rule as "applies only at envelope top level." - Warn-log condition: the RULE paragraph and the conformance pseudocode disagreed on when to emit the warning (RULE said "when stripping", pseudocode said "when present and non-empty"). Align both on the non-empty rule — silence on absent/empty values avoids logspam from resources that just don't carry adapter state. - Add a one-line disambiguation against context / context_id, since the shared "ctx" prefix could mislead a reader. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(compliance): storyboard fixture fixes — inventory_list_targeting sandbox routing + sales_guaranteed task-completion path (mirror of #3989 / #3990) Storyboard-fixture-only fixes applied directly to the 3.0.x line, mirroring the same diffs filed against main in #3989 and #3990. Storyboard fixtures ship in the compliance bundle that goes out with each release, so 3.0.x consumers running the worked-example seller (hello_seller_adapter_guaranteed from @adcp/sdk 6.7+) hit both bugs today. inventory_list_targeting: the 5 account blocks were missing sandbox: true. The SDK runner's create_media_buy enricher inherits sandbox: true from the test-kit, but its get_media_buys enricher merges the storyboard's bare account block — different accountId → mediaBuyStore can't backfill targeting_overlay → verify_create_persisted / verify_update_persisted fail. Setting sandbox: true on every account block keeps create and get on the same namespace. sales_guaranteed/create_media_buy: the step uses the spec-correct guaranteed-seller flow (A2A submitted-arm envelope; media_buy_id materializes on the task-completion artifact). The bare context_outputs path "media_buy_id" resolved against the immediate response, producing capture_path_not_resolvable. Changed to "task_completion.media_buy_id" so the runner polls tasks/get and captures the seller-issued id from the terminal artifact, per the runner contract introduced in adcp-client#1426. Empty changeset (non-protocol fixture-only changes); no version bump. Refs: - #3989 (main-side inventory_list_targeting fix) - #3990 (main-side sales_guaranteed fix) - adcontextprotocol/adcp-client#1487 (follow-up: align get_media_buys enricher account resolution) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 3.0.6 follow-up cherry-picks: task_completion. prefix docs + comply_test_controller deployment-scope clarification (#4002) * docs(storyboard-schema): document task_completion. prefix for context_outputs (#3955) When the immediate response is a non-terminal task envelope (status submitted/working/input-required), the runner polls tasks/get until terminal and resolves the suffix against the completion artifact's data. Required for captures like seller-assigned media_buy_id on IO-signing / async-signed HITL flows. Requires runner >= adcp-client v6.7; older runners treat the prefix as a literal key. Closes #3950. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(spec): comply_test_controller is deployment-scoped, not request-gated (#3992) * docs(spec): comply_test_controller is deployment-scoped, not request-gated Production deployments MUST NOT expose comply_test_controller on any surface: tools/list MUST omit the tool, get_adcp_capabilities MUST omit the compliance_testing block, dispatch MUST return unknown-tool. A production deployment that exposes the tool is non-conformant regardless of whether dispatch is gated. The canonical pattern is two deployments — one production with no controller wired, one sandbox/staging with the controller wired for all comers. Per-principal projection on a single deployment remains permitted as an implementation pattern, not the canonical model. FORBIDDEN is reserved for the in-sandbox case where params reference a non-sandbox account; live-mode probes get the transport's standard unknown-tool error so the response is byte-identical to a seller that does not implement the tool. Closes #3986. Verifier-provisioning question moves to #3991 (Socket Mode conformance client). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(spec): address expert review on comply_test_controller scoping - Pair tools/list (MCP) and skills[] (A2A) symmetrically across the rule; agent-card discovery is cache-friendly so the leak surface is at least as bad on A2A - Replace "byte-identical" with "indistinguishable from the same-transport response of a seller that does not implement the tool" — JSON-RPC -32601 and A2A unknown-skill aren't byte-identical to each other - Strengthen the per-principal MAY carve-out: all three surfaces (tools/list, capability block, dispatch) MUST be projected consistently, and live-mode probes on a mixed deployment must dispatch to unknown-tool not FORBIDDEN (otherwise the side channel reopens) - Strengthen storyboard-runner SHOULD → MUST and add symmetric capability-block check - Reorder Sandbox gating: rule → canonical pattern → permitted alternative → FORBIDDEN reservation → ops/runner notes - Reconcile the stale :::note in get_adcp_capabilities.mdx that still described request-time gating - Update implementation-guidance line so it doesn't undercut the new rule Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Version Packages (#3933) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(release-notes): add 3.0.6 section to /docs/reference/release-notes * changesets: add empty changeset for forward-merge 3.0.x → main --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: aao-release-bot[bot] <280565558+aao-release-bot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Brian O'Kelley <bokelley@gmail.com>
Summary
Partial fix for #3145. Six `static/schemas/source/error-details/*.json` files carried SCREAMING_SNAKE titles (`ACCOUNT_SETUP_REQUIRED Details`, etc.) that propagate awkwardly through `json-schema-to-typescript` into `@adcp/client`'s public type surface — names like `RATE_LIMITEDDetails_ScopeValues` read as typos to every consumer.
Changes
account-setup-required.jsonACCOUNT_SETUP_REQUIRED DetailsAccountSetupRequiredDetailsaudience-too-small.jsonAUDIENCE_TOO_SMALL DetailsAudienceTooSmallDetailsbudget-too-low.jsonBUDGET_TOO_LOW DetailsBudgetTooLowDetailsconflict.jsonCONFLICT DetailsConflictDetailscreative-rejected.jsonCREATIVE_REJECTED DetailsCreativeRejectedDetailspolicy-violation.jsonPOLICY_VIOLATION DetailsPolicyViolationDetailsrate-limited.jsonalready had PascalCase (`Rate Limited Details`); `vendor-error-codes.json` already had `Vendor Error Code Registry`. No change to either.Wire-format unchanged: `$id` values stay kebab-case (`/schemas/error-details/account-setup-required.json`). Only the `title` field is touched, which controls codegen output. No producer/consumer needs to change anything.
Why `--empty` changeset
Title-only changes have no schema-shape impact and don't affect the wire. Counts as schema metadata cleanup.
What this PR doesn't fix
#3145's other half — `Foo1`-suffixed enum dupes (`AgeVerificationMethod1`, `BriefAsset1`, `VASTAsset1`, `DAASTAsset1`, `CatalogAsset1`) — is downstream codegen behavior in `json-schema-to-typescript` reaching the same enum through multiple parent paths. The shared `$ref` is already in place upstream (verified — both `targeting.json` and `get-adcp-capabilities-response.json` reach `/schemas/enums/age-verification-method.json` via `$ref` to the same `$id`), so the dedup needs SDK-side post-process renaming with one-minor-version aliases. Tracked at adcp-client#942 — out of scope for this PR.
Test plan
npm run test:schemas— 7/7 passingnpm run test:json-schema— 255/255 passingnode scripts/build-compliance.cjs— clean🤖 Generated with Claude Code