Skip to content

fix(build): build-path conformance — stable generated-entry materialization, sealed self-contained invariant, ID-keyed inspection, typed Rslib composition - #79

Merged
ScriptedAlchemy merged 7 commits into
mainfrom
fix/build-path-conformance
Aug 31, 2026
Merged

fix(build): build-path conformance — stable generated-entry materialization, sealed self-contained invariant, ID-keyed inspection, typed Rslib composition#79
ScriptedAlchemy merged 7 commits into
mainfrom
fix/build-path-conformance

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Summary

Implements the build-path cluster of the 2026-08-31 Rspack/Rslib/Rsbuild API conformance audit (four read-only reports; the dev-server cluster is handled separately). The generated-executable build path now runs entirely on documented, stable bundler surfaces, and the self-contained-artifact invariant is sealed against hostile tools hatches.

What changed

  1. Stable generated-entry materializationbuildWithRslib writes generated wrapper entries and registry modules as real files under the already-reserved .agent-bundle-virtual/ directory for the duration of one build, then removes them before artifact listing/publication. This replaces rspack.experiments.VirtualModulesPlugin (experimental tier: "Rspack may change the APIs of experimental features in minor releases") and the undocumented real-file-overlay that served virtual content at the framework module's own path (entryAnchor). One path, no plugin fallback. Emitted bundles were verified byte-identical across output roots; the only content delta vs. the old path is one scope-hoisting identifier now derived from the stable generated-entry name instead of the framework's install-dependent bundle filename.
  2. Self-contained invariant sealed — the last-running invariant hook rejects statically visible externals (string/RegExp/object) naming reserved specifiers (agent-bundle/mcp-entry, agent-bundle/mcp-apps, generated registry names), and a post-build scan of every emitted bundle fails closed on residual reserved imports (catches function-form externals). Three regression tests cover the benign path, a hostile object hatch, and a hostile function hatch.
  3. ID-keyed inspectionassertExecutableConfig keys on the documented origin.environmentConfigs[lib.id] and matches Rspack configs by their name (which Rslib derives from lib.id; verified empirically) instead of assuming positional correspondence of bundlerConfigs.
  4. Typed composition + dual-engine truth — per-entry configs compose through Rslib's documented mergeRslibConfig (merged by id); invariant hooks are typed against each executing engine's own Rspack.Configuration and return the config; all four as never casts are gone, leaving one documented type seam between the public hatch types (@rsbuild/core 2.2.x) and Rslib's nested engine (2.1.x). The dual-engine reality is documented on AgentBundleToolsConfig and in docs/entry-conventions.md, steering hatch authors to the { rspack } utils argument instead of importing @rspack/core.
  5. Small corrections — direct @rspack/core dependency dropped per Rslib guidance (the packed declaration test now resolves @rspack/core through @rsbuild/core); exact-match ($) alias keys; bundleless-dts wording in core/types.ts + package-build.ts; the mcp run docs no longer claim programmatic builds load .env (they load none); two leaked Rsbuild build() results closed in tests; createRsbuild({ rsbuildConfig }) modernized to { config }.

Audit findings → disposition

Report Finding Classification Disposition
Rspack F1 experiments.VirtualModulesPlugin in the published build path HIGH / experimental tier Fixed — real-file materialization, plugin removed
Rspack F2 Virtual content shadowing the on-disk entryAnchor HIGH / wrong-but-lucky Fixed — generated entries are real files at non-colliding paths
Rspack F3 · Rslib Wave-5 Direct @rspack/core dependency contradicts Rslib guidance MED-HIGH / works-but-dated Fixed — dependency dropped; declaration-graph test resolves via @rsbuild/core
Rspack F4 · Rslib Wave-3 · Rsbuild Wave-0 Dual-engine hatch, as never composition, version-split type contract MED / undocumented-internal FixedmergeRslibConfig, engine-typed hooks, one documented seam, docs
Rspack F5 Prefix-matching agent-bundle/mcp-entry alias LOW / hardening Fixed — exact-match $ keys
Rspack F6 tools.rspack mutators return void LOW / works-but-dated Fixedreturn config
Rslib Wave-1 externals bypasses the self-contained invariant WRONG Fixed — static rejection + post-build residual-import scan + regression tests
Rslib Wave-2 · Rsbuild Wave-2 #5 Positional bundlerConfigs[index] assumption UNDOCUMENTED-INTERNAL Fixed — ID-keyed assertions
Rslib Wave-4 "Bundled declarations" wording (dts is bundleless) WRONG (wording) Fixed — comments + emitted-declaration JSDoc corrected
Rsbuild #2 Two unclosed build() results in tests WRONG Fixed — results retained and closed
Rsbuild #3 Docs claim programmatic builds load the mcp run .env set WRONG (docs) Fixed — launch-time-only wording
Rsbuild #6 createRsbuild({ rsbuildConfig }) alias WORKS-BUT-DATED Fixed{ config }
Rsbuild Wave-2 #7 provenance.ts Stats without all: false (perf) CORRECT-but-expensive Not in this cluster — unchanged
Rspack F4 (RSC example) · F7 · F8 Experimental-tier tracking / documented-correct usages / consumer state INFO No action required
Dev-server report All findings Owned by the sibling dev-server task

Audit provenance: four read-only conformance reports produced against main@12526a8b (Rspack, Rslib, Rsbuild, dev-server), resolved versions @rspack/core 2.2.1 / @rslib/core 0.23.2 (nested Rsbuild 2.1.13) / @rsbuild/core 2.2.1.

Gate results

Three full gate runs (initial implementation, post-review fixes, final). Final run on abb57676:

