spec + tooling: x-entity annotation and storyboard context-entity lint (#2660)#2668
Merged
Conversation
#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
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>
5 tasks
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>
This was referenced Apr 21, 2026
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
Closes rule 3 of the #2634 contradiction trio. Adds a non-validating
x-entityschema 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
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)scripts/lint-storyboard-context-entity.cjscontext_outputscapture sites and$context.*consume sitesrights[0].rights_id) and dotted paths — bracket is the canonical authoring form perstoryboard-schema.yaml$ref; composesoneOf/anyOf/allOfvariantsentity_mismatch,unknown_entity(with Levenshtein did-you-mean),capture_name_collisionx-entity— partial rollout is safebrand/schemas (request + response) pluscore/brand-id.jsontaggedadvertiser_brandso every$refsite inherits the scope. Canonical split:brand_idonget_brand_identity→advertiser_brand,brand_idinsideget_rightsresult items →rights_holder_brand.docs/contributing/x-entity-annotation.mdauthoring guide, cross-linked fromstoryboard-authoring.mdand.agents/playbook.mdnpm testandnpm run build:compliancealongside the existing storyboard lintsNaming decisions reviewers may want to push on
rights_contract→rights_grant— grants cover pending/rejected states, not just signed contracts (per AI licensing convention)signalvssignal_activation_id— two distinct referents (catalog ref vs. opaque activation handle)media_planvsgovernance_plan— sameplan_idfield, genuinely different referent domains; kept split rather than collapsedcore/brand-id.jsongetsx-entity: advertiser_brand— every$refinherits 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)
media-buy/,creative/,signals/,account/,governance/,property/,sponsored-intelligence/— one PR per domain (or paired by flow, TBD)*_idfields added withoutx-entitycore/rights-holder-brand-id.jsonshared 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,$refwalker,oneOfwalker, registry validity)npm run test:storyboard-branch-setsandtest:storyboard-scopingstill passnpm run test:schemas— all 484 schemas validate with new annotationsnpm run build:compliance— new lint runs clean against the source treenpm run test:unit(pre-commit) — 631 tests pass, typecheck clean🤖 Generated with Claude Code