Skip to content

fix(state): attribute backfilled stasis-core files and folded manifests in reason - #165

Merged
ChALkeR merged 1 commit into
mainfrom
claude/attribute-derived-captures
Aug 10, 2026
Merged

fix(state): attribute backfilled stasis-core files and folded manifests in reason#165
ChALkeR merged 1 commit into
mainfrom
claude/attribute-derived-captures

Conversation

@exo-nikita

Copy link
Copy Markdown
Collaborator

The symptom

Under stasis run + metro, the bundle's reason map leaves files consumer-less, so downstream tooling can't 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,
node_modules/@exodus/stasis-core/src/bundle-util.js, node_modules/@exodus/stasis-core/src/bundle.js, …

Reproduced against real Metro 0.87.0 (4 workers, --child-process --package-json, withStasis): 116 unattributed files — exactly two classes, from the two reason: null sites in state.js.

Class 1: stasis-core's own src files (the reported list)

Node evaluates the preload machinery (hooks.js, state.js, util.js, …) before registerHooks can observe it. When app code or a plugin later imports the same copy — withStasis@exodus/stasis-core/state in 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 with reason: 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 separate stasis consumer was considered and rejected: it would make every plain stasis run bundle two-consumer, emitting a reason field 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 deduped node_modules does.

Class 2: every --package-json folded manifest (104 of the 116)

includePackageJson folds in manifests the run never read — that's its purpose — with reason: 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 (reason is informational and never attested — same file/format hashes before and after):

file consumer
node_modules/@exodus/stasis-core/src/state.js run
node_modules/@exodus/stasis-core/package.json run
node_modules/chalk/package.json (toolchain dep) run
metro-graph dep manifests metro

Tests:

  • preload-self-import.test.js: a spawned capture with a second consumer asserts every backfilled stasis-core file lands under run (single-consumer bundles omit reason entirely, 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

…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
ChALkeR merged commit a76eb45 into main Aug 10, 2026
5 checks passed
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>
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.

3 participants