refactor(compliance): normalize storyboard brand/account consistency (#2528, #2530, #2533)#2562
Merged
Conversation
…2528, #2530, #2533) Mechanical storyboard-consistency pass across static/compliance/source/: - #2530: Normalize to RFC 2606 .example TLD. acmeoutdoor.com → acmeoutdoor.example, amsterdam-steakhouse.com → .example, pinnacle-agency.com → .example, and fix the acme-outdoor.example.com typo in creative-template and creative-generative. - #2533: Fix operator = brand.domain in property-lists and collection-lists. These specialisms are meant to demonstrate an agency operating on behalf of a brand, not the brand operating directly. Switch to pinnacle-agency.example to match the rest of the buyer-side storyboards. - #2528: Standardize buyer-side storyboards on account { brand, operator }. When a step already had an account block, drop the redundant top-level brand. Otherwise, promote top-level brand into the account wrapper with operator: "pinnacle-agency.example". No runtime behavior change: both shapes resolve to the same session key via sessionKeyFromArgs. Fresh build:compliance and test:schemas pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
…create_media_buy brand (#2528, #2530) Review findings from ad-tech-protocol-expert on #2562: - #2530 follow-up: sales-broadcast-tv/index.yaml missed the initial TLD sweep. Normalize `novamotors.com → .example` (6 sites) and `videoamp.com → .example` (2 sites) so spec examples don't resolve to real third-party domains. - sales-social/index.yaml: list_accounts had an injected `account { brand, operator }` block, but the list-accounts-request schema has no `account` field (listing all accounts for the authenticated caller). Drop the identity block; keep only the context wrapper. additionalProperties: true would silently accept it, but the semantics were misleading to spec readers. - #2528 follow-up (H2): create-media-buy-request schema requires BOTH top-level `brand` and `account`. The initial standardization to `account { brand, operator }` dropped top-level brand as "redundant"; it isn't — `brand` and `account` are distinct schema fields. Restore top-level `brand: { domain }` alongside the existing account block on 20 create_media_buy steps so the requests validate against the published schema. build:compliance and test:schemas pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Apr 20, 2026
…tions (review: H2) Review feedback from ad-tech-protocol-expert on #2563: The scoping-lint fix added `account { brand, operator }` to 11 create_media_buy steps that lacked any identity, but the create-media-buy-request schema requires BOTH `brand` (top-level) and `account`. Adding only `account` makes the probes fail on missing-brand validation before exercising the error path they're meant to test (negative budget, reversed dates, etc.). Add top-level `brand: { domain }` alongside the injected account on: - universal/error-compliance.yaml (5 steps) - universal/schema-validation.yaml (3 steps) - protocols/media-buy/state-machine.yaml (1) - protocols/governance/index.yaml (1) - specialisms/governance-spend-authority/index.yaml (1) The companion top-level-brand restoration for PR A's create_media_buy steps landed in #2562. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Apr 20, 2026
…fallback (#2527, #2529, #2531) (#2563) * refactor(compliance): normalize storyboard brand/account consistency (#2528, #2530, #2533) Mechanical storyboard-consistency pass across static/compliance/source/: - #2530: Normalize to RFC 2606 .example TLD. acmeoutdoor.com → acmeoutdoor.example, amsterdam-steakhouse.com → .example, pinnacle-agency.com → .example, and fix the acme-outdoor.example.com typo in creative-template and creative-generative. - #2533: Fix operator = brand.domain in property-lists and collection-lists. These specialisms are meant to demonstrate an agency operating on behalf of a brand, not the brand operating directly. Switch to pinnacle-agency.example to match the rest of the buyer-side storyboards. - #2528: Standardize buyer-side storyboards on account { brand, operator }. When a step already had an account block, drop the redundant top-level brand. Otherwise, promote top-level brand into the account wrapper with operator: "pinnacle-agency.example". No runtime behavior change: both shapes resolve to the same session key via sessionKeyFromArgs. Fresh build:compliance and test:schemas pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(compliance): address review — TLD sweep, list_accounts identity, create_media_buy brand (#2528, #2530) Review findings from ad-tech-protocol-expert on #2562: - #2530 follow-up: sales-broadcast-tv/index.yaml missed the initial TLD sweep. Normalize `novamotors.com → .example` (6 sites) and `videoamp.com → .example` (2 sites) so spec examples don't resolve to real third-party domains. - sales-social/index.yaml: list_accounts had an injected `account { brand, operator }` block, but the list-accounts-request schema has no `account` field (listing all accounts for the authenticated caller). Drop the identity block; keep only the context wrapper. additionalProperties: true would silently accept it, but the semantics were misleading to spec readers. - #2528 follow-up (H2): create-media-buy-request schema requires BOTH top-level `brand` and `account`. The initial standardization to `account { brand, operator }` dropped top-level brand as "redundant"; it isn't — `brand` and `account` are distinct schema fields. Restore top-level `brand: { domain }` alongside the existing account block on 20 create_media_buy steps so the requests validate against the published schema. build:compliance and test:schemas pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(compliance): storyboard scoping lint + sessionKeyFromArgs plans fallback (#2527, #2529, #2531) Three guardrails against the class of bug #2236/#2526 exposed — steps that silently write to open:default because sample_request lacks brand/account. - #2527: scripts/lint-storyboard-scoping.cjs, wired into npm run build:compliance. TENANT_SCOPED_TASKS enumerates every tool that calls getSession(sessionKeyFromArgs(...)). Each step invoking one must carry sample_request.{account.account_id | account.brand.domain | brand.domain | plans[0].brand.domain (sync_plans)}. Per-step opt-out via `scoping: global` for intentionally cross-tenant probes. Fixed 26 pre-existing violations across 10 storyboards. - #2529: tests/lint-storyboard-scoping.test.cjs parity guard. Parses HANDLER_MAP from server/src/training-agent/task-handlers.ts and asserts every registered task is classified in exactly one of TENANT_SCOPED_TASKS / EXEMPT_FROM_LINT. Also catches stale entries in those sets after a rename. Added as npm run test:storyboard-scoping. - #2531: sessionKeyFromArgs falls back to plans[0].brand.domain before open:default. sync_plans carries brand identity inside plans[], not at the envelope level — without this fallback, governance storyboards that only identify the tenant via plans land in the wrong session. Matches what the scoping lint already accepts as a valid identity shape. - Authoring guide: docs/contributing/storyboard-authoring.md documents valid identity shapes, scoping: global opt-out, and local lint usage. build:compliance, test:schemas, typecheck, and training-agent.test.ts all pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(compliance): upgrade brand-only injections to account { brand, operator } Follow-up self-review: the initial scoping-lint fix injected top-level brand.domain into 47 steps. For create_media_buy/update_media_buy/ sync_creatives that is schema-invalid — AccountRef requires operator whenever the natural-key (brand) form is used, so the canonical identity shape is account { brand, operator }. Top-level brand on those tasks is either a separate schema field (the campaign's brand on create_media_buy) or not a schema field at all. Changes: - Upgrade existing brand-only injections to account { brand, operator } across 18 storyboards. Where a step already had an account block, drop the redundant brand. Keep top-level brand only where the storyboard legitimately expresses the campaign brand (separate from identity). - Lint: also accept plans[0].account.brand.domain (canonical shape for sync_plans inside the plans array) in addition to plans[0].brand.domain. - Runtime (sessionKeyFromArgs): mirror the lint — check plans[0].account.brand.domain before plans[0].brand.domain. New test coverage added. - Authoring guide: stop documenting top-level brand as "shorthand identity." Document account { brand, operator } as canonical, with a clear note that top-level brand is a training-agent routing fallback, not a spec-sanctioned shape. - Lint error message: recommend the account shape directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(compliance): sync_plans plan-level brand + harden parity test (review: H1, should-fix) Review feedback from ad-tech-protocol-expert and code-reviewer on PR #2563: H1 (blocking): sync-plans-request.json declares plan items with `required: [plan_id, brand, ...]` and `additionalProperties: false`. My amendment mis-wrapped plan identity in `account { brand, operator }`, which AJV rejects. Revert to `brand: { domain }` at the plan-item level across five storyboards (governance, governance-spend-authority × 2, governance-delivery-monitor, brand-rights/governance_denied). Strip the `plans[0].account.brand.domain` shape from the lint, the runtime sessionKeyFromArgs fallback, tests, and the authoring guide so no-one gets misled again. Should-fix (parity test robustness): - Strip `// comments` before matching handler-map entries so a `// TODO` beside a handler line doesn't silently drop the task from lint coverage. - Drop the end-of-line anchor so an inline type annotation after the handler name doesn't fail the match. - Add a canary: assert the parser captured ≥30 tasks. If the regex ever chops the body (e.g. future nested braces), the canary fires before the two downstream assertions pass vacuously. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(compliance): add top-level brand on PR B's create_media_buy injections (review: H2) Review feedback from ad-tech-protocol-expert on #2563: The scoping-lint fix added `account { brand, operator }` to 11 create_media_buy steps that lacked any identity, but the create-media-buy-request schema requires BOTH `brand` (top-level) and `account`. Adding only `account` makes the probes fail on missing-brand validation before exercising the error path they're meant to test (negative budget, reversed dates, etc.). Add top-level `brand: { domain }` alongside the injected account on: - universal/error-compliance.yaml (5 steps) - universal/schema-validation.yaml (3 steps) - protocols/media-buy/state-machine.yaml (1) - protocols/governance/index.yaml (1) - specialisms/governance-spend-authority/index.yaml (1) The companion top-level-brand restoration for PR A's create_media_buy steps landed in #2562. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): add SEO frontmatter to storyboard-authoring doc check-seo CI requires description + og:title on every doc page. Match the sibling testable-*.md frontmatter style. 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 was referenced Apr 20, 2026
Closed
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
Mechanical storyboard consistency pass across
static/compliance/source/. Three related cleanups bundled because they touch the same files and have zero runtime impact — both shapes still resolve to the same session key viasessionKeyFromArgs.#2530 — Normalize to RFC 2606
.exampleTLDacmeoutdoor.com→acmeoutdoor.exampleamsterdam-steakhouse.com→amsterdam-steakhouse.examplepinnacle-agency.com→pinnacle-agency.exampleacme-outdoor.example.comtypo (extra dot) →acmeoutdoor.examplein creative-template and creative-generativeSpec examples should never resolve to a real third-party domain.
#2533 — Fix
operator = brand.domainin property-lists and collection-listsProperty-lists and collection-lists demonstrate an agency operating on behalf of a brand, not a brand operating directly. Setting
operatorto the brand's own domain misleads readers about theauthorized_operators/ brand.json trust model. Switched topinnacle-agency.exampleto match the rest of the buyer-side storyboards.#2528 — Standardize buyer-side steps on
account { brand, operator }Two shapes existed in the tree:
brand: { domain }(shorthand)account: { brand, operator }(expressive — required for agency-on-behalf-of-brand, billing, governance trails)Some storyboards mixed both within one file (e.g.
invalid_transitions.yamlstep 1 used the account shape, steps 2–6 used top-level brand). Standardized buyer-side to the expressive form:accountblock, dropped the redundant top-levelbrandbrandinto anaccountwrapper withoperator: "pinnacle-agency.example"Top-level
brandremains valid as documented shorthand for minimal-identity examples — not touched in this PR.Closes
Test plan
npm run build:complianceclean (10 universal / 6 protocols / 24 specialisms)npm run test:schemaspasses (all 483 schemas)npm run test:examplespasses (31/31)npm run test:unitpasses (587/587)Follow-up
PR B (#2527, #2529, #2531) will land the scoping lint + runtime fallback on top of this cleaned tree.
🤖 Generated with Claude Code