Skip to content

refactor(compliance): normalize storyboard brand/account consistency (#2528, #2530, #2533)#2562

Merged
bokelley merged 2 commits into
mainfrom
bokelley/storyboard-audit
Apr 20, 2026
Merged

refactor(compliance): normalize storyboard brand/account consistency (#2528, #2530, #2533)#2562
bokelley merged 2 commits into
mainfrom
bokelley/storyboard-audit

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

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 via sessionKeyFromArgs.

#2530 — Normalize to RFC 2606 .example TLD

  • acmeoutdoor.comacmeoutdoor.example
  • amsterdam-steakhouse.comamsterdam-steakhouse.example
  • pinnacle-agency.compinnacle-agency.example
  • fix acme-outdoor.example.com typo (extra dot) → acmeoutdoor.example in creative-template and creative-generative

Spec examples should never resolve to a real third-party domain.

#2533 — Fix operator = brand.domain in property-lists and collection-lists

Property-lists and collection-lists demonstrate an agency operating on behalf of a brand, not a brand operating directly. Setting operator to the brand's own domain misleads readers about the authorized_operators / brand.json trust model. Switched to pinnacle-agency.example to match the rest of the buyer-side storyboards.

#2528 — Standardize buyer-side steps on account { brand, operator }

Two shapes existed in the tree:

  • top-level 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.yaml step 1 used the account shape, steps 2–6 used top-level brand). Standardized buyer-side to the expressive form:

  • when a step already had an account block, dropped the redundant top-level brand
  • otherwise, promoted the top-level brand into an account wrapper with operator: "pinnacle-agency.example"

Top-level brand remains valid as documented shorthand for minimal-identity examples — not touched in this PR.

Closes

Test plan

  • npm run build:compliance clean (10 universal / 6 protocols / 24 specialisms)
  • npm run test:schemas passes (all 483 schemas)
  • npm run test:examples passes (31/31)
  • npm run test:unit passes (587/587)
  • typecheck passes
  • spot-checked transformed YAML — account wrapper is well-formed, redundant brand removal is correct

Follow-up

PR B (#2527, #2529, #2531) will land the scoping lint + runtime fallback on top of this cleaned tree.

🤖 Generated with Claude Code

…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>
…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
bokelley merged commit 99d395e into main Apr 20, 2026
12 checks passed
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>
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