Skip to content

fix(accounts): advertise account.requests and serve account_requests on the platform path - #3586

Merged
bokelley merged 4 commits into
mainfrom
bokelley/account-requests-seller
Nov 2, 2026
Merged

bokelley merged 4 commits into
mainfrom
bokelley/account-requests-seller

Conversation

@bokelley

@bokelley bokelley commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3575.

A seller that registers request_account through createAdcpServerFromPlatform(platform, { accounts: { requestAccount } }) served the tool but did not advertise it, so a TS buyer's client.accounts.ensure fell back to sync_accounts. The seller also had no way to return account_requests[] on list_accounts, which is the read task buyers use to poll a pending request.

Capability

Registering requestAccount now advertises the account.requests block on get_adcp_capabilities, on both createAdcpServer and the platform path:

"account": {
  "supported_billing": ["operator"],
  "requests": {
    "supported": true,
    "request_task": "request_account",
    "read_task": "list_accounts",
    "changeable_attributes": ["billing", "payment_terms", "timezone"],
    "pending_action_kinds": ["review", "accept_terms"]
  }
},
"experimental_features": ["account.requests"]
  • The seller supplies the advisory lists through capabilities.accountRequests (changeableAttributes, pendingActionKinds, eventType). This is platform.capabilities.accountRequests on the platform path and capabilities.accountRequests on createAdcpServer. Setting it without requestAccount is a configuration error.
  • The feature id is handled the same way as the other tool-backed experimental features (list_collections, list_proposals): it is added for releases that define request_account and removed for earlier ones. Those releases don't get the account.requests block either. A seller that lists account.requests in overrides.experimental_features without registering the tool loses the claim.
  • If an adopter override sets account.requests, its optional fields are kept, but supported, request_task and read_task are always the framework's values.
  • A seller that declared no account capabilities gets a minimal account block, { supported_billing: ['agent'] }, the same default the platform path already uses. Releases without requests don't see this block.

accounts.listRequests

AccountStore gains an optional listRequests(request, ctx) => CursorPage<AccountRequest> hook. It runs only when the buyer sends include_requests, request_ids or requests_pagination. The framework returns its items as account_requests[] and builds account_requests_pagination from nextCursor / totalCount. The accounts page is unchanged, and the result still passes through reporting.projectListAccounts.

  • listRequests without accounts.list throws PlatformConfigError.
  • Registering requestAccount with neither listRequests nor an opts.accounts.listAccounts override logs a startup warning, because buyers would have no way to poll a pending request.

Account.revision

The platform Account gains revision, which is now projected onto list_accounts. Buyers read it there and send it with a request_account change. Without this field, a platform seller had no way to serve it.

