fix(state): attribute backfilled stasis-core files and folded manifests in reason - #165
Merged
Merged
Conversation
…ts in `reason` Under `stasis run` + metro, the bundle's `reason` map left two classes of files consumer-less, so downstream tooling could not sort them into bundled app code vs bundler toolchain: no consumer recorded, so they can't be sorted into bundled vs bundler: node_modules/@exodus/stasis-core/package.json, node_modules/@exodus/stasis-core/src/brotli.js, ... - stasis-core's own src files. Node evaluates the preload machinery before registerHooks can observe it, so the write-time BFS backfill is their ONLY recorder -- and it recorded them with no attribution. They are run-loaded modules like any other; attribute them to 'run'. (This also keeps plain single-consumer runs emitting no `reason` field at all, which a separate 'stasis' consumer would have broken.) - every manifest includePackageJson folds in under --package-json (104 of 116 unattributed files on the repro). Nothing observed those reads, but each manifest rides along BECAUSE some consumer bundled its bucket -- so it inherits every consumer that recorded a file of the bucket, derived before the folds so one manifest can't count as another bucket's evidence. Verified against real Metro 0.87.0 (4 workers, --child-process, --package-json, deduped install so the plugin shares the loader's stasis-core copy, which is what makes the backfill the only recorder): 116 unattributed files before, 0 after, with a byte-identical attested set (reason is informational, never attested). The repro required the dedupe: with stasis-plugins carrying its own nested stasis-core copy, the plugin's imports load fresh post-registration and get captured live as 'run', hiding the hole the user hit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TMPW6hy7z3BTnJTJjS8k7k
ChALkeR
pushed a commit
that referenced
this pull request
Aug 10, 2026
…ts in `reason` (#165) Under `stasis run` + metro, the bundle's `reason` map left two classes of files consumer-less, so downstream tooling could not sort them into bundled app code vs bundler toolchain: no consumer recorded, so they can't be sorted into bundled vs bundler: node_modules/@exodus/stasis-core/package.json, node_modules/@exodus/stasis-core/src/brotli.js, ... - stasis-core's own src files. Node evaluates the preload machinery before registerHooks can observe it, so the write-time BFS backfill is their ONLY recorder -- and it recorded them with no attribution. They are run-loaded modules like any other; attribute them to 'run'. (This also keeps plain single-consumer runs emitting no `reason` field at all, which a separate 'stasis' consumer would have broken.) - every manifest includePackageJson folds in under --package-json (104 of 116 unattributed files on the repro). Nothing observed those reads, but each manifest rides along BECAUSE some consumer bundled its bucket -- so it inherits every consumer that recorded a file of the bucket, derived before the folds so one manifest can't count as another bucket's evidence. Verified against real Metro 0.87.0 (4 workers, --child-process, --package-json, deduped install so the plugin shares the loader's stasis-core copy, which is what makes the backfill the only recorder): 116 unattributed files before, 0 after, with a byte-identical attested set (reason is informational, never attested). The repro required the dedupe: with stasis-plugins carrying its own nested stasis-core copy, the plugin's imports load fresh post-registration and get captured live as 'run', hiding the hole the user hit. Claude-Session: https://claude.ai/code/session_01TMPW6hy7z3BTnJTJjS8k7k Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The symptom
Under
stasis run+ metro, the bundle'sreasonmap leaves files consumer-less, so downstream tooling can't sort them into bundled app code vs bundler toolchain:Reproduced against real Metro 0.87.0 (4 workers,
--child-process --package-json,withStasis): 116 unattributed files — exactly two classes, from the tworeason: nullsites instate.js.Class 1: stasis-core's own src files (the reported list)
Node evaluates the preload machinery (
hooks.js,state.js,util.js, …) beforeregisterHookscan observe it. When app code or a plugin later imports the same copy —withStasis→@exodus/stasis-core/statein any deduped install — the resolve hook records the edge, but the load hook never fires (Node's module cache short-circuits it), so the write-time BFS backfill is those files' only recorder. It recorded them withreason: null.They're run-loaded modules like any other — the backfill exists precisely because the hooks were blind at the moment the run loaded them — so they're now attributed to
run, the consumer that already means "loaded by the instrumented process". A separatestasisconsumer was considered and rejected: it would make every plainstasis runbundle two-consumer, emitting areasonfield where today there is none.Reproducing this needed one repro correction that explains why it wasn't caught earlier: with a nested stasis-core copy under stasis-plugins (what a naive vendoring produces), the plugin's imports load fresh after hook registration and get captured live as
run— the hole only opens when the plugin resolves to the same copy the loader runs from, which is what a real dedupednode_modulesdoes.Class 2: every
--package-jsonfolded manifest (104 of the 116)includePackageJsonfolds in manifests the run never read — that's its purpose — withreason: null. Nothing observed them, but each one rides along because some consumer bundled its bucket. Each folded manifest now inherits every consumer that recorded a file of its bucket: a metro-only dep's manifest is metro's, a toolchain dep's is run's, a shared bucket's is both. Attribution is derived before the folds are added, so one fold's manifest can't count as another bucket's consumer evidence.Verification
On the repro: 116 unattributed → 0, with a byte-identical attested set (
reasonis informational and never attested — same file/format hashes before and after):node_modules/@exodus/stasis-core/src/state.jsrunnode_modules/@exodus/stasis-core/package.jsonrunnode_modules/chalk/package.json(toolchain dep)runmetroTests:
preload-self-import.test.js: a spawned capture with a second consumer asserts every backfilled stasis-core file lands underrun(single-consumer bundles omitreasonentirely, so the second consumer is what makes the map observable).state.test.js: manifests inherit their bucket's consumers — metro-only bucket → metro alone, dual-consumer bucket → both, workspace manifest → the workspace's consumer, and never a consumer that recorded nothing of the bucket.Full suite: 1765 pass, same 12 pre-existing Node-22 failures as
main.Generated by Claude Code