Skip to content

Release-path guards: post-emit dangling-ref check (#202) + latest-blend gate (#203) - #207

Open
jagguji wants to merge 2 commits into
mainfrom
batch/202-203-release-guards
Open

Release-path guards: post-emit dangling-ref check (#202) + latest-blend gate (#203)#207
jagguji wants to merge 2 commits into
mainfrom
batch/202-203-release-guards

Conversation

@jagguji

@jagguji jagguji commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Batches the two release-path safety nets from the #198 post-mortem. Neither changes generated output — they only catch bad output before it ships. Closes #202, closes #203.

#202 — post-emit dangling-reference guard

The reachability sweep's root/child allowlist fell behind the emitter four times (#191, #195, #197 ×2), each shipping output where a Module.type / JsFn.t reference pointed at a declaration the sweep dropped — a ReScript compile error caught only by manual review or downstream validation. Each fix widened the allowlist; the next change outran it again.

src/validate.mjs adds an allowlist-independent post-emit check: after every .res is written, assert each reference into one of our own file-modules resolves to a real declaration (plus bare JsFn.t requires a written JsFn.res). It never compiles anything — it's a fast, offline structural mirror of "does this compile?" for the one failure mode that recurs.

  • Hard-fails the golden suite (controlled output must be clean); warns non-fatally at generation time for real packages (a text-parse edge case can never break a user's build).
  • Held to the issue's acceptance bar empirically: 0 false positives across all 132 golden + benchmark baseline trees (1769 .res files), and it trips on synthetic dangling refs (test/dangling-refs.mjs, 11 cases).
  • stripNoise is a single-pass tokenizer (strings / line / nestable block comments interact correctly; \ escapes respected); collectDeclaredNames is deliberately over-collecting (can only miss a real dangling ref, never invent a false positive).

#203 — latest-blend pre-release gate

The benchmark pins specific blend versions, but blend is the primary downstream target and moves faster than the pins. Twice a regression compiled on the pins yet broke on a newer real blend (#110, and #198's crash-before-emit — invisible to compile gates because there's no output to compile).

npm run bench:latest-blend (benchmark/run.mjs --latest-blend) resolves the live @juspay/blend-design-system@beta dist-tag, generates with this checkout, and fails on crash / empty output / compile break; informational diff vs the newest pinned baseline. Weekly + on-demand workflow (.github/workflows/latest-blend-gate.yml); run before cutting a release. Complements the permanent regression pin: the pin catches that case forever, this gate catches the next one.

  • Validated end-to-end: resolved 0.0.38-beta.1, generated 321 files, compiled clean, fails closed.

Review

Two adversarial subagent review rounds. Round 1 found 5 latent issues (no P1s; parser under-collection / comment-string ordering / semver sort) — all fixed in f6cb926 and pinned as regression tests. Round 2 re-reviewed the current HEAD (tokenizer attacked with 12 adversarial inputs, cmpVersion ordering table, gate-fails-closed): clean, no P1/P2.

Tests

  • npm test green (121 goldens + new test/dangling-refs.mjs).
  • FP sweep: 0 flags / 132 trees.
  • Latest-blend gate: PASS.

🤖 Generated with Claude Code

jagguji and others added 2 commits August 20, 2026 01:43
…-blend gate (#203)

Two allowlist-independent safety nets for the crash/dangling-ref class the #198
post-mortem surfaced. Neither changes generated output.

#202 — post-emit dangling-reference guard (src/validate.mjs):
- After emit, assert every `Module.type`/`JsFn.t` reference into one of our own
  file-modules resolves to a real declaration. Catches the failure that shipped
  four times (an emitter change outran the reachability sweep's roots, stranding a
  reference whose declaration was dropped -> ReScript compile error) WITHOUT compiling.
- Hard-fails the golden suite; warns (non-fatal) at generation time for real packages.
- Verified zero false positives across every golden + benchmark baseline; trips on a
  synthetic dangling ref (test/dangling-refs.mjs). Handles decorated/@unboxed/rec
  decls, nested modules, externals, and comment/string noise.

#203 — latest-blend pre-release gate (benchmark/run.mjs --latest-blend):
- Resolves the LIVE `@juspay/blend-design-system@beta` dist-tag (not a pin), generates
  with this checkout, fails on crash / empty output / compile break; informational diff
  vs the newest pinned baseline. Validated: resolved 0.0.38-beta.1, 321 files, compiles.
- Weekly + on-demand workflow; run before cutting a release. Catches "compiles on
  pinned, breaks on latest" (the exact #198 gap; a crash-before-emit is invisible to
  compile gates).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial review found five latent defects (no P1s; all zero-flag today, but
each would break CI on legitimate output under a plausible emitter change, or was
a correctness bug). All fixed + pinned as unit tests / verified:

#202 dangling-ref guard (src/validate.mjs):
- P2: first @unboxed/variant arm (no leading `|`) was not collected, so a
  cross-module `Mod.FirstArm` value ref would false-positive. Collect after `=` too.
- P2: stripNoise stripped line-comments before strings, so `//` inside a
  `@module("http://…")` string ate the line's declaration -> false positive.
  Rewrote stripNoise as a single left-to-right tokenizer (strings/line/block
  interact correctly; block comments nest; backslash escapes respected).
- P3: nested block comments no longer leak their tail (same tokenizer).
- Still 0 false positives across all 132 golden+baseline trees; still trips on a
  synthetic dangling ref. Three regression cases added to test/dangling-refs.mjs.

#203 latest-blend gate (benchmark/run.mjs):
- P3: rank() sorted a prerelease AFTER its release (backwards). Replaced with a
  correct cmpVersion (prerelease < release; numeric prerelease ids compare
  numerically). Only affected the informational baseline-diff target, not PASS/FAIL.
- P3: npm view parse took the blind last line; now takes the last line MATCHING a
  version (still fails closed). Gate re-verified: 0.0.38-beta.1, 321 files, compiles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jagguji
jagguji deployed to benchmark August 19, 2026 20:34 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

Benchmark: ✅ PASS

Package Compile Diff vs baseline usable review broken Verdict
@juspay/blend-design-system@0.0.36 identical 102 5 0 ✅ PASS
@juspay/blend-design-system@0.0.37-beta.8 identical 215 7 0 ✅ PASS
@juspay/blend-design-system@0.0.37 identical 219 7 0 ✅ PASS
@juspay/blend-design-system@0.0.38-beta.1 identical 226 9 0 ✅ PASS
react-day-picker@10.0.1 identical 19 7 0 ✅ PASS
react-tooltip@6.0.7 identical 1 0 0 ✅ PASS
react-markdown@10.1.0 identical 0 2 0 ✅ PASS
@smastrom/react-rating@1.5.0 identical 1 0 0 ✅ PASS
clsx@2.1.1 identical 0 0 0 ✅ PASS
hono@4.12.25 identical 0 0 0 ✅ PASS
@base-ui-components/react@1.0.0-rc.0 identical 174 21 0 ✅ PASS

@pkg-pr-new

pkg-pr-new Bot commented Aug 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@juspay/rescript-bindgen@207

commit: f6cb926

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant