Skip to content

build: commit bun.lock and fix the lockfile ignore rules - #37

Merged
andrei-hasna merged 1 commit into
mainfrom
build/hc-lockfile-policy
Jul 27, 2026
Merged

build: commit bun.lock and fix the lockfile ignore rules#37
andrei-hasna merged 1 commit into
mainfrom
build/hc-lockfile-policy

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Closes two filed defects that share one line of .gitignore, so they cannot be separated
into two PRs without conflicting on it: HC no-committed-lockfile (1be7ee90) and
HC stray-pnpm-lock (621ce5a8).

The defect

.gitignore:2 was *.lock. It reads like a lockfile rule and gets both halves wrong.

It swallowed Bun's own lockfile. Measured at base 64d05ae:

git ls-files | grep -i lock          -> tests/store-lock.test.ts   (only hit)
git check-ignore -v bun.lock         -> .gitignore:2:*.lock

So there was no committed lockfile, and bun install --frozen-lockfile exited 0 reporting
"no changes" while pinning nothing.

That is not hygiene in this repo. @hasna/events is imported at src/cli.ts:17 and is
not in the build's --external list, so it is bundled into bin/knowledge.js, and
it was declared ^0.1.3. The bytes of the shipped bundle therefore depended on whatever the
tree happened to resolve:

@hasna/events rebuilt bytes after whitespace strip matches committed bundle
0.1.14 1045357 1045325 (sha256 0630038c…) yes
0.1.13 1040517 1040485 (sha256 4e3515ab…) no

Both are reachable on one machine: worktrees/knowledge-src has 0.1.13 installed while a
fresh bun install takes 0.1.14, and the package moved 0.1.11 → 0.1.14 inside a single
working session. Anyone running the rebuild-and-compare check the CHANGELOG recommends
against a differently-resolved tree gets a false "bundle out of sync" alarm — that has
already happened and invalidated a measurement.

It missed pnpm-lock.yaml and package-lock.json. Neither name ends in .lock, so
both were untracked and unignored — one git add -A from being committed. Already
materialised: 36b2099 "chore: preserve local drift (regenerated bins, pnpm-lock) before
serve build" added pnpm-lock.yaml, +1462 lines. A pnpm or npm install also does not honour
Bun's package release-age quarantine, so that path is a supply-chain hole and not untidiness.

The fix

  • !bun.lock, not deleting *.lock. The blanket rule has an unrelated real job:
    src/store.ts:235 writes <storePath>.lock beside any JSON store, so
    knowledge --store ./db.json in a checkout leaves ./db.json.lock behind. Removing the
    rule would start tracking those.
  • Foreign lockfiles named individuallypnpm-lock.yaml, package-lock.json,
    npm-shrinkwrap.json, bun.lockb — rather than matched by pattern, so adopting a new
    package manager has to be a deliberate edit to this file. bun.lockb is in that list on
    purpose: it is Bun's own legacy binary lockfile, and tracking it beside the text
    lockfile would mean two lockfiles disagreeing with no diffable record of which won.
    yarn.lock needs no new line; *.lock already covers it.
  • bun.lock committed, pinning @hasna/events to 0.1.14 with its integrity hash.
  • tests/lockfile.test.ts enforces it, because a hook cannot.

Resulting ignore state, measured:

bun.lock             NOT ignored          <- was .gitignore:2:*.lock
bun.lockb            IGNORED .gitignore:30
pnpm-lock.yaml       IGNORED .gitignore:27
package-lock.json    IGNORED .gitignore:28
npm-shrinkwrap.json  IGNORED .gitignore:29
yarn.lock            IGNORED .gitignore:2:*.lock
db.json.lock         IGNORED .gitignore:2:*.lock   <- the store lock still covered

Why a test and not a pre-commit hook

core.hooksPath is set globally on this fleet
(/home/hasna/.config/hasna/git-hooks/no-cursor-coauthor), so a repo-local
.git/hooks/pre-commit is not the durable control — and that hook's own header says
plainly that any client-side hook is advisory to whoever controls the git invocation. A test
runs in CI on every PR. tests/lockfile.test.ts asserts:

  • bun.lock exists, is not ignored, and is tracked
  • every foreign lockfile is ignored and untracked
  • every dependency and devDependency in package.json resolves in bun.lock to an exact
    version and carries a sha512- integrity hash (a name+version without bytes is not a
    pin for supply-chain purposes)
  • the installed @hasna/events equals what bun.lock names — the one assertion that
    would have caught the 0.1.13/0.1.14 accident

It also carries positive controls for its own lockfile reader, because a reader that matched
nothing would make every assertion above pass while measuring nothing: absent package,
prefix collision (@hasna/event must not match @hasna/events), a range line vs a real
entry, and the exact-version regex rejecting ^0.1.3. git unavailability fails the suite
rather than passing it.

Verification

Mutation-tested — each planted defect reverted immediately, tree clean after
(git status --porcelain empty). Unpiped exit codes:

M0 baseline                              rc=0   6 pass  0 fail
M1 drop `!bun.lock`                      rc=1   5 pass  1 fail
M2 drop pnpm-lock.yaml from ignores      rc=1   5 pass  1 fail
M3 pin @hasna/events to a range          rc=1   4 pass  2 fail
M4 duplicate the @hasna/events entry     rc=1   4 pass  2 fail
M5 installed 0.1.13 vs pinned 0.1.14     rc=1   5 pass  1 fail
M6 restore                               rc=0   6 pass  0 fail

The test was also red before the commit that fixes itisTracked('bun.lock') returned
false at that point, which is what the change makes true.

Full suite, env -u HASNA_KNOWLEDGE_API_URL -u HASNA_KNOWLEDGE_API_KEY bun test --timeout 60000:

rc=1   261 pass / 2 skip / 1 fail / 2527 expect() calls, 264 tests across 38 files
sole failure: knowledge cli > context pack and proposal context commands return bounded agent JSON

That failure is pre-existing and unrelated — it is the same sole failure recorded on base
64d05ae and on PR #36, and it is why CI is red on main too (run 30299169003).

Measurement trap, worth more than this PR. On this box HASNA_KNOWLEDGE_API_URL and
HASNA_KNOWLEDGE_API_KEY are exported in a plain shell, which flips the CLI into cloud mode
and makes the local sqlite catalog unavailable. bun test then reports 99 failures
(64 carrying the identical "cloud API flip is active" error) instead of 1. Anyone measuring
this repo must env -u both or they are measuring nothing.

Explicitly not done here

  • The pnpm cascade's root cause is not fixed here and should not be — it is a global
    core.hooksPath concern owned by task df74b2d2, and it is an owner decision. What this PR
    fixes is what this repo can fix: a stray pnpm-lock.yaml can no longer be staged by
    accident. Related measurement: committing on this branch produced no pnpm-lock.yaml
    (checked immediately after git commit), and the current global pre-commit states it
    deliberately does not dispatch to install-on-demand binaries — so the cascade appears to
    have been changed since the defect was filed. Not confirmed by me; treat as an observation.
  • 36b2099's pnpm-lock.yaml is left alone. It exists only on
    origin/wave-preserve/knowledge-20260706-serve (git merge-base --is-ancestor 36b2099 origin/main → exit 1) and pnpm-lock.yaml is not tracked on main. That branch exists to
    preserve local drift; rewriting it would defeat its purpose. It is not a landing candidate.
  • @hasna/events was NOT moved to --external. It would remove the dependency from the
    bundle-reproducibility surface, but it changes runtime resolution for every installed copy
    of the CLI, and the committed lockfile already makes the rebuild reproducible. That is a
    behaviour-adjacent change and belongs in its own PR with its own review.
  • No release-age exclusion registry change was needed: @hasna/events is already in
    ~/.bunfig.toml's minimumReleaseAgeExcludes, so pinning 0.1.14 does not bypass the
    7-day quarantine.
  • bun.lock is not published: package.json files is an allowlist and does not list it.

Tasks: 1be7ee90, 621ce5a8. Not merged by me — needs an independent adversarial review first.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Two defects in one line of .gitignore, both of which have already caused real
damage.

1. `*.lock` swallowed bun.lock, so the repo had no lockfile at all

`git ls-files | grep -i lock` returned only tests/store-lock.test.ts, and
`git check-ignore -v bun.lock` pointed at `.gitignore:2:*.lock`. So
`bun install --frozen-lockfile` exited 0 and reported "no changes" while pinning
nothing.

That is not hygiene here. `@hasna/events` is imported at src/cli.ts:17 and is NOT
in the build's `--external` list, so it is BUNDLED into bin/knowledge.js, and it
was declared `^0.1.3`. The bytes of the shipped bundle therefore depended on
whatever the tree happened to resolve. Measured on one box, same bun 1.3.14:
0.1.14 reproduces the committed bundle (1045325 bytes stripped, sha256
0630038c...), 0.1.13 does not (1040485). Both are reachable: the shared main
checkout on this machine has 0.1.13 installed while a fresh install takes 0.1.14,
and the package moved 0.1.11 -> 0.1.14 inside one working session. Anyone running
the documented rebuild-and-compare check against a differently-resolved tree gets
a false "bundle out of sync" alarm.

`*.lock` stays for a reason unrelated to package managers: src/store.ts:235
writes `<storePath>.lock` beside any JSON store, so `knowledge --store ./db.json`
in a checkout leaves ./db.json.lock behind. The fix is `!bun.lock`, not deleting
the rule.

2. `*.lock` missed pnpm-lock.yaml and package-lock.json

Neither name ends in `.lock`, so both were untracked AND unignored — one
`git add -A` from being committed. Already materialised: 36b2099 "chore: preserve
local drift (regenerated bins, pnpm-lock) before serve build" added pnpm-lock.yaml,
+1462 lines. A pnpm or npm install also does not honour Bun's package release-age
quarantine, so this is a supply-chain hole and not just untidiness. Foreign
lockfiles are now named individually rather than matched by pattern, so adopting a
new package manager has to be a deliberate edit. bun.lockb is in that list too: it
is Bun's own legacy binary lockfile, and tracking it next to the text lockfile
would mean two lockfiles disagreeing with no diffable record of which won.

Enforcement is a test, not a hook. core.hooksPath is set globally on this fleet, so
a repo-local .git/hooks/pre-commit is not the durable control, and a client-side
hook is advisory to whoever controls the git invocation in any case.
tests/lockfile.test.ts pins both halves — bun.lock tracked and unignored, every
foreign lockfile ignored and untracked, every declared dependency resolved to an
exact version with an integrity hash, and the installed @hasna/events matching what
the lockfile names. That last one is the assertion that would have caught the
0.1.13/0.1.14 accident.

The test carries positive controls for its own readers, because a lockfile check
that matches nothing would pass while measuring nothing: the absent-package,
prefix-collision, range-vs-entry and exact-version branches are each asserted to
come back negative.

bun.lock is not published: package.json `files` is an allowlist and does not list it.
@andrei-hasna
andrei-hasna force-pushed the build/hc-lockfile-policy branch from 20e8ee6 to 4de1db3 Compare July 27, 2026 22:04
@andrei-hasna
andrei-hasna merged commit 535daf0 into main Jul 27, 2026
0 of 7 checks passed
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

Adversarial review — APPROVED, merged (535daf0). No defect found. Reviewed at 20e8ee6 and re-reviewed after the rebase to 4de1db3; the three PR files are byte-identical blobs across the rebase, so the review carries over.

The pin is real, verified independently of the PR's own test: npm view @hasna/events@0.1.14 dist.integrity returns the same sha512-UXzEM6/8XUeys…BRFyLA== as the lockfile, so it pins bytes and not just a name. Best evidence — bun install in a clean worktree exited 0 and left git status --porcelain empty, with 0.1.14 installed. All 16 declared deps resolve to an exact x.y.z with an integrity hash (16/16). git check-ignore -v confirms bun.lock is not ignored and git ls-files confirms it is tracked, while each foreign lockfile is ignored by its own named line and *.lock still covers the store lock. The diff is exactly 3 files, no pnpm lockfile. The npm/pnpm grep hits are prose only; the sole subprocess in the new test is git.

One correction, recorded here rather than by editing a merged body. The body says "1 fail / sole failure: context pack". There are two pre-existing failures, and the second is worth someone's attention:

  1. knowledge cli > context pack and proposal context commands return bounded agent JSON
  2. knowledge machine sync ledger > sync export redacts local file and workspace refs from bundles

Neither is caused by this PR — CI on main at e99f4fd, which has no bun.lock, fails on exactly those same two names. The extra local failure is also a hardcoded }, 10000) budget that --timeout 60000 cannot raise; it timed out at 10022ms under load 54.66 and times out at base 64d05ae too.

Two measurement notes for whoever re-checks: gh pr checks reported 7 failures, but 5 of those jobs were CANCELLED by matrix fail-fast — only 2 genuinely failed. And bun printed locally but (pass)/(fail) in CI, so a single grep marker would have measured nothing. The new lockfile test logs (pass) 6/6 on both jobs that ran to completion, and 6 pass / 0 fail locally.

Merged with UNSTABLE deliberately: main has been red on these same unrelated tests for 5 consecutive pushes. Separately, (2) is a redaction test failing on main — that deserves its own task; I did not touch it.

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.

1 participant