Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/agent-identity-3-3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,11 @@ The SDK vendors its PSL snapshot instead of using the one bundled in the install
`TrustGrantEvaluator.check(...)` follows L2 §Grants in trust.json:

- **One record authorizes: the house's.** Pass `resolve_house=`, an async `(brand_domain, brand_id) -> BrandHouse | str | None`, returning the house named by the brand's verified brand.json (a house redirect, or a `house_domain` the house's `brand_refs[]` reciprocates). `BrandHouse(domain, brand_id)` also gives the brand's child-brand id in the house's portfolio, which grants' `profiles.adcp.brands` are checked against (as in TypeScript). Without the hook, or when it returns `None`, the brand's own domain is the house. A resolver failure is `SERVICE_UNAVAILABLE` (see below). Grants in any other record are never consulted.
- **A `grantee` must be a registrable domain.** A grant whose `grantee` is not one covers no claim: a host below a registrable domain (`ads.platform.com` does not match `platform.com`) or a public suffix itself (`github.io`; `alice.github.io` is registrable, because `github.io` is in the PSL's private section). Its `scopes` still count for `exclusive_grants`. `validate_trust_record` refuses such a grantee when authoring. To limit a grant to some of the grantee's agents, list them in `agents[]`.
- **Domains compare as registrable domains** under the pinned PSL snapshot: `account.operator`, the brand's domain and the signer's operator. Authority is implicit when `account.operator` and the signer's operator share one with the brand's domain or with its house's (the house `resolve_house` returns): an organization's own agents act for its child brands without a grant. A house the brand claims but whose `brand_refs[]` does not list it back is unverified, so the resolver returns `None`, the brand is its own house, and the claimed house's operator needs a grant like any other operator. `resolve_house` is not called for a claim already implicit on the brand's own domain; for every other claim a resolver failure is `SERVICE_UNAVAILABLE`, so an outage never turns what would be implicit authority into `grant_absent`.
- **Otherwise a chain must reach the signer's operator with `account.operator` on it:** the house grants the signer's operator, which is `account.operator`; the brand is `account.operator` (a self-operated brand) and its house grants the signer's operator; or the house grants `account.operator`, whose own record grants the signer's operator. There is no third link.
- **A `grantee` must be a registrable domain.** A grant whose `grantee` is not one covers no claim: a host below a registrable domain (`ads.platform.com` does not match `platform.com`) or a public suffix itself (`github.io`; `alice.github.io` is registrable, because `github.io` is in the PSL's private section). `validate_trust_record` refuses such a grantee when authoring. To limit a grant to some of the grantee's agents, list them in `agents[]`.
- **Domains compare as registrable domains** under the pinned PSL snapshot: `account.operator`, the brand's domain and the signer's operator. Authority is implicit when `account.operator` and the signer's operator are each the brand's domain or its house's, in any combination (the house `resolve_house` returns): an organization's own agents act for its child brands without a grant. A house the brand claims but whose `brand_refs[]` does not list it back is unverified, so the resolver returns `None`, the brand is its own house, and the claimed house's operator needs a grant like any other operator. `resolve_house` is not called for a claim already implicit on the brand's own domain; for every other claim a resolver failure is `SERVICE_UNAVAILABLE`, so an outage never turns what would be implicit authority into `grant_absent`.
- **Otherwise one delegation-chain rule applies** (adcontextprotocol/adcp#8473): at most two grants lead from the house's record to the signer's operator, either one grant to it, or one grant to an intermediary whose own record grants it, with `account.operator` on the chain as the brand, its house, or the first grant's grantee. Every covering first grant is a candidate: distinct intermediary records are read concurrently (at most 8 at a time), and an unreadable one is an outage only when no other chain authorizes. There is no third link. The cross-SDK vectors live in `tests/fixtures/delegation-chains/`.
- **Every link must cover** the scope, `profiles.adcp.brands`, the countries and the agent narrowing (a narrowed link that does not list the signer's canonical origin ends the chain), and the check time must fall inside every link's `valid_from`/`valid_until`, so the effective window is the latest `valid_from` to the earliest `valid_until`.
- `exclusive_grants` makes `grants[]` exhaustive for the scopes it names, disabling `seller_authorization` for them.
- **Absence.** With no covering chain, `exclusive_grants: true` on the house's record makes `grants[]` the complete list, whatever the scope, and the claim is `grant_absent`. Without it the claim is at `seller_authorization`'s discretion; a grant that does not cover the claim never narrows that discretion, and no other record's `exclusive_grants` counts. `GrantAuthorization.source` reports how an authorized claim was established: `"implicit"`, `"trust"`, `"seller"` or `"legacy"`.
- **No downgrade.** A 200 from the house's trust.json makes it the only source of grants: `legacy_authorization` (brand.json `authorized_operators`) runs only while the house has never published one.
- **Fetch failures.** A grantor or intermediary record that can't be read, including a 404/410 after it was read, is never absence and never `grant_absent`. A claim marked non-spend (`allow_stale_on_outage=True`) is evaluated on the last records read within the outage-continuation bound; anything else gets `SERVICE_UNAVAILABLE`. An intermediary that has never published a record provides no second link.

Expand Down
Loading
Loading