fix(build): keep the checkout path out of artifact bytes - #835
Conversation
🦋 Changeset detectedLatest commit: aef1975 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ef5ecac934
ℹ️ 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".
8ff772f to
34636aa
Compare
Every generated-module emitter names project modules through generatedModuleSpecifier, which returns a project-relative POSIX specifier. NormalizedPlugin carries projectRoot so hook wrappers and entry shells receive it; model identity excludes it. canonicalizeNormalizedModel now canonicalizes generated route, event handler, web, and state paths. Fixes #833
34636aa to
7c9722c
Compare
…lizedPlugin.projectRoot breaking
…ports in build fixtures
Fixes #833
Why
The same commit built from two checkout paths emits different artifact bytes. Consumers that commit their artifact, such as cargo-hauler, get a dirty tree whenever they build outside the original directory.
Two leaks cause this. Generated wrappers imported project modules by absolute path. Rspack names each import binding after the request, so the root appeared in identifiers such as
_tmp_ab_a_cargo_hauler_src_scripts_hauler_ts__rspack_import_1.canonicalizeNormalizedModelalso skipped several model paths, somodelDigestchanged with the root.Every generator now emits a project-relative POSIX specifier from one helper. No later step rewrites generated source, so
inspect --bundlershows exactly what Rspack compiles.Scope
generatedModuleSpecifier(projectRoot, source)inbuild/meta.tsturns a project path into../<posix path>. Generated modules always live one directory below the root, in.agent-bundle-virtual. Bare specifiers and paths outside the project pass through.NormalizedPlugin.projectRootis set bynormalizeProject.canonicalizeNormalizedModelexcludes it from model identity.entry-shell.tstakeprojectRootin their options. The hook wrappers inhook-contract.tsandamp.tsreadTargetHookWrapper.projectRoot, whichplanHookssets from the model.planHooksSurfaceandplanMcpEntriesSurfacetakeprojectRoot, andeventHandlerStateSourceandproviderRegistrySourcetake it as their first argument.composeEntryLibConfig(build/rslib.ts) is deleted.canonicalizeNormalizedModelcanonicalizesmcpServers[].generatedRoutes,packageBuild.bins[].generatedCliroutes andprojectionSources,hooks[].eventRoute.handler,web.provenance, andstate.build-reproducibility.test.tsbuilds the fixture from two checkout paths.generated-module-specifier.test.tspins literal POSIX andpath.win32cases. Test fixtures gainprojectRootthrough a tsc-driven codemod, and assertions that pinned absolute import strings now pin the relative specifier.Blast Radius
Every generated executable changes bytes once, because import binding names change. Imports resolve to the same modules. The missing-
mainerror text now names../src/....modelDigestchanges once for projects that use the affected fields. Hand-builtNormalizedPluginvalues must now carryprojectRoot.Verification
main(11 differing files on64492ddf43per the review, 12 onb0b131bbin my run) and passes on this head.generated-module-specifier.test.tspasses. Its Windows case turnsC:\repo\src\events\tool\before.tsinto../src/events/tool/before.ts.4bde6a6was built from/tmp/ab-a/cargo-haulerand/tmp/ab-bb/other-namewith this change applied to64492ddf43. That base predates refactor: remove legacy API compatibility paths #810, which makes cargo-haulermasterfailAB4810on currentmainindependent of this PR. The twoartifact/anddist/trees are identical, and no/tmppath appears.pnpm checkon head7c9722cpasses. Unit ran 310 files with 4477 passed, route-unit 91 passed, projection 197 passed, integration 104 files with 1183 passed, then lint and typecheck. The head's base is18a913e9d8.mainhas since gained chore(deps): move zod to 4.6.4 and raise the runtime zod peer floor to ^4.6.4 #842 and refactor(workbench): delete the unmounted ext-apps runtime App bridge #845.Principles
rslib.tsis deleted rather than kept beside them. Every caller and test fixture migrated in this PR, and nothing falls back to the absolute form.Review
Verdict: PASS+NOTES. Independent review of head
34636aa41ba7715602919b2274de326ade49e82a. The stable patch-id ofgit diff origin/main...34636aaisa2cea75b99ba108726c9c0241f4486a86d0bac60.Base vs head.
tests/build-reproducibility.test.tsfrom head fails on base64492ddf43with 11 differing files, and passes on head (measured).e9f7f2abc3into/tmp/rev835-a/ccand/tmp/rev835-zz/deeper/nest/cargo-hauler-other. These paths differ in name and depth. Each clone installed the packedagent-bundleserved over HTTP. On base,diff -rq artifact/lists 14 differing files. On head it lists 0, and no checkout path appears in the head artifact (measured).file:makes pnpm name the store directory by its relative path (file+..+..vsfile+..+..+..+..). That puts depth into module ids even on head. This comes from the install method, not the PR (measured).Gate. I merged
origin/maininto head locally (not pushed), then ranpnpm check. It exited 0: build, unit 4487, route-unit 91, projection 197, integration 1183, lint, and typecheck (measured).Findings.
packages/agent-bundle/src/build/rslib.ts:181rewrites the generated source as text after generation, rather than having the generators emit relative specifiers. Generators still produce absolute specifiers.inspect-bundler.ts:355reports the unrewrittengeneratedEntry, soinspectshows different source from what was compiled. Per Outcome-Oriented Execution this is a post-hoc shim with two representations. It is not a correctness bug (inferred). A follow-up could emit project-relative specifiers inentry-shell.tsandhook-contract.ts.rslib.ts:184posix-normalises only the prefix. The specifier becomes"../src\\events\\tool\\before.ts", with mixed separators. I measured this withpath.win32on a copy of the function. Builds should still resolve and still reproduce within Windows, but bytes will differ between Windows and POSIX builds (inferred, untested on Windows).rslib.ts:181rewrites every double-quoted string under the root, not only imports. For example, the error message atentry-shell.ts:149is rewritten too. That is harmless and removes a leak (inferred). Sibling-prefix roots likerepo-otherare correctly left alone (measured).<root>/.agent-bundle-virtual, so the relative prefix is always... Specifiers outside the root stay absolute. That is the known ceiling named in the test comment, where dependency symlinks resolving outside the root stay depth-dependent (inferred).packages/agent-bundle/src/core/project-context.ts:441-453now runs route, handler, and view paths throughresolvedProjectPath, which throws aRangeErrorfor paths outside the root. A route source that is a symlink to outside the root would now failmodelDigest(inferred; route discovery probably rejects these earlier). The canonicalized fields covergeneratedRoutes, theeventRoute.handler, andgeneratedCliroutes andprojectionSources, plusstateandwebprovenance. The head manifests match end to end (measured).Review (delta)
Verdict: PASS. Independent re-review of head
7c9722c7acd3e8fc771b7e32661cf0763b98165b. The stable patch-id ofgit diff origin/main...7c9722cisea9e44226716fdb406586acdd5b54c958748e1c8. The earlier findings 1 and 2 are resolved.Old path removed.
checkoutIndependentSourceis gone, andrslib.tshas no diff againstmain. Every artifact import generator goes throughgeneratedModuleSpecifier:entry-shell.ts,hook-contract.ts, andamp.ts. Only the test-time generators inrstest/setup-module.tsandtest/script.tskeep their own specifiers, and those never reach artifact bytes (measured by grep). Every generator output is compiled as a virtual module at<root>/.agent-bundle-virtual/*.mjs, so the fixed../prefix holds for every caller (inferred from the callers).Inspect equals compiled. I built a scratch copy of
distthat dumps each source handed to the virtual-modules plugin. Foraudiobook-curator,hooks-and-scripts, andmcp-app, thegeneratedEntrysets frominspect --bundler --jsonequal the compiled-entry.mjssources byte for byte, covering 4, 4, and 6 entries. None of the 84 virtual modules contains the project root (measured).Tests.
generated-module-specifier.test.tsandinspect-bundler.test.tspass, 8 tests in total. That includes theC:\repoWindows case (measured).build-reproducibility.test.tspasses on head. It fails on merge base18a913ewith 11 differing files (measured).cargo-hauler end to end, fixer's approach. The merge base
18a913eis #810 itself, and cargo-haulermaster27ef3cdfailsAB4810against it (measured). So I cherry-picked261aaa8and7c9722conto64492ddf43without conflicts, and compared against plain64492ddf43. Each build ran in/tmp/rev835b-a/ccand/tmp/rev835b-zz/deeper/nest/cargo-hauler-other, with the tarball installed over HTTP.artifact/files and base gives 14.Gate. I merged
origin/mainff7421b(#842, #845) into head locally, not pushed, and ranpnpm check. It exited 0: unit 4434, route-unit 91, projection 197, integration 1170, then lint and typecheck (measured).Note, non-blocking.
NormalizedPlugin.projectRootshows up in the publicinspectresultmodelas an absolute path. It is output-only, and publicbuilddoes not accept a caller-built model, so nothing breaks (inferred fromapi.ts).Local merge gate (head aef1975, contains origin/main 2a129e9)
pnpm build: passpnpm typecheck: passpnpm lint: passpnpm test:unit: pass (4433 passed)pnpm test:route-unit,pnpm test:projection: pass (on the earlier base)pnpm test:integration:run(whole pool, on main 3b667c8): 1154 passed. Three failures came from concurrent runs rewritingdist/example fixtures (host-install-proof,route-invocation-dev-server,examples-realSkills Starter). All three pass alone (25/25).host-install-proof+build-reproducibilityintegration pass (14/14)pnpm test:packed: pass (16 files, 46 passed, 1 skipped), includingpacked-consumer;packed-deleted-sourcepassed in the integration poolpnpm docs:site:build: pass (0 broken links, language parity OK)Review (change-risk-reviewer): no material risks. Low-severity items fixed: changeset wording, docs scoped to modules inside the project root, the executable-entry error names a project-relative path, and build/compiler-evidence/generated-module-evidence/hooks fixtures use the real temp root so they exercise
../specifiers.