The kit's value scales with the size of the quirks knowledge base. If you discover a new BitBox02 firmware constraint — a quirk that bit your code or a customer's — please file it back into the JSON so every consumer picks up coverage on their next install.
-
Pick an ID. Choose the next free slot in the category (e.g.
E11for an 11th ETH quirk). Categories:eth,btc,cardano,mnemonic,protocol,app. -
Edit
/go/bitbox/quirks/quirks.json. Add an entry with:See
quirks/SCHEMA.mdfor the full schema. -
Sync to the TypeScript side:
./scripts/sync-quirks.sh
The TS loader reads the synced copy at
/ts/src/quirks/quirks.json. CI runs--checkand fails on drift. -
(Optional) Add a Scenario factory if the firmware response shape differs from the generic
ErrInvalidInput101rejection./go/bitbox/scenarios/scenarios.go— Go side./ts/src/scenarios/index.ts— TS side.- Wire into
/go/bitbox/quirks/callbacks.goand/ts/src/quirks/callbacks.tsattachCallbacks/switch q.idarms so the quirk'sScenariofield gets your factory instead of the default.
-
Add tests for the new detection rule in
/go/cmd/bitbox-audit/audit_test.go. Include both a positive case (pattern fires correctly) and a negative case (similar code that should NOT fire). Without the negative test, false-positive regressions creep in. -
Re-validate against real repos. Run
bitbox-audit --repo <somewhere-with-bitbox-code>against at least one consumer that exercises the new quirk's surface. False positives here cost more than missing detection — a noisy audit gets muted by consumers. -
Update
CHANGELOG.mdunder the next-release section with a one-line entry.
| Kind | When to use |
|---|---|
regex |
Simple line-level match; no per-file gating needed. |
regex_in_context |
Match regex only in files whose content satisfies context_regex. Suppresses noise where the same surface text means different things in different contexts. |
ordered_pair |
"X must appear before Y." File must contain both before_regex and after_regex; a finding is emitted only when after_regex precedes before_regex in byte offset. |
missing_pair_within |
"Every X must be followed by Y within N lines." Use for export-with-guard, lock-with-defer-unlock, and other proximity-paired patterns. |
Regex compatibility: Go uses RE2 (no lookahead / lookbehind / backreferences). JS regex is RE2-compatible for the patterns we need. Write regexes that work in both engines; the audit-runner uses Go RE2.
A Scenario returns a configured fake suitable for use in a single test. Two questions to ask:
-
Does the firmware respond differently from a generic
ErrInvalidInput101? If no, reuse the default. Adding identical wrappers just adds noise. -
Is there a multi-step flow? Use a closure that captures state (counter, confirm flag) and returns different responses on subsequent calls. See
scenarioChannelHashEarlyfor the canonical example.
# Go: full test sweep with race detector
(cd go && go test -race -timeout 60s ./...)
# TS: typecheck + jest
(cd ts && npx tsc --noEmit && npm test)
# JSON sync gate (CI runs this as well)
./scripts/sync-quirks.sh --check
# Audit against your own working tree
(cd go && go run ./cmd/bitbox-audit --repo .. --format markdown)Tags follow semver vMAJOR.MINOR.PATCH. The TypeScript package and the Go module both pick up the same tag — there's no separate cadence. CHANGELOG.md is updated as part of the change PR (not as a separate "release commit").
Releases happen automatically off main. Once a PR merges into develop, the Auto Release PR workflow opens a Release: develop -> main PR. When that PR is merged, the Auto Tag on Merge workflow runs, looks at every commit between the previous tag and the new main HEAD, parses each subject as Conventional Commits, picks the highest bump, and creates both tags (vX.Y.Z and go/vX.Y.Z) plus the matching GitHub Release.
The Go module lives at /go/, so Go's submodule-tagging convention requires the dual tag at the same commit. Without the go/ prefixed tag, consumers hit:
module github.com/DFXswiss/bitbox-testkit@vX.Y.Z found, but does not contain package …/go/cmd/bitbox-audit
The auto-tagger reads Conventional Commits 1.0. Use these subjects in your PR commits:
| Subject prefix | Bump | Example |
|---|---|---|
feat!:, fix!:, <type>!: |
MAJOR | feat!: drop legacy bitbox-api v0.11 support |
BREAKING CHANGE: in commit body |
MAJOR | (paired with any subject) |
feat:, feat(scope): |
MINOR | feat(simulator): add BTC scenarios |
fix:, perf:, refactor:, revert: |
PATCH | fix(audit): suppress doc-comment false positive |
chore:, ci:, docs:, test:, style:, build: |
PATCH | ci: cache go modules |
| (anything else) | PATCH + warning | the auto-tagger logs a warning to the CI step |
The aggregator picks the highest bump across every commit in the range — one feat!: is enough to promote the whole release to a major bump, one feat: is enough for a minor.
Before merging a release PR you can preview the version the auto-tagger will pick:
go -C go run ./cmd/release-version --base "$(git describe --tags --abbrev=0 --match='v*.*.*')" --reportThe first stdout line is the next tag; the rest is a per-commit explanation of why each commit voted the way it did.
If you ever need to ship out-of-band — e.g. an emergency security fix from a hotfix branch — push the tags by hand:
# Update CHANGELOG.md, /ts/package.json version
git commit -am "Release vX.Y.Z"
# Two tags, one commit. The 'go/' prefix is required by Go's
# submodule resolver — see https://go.dev/ref/mod#vcs-version.
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git tag -a go/vX.Y.Z -m "go/vX.Y.Z: submodule tag matching vX.Y.Z" vX.Y.Z^{}
git push origin main --tagsWhen the auto-tagger next runs, it will see the manual tags in git tag -l and pick the bump relative to them — no special-case handling needed.
{ "id": "E11", "name": "kebab-case-slug", "category": "eth", "severity": "critical", // hint | warning | critical "description": "One-paragraph human explanation.", "source": "messages/eth.proto: … or CHANGELOG v9.X.Y: … or observed in production", "firmware": { "min": "9.0.0", "max": "" }, // empty = unbounded "match_regex": "(?i)…", // optional: test-output classifier "detect": [ // optional: static-detection rules { "kind": "regex", "regex": "…", "reason": "…", "fix_hint": "…" } ] }