Skip to content

fix(registry): gate brand.json serving on source_type to prevent enriched data misrepresentation#3529

Merged
bokelley merged 1 commit into
mainfrom
claude/issue-3528-brand-json-source-type-gate
May 14, 2026
Merged

fix(registry): gate brand.json serving on source_type to prevent enriched data misrepresentation#3529
bokelley merged 1 commit into
mainfrom
claude/issue-3528-brand-json-source-type-gate

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Closes #3528

The /brands/:domain/brand.json endpoint was serving Brandfetch-enriched entries as if they were brand-attested brand.json files. The response spread raw Brandfetch data (name, url, company, description, classification) at the root level of the response, injected a non-spec name field, and omitted $schema — producing 40+ validation errors against the brand.json schema, which has additionalProperties: false on all four variants.

Changes:

  • Gate on source_type: only brand_json and community source types are served; enriched (Brandfetch) and any other non-attested type returns 404. Serving third-party commercial data under a brand-attested URL path is both a protocol correctness failure and a trust/consent violation.
  • Community entries additionally require review_status !== 'pending' and a two-field structural shape check (presence of house+brands, house string, agents, brand_agent, or authoritative_location) before being served.
  • Inject $schema: https://adcontextprotocol.org/schemas/v3/brand.json when the stored manifest lacks a well-formed HTTPS $schema value. Uses the /schemas/v3/ alias (released stable schema) per playbook schema URL conventions.
  • Remove the erroneous root-level name injection — name is not a top-level field in any brand.json variant; its presence caused additionalProperties: false failures on every served document.

Non-breaking justification: Consumers currently receiving invalid enriched data will now receive 404. A 404 is strictly less harmful than invalid JSON that silently fails schema validation. The A2UI theming feature (brand-viewer.html) already has a graceful 404 fallback path and is unaffected. No schema changes.

Pre-PR review:

  • code-reviewer: approved — raised two blockers (community review_status gate, $schema URL alias) and one nit ($schema pass-through validation), all addressed in the final diff
  • ad-tech-protocol-expert: approved — confirmed additionalProperties: false on all four oneOf variants makes the root name injection a spec violation; confirmed 404 for enriched is protocol-correct; confirmed /schemas/v3/ is the correct alias for this released schema

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See #3121
for context.

Session: https://claude.ai/code/session_01PR6QXBoh1GW1qqrnz6jEkY


Generated by Claude Code

@bokelley bokelley added the claude-triaged Issue has been triaged by the Claude Code triage routine. Remove to re-triage. label Apr 29, 2026
@bokelley bokelley marked this pull request as ready for review May 14, 2026 01:42
@bokelley bokelley merged commit 43d2650 into main May 14, 2026
13 checks passed
@bokelley bokelley deleted the claude/issue-3528-brand-json-source-type-gate branch May 14, 2026 01:43
bokelley added a commit that referenced this pull request May 18, 2026
… to verified owners (#4743)

* fix(brand-viewer): restore community brand serving + lock logo writes to verified owners

Issue A — /brands/{domain}/brand.json silently started 404ing for
hand-curated rows after #3529. Two root causes:
  - editDiscoveredBrand never promoted source_type when a human edited,
    so Brandfetch-seeded rows that AAO members curated (scope3.com,
    fandom.com) stayed source_type='enriched' and tripped the new gate.
  - The structural-shape check in #3529 demanded house/brands/agents/
    brand_agent/authoritative_location at the manifest root; the AAO
    edit UI writes flat manifests that don't fit any of those shapes.

Fix: drop the structural-shape check (the source_type gate alone keeps
raw Brandfetch data out); promote enriched→community in
editDiscoveredBrand; one-shot migration backfills any enriched row that
already has revisions (= a human curated it).

Issue B — logo uploads auto-approved instantly for any AAO member,
including on brands they don't own (#3393). Brian's call: if a brand
has a verified owner, only that org can write. Implemented:
  - Verified owner exists + caller is org member → 201 approved
  - Verified owner exists + caller is not in org → 403
    verified_owner_required
  - No verified owner → community uploads queue as pending review

Walks back the community half of #3393's auto-approval. Moderation
queue throughput is an ops problem.

24 unit tests across 3 new files cover the brand.json gate, source_type
promotion (enriched promotes, community doesn't churn, brand_json still
rejected), and the full upload auth matrix.

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

* chore(test): drop unused PNG_HEADER constant flagged by code-quality bot

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

* fix(brand-viewer): address expert-review feedback

Defense-in-depth from security + code review of #4743:

- Lock Addie's upload_brand_logo MCP tool to the same write-authority
  gate as the HTTP route. Previously the tool hardcoded
  review_status='approved' and bypassed the verified-owner check, making
  it a parallel door past the route-level fix. Now refuses on
  verified-owned brands and queues as 'pending' otherwise.

- Restrict enriched→community promotion in editDiscoveredBrand to actual
  brand-content changes from non-system editors. Previously, any edit
  call promoted enriched rows — including the audit-only revision the
  logo-upload route writes (no manifest input) and the manifest-rebuild
  call the logo-service makes after approval (system caller, just refreshes
  the logos array). Either path was a stealth route to publish raw
  Brandfetch data under the community label.

- Mirror the rule in migration 483: only backfill enriched rows whose
  brand_revisions contain at least one non-system-attributed entry.
  Logo-upload manifest-rebuild revisions (system:logo-service) no longer
  trigger retroactive promotion.

- Improve 403 copy on verified_owner_required: include a claim_url
  pointing at /brand/builder?domain=... so rejected users know how to
  prove ownership instead of giving up.

- Add a "queued for moderator review" message to community-upload 201
  responses so the uploader knows their change isn't live yet.

- Replace empty catch around the audit revision write with a logger.debug
  call.

41 unit tests across 4 files pass (incl. new addie-upload-brand-logo-auth
suite). Typecheck clean.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-triaged Issue has been triaged by the Claude Code triage routine. Remove to re-triage.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Brand registry serves invalid brand.json format for enriched entries

2 participants