feat(storyboard): cross-step assertion registry + runner hooks (adcp#2639)#692
Merged
Conversation
Adds `registerAssertion(spec)` plus onStart/onStep/onEnd lifecycle hooks in `runStoryboard`. Storyboards opt in via a top-level `invariants: [id, ...]` array; the runner resolves ids at start (throws on unknowns), drives the three hooks, routes step-scoped failures into `validations[]` as `check: "assertion"`, and records every result in a new `StoryboardResult.assertions[]` field. A failed assertion flips `overall_passed` — gating, not advisory. Framework only; no assertion implementations land here. Concrete modules (idempotency dedup, governance denial mutation-block, status monotonicity, context-echo secret redaction) live in adcontextprotocol/adcp alongside the specialisms that own them. Part of adcontextprotocol/adcp#2639. 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 21, 2026
bokelley
added a commit
that referenced
this pull request
Apr 21, 2026
#713) The underlying `./storyboard/assertions` module exports `registerAssertion` and friends, and the CLI `--invariants` flag expects users to import them from `@adcp/client/testing`. The parent `testing/index.ts` forgot the re-exports in #692. Fix. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
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
Adds a cross-step assertion registry and
onStart/onStep/onEndlifecycle hooks torunStoryboard. Storyboards express per-step checks inline already (response_schema,field_value,http_status, …); this PR adds the missing layer for properties that must hold across a whole run — governance denial never mutates, idempotency dedup across replays, status transitions monotonic, context never echoes secrets on error.Framework only. Concrete assertion modules (idempotency dedup, governance mutation-block, status monotonicity, secret redaction) live in adcontextprotocol/adcp alongside the specialisms that own them; they'll register via the new API and be declared on their specialism's
invariants: [...]list.What changed
src/lib/testing/storyboard/assertions.ts— new module.registerAssertion(spec),getAssertion(id),listAssertions(),clearAssertionRegistry(),resolveAssertions(ids). Throws on duplicate ids at registration and on unknown ids at resolve.src/lib/testing/storyboard/types.ts—Storyboardgains optionalinvariants?: string[].StoryboardResultgains optionalassertions?: AssertionResult[]. NewAssertionResulttype (scope: "step" | "storyboard", optionalstep_id).src/lib/testing/storyboard/runner.ts— three insertions inexecuteStoryboardPass:storyboard.invariants→ specs (fails fast on unknown id); init per-assertion state; awaitonStart(ctx)on each.onStep(ctx, stepResult); append each returned result intostep.validations[]undercheck: "assertion"AND into the storyboard-levelassertions[]withscope: "step"; flipresult.passed = falseon any failure so the existing count/stateful-cascade logic treats it as a validation failure.onEnd(ctx); record results withscope: "storyboard"; factorassertionsFailedintooverall_passed.assertions[](no dedup — a divergence across passes should surface).@adcp/client/testingfor the registry + types.Shape
Storyboard wiring:
Why gate
overall_passed?Assertions encode conformance properties — a run where every validation is green but
plan_hashchurned across re-renders, or a denied plan still acquired a buy, is not a conformant run. Treating assertions as advisory would recreate the problem storyboards already have (authors memorize scripts and ignore broader guarantees).Test plan
onStartcalled once with fresh per-assertionstateonStepcalled for every step in orderstep.validations[]andresult.assertions[]and flipsoverall_passedonEndfailure surfaces asscope: "storyboard"and flipsoverall_passedeven when all steps passstatecarries acrossonStart→onStep→onEndon the same assertioninvariantsare unaffected (noassertionskey on result; no hooks fire)invariantsthrows at run startoverall_passedvs baselinestoryboard-brand-invariant,storyboard-idempotency-invariant,storyboard-runner-contract) still green.Non-goals
Related
🤖 Generated with Claude Code