Skip to content

Compiler evidence: prove self-containment from the Rspack module graph and Artifact IR; delete the generated-JS load scanners (replaces #591/#602) #619

Description

@ScriptedAlchemy

Replaces #591 and PR #602 (closed unmerged, owner decision). Shared-compiler-policy step of #592; #604 (Artifact IR / manifest v2) records the compile provenance this issue produces; #555 is the composite root the compiler serves.

Why (owner rationale, 2026-09-05)

The approach in #591/#602 was the wrong layer. A meta-framework must not reverse-engineer emitted JavaScript to discover what its own bundler did. Rspack already knows module identity, resolved resource, external modules, dependency type, issuer, chunk membership, and runtime requirements. A bespoke scanner over emitted output (require(…), createRequire(…), bound loaders, aliases, templates, regex literals, shadowing, minified forms) is a second JavaScript parser maintained in parallel with Rspack and Node syntax, and it is fragile by construction.

Dependency-direction lesson from #602. The scanner flagged Agent Bundle's own createRequire(…).resolve(…) in src/core/dependency-manifest.ts; the runtime was rewritten to an ancestor node_modules walk to satisfy the scanner; Yarn PnP broke (locateFrameworkCliframework-not-installed). A validator implementation detail must never force runtime architecture. The direction is: runtime requirements → the compiler resolves them → the validator checks the compiler's evidence.

Correct flow

source
  → Agent Bundle compiler service
  → Rspack compilation / module graph
  → external / module resolution report
  → Artifact IR
  → self-containment validation
  → emit artifact

Not: generated JS → reparse → infer → AB6005.

Compiler API

compiler.compile({ entry, runtime: 'node', selfContained: true })
   CompileResult { assets: AssetIR[]; modules: ModuleIR[]; externals: ExternalIR[]; diagnostics }

selfContained: true means any runtime dependency not explicitly permitted by the artifact contract is a compiler error. Enforcement is one loop over compiler evidence:

for (const external of compilation.externals) {
  if (!isAllowedBuiltin(external.request)) diagnostic('AB6005', );
}

Deterministic, and indifferent to whether Rspack emits require(...), __rspack_createRequire_require(...), or any other shim: the external is known as an external, not inferred from its emitted spelling.

Closed-world by default

  • bundleDependencies: 'all', allowedExternals: ['node-builtins'].
  • tools.rspack.externals: ['left-pad'] is rejected by the configuration layer before compilation, or the compilation result shows the external and fails. Either way, no scanner.
  • The AGENTS.md "Generated plugin output" rule (no autoExternal, the framework never adds externals, the author's tools hatch cannot externalize a dependency on the author's behalf) is unchanged; the compiler policy is what enforces it, at configuration time and again on the compilation result.

Evidence adapter

A tiny Rspack plugin — ArtifactDependencyAuditPlugin — taps thisCompilation, captures external modules and unresolved runtime loads, and attaches structured evidence to CompileResult. Alternatives, in preference order if the plugin cannot see something: compilation modules / external-module metadata (ExternalModule.request, externalType, issuer via module reasons), then stats module reasons.

Opaque content

Content Agent Bundle did not compile — assets/, prebuilt native executables, verbatim-copied JavaScript, external command integrations — is either fully self-contained by contract or a declared opaque dependency:

definePrebuilt({ path, runtimeDependencies })
   Artifact IR { kind: 'prebuilt', path, dependencies }

Never "parse arbitrary JavaScript harder".

AB7014 (unused dependency) from evidence

Settled (owner decision, 2026-09-05 16:20 UTC, option A): bundled ≠ used. A package.json dependencies/optionalDependencies/peerDependencies entry counts as used only when it must exist at consumer run time: (1) a definePrebuilt runtimeDependencies declaration, (2) a shipped .d.ts reference read with TypeScript's preProcessFile, (3) the install-script grammar (package.json scripts, bin commands, files, preloads), or (4) the framework's process-dependency record — which is empty today: no runtime module bundled into a generated executable resolves or spawns a package. A package the compiler merely inlined stays AB7014; the graph evidence (ModuleIR.package) upgrades the message to name the bundle that inlined it. No regex or token scanner over packed files.

Invariant

For every compiler-owned executable, all runtime code is one of:

  1. a bundled module,
  2. a Node built-in,
  3. an artifact-relative emitted module,
  4. an explicitly declared opaque/prebuilt dependency.

Anything else is a build error, proven before artifact emission. Artifact validation is then defense in depth only: files exist, hashes match, paths are inside the root, manifests reference declared executables — no JavaScript syntax understanding at all.

Implementation order

  1. Central compiler service.
  2. Rspack compilation evidence adapter (ArtifactDependencyAuditPlugin).
  3. External dependency policy (closed-world configuration layer + compilation-result check).
  4. Artifact IR records compile provenance (with feat(manifest): authoritative agent-bundle.manifest.json v2 — consumers read the composite root through the manifest (#592 step 3, #555 W2/S3) #604).
  5. AB6005 evaluates the external / module graph evidence.
  6. Delete the generated-JS load scanners (inventory below), and rewrite the docs that describe them.

PR-sized steps: 1–3, then 4–5, then 6 (which also carries the AGENTS.md, docs/diagnostics.md, and website en+zh validation / entry-conventions updates). One minor changeset per PR.

Reordered by the owner audit of 2026-09-05 17:11 UTC (see comments): step 4 lands first as a persisted compile evidence record (agent-bundle.compile-evidence.json, #638); step 6 deletes a scanner only where compiler evidence or an explicit declaration replaces it, per the coverage matrix posted below — everything else stays, gated so it skips what the record proves.

Behavioral tests to preserve from #602

Each of these must hold on compiler evidence, with a fixture that actually builds:

  • An externalized package must fail: tools.rspack.externals: ['left-pad'] (import form) and externalsType: 'node-commonjs' (the createRequire shim form) both fail AB6005 in a host pack and in a package build's dist.
  • A Node built-in must pass, with and without the node: prefix, imported or required.
  • A relative emitted module must pass: an artifact-relative .js/.mjs reached from an entry is fine; a relative target that is missing, outside the root, or not in the manifest fails.
  • An opaque prebuilt remains opaque: a prebuilt payload module that loads a bare package neither fails AB6005 nor loses the dependency under AB7014; the dependency is kept by the prebuilt declaration.
  • tools.rspack cannot escape the bundling policy: externals, externalsType, autoExternal, and alias tricks on reserved specifiers are rejected at configuration time or on the compilation result.
  • Framework-generated modules (entry-shell.ts bins and MCP entries, install bins, hook wrappers, the composite root's bin/ and mcp/ entries, agent-bundle/meta, agent-bundle/mcp-apps) compile with zero externals other than built-ins — the audit AB6005: also refuse bare createRequire/require/import.meta.resolve loads in compiled modules #591 asked for, now as a compile-evidence assertion instead of a scan of rendered source.

The salvaged fixtures and test files from #602 are in the branch history of feat/591-ab6005-module-loads (commit 9ab94d19d) and were extracted with a README to /tmp/602-salvage/ on the owner's machine; the useful ones are the package-build.test.ts externals fixtures (node-commonjs shim, createRequire literal/computed), the prepack.test.ts prebuilt-payload fixture, and the generated-code audit list in generated-module-loads.test.ts (the list of generators, not the scan).

Scanners to delete (inventory, current main)

Scanner Where Replaced by
Emitted-JS import walk for AB6005 (host packs) src/build/validate-artifact-modules.tsvalidateJavaScriptModules, resolveJavaScriptImport; called from src/build/validate-artifact.ts (validateArtifact, ~L612) CompileResult.externals/modules checked by the external dependency policy (step 3/5); the relative-target file checks stay as the manifest/file-table check (defense in depth, no JS parsing)
#588 dist walk for AB6005 (package builds) src/build/package-build.ts — the selfContainment block in buildPackageOutputs (~L383) calling validateJavaScriptModules over the staged dist The same compiler evidence for the package build's compilations (dist/bin/*, Flight workers, lib)
Residual reserved-import lex src/build/rslib.tsassertNoResidualReservedImports (readModuleImports over emitted bundles) Reserved specifiers appear in externals evidence or not at all; the config-layer rejection (reservedExternalsViolation, guardReservedExternals) stays
AB7014 import/require token scanning over packed files src/build/pack-dependencies.tsmoduleLoads, javaScriptEvidence, fileEvidence, literalLoad, computedLoad, loaderReference, loaderBinding, factoryNames, loaderNames, codeOnly, decodeLiteral, declarationSpecifiers, importedPackageNames; consumed by src/build/pack-inventory.ts dependencyDiagnostics Dependency usage from CompileResult.modules (which packages the graph references) plus prebuilt declarations (definePrebuilt({ runtimeDependencies })); install-script and bin command evidence stays declarative (package.json), not scanned from JS
ES-module lexer cache src/build/module-imports.tsreadModuleImports, bundleSyntaxCheckFor (es-module-lexer + acorn parse) No consumer once the three walks above go; routes/syntax.ts (TypeScript AST over source, not emitted output) is unaffected
#602's leaf scanner (never merged) src/build/module-loads.tsscanModuleLoads Nothing; do not land

AGENTS.md

"Generated plugin output" currently says: "Proof is bytes and processes, not config: every artifact build walks the compiled host-pack modules (AB6005 fails a bare package specifier there …)". When the scanners go (step 6), that sentence must be rewritten to "proof is compiler evidence + packed-process tests": the compilation's external/module report is the proof of self-containment, and the packed pool (pnpm test:packed, packed-deleted-source) remains the process-level proof. The rest of the section — autoExternal: false, bundle: true, splitChunks: false, no framework externals, MCP App views inlined — is unchanged and is the policy the compiler enforces.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmeta-frameworkAgent Bundle compiler-coupled meta-framework

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions