Skip to content

fix(storyboard): add pattern validation checks#2122

Merged
bokelley merged 3 commits into
mainfrom
github-issue-2121
May 30, 2026
Merged

fix(storyboard): add pattern validation checks#2122
bokelley merged 3 commits into
mainfrom
github-issue-2121

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Adds field_pattern and envelope_field_pattern storyboard validations with regex compilation, missing-field handling, and non-string failure reporting.

Extends conformance replay and storyboard drift detection so the new checks are enforced consistently, including version-envelope fields such as adcp_version.

Adds regression coverage for payload fields, envelope fields, invalid patterns, replay drift, and version negotiation.

Validated with npm run build:lib, focused storyboard tests, git diff --check, and the pre-push typecheck/build hook.

@bokelley bokelley marked this pull request as ready for review May 30, 2026 15:52
Copy link
Copy Markdown

@aao-ipr-bot aao-ipr-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two blockers. The library/CLI change ships without a changeset, and the drift detector is half-wired for the new payload check kind. Both are mechanical fixes — the actual implementation is sound.

Blockers

  1. Missing changeset. src/lib/testing/storyboard/validations.ts and src/lib/testing/storyboard/types.ts are published via the ./testing export. Check for changeset is already failing CI. npm run changesetminor (additive StoryboardValidationCheck union members + new pattern?: string field on StoryboardValidation).

  2. Drift detection collects payload field_pattern and drops it. test/lib/storyboard-drift.test.js:235 extends collectFieldValidations to gather payload field_pattern entries, but the three reachability describe blocks at L325 (field_present), L398 (field_value), and L419 (field_value_or_absent) each filter strictly on their own check kind. The envelope-variant block at L371-377 correctly added envelope_field_pattern. Payload field_pattern is collected and silently discarded — the moment a storyboard authors field_pattern { path: 'creative.bogus_url' }, a typo'd path passes drift detection. Add a mirror block:

    ```js
    describe('field_pattern paths are reachable in response schemas', () => {
    const patternValidations = fieldValidations.filter(v => v.check === 'field_pattern');
    // …same body as the field_value block at L398-417
    });
    ```

Things I checked

  • `validateFieldPattern` shape matches sibling validators — `resolvePath` + `toJsonPointer`, `actual ?? null` for missing fields, echoes `check` so reporters distinguish payload vs envelope variants. Same envelope as `validateFieldValue` (validations.ts:837-903).
  • `scripts/conformance-replay.ts:336-362` inline implementation is in lockstep with the SDK helper — same empty-pattern reject, same invalid-regex reject, same non-string typeof reporting. The inline pattern is the existing convention in this script (`field_value`, `array_length` are inlined the same way). Acceptable as long as the two sides stay in sync; flag if a future PR drifts one without the other.
  • `ProtocolEnvelopeSchema.merge(AdCPVersionEnvelopeSchema)` is valid. Both are `z.object(...).passthrough()`, the field sets are disjoint (envelope: `status`/`task_id`/`message`/`replayed`/etc. vs version envelope: `adcp_version`/`adcp_major_version`), no silent override.
  • The version-negotiation integration test at L171-178 actually proves what the PR claims — asserting both `validation.not_applicable === undefined` and `result.validations_not_applicable === undefined` confirms `envelope_field_pattern` hits the explicit switch case and not the forward-compat fallback.
  • ReDoS surface acceptable. Storyboard YAML is authored content, runner is a test harness, no adopter-traffic path.

Follow-ups (non-blocking)

  1. Asymmetric `?? null` in misconfiguration branches. validations.ts:1047 uses `actual: validation.pattern ?? null`; the invalid-regex branch at 1065 uses `actual: validation.pattern` (no `?? null`). The latter is guarded to be a non-empty string, so the `?? null` would be a no-op — but the asymmetry reads as a thinko. Match the two for grep symmetry.
  2. Stale assertion message at storyboard-drift.test.js:270. Says `'Expected at least one field_present, field_value, or field_value_or_absent validation'` — doesn't mention the pattern variants. Cosmetic.

Re-request review once the changeset lands and the payload-pattern reachability block is added.

aao-ipr-bot[bot]
aao-ipr-bot Bot previously approved these changes May 30, 2026
Copy link
Copy Markdown

@aao-ipr-bot aao-ipr-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Closes a real gap in the storyboard validation surface — runners were grading author-supplied field_pattern checks not_applicable under the forward-compat default, so storyboards asserting "looks like a release-precision version string" were silently passing. Fix-shaped, wired through all four enforcement seams (dispatcher, drift detection, conformance replay, version-negotiation runner), with the failure-shape matrix tested end-to-end.

Things I checked

  • validateFieldPattern (src/lib/testing/storyboard/validations.ts:1020-1106) — five failure modes branch correctly: missing path, missing/empty pattern, invalid regex source, missing/non-string value, mismatch. JSON pointer emitted on every failure path. expected: { pattern } shape consistent with the rest of the runner-output contract.
  • Dispatcher wiring at validations.ts:256-273 — both field_pattern and envelope_field_pattern route to validateFieldPattern. Envelope case-block fall-through still resolves correctly today.
  • Drift detection (test/lib/storyboard-drift.test.js:22-25) — ProtocolEnvelopeSchema.merge(AdCPVersionEnvelopeSchema) widens the reachability set to include adcp_version. The prior comment claiming adcp_version is "NOT an envelope field" is now contradicted; the adcp#5195 reference is the right justification.
  • Conformance replay (scripts/conformance-replay.ts:333-362) — same branching as validateFieldPattern, distinct shape because replay emits failures.push(string) not ValidationResult.
  • Test coverage — 10 new cases on the validations file (pass / mismatch / missing / non-string / no-pattern / invalid-regex × field_pattern and envelope_field_pattern) plus the version-negotiation integration test proving not_applicable short-circuit no longer fires.
  • ReDoS surface — patterns are storyboard-author-supplied and run only in CI, not on hot adopter paths. Acceptable.

Follow-ups (non-blocking — file as issues)

  • Changeset type. patch is debatable. Two members added to an exported StoryboardValidationCheck union — additive, not breaking, but storyboard authors using exhaustive switch on the union will notice. minor would match how array_length and the envelope-prefixed checks landed. Not blocking; consumer-side narrowing stays safe.
  • Replay-script duplication. conformance-replay.ts:336-362 reimplements the same misconfig / invalid-regex / wrong-type branching as validateFieldPattern. Different output shape justifies separate sites, but a shared compilePattern(pattern): { re?: RegExp; error?: string } helper would keep error wording in lockstep across the two seams.
  • No regex flag support. A storyboard wanting case-insensitive match can't get it. Out of scope here; flag for a later minor that introduces pattern_flags?: string with an allowlist (i/m/s only — no g).

Minor nits (non-blocking)

  1. Positional fall-through at validations.ts:267. return validateFieldValueOrAbsent(...) is correct today because envelope_field_value_or_absent is the only case left in the block. Anyone adding a future envelope kind to the union without a matching if arm above will silently route to validateFieldValueOrAbsent. Replace with an explicit if (validation.check === 'envelope_field_value_or_absent') return validateFieldValueOrAbsent(...) and let the switch's default (already an exhaustiveness-style forward-compat) carry the unknown-kind case.
  2. actual: actual ?? null at validations.ts:1082. Collapses explicit-null and undefined to the same Field not found branch (the === undefined || === null test at L1077 already does this). Consistent with the existing field_present/field_absent convention — leave it, but a one-line comment noting "explicit null is reported as missing per convention" would save the next reader a trip.

Notable that the PR title/changeset both use fix(storyboard): while strictly adding new API surface — fits how this fills a gap the runner already half-supported, but the changeset-versioning purists will note it.

LGTM. Follow-ups noted.

Copy link
Copy Markdown

@aao-ipr-bot aao-ipr-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Closes a real gap — the runner's forward-compat fallback was silently grading field_pattern assertions not_applicable, so "seller echoes release-precision adcp_version like 3.1-rc.3" was passing on every implementation regardless of what the wire said. Fix is downstream of upstream spec adcp#5195 (runner-output-contract v2.5.0), which authoritatively adds the two new check kinds and reclassifies adcp_version as an envelope field. SDK is implementing the spec, not ahead of it.

Things I checked

  • validateFieldPattern at src/lib/testing/storyboard/validations.ts:1020-1106 — five failure modes branch cleanly (missing path / missing-or-empty pattern / invalid regex / non-string / mismatch), JSON pointer emitted on every failure path, expected: { pattern } shape consistent with runner-output-contract v2.5.0.
  • Dispatcher refactor at validations.ts:252-273 — positional fall-through replaced with explicit case arms (addressed a prior reviewer's note). Read line-by-line, no missing break, no typos.
  • ProtocolEnvelopeSchema.merge(AdCPVersionEnvelopeSchema) at test/lib/storyboard-drift.test.js:24-25 — envelope schemas at src/lib/types/schemas.generated.ts:2325-2342 have disjoint keys (status/task_id/etc. vs adcp_version/adcp_major_version), .merge() is safe. The errors lives-in-payload invariant comment is preserved.
  • scripts/conformance-replay.ts:333-362 mirrors the SDK helper on all five failure modes — same empty-pattern reject, same invalid-regex reject, same non-string reporting. Output shape diverges as expected (replay emits failures.push(string) not ValidationResult).
  • Drift detector now has a payload-field_pattern reachability describe block at test/lib/storyboard-drift.test.js:419-440 — verbatim mirror of the field_value block. Earlier iteration collected field_pattern and dropped it; that's now closed.
  • Integration test at test/lib/storyboard-version-negotiation.test.js:107-180 asserts both validation.not_applicable === undefined AND result.validations_not_applicable === undefined — proves the new kind hits the explicit switch case, not the forward-compat fallback. That's the actual behavior change this PR claims.
  • Changeset is minor. Correct: two additive members on the exported StoryboardValidationCheck union plus one new optional pattern?: string field. Aligns with how array_length and the envelope-prefixed kinds landed.
  • Witness-not-translator invariant intact — no wire fabrication, no normalization, no envelope synthesis. Test-runner-only addition.

Follow-ups (non-blocking — file as issues)

  1. Replay-script duplication. conformance-replay.ts:333-362 reimplements the same misconfig / invalid-regex / wrong-type branching as validateFieldPattern. Different output shape justifies separate sites today, but a shared compilePattern(pattern): { re?: RegExp; error?: string } helper would keep the two seams in lockstep. Flag the next PR that touches one without the other.
  2. No regex flags. Storyboards can't author case-insensitive or multiline matches. Out of scope here — the spec yaml describes pattern as bare regex source, so this matches the upstream contract. If a future spec rev adds flags, both validations.ts and conformance-replay.ts need updating in lockstep.

Minor nits (non-blocking)

  1. Array-type reporting drift. validateFieldPattern at validations.ts:1079 reports arrays as 'array' via an explicit Array.isArray check; conformance-replay.ts:356 falls through to bare typeof and reports them as 'object'. Cosmetic — pass/fail decision is identical — but the next person to grep failure messages will notice.

Interesting choice to ship purely additive surface under a fix(...) title and commit message — the changeset type and the prior bot's "fits the gap" framing both land on the right shape, but the convention purists will note it.

LGTM. Follow-ups noted.

@bokelley bokelley merged commit f9ed580 into main May 30, 2026
30 checks passed
@bokelley bokelley deleted the github-issue-2121 branch May 30, 2026 16:21
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