Skip to content

docs(decisioning): document the AccountStore.resolve() → account.id encoding seam for multi-tenant adopters #738

Description

@bokelley

Spun out from the (now-closed) #737 — the gap is documentation, not API.

The seam

AccountStore.resolve() is the single layer that owns Account.id minting. Multi-tenant seller adopters need to encode tenant scope into Account.id here, and every downstream store (ProposalStore, IdempotencyStore, TaskRegistry, future media-buy stores) treats account.id as opaque after that.

Today this is implicit. Adopters reading the Protocol surface in isolation can mistake account.id for a wire-level buyer reference — and either (a) try to push tenant context into the downstream Protocol methods (filing issues like #737), or (b) push the encoding into the downstream store and parse it back out (which is what salesagent currently does in SalesAgentProposalStore._resolve_tenant_id_for_account, and which is wrong). Neither path is obvious from the docstrings as they stand.

Concrete docs changes

  1. AccountStore Protocol docstring — one paragraph stating that Account.id is the adopter's canonical opaque identity scope, and that multi-tenant seller adopters MUST encode tenant scope into it at resolve() time. Include a 4-line example:

    class MyAccountStore:
        def resolve(self, ref=None, auth_info=None):
            tenant_id = self._tenant_from(auth_info)
            buyer_ref = (ref or {}).get(\"account_id\", \"default\")
            return Account(
                id=f\"{tenant_id}:{buyer_ref}\",          # globally unique
                metadata={\"tenant_id\": tenant_id},      # opaque sidecar for adopter-internal use
            )
  2. create_tenant_store docstring — call out that it's the canonical helper for this pattern, link to it from the AccountStore docstring.

  3. ProposalStore / IdempotencyStore / TaskRegistry Protocol docstrings — one sentence each: "account_id is treated as opaque; adopters with multi-tenancy concerns encode their scope into it at the AccountStore.resolve() layer." Prevents the reaching-back-into-encoding pattern.

  4. PgProposalStore module docstring — pointer to (1) so adopters arriving from a search for "multi-tenant proposal storage" land on the right seam, not on "how do I add a tenant_id column."

Why this is worth filing

#737 wasn't an isolated misread — every multi-tenant adopter who writes a custom ProposalStore is going to hit the same wrong-layer pull. salesagent did. The library is well-designed; it just needs to signal where the seam is.

salesagent follow-ups (not blocking this)

  • Fix SalesAgentProposalStore._resolve_tenant_id_for_account to read account.metadata[\"tenant_id\"] instead of parsing account.id.
  • Adopt PgProposalStore (closes the seller-side compliance work entirely).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions