Skip to content

RFC: advertiser identity model (brand claims, account connections, buy binding) #7782

Open

Description

@bokelley

Problem

Many social and retail products run as one of the advertiser's identities: a Facebook Page, an Instagram or TikTok profile, a YouTube channel, a LinkedIn organization. AdCP has no clear home for that identity today:

  • downstream-connection-requirement models it as connection_type: "publisher_identity", with the platform ID in resource_ref.identity_id / handle / profile_url.
  • published_post.identity_ref names the identity that owns a boosted post, as a hint only.
  • Nothing lists which identities an account has, and nothing on create_media_buy lets the buyer choose which one a buy runs as. Adapters work around this with seller extensions (for example ext.scope3_execution_readiness on Meta, TikTok and Snap), and the seller applies an undocumented default.

This came up while shipping #7774 (static execution requirements, experimental in 3.2) and scoping per-account readiness in #7763. It is one of the pieces left for 3.3.

Proposed direction: split identity across three layers

Layer Holds Why
Brand (brand.json) A public claim that the brand owns these handles or profile URLs, optionally scoped by brand.countries Public, verifiable and the same for every seller. It fits the "Brand FR / Brand BE" case from #7763
Account Platform connections and authorizations: the platform's identity ID and status (connected / missing / expired / revoked) Authorization is granted per platform account, and one brand can have different Pages on Meta and TikTok. The existing downstream-connection shape is already scoped to the account
Buy / package Which identity this buy runs as A choice made per buy. This is the typed binding field from #7763

What each layer should not do:

  • Not on the brand alone. That mixes public claims with private platform IDs and grant state.
  • Not in the account's identifying key. A Page must not become part of the natural account key (brand + operator + …). Otherwise, adding or rotating a Page would look like a different advertiser and trigger re-keying or ACCOUNT_MOVED logic.

Questions for the working group

  1. Brand claims. Does brand.json get a social_identities[] (or similar) block, and how do sellers verify it against platform state? published_post.identity_ref already says buyer claims are hints, not proof.
  2. Account discovery. Should there be a discovery read, similar to the read-only mode of sync_event_sources / sync_catalogs, that lists the identities an account has connected on a seller? Or should this come only through the per-account readiness candidates in RFC: per-account execution readiness for products (required resources, candidates, and binding) #7763?
  3. Buy binding. The RFC: per-account execution readiness for products (required resources, candidates, and binding) #7763 triage decision was typed fields, not a generic resource_bindings list. Where does advertiser_identity_id go: package, media buy, or creative? Meta's rule "if you use page_id, the creative must promote that page_id" suggests validating the package against the creative.
  4. Proposals. Buys created from proposal_id carry no package fields, so how does a proposal carry the identity binding? This is the same open question as event source and catalog bindings for proposal buys (feat(schema): add experimental product execution requirements #7774 left them out of the rejection rule in 3.2).
  5. Candidates. Only identities that are connected are listed as candidates (RFC: per-account execution readiness for products (required resources, candidates, and binding) #7763 triage decision 2). An identity that isn't connected is reported as setup_required, with the connection's authorization_url.

Related

Activity

  1. added this to the 3.3.0 milestone on Sep 30, 2026
  2. added
    rfcProtocol change — auto-adds to roadmap board
    needs-wg-reviewBlocked on a working-group decision — surface in WG meeting agendas
    media-buyIssue concerns the media-buy protocol domain
    brandIssue concerns the brand protocol domain
    on Sep 30, 2026
  3. added
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Sep 30, 2026
  4. bokelley commented on Sep 30, 2026

    @bokelley
    ContributorAuthor

    Triage

    Classification: RFC
    Bucket(s): spec / protocol, registry / discovery, media-buy, brand
    Status: ready-for-human
    Milestone: 3.3.0

    What the experts said:

    • ad-tech-protocol-expert: decomposition is protocol-correct; three existing schemas carry parallel identity-ref shapes with different field names — a single canonical $ref must be defined before 3.3 ships; Q4 puntable with spec note; Q5 must be MUST not an open question
    • ad-tech-protocol-expert-deep: Q1 should extend brand.properties[] + property-type.json, not add a social_identities[] block; Q2 warrants a new standalone sync_publisher_identities task that composes with RFC: per-account execution readiness for products (required resources, candidates, and binding) #7763 via the existing downstream-connection-requirement shape; Q4 explicitly exclude with prose guard; Q5 show all identities with status
    • adtech-product-expert: three-layer split maps accurately to DSP/SSP practice (Meta Business Manager, TikTok Ads Manager separation); Q2 prefers embedding in RFC: per-account execution readiness for products (required resources, candidates, and binding) #7763 readiness candidates; Q3 binding belongs at media-buy (line-item) level, not package; Q4 needs a minimum setup_required gate before deferral is safe for PG/upfront flows; Q5 surfaces two new operator gaps: agency-vs-advertiser Page access grant, and multi-country Page hierarchy (Global Pages)

    My take: Expert consensus covers the decomposition, the Q1 brand.json approach, and Q4 deferral prose. Three decisions genuinely split and need WG resolution before implementation can begin.


    Consensus — these can be implemented as written:

    • Three-layer model is correctly decomposed. downstream-connection-requirement (existing) already implements the account layer.
    • Q1 brand.json: Do not add a separate social_identities[] block. Extend property-type.json with social platform values (social_page, social_channel, etc.) and express handles as brand.properties[] entries with relationship: owned. This keeps public claims in the existing property-claim model and avoids a second enumeration that must stay in sync.
    • Q4 proposals: Explicitly exclude in spec prose: "Proposal-sourced buys use seller platform defaults for identity. Buyers requiring identity control MUST use the direct-purchase path." Add a KNOWN_LIMITATION note in 3.3 release notes consistent with feat(schema): add experimental product execution requirements #7774's treatment.
    • Q5 filter: Show all identities including disconnected ones (with status and authorization_url), not only connected ones — filtering to connected is a footgun when tokens expire mid-flight.

    @bokelley, your call — Q2: Identity discovery shape

    Option A — New standalone sync_publisher_identities task (both protocol experts):

    // sync_publisher_identities response — all identities for account
    {
      "identities": [
        {
          "connection_type": "publisher_identity",
          "status": "connected",
          "connection_id": "conn_abc",
          "resource_ref": { "handle": "Brand FR", "identity_id": "pg_1" }
        },
        {
          "connection_type": "publisher_identity",
          "status": "expired",
          "resource_ref": { "handle": "Brand BE", "identity_id": "pg_2" },
          "authorization_url": "https://..."
        }
      ]
    }

    Separate caching profile from readiness; composable with #7763 candidates via connection_id reference. Two surfaces, one primitive shape.

    Option B — Embedded in execution_readiness candidates (product expert):

    // No new task; identities surface inside get_products per-account readiness
    {
      "resources": [{
        "kind": "publisher_identity",
        "status": "selection_required",
        "candidates": [
          { "connection_id": "conn_abc", "name": "Brand FR", "status": "connected" }
        ]
      }]
    }

    No new task surface; buyers see identities only in buy-planning context. Simplifies implementation but couples identity visibility to product plan requests.


    @bokelley, your call — Q3: Binding placement

    Option A — Package level (both protocol experts):

     // packages array on create_media_buy
     {
       "packages": [{
         "package_id": "pkg_1",
    +    "identity_binding": { "connection_id": "conn_abc" },
         "other_fields": "..."
       }]
     }

    Consistent with how execution_readiness resources bind in #7763; published-post-asset.identity_ref at creative level is the validation counterpart for Meta's page_id rule.

    Option B — Media buy level (product expert):

     // create_media_buy top level
     {
       "media_buy_id": "buy_1",
    +  "identity_binding": { "connection_id": "conn_abc" },
       "packages": [...]
     }

    Matches TTD/DV360 line-item model; avoids multi-brand package ambiguity where one package spans buys for different regional Pages.


    @bokelley, your call — Q4 guard level

    Protocol experts say fully defer with a spec note. Product expert says proposal buys without identity binding risk silent policy violations at trafficking time (especially for upfront/PG flows where identity matters most) — a minimal setup_required gate is needed even in 3.3.

    Is a minimum guard ("proposal buy with no identity binding, on a product that requires identity, enters setup_required before trafficking") feasible for 3.3, or is full deferral safe given your PG use cases?


    Q5 — two operator gaps needing explicit deferral call or spec text:

    1. Agency Page access: DSP token being connected ≠ agency holds the brand's Page permission grant. The account layer reflects the DSP token; the brand-to-agency Page grant lives in the platform's Business Manager. Does the RFC need to address this path, or declare it setup_required territory?
    2. Multi-country Page hierarchy: A global-pages parent + country-child structure means a DE-targeted buy and a US-targeted buy need different connected Page IDs. The current identity model has no geo-scoping. Does this need a geo_scope field on the social identity entry, or explicit guidance that geo-Page selection is outside protocol scope for 3.3?

    Triaged by Claude Code. Session: https://claude.ai/code/session_01Gp4CfsU2bUmHL8edovVbqJ


    Generated by Claude Code

  5. added
    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Sep 30, 2026
  6. modified the milestones: 3.3.0, 3.4.0 on Oct 6, 2026
  7. bokelley commented on Nov 2, 2026

    @bokelley
    ContributorAuthor

    Proposed 3.4 design: account identity discovery, diagnostics, and binding

    Class: Normative recommendation. Status: Proposed; not a recorded or
    ratified decision. Target: 3.4, experimental. Date: 2026-11-02.

    References: #7782; Meta Page evidence on #7763;
    #7934; #7783; #8655.

    Decision proposed

    1. Add a read-only list_account_identities task, independent of products.
      It can optionally evaluate those same identities against one declared
      product requirement. Use one canonical seller-assigned identity reference
      on discovery, readiness, and binding.
    2. Show every caller-visible identity relevant to the requirement, including
      blocked identities. Separate downstream authorization from product
      eligibility. Use candidate readiness_status and distinguish
      not_authorized, not_eligible, and an inability to check.
    3. Add typed packages[].identity_bindings[], keyed by the echoed identity
      requirement. Bind at package level and validate the chosen identity against
      every creative assigned to that package. This is an identity-specific field,
      not a generic resource binding list.
    4. Keep the existing connection vocabulary in 3.x. Do not define
      publisher_identity as third-party-only or reinterpret advertiser_account
      to mean an advertiser-owned Page. Resolve any ownership distinction and
      renaming through Rename publisher_identity to advertiser_identity (4.0; optional 3.3 alias) #7783, with a compatibility plan.

    These are recommendations for WG review. Publication of this memo does not
    settle #7782 Q2/Q3 or reverse the earlier connected-only call by itself.

    Rationale and alternatives

    Connected-only filtering hides identities a buyer expects to find and makes
    different repair paths indistinguishable. A missing grant needs authorization;
    an authorized identity lacking a product feature needs setup on that identity.
    Neither is evidence that the identity does not exist.

    A dedicated read gives identity enumeration its own pagination, refresh, and
    failure handling. An opt-in nested list on list_accounts is viable but adds
    per-account pagination inside account pagination. Product-only discovery cannot
    answer which identities an account can use before choosing a product.

    Package binding supports different regional identities within one buy. A
    buy-level binding forces an unnecessary common choice; creative-only binding
    does not resolve package-level execution requirements. Creative identity hints
    remain useful validation inputs, not authorization proof.

    The earlier #7782 discussion contains different recommendations on discovery
    and binding placement. This memo recommends the dedicated read and package
    placement; it does not claim consensus or a new expert review.

    Implications and scope

    The proposed feature ids are account.identities and
    media_buy.identity_readiness, advertised in experimental_features.
    The latter covers identity-aware readiness and typed package bindings;
    it requires the former plus media_buy.execution_requirements and
    media_buy.execution_readiness. Exact ids remain part of the review.

    The new read has no write/provisioning mode. It does not create accounts,
    connect identities, mutate grants, or change the account's natural key or
    account_id. Brand public claims remain separate; this memo adds no second
    identity directory to brand.json.

    Identity-aware product readiness requires a new explicit
    include_identity_readiness: true on get_products, together with
    include_execution_readiness: true and account. Without that opt-in,
    the shipped 3.3 map, connection-state derivation, and seller-default behavior
    remain unchanged. Consumers must discover support before using the new fields;
    3.4 implementations that understand this contract reject unsupported opt-ins with
    UNSUPPORTED_FEATURE, rather than silently claiming identity-aware results.
    Legacy servers may ignore unknown fields, so buyers must verify both the
    advertised capability and the presence of the requested identity-aware result.

    All wire shapes below are proposed API designs, not examples of existing
    AdCP schemas. The implementation must add schemas before these shapes appear
    in normative reference documentation. Existing versioned artifacts stay
    immutable. No runtime or schema implementation is included in this memo.

    Canonical identity record

    Field Contract
    identity_id Required nonempty seller-assigned string, at most 128 characters. Stable within (seller agent, account_id), not reassigned to another identity. Directly accepted by the typed binding even before authorization exists.
    provider Required stable provider namespace; identity resolution includes the provider internally.
    name Optional display name; never a routing or authorization key.
    resource_ref Optional provider-native hint, preserving the existing downstream-connection resource-ref semantics. Not the canonical binding id and not authorization proof.
    authorization_status Required authorized, not_authorized, or unknown. authorized means the account currently has the grants required to use this identity for advertising, not just a valid platform login.
    connection_state Optional existing downstream-connection state record, including safe authorization remediation when relevant. Existing grant statuses keep their meaning.
    unknown_reason Required exactly when authorization_status is unknown: permission_denied, platform_unavailable, or not_evaluated.
    evaluation Present only in product-context reads; candidate readiness defined below.
    ext Existing namespaced extension object.

    Identity references do not contain credentials. A connection can expire or be
    recreated without changing identity_id. Raw platform ids can be disclosed
    only when safe, and cannot substitute for the canonical id unless the seller
    explicitly uses that same string in its own identity namespace.

    list_account_identities

    Required request field: account, using the existing AccountRef. Optional:

    • pagination: existing request shape, max_results 1–100, default 50.
    • product_id and requirement: either both absent, or both present.
      requirement must deep-equal a current execution_requirements[] entry of
      that product, with kind: downstream_connection and connection.scope: identity. Account-wide discovery needs neither field.
    • Existing version-envelope, context, and extension fields.

    An incomplete product/requirement pair returns INVALID_REQUEST; an
    inaccessible or unknown product uses PRODUCT_NOT_FOUND, and a requirement
    that no longer matches the product uses VALIDATION_ERROR at requirement.

    Proposed account-only request:

    {
      "account": { "account_id": "acc_nova" },
      "pagination": { "max_results": 50 }
    }

    Successful responses require identities, version, evaluated_at,
    enumeration_complete, and existing response pagination. The identity list
    contains only records visible to this caller under this account. A page is
    bounded to 100 records. version is opaque; evaluated_at is a UTC timestamp.
    The existing transport/protocol envelope remains unchanged.

    {
      "identities": [
        {
          "identity_id": "ident_nova_fr",
          "provider": "social.example",
          "name": "Nova France",
          "authorization_status": "authorized",
          "resource_ref": { "identity_id": "platform_page_17" }
        },
        {
          "identity_id": "ident_nova_be",
          "provider": "social.example",
          "name": "Nova Belgium",
          "authorization_status": "not_authorized",
          "connection_state": {
            "provider": "social.example",
            "connection_type": "publisher_identity",
            "scope": "identity",
            "status": "missing",
            "authorization_url": "https://seller.example/connections/identity"
          }
        }
      ],
      "version": "identities_42",
      "evaluated_at": "2026-11-02T12:00:00Z",
      "enumeration_complete": true,
      "pagination": { "has_more": false }
    }

    enumeration_complete means all relevant caller-visible identities were
    enumerated into the snapshot, not that this page contains them all.
    pagination.has_more identifies further pages of that snapshot. Sellers must
    not label a truncated or permission-limited upstream enumeration complete.
    An incomplete read returns known records with enumeration_complete: false
    and required unknown_reason, using the same three values above. A failed
    enumeration with no known records is an incomplete result, never an empty
    complete result. Expired account authorization remains an account access error,
    not a diagnostic identity listing.

    Product-context candidates

    With product_id and requirement, the same read returns only visible
    identities relevant to that requirement and adds evaluation to each record:

    {
      "identity_id": "ident_nova_fr",
      "provider": "social.example",
      "authorization_status": "authorized",
      "evaluation": {
        "readiness_status": "setup_required",
        "reason": "not_eligible",
        "issues": [
          { "severity": "error", "message": "Create a shop for this identity before using this product." }
        ]
      }
    }

    Candidate readiness_status uses a subset of the existing vocabulary:

    • ready: authorization and product eligibility were both verified.
    • setup_required: an established blocker. Required reason is
      not_authorized or not_eligible; required nonempty issues uses the
      existing diagnostic issue shape with actionable remediation. Authorization
      recovery belongs in the candidate's connection_state.
    • unknown: no established blocker, but a required check could not complete.
      Required unknown_reason is permission_denied, platform_unavailable, or
      not_evaluated. It must not be reported as ineligibility.

    selection_required and input_required describe a requirement-level buyer
    action, not the readiness of one named candidate, so neither appears here.
    For an established authorization failure, use not_authorized before testing
    eligibility; do not claim an eligibility result a missing grant prevented the
    seller from checking. An authorization URL is not remediation for
    not_eligible. reason is absent on ready and unknown.

    The response also echoes product_id and requirement, and requires
    evaluation_complete, known_ready_count, and aggregate readiness_status.
    Whenever evaluation is incomplete, require a summary unknown_reason, even
    if enough verified ready identities already establish selection_required.
    Counts and completeness describe the entire snapshot, not this page.
    evaluation_complete requires complete enumeration and an established
    ready/blocked result for every relevant identity. Provider-agnostic
    requirements may enumerate multiple providers; provider-specific requirements
    filter accordingly. A visible identity must not be filtered out merely
    because its grant is missing or its product eligibility fails.

    Snapshot result Aggregate readiness
    At least two verified ready identities selection_required, including when other checks are incomplete
    Exactly one ready identity and evaluation complete ready; require resolved_identity_id
    No ready identities and evaluation complete setup_required; require actionable summary issues, including when the list is empty
    Zero or one ready identity and evaluation incomplete unknown; require unknown_reason

    An unknown alternative prevents a uniqueness claim, but does not prevent the
    buyer explicitly binding an independently verified ready identity. Blocked
    alternatives do not downgrade a known-ready identity. Candidate selection has
    no seller-default exception in this opt-in contract.

    Proposed product-context request:

    {
      "account": { "account_id": "acc_nova" },
      "product_id": "social_shop",
      "requirement": {
        "kind": "downstream_connection",
        "connection": {
          "provider": "social.example",
          "connection_type": "publisher_identity",
          "scope": "identity"
        }
      },
      "pagination": { "max_results": 50 }
    }

    Its complete response can contain one usable identity and a blocked alternative:

    {
      "product_id": "social_shop",
      "requirement": {
        "kind": "downstream_connection",
        "connection": {
          "provider": "social.example",
          "connection_type": "publisher_identity",
          "scope": "identity"
        }
      },
      "identities": [
        {
          "identity_id": "ident_nova_fr",
          "provider": "social.example",
          "authorization_status": "authorized",
          "evaluation": { "readiness_status": "ready" }
        },
        {
          "identity_id": "ident_nova_be",
          "provider": "social.example",
          "authorization_status": "authorized",
          "evaluation": {
            "readiness_status": "setup_required",
            "reason": "not_eligible",
            "issues": [
              { "severity": "error", "message": "Create a shop for this identity before using this product." }
            ]
          }
        }
      ],
      "version": "shop_identities_43",
      "evaluated_at": "2026-11-02T12:05:00Z",
      "enumeration_complete": true,
      "evaluation_complete": true,
      "known_ready_count": 1,
      "readiness_status": "ready",
      "resolved_identity_id": "ident_nova_fr",
      "pagination": { "has_more": false }
    }

    Integration with get_products

    For opted-in identity requirements, use the existing echoed requirement and
    top-level readiness_status, replacing the single connection_state with an
    identity_candidates object. It contains the product-context read's
    identities, version, evaluated_at, enumeration_complete, pagination,
    evaluation_complete, known_ready_count, and any required unknown reason or
    summary issues. Include at most the first 50 identities; paginate by calling
    list_account_identities with the same account, product, echoed requirement,
    and returned cursor. Candidate fields and the wrapper need named schema refs,
    not independent copies of the list-task definitions.

    The requirement's readiness_status follows the table above. When ready, add
    resolved: { "resource_id": "ident_nova_fr" }. binding_field is
    packages[].identity_bindings[].identity_id. identity_candidates and the
    legacy requirement-level connection_state are mutually exclusive.
    Non-identity connection requirements retain the existing shape and rules.

    This requires an explicit 3.4 schema branch: the current 3.3 conditionals
    require one connection state, prohibit identity candidates, and derive
    readiness solely from grant status. Simply adding candidate properties would
    leave the schema contradictory. Existing candidates[] for event sources and
    catalogs retains its current usable-resource semantics and minimum size.

    The existing product rollup priority remains setup_required,
    input_required, selection_required, unknown, else ready. Identity
    diagnostics remain in the private response-level readiness map and never enter
    the public product object, wholesale webhooks, or shared public caches.

    Typed binding and mutation validation

    Proposed package fragment, with the full requirement echoed to avoid an
    unstable positional index:

    {
      "identity_bindings": [
        {
          "requirement": {
            "kind": "downstream_connection",
            "connection": {
              "provider": "social.example",
              "connection_type": "publisher_identity",
              "scope": "identity"
            }
          },
          "identity_id": "ident_nova_fr"
        }
      ]
    }

    Each binding requires both fields. The list is nonempty, capped at 100, and
    contains at most one entry per deep-equal identity requirement. Distinct
    requirements can bind different identities. The seller rejects a requirement
    not declared by the package's current product; a buyer refetches stale product
    requirements rather than guessing an index. A supplied list must bind every
    declared identity requirement; omitting the whole field preserves legacy
    seller-default behavior. A buyer requiring identity control must send it.

    Use the same field in create package requests, new_packages, package updates,
    package response objects, and get_media_buys. An update replaces the entire
    binding list; omission preserves it. Null and empty-list clearing are excluded
    from this slice. Returning the effective bindings on reads is required for
    feature-enabled packages, including defaults that the seller resolved.
    Support is advertised by media_buy.identity_readiness; implementations must
    reject explicit bindings they do not support with UNSUPPORTED_FEATURE before
    mutation. The account-read feature alone does not advertise binding support.
    This rejection requirement applies to 3.4 implementations of this contract;
    buyers must not send the new field to legacy servers and assume it took effect.
    Identity updates also obey the caller's existing field grants and the
    package's advertised action/change-right constraints.

    The seller rechecks account visibility, authorization, eligibility, and
    creative compatibility before accepting creation, identity changes, or new
    creative assignments. It must not substitute a different identity when the
    explicit selection fails. Provider-native hints and public brand claims never
    replace grant verification. Cross-account/unknown identity ids return the
    same VALIDATION_ERROR with details.reason: not_found, without confirming
    another account's identity exists.

    Mutation failures use existing error codes with exact indexed error.field
    and proposed identity-specific details: product_id, echoed requirement,
    and reason (not_bound, not_found, not_eligible, or
    creative_identity_mismatch). Known authorization failures use existing
    AUTHORIZATION_REQUIRED details instead; inability to verify because of a
    transient platform failure uses SERVICE_UNAVAILABLE with no mutation.
    Unreadable required grants must not be treated as authorized. Any other
    indeterminate verification must fail before mutation with a truthful existing
    error code and remediation. Validation of a proposed package identity change
    and its affected creative assignments occurs before applying either change.

    Concrete error example for an existing but ineligible choice:

    {
      "code": "VALIDATION_ERROR",
      "message": "The selected identity has no shop for this product.",
      "field": "packages[0].identity_bindings[0].identity_id",
      "details": {
        "product_id": "social_shop",
        "requirement": {
          "kind": "downstream_connection",
          "connection": {
            "provider": "social.example",
            "connection_type": "publisher_identity",
            "scope": "identity"
          }
        },
        "reason": "not_eligible"
      }
    }

    Published-post identity hints must resolve to the bound identity when the
    platform requires it; a seller unable to verify that match must not accept
    the assignment as verified. Revocation observed later stops affected serving
    through existing lifecycle mechanisms; buyers repair with an authorized
    binding. This proposal adds no new webhook type or change-feed coverage claim.

    Pagination, freshness, and visibility

    “All identities” means the caller-visible set in the resolved account, never
    the seller credential's global set. Permission to inspect an identity and the
    account's permission to promote it are separate checks.

    Versions and cursors bind to seller, resolved account, caller visibility,
    product/requirement context when present, and the snapshot. Contextual versions
    also bind to the product's eligibility configuration. Equal versions imply
    identical records and aggregate evaluations across pages. Changing grants or
    eligibility yields a new version, but a cursor never silently jumps snapshots.
    Reuse in a different scope returns INVALID_REQUEST without revealing data.
    An expired/unrecoverable snapshot cursor returns INVALID_REQUEST with
    details.reason: cursor_expired; restart from the first page. Do not reuse
    CURSOR_EXPIRED without amending its currently account-change-feed-specific
    contract.

    Recheck caller visibility on every page; if permissions changed, invalidate
    the snapshot instead of serving its now-inaccessible records. Account-wide
    version covers discovery/authorization facts; contextual version additionally
    covers product evaluations. Cache keys include these scopes. For readiness,
    the existing execution_readiness.version covers the identity results too,
    without changing cache_scope, feed versions, or public catalogue dedupe.

    Missing new fields on legacy responses mean the feature was not requested or
    not available, never “no identities.” An empty complete list means none were
    found; an incomplete empty list means enumeration could not establish that.
    All evaluations are observations at evaluated_at, not a future authorization
    guarantee. Every mutation revalidates. Initial scope uses explicit rereads;
    conditional fetch and notifications remain #8655.

    Proposal buys and other exclusions

    This recommendation covers explicit package purchases and later package
    updates only. It adds no binding override beside proposal_id and does not
    claim identity control for proposal execution. Buyers needing an explicit
    identity use the direct-package path. A later explicit package update cannot
    retroactively guarantee which identity served before the update.

    Carry bindings in committed proposal snapshots and require validation before
    finalization/execution as a follow-up with #8655; do not invent a defaulting
    guarantee in this draft. Existing proposal behavior is unchanged. Also excluded:
    new brand property types, lead-form/messaging/app resource kinds, a generic
    resource-binding list, geo-selection automation, and a 3.x enum rename.

    Acceptance and implementation handoff

    1. Account-only discovery needs no product and never claims product
      eligibility. An authorized identity may be ready for traffic and ineligible
      for a shop product in contextual reads.
    2. Visible unauthorized and ineligible identities both remain listed with
      distinct reasons and relevant remediation; unknown checks never become
      invented setup failures.
    3. One ready plus blocked alternatives is ready; two ready requires selection;
      one ready plus unknown alternatives is unknown but permits explicit binding
      of the independently verified ready identity.
    4. An empty complete result differs from failed/partial enumeration. Paging
      does not alter whole-snapshot counts, readiness, or versions.
    5. Candidate cursors work from the readiness map on the dedicated read.
      Scope reuse, permission changes, expired snapshots, and upstream truncation
      cannot leak data or silently claim completeness.
    6. A discovered identity id binds directly even if the seller uses a different
      native id. Recreating its downstream connection preserves the identity id.
    7. Unknown and cross-account selections are indistinguishable. A grant revoked
      after discovery fails revalidation; the seller never silently substitutes.
    8. Creation and updates echo effective bindings on package reads. Creative
      mismatch, binding replacement, and adding creatives to an existing package
      are checked before mutation. Two packages may use different identities.
    9. Legacy 3.3 requests/responses and their existing conformance tests retain
      their current behavior. Capability checks and response-field verification
      prevent an ignored opt-in from being mistaken for identity-aware success.
    10. Proposal execution is not advertised as supporting explicit identity
      choice; direct purchases cover the first implementation.

    After the decisions above are accepted, implement named identity, candidate,
    page, binding, and error-detail schemas; the new account task and its registry
    entry; opt-in readiness conditionals and capabilities; package request/update/
    read fields; and corresponding normative docs, schema tests, and conformance
    storyboards. Annotate cross-task identity ids with x-entity. Add the protocol
    minor changeset for that implementation, not for this discussion memo. Run
    schema, projection, registry, documentation-example, and storyboard checks.

    Review requested: accept or amend the dedicated read (including optional
    product context), package-level typed binding, all-visible diagnostic
    candidates and rollup, and unchanged 3.x connection terminology. Once those
    choices are recorded, this is the implementation brief for the 3.4 slice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    brandIssue concerns the brand protocol domainclaude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.media-buyIssue concerns the media-buy protocol domainneeds-wg-reviewBlocked on a working-group decision — surface in WG meeting agendasrfcProtocol change — auto-adds to roadmap boardspec / protocol

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions