Skip to content

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

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

feat(brand-protocol): schema cut for distributed brand.json RFC#3764
bokelley wants to merge 10 commits into
mainfrom
bokelley/rfc-distributed-brand-json-impl

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 1, 2026

Draft / do-not-merge. Implementation cut for RFC #3533. Lets reviewers see the concrete schema shape and signal formal approval against actual JSON Schema diffs rather than prose.

Pairs with #3533 (RFC). This PR will land only after the RFC ratifies.

What's in the schema

Additive additions to static/schemas/source/brand.json. Existing publishers and existing four variants are unchanged.

New variant — Brand Canonical Document

Self-published per-brand document carrying the brand's identity attributes plus:

  • parent_house: BrandRef — pointer to the corporate house this brand belongs to. The named house's brand_refs[] MUST reciprocate for mutual-assertion trust.
  • house_attributes_overrides — per-brand overrides for inherited house_attributes.

Composes the existing brand definition via allOf so a self-published canonical document carries the same identity fields a brands[] entry would.

House Portfolio variant additions

  • brand_refs: BrandRef[] — pointer brands (child-owned data). Each entry references a brand whose canonical document is published elsewhere.
  • house_attributes — house-wide inheritable attributes (privacy policy, compliance flags, corporate legal entity).
  • required widened from ["house", "brands"] to ["house"] with anyOf requiring at least one of brands[] or brand_refs[].

Cross-array invariant

A brand_id MUST NOT appear in both brands[] and brand_refs[] of the same house. Documented in field descriptions; JSON Schema can't express it directly. If the RFC ratifies, follow-up work adds a lint/validator rule.

Examples

Two new entries in the schema's examples array illustrating:

  1. Mixed-shape House Portfolio (Nike, Inc. with brands[]: [Nike SB inline] and brand_refs[]: [converse, jordan]).
  2. Brand Canonical Document (Converse self-published, declaring parent_house: { domain: nikeinc.com }).

Docs

docs/brand-protocol/brand-json.mdx gets a "Proposed (RFC)" callout pointing at the RFC and this PR. Existing four-variant documentation stays unchanged.

Validation

  • npm run build:schemas — compiles
  • npm run test:schemas — 7/7 passes (schemas valid, refs resolve, examples validate)
  • npm run test:examples — 34/34 passes
  • npm run test:json-schema — 255 $schema-tagged JSON blocks validate
  • npm run test:composed — 32/32 passes (bundled schemas compile standalone)
  • npm run check:owned-links — clean
  • npm run typecheck — clean

Test plan

  • Spec-owner review of concrete schema shape against RFC text
  • Decision on whether brand_refs[] should reference core/brand-ref.json (current choice) or get its own type
  • Decision on whether to add a JSON-level validator for the cross-array brand_id uniqueness invariant in this PR or as follow-up
  • Land only after RFC docs(brand-protocol): RFC for distributed brand.json #3533 ratifies

🤖 Generated with Claude Code

bokelley and others added 4 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>
…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>
…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 added a commit that referenced this pull request May 2, 2026
…arent_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>
bokelley and others added 4 commits May 2, 2026 19:51
…_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>
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>
…arent_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>
…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>
@bokelley bokelley force-pushed the bokelley/rfc-distributed-brand-json-impl branch from 6d591ab to 7df840d Compare May 2, 2026 23:57
bokelley and others added 2 commits May 2, 2026 20:02
…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>
…ion + 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>
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

Schema v3 + review fixes commit `07cfa8c61` — second-round expert review returned no blockers; folded in two cleanups:

  1. Extracted `brand_refs[]` item shape from inline to `#/definitions/brand_ref` for SDK codegen / generator output. Description includes the `managed_by` non-trust normative language directly on the field.
  2. Tightened 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.

Round-2 confirmed:

  • oneOf disambiguation holds against all four existing variants (Authoritative Location Redirect, House Redirect, Brand Agent, House Portfolio).
  • All four examples validate (Nike mixed inline+pointer, WPP with managed_by delegation, Converse self-published, Patagonia standalone).
  • `type: object` present at variant root.
  • Round-1 fixes (additionalProperties hole, weak `not`, parent_house example, type:object) all landed.

Validation: schemas (7/7), examples (34/34), json-schema (259 blocks), composed (32/32), typecheck, owned-links — clean.

Open follow-up (not in this PR): typed `trademarks` field on Brand Canonical Document. Brand definition currently accepts trademarks via `additionalProperties: true` but doesn't type them. Track separately.

Pairs with RFC PR #3533 commit `91f80f5be7`.

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

Issue #3910 proposes folding the distributed-brand-json RFC content into docs/brand-protocol/brand-json.mdx and deleting docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx — same surface as this PR; consider folding before merge or confirm as a follow-up.


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 3, 2026

Issue #3909 proposes typed trademarks: Trademark[] on #/definitions/brand (extracting the inline house-portfolio shape into a named trademark definition, optionally extended with status, license_type, countries) — same surface (brand.json) as this PR; consider folding before merge or confirm follow-up.


Generated by Claude Code

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). The schema cut, RFC fold (#3910), typed trademarks (#3909), expert-review fixes, and 3.1 release-notes entry all consolidated into the merged PR.

@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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant