From 9c6c6db8fce75d6308ace421884ed507a3a70aad Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 2 Nov 2026 18:20:08 +0000 Subject: [PATCH 1/2] test(identity): vendor the adcp#8473 delegation-chain vectors Co-Authored-By: Claude Opus 5.5 --- tests/fixtures/delegation-chains/PINNED.json | 6 + .../delegation-chains/delegation-chains.json | 2743 +++++++++++++++++ tests/test_delegation_chain_vectors.py | 115 + 3 files changed, 2864 insertions(+) create mode 100644 tests/fixtures/delegation-chains/PINNED.json create mode 100644 tests/fixtures/delegation-chains/delegation-chains.json create mode 100644 tests/test_delegation_chain_vectors.py diff --git a/tests/fixtures/delegation-chains/PINNED.json b/tests/fixtures/delegation-chains/PINNED.json new file mode 100644 index 0000000000..787b963d5d --- /dev/null +++ b/tests/fixtures/delegation-chains/PINNED.json @@ -0,0 +1,6 @@ +{ + "source": "adcontextprotocol/adcp#8473", + "sha": "d397b8ea2d096ace015102fdf98c04f22b4874f9", + "path": "static/test-vectors/delegation-chains.json", + "sha256": "962d0f954c03a03c0e27ca6c9bd6c3236bf9cac140a46b60a4c9d2392f552b86" +} diff --git a/tests/fixtures/delegation-chains/delegation-chains.json b/tests/fixtures/delegation-chains/delegation-chains.json new file mode 100644 index 0000000000..3f41a618b8 --- /dev/null +++ b/tests/fixtures/delegation-chains/delegation-chains.json @@ -0,0 +1,2743 @@ +{ + "version": 1, + "name": "Delegation chains for demand-side grants", + "specification": "https://adcontextprotocol.org/docs/building/by-layer/L2/accounts-and-agents#grants-in-trust-records-experimental", + "description": "Cross-SDK vectors for the delegation-chain rule in L2 Grants in trust records, which is experimental in 3.3 (adcontextprotocol/adcp#8118, P7). Each vector gives an acts-for claim and the trust records that each registrable domain publishes, and the seller's expected outcome. `input.brand` is `account.brand`. `input.parent_organization` is the parent organization that brand.json resolves, or null for a standalone brand. `input.signer.operator` is the signer's operator, and `input.signer.origin` is the signer's canonical origin. `input.trust_records` maps each registrable domain to the `grants` and `exclusive_grants` of its trust.json; a domain that is absent publishes no grants. All domains are registrable domains already, so no Public Suffix List lookup is needed, and every record is fetched. `expected.outcome` is one of `implicit`, `authorized`, `grant_absent`, or `seller_discretion`. When no delegation chain covers the claim, the outcome is `grant_absent` if the parent organization's record sets `exclusive_grants: true`, and `seller_discretion` otherwise: the seller's own onboarding records decide. `expected` holds the outcome only. Each vector tests one premise: every other premise of the claim holds, so an implementation fails a vector only when it gets that premise wrong, whatever order it checks the premises in. Look up vectors by `id`; `description` prose may change.", + "vectors": [ + { + "id": "parent-grants-signer-operator", + "description": "The parent organization grants the agency, and the agency operates the account and signs with its own agent.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "brand-operates-account-through-hosted-agent", + "description": "The brand operates its account through an agent that a platform hosts. The parent organization grants the platform.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-motors.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "agency-works-through-platform", + "description": "The parent organization grants the agency, and the agency grants the platform that hosts the signing agent. The narrowing is on the second grant.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "parent-record-later-grant-authorizes", + "description": "The parent organization grants the agency twice. The first grant covers governance only, and the second covers media buying, so the second grant authorizes. Every grant in grants[] is a candidate.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + }, + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "intermediary-record-later-grant-authorizes", + "description": "The agency grants the platform twice. The first grant covers governance only, and the second covers media buying, so the delegation chain runs through the second.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + }, + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "parent-operates-account-through-hosted-agent", + "description": "The parent organization operates the account through an agent that a platform hosts, and grants the platform directly. No grant to itself is needed.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-holdings.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "parent-operates-account-on-two-grant-chain", + "description": "The parent organization operates the account, grants the agency, and the agency grants the platform. The rule admits this combination.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-holdings.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "brand-operates-account-on-two-grant-chain", + "description": "The brand operates the account, the parent organization grants the agency, and the agency grants the platform. The rule admits this combination.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-motors.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "implicit-authority-brand", + "description": "The brand operates the account and signs with its own agent. Authority is implicit, and the exclusive record without a grant to the brand does not matter.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-motors.example", + "signer": { + "operator": "nova-motors.example", + "origin": "https://buyer.nova-motors.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "implicit" + } + }, + { + "id": "implicit-authority-parent", + "description": "The parent organization operates the account and signs with its own agent. Authority is implicit.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-holdings.example", + "signer": { + "operator": "nova-holdings.example", + "origin": "https://buyer.nova-holdings.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "implicit" + } + }, + { + "id": "implicit-authority-brand-account-parent-signer", + "description": "The brand operates the account, and the parent organization signs with its own agent. Each is the brand or its parent organization, so authority is implicit.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-motors.example", + "signer": { + "operator": "nova-holdings.example", + "origin": "https://buyer.nova-holdings.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "implicit" + } + }, + { + "id": "implicit-authority-parent-account-brand-signer", + "description": "The parent organization operates the account, and the brand signs with its own agent. Each is the brand or its parent organization, so authority is implicit.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-holdings.example", + "signer": { + "operator": "nova-motors.example", + "origin": "https://buyer.nova-motors.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "implicit" + } + }, + { + "id": "agency-account-brand-signer-denied", + "description": "The brand signs with its own agent, but the account names the agency as its operator. Implicit authority needs the brand or the parent organization as both the account operator and the signer's operator.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "nova-motors.example", + "origin": "https://buyer.nova-motors.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "standalone-brand-record-authorizes", + "description": "A brand with no parent organization authorizes from its own record.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": null, + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-motors.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "standalone-brand-exclusive-record-denied", + "description": "A brand with no parent organization publishes an exclusive record that grants the agency governance only. The brand has no separate parent organization, so its own exclusive_grants counts and the media-buying claim is denied.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": null, + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-motors.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "three-grant-chain-denied", + "description": "The platform grants a relay a third time. A delegation chain has at most two grants, so the relay's agent is not covered.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "relay-exchange.example", + "origin": "https://buy.relay-exchange.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + }, + "orbit-platform.example": { + "grants": [ + { + "grantee": "relay-exchange.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "three-grant-chain-through-brand-denied", + "description": "The parent organization grants the brand, the brand grants the agency, and the agency grants the platform. The brand operates the account. A delegation chain has at most two grants, even when the brand is a middle grantee, so the platform's agent is not covered.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-motors.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "nova-motors.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "nova-motors.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "account-operator-not-on-chain-denied", + "description": "The parent organization grants the platform, but the account names an agency that is neither the brand, the parent organization, nor the first grantee.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "account-operator-is-second-grantee-denied", + "description": "The account names the platform, which is the grantee of the second grant. account.operator must be the brand, the parent organization, or the grantee of the first grant.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "orbit-platform.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "first-grant-scope-denied", + "description": "The first grant covers governance only, so it does not cover the media-buying claim. The second grant covers it.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "first-grant-brands-denied", + "description": "The first grant covers a different brand the parent organization owns. The second grant covers the account's brand.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_trucks" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "first-grant-countries-denied", + "description": "The first grant covers CA only, and the account's brand covers the US. The second grant is global.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "countries": [ + "CA" + ] + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "first-grant-valid-from-denied", + "description": "The check time is before the first grant's valid_from. The second grant is already valid.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "valid_from": "2026-11-01T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "first-grant-valid-until-denied", + "description": "The second grant is still valid, but the check time is at or after the first grant's valid_until.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-12-31T00:00:00Z" + } + ] + } + }, + "check_time": "2027-07-15T00:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "first-grant-agents-denied", + "description": "The first grant lists only the agency's own agents, so it never covers the platform's agent, even though the agency grants the platform.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://buyer.pinnacle-agency.example/mcp" + } + ] + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "second-grant-scope-denied", + "description": "The second grant covers governance only, so it does not cover the media-buying claim. The first grant covers it.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "second-grant-brands-denied", + "description": "The second grant covers a different brand. The first grant covers the account's brand.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_trucks" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "second-grant-countries-denied", + "description": "The account's brand covers GB. The second grant covers CA and the US only, so it doesn't cover the brand's countries.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "GB", + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "countries": [ + "CA", + "US" + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "second-grant-valid-from-denied", + "description": "The first grant is still valid, but the check time is before the second grant's valid_from.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-12-31T00:00:00Z", + "valid_from": "2026-11-01T00:00:00Z" + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "second-grant-valid-until-denied", + "description": "The check time is at or after the second grant's valid_until. The first grant is still valid.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2026-10-01T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "second-grant-agents-denied", + "description": "The second grant lists only another of the platform's agents, so the signer's canonical origin is not listed. The first grant is unnarrowed.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://reporting.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "agents-origin-port-mismatch-denied", + "description": "The parent organization grants the platform for an agent on port 8443. The signer's canonical origin has the default port, so it is a different origin and the grant does not cover it.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-motors.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example:8443/mcp" + } + ] + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "valid-until-at-check-time-denied", + "description": "The check time equals the grant's valid_until. A grant is inactive at or after valid_until, so it doesn't cover the claim.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2026-10-24T12:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "valid-from-at-check-time-authorizes", + "description": "The check time equals the grant's valid_from. A grant is active from valid_from on, so it covers the claim.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "valid_from": "2026-10-24T12:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "brand-without-countries-needs-global-grant-denied", + "description": "The account's brand lists no countries, and the grant covers the US only. A brand reference with no countries is covered only by a global grant.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors" + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "countries": [ + "US" + ] + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "brand-without-brand-id-denied", + "description": "The account's brand reference has no brand_id, and the grant lists specific brands. A brand reference with no brand_id is covered only by [\"*\"] or an omitted list.", + "input": { + "brand": { + "domain": "nova-motors.example", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "brand-not-covered-denied", + "description": "The grant covers a different brand the parent organization owns.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_trucks" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "wildcard-brands-grant-authorizes", + "description": "The grant lists [\"*\"] as its brands, which covers every brand the parent organization owns.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "*" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "scope-mismatch-denied", + "description": "The parent organization's exclusive record grants the platform governance only. No grant covers media buying, and the record is the complete list for every scope, so the claim is denied.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "orbit-platform.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "wildcard-scope-grant-authorizes", + "description": "The grant lists [\"*\"] as its scopes. The bare token * covers every scope in every namespace, so it covers the media-buying claim.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "*" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "brand-record-ignored-when-parent-denies", + "description": "The brand's own record grants the platform, but only the parent organization's record authorizes, and it grants the agency only.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "orbit-platform.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "nova-motors.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "brand-record-ignored-when-parent-grants", + "description": "The brand's own record is exclusive and grants another domain, but the parent organization's record grants the platform.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "orbit-platform.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "nova-motors.example": { + "grants": [ + { + "grantee": "acme-media.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "brand-record-supplies-second-grant", + "description": "The parent organization grants the brand, and the brand operates the account. The brand's own record supplies the second grant, to the platform that hosts the signing agent.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "nova-motors.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "nova-motors.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ], + "exclusive_grants": true + }, + "nova-motors.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "authorized" + } + }, + { + "id": "exclusive-no-grants-denied", + "description": "The parent organization's exclusive record lists no grants, so no third party acts for it.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "grant_absent" + } + }, + { + "id": "nonexclusive-uncovered-seller-discretion", + "description": "The parent organization's record is not exclusive and grants the platform governance only. No delegation chain covers the platform's media-buying claim, so the seller's onboarding records decide.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "orbit-platform.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "seller_discretion" + } + }, + { + "id": "nonexclusive-nonmatching-grant-seller-discretion", + "description": "The parent organization's nonexclusive record grants the agency media buying for another brand it owns. The grant covers nothing for this brand and doesn't narrow the seller's discretion.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_trucks" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "seller_discretion" + } + }, + { + "id": "exclusive-brand-record-seller-discretion", + "description": "The brand's own record is exclusive and lists no grants, and the parent organization's record is not exclusive. Only the parent organization's exclusive_grants counts, so a claim that no delegation chain covers stays at the seller's discretion.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "pinnacle-agency.example", + "origin": "https://buyer.pinnacle-agency.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_trucks" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + }, + "nova-motors.example": { + "grants": [], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "seller_discretion" + } + }, + { + "id": "exclusive-intermediary-record-seller-discretion", + "description": "The agency's own record is exclusive, and the parent organization's record is not. The second grant covers governance only, so no delegation chain covers the media-buying claim. Only the parent organization's exclusive_grants counts, so the claim stays at the seller's discretion.", + "input": { + "brand": { + "domain": "nova-motors.example", + "brand_id": "nova_motors", + "countries": [ + "US" + ] + }, + "parent_organization": "nova-holdings.example", + "account_operator": "pinnacle-agency.example", + "signer": { + "operator": "orbit-platform.example", + "origin": "https://agents.orbit-platform.example" + }, + "trust_records": { + "nova-holdings.example": { + "grants": [ + { + "grantee": "pinnacle-agency.example", + "scopes": [ + "adcp:media_buying" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z" + } + ] + }, + "pinnacle-agency.example": { + "grants": [ + { + "grantee": "orbit-platform.example", + "scopes": [ + "adcp:governance" + ], + "profiles": { + "adcp": { + "brands": [ + "nova_motors" + ] + } + }, + "valid_until": "2027-06-30T00:00:00Z", + "agents": [ + { + "url": "https://agents.orbit-platform.example/mcp" + } + ] + } + ], + "exclusive_grants": true + } + }, + "check_time": "2026-10-24T12:00:00Z", + "scope": "adcp:media_buying" + }, + "expected": { + "outcome": "seller_discretion" + } + } + ] +} diff --git a/tests/test_delegation_chain_vectors.py b/tests/test_delegation_chain_vectors.py new file mode 100644 index 0000000000..87de1f0050 --- /dev/null +++ b/tests/test_delegation_chain_vectors.py @@ -0,0 +1,115 @@ +"""Cross-SDK delegation-chain vectors (L2 "Grants in trust records"). + +Vendored from adcontextprotocol/adcp#8473 (see +``tests/fixtures/delegation-chains/PINNED.json``). Each vector is an acts-for +claim, the parent organization the brand resolves to, and the trust records +each domain publishes; :class:`TrustGrantEvaluator` must reach the expected +outcome. Re-vendor from upstream rather than editing the fixture. +""" + +from __future__ import annotations + +import hashlib +import json +import re +from pathlib import Path +from typing import Any + +import pytest + +from adcp.signing import ( + TrustGrantEvaluator, + TrustRecordEvaluator, + TrustResponse, + trust_record_url, +) +from adcp.signing.trust import BrandHouse + +FIXTURES = Path(__file__).parent / "fixtures" / "delegation-chains" +FIXTURE_BYTES = (FIXTURES / "delegation-chains.json").read_bytes() +FIXTURE = json.loads(FIXTURE_BYTES) +PINNED = json.loads((FIXTURES / "PINNED.json").read_text()) + +# The vectors use ``.example`` names, which have no registrable domain under +# the PSL. Map each to a registrable placeholder; domains are opaque labels. +_EXAMPLE = re.compile(r"\.example\b") + +OUTCOMES = {"implicit": "implicit", "authorized": "trust", "seller_discretion": "seller"} + + +def _mapped(value: Any) -> Any: + return json.loads(_EXAMPLE.sub("-dcv.com", json.dumps(value))) + + +def _instant(value: str) -> float: + from datetime import datetime + + return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() + + +def test_fixture_matches_pinned_upstream_content(): + assert hashlib.sha256(FIXTURE_BYTES).hexdigest() == PINNED["sha256"] + + +def test_the_vendored_vectors_cover_every_outcome(): + outcomes = {vector["expected"]["outcome"] for vector in FIXTURE["vectors"]} + assert outcomes == {"authorized", "grant_absent", "implicit", "seller_discretion"} + + +@pytest.mark.parametrize("vector", FIXTURE["vectors"], ids=lambda vector: vector["id"]) +async def test_delegation_chain_vector(vector): + claim = _mapped(vector["input"]) + now = _instant(claim["check_time"]) + signer = claim["signer"] + documents = {domain: dict(record) for domain, record in claim["trust_records"].items()} + # Every operator a chain reaches lists the signer, so binding is never the + # reason a vector fails. + documents[signer["operator"]] = { + **documents.get(signer["operator"], {}), + "agents": [{"url": signer["origin"], "roles": ["adcp:buying"]}], + } + by_url = {trust_record_url(domain): record for domain, record in documents.items()} + + async def fetch(url, *, allow_private=False): + record = by_url.get(url) + if record is None: + return TrustResponse(b"", 404) + return TrustResponse(json.dumps(record).encode()) + + seller_calls = [] + + async def seller_authorization(request): + seller_calls.append(request) + return True + + brand = claim["brand"] + parent = claim["parent_organization"] + + async def resolve_house(domain, brand_id): + assert domain == brand["domain"] + return BrandHouse(parent, brand.get("brand_id")) if parent else None + + evaluator = TrustGrantEvaluator( + TrustRecordEvaluator(fetcher=fetch, clock=lambda: now), + clock=lambda: now, + seller_authorization=seller_authorization, + resolve_house=resolve_house, + ) + result = await evaluator.check( + agent_origin=signer["origin"], + brand_domain=brand["domain"], + account_operator=claim["account_operator"], + scope=claim["scope"], + brand_id=brand.get("brand_id"), + countries=brand.get("countries", ()), + ) + expected = vector["expected"]["outcome"] + if expected == "grant_absent": + assert not result.authorized, vector["description"] + assert result.reason == "grant_absent", result + assert result.error_code == "PERMISSION_DENIED" + assert not seller_calls, "an exclusive record never defers to the seller" + else: + assert result.authorized, (vector["description"], result) + assert result.source == OUTCOMES[expected], vector["description"] + assert len(seller_calls) == (expected == "seller_discretion") From 5e0886ddfdc47ed81761ea4b01fccd4ce052a932 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 2 Nov 2026 18:32:14 +0000 Subject: [PATCH 2/2] fix(identity): one delegation-chain rule for demand-side grants Ports adcontextprotocol/adcp#8473 to TrustGrantEvaluator: implicit authority when account.operator and the signer's operator are each the brand or its house; at most two grants from the house's record, every covering first grant a candidate, account.operator the brand, house or first grantee; an unreadable intermediary is an outage only when no chain authorizes; exclusive_grants is exhaustive whatever the scope. Adds GrantAuthorization.source. Co-Authored-By: Claude Opus 5.5 --- docs/agent-identity-3-3-migration.md | 8 +- src/adcp/signing/trust.py | 202 +++++++++++++++++++-------- tests/test_experimental_identity.py | 44 ++++++ tests/test_identity_3_3_alignment.py | 7 +- 4 files changed, 195 insertions(+), 66 deletions(-) diff --git a/docs/agent-identity-3-3-migration.md b/docs/agent-identity-3-3-migration.md index 16bdf0f189..917a747c8d 100644 --- a/docs/agent-identity-3-3-migration.md +++ b/docs/agent-identity-3-3-migration.md @@ -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. diff --git a/src/adcp/signing/trust.py b/src/adcp/signing/trust.py index 5239df12b9..0c872b6e78 100644 --- a/src/adcp/signing/trust.py +++ b/src/adcp/signing/trust.py @@ -854,6 +854,12 @@ class GrantAuthorization: include_grant_absent_details: bool = True outage_expires_at: float | None = None stale_on_outage: bool = False + #: How an authorized claim was established: ``"implicit"`` (the brand's or + #: its house's own agent), ``"trust"`` (a grant chain), ``"seller"`` (the + #: seller's discretion over an uncovered claim) or ``"legacy"`` (brand.json + #: ``authorized_operators`` before the house published a trust record). + #: ``None`` when not authorized. + source: Literal["implicit", "trust", "seller", "legacy"] | None = None def require(self) -> None: """Only call after signer establishment/registry authorization, avoiding an oracle.""" @@ -918,19 +924,36 @@ def _instant(value: str) -> float: return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() -def _grant_matches( +#: Candidate intermediary records fetched concurrently for one claim. +_INTERMEDIARY_FETCH_CONCURRENCY = 8 + + +def _registrable_grantee(grant: Mapping[str, Any]) -> str | None: + """The grant's grantee when it is itself a registrable domain, else ``None``. + + Compared exactly: a host grantee is never reduced to its eTLD+1. + """ + grantee = grant["grantee"] + return grantee if isinstance(grantee, str) and _registrable(grantee) == grantee else None + + +def _grant_matches(grant: Mapping[str, Any], *, grantee: str, **claim: Any) -> bool: + # ``grantee`` is a registrable domain; the grant's is compared exactly, + # never reduced (see TrustGrantEvaluator). + return grant["grantee"] == grantee and _grant_covers(grant, **claim) + + +def _grant_covers( grant: Mapping[str, Any], *, - grantee: str, origin: str, scope: str, brand_id: str | None, countries: frozenset[str], now: float, ) -> bool: - # ``grantee`` is a registrable domain; the grant's is compared exactly, - # never reduced (see TrustGrantEvaluator). - if grant["grantee"] != grantee or not _scope_covers(grant["scopes"], scope): + """Every covering condition but the grantee: scope, agents, brands, countries, window.""" + if not _scope_covers(grant["scopes"], scope): return False # agents[] narrows the link that reaches the signer: the signer's # canonical origin must be listed, or the chain ends at this link. @@ -977,8 +1000,9 @@ class TrustGrantEvaluator: consulted. * Domains compare as registrable domains under the pinned PSL snapshot. Authority is implicit when ``account.operator`` and the signer's - operator share one with the brand's domain or with its house's: an - organization's own agents act for its child brands without a grant. + operator are each the brand's domain or its house's (in any + combination): an organization's own agents act for its child brands + without a grant. The house is the one ``resolve_house`` returns, so implicit authority and the authorizing record never disagree; a house the brand claims but that does not reciprocate is the resolver's ``None``, and its operator @@ -989,18 +1013,24 @@ class TrustGrantEvaluator: a denial. * A grant's ``grantee`` must itself be a registrable domain. A grant whose grantee is not one (``ads.platform.com``, or a public suffix such - as ``github.io``) covers no claim, and its ``scopes`` still count for - ``exclusive_grants``. - * 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. + as ``github.io``) covers no claim. + * Otherwise one delegation-chain rule applies (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. ``account.operator`` must be on the chain: the brand, its + house, or the first grant's grantee. Every covering first grant is a + candidate; distinct intermediary records are read concurrently, and an + unreadable one is an outage only when no other chain authorizes. There + is no third link. * Every link must cover the scope, brand, countries and agent narrowing (a narrowed link that does not list the signer ends the chain), and the check time must fall inside every link's ``valid_from``/``valid_until`` window, so effective authority is the intersection along the chain. + * With no covering chain, ``exclusive_grants: true`` on the house's + record makes ``grants[]`` the complete list, whatever the scope: + ``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 flag counts. * 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. A record that cannot be read, @@ -1038,6 +1068,7 @@ async def _authorization( legacy_fallback_allowed: bool = False, agent_origin: str | None = None, legacy_unpublished_domain: str | None = None, + source: Literal["implicit", "trust", "seller", "legacy"] | None = None, ) -> GrantAuthorization: if authorized: try: @@ -1067,6 +1098,7 @@ async def _authorization( else None ), stale_on_outage=any(record.stale_on_outage for record in records), + source=source if authorized else None, ) def _outage_deadline(self, record: EvaluatedTrustRecord) -> float: @@ -1128,7 +1160,9 @@ async def check( return listing signer_record = listing if signer_operator == brand_operator == operator: - return await self._authorization(True, [signer_record], agent_origin=origin) + return await self._authorization( + True, [signer_record], agent_origin=origin, source="implicit" + ) house = brand_domain # Implicit authority on the brand's own domain never depends on its # house, so an RFC 9421 brand-implicit claim keeps reading the @@ -1148,10 +1182,13 @@ async def check( house_operator = _registrable(house) if house_operator is None: return _unlisted("grant_absent") - if signer_record is not None and signer_operator == operator == house_operator: - # The house operates its own child brand: implicit, and the - # signer's record is the house's record. - return await self._authorization(True, [signer_record], agent_origin=origin) + # account.operator and the signer's operator are each the brand or its + # house: implicit. The signer's record, already matched, lists the agent. + own = {brand_operator, house_operator} + if signer_record is not None and signer_operator in own and operator in own: + return await self._authorization( + True, [signer_record], agent_origin=origin, source="implicit" + ) try: brand_record = await self.records.evaluate(house, allow_stale_on_outage=non_spend) except TrustRecordAbsentError: @@ -1167,12 +1204,14 @@ async def check( legacy_fallback_allowed=True, agent_origin=origin, legacy_unpublished_domain=house, + source="legacy", ) return await self._authorization( authorized, [], legacy_fallback_allowed=True, legacy_unpublished_domain=house, + source="legacy", ) return GrantAuthorization(False, "trust_record_absent", legacy_fallback_allowed=True) except TrustRecordError: @@ -1187,8 +1226,10 @@ async def check( if isinstance(listing, GrantAuthorization): return listing signer_record = listing - if signer_operator == operator and operator in {brand_operator, house_operator}: - return await self._authorization(True, [signer_record], agent_origin=origin) + if signer_operator in own and operator in own: + return await self._authorization( + True, [signer_record], agent_origin=origin, source="implicit" + ) args: dict[str, Any] = { "origin": origin, "scope": scope, @@ -1197,52 +1238,95 @@ async def check( "now": self._clock(), } grants = brand_record.document.get("grants", []) + + def first_links() -> dict[str, list[Mapping[str, Any]]]: + """The house's covering grants that can start a chain, by grantee. + + account.operator must be on the chain: the brand, its house, or + the first grant's grantee. + """ + firsts: dict[str, list[Mapping[str, Any]]] = {} + for grant in grants: + grantee = _registrable_grantee(grant) + if grantee is None or (operator not in own and operator != grantee): + continue + if _grant_covers(grant, **args): + firsts.setdefault(grantee, []).append(grant) + return firsts + + # The one delegation-chain rule (L2 Grants in trust records): at most + # two grants from the house's record to the signer's operator, every + # grant covering the claim. There is no third link. + firsts = first_links() links: list[list[Mapping[str, Any]]] = [] used_records = [signer_record, brand_record] - if operator in {signer_operator, brand_operator}: - # The house grants the signer's operator, which is account.operator; - # or a self-operated brand (it is account.operator) whose house - # grants the signer's operator directly. - links = [[g for g in grants if _grant_matches(g, grantee=signer_operator, **args)]] - else: - first = [g for g in grants if _grant_matches(g, grantee=operator, **args)] - if first: - try: - intermediary: EvaluatedTrustRecord | None = await self.records.evaluate( - operator, allow_stale_on_outage=non_spend - ) - except TrustRecordAbsentError: - # The intermediary publishes no record: no second link. - intermediary = None - except TrustRecordError: - return _unavailable("trust_record_unavailable") - second: list[Mapping[str, Any]] = [] - if intermediary is not None: + outage = False + if signer_operator in firsts: + links = [firsts[signer_operator]] + elif firsts: + intermediaries = list(firsts) + semaphore = asyncio.Semaphore(_INTERMEDIARY_FETCH_CONCURRENCY) + + async def read(domain: str) -> EvaluatedTrustRecord | TrustRecordError | None: + async with semaphore: + try: + return await self.records.evaluate(domain, allow_stale_on_outage=non_spend) + except TrustRecordAbsentError: + # The intermediary publishes no record: no second link. + return None + except TrustRecordError as exc: + return exc + + # Each distinct intermediary record is read once, a bounded number + # at a time, so N candidate chains cost about N / concurrency + # fetch timeouts rather than N. + fetched = await asyncio.gather(*(read(domain) for domain in intermediaries)) + # Fetching may outlive an earlier grant: evaluate every link at the + # decision time. + args["now"] = self._clock() + firsts = first_links() + readable: list[EvaluatedTrustRecord] = [] + for domain, intermediary in zip(intermediaries, fetched): + if isinstance(intermediary, TrustRecordError): + # Another chain may still authorize; an unreadable link is + # an outage only if none does. + outage = True + continue + if intermediary is None: + continue + readable.append(intermediary) + second = [ + g + for g in intermediary.document.get("grants", []) + if _grant_matches(g, grantee=signer_operator, **args) + ] + if firsts.get(domain) and second: + links = [firsts[domain], second] used_records.append(intermediary) - # Fetching the intermediary may outlive an earlier grant. - # Evaluate both links at the decision time. - args["now"] = self._clock() - first = [g for g in first if _grant_matches(g, grantee=operator, **args)] - second = [ - g - for g in intermediary.document.get("grants", []) - if _grant_matches(g, grantee=signer_operator, **args) - ] - links = [first, second] + break + if not links: + # A seller decision relies on every record read finding no chain. + used_records.extend(readable) if any(record.expires_at <= self._clock() for record in used_records): return _unavailable("trust_record_unavailable") - if not links or any(not link for link in links): - exhaustive = brand_record.document.get("exclusive_grants", False) and any( - _scope_covers(grant["scopes"], scope) for grant in grants - ) - if not exhaustive and self._seller_authorization is not None: + if not links: + if outage: + return _unavailable("trust_record_unavailable") + # L2 Absence: exclusive_grants on the house's record makes grants[] + # the complete list, whatever the scope. Without it an uncovered + # claim is at the seller's discretion, and a grant that does not + # cover the claim never narrows that discretion. + exclusive = brand_record.document.get("exclusive_grants") is True + if not exclusive and self._seller_authorization is not None: authorized = bool(await self._seller_authorization(request)) # Seller discretion cannot outlive the records it relied on. if any(record.expires_at <= self._clock() for record in used_records): return _unavailable("trust_record_unavailable") - return await self._authorization(authorized, used_records, agent_origin=origin) + return await self._authorization( + authorized, used_records, agent_origin=origin, source="seller" + ) return GrantAuthorization(False, "grant_absent") - # Every surviving grant covers the requested dimensions. The best path + # Every surviving grant covers the requested dimensions. The chain # expires at the minimum over links of each link's latest valid_until. # Avoid materializing the cartesian product of untrusted grant arrays. grant_expiry = min( @@ -1254,7 +1338,7 @@ async def check( if any(record.expires_at <= self._clock() for record in used_records): return _unavailable("trust_record_unavailable") return await self._authorization( - True, used_records, grant_expiry=grant_expiry, agent_origin=origin + True, used_records, grant_expiry=grant_expiry, agent_origin=origin, source="trust" ) diff --git a/tests/test_experimental_identity.py b/tests/test_experimental_identity.py index 4e6840ebad..149b04cc9b 100644 --- a/tests/test_experimental_identity.py +++ b/tests/test_experimental_identity.py @@ -681,3 +681,47 @@ async def fetch(url, *, allow_private=False): agent_origin=ORIGIN, brand_domain="brand.com", account_operator="agency.com" ) assert not result.authorized + + +async def test_unreadable_intermediary_is_an_outage_only_when_no_chain_authorizes(): + # adcp#8473: every covering first grant is a candidate chain. + docs = { + trust_record_url("brand.com"): {"grants": [grant("down.com"), grant("agency.com")]}, + trust_record_url("down.com"): OSError("offline"), + trust_record_url("agency.com"): {"grants": [grant("platform.com")]}, + } + accepted = await check(docs, operator="brand.com") + assert accepted.authorized and accepted.source == "trust" + docs[trust_record_url("agency.com")] = {"grants": []} + outage = await check(docs, operator="brand.com") + assert not outage.authorized and outage.error_code == "SERVICE_UNAVAILABLE" + + +async def test_exclusive_grants_is_exhaustive_whatever_the_scope(): + called = [] + + async def authorize(request): + called.append(request) + return True + + docs = { + trust_record_url(ORIGIN): SIGNER_RECORD, + trust_record_url("brand.com"): { + "grants": [{"grantee": "other.com", "scopes": ["adcp:creative_generation"]}], + "exclusive_grants": True, + }, + } + evaluator, _ = records(docs) + denied = await TrustGrantEvaluator( + evaluator, clock=lambda: 1000, seller_authorization=authorize + ).check(agent_origin=ORIGIN, brand_domain="brand.com", account_operator="agency.com") + assert not denied.authorized and denied.reason == "grant_absent" and not called + # A non-covering grant to the account operator never narrows the seller's discretion. + docs[trust_record_url("brand.com")] = { + "grants": [{"grantee": "agency.com", "scopes": ["adcp:creative_generation"]}] + } + evaluator, _ = records(docs) + allowed = await TrustGrantEvaluator( + evaluator, clock=lambda: 1000, seller_authorization=authorize + ).check(agent_origin=ORIGIN, brand_domain="brand.com", account_operator="agency.com") + assert allowed.authorized and allowed.source == "seller" and len(called) == 1 diff --git a/tests/test_identity_3_3_alignment.py b/tests/test_identity_3_3_alignment.py index 66498b783a..e857074677 100644 --- a/tests/test_identity_3_3_alignment.py +++ b/tests/test_identity_3_3_alignment.py @@ -1677,14 +1677,15 @@ async def test_an_operator_outside_the_brand_and_its_house_still_needs_a_grant() ).check(agent_origin=HOUSE_AGENT, brand_domain="novamotors.com", account_operator="agency.com") assert not other_account.authorized and other_account.reason == "grant_absent" - # The house's agent under its self-operated child brand's account is not - # implicit: account.operator is the brand, not the signer's operator. + # The house's agent under its self-operated child brand's account is + # implicit (adcp#8473): account.operator and the signer's operator are + # each the brand or its house. self_operated = await TrustGrantEvaluator( _grant_records(_house_documents()), resolve_house=_mutual_assertion_house ).check( agent_origin=HOUSE_AGENT, brand_domain="novamotors.com", account_operator="novamotors.com" ) - assert not self_operated.authorized and self_operated.reason == "grant_absent" + assert self_operated.authorized and self_operated.source == "implicit" granted = await TrustGrantEvaluator( _grant_records(