Skip to content

Declare the runtime floor, and derive the release bump - #357

Merged
sroussey merged 3 commits into
mainfrom
claude/p1-sec-347-release-floor
Sep 8, 2026
Merged

Declare the runtime floor, and derive the release bump#357
sroussey merged 3 commits into
mainfrom
claude/p1-sec-347-release-floor

Conversation

@sroussey

@sroussey sroussey commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #347. Closes #357.

The problem

release cut a patch unconditionally. On a 0.x line the minor is the break slot, so the re-founding — a changelog entry that opens with BREAKING and removes the package's only import entry point — would have gone out as 0.1.6 and resolved for anyone on ^0.1.5.

What I found that changes the fix

bunset already refuses --patch on a breaking change (assertBumpAllowsBreakingChanges, src/version.ts:36-44). But that guard reads commit messages — a trailing ! or a BREAKING CHANGE: footer — and across the 27 commits since v0.1.5 there is not one:

$ git log --format='%s' v0.1.5..origin/main | grep -E '^[a-z]+(\([^)]*\))?!:'
(none)
$ git log --format='%H %b' v0.1.5..origin/main | grep -iE 'BREAKING[ -]CHANGE:'
(none)

So the guard never fires for a break recorded only as a changelog heading. The issue's premise is confirmed.

Changes

The bump is derived, not chosen. release-checks holds the gate set (format, lint, typecheck, build, prepack-check); one release script runs it and then bunset --auto.

--auto reads the bump off the commits since the last tag and off a diff of package.json against the one at that tag — a lost exports subpath or bin entry, an engines floor that appeared or moved up — raising the derived bump to the break slot when it finds one. Both breaks in this release are exactly that class, and it finds them unaided:

[debug]   manifest surface changes: 3
[debug]     - [entry-point-removed] no "exports", "main" or "module" — importing @workglow/sec no longer resolves
[debug]     - [engines-added] engines.node is now ">=24" — it was unconstrained at the last tag
[debug]     - [engines-added] engines.bun is now ">=1.4.0" — it was unconstrained at the last tag
[debug] @workglow/sec: derived bump = minor
@workglow/sec: 0.1.5 → 0.2.0 (minor)

release-minor is removed rather than kept beside it: a script that names a level is the choice --auto takes away, back in the place it was made from. bunset is pinned to 1.1.1--auto arrived in 1.1.0 and its 0.x table was corrected in 1.1.1, and an older pin does not fail loudly, it takes the flag as unknown.

This also narrows sroussey/bunset#8, which I filed asking for the changelog's leading unreleased heading as a second input to the break-slot guard. The manifest diff covers this release without it. What it still cannot see is a break with no manifest footprint and no commit marker — a changed signature, a removed CLI flag — so the ask stands, smaller.

engines: {"node": ">=24", "bun": ">=1.4.0"}. The floor node:sqlite needs was asserted in .claude/CLAUDE.md and nowhere a package manager reads, so npm i -g @workglow/sec on Node 22 installed cleanly and failed later, inside the storage layer, reading as a storage bug.

better-sqlite3 dropped from devDependencies and trustedDependencies, unused since @workglow/sqlite moved to node:sqlite.

A latent bug that swap left behind. isUniqueConstraintError matched code === "SQLITE_CONSTRAINT_UNIQUE" — a better-sqlite3 spelling. node:sqlite reports every failure as code: "ERR_SQLITE_ERROR" and puts the specific one in a numeric errcode, so that branch never fired and SQLite was left with the English error message as its only signal, against the two-signal design the function's own JSDoc states. Verified on Node 24.20:

case code errcode message
UNIQUE index ERR_SQLITE_ERROR 2067 UNIQUE constraint failed: t.a
PRIMARY KEY ERR_SQLITE_ERROR 1555 UNIQUE constraint failed: pk.a
CHECK ERR_SQLITE_ERROR 275 CHECK constraint failed: …
NOT NULL ERR_SQLITE_ERROR 1299 NOT NULL constraint failed: …

Both UNIQUE codes are now matched and both non-UNIQUE ones are asserted not to be.

Note for review: isUniqueConstraintError has no callers in src/ — it went dead with the identity tier. I fixed rather than deleted it because deletion belongs with #355, which enumerates the dead exports; I have commented there, since its list of nine missed this one.

Docs. README's "Cutting a release" section explains the derivation, and states the binary-only package shape explicitly (the issue's ask 3), since a manifest with no exports/main/types otherwise reads as a field someone deleted by mistake.

The ratchet

src/packageManifest.test.ts pins the floor, the absent native driver, the binary-only shape, and the release script's shape: that it derives rather than names, that no bump flag survives in it, and that the pinned bunset has --auto. Written first — 6 of its assertions were red against the old manifest, the green ones being the package shape that already held.

Verification

format-check, lint, typecheck clean. packageManifest.test.ts 11/11, isUniqueConstraintError.test.ts 17/17 (3 new assertions red before the fix). bunset --auto --dry-run --debug on this branch derives the minor and would tag v0.2.0.

Merge note

Conflicts with claude/p1-sec-352-dry-run-kb on CHANGELOG.md only — both append to the same ### Changed list. Resolution is to keep both bullet sets; no code overlap.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LWp6Z6wvAPDaDCjAFcTSj6

…a script

`release` cut a patch unconditionally. On a 0.x line the minor is the break
slot, so the re-founding — a changelog entry that opens with BREAKING and
removes the package's only import entry point — would have gone out as 0.1.6
and resolved for anyone on `^0.1.5`.

`bunset` already refuses `--patch` when a commit carries a Conventional Commits
break marker, but that guard reads commit messages: none of the 27 commits since
v0.1.5 carries one, so it never fires for a break recorded as a changelog
heading. Rather than hand-roll changelog parsing here — four repos use bunset,
and it is the one that owns this question — `release` and `release-minor` now
run one shared `release-checks` gate set and differ only in the bump.

Also declares `engines: {node: ">=24", bun: ">=1.4.0"}`. The floor node:sqlite
needs was asserted in prose and nowhere a package manager reads, so installing
on Node 22 succeeded and failed later, inside the storage layer.

Drops the better-sqlite3 devDependency and its trustedDependencies entry, unused
since @workglow/sqlite moved to node:sqlite. That swap also left
isUniqueConstraintError matching a string only the native driver produced:
node:sqlite reports every failure as code "ERR_SQLITE_ERROR" and puts the
specific one in a numeric errcode, so SQLite had the English error message as
its only signal. Verified against Node 24.20 — UNIQUE index 2067, PRIMARY KEY
1555, and CHECK 275 / NOT NULL 1299 which must not match.

src/packageManifest.test.ts pins the floor, the absent driver, the binary-only
shape, and that both bump levels share one gate set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWp6Z6wvAPDaDCjAFcTSj6
`release` cut a patch and `release-minor` was the second script to
remember when the changelog opened with BREAKING. That leaves the number
to whoever types the command, which is the failure it was meant to
prevent, one step further back.

bunset 1.1.1 derives it. `--auto` reads the bump off the commits since
the last tag AND off a diff of `package.json` against the one at that
tag — a lost `exports` subpath or `bin` entry, an `engines` floor that
appeared or moved up — and raises the derived bump to the break slot when
it finds one. That is the class of break no commit message describes,
because nothing about it looks like a breaking edit.

Both breaks in this release are exactly that class, and `--auto` finds
them without being told:

    [entry-point-removed] no "exports", "main" or "module"
    [engines-added] engines.node is now ">=24"
    [engines-added] engines.bun is now ">=1.4.0"
    derived bump = minor → 0.1.5 → 0.2.0

`release-minor` is removed rather than kept beside it: a script that
names a level is the choice `--auto` takes away, back in the place it was
made from. The shared `release-checks` gate set is unchanged.

The manifest ratchet follows — it now asserts the release script derives
rather than names, that no bump flag survives anywhere in it, and that
the pinned bunset is one that has `--auto` (an older pin does not fail
loudly; it takes the flag as unknown).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWp6Z6wvAPDaDCjAFcTSj6

sroussey commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Switched to bunset --auto in fa8057d3, and it turns out to answer the open question in this PR rather than just replace a flag.

--auto (bunset 1.1.0, 0.x table corrected in 1.1.1) derives the bump per package from the commits since the last tag and from a diff of package.json against the one at that tag. Verified against this branch:

[debug]   manifest surface changes: 3
[debug]     - [entry-point-removed] no "exports", "main" or "module" — importing @workglow/sec no longer resolves
[debug]     - [engines-added] engines.node is now ">=24" — it was unconstrained at the last tag
[debug]     - [engines-added] engines.bun is now ">=1.4.0" — it was unconstrained at the last tag
[debug] @workglow/sec: derived bump = minor
@workglow/sec: 0.1.5 → 0.2.0 (minor)
Would tag: v0.2.0

So the same 0.2.0 release-minor was hand-picked for, derived — and derived from the two things that actually broke, not from someone reading the changelog heading. The entry-point removal is the re-founding itself; bunset sees it because the manifest lost its only import entry, which is the class of break no commit message describes.

This narrows sroussey/bunset#8. I filed that asking for the changelog's leading unreleased heading as a second input to the break-slot guard. The manifest diff covers this release without it. What it still cannot see is a break with no manifest footprint and no commit marker — a changed function signature, or a removed CLI flag — so the ask stands, but it is smaller than I wrote it.

release-minor is removed rather than kept beside release: a script that names a level is the choice --auto takes away, back in the place it was made from. release-checks is unchanged, and the ratchet in packageManifest.test.ts now asserts the release script derives rather than names, that no bump flag survives in it, and that the pinned bunset is one that has --auto — an older pin does not fail loudly, it takes the flag as unknown.

The PR body above is now out of date on this point; the commit message and the changelog entry carry the current story.


Generated by Claude Code

@sroussey sroussey changed the title Declare the runtime floor, and offer the minor bump as a script Declare the runtime floor, and derive the release bump Sep 8, 2026
The conflict is the one this PR's merge note predicted: both sides append
to the same `### Changed` list and neither edits the other's bullets, so
both sets are kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWp6Z6wvAPDaDCjAFcTSj6
@sroussey
sroussey merged commit 24025a6 into main Sep 8, 2026
1 check passed
@sroussey
sroussey deleted the claude/p1-sec-347-release-floor branch September 8, 2026 18:31
sroussey added a commit that referenced this pull request Sep 8, 2026
### Features

- implement ITabularStorage.join for @WorkGlow 0.5.0 (#362)
- implement CLI signal teardown and resource shutdown

### Bug Fixes

- declare the embedding width beside the model instead of assuming it (#359)

#### release

- declare the runtime floor, and derive the release bump (#357)

#### kb

- stop --dry-run creating the index tables, and report them in db stats
- bound `ask`'s implicit index, pin the embedding model, and let `db reset` drop the index

#### ask

- refuse rather than answer from model memory when nothing is retrieved

#### adv

- scope each Form ADV archive to its own folder and make ingest idempotent

### Performance

#### status

- read the newest ADV snapshot with one indexed row

### Tests

#### coverage

- make the README witness check capable of failing (#360)

### Chores

- update deps
- update dependencies to latest versions
- migrate from Prettier to oxfmt for code formatting

#### deps

- upgrade Vitest to 5 (#342)

### Updated Dependencies

- `@workglow/cli`: 0.5.0
- `typebox`: 1.3.29
- `workglow`: 0.5.0
- `@types/bun`: 1.4.2
- `bunset`: 1.1.1
- `oxlint`: ^1.82.0
- `vitest`: ^5.0.0
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.

The re-founding's BREAKING marker is queued for a patch bump, and 0.1.5 on npm still advertises an exports map HEAD no longer builds

2 participants