spec: extend x-entity annotation to media-buy, creative, signals (#2660 phase 2)#2672
Merged
Conversation
… phase 2) Phase 2 of the issue #2660 rollout. Phase 1 (#2668) added the annotation mechanism with brand/ as the canonical case. This PR sweeps the three sales-flow domains — where most real context capture/consume flows happen. Annotated 13 core/ shared types (single edits that propagate to every $ref site) plus leaf properties across media-buy/, creative/, and signals/ schemas. No new entity types were needed — the 24-value registry from phase 1 covered everything in this sweep. Walker enhancement: resolveEntityAtPath now reads root-level x-entity on oneOf/anyOf/allOf schemas before descending. This lets composite types like core/signal-id.json carry one root annotation for whole-object captures (e.g., signals[0].signal_id) without duplicating on each variant. Two regression tests added covering the walker enhancement and array-items path resolution. Lint runs clean against the source tree — no new mismatches uncovered in the storyboards covered by phase 1 + phase 2 annotations. Remaining follow-ups: account/, governance/, property/, collection/, sponsored-intelligence/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Protocol expert flagged `pricing_option` as a latent brand_id-shape conflation: media-buy product pricing (seller rate card) and vendor pricing (agent-issued service fees) are different namespaces, and the repo already models them with distinct shared types (core/pricing-option.json vs core/vendor-pricing-option.json). Split the entity type accordingly: - `pricing_option` → `product_pricing_option` + `vendor_pricing_option` - core/vendor-pricing-option.json root-annotated `vendor_pricing_option` - Inline annotations re-tagged across 7 sites (brand/, media-buy/, creative/, signals/) Code reviewer flagged a silent-drop hazard: the walker's root-level x-entity check wins at the empty path, so if a root annotation and a variant's x-entity disagree, the variant silently loses. Added a `composite_entity_disagreement` rule to lintRegistry that flags the disagreement at schema-load time, before any storyboard depends on it. One regression test covers the new rule. DX expert flagged doc drift from phase-1 → phase-2: - Added "Shared types with oneOf / anyOf / allOf variants" section covering the signal-id.json invariant (root-annotate only; do NOT annotate inner ids of composite-key variants). - Updated the registry category table: pricing_option split into product/vendor rows. - Replaced the stale "Current coverage" forward-looking list with a `git grep` one-liner against the source of truth. - Fixed the "Phase 3 implements..." reference (lint is live, not future). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 21, 2026
bokelley
added a commit
that referenced
this pull request
Apr 21, 2026
…se 3) (#2678) * spec: extend x-entity annotation to account and governance (#2660 phase 3) Continues the rollout from phase 2 (#2672). Annotates 10 account schemas and 13 governance schemas — the "control plane" cluster. Registry grows by one entity type: `policy` (governance policy identifier, distinct from governance_plan — plans contain policies). Added at the shared types `governance/policy-entry.json` and `governance/policy-ref.json`; propagates to every $ref site. Every plan_id in governance/ is tagged governance_plan (phase-2 taxonomy decision carried forward). Array items on plan_ids[], portfolio_plan_ids[], member_plan_ids[], policy_ids[], shared_policy_ids[] explicitly annotated since the scalar patcher doesn't reach into items. account/report-usage-request picks up seven inherited entity references (media_buy, vendor_pricing_option, signal_activation_id, content_standards, rights_grant, creative, property_list) — usage rollups flow through every entity type the buyer touched, so this is a natural propagation point. Deferred to phase 4 / capstone PR: property/, collection/, sponsored-intelligence/, plus the coverage counter in CI output and the schema-side nudge for new *_id fields without x-entity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec + tooling: address expert review feedback on #2660 phase 3 Protocol expert flagged three things: (1) policy → governance_policy rename. The bare `policy` will collide with other policy-shaped concepts (cancellation policy, retention policy, pricing policy). Renamed across the registry, 15 annotation sites, and the doc category table. (2) check_id is cross-step, not forensic. check_governance returns a check_id; report_plan_outcome requires the same check_id to link outcomes to authorizing checks. That's a real capture/consume pair. Added `governance_check` entity type, annotated 3 sites (check-governance- response, report-plan-outcome-request, get-plan-audit-logs-response escalations). (3) Registry-scoped vs. plan-scoped inline policy ids share x-entity: governance_policy today — the brand_id bug shape in disguise. Splitting them requires schema-shape discrimination (either a oneOf on the source field in policy-entry.json or a type split). Filed as a follow-up — #2685. Documented the caveat in the registry definition. Code reviewer flagged: (4) Four unannotated policy_id sites outside the governance/ scope: property/validation-result, error-details/policy-violation, content-standards/validate-content-delivery-response, content-standards/calibrate-content-response. All semantically the same as governance policies. Annotated all four as `governance_policy`. (5) Stale `media_plan` registry definition citing `governance/sync-plans- request` (which is now all `governance_plan`). Updated definition to reflect reality — `media_plan` is reserved for future media-plan schemas. DX expert flagged: (6) Plan vs. policy vs. check disambiguation not obvious from the doc. Added a one-paragraph explainer under the registry category table. (7) Category table silently drifts from the registry. Added an editorial- grouping HTML comment clarifying the registry is authoritative. Also documented `seller_reference` in the changeset as a deliberate skip: polymorphic reference (media_buy_id, rights_grant_id, or deployment_id depending on purchase_type), so no single x-entity fits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 2 of the #2660 rollout, following phase 1 (#2668). The mechanism landed in phase 1 with
brand/annotated as the canonical case; this PR sweeps the three sales-flow domains — where most real$contextcapture/consume flows live — and annotates thecore/shared types they reference.Reviewer skim guide (48 files → 8 load-bearing)
Mechanical (~40 files, skimmable): leaf-property annotations across
media-buy/,creative/,signals/. Applied via a one-off patch script; every diff is a single"x-entity": "…"line.Load-bearing, please review carefully:
static/schemas/source/core/x-entity-types.json— registry split forpricing_optionscripts/lint-storyboard-context-entity.cjs— walker enhancement + newcomposite_entity_disagreementrulestatic/schemas/source/core/signal-id.json— root-levelx-entity: signal(composite oneOf pattern)static/schemas/source/core/vendor-pricing-option.json—vendor_pricing_optionat the shared typestatic/schemas/source/core/account-ref.json,core/account.json— natural-key annotation onoperatorandbrandfieldsstatic/schemas/source/core/format-id.json— root-levelx-entity: creative_format(composite pattern)static/schemas/source/media-buy/create-media-buy-request.json—plan_id→governance_plan(description explicitly governance-scoped)docs/contributing/x-entity-annotation.md— new "Shared types with oneOf" sectionRegistry split:
pricing_option→product_pricing_option+vendor_pricing_optionProtocol-review finding: the original flat
pricing_optionentity was the same brand_id-shape bug it exists to catch. The repo already distinguishes at the shape level (core/pricing-option.jsonvscore/vendor-pricing-option.json):product_pricing_option— a pricing tier on a seller's inventory product (CPM / CPC / CPCV / etc). Annotated atcore/package.json,media-buy/package-request.json. Scoped to the seller's product rate card.vendor_pricing_option— a pricing tier offered by a vendor agent (rights, signals, creative, governance) for its own services. Annotated atcore/vendor-pricing-option.json(propagates via$ref) plus inline onbrand/acquire-rights-*,signals/activate-signal-request,media-buy/build-creative-response,creative/get-creative-features-response. Scoped to the issuing agent.A storyboard capturing one and consuming the other would now fire the lint; before the split it was silent.
Annotations added
core/shared types (single edit propagates to every$refsite):core/account.json,core/account-ref.json—account,operatorcore/media-buy.json—media_buycore/package.json—package,product,product_pricing_optioncore/product.json,core/catalog.json—product,catalogcore/creative-asset.json,core/creative-assignment.json—creativecore/format-id.json—creative_format(root-level annotation)core/signal-id.json—signal(root-level annotation, both oneOf variants)core/vendor-pricing-option.json—vendor_pricing_optioncore/protocol-envelope.json,core/tasks-get-*,core/tasks-list-response.json,core/mcp-webhook-payload.json—taskDomain leaves:
media-buy/—media_buy_id,media_buy_ids[],package_id,product_id,product_pricing_option_id,audience_id,event_source_id,catalog_ids[],task_id.plan_idoncreate-media-buy-request→governance_plan.vendor_pricing_option_idonbuild-creative-response(creative agent rate card).creative/—creative_id,creative_ids[],media_buy_ids[],package_id,task_id,vendor_pricing_option_idonget-creative-features-response.signals/—signal_agent_segment_id→signal_activation_id,vendor_pricing_option_id, plus inheritedsignalviacore/signal-id.json.Walker + lint enhancements
resolveEntityAtPathnow reads root-levelx-entityon composite types (oneOf/anyOf/allOf) before descending into variants. Letscore/signal-id.jsoncarry one root annotation for whole-object captures without duplication on each variant.New
composite_entity_disagreementrule inlintRegistry: flags when a rootx-entityand a variant'sx-entitydisagree at the empty path. The walker's root check wins silently, so forcing them to agree prevents a hidden-drop hazard. Code-reviewer finding.Authoring guide updated with a new "Shared types with oneOf / anyOf / allOf variants" section documenting when to root-annotate vs. split.
Three regression tests total: walker enhancement, array-items path resolution, root+variant disagreement detection.
Not closing #2660
Still remaining:
account/,governance/,property/,collection/,sponsored-intelligence/, plus a capstone with a coverage counter + schema-side nudge for new*_idfields withoutx-entity.Test plan
npm run test:storyboard-context-entity— 13 tests pass (source-tree guard, canonical brand_rights storyboard conflates advertiser brand_id with talent brand_id #2627 mismatch, bracket-notation regression guard, happy path, silent-on-missing, collision, unknown_entity did-you-mean, root-on-oneOf resolution, array-items path, composite disagreement, $ref walker, oneOf walker, registry validity)npm run test:schemas— all schemas validate with new annotationsnpm run test:storyboard-branch-setsandtest:storyboard-scopingstill passnpm run build:compliance— lint runs clean against source treenpm run test:unit(pre-commit) — 631 tests pass, typecheck clean🤖 Generated with Claude Code