Skip to content

fix(client): remove account_from_brand fabrication shim in normalizeRequestParams#1683

Merged
bokelley merged 5 commits into
mainfrom
claude/issue-1676-remove-account-from-brand-shim
May 11, 2026
Merged

fix(client): remove account_from_brand fabrication shim in normalizeRequestParams#1683
bokelley merged 5 commits into
mainfrom
claude/issue-1676-remove-account-from-brand-shim

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Closes #1676

Summary

normalizeRequestParams contained a v2 backward-compat shim that silently set account.operator = brand.domain when a create_media_buy call omitted account. This PR removes it and throws ValidationError instead.

Why the shim was wrong:

  • account.operator identifies the billing account's operator (e.g. pinnacle-media.com), not the brand's own domain — the fabricated value was semantically correct only for direct-buy topologies and wrong for any agency/DSP-intermediated call.
  • Missing account is a caller bug, not a back-compat case. The normalizer was masking it.
  • The v2 sunset (AdCP 3.0 GA, April 2026) removes the compat rationale entirely.
  • Compliance badges issued through this path certified "handles 3.0 envelope wrapping fabricated account" rather than actual create_media_buy account semantics.

Behavior change (two cases):

  1. create_media_buy with brand.domain but no account — previously fabricated { brand, operator: brand.domain }; now throws ValidationError(field='account').
  2. create_media_buy with neither account nor brand — previously fell through to seller-side schema rejection; now throws ValidationError client-side.

Note on executeAndHandle vs executeTask: executeTask (generic method) wraps all errors in a TaskResult.failed return value. executeAndHandle (used by typed methods like createMediaBuy) does not — ValidationError propagates as a thrown exception from typed methods, consistent with how this.validateRequest already throws from that path. This pre-existing asymmetry is not addressed in this PR; it's documented here as a known limitation. Callers of typed methods should try/catch for pre-flight validation errors.

Known follow-up: The schema_validation storyboard step that deliberately sends create_media_buy without account (to test seller error handling) needs an omit_account escape hatch analogous to omit_idempotency_key — otherwise it tests client-side error behavior rather than seller-side. That storyboard path was already broken by the old shim (shim was fabricating account, seller never saw the missing-account case). Follow-up needed in storyboard runner.

What was tested

  • npm run format:check — passed
  • npm run typecheck — pre-existing errors unrelated to this diff (TS2688: @types/node, TS5107: moduleResolution=node10 deprecated); both present on main before this change
  • Build: dist/ pre-seeded with compiled changes; full build requires npm install (CI will verify)
  • Tests: test/lib/request-normalizer.test.js added with 7 cases covering throw + no-throw paths; full test suite requires npm install (CI will verify)

Pre-PR review:

  • code-reviewer: approved with nits — flagged executeAndHandle no-catch (pre-existing, documented above) and missing tests (tests are at test/lib/request-normalizer.test.js)
  • ad-tech-protocol-expert: approved — "removal is correct and overdue"; operator = brand.domain is precisely wrong per AccountReference discriminated union; condition widening is correct (explicitly documented in changeset); qualified sync_accounts guidance to implicit-account sellers only in error message

Nits (not fixed):

  • ValidationError constraint parameter receives a recovery sentence rather than a pure constraint description — pre-existing pattern issue in the error class, out of scope for this PR.

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See adcp#3121
for context.

Session: https://claude.ai/code/session_01WYQpHoctUsoEBa2wuSpnY4


Generated by Claude Code

