Skip to content

Enforce v3 envelope integrity storyboard#4967

Merged
bokelley merged 2 commits into
mainfrom
bokelley/kingston-v11
May 23, 2026
Merged

Enforce v3 envelope integrity storyboard#4967
bokelley merged 2 commits into
mainfrom
bokelley/kingston-v11

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

  • Enables live envelope_field_absent assertions in v3_envelope_integrity for legacy task_status / response_status fields.
  • Adds envelope_field_absent to the authored check contract and documents failure expected / actual semantics.
  • Tightens storyboard path linting so envelope-prefixed checks resolve only against core/protocol-envelope.json, while preserving response-schema forbidden-field support for plain field_absent.
  • Corrects version_negotiation to use field_present for adcp_version, which comes from core/version-envelope.json, not core/protocol-envelope.json.

Fixes #4930.

Tests

  • npm run test:storyboard-validations-paths
  • npm run test:storyboard-check-enum
  • npm run test:storyboard-contradictions
  • TENANT_PATH=sales npx tsx server/tests/manual/run-storyboards.ts --filter v3_envelope_integrity
  • npm run test:storyboards
  • npm run test:storyboards:3.0-compat
  • pre-commit: npm run test:unit, npm run test:test-dynamic-imports, npm run test:callapi-state-change, npm run typecheck
  • pre-push: current storyboard matrix and 3.0.12 compatibility matrix

aao-release-bot[bot]
aao-release-bot Bot previously approved these changes May 23, 2026

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving. Closes the v3 envelope absence loop cleanly — task_status/response_status are now asserted absent at runtime, and the lint tightening keeps envelope_field_* honest about which schema it walks.

Things I checked

  • static/schemas/source/core/protocol-envelope.json:64-69 carries the not: { anyOf: [{ required: [task_status] }, { required: [response_status] }] } constraint. Storyboard now matches the schema; the TODO is retired correctly, not papered over.
  • adcp_version lives in static/schemas/source/core/version-envelope.json, not protocol-envelope.json. The flip to field_present in version-negotiation.yaml:117 is the right routing — once envelope_field_* was pinned to protocol-envelope.json, the old check would have started failing. Pairing is coherent, not accidental.
  • pathIsForbiddenByRequiredNot at scripts/lint-storyboard-validations-paths.cjs:310-340 handles the exact top-level not.anyOf[].required pattern in the envelope and breaks $ref cycles via the seen set.
  • Tests cover the three branching outcomes — envelope-absent acceptance, wrong-kind rejection, payload-path rejection — plus the contradictions classifier addition at tests/lint-storyboard-contradictions.test.cjs:177.
  • runner-output-contract.yaml bumped 2.2.0 → 2.3.0. Additive (new enum + expected/actual docs); the right semver call.

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

  • No changeset for the contract bump. Prior 2.1.0 → 2.2.0 shipped .changeset/4418-runner-output-notices-field.md, and static/compliance/source/ has been treated as changeset-eligible elsewhere (4547, 4399, 4339). Either add a minor changeset, or document the carve-out so the next compliance-source PR knows it's intentional.
  • storyboard-schema.yaml check-enum docs still omit plain field_absent even though runner-output-contract.yaml now lists it as an authored kind. Pre-existing drift, but this PR is the natural place to harmonize since the enum block is already being edited.

Minor nits (non-blocking)

  1. Dead-code arm at scripts/lint-storyboard-validations-paths.cjs:404. The fall-through if (v.check === 'field_absent' || v.check === 'envelope_field_absent') includes the envelope kind, but the routing block at L385-L402 unconditionally continues for both envelope kinds — that half of the || is unreachable. Either drop it or leave a one-line comment, otherwise the next reader will assume envelope-absent has a payload-schema fallback.
  2. pathIsForbiddenByRequiredNot is envelope-root only. The segments.length !== 1 bail at scripts/lint-storyboard-validations-paths.cjs:310 means nested paths silently won't get the forbidden-by-required-not exemption. Fine for today's usage; worth a one-line comment so a future caller doesn't assume deeper paths work.
  3. Stray tab at static/compliance/source/universal/runner-output-contract.yaml:266. Leading \t on the envelope_field_absent actual-continuation comment; every other line in that block is space-indented. Doesn't break YAML parse, but git diff --check will flag it.

