Skip to content

spec: extend x-entity annotation to media-buy, creative, signals (#2660 phase 2)#2672

Merged
bokelley merged 2 commits into
mainfrom
bokelley/2660-sales-flow
Apr 21, 2026
Merged

spec: extend x-entity annotation to media-buy, creative, signals (#2660 phase 2)#2672
bokelley merged 2 commits into
mainfrom
bokelley/2660-sales-flow

Conversation

@bokelley

@bokelley bokelley commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

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 $context capture/consume flows live — and annotates the core/ 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:

  1. static/schemas/source/core/x-entity-types.json — registry split for pricing_option
  2. scripts/lint-storyboard-context-entity.cjs — walker enhancement + new composite_entity_disagreement rule
  3. static/schemas/source/core/signal-id.json — root-level x-entity: signal (composite oneOf pattern)
  4. static/schemas/source/core/vendor-pricing-option.jsonvendor_pricing_option at the shared type
  5. static/schemas/source/core/account-ref.json, core/account.json — natural-key annotation on operator and brand fields
  6. static/schemas/source/core/format-id.json — root-level x-entity: creative_format (composite pattern)
  7. static/schemas/source/media-buy/create-media-buy-request.jsonplan_idgovernance_plan (description explicitly governance-scoped)
  8. docs/contributing/x-entity-annotation.md — new "Shared types with oneOf" section

Registry split: pricing_optionproduct_pricing_option + vendor_pricing_option

Protocol-review finding: the original flat pricing_option entity was the same brand_id-shape bug it exists to catch. The repo already distinguishes at the shape level (core/pricing-option.json vs core/vendor-pricing-option.json):

  • product_pricing_option — a pricing tier on a seller's inventory product (CPM / CPC / CPCV / etc). Annotated at core/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 at core/vendor-pricing-option.json (propagates via $ref) plus inline on brand/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 $ref site):

  • core/account.json, core/account-ref.jsonaccount, operator
  • core/media-buy.jsonmedia_buy
  • core/package.jsonpackage, product, product_pricing_option
  • core/product.json, core/catalog.jsonproduct, catalog
  • core/creative-asset.json, core/creative-assignment.jsoncreative
  • core/format-id.jsoncreative_format (root-level annotation)
  • core/signal-id.jsonsignal (root-level annotation, both oneOf variants)
  • core/vendor-pricing-option.jsonvendor_pricing_option
  • core/protocol-envelope.json, core/tasks-get-*, core/tasks-list-response.json, core/mcp-webhook-payload.jsontask

Domain 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_id on create-media-buy-requestgovernance_plan. vendor_pricing_option_id on build-creative-response (creative agent rate card).
  • creative/creative_id, creative_ids[], media_buy_ids[], package_id, task_id, vendor_pricing_option_id on get-creative-features-response.
  • signals/signal_agent_segment_idsignal_activation_id, vendor_pricing_option_id, plus inherited signal via core/signal-id.json.

Walker + lint enhancements

  1. resolveEntityAtPath now reads root-level x-entity on composite types (oneOf / anyOf / allOf) before descending into variants. Lets core/signal-id.json carry one root annotation for whole-object captures without duplication on each variant.

  2. New composite_entity_disagreement rule in lintRegistry: flags when a root x-entity and a variant's x-entity disagree at the empty path. The walker's root check wins silently, so forcing them to agree prevents a hidden-drop hazard. Code-reviewer finding.

  3. 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 *_id fields without x-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 annotations
  • npm run test:storyboard-branch-sets and test:storyboard-scoping still pass
  • npm run build:compliance — lint runs clean against source tree
  • npm run test:unit (pre-commit) — 631 tests pass, typecheck clean

🤖 Generated with Claude Code

… 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>
@bokelley bokelley merged commit b9b5cfc into main Apr 21, 2026
15 checks passed
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>
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