Skip to content

feat(api-sync): enum + nested-object coverage checks, delivery manifest verification - #70

Merged
ericviana merged 1 commit into
mainfrom
eric/api-sync-hardening
Aug 4, 2026
Merged

ericviana merged 1 commit into
mainfrom
eric/api-sync-hardening

Conversation

@ericviana

Copy link
Copy Markdown
Member

Summary

No version bump: infra/tooling only, no SDK surface change.

a) Enum coverage check (blocking in CI)

scripts/api-sync/enum-coverage.ts walks every in-scope (reachable, non-ignored) schema and finds every enum-constrained property: direct enum, array-of-enum (items.enum), and anyOf/oneOf-wrapped enums. A property is covered if it resolves to a mapped enum symbol in spec-map.json's enums[], is covered by the ordinary field-type comparison (direct/items enums on any schema already covered by a map.types entry already catch drift today via type-check.ts's specTypeSignature, just without a dedicated enums[] entry), or has a recorded exclusion in .api-sync/unmodeled.json's new enumPropertyOmissions.

The one real, previously-invisible gap this closes: specTypeSignature never looks inside anyOf/oneOf, so an enum wrapped that way was checked by nothing. Wired into index.ts's main flow as a hard failure, so it blocks --check/--apply and gates the existing "sync:check (state + map validity)" job in api-sync-check.yaml. Also covered by a map.test.ts assertion against the committed snapshot. Found zero violations against the current spec; enumPropertyOmissions starts empty.

b) Nested-object coverage (map validation)

scripts/api-sync/nested-coverage.ts recursively enumerates inline object and array-item-object shapes under every in-scope schema (things like PayinOut.tracking_transaction.pse_instruction, VirtualAccountOut.us.ach). spec-map.json's format is flat (schema + top-level property only), so there's no way to "map" a nested shape today; every one of them needs a recorded, honest omission in the new nestedShapeOmissions ledger, or map.test.ts's new assertion fails.

Found 72 pre-existing nested shapes with no tracking at all; recorded all 72 as omissions with a shared, honest reason ("pre-existing, not yet modeled, needs a human pass") so this PR doesn't silently model 72 shapes it hasn't reviewed, but so that any new unmapped nested shape from here on fails loudly instead of joining the pile.

c) Delivery manifest verification

.github/workflows/api-sync.yml's spec-fetch step now fetches both .api-sync/spec-current.json and .api-sync/delivery.json from the api-sync-data branch and:

  • fails loudly if either file is missing,
  • fails if sha256(spec-current.json) doesn't match delivery.json's spec_sha256,
  • fails with "stale delivery" if the repository_dispatch client_payload.spec_sha256 is non-empty and disagrees with delivery.json's.

A dispatch without the manifest (today's state, until v2 PR #1922 ships it) fails loudly by design instead of syncing against an unverified spec — that's the intended failure mode while the two repos land this in parallel.

Sanity check: pipeline-alert.yml

Verified .github/workflows/pipeline-alert.yml on main is present and injection-safe: event fields (WF_NAME, WF_BRANCH, WF_URL, WF_ACTOR) come in only via env:, the Slack payload is built with jq --arg, the webhook URL is quoted, and a missing SLACK_WEBHOOK_URL exits non-zero. No changes made.

Test plan

  • bun run test — 29 files, 178 tests, all pass (new: enum-coverage.test.ts, nested-coverage.test.ts, 2 new map.test.ts assertions)
  • bun run check-types, bun run lint:check — clean
  • bun run sync:check — OK, no pending drift (130 known divergences recorded and skipped)
  • node scripts/contract-check.mjs — unaffected, still passes
  • bun run build — clean

https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs

…st verification

- Add scripts/api-sync/enum-coverage.ts: every enum-constrained property
  (direct, items.enum array form, anyOf/oneOf wrapper) on every in-scope
  (reachable, non-ignored) schema must resolve to a mapped enum symbol
  in spec-map.json's enums[], be covered by the ordinary field-type
  comparison (direct/items enums on a map.types-covered schema; that
  path already catches drift today, just not via a dedicated enums[]
  entry), or have a recorded exclusion in
  .api-sync/unmodeled.json's new enumPropertyOmissions. The real,
  previously-invisible gap is anyOf/oneOf-wrapped enums:
  specTypeSignature never looks inside them. Wired into index.ts's main
  flow as a hard failure (blocks --check/--apply, so it gates the
  existing "sync:check (state + map validity)" CI job) and covered by
  a map-validation vitest test against the committed snapshot. Found
  zero violations against the current spec; enumPropertyOmissions
  starts empty.
- Add scripts/api-sync/nested-coverage.ts: recursively enumerate inline
  object and array-item-object shapes under every in-scope schema.
  spec-map.json's format is flat (schema.property only), so every one
  of these needs a recorded omission in the new
  nestedShapeOmissions ledger; unrecorded ones fail a map-validation
  vitest test. Recorded honest, unreviewed-but-tracked omissions for
  the 72 nested shapes that predate this check (tracking_*, blindpay_bank_details,
  owners, etc.) so new unmapped nested shapes fail loudly going
  forward instead of joining the pile silently.
- Verify .github/workflows/pipeline-alert.yml on main: event fields
  only via env:, jq --arg, quoted webhook URL, missing
  SLACK_WEBHOOK_URL exits non-zero. No changes needed.
- Harden .github/workflows/api-sync.yml's spec-fetch step into a
  delivery-manifest verification: fetch both
  .api-sync/spec-current.json and .api-sync/delivery.json from
  api-sync-data, fail loudly if either is missing, fail if
  sha256(spec-current.json) != delivery.json's spec_sha256, and fail
  with "stale delivery" if the repository_dispatch client_payload
  carries a non-empty spec_sha256 that disagrees with delivery.json's.
  A dispatch without the manifest (today's state, until v2 PR #1922
  ships it) fails loudly by design rather than syncing against an
  unverified spec.

No version bump: infra/tooling only, no SDK surface change.

Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
@BernardoSM

BernardoSM commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@ericviana
ericviana merged commit 80bc659 into main Aug 4, 2026
11 checks passed
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.

2 participants