Skip to content

decisioning: rework 'derived' resolution mode — upstream-managed roster (Meta/Snap/AudioStack), invert account_id rejection #1647

@bokelley

Description

@bokelley

What

The 'derived' resolution mode is currently shipped with inverted wire semantics. It's documented and implemented as "single-tenant; refuse account_id; permit brand+operator," but the pattern adopters actually need — and that the dominant programmatic/social case requires — is the opposite: upstream platform owns the roster, buyer must list_accounts, buyer passes account_id on every subsequent call, sync_accounts is forbidden.

The "single-tenant agent" framing was a mistake. AudioStack and flashtalking sit in this mode at N=1 (one account per credential); Meta/Snap/Figma MCP sit in this mode at N=many. The wire contract is identical — roster size is an operational property of the upstream, not a different SDK pattern.

Corrected three-mode taxonomy

Mode Roster owner Onboarding Wire reference
'explicit' Seller (no list) None account_id inline
'implicit' Buyer-declared sync_accounts brand + operator
'derived' Upstream platform list_accounts account_id inline

Concrete changes

  1. Invert refuseInlineAccountIdWhenForbidden for derived. src/lib/server/decisioning/runtime/from-platform.ts:193, 1201 currently refuses { account_id } for both 'implicit' and 'derived'. For derived it should refuse the brand+operator union arm and accept account_id. Implicit's behavior is unchanged.
  2. Refuse sync_accounts dispatch when resolution: 'derived'. Either fail at platform-config time when the adopter wires accounts.upsert alongside 'derived', or return UNSUPPORTED_FEATURE at dispatch. Upstream owns the roster.
  3. Require list_accounts (not OR) for derived in the storyboard: missing required-account tool should fail, not skip #1624 compliance gate. Currently list_accounts OR sync_accounts. For derived it must be list_accounts only.
  4. Update AccountStore.resolution JSDoc at src/lib/server/decisioning/account.ts:389-412 and docs/guides/account-resolution.md to reflect the corrected taxonomy. The "Multi-tenant derived" framing disappears — derived is uniformly upstream-managed regardless of N.
  5. Update scripts/generate-agent-docs.ts source string (the "Four reference AccountStore shapes" paragraph) so the regenerated docs/llms.txt matches the corrected semantics.
  6. Audit reference adapters (audiostack, flashtalking, scope3data/agentic-adapters#100) — createDerivedAccountStore's toAccount(ctx) callback now needs to verify a buyer-supplied account_id matches what the credential maps to, instead of being a pure "ignore the ref, return the singleton" shortcut.

Migration / breaking-change scope

Technically breaking for any adopter relying on current (inverted) rejection behavior. In practice the inverted behavior is unusable for upstream-managed adapters (they can't get account_id through), so adopters that successfully shipped with 'derived' today are either:

  • N=1 single-tenant (audiostack-shape) where toAccount(ctx) ignores the ref — minor adjustment to verify the ref instead
  • Bypassing the SDK's resolve and routing on credential themselves — unaffected

Major version bump warranted. Feature flag (accounts.resolutionVersion: 2) considered but probably not worth the dual-codepath maintenance.

Cross-references

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions