Skip to content

spec + tooling: x-entity annotation and storyboard context-entity lint (#2660)#2668

Merged
bokelley merged 1 commit into
mainfrom
bokelley/issue-2660
Apr 21, 2026
Merged

spec + tooling: x-entity annotation and storyboard context-entity lint (#2660)#2668
bokelley merged 1 commit into
mainfrom
bokelley/issue-2660

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Closes rule 3 of the #2634 contradiction trio. Adds a non-validating x-entity schema annotation that tags identity-bearing fields with the entity type they resolve against, plus a cross-storyboard lint that catches the class of bug fixed manually in #2627 — a value captured from a field of one entity type (advertiser_brand) consumed as a field of a different entity type (rights_holder_brand), with both sites passing local schema validation.

This is phase 1 of #2660: the mechanism ships end-to-end with the brand/ domain annotated (the canonical conflation case). Follow-up PRs will sweep the remaining six domains — the lint is silent on un-annotated fields so partial rollout is safe.

What's in this PR

  • Registry: static/schemas/source/core/x-entity-types.json — 24 entity types with definitions (e.g., advertiser_brand, rights_holder_brand, rights_grant, media_buy, package, signal, signal_activation_id)
  • Lint: scripts/lint-storyboard-context-entity.cjs
    • Walks context_outputs capture sites and $context.* consume sites
    • Accepts both bracket (rights[0].rights_id) and dotted paths — bracket is the canonical authoring form per storyboard-schema.yaml
    • Follows $ref; composes oneOf / anyOf / allOf variants
    • Rules: entity_mismatch, unknown_entity (with Levenshtein did-you-mean), capture_name_collision
    • Silent on fields without x-entity — partial rollout is safe
  • Phase 1 annotations: brand/ schemas (request + response) plus core/brand-id.json tagged advertiser_brand so every $ref site inherits the scope. Canonical split: brand_id on get_brand_identityadvertiser_brand, brand_id inside get_rights result items → rights_holder_brand.
  • Docs: docs/contributing/x-entity-annotation.md authoring guide, cross-linked from storyboard-authoring.md and .agents/playbook.md
  • CI: wired into npm test and npm run build:compliance alongside the existing storyboard lints

Naming decisions reviewers may want to push on

  • rights_contractrights_grant — grants cover pending/rejected states, not just signed contracts (per AI licensing convention)
  • signal vs signal_activation_id — two distinct referents (catalog ref vs. opaque activation handle)
  • media_plan vs governance_plan — same plan_id field, genuinely different referent domains; kept split rather than collapsed
  • core/brand-id.json gets x-entity: advertiser_brand — every $ref inherits advertiser scope. Rights-holder brand ids must use a separate shared type even if the string shape is identical. Docs call this out as the "shared-type invariant."

Why vertical slice

#2660 said "PR sequence." Shipping the mechanism end-to-end with one domain proves the approach against the canonical case (#2627) before we ask reviewers to agree on taxonomy for 60+ fields. Taxonomy debate now runs against concrete annotations, not hypotheticals.

Not in this PR (follow-ups)

  • Annotation passes for media-buy/, creative/, signals/, account/, governance/, property/, sponsored-intelligence/ — one PR per domain (or paired by flow, TBD)
  • Adoption signals: a coverage counter in CI output and a warn-only check for new *_id fields added without x-entity
  • Preemptive core/rights-holder-brand-id.json shared type (not needed until a rights-roster id field materialises)

Test plan

  • npm run test:storyboard-context-entity — 10 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, $ref walker, oneOf walker, registry validity)
  • npm run test:storyboard-branch-sets and test:storyboard-scoping still pass
  • npm run test:schemas — all 484 schemas validate with new annotations
  • npm run build:compliance — new lint runs clean against the source tree
  • npm run test:unit (pre-commit) — 631 tests pass, typecheck clean

🤖 Generated with Claude Code

#2660)

Adds a non-validating `x-entity` annotation to schema fields that carry
entity identity, plus a cross-storyboard lint that catches the class of
bug fixed manually in #2627 — a value captured from a field of one entity
type (advertiser_brand) consumed as a field of a different entity type
(rights_holder_brand), with both sites passing local schema validation.

- Registry: static/schemas/source/core/x-entity-types.json (24 values)
- Lint: scripts/lint-storyboard-context-entity.cjs
  - Accepts bracket (`rights[0].rights_id`) and dotted paths
  - Follows $ref; composes oneOf/anyOf/allOf; did-you-mean on unknowns
  - Rules: entity_mismatch, unknown_entity, capture_name_collision
- Phase 1 annotation: brand/ domain (the canonical #2627 case) plus
  core/brand-id.json for the advertiser_brand scope
- Silent on un-annotated fields, so partial rollout across the remaining
  six domains is safe (media-buy, signals, creative, account, governance,
  property, sponsored-intelligence — follow-up PRs)
- Wired into npm test and npm run build:compliance

Docs: docs/contributing/x-entity-annotation.md (authoring guide, cross-
linked from storyboard-authoring.md and .agents/playbook.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit ff9094c into main Apr 21, 2026
18 checks passed
bokelley added a commit that referenced this pull request Apr 21, 2026
Two fixes from post-merge expert review of #2661.

1. Contradiction fingerprint now handles Date objects. `yaml.load` parses
   unquoted ISO timestamps (YAML 1.1) into JS Dates; `stableStringify`
   was emitting `{}` for them, so two steps with different `start_time`
   values would silently collide. Added a Date branch in
   `normalizeRequestValue` that converts to ISO string. New regression
   test locks in the behavior.

2. Changeset reflects the landed state. Rule 3 (field-entity-context)
   was filed as #2660 during this PR and shipped separately via #2668's
   context-entity lint; the changeset's previous language treating it
   as an open follow-up was stale.

Protocol follow-ups filed (not in this PR):
- #2669: derive MUTATING_TASKS from request-schema annotations
- #2670: include test_kit + top-level fixtures in env fingerprint; plan
  sb=doc.id removal
- #2671: resolve requires_scenarios before flagging orphan_contribution

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Apr 21, 2026
…2634) (#2661)

* compliance(storyboards): contradiction lint + contributes shorthand (#2634)

Closes #2634 rules 1 and 2. Rule 3 (field-entity-context) is filed as
#2660 — requires an upstream schema-annotation pass before the lint is
tractable.

Contradiction lint (scripts/lint-storyboard-contradictions.cjs):
- Groups every step-with-assertions across all storyboards by
  (task, canonicalized request fingerprint, prior-state fingerprint,
  env fingerprint) and flags groups whose outcomes disagree in a way
  no conformant agent can satisfy (success vs error, or disjoint
  error-code sets).
- Fingerprinting strips idempotency_key and context.correlation_id,
  normalizes $generate / $context / {{prior_step}} / {{runner}}
  substitutions, and hashes a stable stringification (fixed a bug
  where JSON.stringify with an array replacer silently dropped nested
  fields).
- State path is computed from prior mutating steps in the same phase
  and earlier non-optional phases, so double-cancel (first success +
  second NOT_CANCELLABLE) no longer collides.
- Env fingerprint includes doc.id so independent storyboard files
  don't false-positive across controller-seeded state boundaries.
- Branch-set peers (same storyboard, same branch_set.id) are exempt
  by design — any_of intentionally asserts mutually exclusive
  outcomes.
- Wired into build:compliance and npm test; 14 synthetic-fixture
  tests cover fingerprint, outcome classification, state
  discrimination, env scoping, and peer exemption.

Contributes shorthand (adcp-client#693, @adcp/client 5.8.0):
- Schema accepts `contributes: true` inside a branch_set phase; the
  runner's loader resolves it to phase.branch_set.id.
- Extended lint-storyboard-branch-sets with three new rules mirroring
  the loader's checks: contributes_both, contributes_outside_branch_set,
  contributes_bad_type. Fail-fast at build time instead of storyboard-
  load time.
- Added orphan_contribution: any contributes_to / contributes: true
  whose resolved flag is never consumed by an assert_contribution
  any_of is dead code. Complements no_assertion and peer_not_declared.
- Migrated schema-validation.yaml (past_start_handled) and
  security.yaml (auth_mechanism_verified) to the shorthand end-to-end,
  dogfooding the spec + runner + lint stack.

Bumps @adcp/client to ^5.8.0.

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

* Revert client bump + contributes migration; keep defensive shorthand lints

@adcp/client 5.8.0 introduced stricter response-schema validation that
regresses ~9 unrelated storyboards (list_creative_formats formats[16..25]
oneOf mismatches in assets[0], validate_property_delivery additional-
properties). Training Agent Storyboards CI drops from 35/279 to 27/271
on the legacy matrix and 21/237 to 19/226 on framework — below both
regression floors.

The adoption is independent of this PR's goal (#2634 contradiction lint).
Unbundle to keep CI green and file follow-up for the 5.8.0 upgrade.

Changes:
- Pin @adcp/client to ^5.6.0 (matches main).
- Revert `contributes: true` shorthand migration in schema-validation.yaml
  and security.yaml back to `contributes_to: <flag>`.
- Schema doc notes the shorthand exists in 5.8.0+ but this repo stays on
  the string form until the runner upgrade is unblocked.

Kept: contradiction lint, orphan_contribution rule, defensive branch-set
lint rules for the shorthand (contributes_both,
contributes_outside_branch_set, contributes_bad_type). These are static
checks that don't require 5.8.0 at runtime — they protect authors who
start using the shorthand before the runner rolls forward.

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

* Address expert review: Date fingerprint fix + changeset freshness

Two fixes from post-merge expert review of #2661.

1. Contradiction fingerprint now handles Date objects. `yaml.load` parses
   unquoted ISO timestamps (YAML 1.1) into JS Dates; `stableStringify`
   was emitting `{}` for them, so two steps with different `start_time`
   values would silently collide. Added a Date branch in
   `normalizeRequestValue` that converts to ISO string. New regression
   test locks in the behavior.

2. Changeset reflects the landed state. Rule 3 (field-entity-context)
   was filed as #2660 during this PR and shipped separately via #2668's
   context-entity lint; the changeset's previous language treating it
   as an open follow-up was stale.

Protocol follow-ups filed (not in this PR):
- #2669: derive MUTATING_TASKS from request-schema annotations
- #2670: include test_kit + top-level fixtures in env fingerprint; plan
  sb=doc.id removal
- #2671: resolve requires_scenarios before flagging orphan_contribution

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 Apr 21, 2026
… 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>
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