Skip to content

feat!: cut 8.0-beta line — flip ADCP_VERSION to 3.1.0-beta.2 (foundation)#1902

Merged
bokelley merged 11 commits into
mainfrom
bokelley/cut-8-0-beta
May 22, 2026
Merged

feat!: cut 8.0-beta line — flip ADCP_VERSION to 3.1.0-beta.2 (foundation)#1902
bokelley merged 11 commits into
mainfrom
bokelley/cut-8-0-beta

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Draft. Foundation for the 8.0-beta line per the v8.0-beta plan doc. CI will be red on 17 structural compile errors that map to separate follow-up PRs — listed below.

What's in the foundation

  • Enters changesets pre-mode with tag `beta`. Subsequent releases publish as `8.0.0-beta.N` under the `@beta` npm dist-tag.
  • `ADCP_VERSION` file: `3.0.12` → `3.1.0-beta.2`. `src/lib/version.ts` regenerated via `sync-version --force`.
  • `scripts/sync-version.ts` extended: `buildCompatibleVersions()` now accepts `3.1.0-beta.x` pins and retains 3.0.x GA compat through `LAST_3_0_GA_PATCH = 12` so an 8.0-beta SDK still talks to 3.0-pinned sellers (spec's open-enum wire compat).
  • Synced `schemas/cache/3.1.0-beta.2` as primary (cosign verified).
  • Regenerated types: `tools`, `schemas`, `core`, `manifest` (80 codes, 61 tools, 20 specialisms), `enums`, `entity-hydration` map.
  • `FORWARD_COMPAT_ERROR_CODES` emptied — `AUTH_MISSING`, `AUTH_INVALID`, `AGENT_SUSPENDED`, `AGENT_BLOCKED` are now in the primary manifest-driven `ErrorCodeValues`. The compile-time disjointness assertion would fail if a code returned to the overlay after manifest absorption.
  • `BuyerRetryPolicy.DEFAULT_CODE_POLICY` gained 31 entries for new 3.1 codes (provenance, billing, format-projection, pixel-tracker, scope/permission, etc.).
  • Mechanical `undefined`-guards in `responses.ts`, `signals.ts`, `test-controller-bridge.ts`, `test-controller.ts`.

What's red (17 structural compile errors, separate PRs)

Each maps to a per-feature spec migration:

  1. `OutcomeMeasurement` export removed — 2 sites in `index.ts` and `compat.ts`. Needs investigation of what replaced it in 3.1.0-beta.2.
  2. Brand object lost `categories` field — 7 sites (`account.ts` × 5, `responses.ts` × 2). Spec migration; need to locate where categories moved.
  3. `AssetVariant` is now a union with array — 3 sites in `manifest-helpers.ts`. Need to handle the new union shape.
  4. `creative-asset` shape changed — 4 sites in `preview-utils.ts` (`format_id`, `manifest` accesses). Spec migration.
  5. `ControllerScenario` exhaustiveness guard fails — `test-controller.ts:210`. 3.1.0-beta.2 added new scenarios; need to add them to `CONTROLLER_SCENARIOS`.

Plus from the beta.2 release notes, not yet implemented:

  1. Envelope `status` REQUIRED — spec explicitly calls our auto-registered `get_adcp_capabilities` handler out as the gap. Small focused fix in a follow-up.
  2. Drop legacy `mirror.adcontextprotocol.org` from `DEFAULT_MIRROR_HOSTS` — text-only spec change.

Why merge this draft now

This is the foundation. Every `needs:adcp-3.1` queue item now becomes a per-feature PR against this branch line, exactly as the v8.0-beta plan predicted. Without this foundation merged, every follow-up PR carries the pin-flip + type regen as duplicated work.

Recommended sequencing:

  1. Merge this foundation (mark ready when reviewers OK the strategy + breaks list)
  2. Each structural break gets its own PR (small, focused, reviewable)
  3. `changeset version` periodically to cut `8.0.0-beta.N` releases
  4. When upstream AdCP 3.1 goes GA: `changeset pre exit` + cut `8.0.0`

Verification

  • `sync-version --force` succeeded; `COMPATIBLE_ADCP_VERSIONS` includes 3.0.0–3.0.12, all 3.1.0-beta.N, plus the new pin.
  • `generate-types` succeeded against beta.2 schemas (80 codes, 61 tools, 20 specialisms).
  • tsc on tsconfig.lib.json: 30 errors → 17 errors after mechanical sweep. Remaining 17 are the structural items above.
  • prettier --check clean on all touched files.

References

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

Foundation for the 8.0-beta line per docs/development/v8.0-beta-plan.md.
Enters changesets pre-mode with tag 'beta'; subsequent releases publish
as 8.0.0-beta.N under the @beta npm dist-tag until upstream AdCP 3.1
goes GA.

Changes:
- ADCP_VERSION file: 3.0.12 → 3.1.0-beta.2
- src/lib/version.ts regenerated via sync-version --force
- scripts/sync-version.ts: extended buildCompatibleVersions() to accept
  3.1.0-beta.x pins; retains 3.0.x GA compat through LAST_3_0_GA_PATCH=12
  so an 8.0-beta SDK still talks to a 3.0-pinned seller per spec's
  open-enum wire compat.
- schemas/cache/3.1.0-beta.2 synced as primary (cosign verified)
- Regenerated types: tools, schemas, core, manifest (80 codes, 61 tools,
  20 specialisms), enums, entity-hydration map
- FORWARD_COMPAT_ERROR_CODES emptied — AUTH_MISSING/AUTH_INVALID/
  AGENT_SUSPENDED/AGENT_BLOCKED are now in primary manifest-driven
  ErrorCodeValues. Compile-time disjointness assertion would fail if a
  code returned to the overlay after manifest absorption.
- BuyerRetryPolicy.DEFAULT_CODE_POLICY: added 31 entries for new 3.1
  codes (provenance, billing, format-projection, pixel-tracker,
  scope/permission, configuration, brand, idempotency-in-flight,
  credential-in-args, retention, etc.)
- Mechanical undefined-guards in responses.ts, signals.ts,
  test-controller-bridge.ts, test-controller.ts

What's red (separate follow-up PRs, listed in the changeset):
- OutcomeMeasurement export removed (replaced by ?)
- Brand object lost categories field (spec migration)
- AssetVariant became a union with array
- creative-asset shape changed (format_id/manifest)
- ControllerScenario gained new enum values; exhaustiveness guard fails
- Envelope status REQUIRED on auto-registered get_adcp_capabilities

These are all per-feature spec migrations that map to needs:adcp-3.1
queue items; will land as separate PRs against this branch line.
…gence

Addresses non-blocking review feedback on #1902:

- code-reviewer: 'capability' missing from RetryDecision.escalate.reason
  union; format-projection + pixel-tracker codes were routed to 'commercial'
  with a TODO comment.
- ad-tech-protocol-expert: divergence comment on the escalate union should
  enumerate the billing- and provenance-family escalations explicitly so
  future readers don't "fix" the apparent drift from spec recovery values.

Changes:
- Add 'capability' to the escalate.reason union with a comment listing the
  7 format-projection and pixel-tracker codes that route to it.
- Route the 7 codes from 'commercial' to 'capability'. Dashboards can now
  filter implementation-choice tickets from commercial-policy ones.
- Extend the JSDoc on the existing reasons to enumerate billing + provenance
  escalations and the new SCOPE_INSUFFICIENT / READ_ONLY_SCOPE / etc. routes.
- Note that FORMAT_DECLARATION_* codes are spec-advisory (emitted in
  errors[] on a 200-success), so the retry policy primarily fires when an
  adopter explicitly routes them; separate follow-up to revisit
  advisory-vs-error policy dispatch.
@bokelley

Copy link
Copy Markdown
Contributor Author

Expert review summary (all three: SHIP)

code-reviewer — strategy is sound, hand-written surface is small and defensible. Two non-blocking should-fixes both addressed in commit 44b4538:

  • Added 'capability' to RetryDecision.escalate.reason union; 7 format-projection/pixel-tracker codes routed from 'commercial''capability'.
  • Divergence-comment in the escalate union now enumerates billing + provenance + scope/permission families explicitly.

ad-tech-protocol-expert — wire-compat retention correct, overlay-empty rationale correct, retry-policy defaults mostly correct with two specific notes:

  • PROVENANCE_VERIFIER_NOT_ACCEPTED / PROVENANCE_CLAIM_CONTRADICTED: spec says correctable but we operator-grade override to commercial. Documented in the extended divergence comment (commit above).
  • FORMAT_DECLARATION_* and PIXEL_TRACKER_* codes are spec-advisory (200-success errors[]), not failures. Tracked separately as #1904 — start with JSDoc clarification, defer advisory action variant until adopters surface real cases.

code-reviewer on #1903 — also SHIP, no issues. Dual-name re-export pattern is correct.

CI status

Still 17 errors in this PR on its own (15 with #1903 stacked) — the 5 structural breaks listed in the PR body. Each will be its own focused follow-up PR. Foundation is ready for review; will mark "ready" once the remaining structural break PRs close out CI.

Follow-ups filed

  • #1904 — BuyerRetryPolicy advisory-vs-error policy dispatch (deferred design)

@bokelley

Copy link
Copy Markdown
Contributor Author

Acknowledged. Expert sign-offs recorded, CI state noted (17 errors → structural breaks tracked as follow-up PRs), #1904 filed for the advisory-vs-error dispatch question. Waiting on the structural break PRs to clear before the "ready" flip.


Generated by Claude Code

@bokelley

Copy link
Copy Markdown
Contributor Author

Fold suggestion from issue triage (#1904): before merging, consider adding JSDoc to the seven advisory-code entries in BuyerRetryPolicy.DEFAULT_CODE_POLICY (FORMAT_PROJECTION_FAILED, FORMAT_DECLARATION_DIVERGENT, FORMAT_DECLARATION_V1_AMBIGUOUS, FORMAT_CAPABILITY_UNRESOLVED, FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE, PIXEL_TRACKER_LOSSY_DOWNGRADE, PIXEL_TRACKER_UPGRADE_INFERRED).

The note to add: these entries fire only when an adopter explicitly routes an advisory code (emitted in errors[] on a 200-success response) through decideRetry(). The spec's intended path is that the buyer surfaces the advisory and continues without entering the retry surface. Zero behavior change; purely doc signal so adopters don't escalate-on-advisory by accident.

If scope expansion would slow review, a follow-up PR against bokelley/cut-8-0-beta is equally fine — tracked at #1904.


Triaged by Claude Code. Session: https://claude.ai/code/session_0137SDkRk3WqYttGTdKJztLQ


Generated by Claude Code

bokelley added a commit that referenced this pull request May 21, 2026
…CENARIOS

AdCP 3.1.0-beta.2 added query_upstream_traffic to
ListScenariosSuccess['scenarios'] (spec PR adcp#3816 landed). Promote
it from the open-extension literal-string path to a first-class
CONTROLLER_SCENARIOS member.

Resolves the test-controller.ts:210 exhaustiveness guard from the
#1902 foundation sweep.

Changes:
- CONTROLLER_SCENARIOS.QUERY_UPSTREAM_TRAFFIC added as first-class.
- SCENARIO_MAP extended with the queryUpstreamTraffic → QUERY_UPSTREAM_TRAFFIC
  mapping (auto-advertised via scenariosFromStore, the canonical typed
  path).
- Removed local QUERY_UPSTREAM_TRAFFIC_SCENARIO literal and the
  `as unknown as ComplyTestControllerResponse` cast at the dispatcher
  call site — UpstreamTrafficSuccess is now in the generated union.
- Exhaustive-scenario test extended with queryUpstreamTraffic store
  method so the CONTROLLER_SCENARIOS / SCENARIO_MAP coverage invariant
  holds. 114/114 affected tests pass.

Part of the #1902 8.0-beta sweep (2/5 structural breaks closed).
bokelley added a commit that referenced this pull request May 21, 2026
AdCP 3.1.0-beta.2 narrowed the governance_agents[] wire shape from
{url, categories?} to {url} only. Per-agent category signaling moved
out of band; the wire schema no longer carries categories.

Changes:
- src/lib/server/decisioning/account.ts: projectGovernanceAgent emits
  {url} only; syncGovernanceRowToWire's inline projection mirrors.
- src/lib/server/responses.ts: stripGovernanceAgentSecrets drops the
  categories preservation branch.
- test/lib/sync-governance-credential-strip.test.js: now asserts
  categories is stripped (defense-in-depth with the existing
  authentication.credentials strip). All 82 governance tests pass.
- src/lib/server/wire-spec-fields.generated.ts: regenerated against
  3.1.0-beta.2 schemas (autogenerated).

Adopter migration: the SDK no longer emits categories on
governance_agents[]. Reading the field off the wire returns undefined.
Switch to whatever out-of-band channel the seller now uses for
per-agent category metadata.

Part of the #1902 8.0-beta sweep (3/5 structural breaks closed).
bokelley added a commit that referenced this pull request May 21, 2026
AdCP 3.1.0-beta.2 changed product_card from creative-agent-rendered
({format_id, manifest}) to a self-contained visual card ({image, title,
description, price_label, cta_label}). The card IS the preview now;
no creative-agent round-trip is required.

batchPreviewProducts rewritten to extract product_card.image?.url
directly. The creativeAgentClient + options parameters are retained
for signature compatibility (renamed to _-prefixed unused args)
and marked @deprecated; planned removal in 8.0 final or 9.0.

format_card / batchPreviewFormats are unchanged — only product_card
had this spec migration in 3.1.0-beta.2.

Adopter migration: existing calls keep working (PreviewResult[] still
populated with previewUrl from the new inline image.url). Direct
product.product_card?.image?.url access is the new recommended path.

Part of the #1902 8.0-beta sweep (5/5 structural breaks closed — CI
should now be green on the foundation stack).
bokelley added 5 commits May 21, 2026 11:30
…recated (#1903)

AdCP 3.1.0-beta.2 renamed the OutcomeMeasurement interface to
OutcomeMeasurementDeprecated to signal the surface is on the 4.0
removal track. Update compat.ts and the index.ts re-export to point
at the new name, with the original name preserved as a re-export
alias so existing adopter imports keep working.

Part of the #1902 8.0-beta sweep (1/5 structural breaks).
…CENARIOS (#1905)

AdCP 3.1.0-beta.2 added query_upstream_traffic to
ListScenariosSuccess['scenarios'] (spec PR adcp#3816 landed). Promote
it from the open-extension literal-string path to a first-class
CONTROLLER_SCENARIOS member.

Resolves the test-controller.ts:210 exhaustiveness guard from the
#1902 foundation sweep.

Changes:
- CONTROLLER_SCENARIOS.QUERY_UPSTREAM_TRAFFIC added as first-class.
- SCENARIO_MAP extended with the queryUpstreamTraffic → QUERY_UPSTREAM_TRAFFIC
  mapping (auto-advertised via scenariosFromStore, the canonical typed
  path).
- Removed local QUERY_UPSTREAM_TRAFFIC_SCENARIO literal and the
  `as unknown as ComplyTestControllerResponse` cast at the dispatcher
  call site — UpstreamTrafficSuccess is now in the generated union.
- Exhaustive-scenario test extended with queryUpstreamTraffic store
  method so the CONTROLLER_SCENARIOS / SCENARIO_MAP coverage invariant
  holds. 114/114 affected tests pass.

Part of the #1902 8.0-beta sweep (2/5 structural breaks closed).
…on (#1906)

AdCP 3.1.0-beta.2 narrowed the governance_agents[] wire shape from
{url, categories?} to {url} only. Per-agent category signaling moved
out of band; the wire schema no longer carries categories.

Changes:
- src/lib/server/decisioning/account.ts: projectGovernanceAgent emits
  {url} only; syncGovernanceRowToWire's inline projection mirrors.
- src/lib/server/responses.ts: stripGovernanceAgentSecrets drops the
  categories preservation branch.
- test/lib/sync-governance-credential-strip.test.js: now asserts
  categories is stripped (defense-in-depth with the existing
  authentication.credentials strip). All 82 governance tests pass.
- src/lib/server/wire-spec-fields.generated.ts: regenerated against
  3.1.0-beta.2 schemas (autogenerated).

Adopter migration: the SDK no longer emits categories on
governance_agents[]. Reading the field off the wire returns undefined.
Switch to whatever out-of-band channel the seller now uses for
per-agent category metadata.

Part of the #1902 8.0-beta sweep (3/5 structural breaks closed).
… widening) (#1907)

AdCP 3.1.0-beta.2 widened each creative_manifest.assets[asset_id] slot
from AssetVariant to AssetVariant | AssetVariant[] so carousel cards,
responsive_creative headlines, etc. can carry multiple assets per slot.

Changes:
- getAsset / requireAsset: when the slot is an array, return the first
  element. Preserves pre-3.1 behavior for single-asset callers.
- New getAssetSlot(manifest, assetId, assetType): returns the full
  array (or single-element array if scalar), filtered by asset_type.
  Use for carousel / responsive_creative platforms.
- 7 new tests pinning array-unwrap behavior on getAsset, the new
  getAssetSlot helper, and asset_type filtering.

Resolves 3 of 3 AssetVariant compile errors in src/lib/server/decisioning/manifest-helpers.ts.

Part of the #1902 8.0-beta sweep (4/5 structural breaks closed).
…sweep — CI green!) (#1908)

* feat(preview-utils): adopt 3.1.0-beta.2 self-rendering product_card

AdCP 3.1.0-beta.2 changed product_card from creative-agent-rendered
({format_id, manifest}) to a self-contained visual card ({image, title,
description, price_label, cta_label}). The card IS the preview now;
no creative-agent round-trip is required.

batchPreviewProducts rewritten to extract product_card.image?.url
directly. The creativeAgentClient + options parameters are retained
for signature compatibility (renamed to _-prefixed unused args)
and marked @deprecated; planned removal in 8.0 final or 9.0.

format_card / batchPreviewFormats are unchanged — only product_card
had this spec migration in 3.1.0-beta.2.

Adopter migration: existing calls keep working (PreviewResult[] still
populated with previewUrl from the new inline image.url). Direct
product.product_card?.image?.url access is the new recommended path.

Part of the #1902 8.0-beta sweep (5/5 structural breaks closed — CI
should now be green on the foundation stack).

* Merge bokelley/cut-8-0-beta: resolve wire-spec-fields timestamp conflict
* array — every element must match `assetType`; mixed-type slots are
* the adopter's responsibility to discriminate per-element.
*
* Returns `undefined` if the slot is missing or empty. Returns `[]` if

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSDoc says "Returns undefined if the slot is missing or empty" but ![] is false in JS, so an empty array slot flows through to the filter and returns [], not undefined. either fix the doc to "returns undefined if the slot is missing; returns [] if the slot is empty or no elements match assetType", or add || arr.length === 0 to the !slot guard. behavior is probably fine since empty slots would be spec-invalid, just needs the docs to match the code.

Comment thread scripts/sync-version.ts
const major = Number(semverMatch[1]);
const minor = Number(semverMatch[2]);
const patch = Number(semverMatch[3]);
const prerelease = semverMatch[4];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heads up: updatePackageJsonVersion lower in this file uses the same adcpVersion.split('.').map(Number) pattern without the prerelease guard, so '0-beta' becomes NaN at index 2. current destructuring only pulls [newMajor, newMinor] so it doesn't blow up today, but worth a comment on that block noting only major/minor are used and the NaN at patch position is intentional — otherwise the next person who adds a newPatch destructure will be surprised.

bokelley added a commit that referenced this pull request May 22, 2026
… T> (#1924)

The Zod-from-TS post-processor annotates schemas that hit TypeScript's
.d.ts serialization limit (TS7056) so the compiler stops trying to
serialize the inferred shape. The previous bare z.ZodType annotation
made z.input<typeof X> resolve to `unknown` — breaking
AdcpToolMap[K]['params'] narrowing for any annotated request schema.

Changes:
- TS7056_SCHEMAS entries now carry an optional `tsType`. When present,
  the annotation uses the 2-type-param Zod v4 form
  `z.ZodType<T, T>` with `& Record<string, unknown>` widening to
  reflect runtime passthrough() semantics. z.input<...> resolves
  correctly; downstream destructures keep field types.
- Auto-inject `import type { ... } from './tools.generated'` for the
  typed annotations.
- Pre-emptively annotate 5 schemas that hit TS7056 on 3.1.0-beta.2
  (PreviewCreativeRequest, UpdateMediaBuyRequest/Response,
  BuildCreativeResponse, SyncEventSourcesResponse). Annotation is
  harmless on the current 3.0.12 pin.
- One-line cast at the withOptionalAccount(UpdateMediaBuyRequestSchema)
  call site so the helper's z.ZodObject<...> constraint is satisfied
  after annotation widening. Runtime shape unchanged.

Why pre-emptive: the 8.0-beta cut (#1902) needs this codegen behavior
to compile its dist/. Landing on main decouples it from the 8.0-beta
foundation stack.

Patch bump — purely codegen tooling; no wire change; no API change.
@github-actions github-actions Bot mentioned this pull request May 22, 2026
bokelley added 4 commits May 22, 2026 10:29
…al sweep)

Foundation rebased onto main. Picks up:
- Pre-mode entry (#1933) so foundation publishes under @beta
- Codegen TS7056 fix (#1924)
- Wholesale-feed-sync rename (#1932, replaces catalog-sync from earlier branch)
- 3.1.0-beta.3 schema bundle (was beta.2 on the foundation branch)

Resolutions:
- .changeset/pre.json: took main's (initialVersions=8.0.0)
- src/lib/version.ts: regenerated via sync-version --force
- src/lib/types/schemas.generated.ts: regenerated
- ADCP_VERSION: bumped 3.1.0-beta.2 → 3.1.0-beta.3

Demoted foundation changeset major → minor so the foundation merge
publishes as 8.1.0-beta.0 (stays on 8.x line). The 9.0 major bump can
come when v1 deprecation calendar tightens, not from this cut.

Beta.3 spec changes folded in (partial):
- RETENTION_EXPIRED removed from ErrorCodeValues; STALE_RESPONSE added.
  Updated DEFAULT_CODE_POLICY accordingly.
- Envelope `status` REQUIRED on all responses — added 'failed' to two
  synthetic-error envelopes in response-unwrapper.ts.
- ControllerError / SeedSuccess / wrapped store returns updated to
  carry envelope `status` (introduced wrapStoreSuccess helper in
  src/lib/server/test-controller.ts).
- Type guards in src/lib/testing/test-controller.ts widened to handle
  the new union shape with envelope intersection.

KNOWN ISSUE: ~45 more compile errors remain across decisioning/
adapter/middleware code from the envelope-status-required change. Each
is mechanical (add `status: 'completed'` to a success-shape return) but
the sweep is wider than this single merge commit. Pushed for visibility;
follow-up PRs will sweep the remainder.
Closes the remaining 45+ compile errors from the beta.2 → beta.3 jump.
The spec broadened the envelope-required-status change AND renamed
several decision-shape fields to free `status` for `TaskStatus` at the
envelope level.

Envelope status added to:
- src/lib/adapters/content-standards-adapter.ts
- src/lib/adapters/governance-adapter.ts
- src/lib/adapters/property-list-adapter.ts
- src/lib/adapters/si-session-manager.ts
- src/lib/core/GovernanceMiddleware.ts
- src/lib/server/create-adcp-server.ts
- src/lib/server/decisioning/account.ts
- src/lib/server/decisioning/list-helpers.ts
- src/lib/server/decisioning/runtime/from-platform.ts
- src/lib/server/governance.ts
- src/lib/server/responses.ts
- src/lib/server/test-controller-bridge.ts
- src/lib/core/GovernanceTypes.ts

Field renames absorbed:
- Governance decision `status: 'approved' | 'denied'` → `verdict`
- ReportPlanOutcomeResponse `status` → `outcome_state`
- Rights response `status: 'acquired'` → `rights_status`
- NotificationConfig[] intersection collision resolved at the two
  account.ts call sites

Subagent-executed mechanical sweep. Build:lib clean (0 errors).
…SE policy

Closes the BLOCK items flagged by code-reviewer + ad-tech-protocol-expert
on the post-rebase foundation:

Governance verdict rename (`CheckGovernanceResponse.status` → `verdict`):
- src/lib/testing/scenarios/governance.ts: 19 sites across 5 scenarios
  (initial check, over-budget, geo, conditions+recheck, delivery+drift).
  Renamed local `const status = data.status` → `const verdict = data.verdict`
  and threaded through step.details log strings ("verdict=approved").
  Plan-sync status sites (867/872/883) left untouched — different field.
- src/lib/testing/storyboard/context.ts:261: extractor `d?.status` → `d?.verdict`.
- src/lib/testing/stubs/governance-agent-stub.ts:288: stub emits `verdict: 'approved'`.

Outcome rename (`ReportPlanOutcomeResponse.status` → `outcome_state`):
- src/lib/testing/storyboard/context.ts:269: extractor → `d?.outcome_state`.
- src/lib/testing/stubs/governance-agent-stub.ts:315: stub emits `outcome_state: 'accepted'`.

Rights rename (`AcquireRights*.status: 'acquired'` → `rights_status`):
- src/lib/server/responses.ts:616 JSDoc autocomplete hint.
- src/lib/server/decisioning/specialisms/brand-rights.ts:85/91/99 JSDoc.

STALE_RESPONSE retry-policy correction:
- src/lib/utils/buyer-retry-policy.ts: was {action: 'mutate-and-retry',
  reason: 'state'} (wrong — implies the payload should be re-fetched).
  Now {action: 'escalate', escalateReason: 'terminal'}. Spec semantics:
  STALE_RESPONSE is a non-fatal advisory paired with a populated success
  payload — consume the payload, surface the advisory, do not retry.
- RetryDecision.escalate.reason JSDoc updated to enumerate STALE_RESPONSE
  alongside CREDENTIAL_IN_ARGS in the 'terminal' branch.

Symmetric pattern with the witness-not-translator memory: stub and
consumer must speak the same wire vocabulary or scenarios silently
pass by reading undefined on both sides.

Build:lib clean (0 errors).
Addresses ad-tech-protocol-expert's must-fix on PR #1902.

AdCP 3.1.0-beta.2 made envelope `status` REQUIRED. A 3.1-pinned SDK
acting as a receiver against a 3.0.12 seller that omits envelope
`status` would fail strict validation on every response —
COMPATIBLE_ADCP_VERSIONS enumeration alone doesn't relax the
validators.

New helper: src/lib/utils/envelope-status-compat.ts
- `injectLegacyEnvelopeStatus(response)` synthesizes envelope `status`
  ONLY for responses that declare themselves 3.0.x (adcp_version
  starting with "3.0", or adcp_major_version: 3 with no adcp_version,
  or no version fields at all).
- `completed` when no top-level errors[] present, `failed` otherwise.
- Never overwrites an existing truthy `status`.
- 3.1+ responses that omit `status` still fail strict validation —
  the leniency is back-compat affordance, not permanent loosening.

Wired into 5 validation paths:
- response-unwrapper.ts:165 — unwrapProtocolResponse main path
- response-unwrapper.ts:566 — isAdcpSuccess symmetric path
- validation/schema-validator.ts:693 — Ajv validateResponse
  (applied BEFORE selectResponseVariant so synthesized `completed`
   doesn't misroute to the async-variant schema)
- core/ResponseValidator.ts:327 — runtime ResponseValidator
- testing/client.ts:672 + testing/storyboard/validations.ts:476 —
  storyboard/test-kit direct Zod paths

Tests: test/lib/envelope-status-compat.test.js — 17 cases covering
all required pins (3.0 → completed, 3.0+errors → failed, 3.1
unchanged, 3.1 with status unchanged, 3.0 with explicit status
unchanged, no version → legacy) plus adcp_major_version: 3 legacy,
foreign majors (4.0, 2.5), mutation-safety, and null/non-object
guard. All pass.

Subagent-executed sweep. Build:lib clean.
@bokelley bokelley marked this pull request as ready for review May 22, 2026 15:28
@bokelley bokelley merged commit fa99cfc into main May 22, 2026
9 of 11 checks passed
@bokelley bokelley deleted the bokelley/cut-8-0-beta branch May 22, 2026 15:28
bokelley added a commit that referenced this pull request May 22, 2026
…r.ts (#1934)

Release workflow on #1902 failed for two reasons:

1. Computed version was 9.0.0-beta.0 (next major bump from 8.0.0 in
   pre-mode). The 5 structural-break changesets from the 8.0-beta sweep
   (OutcomeMeasurement, ControllerScenario, governance categories,
   AssetVariant, product_card) were still `major` — only the foundation
   changeset got demoted before #1902 merged. Demote all 5 so the next
   release computes 8.1.0-beta.0 (stays on the 8.x line, per intent).

2. prettier --check fail on src/lib/server/test-controller.ts during
   the release commit's pre-push hook. Pure formatting drift from the
   wrapStoreSuccess sweep — `npm run format` resolves cleanly.

Next release workflow run after this merges should produce a Release
PR titled "chore: release package (beta)" cutting 8.1.0-beta.0 under
@beta dist-tag.

@aao-ipr-bot aao-ipr-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Follow-ups noted below. The pre-mode foundation pattern is the right shape -- pin flip, type regen, overlay drain, and per-feature breaks folded into the cut as discrete changesets gives reviewers a real surface to read instead of one mega-diff.

Things I checked

  • Largest-file rule. Read every non-generated TS file with >50 net lines: envelope-status-compat.ts (new, 90L), buyer-retry-policy.ts (+89), forward-compat-error-codes.ts (rewritten to empty overlay), preview-utils.ts (rewrite, -112 net), response-unwrapper.ts (+17), responses.ts (+20 net), test-controller.ts (+17 net), manifest-helpers.ts (+31), compat.ts (+11), scripts/sync-version.ts (+28). Generated files skipped.
  • BuyerRetryPolicy grading vs spec recovery class. All 31 new entries trace correctly. CREDENTIAL_IN_ARGS: terminal (buyer-retry-policy.ts:322) -- retrying would re-leak. IDEMPOTENCY_EXPIRED: idempotency_check_required (:196) -- natural-key check before fresh mint. IDEMPOTENCY_IN_FLIGHT: retry with same key (:258) -- safe; seller replay window dedupes. PROVENANCE_VERIFIER_NOT_ACCEPTED and _CLAIM_CONTRADICTED route to commercial (:288-289); other provenance codes route to mutate-and-retry/validation. STALE_RESPONSE: terminal (:327) with the consume-payload, surface-advisory semantic. New capability reason on the escalate union (:76) cleanly separates implementation-choice tickets from commercial.
  • stripGovernanceAgentSecrets defense-in-depth. Both v5 (responses.ts:768-784 -- explicit url-only projection) and v6 (decisioning/account.ts via projectGovernanceAgent) project at the wire-emit seam. Closes the as-any smuggling path even for adopters writing raw JS. authentication.credentials strip on notification_configs (responses.ts:111-133) covered by existing tests at four boundaries.
  • SAFE_VERSION regex. The anchor pattern at scripts/sync-version.ts:67 has no m flag, so newlines cannot satisfy the end anchor. Newlines, NUL, quotes, backticks, backslashes, underscore, slash all rejected by the class. Template-injection-into-generated-version.ts sealed. MAX_PATCH_ENUMERATION = 500 defensive cap (:120) and LAST_3_0_GA_PATCH = 12 (:132) at the right shape.
  • FORWARD_COMPAT_ERROR_CODES empty-overlay disjointness. as const satisfies on an empty object compiles. ForwardCompatErrorCode resolves to never; the compile-time check at error-codes.ts would fail closed if a code returned post-absorption.
  • AssetVariant slot widening. getAsset and requireAsset unwrap single-element arrays (manifest-helpers.ts:48-51, 99-100); new getAssetSlot returns the filtered array. Pre-3.1 single-asset callers keep working without code changes.
  • product_card self-rendering migration scope. batchPreviewProducts rewrite (preview-utils.ts:131-143) only touches product_card; format_card and batchPreviewFormats correctly left alone -- only the product surface changed in beta.2.
  • OutcomeMeasurement rename. Type alias to OutcomeMeasurementDeprecated (compat.ts:15, 22) plus index.ts re-export -- wire shape unchanged, adopter imports preserved, deprecation visible at the new identifier.
  • No transport reimplementation. New code is post-unwrap object manipulation. Generated agent surface at agents/index.generated.ts:155-178 delegates to ProtocolClient.callTool plus unwrapProtocolResponse. Official @a2a-js/sdk and @modelcontextprotocol/sdk paths intact.

Follow-ups (non-blocking -- file as issues)

  1. L1 MCP error branch missing envelope status. src/lib/utils/response-unwrapper.ts:315-323 synthesizes an adcp_error-shaped envelope with no top-level status, while the sibling synthetic envelope at :371 correctly carries status: 'failed' as const. Both are 3.1-strict-validator surfaces. Add status: 'failed' as const to the L1 raw-text fallback to match.
  2. wrapStoreSuccess 'status' in result bypass. src/lib/server/test-controller.ts:614-617 -- the in-operator returns true when an adopter explicitly sets status: undefined, short-circuiting injection and producing an envelope with undefined status that fails wire validation. Match the truthiness check at envelope-status-compat.ts:74 (response.status truthy, not just in).
  3. Changeset coordination produces 9.0.0-beta.0, not 8.x-beta.N. Six changesets in this PR: cut-8-0-beta.md is minor (intentionally demoted in the merge commit), the other five (asset-variant-array-slots, governance-agents-categories-removed, outcome-measurement-rename, product-card-self-rendering, promote-query-upstream-traffic) are major. Under changesets pre-mode the highest bump wins per release, so the next changeset version will compute 9.0.0-beta.0 from the current 8.0.0 initialVersion -- undoing the merge-commit-documented "stays on the 8.x line" intent. Either demote the five follow-ups to minor (intra-beta surface churn, not first-publish-of-major bumps) or update the foundation plan plus the cut-8-0-beta.md body to acknowledge the line is actually 9.0-beta.
  4. Envelope shim no-version-fields-to-legacy branch. src/lib/utils/envelope-status-compat.ts:50-51 grants status synthesis to ANY version-less payload. A buggy or compromised 3.1 peer that omits both adcp_version and adcp_major_version gets the leniency and passes strict 3.1 envelope validation. Tighten to strict-reject once 3.1 GA ships (or now -- the SDK's own server stamps adcp_version, and a peer that does not is already drifting from spec). Witness-not-translator points the same direction.
  5. Shim is blind to legacy task_status and response_status. Per the spec note at tools.generated.ts:1151 ("Agents MUST NOT emit the legacy task_status or response_status fields alongside this field"), a 3.0.x payload carrying task_status: 'submitted' with no top-level status gets stamped completed by the synthesizer. Extend isLegacy30xPayload and injectLegacyEnvelopeStatus to read the legacy fields before defaulting to completed or failed.
  6. Changeset body cites 3.1.0-beta.2; actual pin is 3.1.0-beta.3. .changeset/cut-8-0-beta.md:5,7,17 will land in CHANGELOG.md verbatim once the Release PR runs. Update before cut.
  7. schema-validator.ts v2-vs-v3 leakage. validation/schema-validator.ts:695-711 runs injectLegacyEnvelopeStatus before the isV2Bundle gate, so a v2.x seller that omits both adcp_version and adcp_major_version lands in the no-version-fields-to-legacy branch and gets status: 'completed' injected. Mostly harmless because v2 schemas are additionalProperties: true, but cross-version leakage is a code smell. Gate on parseAdcpMajorVersion(version) === 3 || version == null.

Minor nits (non-blocking)

  1. testing/client.ts:676-679 does not strip _message pre-inject. Other call sites do (response-unwrapper.ts:162-166, storyboard/validations.ts:471-482). Low-risk because raw wire data here typically does not carry _message, but the inconsistency is worth a one-line comment or a symmetric strip.
  2. A2A extraction-layer divergence. core/ResponseValidator.ts:301-309 reads artifacts[0].parts[0].data (FIRST artifact, FIRST data part); the canonical unwrapper at response-unwrapper.ts:508-520 reads LAST of both (conversational-protocol semantics). Pre-existed this PR; the new shim amplifies the divergence -- missing status may be caught on one extractor but not the other. Worth a separate cleanup PR.
  3. responses.ts:178 capabilitiesResponse spread order. Spreading data first and status: 'completed' last overwrites any adopter-set status silently. Today GetAdCPCapabilitiesResponse does not carry status on its typed surface so this is theoretical, but spreading status first is the safer default since the auto-registered handler at create-adcp-server.ts:5048 already stamps it upstream -- making the in-builder stamp a backstop, not the source of truth.
  4. buyer-retry-policy.ts:77 doc-comment lists RETENTION_EXPIRED as a terminal example, but RETENTION_EXPIRED is not in DEFAULT_CODE_POLICY and the ErrorCode manifest dropped it in beta.3 (STALE_RESPONSE replaced it). Either re-add the code if the spec still emits it, or drop the reference so a copy-paste into an override does not land on a stale code.
  5. enforceMapCap is exported but not enforced. The TestControllerStore interface JSDoc names it in the example but does not require it. An adopter who forgets the cap in their custom forceAccountStatus is vulnerable to a sandbox caller looping over fresh IDs. Consider a BoundedSessionMap wrapper class for adopters, or strengthen the per-method JSDoc on the interface.
  6. envelope-status-compat.ts:67 generic constraint lies on the null edge. <T extends Record<string, unknown>> plus the return response on non-object input means injectLegacyEnvelopeStatus(null) returns null typed as T. Runtime fine; type is sloppy. Widen the signature to T | null | undefined or add a runtime-guard JSDoc note.

Approving on the strength of the V2/V3 ergonomic strategy plus the clean overlay-drain pattern. The forward-compat overlay plus manifest absorption plus compile-time disjointness check is the textbook ratchet -- that part is worth borrowing into adjacent SDKs.

bokelley added a commit that referenced this pull request May 24, 2026
Both spotted by @nastassiafulconis during the 8.0-beta foundation cut
and acknowledged at the time; landing now as a small cleanup.

- `getAssetSlot` (`src/lib/server/decisioning/manifest-helpers.ts`):
  JSDoc said "Returns undefined if the slot is missing or empty" but
  `![]` is falsy, so an empty array slot passes the guard and returns
  `[]` from the filter. Behavior is correct — returning `[]` for an
  empty slot is consistent with the filter contract and empty arrays
  are spec-invalid per `minItems: 1` anyway. Updated the JSDoc.

- `updatePackageJsonVersion` (`scripts/sync-version.ts`):
  `adcpVersion.split('.').map(Number)` yields NaN at index 2+ for
  prereleases like `'3.1.0-beta.3'`. Today only [major, minor] are
  destructured so the NaN is intentionally discarded — comment
  added so the next person to add `newPatch` doesn't get bitten.

Empty changeset — docs/comments only, no behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants