pocket
Build and contribute

The gate

One command for everyday work, seven gates before a release, and what each one catches.

Everyday: npm run check

cd extension && npm run check

Four things behind one exit code:

tsc --noEmit                             the source
tsc --noEmit -p tsconfig.tests.json      the tests, which are a separate project
eslint src
vitest run                               src/**/*.test.ts
vitest run --config vitest.suites.config.ts   tests/{auth,failure,edge,qa}/**/*.test.ts

The tests are typechecked separately because they are a separate TypeScript project, and the source config does not reach them. Without their own pass, a test file can construct an error class with arguments it does not take and nothing objects, in a file whose whole job is asserting what that class says.

The second vitest config exists for a related reason: vitest.config.ts covers src/** only, so a suite added under tests/ would be run by nothing, and a run reporting hundreds of passes would be silent about every test in that directory.

A test not named in a config is a file, not a test. A new suite directory needs adding to vitest.suites.config.ts or it will never run while the gate stays green.

Before a release: seven gates

./scripts/release-gate.sh

Each gate exists because getting the thing wrong produces a failure that looks like a protocol bug rather than a build mistake.

Toolchain pin

nargo 1.0.0-beta.11 and bb 0.87.0, read from the binaries.

bb is the binding constraint, not nargo: it determines the proof and verification-key byte layout the on-chain verifier hardcodes.

Verification keys reproduce from circuit source

All six keys are regenerated with bb write_vk and hashed, then compared against the pinned hashes and against the vendored keys the extension ships.

Reproduce, then hash. A size check is not enough: a key from a different revision of the same circuit is the same 1,760 bytes. The measured sizes are 1,760 for a keccak transcript and 1,764 for poseidon2, so size catches a wrong transcript only by accident and the hash is what actually pins it.

The gate also proves a register circuit and asserts the proof is 14,592 bytes. An accidental zero-knowledge flag produces 16,224, which the key hash cannot catch, so both assertions are needed.

Deployment addresses resolve on chain

Every contract id the deployment record declares, derived from the record rather than a hardcoded list of three.

Testnet is wiped on resets and confidential identities are per deployment, so a stale address means every user silently re-registers.

The pinned verifier fork carries no Rust changes

The fork exists only to bump the Soroban SDK from 26 to 27. The gate diffs it against upstream main and fails on any .rs difference, because a Rust change would mean running verifier logic upstream has not reviewed.

Public-input slot counts match the circuits

Counted from the pub Field parameters of each circuit's main, against the recorded 6, 15, 24, 24, 24, 19.

The gate is named for what it asserts. Counting is not ordering, and ordering is the sharper risk, since a permutation of two same-typed inputs verifies a different statement. That is caught by the parity test, not here.

The shipping package is the one you think you are shipping

Gates 1 to 5 inspect sources. This one builds the package and reads it.

CheckedFails on
Versiona manifest version that disagrees with package.json, is not a version Chrome accepts, or is not newer than the last release tag
Source mapsany .map file or sourceMappingURL reference
Endpointsany loopback address, or any plaintext http:// endpoint
Content security policya missing policy, unsafe-eval, or a remote origin
Iconsa missing icons key, a file it names that is absent, or a missing 16, 48 or 128 px size
Host permissionsa declared host the shipped code never uses
Deployment idsany recorded contract id absent from the built bundle

The version check earns its place: Chrome only replaces an installed extension when the published version is newer, so a fix that never reaches an existing install is the worst kind of release, because the release looks successful.

The deployment-id check earns its place too: confidential openings are stored under a key containing the token contract address, so a build pointing at a different deployment silently orphans every existing user's openings.

The browser suite runs against the package gate 6 built

POCKET_EXT_PATH points Playwright at that exact artifact rather than building a second one, which is also the stronger check: the browser tests then run against the thing the other gates inspected.

It is the only gate that exercises the interface. Everything before it is tsc, eslint and two vitest configs, none of which opens a browser, so a shared locator that goes stale against a rebuilt interface turns green all the way to here.

Skipped tests fail the gate

vitest exits successfully with any number of skipped tests, so the gate reads the machine-readable report and fails when anything skipped itself.

This is not pedantry. The parity test, which is the only thing checking public-input ordering, gates on a nargo binary and on circuit sources under an ignored directory. On a machine without the toolchain it reports "24 skipped" and exits zero.

Gate 5 says in its own comment that ordering is caught by that test. So without this rule, a machine without the toolchain would assert a property nothing had checked, and print PASS.

Failures print

A gate that says only FAIL is a gate people learn to skip, so each step prints an excerpt of what went wrong.

The three checks behind npm run check are also run separately inside the gate, rather than through the single command, because one exit code cannot tell a real pass from a run that quietly disabled part of itself.

The vitest excerpt is taken from the tail of the output, because vitest prints the run first and the failures last, and printing the head would show forty green ticks and hide the reason.

On this page