feat: lock=ignore, none-as-footgun-guard, fix integrity story & bundle scope check#16
Merged
Merged
Conversation
…ty story
Three concerns from the high-level audit, addressed together because they
all touch the same code paths:
1. Drop the tautological hash check (lock=none + bundle=load)
The previous behaviour populated this.hashes from sha512(bundle source) and
then asserted that hash matched sha512(bundle source) at getFile time --
zero protection, while reading like an integrity check. The intent for
lock=none + bundle=load is that the bundle is the source of truth and the
hashes-in-lockfile would be redundant when source-in-bundle is right next
to them. Express that directly: stop populating hashes from the bundle,
and skip the hash assertion in getFile when !useLockfile. lock=frozen +
bundle=load still verifies (the lockfile's hashes vs the bundle's source).
2. Verify bundle scope on load (parallel to the lockfile check)
In frozen mode, the lockfile loader does assert.equal(json.config.scope,
this.config.scope). Mirror that for bundle loading -- a bundle saved with
scope=full loaded under scope=node_modules (or vice versa) is now rejected
in frozen mode with ERR_ASSERTION.
3. lock=none becomes a footgun guard; add lock=ignore as the explicit
opt-in to tolerate file presence
Today lock=none silently skips an existing stasis.lock.json, and
bundle=ignore behaves the same as bundle=none (both throw when the file is
present). Repurpose `none` as the implicit default that refuses to silently
ignore an existing lockfile/bundle, and add `ignore` as the explicit "I
know, leave it alone" mode for both lock and bundle:
- lock=none + stasis.lock.json on disk -> reject (NEW)
- lock=ignore + stasis.lock.json on disk -> tolerate, don't load,
don't write (NEW mode)
- bundle=none + stasis.code.br on disk -> reject (unchanged)
- bundle=ignore + stasis.code.br on disk -> tolerate (FIX -- used
to reject identically
to bundle=none)
useLockfile now also excludes `ignore` (parallel to bundle). bundle=load
now allows lock=ignore alongside lock=frozen / lock=none. CLI usage banner
adds `ignore` to --lock and updates the bundle=load error message.
=none is intentionally not in the help text: it's the implicit default,
and surfacing it would defeat the footgun guard.
Tests:
- Config: lock=ignore getters, bundle=load works with lock=ignore.
- CLI:
- lock=none rejects an existing lockfile (explicit and implicit-default).
- lock=ignore tolerates an existing lockfile, doesn't touch it.
- lock=ignore + bundle=load serves from bundle without lockfile.
- bundle=none rejects an existing bundle file.
- bundle=ignore tolerates and leaves it untouched (regression for the
previous "ignore behaves like none" bug).
- lock=frozen + bundle=load rejects a bundle with a mismatching scope.
- bundle=load + lock=replace is rejected (added an explicit test
alongside the existing lock=add one).
Docs updated, kept compact.
Per review: the bundle's structure does not vary by scope (sources/formats/ imports are unconditional), so a scope mismatch is a configuration error in any mode, not only under lock=frozen. Drop the frozen guard and assert scope equality whenever the bundle is loaded. Add a non-frozen regression test (lock=add + bundle=add) to lock the stricter behaviour.
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.
Three concerns from the high-level audit, addressed together because they all touch the constructor's per-dir block in
state.jsand theConfiggetters.1. Drop the tautological hash check (lock=none + bundle=load)
The previous code populated
this.hashesfromsha512(bundle source)andthen asserted that the same hash matched
sha512(source)ingetFile—zero protection, while reading like an integrity check.
The intent for
lock=none + bundle=loadis correct: the bundle is thesource of truth, and shipping hashes-of-the-same-sources in the same file
is redundant. Express that directly: stop populating hashes from the
bundle, and skip the hash assertion in
getFilewhen!useLockfile.lock=frozen + bundle=loadstill verifies (lockfile's hashes vs bundle'ssource).
2. Verify bundle scope on load
The lockfile loader does
if (frozen) assert.equal(json.config.scope, this.config.scope). Mirror that for bundle loading — a bundle saved withscope=fullloaded underscope=node_modules(or vice versa) is now rejected in frozen mode.3.
lock=ignoremode;nonebecomes a footgun guardToday
lock=nonesilently skips an existingstasis.lock.json, andbundle=ignorebehaves the same asbundle=none(both throw on file presence). Repurposenoneas the implicit default that refuses to silently ignore an existing lockfile/bundle, and addignoreas the explicit "I know, leave it alone" mode for both lock and bundle:lock=none(default)lock=ignore(NEW)bundle=none(default)bundle=ignorenone)useLockfilenow also excludesignore(parallel to bundle).bundle=loadnow allowslock=ignorealongsidelock=frozen/lock=none. CLI banner addsignoreto--lockand updates thebundle=loaderror message.=noneis intentionally not in the help — it's the implicit default, and surfacing it would defeat the footgun guard.Tests (104/104 passing)
lock=ignoregetters;bundle=loadworks withlock=ignore.lock=nonerejects an existing lockfile (explicit and implicit-default forms).lock=ignoretolerates an existing lockfile and doesn't touch it.lock=ignore + bundle=loadserves from the bundle without lockfile interaction.bundle=nonerejects an existing bundle file.bundle=ignoretolerates an existing bundle and leaves it untouched (regression for the previous "ignore behaves like none" bug).lock=frozen + bundle=loadrejects a bundle with a mismatching scope.--bundle=load --lock=replaceis rejected (added an explicit test alongside the existinglock=addone).Test plan
pnpm test— 104/104 passpnpm lint— cleanlock=frozen+bundle=loadblocks tampered bundle withERR_ASSERTION;lock=ignore+bundle=loadruns the tampered payload (intentional, documented);lock=nonewith an existing lockfile fails withUnexpected …/stasis.lock.json with config.lock = 'none'.Generated by Claude Code