Gate Result
pnpm build
pnpm typecheck (3 projects) ✅ 0 errors
pnpm lint (661 files, 86 rules) ✅ 0/0
pnpm test:unit ✅ 1771 passed, 4 env-gated skips
Targeted integration (build / package-build / hooks / dev-package-build(+service) / public-api / runtime-inspector) ✅ all passed
RSC example dev-invocation.integration ✅ (two prior default-budget waits now aligned to the file's 15s convention after a load-correlated flake diagnosed off this code path)
pnpm test:packed (incl. scaffolder e2e + public-api-packed) ✅ 22 passed, 1 opt-in skip
Examples spot: audiobook-curator check, mcp-app check ✅ both clean

Byte-identity probe: the same fixture built into two output roots emits identical bundles before and after the change; .agent-bundle-virtual/ never survives a build.

Independent review (pre-merge)

An independent model review of the full diff raised 3 blockers and 3 should-fixes; all were verified and fixed in follow-up commits, and the re-review confirmed no blockers remain:

  1. pnpm symlink resolution in the packed declaration test → now realpaths @rsbuild/core before createRequire (the old form worked only under rstest's resolution hooks, not plain Node).
  2. Function-form externals could evade the text scan by remapping a reserved request → every function external is now wrapped at build time (both calling conventions, arity preserved); a positive externalization of a reserved request records the violation and the build fails with the actionable diagnostic.
  3. A consumer alias could shadow the framework's exact-match reserved alias by insertion order → alias keys capable of capturing a reserved specifier (exact, $-exact, or path prefix) are now rejected, with the framework's own keys exempted.
  4. Object externals with value false (explicit opt-out) no longer count as violations; cleanup now survives a rejecting close(); the residual scan is syntax-aware (es-module-lexer parse of the emitted ESM, shebang-stripped) so strings/comments mentioning a reserved specifier cannot fail a legitimate build.

Test plan

  • Regression tests: benign exact-match alias + materialized registry inlining; hostile object-externals hatch rejected statically; hostile function-externals hatch rejected by the post-build scan
  • Byte-identity probe: same fixture built into two output roots produces identical bundles; .agent-bundle-virtual/ never survives a build
  • Full gate: build, typecheck, lint, test:unit, targeted build/package-build/inspect integration files, test:packed (scaffolder e2e included), examples spot (audiobook-curator, mcp-app)

… rejection, ID-keyed inspection, typed Rslib composition

Conformance fixes from the Rspack/Rslib/Rsbuild audit (build-path cluster):
materialize generated wrapper/registry modules as real files under the
reserved .agent-bundle-virtual/ directory instead of the experimental
VirtualModulesPlugin and its real-file overlay of the module anchor; reject
tools-hatch externals that name reserved specifiers and scan emitted bundles
for residual reserved imports; key inspection assertions on the documented
lib.id instead of array order; compose per-entry configs with mergeRslibConfig
and engine-typed invariant hooks (no as-never casts); exact-match ($) alias
keys; drop the unused direct @rspack/core dependency; correct bundleless-dts
wording; close leaked build results in tests and retire the rsbuildConfig
alias.
Guard function-form externals at build time (recording violations so the
generic bundler failure surfaces as the actionable diagnostic), reject
consumer aliases that would shadow reserved specifiers ahead of the
framework's exact-match keys, treat object externals with value false as
opt-outs, keep materialized-module cleanup running even when close()
rejects, and realpath the pnpm symlink before resolving @rspack/core in the
packed declaration test. Adds regression coverage: remapped function
externals, alias shadowing, benign non-reserved externals, and a
residual-import scan probe.
…n dev-invocation generation waits

Replace the text-pattern residual-import scan with an es-module-lexer parse
of each emitted bundle (the emitted format is ESM by contract), so string
literals or comments mentioning a reserved specifier can no longer fail a
legitimate build; a bundle that does not parse as ESM fails closed. Also
give the three default-budget "generation two" waits in the RSC
dev-invocation suite the same 15s budget every sibling generation wait in
that file already uses.
@changeset-bot

changeset-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a59bf03

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T10:09:19.946791Z abb5767 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Aug 31, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@79
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@79
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/rsc-runtime@79

commit: a59bf03

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: abb57676c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/agent-bundle/src/build/rslib.ts
…erved specifier

The self-containment scan parses each emitted bundle as an ES module rather
than grepping it, so a reserved specifier that only appears as a string
literal is not a violation. Nothing exercised that distinction.
A consumer tools.rsbuild output.cleanDistPath override would delete the
build's own materialized generated sources and any sibling entry already
emitted into the shared staged root. The final merge fragment now pins
cleanDistPath off on both the Rslib executable path and the MCP Apps path
(the asyncChunks precedent: reserved knobs are pinned, not honored), and
the resolved-config assertions verify the pin. Adds a regression test
proving a hatch that asks to clean the root still gets live outputs.

Co-authored-by: ScriptedAlchemy <zackary.l.jackson@gmail.com>
@ScriptedAlchemy
ScriptedAlchemy merged commit a2fa7d0 into main Aug 31, 2026
9 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the fix/build-path-conformance branch August 31, 2026 10:54
ScriptedAlchemy added a commit that referenced this pull request Aug 31, 2026
… at dedicated virtual paths (#80)

* refactor(build): serve generated entries through VirtualModulesPlugin at dedicated virtual paths

Architecture reversal of the generated-source transport from PR #79's
on-disk materialization, preserving all its conformance invariants:
generated wrappers and registry modules keep their deterministic paths
under the reserved .agent-bundle-virtual/ namespace but are now
guaranteed-nonexistent module ids served from memory. Rslib validates
source.entry existence on the real filesystem, so the profile keys each
entry on the authored program and the invariant hook redirects the
lowered Rspack entry to the virtual wrapper path — no real path is ever
shadowed (the old entryAnchor overlay stays dead). A narrow feature
check turns an upstream plugin removal into an actionable diagnostic.

* test(build): prove invariant-hook ordering and fail-closed inspection; truthful changeset headline

Sol review should-fixes: a hostile tools hatch that strips plugins and
repoints the entry is overridden by the framework hook (merged last);
resolved environments missing the virtual-module plugin or the wrapper
entry fail closed before the build; the conformance changeset headline
no longer claims fully documented surfaces now that the virtual-module
transport deliberately rides one experimental plugin behind a feature
check.

* refactor(build): simplify virtual-entry composition with shared narrowing

Reuse the core isRecord guard for entry-record and descriptor narrowing
(dropping two casts and the self-recursive entryImportsOf trick), inline
the one-shot plannedVirtualModules helper at its composition site, hoist
the per-entry registry-module computation in assertExecutableConfig, and
drop the patch changeset that described replacing disk materialization no
release ever shipped (the amended minor changeset already documents the
virtual transport).
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