Skip to content

docs(brand-protocol): RFC for distributed brand.json#3533

Closed
bokelley wants to merge 6 commits into
mainfrom
bokelley/rfc-distributed-brand-json
Closed

docs(brand-protocol): RFC for distributed brand.json#3533
bokelley wants to merge 6 commits into
mainfrom
bokelley/rfc-distributed-brand-json

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Tracks #3409. Draft RFC — not normative; needs spec-owner sign-off before any schema or code changes.

Summary

Proposes evolving brand.json from a monolithic house portfolio (one big document containing inline child-brand definitions) to a collection of canonical per-brand documents linked by mutual-assertion pointers. Hosting (static, CDN, brand-agent, AAO-hosted, self-hosted) is independent of the data model and stays an implementation choice.

Key proposed changes

  • Each brand publishes one canonical brand.json owning its own attributes
  • New house pointer for declaring an immediate parent (multi-level chains via recursion: StreetKix → Converse → Nike)
  • New brand_refs[] field replacing inline brands[] content (pointer-only {id, domain})
  • New house_attributes block for inheritable house-wide metadata (privacy, compliance, corporate entity)
  • Mutual-assertion as the canonical trust primitive — child's house must be reciprocated by parent's brand_refs[]
  • Hosting decoupled from data model — small publishers can host on AAO, holdcos can run their own brand-agent, sub-brands can delegate to a parent's brand-agent

Migration

3.x accepts both shapes with deprecation warnings; brand-protocol 2.0 (decoupled from AdCP major) cuts over.

Where it lives

docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx — under a new "Proposals" subgroup in the brand-protocol nav.

Test plan

  • Spec-owner review on data model (house, brand_refs[], house_attributes)
  • Trust-model review (mutual-assertion as canonical primitive; conflict resolution table)
  • Migration-path review (3.x dual-shape acceptance → 2.0 cutover)
  • AAO-API ergonomic note acknowledged (server-side merge keeps the protocol clean)
  • Decision on open questions before promoting from RFC to normative

🤖 Generated with Claude Code

bokelley and others added 2 commits April 29, 2026 11:00
Draft RFC proposing per-brand canonical brand.json documents linked by
mutual-assertion pointers, replacing the monolithic-with-inline-children
shape. Hosting (static / CDN / brand-agent / AAO / self) stays an
implementation choice independent of the data model.

Key proposed changes (subject to discussion):
- Each brand publishes one canonical brand.json owning its own attributes
- New `house` pointer for declaring the immediate parent (multi-level
  chains via recursion)
- New `brand_refs[]` (pointer-only) replacing inline `brands[]` content
- New `house_attributes` block for inheritable house-wide metadata
- Mutual-assertion as the trust primitive — child's `house` must be
  reciprocated by parent's `brand_refs[]`

Migration path: 3.x accepts both shapes with deprecation warnings;
brand-protocol 2.0 (decoupled from AdCP major) cuts over.

Lives at docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx
under a new "Proposals" subgroup. Not yet normative — needs spec-owner
sign-off before any code or schema changes land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RFC stays at docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx
as a working document, but isn't surfaced in the published docs nav.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pkras
Copy link
Copy Markdown
Collaborator

pkras commented Apr 30, 2026

OK OK reviewing - initial thoughts

  1. Might be good to add on top the exact problem we are solving - describing a structural change but is there a visible pain point in the current model and if so - the way i read it: it is that holdco brand teams can't independently update their own data? I think putting this on top will be beneficial also as we are rolling this out. Primarily seems like an operational ownership problem - e.g. ff Converse wants to update their logo in AdCP, someone has to edit Nike's file. Then that becomes an operational pain that gets worse as portfolios get larger and brand teams get more autonomous.

  2. What happens to brands that don’t have their own domain - nike sb vs jordan use case, first does not have it, second one does not. Lots of sub brands live entirely under parent’s domain ->

  • option a)canonical doc lives at the path not just domain - like nike.com/.well-known/brand.json/sb or a registry-hosted URL like agenticadvertising.org/brands/nike-sb/brand.json. The brand_refs[] pointer becomes {domain, brand_id, location?} where location is the canonical URL if it's not at the standard well-known path. -> here we have each brand owns it’s own canonical doc without saying that brand needs to get a domain
  • option b) brands with their own domain get canonical per-brand docs, brands without stay inline under the parent. not clean but reality matching?
  • option c) ?
  1. House field "collision" -> house already means something different
    option a) renaming and migration.... but this is a breaking change i guess
    e.g.Child's file: parent_brand_domain: "nikeinc.com" so it clearly means "my parent is here"
    e.g. Parent's file: keep house as the object, or rename that too to something like house_identity
    option b) version gating? like we say e.g. In v3, house stays as-is (both variants). In v4, house is formally split and string variant is deprecated, new field takes over

…ouse rename

Addresses Pawel's review on PR #3533:

1. Lead with the operational pain point — Converse can't update its own
   logo in AdCP without editing Nike, Inc.'s file. Reframed Motivation.
2. Hybrid model — brands[] (inline, parent-owned) and brand_refs[]
   (pointer, child-owned) both first-class. Pull-based migration:
   brands[] is no longer deprecated. Sub-brands without their own domain
   stay inline.
3. Flat hierarchy — only the house declares ownership via brand_refs[]
   or brands[]. A brand cannot have its own brand_refs[]. Trust collapses
   to a single hop; no recursive walking.
4. Renamed child-side pointer from `house` to `parent_house` to avoid
   collision with the existing `house` declaration object on the root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Thanks @pkras — addressed all three in 424e0f4:

1. Lead with the operational pain. Motivation now opens with the Converse-can't-update-its-own-logo vignette. Brand teams own identity; the protocol forces a single ops choke point at the corporate parent.

2. Brands without their own domain. Going hybrid rather than path-pointers — closer to your option (b), framed positively:

  • brands[] stays first-class (no longer deprecated). Inline children = parent owns the data. Best for sub-brands without their own domain (Nike SB) or holdco-managed brands.
  • brand_refs[] is the new pointer shape. Used when a child has its own domain and wants self-publish authority (Converse, Jordan).
  • A house mixes freely. Migration is pull-based per-child, not a forced cutover. No location? on the pointer needed — domainless brands just stay inline.

3. house field collision. Renamed the child-side pointer to parent_house. Existing house: {domain, name, architecture} on the root keeps its current meaning. Two distinct fields, no overload, no version-gating needed.

Bonus simplification: flat hierarchy. Only the house declares ownership; a brand cannot have its own brand_refs[]. Multi-level real-world arrangements (StreetKix run by Converse but legally owned by Nike, Inc.) flatten in the data model — StreetKix's parent_house points to nikeinc.com directly. Operational delegation is internal to Nike's org, not a protocol concept. Trust model collapses to a single hop.

@bokelley bokelley added the needs-wg-review Blocked on a working-group decision — surface in WG meeting agendas label May 1, 2026
…ed example

check:owned-links treated the example URL as a real link and failed CI
when the path 404'd. Wrap the brand-domain segment as \${domain} so the
linter recognizes it as a placeholder (it skips URLs containing \${).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Concrete schema cut for review: #3764 (DRAFT). Reviewers can see the actual JSON Schema diffs (new Brand Canonical Document variant + brand_refs[] / house_attributes / parent_house fields) and react to the real shape rather than prose. Will land only after this RFC ratifies.

…_domain; add managed_by

Resolves the second round of expert review on PR #3533.

Substantive changes:
- Drop house_attributes / house_attributes_overrides / house_attributes_locked
  entirely. Inheritance/override semantics turned out to be muddy: if a brand
  could weaken a house policy, it wasn't really a house policy. House-level
  fields (data_subject_contestation, trademarks, authorized_operators) are
  already on the house schema; consumers walk house_domain to read them.
  Brand-level additions are just brand-level fields.
- Rename child-side pointer from parent_house ({domain}) to house_domain
  (string). Reuses the existing #/definitions/domain pattern. Drops the
  proposed core/house-ref.json file. Matches the existing House Redirect's
  string-domain convention.
- Add managed_by (string, optional) on brand_refs[] entries. House-declared
  delegation for grouping/discovery. Non-trust-bearing. Captures WPP/Publicis
  reality without reintroducing recursive trust.
- Make house_domain optional on the Brand Canonical Document so standalone
  brands (Patagonia, Liquid Death) have a valid shape. Acquisition adds
  house_domain later; no migration required.
- Add an explicit M&A section: existing redirect variants (House Redirect,
  Authoritative Location Redirect) handle reorganizations; resolution follows
  redirects through house_domain.
- Add a clear field-resolution table: where each consumer-side question is
  answered. No inheritance/override, just per-brand vs per-house ownership.

Trust model still single-hop, mutual-assertion via house_domain ↔ brand_refs[].
Migration still pull-based. brands[] still first-class (not deprecated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 2, 2026
Concrete schema additions for review against RFC #3533. Additive only —
existing publishers unchanged.

brand.json schema:
- New variant: Brand Canonical Document. Self-published per-brand doc
  with `parent_house: BrandRef` pointer + optional house_attributes_overrides.
  Composes the existing `brand` definition via allOf so identity fields
  match the inline `brands[]` shape.
- House Portfolio variant gains `brand_refs[]` (pointer brands, child-owned
  data) and `house_attributes` (house-wide inheritable attributes). Required
  changed from ["house","brands"] to ["house"] with anyOf at-least-one of
  brands[]/brand_refs[].
- Two new examples illustrating mixed inline+pointer house and a self-
  published Converse canonical document.

docs/brand-protocol/brand-json.mdx: added a "Proposed (RFC)" callout
pointing at the RFC and PR #3533. Existing four variants documented as-is.

Cross-array invariant — a brand_id MUST NOT appear in both brands[] and
brand_refs[] of the same house — is documented in field descriptions.
JSON Schema can't express it; lint/validator follow-up needed if RFC ratifies.

Status: review-only. Not normative until RFC ratifies. Marked DRAFT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 2, 2026
…managed_by + drop house_attributes

Tracks RFC #3533 v3. Replaces the v2 cut with the simplified model decided
during expert review.

Schema deltas (relative to previous impl cut):
- Replace parent_house ($ref core/brand-ref.json) with house_domain (string,
  reuses #/definitions/domain). Strings match the existing House Redirect
  convention and drop the planned core/house-ref.json file entirely.
- Make house_domain optional on Brand Canonical Document so standalone brands
  (Patagonia, Liquid Death) have a valid shape without spinning up a degenerate
  "house of one." If a standalone brand is later acquired, it adds house_domain
  and the new house adds it to brand_refs[]. No new variant needed.
- Drop house_attributes / house_attributes_overrides entirely. Inheritance/
  override semantics turned out muddy — if a brand could weaken a house policy,
  it wasn't really a house policy. House-level fields stay where they already
  are (data_subject_contestation, trademarks, authorized_operators on the
  house schema). Brand-level constraints are additive, not overrides.
- Replace the brand_refs[] $ref to core/brand-ref.json with an inline
  {domain, brand_id?, managed_by?} shape. brand-ref.json's existing
  governance-override fields (industries, data_subject_contestation) don't
  belong on a house-side declaration; they're consumer-side overrides.
- Add managed_by (string, optional) on brand_refs[] entries — house-declared
  delegation for grouping/discovery, non-trust-bearing. Captures WPP/Publicis
  reality (BBH manages this brand for WPP) without reintroducing recursive
  trust.

Examples updated:
- Nike Inc. mixed-shape (inline Nike SB + pointer Converse, Jordan)
- WPP with managed_by (BBH Sport managed_by bbh.com, etc.)
- Converse self-published with house_domain
- Patagonia standalone (no house_domain)

docs/brand-protocol/brand-json.mdx: rewritten Distributed Extensions section
to match the new shape. Adds the four worked examples. Adds field-resolution
table showing where each consumer-side question is answered (no inheritance,
no overrides). Adds explicit M&A section showing existing redirect variants
handle reorganizations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rmance, strictest-of compliance

Addresses round-2 expert review on RFC v3:

- managed_by gets normative language: MUST NOT be used for trust/auth
  decisions; verifiers MUST ignore for authorization. UIs SHOULD render as
  unilateral house claim, not aggregate cross-house.
- New "Compliance fields: strictest-of resolution" section. Identity fields
  (logos, voice, tone) stay brand-wins. Compliance/governance fields
  (data_subject_contestation, compliance_policies, regulated-category flags)
  resolve as union/strictest of house-level and brand-level — brand cannot
  weaken house assertions, only add stricter constraints.
- New "Standalone brands" subsection: absence of house_domain ⇒ standalone,
  regardless of one-sided third-party brand_refs[] claims.
- New "Conformance" section formalizing brand_id cross-array uniqueness,
  within-array uniqueness, mutual-assertion as canonical trust primitive,
  managed_by non-trust, standalone trumps third-party claim, strictest-of
  rule, 180-day TTL.
- New "Prior art" section citing IAB Tech Lab ads.txt/sellers.json reciprocal
  publication model — same trust shape, deployed industry pattern.

No structural changes to the data model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

RFC v3 + tightening commit 91f80f5be7 — second-round expert review (protocol, code, product) all returned with no blockers. Three substantive RFC tightenings folded in:

  1. managed_by normative language. Spec now says: MUST NOT be used for trust/auth decisions; verifiers MUST ignore for authorization. UIs SHOULD render as unilateral house claim, not aggregate cross-house.
  2. Compliance fields strictest-of resolution. Identity fields (logos, voice, tone) stay brand-wins. Compliance/governance fields (data_subject_contestation, compliance_policies, regulated-category flags) resolve as union/strictest of house-level and brand-level — brand cannot weaken house assertions, only add stricter constraints. Closes the "brand publishes weaker contestation policy than its house" hole the product expert flagged.
  3. Standalone resolution rule. Absence of `house_domain` ⇒ standalone, regardless of any one-sided `brand_refs[]` claim from a third-party house.

Plus:

  • New Conformance section formalizing the invariants validators must enforce (brand_id cross-array + within-array uniqueness, mutual-assertion as canonical trust primitive, managed_by non-trust, strictest-of for compliance, 180-day TTL).
  • New Prior art section citing IAB Tech Lab ads.txt/sellers.json — same reciprocal-publication trust shape, deployed industry pattern.

No structural changes to the data model. Pairs with impl PR #3764 commit `07cfa8c61`.

bokelley added a commit that referenced this pull request May 3, 2026
…d.json redirect variants

Both Authoritative Location Redirect and House Redirect gain two optional
fields for handling transitional state cleanly:

- redirect_reason (enum: acquisition | rebrand | regional | legacy |
  consolidation | other) — structured signal for cache TTL handling.
  In-transition reasons (acquisition, rebrand, consolidation) tell consumers
  to shorten cache TTL until stable. Stable reasons (regional, legacy) keep
  standard caching. Free-text rationale stays in note.
- redirect_effective_at (ISO 8601 timestamp) — caches treat anything cached
  before this timestamp as stale and re-fetch.

Both fields are additive and optional. Existing publishers unchanged.

Motivated by review of the distributed brand.json RFC (#3533) — the M&A
migration path uses existing redirect variants (House Redirect from Dentsu
to WPP after acquisition), and DSPs/crawlers/prebid configs need to know
when to invalidate cached portfolio state. Free-text note is fine for
humans but not parseable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 3, 2026
…d.json redirect variants (#3912)

* feat(brand-protocol): redirect_reason + redirect_effective_at on brand.json redirect variants

Both Authoritative Location Redirect and House Redirect gain two optional
fields for handling transitional state cleanly:

- redirect_reason (enum: acquisition | rebrand | regional | legacy |
  consolidation | other) — structured signal for cache TTL handling.
  In-transition reasons (acquisition, rebrand, consolidation) tell consumers
  to shorten cache TTL until stable. Stable reasons (regional, legacy) keep
  standard caching. Free-text rationale stays in note.
- redirect_effective_at (ISO 8601 timestamp) — caches treat anything cached
  before this timestamp as stale and re-fetch.

Both fields are additive and optional. Existing publishers unchanged.

Motivated by review of the distributed brand.json RFC (#3533) — the M&A
migration path uses existing redirect variants (House Redirect from Dentsu
to WPP after acquisition), and DSPs/crawlers/prebid configs need to know
when to invalidate cached portfolio state. Free-text note is fine for
humans but not parseable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(brand-protocol): protocol expert tightenings on redirect ergonomics

Expert review on PR #3912:

- Add `divestiture` to redirect_reason enum. Real M&A direction not covered
  by acquisition (target gained) or consolidation (folded in) — when an
  owning entity spins out or sells off a sub-brand, the redirect points at
  a different ownership chain. Adding now is cheap; later requires consumer
  enum-handling updates.
- Tighten redirect_effective_at description from SHOULD to MUST. TTL-
  shortening on redirect_reason stays SHOULD (consumers can't always
  shorten — prebid configs are bundled at build time), but the timestamp is
  the hard invariant: caches MUST treat any entry cached before this
  timestamp as stale. That timestamp is what closes the cache-poisoning gap
  regardless of TTL.

Docs page also notes this is the brand.json analogue to ads.txt OWNERDOMAIN
and sellers.json seller_type migration patterns — both of which historically
relied on out-of-band coordination with no machine-readable signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 3, 2026
…3924)

* docs(trust): add Identity surface to trust.mdx (7th trust surface)

Documents brand.json + adagents.json identity declarations, the
mutual-assertion model from #3533, and AAO Verified behavioral
attestation. Three explicit gaps called out: aggregated public-registry
identity claims, buyer-side authorization symmetric to adagents.json,
and operator/human KYC.

Compliance-reviewer table gets brand identity, mutual-assertion trust
state, agent identity, and behavioral verification rows.

Master tracking issue link is a TBD-master placeholder; will be
updated when the master issue is filed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(trust): wire trust.mdx master-issue link to #3925

Replaces the TBD-master placeholder with the actual master tracking
issue number now that #3925 is filed. The aggregation RFC #3926 is
referenced from #3925's Identity surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 14, 2026
…ademarks (#4505)

* docs(brand-protocol): RFC for distributed brand.json (#3409)

Draft RFC proposing per-brand canonical brand.json documents linked by
mutual-assertion pointers, replacing the monolithic-with-inline-children
shape. Hosting (static / CDN / brand-agent / AAO / self) stays an
implementation choice independent of the data model.

Key proposed changes (subject to discussion):
- Each brand publishes one canonical brand.json owning its own attributes
- New `house` pointer for declaring the immediate parent (multi-level
  chains via recursion)
- New `brand_refs[]` (pointer-only) replacing inline `brands[]` content
- New `house_attributes` block for inheritable house-wide metadata
- Mutual-assertion as the trust primitive — child's `house` must be
  reciprocated by parent's `brand_refs[]`

Migration path: 3.x accepts both shapes with deprecation warnings;
brand-protocol 2.0 (decoupled from AdCP major) cuts over.

Lives at docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx
under a new "Proposals" subgroup. Not yet normative — needs spec-owner
sign-off before any code or schema changes land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(brand-protocol): drop Proposals subgroup from docs.json

RFC stays at docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx
as a working document, but isn't surfaced in the published docs nav.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(brand-protocol): RFC v2 — hybrid model, flat hierarchy, parent_house rename

Addresses Pawel's review on PR #3533:

1. Lead with the operational pain point — Converse can't update its own
   logo in AdCP without editing Nike, Inc.'s file. Reframed Motivation.
2. Hybrid model — brands[] (inline, parent-owned) and brand_refs[]
   (pointer, child-owned) both first-class. Pull-based migration:
   brands[] is no longer deprecated. Sub-brands without their own domain
   stay inline.
3. Flat hierarchy — only the house declares ownership via brand_refs[]
   or brands[]. A brand cannot have its own brand_refs[]. Trust collapses
   to a single hop; no recursive walking.
4. Renamed child-side pointer from `house` to `parent_house` to avoid
   collision with the existing `house` declaration object on the root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(brand-protocol): use template-syntax placeholder in RFC AAO-hosted example

check:owned-links treated the example URL as a real link and failed CI
when the path 404'd. Wrap the brand-domain segment as \${domain} so the
linter recognizes it as a placeholder (it skips URLs containing \${).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(brand-protocol): RFC v3 — drop house_attributes; rename to house_domain; add managed_by

Resolves the second round of expert review on PR #3533.

Substantive changes:
- Drop house_attributes / house_attributes_overrides / house_attributes_locked
  entirely. Inheritance/override semantics turned out to be muddy: if a brand
  could weaken a house policy, it wasn't really a house policy. House-level
  fields (data_subject_contestation, trademarks, authorized_operators) are
  already on the house schema; consumers walk house_domain to read them.
  Brand-level additions are just brand-level fields.
- Rename child-side pointer from parent_house ({domain}) to house_domain
  (string). Reuses the existing #/definitions/domain pattern. Drops the
  proposed core/house-ref.json file. Matches the existing House Redirect's
  string-domain convention.
- Add managed_by (string, optional) on brand_refs[] entries. House-declared
  delegation for grouping/discovery. Non-trust-bearing. Captures WPP/Publicis
  reality without reintroducing recursive trust.
- Make house_domain optional on the Brand Canonical Document so standalone
  brands (Patagonia, Liquid Death) have a valid shape. Acquisition adds
  house_domain later; no migration required.
- Add an explicit M&A section: existing redirect variants (House Redirect,
  Authoritative Location Redirect) handle reorganizations; resolution follows
  redirects through house_domain.
- Add a clear field-resolution table: where each consumer-side question is
  answered. No inheritance/override, just per-brand vs per-house ownership.

Trust model still single-hop, mutual-assertion via house_domain ↔ brand_refs[].
Migration still pull-based. brands[] still first-class (not deprecated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(brand-protocol): schema cut for distributed brand.json RFC (#3533)

Concrete schema additions for review against RFC #3533. Additive only —
existing publishers unchanged.

brand.json schema:
- New variant: Brand Canonical Document. Self-published per-brand doc
  with `parent_house: BrandRef` pointer + optional house_attributes_overrides.
  Composes the existing `brand` definition via allOf so identity fields
  match the inline `brands[]` shape.
- House Portfolio variant gains `brand_refs[]` (pointer brands, child-owned
  data) and `house_attributes` (house-wide inheritable attributes). Required
  changed from ["house","brands"] to ["house"] with anyOf at-least-one of
  brands[]/brand_refs[].
- Two new examples illustrating mixed inline+pointer house and a self-
  published Converse canonical document.

docs/brand-protocol/brand-json.mdx: added a "Proposed (RFC)" callout
pointing at the RFC and PR #3533. Existing four variants documented as-is.

Cross-array invariant — a brand_id MUST NOT appear in both brands[] and
brand_refs[] of the same house — is documented in field descriptions.
JSON Schema can't express it; lint/validator follow-up needed if RFC ratifies.

Status: review-only. Not normative until RFC ratifies. Marked DRAFT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(brand-protocol): tighten Brand Canonical Document variant + fix parent_house example

Code-reviewer + protocol-expert findings on PR #3764:

- Brand Canonical Document variant lacked oneOf disambiguation.
  brand definition has additionalProperties: true and the inner allOf
  member had no constraint, so a malformed Portfolio with a stray
  parent_house could silently re-type as a Canonical Document. Replaced
  the narrow not: {required: ["brand_refs"]} with not: {anyOf: [...]}
  blocking all house-only top-level keys (house, brands, brand_refs,
  house_attributes, authorized_operators).
- Fixed example: parent_house: { domain: "nikeinc.com", brand_id: "converse" }
  read as "I'm pointing at converse inside nikeinc.com" — but Converse owns
  this document; the brand_id was the self, not the parent. parent_house
  is a pointer UP, only domain is meaningful for a typical house pointer.
  Updated both the schema example and the docs section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(brand-protocol): align impl with RFC v3 — house_domain string + managed_by + drop house_attributes

Tracks RFC #3533 v3. Replaces the v2 cut with the simplified model decided
during expert review.

Schema deltas (relative to previous impl cut):
- Replace parent_house ($ref core/brand-ref.json) with house_domain (string,
  reuses #/definitions/domain). Strings match the existing House Redirect
  convention and drop the planned core/house-ref.json file entirely.
- Make house_domain optional on Brand Canonical Document so standalone brands
  (Patagonia, Liquid Death) have a valid shape without spinning up a degenerate
  "house of one." If a standalone brand is later acquired, it adds house_domain
  and the new house adds it to brand_refs[]. No new variant needed.
- Drop house_attributes / house_attributes_overrides entirely. Inheritance/
  override semantics turned out muddy — if a brand could weaken a house policy,
  it wasn't really a house policy. House-level fields stay where they already
  are (data_subject_contestation, trademarks, authorized_operators on the
  house schema). Brand-level constraints are additive, not overrides.
- Replace the brand_refs[] $ref to core/brand-ref.json with an inline
  {domain, brand_id?, managed_by?} shape. brand-ref.json's existing
  governance-override fields (industries, data_subject_contestation) don't
  belong on a house-side declaration; they're consumer-side overrides.
- Add managed_by (string, optional) on brand_refs[] entries — house-declared
  delegation for grouping/discovery, non-trust-bearing. Captures WPP/Publicis
  reality (BBH manages this brand for WPP) without reintroducing recursive
  trust.

Examples updated:
- Nike Inc. mixed-shape (inline Nike SB + pointer Converse, Jordan)
- WPP with managed_by (BBH Sport managed_by bbh.com, etc.)
- Converse self-published with house_domain
- Patagonia standalone (no house_domain)

docs/brand-protocol/brand-json.mdx: rewritten Distributed Extensions section
to match the new shape. Adds the four worked examples. Adds field-resolution
table showing where each consumer-side question is answered (no inheritance,
no overrides). Adds explicit M&A section showing existing redirect variants
handle reorganizations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(brand-protocol): RFC v3 tightening — normative managed_by, conformance, strictest-of compliance

Addresses round-2 expert review on RFC v3:

- managed_by gets normative language: MUST NOT be used for trust/auth
  decisions; verifiers MUST ignore for authorization. UIs SHOULD render as
  unilateral house claim, not aggregate cross-house.
- New "Compliance fields: strictest-of resolution" section. Identity fields
  (logos, voice, tone) stay brand-wins. Compliance/governance fields
  (data_subject_contestation, compliance_policies, regulated-category flags)
  resolve as union/strictest of house-level and brand-level — brand cannot
  weaken house assertions, only add stricter constraints.
- New "Standalone brands" subsection: absence of house_domain ⇒ standalone,
  regardless of one-sided third-party brand_refs[] claims.
- New "Conformance" section formalizing brand_id cross-array uniqueness,
  within-array uniqueness, mutual-assertion as canonical trust primitive,
  managed_by non-trust, standalone trumps third-party claim, strictest-of
  rule, 180-day TTL.
- New "Prior art" section citing IAB Tech Lab ads.txt/sellers.json reciprocal
  publication model — same trust shape, deployed industry pattern.

No structural changes to the data model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(brand-protocol): schema review fixes — extract brand_ref definition + tighten not deny-list

Code-reviewer round-2 nits on PR #3764:

- Extract brand_refs[] item shape from inline to #/definitions/brand_ref
  (named type for SDK codegen / generator output). Description includes the
  managed_by non-trust normative language directly on the field.
- Tighten Brand Canonical Document not.anyOf to also block House Redirect's
  region and note keys. Disambiguation against House Redirect was already
  clean via id+names requirement, but the deny-list now matches all four
  other variants' top-level fields explicitly.

No example or behavior changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(brand-protocol): ratify distributed brand.json + fold RFC + typed brand-level trademarks

Consolidates #3409 / #3533 (RFC) / #3764 (schema cut) / #3910 (fold) / #3909
(typed trademarks) into one normative PR.

**Fold (#3910):** RFC content (Motivation, Conformance, strictest-of compliance
resolution, Prior art) absorbed into docs/brand-protocol/brand-json.mdx as
the normative spec. Proposals subdir and the standalone RFC file deleted.
Variant list now reads as five variants from the top; Brand Canonical Document
slots in as #5 alongside the other four.

**Typed trademarks (#3909):** New #/definitions/trademark extracts the inline
house-portfolio shape ({registry, number, mark}) with optional status,
license_type, countries. brand definition gains trademarks: Trademark[].
House Portfolio's inline trademarks[] migrated to $ref. House-level + brand-
level resolution is union (both lists are valid claims).

Existing publishers are unaffected — all additions are optional, the existing
four variants are unchanged, and the inline trademark shape continues to
validate against the extracted definition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(brand-protocol): expert-review fixes — third trust tier, schema tightening, conformance polish

Folds protocol/product/docs expert review on PR #4505. No design rollbacks;
all changes tightening or expanding.

Schema
- Rename #/definitions/brand_ref → portfolio_entry (disambiguate from
  buyer-side core/brand-ref.json which identifies brands in media plans).
  Make brand_id required on portfolio_entry so cross-array invariant is
  enforceable. Add optional effective_at (ISO 8601) so consumers age edges
  from a publisher-anchored date.
- Brand Canonical Document not.anyOf deny-list adds authoritative_location,
  redirect_reason, redirect_effective_at — closes ambiguous-match holes
  against redirect variants.
- Trademark gains optional licensor_domain (when license_type=licensed_in)
  and nice_classes (Nice Classification for cross-industry disambiguation
  — Delta-airline vs Delta-faucet).

Trust model
- Three-tier trust table: brand-identity (TLS-only) and brand-relationships
  (mutual-assertion-gated) resolve separately. A leaf-only edge keeps
  identity trust; only relationships block.
- Self-healing notification SHOULD: consumers SHOULD email the house's
  contact.email when they encounter a leaf-only edge, so the parent team
  can complete the reciprocal entry. Rate-limited per {leaf, house}.
- managed_by reframed as a directory field (aggregation across houses is
  the intended use); MUST NOT trust kept, SHOULD NOT aggregate dropped —
  the latter was fiction.

Conformance
- New invariants: house_domain MUST NOT appear in brands[]; brand_refs[]
  unique by domain (not just brand_id); mutual-assertion verification MUST
  follow House Redirects on the house side; strictest-of compliance
  expanded to include policy_categories and brand-level disclaimers[];
  edge-aging language reframed around effective_at rather than a fixed
  180-day SHOULD.
- Resolution algorithm: "resolve recursively (single hop)" → "resolve once"
  with explicit clause that the followed document MUST be a Brand Canonical
  Document.

Docs / structure
- Variant 4/5 cross-reference trust model forward instead of asserting it
  inline — variant 5 reads cleanly when first encountered.
- Variant 5 field table fully enumerates top-level fields and explicitly
  lists prohibited fields.
- New "Adopting brand_refs[] for an existing portfolio" subsection
  documenting the migration path and AAO registry behaviour.
- New "Out of scope" subsection: JVs with two parents, PE-opacity rollups,
  jurisdictional governance divergence — explicitly outside brand.json's
  scope (brand identity ≠ corporate legal structure).
- Prior art expanded with app-ads.txt and WebFinger / host-meta (RFC 7033,
  6415) as IETF analogues for well-known + JSON resource discovery.
- Frontmatter description and managed_by prose updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(release-notes): kick off 3.1.0 entry with distributed brand.json headline

3.1 doesn't ship today; minor releases accumulate from changesets. This
opens the 3.1 section with PR #4505 as the headline feature so the
narrative space exists when subsequent 3.1 changesets land.

Includes adopter-action table for the publisher-visible behaviour change
(trademark string drift) and links to the four design follow-ups (#4521
verification endpoint, #4522 JV multi-parent, #4523 PE-opacity tradeoff,
#4524 manager-edge reciprocation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley
Copy link
Copy Markdown
Contributor Author

Superseded by #4505 (merged 2026-05-14). RFC content folded into the normative spec at docs/brand-protocol/brand-json.mdx per the plan in #3910. The proposals/distributed-brand-json-rfc.mdx file is deleted; everything load-bearing from the RFC now lives in the normative spec.

@bokelley bokelley closed this May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-wg-review Blocked on a working-group decision — surface in WG meeting agendas

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants