Skip to content

Fix allOf + additionalProperties validation and add oneOf titles#334

Merged
bokelley merged 4 commits into
mainfrom
bokelley/issue-275-formats-response
Dec 17, 2025
Merged

Fix allOf + additionalProperties validation and add oneOf titles#334
bokelley merged 4 commits into
mainfrom
bokelley/issue-275-formats-response

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Changes

Schema fixes:

  • Inline width/height properties into video-asset.json and image-asset.json instead of using allOf with dimensions.json
  • Add titles to all oneOf variants in response schemas (e.g., CreateMediaBuySuccess, CreateMediaBuyError) so UI tools show meaningful labels instead of "Option 1", "Option 2"
  • Delete unused dimensions.json schema

New features (experimental):

  • Add bundled schema generation to build-schemas.js - creates self-contained schemas with all $ref resolved inline
  • Document bundled schemas in schema-versioning.mdx with experimental warning

Testing:

  • Add composed-schema-validation.test.js to catch allOf + additionalProperties conflicts
  • Test bundled schemas validate without custom ref resolution

Test plan

  • All existing tests pass
  • New composed schema tests pass
  • Bundled schemas validate data without loadSchema configuration

🤖 Generated with Claude Code

bokelley and others added 4 commits December 17, 2025 07:23
Fixes JSON Schema validation failures when using allOf composition with
additionalProperties: false. Each sub-schema was independently rejecting
the other's properties.

**Schema fixes:**
- dimensions.json: removed additionalProperties: false (composition-only)
- push-notification-config.json: removed additionalProperties: false
- video-asset.json: inlined width/height, removed allOf
- image-asset.json: inlined width/height, removed allOf

**Bundled schemas:**
- Added bundled schemas at /schemas/{version}/bundled/
- Only bundles request/response schemas (root schemas)
- Core objects are embedded inside response schemas when bundled
- Custom resolveRefs() handles multiple refs to same schema correctly
- Generated at build time for tools that don't support $ref resolution

**Testing:**
- New test:composed script validates allOf composition patterns
- Tests bundled schemas work without $ref resolution
- Added to CI test suite

Fixes #275.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds explicit title properties to all oneOf variants in response schemas
so downstream UI tools display meaningful labels instead of "Option 1",
"Option 2" when rendering discriminated unions.

Schemas updated:
- create-media-buy-response: CreateMediaBuySuccess/Error
- update-media-buy-response: UpdateMediaBuySuccess/Error
- sync-creatives-response: SyncCreativesSuccess/Error
- build-creative-response: BuildCreativeSuccess/Error
- provide-performance-feedback-response: ProvidePerformanceFeedbackSuccess/Error
- activate-signal-response: ActivateSignalSuccess/Error
- preview-creative-response: PreviewCreativeSingleResponse/BatchResponse + nested batch result titles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The dimensions schema was no longer referenced after inlining
width/height properties directly into video-asset.json and
image-asset.json. This removes the unused schema file and
updates tests accordingly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds a warning that bundled schemas are experimental and may change,
with a link to share feedback on use cases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@bokelley bokelley merged commit 935eb43 into main Dec 17, 2025
6 checks passed
bokelley added a commit that referenced this pull request May 12, 2026
* fix(copy): clarify which tiers qualify for public agent listing

The publish-agent gate accepts four API-access tiers (Professional,
Builder, Member, Leader) per server/src/services/membership-tiers.ts.
User-facing copy across 11 surfaces said "Professional tier or higher",
which Addie and customers read as "tiers priced above Professional" —
the reverse of the actual ordering, since Builder ($3K), Member ($15K),
and Leader ($50K) all cost more than Professional ($250) but qualify.

Affinity Answers (escalation #334, Builder plan) was told by Addie to
upgrade to Professional after failing to publish. Builder always
qualified; the copy misled both the user and the agent.

Replace the phrase with explicit lists ("Professional, Builder, Member,
or Leader" / "paying AAO members") across error messages, dashboard
tooltips, Addie's behavior rules, OpenAPI schema descriptions, and the
registry-of-an-agent doc. No behavior change; tests assert on the
`tier_required` error code, not the message text.

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

* chore(docs): regenerate addie-tools.mdx after save_agent description fix

`docs/aao/addie-tools.mdx` is auto-generated from MCP tool descriptions.
The `save_agent` fix in the prior commit only updated the source file
(`server/src/addie/mcp/member-tools.ts`); the published doc still
carried the misleading "Professional-tier (or higher)" phrasing. This
runs `npm run build:addie-tools` to reflect the updated description.

The regen also picks up unrelated pre-existing drift (the
`list_github_issues` description and the new `agent_conformance` tool
set) that had not yet been regenerated. CI doesn't gate on this, so the
drift was silently accumulating; folding the catch-up in here.

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

* fix(copy): update OpenAPI YAML tier-copy descriptions

Two surgical edits in the published `static/openapi/registry.yaml`
mirroring the source descriptions in
`server/src/schemas/member-agents-openapi.ts`. The full regen output
includes a lot of unrelated pre-existing drift (new fields like
`discovery_method`, `resolved_url`, `self_redirected` hosting mode);
keeping those out of this copy-fix PR — they should land in a separate
regen-only PR.

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 12, 2026
…4448) (#4457)

* fix(copy): clarify which tiers qualify for public agent listing

The publish-agent gate accepts four API-access tiers (Professional,
Builder, Member, Leader) per server/src/services/membership-tiers.ts.
User-facing copy across 11 surfaces said "Professional tier or higher",
which Addie and customers read as "tiers priced above Professional" —
the reverse of the actual ordering, since Builder ($3K), Member ($15K),
and Leader ($50K) all cost more than Professional ($250) but qualify.

Affinity Answers (escalation #334, Builder plan) was told by Addie to
upgrade to Professional after failing to publish. Builder always
qualified; the copy misled both the user and the agent.

Replace the phrase with explicit lists ("Professional, Builder, Member,
or Leader" / "paying AAO members") across error messages, dashboard
tooltips, Addie's behavior rules, OpenAPI schema descriptions, and the
registry-of-an-agent doc. No behavior change; tests assert on the
`tier_required` error code, not the message text.

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

* chore(docs): regenerate addie-tools.mdx after save_agent description fix

`docs/aao/addie-tools.mdx` is auto-generated from MCP tool descriptions.
The `save_agent` fix in the prior commit only updated the source file
(`server/src/addie/mcp/member-tools.ts`); the published doc still
carried the misleading "Professional-tier (or higher)" phrasing. This
runs `npm run build:addie-tools` to reflect the updated description.

The regen also picks up unrelated pre-existing drift (the
`list_github_issues` description and the new `agent_conformance` tool
set) that had not yet been regenerated. CI doesn't gate on this, so the
drift was silently accumulating; folding the catch-up in here.

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

* fix(copy): update OpenAPI YAML tier-copy descriptions

Two surgical edits in the published `static/openapi/registry.yaml`
mirroring the source descriptions in
`server/src/schemas/member-agents-openapi.ts`. The full regen output
includes a lot of unrelated pre-existing drift (new fields like
`discovery_method`, `resolved_url`, `self_redirected` hosting mode);
keeping those out of this copy-fix PR — they should land in a separate
regen-only PR.

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

* chore(scripts): audit-brand-domain-www-mismatch (#4448 scoping)

Adds a read-only audit identifying orgs whose `brand_revisions` history
points at a different brand domain than their `organization_domains.is_primary=true`
row — most commonly `www.<domain>` vs `<domain>`. Stage 2 of #4159
(`5163d21425`, drop of `member_profiles.primary_brand_domain`) moved
the publish path's brand-row authority to the resolver, so any org
whose previous curation lived on the www variant now has its next
publish write land on the root variant — a brand row they haven't
edited.

Affinity Answers (escalation #334) is the known case; this script
quantifies the rest before deciding on a reconciliation strategy
(flip is_primary, merge brand rows, or add a lookup-time canonicalization
fallback at the publish site).

Output groups results into:
  - `www_in_revisions` — revisions on www.<primary>; root primary
  - `www_in_primary` — revisions on root; www.<primary> primary
  - `unrelated` — different brand domain entirely (parent-portfolio /
    multi-brand-curation case; not a regression)

Dry-run only. Run via:
  fly ssh console -a adcp-docs -C \\
    'node /app/dist/scripts/audit-brand-domain-www-mismatch.js'

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

* chore(scripts): reconcile-brand-domain-www-mismatch (#4448 fix)

One-shot reconciliation for the three orgs identified by the audit
(Affinity Answers, BidMachine, Scope3). Each affected org has brand
content on `www.<domain>` while their `organization_domains.is_primary`
points at `<domain>` — Stage 2 of #4159 (5163d21) moved publish-path
brand-domain authority to the resolver, and the prior `member_profiles.primary_brand_domain`
column had been holding the www variant for these orgs.

Per affected org, the script:

  1. Copies `brand_manifest.agents` from www brand row into root brand
     row, deduped on agent URL.
  2. Marks the www brand row `manifest_orphaned=true` (the lookup
     helpers already skip orphaned rows; preserves history without
     serving from the wrong variant).
  3. Inserts a `brand_domain_aliases` row (`www.<domain>` → `<domain>`).
     For Scope3 the alias already exists, so this step is a no-op there.

Hardcoded list — re-deriving at runtime risked silently expanding scope
if a new mismatch appeared between audit and apply. Audit was run via
fly ssh on 2026-05-12 against prod; results captured in the script
header.

Dry-run by default:
  npx tsx server/src/scripts/reconcile-brand-domain-www-mismatch.ts
  npx tsx server/src/scripts/reconcile-brand-domain-www-mismatch.ts --apply

  fly ssh console -a adcp-docs -C \\
    'node /app/dist/scripts/reconcile-brand-domain-www-mismatch.js --apply'

Idempotent: re-running is a no-op (no agents to copy, www already
orphaned, alias already present).

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

* chore(scripts): audit reports manifest_orphaned on the revision-domain row

The reconciliation marks the www brand row `manifest_orphaned=true` and
moves agents to the root row. `brand_revisions` is a permanent log, so
its rows stay on `www.<domain>` — re-running the audit otherwise looks
identical to the pre-fix state.

This adds a `revision_row_orphaned` flag derived from `brands.manifest_orphaned`
at the row the revisions are recorded against, and tags reconciled rows
in the output ("[RECONCILED — www row orphaned]"). Lets a future
operator distinguish "still drifting" from "historical residue".

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 12, 2026
#4459)

WorkOS's `org.domains` array order is not stable. Orgs with a verified
root + a `failed` www variant can have WorkOS list the www variant
first, which overwrote `organizations.email_domain` on every
`organization.updated` webhook fire even though our table's
`organization_domains.is_primary=true` was already pointing at the
correct root.

Scope3 hit this in prod (escalation #334 follow-up):

  WorkOS:
    [{domain: 'www.scope3.com', state: 'failed'},
     {domain: 'scope3.com',     state: 'verified'}]

  Our DB:
    organization_domains.is_primary=true → scope3.com
    organizations.email_domain          → www.scope3.com  (drifted)

`upsertWorkosDomain` already preserves `is_primary` across conflicts, so
our table's choice survives. Reading `email_domain` from the same
canonical source (`organization_domains.is_primary=true`) keeps the two
in lockstep. Fallback to `org.domains[0]` covers the initial-sync case
(cold start, no is_primary row yet); in normal operation the
`upsertWorkosDomain` loop sets `i===0` as primary, so the SELECT finds
a row.

Downstream impact: lookups like `brand-enrichment.ts`'s
`WHERE email_domain = $1` were missing Scope3's org row entirely.

Adds:
  - `server/src/scripts/sync-email-domain-from-is-primary.ts` — backfill
    pre-fix drift. Applied on prod 2026-05-12 11:55 UTC for Scope3.
  - Integration test pinning the new behavior (non-primary first in
    WorkOS array stays at the canonical is_primary value).

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allOf + additionalProperties: false in asset schemas causes validation failures for official examples

1 participant