A new check kind landing without a changeset, after the file's last bump shipped one, is notable. Worth picking a convention before the next bump.

LGTM. Follow-ups noted above.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Follow-ups noted below. Clean activation of a previously stubbed invariant — the lint walker, storyboard, and runner contract all line up, and the envelope_field_presentfield_present flip on adcp_version is the right shape because that field lives in core/version-envelope.json, not core/protocol-envelope.json.

Things I checked

  • scripts/lint-storyboard-validations-paths.cjs:268-310pathIsForbiddenByRequiredNot walks not, not.anyOf, not.oneOf and recurses through oneOf/anyOf/allOf and $ref with the same seen cycle guard pattern as pathResolves. segments.length !== 1 is the right guard for the v3 invariant: protocol-envelope.json's top-level not: { anyOf: [{ required: [task_status] }, { required: [response_status] }] } only forbids top-level keys.
  • scripts/lint-storyboard-validations-paths.cjs:404-407forbiddenByEnvelope is load-bearing, not dead. It's what stops check: field_absent, path: task_status against get-adcp-capabilities-response.json from accidentally passing via the envelope's not constraint reachable through allOf. The new test at tests/lint-storyboard-validations-paths.test.cjs:234 pins that behavior.
  • static/compliance/source/universal/v3-envelope-integrity.yaml:17-23, 99-110 — prose no longer mentions the prior TODO / "not yet shipped" status. Storyboard now asserts the full v3 invariant end-to-end against protocol-envelope.json:64-69.
  • static/compliance/source/universal/version-negotiation.yaml:117adcp_version is defined only in core/version-envelope.json (self-described there as "part of the payload itself"), so field_present (which walks the response body through allOf) is correct and envelope_field_present would have emitted a false negative.
  • static/compliance/source/universal/runner-output-contract.yaml — 2.2.0 → 2.3.0 bump, envelope_field_absent added to authored_check_kinds, expected/actual semantics documented for both field_absent and envelope_field_absent. Forward-compat preserved — older runners grade unknown kinds not_applicable per the contract's own rule.
  • .changeset/envelope-integrity-storyboard.md present and describes the change accurately.
  • Three new lint tests cover the contract correctly: envelope-forbidden absence allowed, wrong-kind rejected, payload-path rejected.

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

  • Changeset level may be off. PR #4418 bumped the same contract 2.1.0 → 2.2.0 to add the notices block and used minor. This PR makes the directly analogous additive runner-surface change and is marked patch. The change is forward-compatible (unknown kinds grade not_applicable) so it doesn't break adopters, but the precedent points at minor. Either bump it or document the rationale in the changeset body.
  • storyboard-schema.yaml:1045-1058 enum prose is partial. The doc-comment now names envelope_field_present / envelope_field_absent but still doesn't name field_absent, even though field_absent is in the canonical authored_check_kinds list. The file's own header says "Add new kinds to that enum AND document semantics here in the same PR" — pre-existing gap that was natural to close here.

Minor nits (non-blocking)

  1. Dead disjunct in the lint walker. scripts/lint-storyboard-validations-paths.cjs:404|| v.check === 'envelope_field_absent' is unreachable because the dedicated branch at L385-L401 always continues for that check kind. The inner narrow at L406 already matches only on field_absent. Either drop the || or simplify the outer condition.
  2. Stray TAB in an otherwise space-aligned comment block. static/compliance/source/universal/runner-output-contract.yaml:266 begins with a literal \t inside the new envelope_field_absent documentation block this PR is editing; every other comment line in that file uses spaces. YAML still parses (the line is inside a comment), but it visibly skews in any editor with non-default tab width.

LGTM. Follow-ups noted below.

@bokelley bokelley merged commit 9026544 into main May 23, 2026
37 of 38 checks passed
@bokelley bokelley deleted the bokelley/kingston-v11 branch May 23, 2026 14:24
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.

compliance: unskip and enforce v3_envelope_integrity before 3.1 GA

1 participant