Monorepo restructuring: standalone build, aligned dependencies, dist + source exports - #23
Merged
Merged
Conversation
Merged the full history of statewalker/webrun-http into this monorepo, with all files rewritten under packages/webrun-http/.
Merged the full history of statewalker/webrun-ports into this monorepo, with all files rewritten under packages/webrun-ports/.
Adds @statewalker/webrun-biscuit: the proto2 codec, the Ed25519 and
secp256r1 signature chain, the Datalog fixpoint engine, the text parser,
the authorizer and the token builder. Two runtime dependencies, no WASM
and no Node built-ins in src/, so it runs wherever the rest of the wire
runs. npm carries no other pure JS/TS Biscuit implementation.
The library arrives from its own session repository; this commit is the
port onto this repo's conventions, and the port is only worth trusting
because the mutation harness still passes.
**Tests: 156 + 55, none removed.** The suite moved from `tsx --test` to
vitest under tests/, and the conversion was deliberately the smallest one
that works: the `node:test` import is dropped so vitest's global `test`
takes over, and every `node:assert` assertion is kept byte-identical, so
nothing was retranslated. node:test's `test(name, {skip}, fn)` has no
vitest equivalent and became `test.skipIf(...)`, which is the only
semantic edit in the suite.
`pnpm mutate` injects ten known defects and requires each to break a
test. All ten are still caught after the port — a green suite would not
by itself show that the ported tests can fail. Each mutation's `find`
string is a literal source excerpt, so biome's reformat of src/ made six
go stale; they are re-derived against the formatted source rather than
loosened.
The cross-reference suite stays out of `pnpm test` behind its own vitest
config. It drives @biscuit-auth/biscuit-wasm, whose build reports
spurious RunLimit timeouts under CPU contention, and a flaky reference
must never be able to redden the main suite or a turbo build.
Two defects fixed in the corpus fetcher while wiring it up:
- A partial download poisoned samples/ permanently. samples.json was
written before the tokens it names, while the "already fetched" guard
checked only samples.json — so an interrupted fetch left a directory
every later run skipped as complete, and tests then failed on missing
files for ever. The manifest is now written last and the guard checks
every file it names.
- All 38 downloads were issued at once, which raw.githubusercontent
resets, surfacing as a bare `TypeError: fetch failed`. Now pooled at
six with retries.
Three lint errors in src/ were exhaustive switches with no terminal
statement; they gain a `default` that throws, so a future unhandled
variant is loud rather than `undefined`.
README follows the package shape in ~/.claude/rules.md, and its four
examples were run against the built dist/ — the published entry point,
not the source. Design documents from the implementation sessions are
kept under docs/webrun-biscuit/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Feat/webrun biscuit
Both packages are new and have never been on npm. A minor changeset against a 0.1.0 manifest would have bumped them to 0.2.0 and made that their first published version, so both were reset to 0.0.0 and the same changesets re-applied, landing them on 0.1.0 with CHANGELOGs that document the version actually shipped. Verified before publishing: the full suite is green (1,100+ tests across 17 packages), and both tarballs were packed, unpacked and installed into a throwaway consumer project. `catalog:` specifiers are rewritten to real ranges in the published manifests (`@noble/curves` ^2.4.0, `@noble/hashes` ^2.4.0), no `workspace:` or `catalog:` protocol leaked, and both entry points import and run from the tarball rather than merely typechecking. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cal ids
A variable's wire id is a symbol index. The builder wrote the parser's local
ids (1, 2, ...), so the reference printed `$k` as `$write`, and a block with
28 or more distinct variables minted a token this library could not read back
("unknown default symbol 28"). BlockContent now carries the parser's variable
names and SymbolWriter interns them, closure parameters included.
Found by porting httpeers-access onto this package: its failed-check text
came back naming `$write`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Crypto verification
What an application needs to use Biscuit without working around the API,
found by porting httpeers-access off biscuit-wasm:
- `{name}` parameters, bound as terms, across build/attenuate/authorize/
evaluate/query; unbound and unused parameters are a ParseError.
- `evaluate(token | null, code)` returning the result plus `query(rule)`,
scoped like the authorizer; the snapshot is computed only on request, so
`authorize` stops printing the world on every call.
- `FailedCheck.rule`, compared against the official corpus and, in the
random differential suite, against the reference.
- `verifyAsync` / `loadTokenAsync` on WebCrypto Ed25519 (0.25 ms against
1.9 ms per token in Node), sharing one list of signature checks with the
sync path.
The seal mutation is re-derived against the refactored chain, and three new
mutations cover the async verdict, query scope and unused parameters: 13/13
caught. Main suite 180, cross-reference 56.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
webrun-biscuit 0.2.0: parameters, queries, token-less evaluation, WebCrypto verification
…; ship LICENSE Names are `[A-Za-z0-9_:]+`, ASCII, with any character first, for predicates and variables alike, and a predicate takes at least one term. The parser had required a Unicode letter first and accepted `f()`: 9 of 12 probed sources disagreed with @biscuit-auth/biscuit-wasm, including `_m(true)`, which the httpeers shell prototypes use. `$ x` is no longer a variable either. tests/grammar-cases.ts records what the reference decided; cross/05-grammar re-asks it for every row and for 400 generated names. Two new mutations (zero-term predicates, Unicode names) are caught: 15/15. 0.2.0 shipped without LICENSE because the file lived only at the repository root; the package now carries its own copy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
webrun-biscuit 0.2.1: parse the names and arities the reference parses; ship LICENSE
… between iterations The limits were read after each fixpoint iteration, so one combinatorial rule enumerated its whole product first, and a check or policy (which derives no facts) was not bounded at all. Measured: a token held the verifier 4.8 s against a 50 ms budget; an exploding check ran 12 s. Found by httpeers-access's "exploding rule set denies rather than hanging" test, which went over its 5 s bound in CI once the engine was this package instead of biscuit-wasm. - The clock is polled every 1024 candidate facts inside `combine`, which rules, checks and policies all go through. - `maxFacts` counts distinct facts as they are derived, so it fires the moment the world would exceed it: the same verdict as the end-of-iteration check, without enumerating the rest. On the last permitted iteration it stands aside, so TooManyIterations keeps the precedence the reference gives it. - The budget closes when the evaluation ends: a later `query` is not charged against it. Main 185, cross-reference 58 (verdict parity unchanged). Two new mutations are caught; the leak one first survived, because its test queried one fact and never reached the clock's polling interval — fixed with 3,000. 17/17. Folded into the unreleased 0.2.1 changelog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
webrun-biscuit 0.2.1: enforce the run budget inside the join (missed #7's merge)
…over from webrun-wire webrun-biscuit imports ../../rolldown.preset.js (a webrun-wire root file) and uses @noble/curves, @noble/hashes, rimraf and rolldown from webrun-wire's catalog (values as of webrun-wire 770ff36).
- packageManager pnpm 10.16.0 -> 10.16.1 - comply.py: 129 specifiers through the catalog (apps used literals); adopted hono 4.13.7 -> ^4.12.14 in apps/sites - apps/sites pinned webrun-files, -mem, -node, -s3 at 0.7.x exactly while the catalog said ^0.9.0: all via catalog ^0.10.0 (-node, -s3 entries added); peers "^0.9.0 || ^0.10.0" - apps/sites: FileStats -> FileEntryStats in serve.ts and lookup.ts. statFile() already returned file stats only; the type now says so, no runtime change.
The packages pin @libp2p/interface 3.2.5, but libp2p and its plugins depend on ^3.x. Upstream's lockfile predates 3.3.0 (2026-08-23), so every transitive copy resolved to 3.2.5. Once the specifiers moved to catalog:, pnpm re-resolved them and the transitive copies took 3.3.0: two copies, and httpeers-libp2p failed to build (TS2322, Libp2p<ServiceMap> not assignable). A fresh install of upstream without its lockfile hits the same. The override keeps the tested upstream state.
Replaces 45368e0's override. The packages pinned @libp2p/interface 3.2.5 while libp2p 3.3.8 and its plugins resolve ^3.x to 3.3.0 (published 2026-08-23): two copies, and Libp2p types from one are not assignable to the other. An override only applies inside this workspace, so every consumer of httpeers-libp2p / -member (statewalker-shell-theia, statewalker-sandbox) still got both. With the catalog on 3.3.0 the whole graph shares one copy. Public packages 0.1.0 -> 0.1.1 (published manifests change; none of them is on npm yet).
workspace:* publishes an exact version of a sibling package ("0.2.0");
workspace:^ links the local package the same way during development and
publishes a range ("^0.2.0"), so consumers can take compatible patches.
Wave 0 of the dependency update (tools/align.mjs): every external catalog entry becomes ^<latest on npm within its breaking line>, the same value in every statewalker repo. No breaking line is crossed (those are separate waves); peer ranges, prerelease pins and documented exact pins are unchanged.
vitest, @vitest/* -> ^5.0.3; jsdom -> ^30.1.1, the same in every statewalker repo. The jsdom 30.0.1 pin is gone: the mime.view.* failure under jsdom 30.1 came from vitest 4's jsdom environment (its URL.createObjectURL read jsdom's internal Blob._buffer, renamed in jsdom 30.1); vitest 5 reads the new field. Peer ranges widened, not replaced (vitest "^4.x || ^5.0.3"), so published packages keep accepting existing consumers.
Versions from tools/releaseplan.mjs: minor on 0.x where a runtime or peer dependency moved to another breaking line, patch where only the published manifest changed (internal ranges ^x.y.z, dependency updates). - @statewalker/httpeers-access 0.1.2 - @statewalker/httpeers-bridge 0.1.2 - @statewalker/httpeers-ghost 0.1.2 - @statewalker/httpeers-hub 0.1.2 - @statewalker/httpeers-join 0.1.2 - @statewalker/httpeers-libp2p 0.1.2 - @statewalker/httpeers-member 0.1.2
Every public package now exports built JavaScript and declarations
from dist, with a "source" condition pointing at the TypeScript file:
{ "source": "./src/x.ts", "types": "./dist/x.d.ts",
"import": "./dist/x.js", "default": "./dist/x.js" }
Packages that exported raw source get an unbundled tsdown build (one
entry per exported module). src stays in "files" for the source
condition. CSS exports are unchanged.
Also: httpeers-member's ./browser and ./node profiles import libp2p,
its transports and @libp2p/crypto at runtime, but they were only
devDependencies (0.1.2 cannot run those subpaths). They are now
dependencies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
Versions from tools/releaseplan.mjs: minor on 0.x where a runtime or peer dependency moved to another breaking line, patch where only the published manifest changed (internal ranges ^x.y.z, dependency updates). - @statewalker/httpeers-access 0.1.3 - @statewalker/httpeers-bridge 0.1.3 - @statewalker/httpeers-core 0.1.2 - @statewalker/httpeers-ghost 0.1.3 - @statewalker/httpeers-hub 0.1.3 - @statewalker/httpeers-join 0.1.3 - @statewalker/httpeers-libp2p 0.1.3 - @statewalker/httpeers-member 0.1.3 - @statewalker/httpeers-qr 0.1.2 - @statewalker/webrun-biscuit 0.2.2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
"root": false marked this config as nested under the umbrella workspace. In a standalone checkout (CI, or a clone of this repo alone) biome then found no root configuration and ignored the file: it formatted with its defaults (tabs) and checked dist/, so lint:check failed on every file. That is why CI on main has been red. The config is now a root config, migrated to the installed biome, and biome's safe fixes are applied (formatting, import order). Tools running biome from the umbrella root should pass --config-path for each repo. Remaining errors were fixed by hand (button types and a placeholder link in demo pages, labels without controls, unsafe optional chaining in tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
…endencies The ./node and ./browser entries import libp2p and its transports, which were only devDependencies. The previous commit made them dependencies, but the boundary test pins the design: a member depends on the transport (httpeers-libp2p) and on no platform. They are now optional peer dependencies (catalog:peers), like idb-keyval: whoever imports ./node or ./browser installs them. @libp2p/crypto stays a dependency because the root entry reaches it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
Versions from tools/releaseplan.mjs: minor on 0.x where a runtime or peer dependency moved to another breaking line, patch where only the published manifest changed (internal ranges ^x.y.z, dependency updates). - @statewalker/httpeers-member 0.1.4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
Resolved from scratch (no lockfile, no node_modules) against npm, so the @statewalker dependencies lock the dist + source releases rather than the versions installed before them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
webrun-biscuit, carried over from webrun-wire with this restructuring, builds with a rolldown config that imports ../../rolldown.preset.js. The hub Dockerfile copies only selected root files, so the image build failed with "Cannot find module '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/repo/rolldown.preset.js'". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
Every other statewalker repo's CI now runs Node 24, the version the restructuring was verified on (TypeScript 7, tsdown 0.23, vitest 5). The member reservation-fallback conformance test failed once on Node 22 in CI while passing 10/10 locally on Node 24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U
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.
Part of the statewalker monorepo restructuring (layer 1). Each repository now builds, typechecks and tests on its own, with every
@statewalkerdependency taken from npm.What changes
workspace:^; external dependencies go through the pnpm catalog and are aligned on their latest versions across all statewalker repos (TypeScript 7, vitest 5, tsdown 0.23).distwith asourceexport condition pointing at the TypeScript source.biome.jsonis a root config again (with"root": falsebiome ignored it in a standalone checkout).Commits
Release order
The new versions are published to npm from this branch (
nexttag, install verified, then promoted tolatest) before this PR is merged, so a release job onmainfinds every version already published.🤖 Generated with Claude Code
https://claude.ai/code/session_01DAiZN6XrTAdQpNN3eXEz2U