Skip to content

fix(storyboard-runner): preserve sample_request fields in enrichers (#1604)#1607

Merged
bokelley merged 1 commit intomainfrom
bokelley/fix-1604-runner-enricher-field-drop
May 8, 2026
Merged

fix(storyboard-runner): preserve sample_request fields in enrichers (#1604)#1607
bokelley merged 1 commit intomainfrom
bokelley/fix-1604-runner-enricher-field-drop

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 8, 2026

Summary

Issue #1604src/lib/testing/storyboard/request-builder.ts fixture-aware enrichers rebuilt their request body from scratch and silently dropped any top-level sample_request field outside an enumerated allowlist. The non-proposal create_media_buy path was the immediate trigger (proposal-mode storyboards lose proposal_id, total_budget, idempotency_key from the YAML before they reach the wire). PR #1603 patched the proposal-mode branch but didn't apply the structural fix to the rest of the enricher.

Audit

FIXTURE_AWARE_ENRICHERS is the only set where the bug manifests — these enrichers bypass the outer fixture overlay ({ ...enriched, ...fixture }) in enrichRequest. Non-fixture-aware enrichers don't drop fields because the outer merge re-applies the fixture.

Enricher Status before Action
create_media_buy (non-proposal) Build-from-scratch, dropped fields Fixed — spread sample_request then override
create_media_buy (proposal) Already spread (PR #1603) Hardened — envelope fields omitted from local spread so mustache tokens expand via outer merge
update_media_buy Already spread (PR #1505) Hardened — same envelope omission
get_media_buys Already spread (PR #1487) Hardened — same envelope omission
get_media_buy_delivery Already spread (PR #1487) Hardened — same envelope omission
comply_test_controller Already spread (#1419) Hardened — same envelope omission

Structural fix

Spread sample_request first (after $context injection), then layer the runner's substitutions (account, brand, normalised dates, packages with discovery-derived identifiers) on top. Envelope fields (context, ext, push_notification_config, idempotency_key) are deliberately omitted from the local spread and re-applied by the outer enrichRequest with runnerVars so mustache substitutions like {{runner.webhook_url:<step_id>}} expand correctly.

A new omitEnvelopeFields helper centralises the envelope-omission discipline and is applied uniformly across every fixture-aware enricher that spreads sample_request.

Regression test

test/lib/request-builder.test.js — new test preserves top-level sample_request fields the enricher does not normalise (#1604). Pins total_budget, buyer_ref, currency, reporting_webhook pass-through. Verified to fail when the fix is reverted (79/80 pass; the new test fails as expected).

Allowlist

test/examples/hello-seller-adapter-proposal-mode.test.js EXPECTED_FAILURES is already [] (cleared by PR #1603 ahead of this structural fix). No further change required.

Verification

  • npm run format:check — clean
  • npx tsc --noEmit — clean
  • node --test test/examples/hello-seller-adapter-proposal-mode.test.js — 3/3 pass
  • node --test test/lib/request-builder.test.js test/lib/storyboard-envelope-passthrough.test.js — 84/84 pass
  • npm test — 8338 pass, 0 fail (3 skipped)

Refs

Test plan

  • New regression test fails when fix is reverted
  • Proposal-mode adapter gate passes with empty allowlist
  • Full test suite passes
  • Format and tsc checks clean

🤖 Generated with Claude Code

… mutating-tool requests (#1604)

Fixture-aware enrichers in `request-builder.ts` rebuilt their request body
from scratch and only copied an enumerated set of fields from
`step.sample_request` (start_time, end_time, packages for create_media_buy).
Anything else the storyboard authored at the top level — `total_budget`,
`buyer_ref`, `currency`, `reporting_webhook`, scenario-specific extensions
— was silently dropped before the request hit the wire. The non-proposal
`create_media_buy` path was the immediate trigger; PR #1603's proposal-mode
branch already spread the fixture but the structural fix wasn't applied to
the rest of the enricher.

The fix spreads `sample_request` first (after `$context` injection), then
layers the runner's substitutions (account, brand, normalised dates,
packages with discovery-derived identifiers) on top. Envelope fields
(`context`, `ext`, `push_notification_config`, `idempotency_key`) are
deliberately omitted from the local spread and re-applied by the outer
`enrichRequest` with `runnerVars` so mustache substitutions like
`{{runner.webhook_url:<step_id>}}` expand correctly. The same
`omitEnvelopeFields` discipline is now applied uniformly across
`create_media_buy`, `update_media_buy`, `get_media_buys`,
`get_media_buy_delivery`, and `comply_test_controller`.

Audit of FIXTURE_AWARE_ENRICHERS (the only path where the bug manifests
because they bypass the outer fixture overlay):

- `create_media_buy` non-proposal — fixed (build-from-scratch dropped fields)
- `create_media_buy` proposal — already spread, hardened with envelope
  omission so push_notification_config tokens expand correctly
- `update_media_buy` — already spread; hardened with envelope omission
- `get_media_buys` — already spread; hardened with envelope omission
- `get_media_buy_delivery` — already spread; hardened with envelope omission
- `comply_test_controller` — already spread; hardened with envelope omission

Non-FIXTURE_AWARE enrichers don't manifest the bug because the outer
`enrichRequest` already does `{ ...enriched, ...fixture }` so the fixture
overlays the enricher's hardcoded keys.

Regression test pinning the field-pass-through is added to
`test/lib/request-builder.test.js` (verified to fail when the fix is
reverted). The proposal-mode adapter gate's EXPECTED_FAILURES allowlist
remains empty.

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