Skip to content

compliance(storyboards): richer env fingerprint + requires_scenarios resolution#2679

Merged
bokelley merged 1 commit into
mainfrom
bokelley/env-fingerprint-scenarios
Apr 21, 2026
Merged

compliance(storyboards): richer env fingerprint + requires_scenarios resolution#2679
bokelley merged 1 commit into
mainfrom
bokelley/env-fingerprint-scenarios

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Bundles two follow-ups filed after #2676:

  • #2670 part 1 — add `doc.prerequisites.test_kit` and top-level `doc.fixtures` to the contradiction lint's env fingerprint.
  • #2671 — resolve `requires_scenarios:` before flagging `orphan_contribution`, so a parent storyboard that delegates grading to a linked scenario doesn't false-positive.

Both are precision-adding, non-breaking changes. Contradiction counts on the current source tree are unchanged (36/56 clean, 295 passed).

Env fingerprint (#2670 part 1)

Before:
```
sb=<doc.id>|scenario=<comply_scenario>|auth=type:strat|seed=<controller_seeding>
```

After:
```
sb=<doc.id>|test_kit=<prerequisites.test_kit>|fixtures=|scenario=<comply_scenario>|auth=type:strat|seed=<controller_seeding>
```

Covers two legitimate "same id+scenario, different outcome" cases:

  • Two storyboards copied from one template, running against different agent fixtures via different `prerequisites.test_kit` paths.
  • Same storyboard seeded with different controller state via top-level `fixtures:` entries.

The `sb=<doc.id>` component stays in place — removing it (so cross-storyboard contradictions surface) remains tracked as part 2 of #2670 with triage time to match.

Orphan contribution + requires_scenarios (#2671)

`buildScenarioFlagIndex` walks the source tree once and produces `Map<doc.id, Set>` keyed on every storyboard/scenario file's top-level `id:`. `lintDoc` accepts a `scenarioFlagIndex` option; when a parent doc declares `requires_scenarios:`, the linked scenarios' asserted flags union into the parent's own before the orphan check runs.

Duplicate `doc.id` across files throws — order-dependent resolution of a collision could silently mask a real orphan elsewhere, so we surface the duplicate at lint time rather than papering over it.

Unresolved scenario ids fall through silently (scoping-lint concern per the issue); non-string `requires_scenarios` entries are skipped defensively.

Tests

  • `npm run test:storyboard-contradictions` — 21 tests (2 new: `test_kit` discrimination, `fixtures` discrimination)
  • `npm run test:storyboard-branch-sets` — 19 tests (4 new: `requires_scenarios` resolution, unresolved-scenario fallback, duplicate-id throw, non-string entry skip)
  • `npm run build:compliance` — all four storyboard lints pass
  • Precommit (`npm run test:unit` + `typecheck`) — 631 tests pass
  • Training Agent Storyboards locally — 36/56 clean, 295 passed (unchanged from main, above CI floors)

Expert review

One round pre-merge:

  • Duplicate-id throw added per code reviewer's load-bearing note (applied in commit).
  • Strengthened `buildScenarioFlagIndex` test with an anchor scenario id check.
  • Non-string `requires_scenarios` entry test pinning defensive skip behavior.

🤖 Generated with Claude Code

…resolution

Closes #2670 part 1 and #2671.

Env fingerprint (#2670 part 1):
- fingerprintEnv now includes doc.prerequisites.test_kit and a hash of
  top-level doc.fixtures. Two storyboards sharing id + comply_scenario
  but loading different test kits or seeding different prerequisite
  state are now discriminated instead of collapsing into one bucket.
- Non-breaking: no change to current storyboards' contradiction
  outcomes. Removal of sb=<doc.id> (so cross-storyboard contradictions
  surface) remains tracked at #2670.

Orphan-contribution: requires_scenarios resolution (#2671):
- buildScenarioFlagIndex walks the source tree once, producing
  Map<doc.id, Set<flag>> of asserted flags per storyboard/scenario.
- lintDoc accepts a scenarioFlagIndex option. When the parent doc
  declares requires_scenarios, the linked scenarios' asserted flags
  union into the parent's own before the orphan check. A parent that
  delegates grading to a shared scenario no longer false-positives.
- Duplicate doc.id across files throws: order-dependent resolution
  could silently mask a real orphan elsewhere, so collisions surface
  at lint time.
- Unresolved scenario ids fall through (scoping lint concern); non-
  string requires_scenarios entries are skipped defensively.

Tests: 21 contradiction tests (2 new env-discrimination cases for
test_kit and fixtures), 19 branch-set tests (4 new: requires_scenarios
resolution, unresolved-scenario fallback, duplicate-id throw, non-string
entry skip).

Build: 36/56 clean / 295 passed — unchanged from main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 9bfd39b into main Apr 21, 2026
12 checks passed
bokelley added a commit that referenced this pull request Apr 21, 2026
…ios spec

Two hygiene follow-ups from #2679 expert review.

Fixture hash normalization (#2682):
- normalizeFixturesForHashing sorts arrays within each documented
  fixture category by its primary id before stableStringify.
- Categories + primary ids: products/product_id, pricing_options/
  pricing_option_id, creatives/creative_id, plans/plan_id,
  media_buys/media_buy_id.
- Unknown fixture category throws: schema-doc and lint updates must
  land together so a new seed category can't silently create a
  false-negative env-fingerprint bucket.
- Closes false-negative envelope before #2670 part 2 removes
  sb=<doc.id> and makes env-fingerprint precision load-bearing.

Spec: flag flow across requires_scenarios (#2683):
- storyboard-schema.yaml now normatively describes how branch_set
  contribution flags flow between parent and linked scenarios.
- Parent-flag flow: contributed in X, asserted in X or in any
  scenario Y listed in X.requires_scenarios.
- Reverse flow prohibited — scenarios MUST be internally self-
  grading to preserve standalone lintability and prevent cycles
  in the composition graph.
- Scenario IDs must match the referenced file's top-level id: and
  the file MUST exist at build time.

Tests: contradiction suite 25 (3 new — array-order stability,
content-difference still discriminated, unknown category throws +
documented categories round-trip).

Follow-up #2687 filed for unresolved_scenario_reference grading
(symmetric with the duplicate-id throw already in place).

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

* compliance(storyboards): drop sb=<doc.id> from contradiction lint env fingerprint (#2670 part 2)

Including the storyboard id was conservative-by-design during the
lint's initial rollout (#2661) but suppressed the exact class of bug
the lint was built to catch — cross-storyboard contradictions (#2627,
#2628, #2629). Prerequisites for the removal are now in place: #2679
added test_kit + top-level fixtures precision, PR #2710 (from #2684)
audited principal identity and added role= as the forward guard, and
#2708 tracks the remaining auth= structural gap.

Experiment on the current 56-storyboard suite: removing sb= surfaces
10 multi-member groups (7 spanning ≥2 files), including 25 files
agreeing that get_adcp_capabilities returns success on acme-outdoor
and 6 doing the same on nova-motors. Zero contradictions — the suite
is genuinely consistent and the lint now exercises cross-storyboard
checks as originally intended.

Inverts one test (no-contradiction-when-ids-differ → contradictions-
surface-when-ids-differ-but-env-matches) to pin the new behavior,
adds a complementary negative test (different test_kit + different
ids → no contradiction) to pin test_kit= as a cross-file separator
now that sb= is gone, and updates the `test_kit discriminates env`
comment to note test_kit= is its sole discriminator post-removal.

Protocol review surfaced a residual hole that sb= removal exposes:
steps inheriting the transport's default auth emit no auth= component,
so two storyboards with different transport defaults could collide.
Out of scope here; tracked as #2711.

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

* compliance(storyboards): resolved-auth fingerprint shape (#2708 + #2711) (#2723)

Extracts auth emission into a describeStepAuth() helper that always
returns a stable token. Fixes two structural gaps that removing
sb=<doc.id> (#2670 part 2) exposed:

  #2711 — inherited-default steps (336 of 340 in the current suite)
    used to emit no auth= component, so they could collide silently
    with explicit steps authored against divergent transport
    defaults. Now emits auth=kit_default.

  #2708 — auth.from_test_kit was read as boolean regardless of
    whether it was true or a string path, collapsing distinct
    principals in multi-principal kits. Now emits
    auth=<type>:from_test_kit:<path> for string form. Literal
    auth.value is sha1-8hex hashed so different literals don't
    collide (defense-in-depth; #2720 tracks flagging literals as a
    code smell).

Zero grouping change on the current suite — no storyboard uses
string-form from_test_kit or literal value; all 336 inheriting
steps get the new kit_default token uniformly.

Tests:
  - describeStepAuth shape-matrix unit test, every branch including
    defensive fallbacks. Literal hashes pinned to precomputed sha1
    values for stability.
  - Inherited-default cross-storyboard test asserting kit_default
    emission and contradiction surface when outcomes disagree.
  - Multi-principal forward-guard test pinning that from_test_kit
    path selection discriminates, with direct fingerprintEnv
    comparison asserting the path token appears verbatim.

Protocol review surfaced one structural question deferred as
#2721: runner test kits (webhook-receiver, substitution-observer,
signed-requests) have no auth: block — if a storyboard ever
composes runner + primary kit, kit_default becomes ambiguous.
No storyboard does that today.

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