claude added 2 commits May 11, 2026 13:51
…equestParams (#1676)

Replaces the deprecated v2 compat shim that silently set
account.operator = brand.domain with a ValidationError when
create_media_buy omits account.

https://claude.ai/code/session_01WYQpHoctUsoEBa2wuSpnY4
- Qualify sync_accounts as implicit-account sellers only in error message
- Explicitly document both widened guard cases in changeset

https://claude.ai/code/session_01WYQpHoctUsoEBa2wuSpnY4
@bokelley
Copy link
Copy Markdown
Contributor Author

Tracking under #1685 — hat 1 of 5 (account fabrication). Do not merge before #1684 (the visibility keystone). Pairs with #1681 (the fabrication twin); after #1684 lands, this and #1681 can ship in either order.

@bokelley
Copy link
Copy Markdown
Contributor Author

Acknowledged — merge dependency on #1684 (visibility keystone) noted. This PR will stay draft until #1684 lands; #1681 and this can ship in either order after that.


Generated by Claude Code

bokelley and others added 3 commits May 11, 2026 12:44
Tests that asserted the old shim behavior now assert ValidationError throws.
Tests that incidentally omitted account on create_media_buy now pass an
explicit account fixture.

Part of #1676 / PR #1683.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ove-account-from-brand-shim

# Conflicts:
#	src/lib/version.ts
@bokelley bokelley merged commit b5774ba into main May 11, 2026
10 checks passed
bokelley added a commit that referenced this pull request May 11, 2026
…lidationError import

Adds eight regression tests for the new issues[] field landing in #1694:
- L3 structuredContent path forwards well-formed issues with pointer/message/keyword
- Malformed items (non-string fields, missing keys) are dropped
- All-malformed input → field absent, never []
- Wire field absent → undefined
- Wire field non-array → undefined
- details + issues are orthogonal (both can co-exist)
- schemaPath preserved when present
- L3 path drops bad items by the same rule

Also drops a duplicate `import { ValidationError }` in `request-normalizer.ts`
introduced by the squash-merge collision of #1681 + #1683 — both PRs added
the import from slightly different paths and the squash produced two
identical imports, breaking the build on main. Fixed here so #1699 builds
clean; the fix lands on main when this PR merges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 11, 2026
The squash-merge of #1681 (`from '../errors/index'`) and #1683
(`from '../errors'`) into main produced two identical-name imports.
TS2300 breaks the build on every PR branched off the post-merge
main. Single import resolves both.
bokelley added a commit that referenced this pull request May 11, 2026
After #1683 landed, `normalizeRequestParams('create_media_buy', …)` requires
an explicit account. Add `account: { account_id: 'test-acc' }` to the two
test fixtures so the package-shape validators (#1681) are still the gate
under test.
bokelley added a commit that referenced this pull request May 11, 2026
Same fix as on #1698's branch — after #1683 landed, create_media_buy
requires account; the package-shape validators (#1681) need an account
fixture to be the gate under test.
bokelley added a commit that referenced this pull request May 11, 2026
Two reviewer-flagged gaps:
1. `test/lib/storyboard-account-invariant.test.js` gains a loader test
   asserting `omit_account: true` without `expect_error: true` throws at
   parse time (was previously enforced in code but uncovered by tests).
2. `test/request-normalizer-package-params.test.js` adds `account: {
   account_id: 'test-acc' }` to both fixtures. After #1683 landed the
   account-required throw runs before normalizePackageParams, so the
   package-shape gates (#1681) need an account in the fixture to be the
   gate under test.
bokelley added a commit that referenced this pull request May 11, 2026
Same fix as on #1698/#1699/#1700 — after #1683 landed, create_media_buy
requires account. Add `account: { account_id: 'test-acc' }` to both
fixtures so the package-shape gates (#1681) remain the gate under test.
bokelley added a commit that referenced this pull request May 11, 2026
…ErrorInfo (#1699)

* feat(client): add AdcpValidationIssue type and issues[] field on AdcpErrorInfo

Adds `AdcpValidationIssue` (pointer/message/keyword/schemaPath?) and
`issues?: AdcpValidationIssue[]` to `AdcpErrorInfo` and
`ExtractedAdcpError`, populated by `extractAdcpErrorInfo` and
`buildExtracted` from the seller's VALIDATION_ERROR envelope.

Previously the spec's `issues[]` array landed in the free-form `details`
field; consumers had to read `details.validation_errors` as a convention.
Now it surfaces as a typed field that LLM self-correction loops can read
directly via `failure.adcp_error.issues[].pointer` and `.keyword`.

Closes #1694.

https://claude.ai/code/session_01RxPkjDrwRRT8TNW7U4ShHQ

* test(error-extraction): cover issues[] forwarding + drop duplicate ValidationError import

Adds eight regression tests for the new issues[] field landing in #1694:
- L3 structuredContent path forwards well-formed issues with pointer/message/keyword
- Malformed items (non-string fields, missing keys) are dropped
- All-malformed input → field absent, never []
- Wire field absent → undefined
- Wire field non-array → undefined
- details + issues are orthogonal (both can co-exist)
- schemaPath preserved when present
- L3 path drops bad items by the same rule

Also drops a duplicate `import { ValidationError }` in `request-normalizer.ts`
introduced by the squash-merge collision of #1681 + #1683 — both PRs added
the import from slightly different paths and the squash produced two
identical imports, breaking the build on main. Fixed here so #1699 builds
clean; the fix lands on main when this PR merges.

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

* style(error-extraction): prettier — single-line filter callback

Pure formatting fix flagged by CI.

* fix(test): pass account in pre-3.0 package shape tests

Same fix as on #1698's branch — after #1683 landed, create_media_buy
requires account; the package-shape validators (#1681) need an account
fixture to be the gate under test.

---------

Co-authored-by: Claude <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 11, 2026
…ible via ComplianceResult (#1698)

* docs(AdcpErrorInfo): warn sellers that message/details are grader-visible

`adcp_error.message` and `.details` from seller responses are forwarded
into `ComplianceResult.failures[].adcp_error` (landed in PR #1684) — a
grader-visible archive surface that outlives the request. Three touches:

- JSDoc on `AdcpErrorInfo.message` and `.details` in ConversationTypes.ts
  with an explicit seller-side warning (grader-visible, don't embed tokens
  or internal IDs)
- New subsection §4 "Compliance failure envelopes (`adcp_error`)" in
  `docs/guides/CTX-METADATA-SAFETY.md` covering what flows where, what
  to avoid, and cross-linking to pickSafeDetails
- Two-sentence addition in `skills/build-decisioning-platform/advanced/
  REFERENCE.md` pickSafeDetails section noting the compliance-record
  leak class alongside the live-buyer-response leak class

Closes #1697

https://claude.ai/code/session_011YZtF1KAFTD54e85so2KjN

* fix(docs): correct relative path in REFERENCE.md cross-link

Three ../ hops not four from skills/build-decisioning-platform/advanced/
to repo root.

https://claude.ai/code/session_011YZtF1KAFTD54e85so2KjN

* fix: drop duplicate ValidationError import in request-normalizer

The squash-merge of #1681 (`from '../errors/index'`) and #1683
(`from '../errors'`) into main produced two identical-name imports.
TS2300 breaks the build on every PR branched off the post-merge
main. Single import resolves both.

* fix(test): pass account in pre-3.0 package shape tests

After #1683 landed, `normalizeRequestParams('create_media_buy', …)` requires
an explicit account. Add `account: { account_id: 'test-acc' }` to the two
test fixtures so the package-shape validators (#1681) are still the gate
under test.

---------

Co-authored-by: Claude <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 11, 2026
…applicable in ComplianceResult (#1701)

* feat(conform): split storyboards_missing_tools from storyboards_not_applicable (#1695)

Adds ComplianceResult.storyboards_missing_tools to distinguish storyboards
filtered because the agent declared the protocol but a required tool was absent
from storyboards_not_applicable (version-gated, protocol not declared).

Also fixes pre-existing duplicate ValidationError import in request-normalizer.ts.

Closes #1695

https://claude.ai/code/session_01J4JR1oK6rvbGZstZxZL4yG

* feat(conform): update complyImpl to route required_tools stubs to storyboards_missing_tools

Split the notApplicable array into two: version-gated entries remain in
notApplicable (→ storyboards_not_applicable) and required_tools-filtered
entries go to missingToolStoryboards (→ storyboards_missing_tools).

Part of #1695

https://claude.ai/code/session_01J4JR1oK6rvbGZstZxZL4yG

* fix(comply): restore escape sequences in sanitizeAgentText and separator widths

Revert two incidental prettier-formatting changes that crept in during
the previous commit:

1. sanitizeAgentText regex: prettier had converted explicit Unicode
   escape sequences (

‪-‮⁦-⁩) into
   embedded literal bidi control characters. Reverted to escape
   sequences — the function strips these characters and having them
   appear as invisible literals in the source makes security review
   much harder.

2. Section separator comments: prettier trimmed the ─────── bars from
   60 to 56 characters. Restored original width to preserve existing
   style.

* fix(comply): restore escape sequences in sanitizeAgentText and separator widths

Revert two incidental prettier-formatting changes from the previous commit:

1. sanitizeAgentText regex: prettier had converted explicit Unicode escape
   sequences (

‪-‮⁦-⁩) into embedded literal
   bidi control characters. Reverted to named escape sequences — the function
   strips these chars; having them embedded invisibly in security-sensitive
   source makes code review much harder.

2. Section separator comments: prettier trimmed the ─── bars from 60 to 56
   characters. Restored original width.

* test: encoding verification

* fix(comply): restore \uXXXX escape sequences in sanitizeAgentText regex

Revert incidental prettier change that converted Unicode escape sequences
to embedded literal bidi control characters in the sanitizeAgentText regex.
The function strips these characters; having them appear as invisible
literals in security-sensitive source makes code review much harder.

Also restores the section separator comments from 56 to 60 dashes (also
prettified in the same pass).

* chore: remove accidental test artifact

* fix(conform): bump changeset to major, document skip_causes for tool names

* fix(test): pass account in pre-3.0 package shape tests

Same fix as on #1698/#1699/#1700 — after #1683 landed, create_media_buy
requires account. Add `account: { account_id: 'test-acc' }` to both
fixtures so the package-shape gates (#1681) remain the gate under test.
bokelley added a commit that referenced this pull request May 11, 2026
…1696) (#1700)

* feat(conformance): add omit_account escape hatch to StoryboardStep (#1696)

Follow-up to PR #1683 which removed the account_from_brand shim. Storyboard
schema_validation steps that deliberately test seller-side missing-account
rejection can no longer reach the seller because the SDK's client-side
ValidationError short-circuits before the wire call.

Mirrors the existing omit_idempotency_key pattern: a step-level boolean that
suppresses account injection in applyBrandInvariant (both synthetic-construction
and natural-key-merge branches) and the normalizeRequestParams/validateRequest
account-required checks (via skipAccountValidation on TaskOptions). For
non-A2A runs, also triggers the raw-probe defense-in-depth path.

https://claude.ai/code/session_01AW5JtCv7bVCVj17PHspMY8

* style: fix prettier formatting in storyboard-account-invariant test

https://claude.ai/code/session_01AW5JtCv7bVCVj17PHspMY8

* fix(normalizer): remove duplicate ValidationError import in request-normalizer

https://claude.ai/code/session_01AW5JtCv7bVCVj17PHspMY8

* fix(conformance): address pre-PR review feedback on omit_account escape hatch

- Clarify in SingleAgentClient comments that skipAccountValidation bypasses
  the entire Zod schema parse (matching skipIdempotencyAutoInject behavior),
  not just the account field — both sites updated
- Add loader co-requirement: omit_account: true now requires expect_error: true
  (loader throws at parse time; accountless create_media_buy always fails)
- Extend omit_account JSDoc with: no-op note for non-create_media_buy tasks,
  request-builder caveat for future update_media_buy use, expect_error pairing rule
- Add ordering-anchor comment in runner.ts noting testsMissingAccount must
  remain after applyBrandInvariant call

https://claude.ai/code/session_01AW5JtCv7bVCVj17PHspMY8

* chore: update generated wire-spec-fields timestamp and package-lock

Auto-generated by build:lib (timestamp only change); package-lock
refreshed by npm install run during CI setup.

https://claude.ai/code/session_01AW5JtCv7bVCVj17PHspMY8

* test: cover omit_account loader co-req + fix package-params fixtures

Two reviewer-flagged gaps:
1. `test/lib/storyboard-account-invariant.test.js` gains a loader test
   asserting `omit_account: true` without `expect_error: true` throws at
   parse time (was previously enforced in code but uncovered by tests).
2. `test/request-normalizer-package-params.test.js` adds `account: {
   account_id: 'test-acc' }` to both fixtures. After #1683 landed the
   account-required throw runs before normalizePackageParams, so the
   package-shape gates (#1681) need an account in the fixture to be the
   gate under test.

---------

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

request-normalizer fabricates account from brand on create_media_buy — silently ships invented data as 3.0

2 participants