Skip to content

feat(test-controller): promote query_upstream_traffic to first-class scenario (2/5 of #1902 sweep)#1905

Merged
bokelley merged 1 commit into
bokelley/cut-8-0-betafrom
bokelley/controller-scenarios-3-1
May 21, 2026
Merged

feat(test-controller): promote query_upstream_traffic to first-class scenario (2/5 of #1902 sweep)#1905
bokelley merged 1 commit into
bokelley/cut-8-0-betafrom
bokelley/controller-scenarios-3-1

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Stacked on #1902. Second of five structural-break follow-ups.

AdCP 3.1.0-beta.2 added `query_upstream_traffic` to `ListScenariosSuccess['scenarios']` (spec PR adcp#3816 landed). The SDK previously carried it as an open-extension literal because the schema cache predated the spec PR.

Changes

  • `CONTROLLER_SCENARIOS.QUERY_UPSTREAM_TRAFFIC = 'query_upstream_traffic'` — first-class constant.
  • `SCENARIO_MAP` extended with `queryUpstreamTraffic` → `QUERY_UPSTREAM_TRAFFIC`; auto-advertised via the canonical typed path (`scenariosFromStore`) rather than the open-extension fallback (`allScenariosFromStore`).
  • Dropped the local `QUERY_UPSTREAM_TRAFFIC_SCENARIO` literal and the `as unknown as ComplyTestControllerResponse` cast at the dispatcher call site — `UpstreamTrafficSuccess` is now in the generated `ComplyTestControllerResponse` union.
  • Exhaustive-scenario test fixture extended with the `queryUpstreamTraffic` store method so the existing `CONTROLLER_SCENARIOS / SCENARIO_MAP coverage` invariant holds.

Adopter migration

Purely additive. Adopters who implement the `queryUpstreamTraffic` store method get type-safe advertisement; existing code unchanged.

Verification

  • `tsc --noEmit` on `tsconfig.lib.json`: foundation had 17 → this PR drops it to 16. The test-controller exhaustiveness guard at `test-controller.ts:210` is gone.
  • 114/114 test-controller tests pass (one fixture extended).
  • prettier --check clean.

Remaining structural breaks (3 more PRs)

  • Brand `categories` field (7 sites)
  • `AssetVariant` union (3 sites)
  • `creative-asset.format_id/manifest` (4 sites)

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

…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 bokelley marked this pull request as ready for review May 21, 2026 15:32
@bokelley bokelley merged commit 5ba17d3 into bokelley/cut-8-0-beta May 21, 2026
5 checks passed
@bokelley bokelley deleted the bokelley/controller-scenarios-3-1 branch May 21, 2026 15:32

@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.

Clean promotion. Right shape — the open-extension literal goes away because the generated union now carries the branch, and the exhaustiveness fixture extends in lockstep.

Things I checked

  • CONTROLLER_SCENARIOS.QUERY_UPSTREAM_TRAFFIC added at test-controller.ts:165; SCENARIO_MAP extended at :560; dispatcher case at :1114 switched from the local literal to the const.
  • QUERY_UPSTREAM_TRAFFIC_SCENARIO private literal deleted; allScenariosFromStore no longer carries the manual push at :585.
  • as unknown as ComplyTestControllerResponse cast at :1123 gone — UpstreamTrafficSuccess (tools.generated.ts:20511) is in the ComplyTestControllerResponse union as of 3.1.0-beta.2.
  • Exhaustive-scenario fixture at test/server-test-controller.test.js:1352 extended; the Object.values(CONTROLLER_SCENARIOS) coverage invariant still holds.
  • wire-spec-fields.generated.ts regenerated against schemas/cache/3.1.0-beta.2/. Spot-checked: adcp_major_version removed across 30 requests, account added to AcquireRightsRequest + UpdateRightsRequest, include_window_breakdown + time_granularity added to GetMediaBuyDeliveryRequest — spec-aligned with the 8.0-beta cut.
  • Changeset present; pre-mode pre-release accumulates everything under 8.0.0-beta.N so the major tag is mechanically benign.

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

  • Orphan JSDoc block at test-controller.ts:531-537. The comment that used to document the deleted QUERY_UPSTREAM_TRAFFIC_SCENARIO literal now floats with no symbol beneath it. The parenthetical "(Empty at 3.1.0-beta.2 — query_upstream_traffic was promoted.)" is also wrong — FORCE_AUDIENCE_STATUS_SCENARIO and FORCE_CATALOG_ITEM_STATUS_SCENARIO at :547-548 still live in that bucket. Either delete the block or repoint it at the force-* scenarios.
  • Changeset undersells the wire-spec sweep. wire-spec-fields.generated.ts regeneration is an adopter-visible allowlist change — SDK validation now rejects adcp_major_version on every mutating request, accepts account on rights requests, etc. The changeset prose names only query_upstream_traffic, so CHANGELOG readers will miss the drift cleanup. Either fold a one-line mention into this changeset or split a separate changeset around the schema-cache bump.
  • Local UpstreamTrafficSuccessResponse diverges from spec by more than the JSDoc admits. tools.generated.ts:20530 declares since_timestamp: string (required); test-controller.ts:407 declares since_timestamp?: string (optional). recorded_calls is (RawAttestation | DigestAttestation)[] in the spec, ReadonlyArray<unknown> locally. The JSDoc at :387-389 — which this PR updated — says the local shape is "preserved for adopters mid-migration." Mid-migration to what? The generated type shipped. Worth either re-exporting UpstreamTrafficSuccess from tools.generated.ts or tightening the local shape before 8.0 GA — the fixture at test/server-test-controller.test.js:1353 omits since_timestamp entirely, which the local interface permits but the spec does not.

LGTM. Follow-ups noted below.

bokelley added a commit that referenced this pull request May 22, 2026
…ion) (#1902)

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

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.

* fix(retry-policy): add 'capability' escalate reason + enumerate divergence

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.

* fix(types): handle OutcomeMeasurement rename to OutcomeMeasurementDeprecated (#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).

* feat(test-controller): promote query_upstream_traffic to CONTROLLER_SCENARIOS (#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).

* fix(governance): drop categories from governance_agents[] wire emission (#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).

* feat(manifest-helpers): handle AssetVariant array slots (3.1.0-beta.2 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).

* feat(preview-utils): adopt self-rendering product_card (5/5 of #1902 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

* fix(beta.3): envelope status + governance/rights field renames sweep

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).

* fix(beta.3): complete governance/outcome/rights rename + STALE_RESPONSE 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).

* feat(compat): 3.0.x receiver-side leniency for envelope `status`

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.
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.

1 participant