This replaces both workarounds in the interop seller (adcontextprotocol/adcp#8679): the hand-written capabilities.overrides block and the projectListAccounts wrapper.

Tests

test/lib/account-requests-seller.test.js:

  • The platform with requestAccount advertises the block and the feature id, and the response is schema-valid. A 3.2 caller sees neither.
  • A platform without requestAccount advertises nothing, even when overrides claim the feature.
  • Under strict response validation, list_accounts with include_requests, request_ids or requests_pagination returns only the caller's requests, schema-valid and paged. Without those flags the hook is never called. Listed accounts carry revision.
  • Configuration errors and the startup warning.
  • createAdcpServer: the synthesized account block, an adopter override, and accountRequests without the tool.
  • A TS buyer (AgentClient over in-memory MCP) runs client.accounts.ensure against the server: ready, then pending. After the seller approves, refresh() reports ready through list_accounts request_ids, and no duplicate request is opened.

The existing capability, account and list_accounts suites pass: 3,066 tests across 75 files, including the toWireAccount projection suites.

Docs: docs/guides/account-resolution.md has a new section, Account requests.

🤖 Generated with Claude Code

bokelley and others added 2 commits November 1, 2026 23:07
…on the platform path

A seller registering request_account now advertises the AdCP 3.3
account.requests capability block (supported, request_task, read_task:
list_accounts) and the account.requests experimental feature id, so a
buyer's client.accounts.ensure routes to request_account instead of
falling back to sync_accounts. capabilities.accountRequests supplies
changeable_attributes, pending_action_kinds and event_type. Releases
without request_account see neither the block nor the feature id.

On the platform path, accounts.listRequests(request, ctx) serves the
caller's requests as account_requests[] and account_requests_pagination
on list_accounts when the buyer sends include_requests, request_ids or
requests_pagination. It needs accounts.list; a requestAccount seller
without it gets a startup warning, since buyers poll pending requests
through list_accounts.

Fixes #3575.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Buyers read the revision a request_account change is based on from
list_accounts. The platform Account had no revision field, so a platform
seller could not serve it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
// AdCP 3.3 account.requests: the caller's requests ride on list_accounts when asked for.
const requests =
accounts.listRequests &&
(filter.include_requests === true ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium: The account_requests emission is gated by the buyer's request fields but not by the served release. filterCapabilitiesForToolAvailability deliberately hides account.requests from capabilities on pre-3.3 releases, yet this handler emits account_requests whenever the buyer sends include_requests/request_ids/requests_pagination at any release. Production defaults to validation: 'off' (create-adcp-server.ts:5732-5733), so a buyer pinned to 3.2 that sends those fields gets a 3.3-only feature on a 3.2 wire response — the exact asymmetry the capability gate exists to prevent. ctx.servedAdcpVersion is available and the file already gates other 3.3 behavior with isAdcpVersionAtLeast(servedAdcpVersion, '3.3.0-beta.0') (L1993); gate this call the same way, and add a test that a 3.2 list_accounts with include_requests returns no account_requests. (Same unconditional-emission pattern applies to Account.revision in toWireAccount, lower impact.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8f7ac80.

  • listRequests now runs only when isAdcpVersionAtLeast(ctx.servedAdcpVersion, '3.3.0-beta.0') holds. Below 3.3, account_requests and account_requests_pagination are never emitted, whatever request fields the buyer sends.
  • Account.revision is now removed from list_accounts rows served to a release before 3.2. When no served version is known, rows keep it, which is the existing behavior.
  • New test: a 3.1 and a 3.2 list_accounts call each send include_requests, request_ids and requests_pagination with validation off. Neither gets account_requests, and listRequests is never called. revision appears at 3.2 but not at 3.1. A 3.3 call still gets its requests.

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

Ladon verdict: Approve

Approve — additive seller-side AdCP 3.3 account.requests support.

What I checked

  • Changeset is minor, correct for a purely additive wire change (account.requests registration, accounts.listRequests on list_accounts, Account.revision pass-through).
  • Witness-not-translator: revision and requests pass through untouched; no fabrication or re-shaping at the seam.
  • Capability gating is release-scoped with synthetic-block cleanup and startup throw/warn guards for missing list/listRequests.
  • Tests are thorough (403 lines): capabilities gating, caller isolation, pagination, buyer ensure/refresh round-trip.

Medium findings (non-blocking)

  • src/lib/server/decisioning/runtime/from-platform.ts:9299 — account_requests response emission is gated by buyer request fields but not by served release, so under production validation:'off' a 3.2-pinned buyer could pull a 3.3-only feature onto a 3.2 response. Recommend gating on ctx.servedAdcpVersion and adding a pre-3.3 test.

Decision rationale
No critical/high findings. gated_paths is false. high_risk is false. No author team gate. Exactly one medium finding (category not data-loss/schema/infra), which falls through to row 9 → approve. The single medium is worth addressing but does not block.

Medium findings

  • src/lib/server/decisioning/runtime/from-platform.ts:9299 — account_requests response emission not gated by served release

aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Nov 1, 2026

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

Ladon verdict: Approve

Approve — additive read-side field, clean diff.

This PR adds Account.revision (revision?: number) to the Account interface and emits wire.revision unconditionally in toWireAccount, consistent with every sibling optional field (sandbox, ext, credit_limit, advertiser). Release gating correctly lives downstream, not in the projection, so the witness-not-translator contract is honored and no new gap is introduced.

Checks performed:

  • Changeset present and correctly typed minor for an additive read-side field (satisfies changeset-vs-wire-impact).
  • DR-0033 minor-release pathway: additive read-side field is droppable in the prior minor with no code change; no identity/mutation impact.
  • Test asserts revision=3 is served and the response is schema-valid under strict validation.

The prior-run Medium (account_requests emission gating in from-platform.ts) is outside this delta and not re-flagged. No high-risk paths, no gated paths, no author-team gate.

Decision table: no critical/high findings (row 1 n/a), gated_paths false (row 2 n/a), no deletions (row 3 n/a), no medium findings (rows 4/5/8 n/a), prior decision was approve (row 6 n/a), no team gate (row 7 n/a) → row 9 approve.

…hat define them

list_accounts emitted account_requests whenever the buyer sent
include_requests, request_ids or requests_pagination, at any served
release, while capabilities hide account.requests below 3.3. listRequests
now runs only for releases from 3.3.0-beta.0, and Account.revision, an
AdCP 3.2 field, is not served to an earlier release.

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

account.requests lives in the account capability block, whose schema requires
supported_billing. The server used to fill in ['agent'] when the seller
declared none, a billing claim the seller never made. It now refuses at
construction and names the one-line fix (capabilities.account.supportedBilling,
or capabilities.supportedBillings on the platform path). The 3.3 beta adoption
tests declare it.

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

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

Ladon verdict: Approve

Approve — subsequent pass on PR #3586, clean.

This pass covers a two-file delta: src/lib/server/decisioning/runtime/from-platform.ts adds version gates to the list_accounts handler (account_requests fails closed behind isAdcpVersionAtLeast(served, '3.3.0-beta.0'), Account.revision stripped below 3.2), plus one new test.

Checks performed:

  • Both version gates are correct — the listRequests gate short-circuits before the hook runs (verified by listRequestCalls.length === 0), and the revision guard follows the file's served-version convention (DR-0033 minor-release backward-compat pathway handled as droppable projection).
  • New test covers 3.1 / 3.2 / current versions.
  • Changeset present (.changeset/account-requests-seller.md) for the src/lib/** changes.
  • No witness-not-translator violations; no official-transport-client concerns.

No critical, high, or medium findings in this run. Prior decision was approve with no findings. high_risk false, gated_paths false, no author team gate. Falls through to row 9.

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

Ladon verdict: Approve

Approve — clean subsequent pass, no blocking or medium findings.

This delta (5 files, 158 lines) replaces the prior fabrication of account.supported_billing: ['agent'] with a construction-time refusal when a seller registers request_account without declaring supported_billing — correctly aligning with the witness-not-translator rule. Verified:

  • create-adcp-server.ts:154-165 — guard scoped to request_account, no null deref (|| short-circuit).
  • Removal of the syntheticAccountBlock deletion step; declared supported_billing legitimately persists to pre-3.3 releases, and filterCapabilitiesForToolAvailability still strips only .requests.
  • Tests and docs updated consistently; changeset stays minor (breaking-for-experimental downgrades to normative — appropriate).

One out-of-delta note (not a finding): from-platform.ts:3065 still defaults supported_billing to ['agent'] for platform-path sellers with an account projection, so the changeset's "SDK never fills in this claim" is slightly overstated for that path — pre-existing, documented default, not introduced by this PR.

No critical/high/medium findings. high_risk false, gated_paths false, no author-team gate. Prior decision was approve with no findings. Falls through to row 9.

@bokelley
bokelley merged commit bfc5a9f into main Nov 2, 2026
45 checks passed
@bokelley
bokelley deleted the bokelley/account-requests-seller branch November 2, 2026 02:52
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.

request_account on the platform path: account.requests not advertised, and list_accounts cannot return account_requests

1 participant