Repository navigation
Release request: cut v0.6.0 with payment fixes since v0.5.0 #339
Description
Activity
Release readiness: v0.6.0
Merged since v0.5.0 (22 PRs)
Critical fixes:
PR Fix #304 gonuts v0.10.0 — URL normalization, swap-counter race, DLEQ keyset lookup, NUT-13 big.Int, 429/5xx retry, module rename #330 Reject spending-condition-locked tokens + fix Fund() decode #274 io.ReadAll 1MB limit (DoS prevention) #307 Sync all dependencies (x/crypto v0.54.0, 28 CVEs patched) + drift checker + pre-commit hook #298 Fall back to degraded mode on wallet init failure (#275) #281 V2 keyset swap crash fix (gonuts v0.7.6) #283 NDS fw4/nftables enforcement bridge #297 Portable WAN detection in NDS enforcement Improvements:
PR What #338 Configurable rate limiter (TOLLGATE_RATE_LIMIT_RPM) #335 Build portal assets from source (no more committed minified JS) #321 Identity: loopback enforcement + body limit #316 Return 200 instead of 500 on /usage when no session #310 Sat standardization + advertisement fix + config migration #265 sysexec wrapper (context, retry, logging) #294 TOLLGATE_TEST_CONFIG_DIR for socket path Infrastructure:
PR What #309 Regression test for wallet-init-failure → degraded mode #302, #303 CI: Blossom mirror fixes #293 gonuts go.sum hash fixes #292 gonuts v0.9.0 bump #286 gonuts v0.8.0 bump #319 gofmt fix Open PRs ready for review (12)
PR Title CI Blocks #314 429 backoff at merchant layer ✅ green #260 #315 NDS autopay session trigger ✅ green #88 #317 Firewall port 2121 to LAN ✅ green #226 #327 greatspectations + AI audit ✅ green — #340 Token recovery tool ✅ pending — #299 WalletPort interface ✅ green — #331 Unified identity (NIP-06) check — #332 WGM vendor IE discovery check — #337 Validator tests check — #336 Stale SSID comment check — #312 WGM discovery logger check — #261 Post-swap-fee characterization check — gonuts-tollgate v0.10.0 changes (15 PRs)
URL normalization, swap-counter race fix, DLEQ per-proof keyset lookup, NUT-13 big.Int + collision detection, error wrapping (%w throughout), empty slice panic guards (5 sites), non-atomic swap fix, silently swallowed error fix, HTTP 429 rate limiting, 5xx retry on GET, module rename (Origami74 → OpenTollGate), resolveShortKeysetIds (V4).
Recommendation
Merge #314, #315, #317 first (bug fixes with CI green + reviews posted). Then cut v0.6.0.
v0.6.0 Release Readiness Assessment — Jul 30
Already merged (on main, ready for release)
PR Category What #281 Fix V2 keyset swap crash (gonuts v0.7.6) #283 Fix NDS fw4/nftables enforcement bridge #286 Deps gonuts v0.8.0 (V4 short keyset ID resolution) #292 Deps gonuts v0.9.0 #294 Fix CLI socket path for test env #297 Fix Portable WAN detection in NDS #298 Fix Degraded mode fallback on wallet init failure (#275) #304 Deps gonuts v0.10.0 + module rename (Origami74 → OpenTollGate) #307 Chore Sync all deps + drift checker + pre-commit hook #310 Fix Sat standardization + advertisement fix #316 Fix Return 200 instead of 500 on /usage #319 Fix gofmt tab indentation #321 Fix Identity loopback enforcement + body limit #330 Fix Reject spending-condition-locked tokens + fix Fund() decode #335 Chore Build portal assets from source #338 Fix Configurable rate limiter Open PRs — ready for review (CI green)
PR Category What Issue #314 Fix 429 backoff at merchant level #260 #315 Fix NDS session trigger after autopay #88 #317 Security Firewall port 2121 to LAN #226 #327 Tooling greatspectations + AI audit prompts — #340 Tool Token recovery tool #88 #261 Test Post-swap-fee characterization #63 #299 Architecture WalletPort interface — #331 Feature Unified identity (NIP-06) — #332 Feature WGM vendor IE discovery — #337 Test tollgate_protocol validators — gonuts-tollgate v0.10.0 highlights (15 PRs merged)
- URL normalization (trailing slash crash-loop Backend crash-loops instead of entering degraded mode on wallet initialization failure #275)
- Swap-counter race fix + retry
- DLEQ per-proof keyset lookup
- NUT-13 big.Int keyset ID + collision detection
- HTTP 429 + 5xx retry
- Error wrapping consistency (%w)
- Empty slice panic guards
- Non-atomic swap fix
- Module rename (Origami74 → OpenTollGate)
Recommendation
v0.6.0 can be cut now with what is on main. PRs #314, #315, #317 are critical bug/security fixes that are CI-green and reviewed — merging them before the cut would make v0.6.0 significantly more robust. The feature PRs (#331, #332, #299) can wait for v0.6.1.
Release readiness: v0.6.0
Merged since v0.5.0 (20 PRs)
Critical fixes:
PR Fix #304 gonuts v0.10.0 — URL normalization, swap-counter race, DLEQ keyset lookup, NUT-13 big.Int, 429/5xx retry, module rename #330 Reject spending-condition-locked tokens + fix Fund() decode #274 io.ReadAll 1MB limit (DoS prevention) #307 Sync all deps (x/crypto v0.54.0, 28 CVEs patched) + drift checker + pre-commit hook #298 Fall back to degraded mode on wallet init failure (#275) #281 V2 keyset swap crash fix (gonuts v0.7.6) #283 NDS fw4/nftables enforcement bridge #297 Portable WAN detection in NDS enforcement Improvements:
PR What #338 Configurable rate limiter via env var #335 Build portal assets from source #321 Identity loopback enforcement + body limit #316 Return 200 not 500 on /usage without session #310 Sat standardization + advertisement fix + config migration #265 sysexec wrapper with context/retry/logging #294 TOLLGATE_TEST_CONFIG_DIR for socket path Infrastructure:
PR What #309 Regression test for degraded mode fallback #302, #303 CI Blossom mirror fixes #292, #293 gonuts v0.9.0 bump + go.sum fix #286 gonuts v0.8.0 bump #319 gofmt fix Open PRs ready for merge (CI green, reviews posted)
PR Title Issue #314 429 backoff at merchant layer #260 #315 NDS autopay session trigger #88 #317 Firewall port 2121 to LAN #226 Recommendation
Merge #314, #315, #317 (all CI green, LGTM posted). Then cut v0.6.0.
Release readiness: v0.6.0 — Jul 30
Merged since v0.5.0 (20 PRs)
Critical fixes:
PR Fix #304 gonuts v0.10.0 — URL normalization, swap-counter race, DLEQ keyset lookup, NUT-13 big.Int, 429/5xx retry #330 Reject spending-condition-locked tokens + fix Fund() decode #274 io.ReadAll 1MB limit (DoS prevention) #307 Sync all deps (x/crypto v0.54.0 — 28 CVEs patched) + drift checker + pre-commit hook #298 Degraded mode fallback on wallet init failure (#275) #281, #283 V2 keyset crash fix + NDS fw4 enforcement #297 Portable WAN detection in NDS Improvements:
#338 (configurable rate limiter), #335 (portal assets from source), #321 (identity loopback), #316 (200 not 500 on /usage), #310 (sat standardization), #265 (sysexec wrapper), #294 (test env socket)Open PRs ready for review (CI green, LGTM posted):
- fix(#260): retry on mint HTTP 429 with exponential backoff #314 — 429 backoff (No HTTP 429 handling: Cashu payments fail when mint rate-limits (coinos.io) #260)
- fix(#88): trigger NDS session after successful autopay #315 — NDS autopay session trigger (Router-to-router autopay implicitly depends on nodogsplash client registration — breaks protocol compatibility #88)
- security(#226): firewall backend API port 2121 to LAN only #317 — Firewall port 2121 (Architecture: move port 2121 to loopback-only (defense-in-depth) #226)
gonuts-tollgate v0.10.0 (15 PRs)
URL normalization, swap race fix, DLEQ per-proof, NUT-13 big.Int, error wrapping, panic guards, non-atomic swap fix, 429+5xx retry, module rename.
Recommendation
Three PRs blocking v0.6.0 — all tested on physical GL-MT3000 hardware
1. PR #315 — NDS gate-open after autopay (fixes #88)
#315
Payment succeeds, client authenticates, but ndsctl returns exit 1 on already-authenticated clients. Go code treats this as failure. Gate never opens. This is THE blocker for end-to-end payment.2. PR #314 — Mint HTTP 429 retry (fixes #260)
#314
Cashu mints (coinos.io, testnut) rate-limit quote polling. Currently fails immediately as "payment-processing-failed." Adds 3 retries with 2s/4s backoff.3. PR #317 — Firewall API port 2121 to LAN only (fixes #226)
#317
nftables rule blocking TCP 2121 from WAN. Currently the payment backend is reachable from upstream.All three PRs build + test clean, updated Jul 29. CI green. Reviews posted.
@c03rad0r — these three unblock end-to-end payment flow. Can we get them merged so we can cut v0.6.0?
Update: all three fixes (#88, #260, #226) are merged on main at b528e1d. E2E verified on physical GL-MT3000: payment → NDS gate opens → internet access confirmed.
Fork release with .ipk: https://github.com/felixfelix-bot/tollgate-module-basic-go/releases/tag/v0.6.1-post-merge
v0.6.0 cut — plan confirmed (Felix, 2026-08-31)
Sequence (strict order):
- Merge ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369 then ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370 (Amperstrand CI stack — both consultant-reviewed 2026-08-31, CI-only:
build-package.yml; these pipeline fixes make the release artifacts themselves).
⚠️ ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369 shows BLOCKED / review gate not satisfied by bot approvals (felixfelix-bot re-approval 2026-08-31 on record) — needs owner approval/merge in UI. ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370 is CLEAN and stacked behind ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369. - Changelog backfill PR —
[Unreleased]is missing entries: feat(identity): unified NIP-06 + HKDF + RevealSeed (v2, rebased) #331, feat(wgm): discovery logger — structured scan history + CLI summary #312, feat(tollwallet): WalletPort interface + GonutsWallet adapter + token flow tests #299, fix(#88): trigger NDS session after autopay (review fixes) #347, fix(deps): drop felixfelix-bot fork replace — build against OpenTollGate gonuts-tollgate v0.11.1 #361, fix(ci): resolve pre-existing CI debt — config_manager buildinfo tests + merchant in go-test matrix #365, fix(ci): package-apk portal build uses setup-node 20, not Debian Node 12 #366, refactor(ci): build captive portal once per run, not per matrix leg #368 (+ ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369/ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370 CI entries). - Race-test gate before tag —
go test -race -count=1 -tags testenv ./...×2–3 fromsrc/. Knowntime.Sleepflake candidates:merchant/,wgm/. Triage → fix or document. - Tag v0.6.0 — maintainer manual ONLY. Tag regex
^v\d+\.\d+\.\d+$triggersbuild-package.ymlone-shot Nostr-wide stable publish (5 blossom servers, kind-1063, unretractable, no draft stage). Never push tags from local checkouts (stray audit tags exist there).
Why now: 14 PRs merged over 2 months since v0.5.0 remain untagged.
Tracking: kanban board
tollgate-module-basic-go— CHANGELOG-BACKFILL-V060 → RACE-TEST-V060 → TAG-PREP-V060, auto-released by watchdog when step 1 merges land.- Merge ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369 then ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370 (Amperstrand CI stack — both consultant-reviewed 2026-08-31, CI-only:
State flag from Amperstrand's verification pass (live queries, 2026-09-01 ~22:00Z) — #370 is merged but not on
main.PR #370 reports MERGED (merge commit
3f53827, 2026-08-30T22:43:30Z, parenta3b1b26), but that commit is not an ancestor of currentmain:maintip isdb8af35(#369, 2026-08-30T22:42:36Z, parent020e647). Content check agrees —git diff main..3f53827is exactly the 16 added lines ofbuild-package.yml(per-target SDK dl/staging/build-tree caches + job timeouts); none of it is onmain(noactions/cachefor SDK trees; apk legs still run without job timeouts, 6h default).Looks like a merge race between the two stacked PRs merged ~54s apart — #370's merge landed on a pre-#369 base and current
maindoesn't contain it.Practical effect on the §6 pre-tag condition ("build-package.yml on main is post-#369/#370"): currently post-#369 only. Tagging now builds the apk artifacts on the uncached, no-timeout pipeline. The 16 lines likely need to land on current
main(re-merge or cherry-pick3f53827) before tag prep.Related, also from our verification pass: review filed on #373 — the changelog backfill is missing an entry for #359 (test-mint injection stop; release-relevant) and #353 (vendor IE discovery).
v0.6.0 pipeline dry-run + race-gate corroboration (Amperstrand, 2026-09-01/02)
1. The post-#369 pipeline builds the full v0.6.0 artifact matrix — verified end-to-end, publish-gated.
Since #369 makes the workflow skip PR branches, the post-#369 pipeline had never actually run. We dispatchedbuild-package.ymlfromdb8af35on the Amperstrand fork in a publish-gated variant (only artifact transport changed: blossom → actions artifacts,publish-metadatasetif: false, zero blossom/nostr calls; build steps byte-identical; branchdryrun/pipeline-publish-gated):compile-binaries✓ (all 5 GOARCH targets),build-portal✓ (from-source portal build — the fix(ci): package-apk portal build uses setup-node 20, not Debian Node 12 #366/refactor(ci): build captive portal once per run, not per matrix leg #368 path, verified working),package-ipk6/6 legs ✓,package-apk2/2 legs ✓ (mediatek-filogic = GL-MT3000, and x86_64; ~90 s per apk leg on theopenwrt/sdk:*-25.12.0images),publish-metadata/trigger-build-oscorrectly skipped. Runs: 33564443068, 33565013577 — all 8 package artifacts retained there for inspection.
Implication for the missing #370 (see our comment above): on the current
25.12.0SDK images the apk legs complete in ~90 s even uncached, so the missing SDK cache mostly costs runner minutes, not correctness — the job-timeout half of #370 is the more valuable half. Once #370's 16 lines land (re-merge or cherry-pick3f53827), tag-prep can proceed with high confidence the pipeline itself is green.2. Race gate: the documented command does not exercise the flaky packages.
go test -race -count=1 -tags testenv ./...fromsrc/covers only the root module — everysrc/*subdir is its own Go module (nestedgo.mod), somerchant/,wgm(wireless_gateway_manager/),config_manager/,tollwallet/etc. never run under the documented gate. We suggest iterating*/go.mod(or adding ago.work) for the actual gate.Corroboration on
db8af35: root module ×3 green; all 15 modules green on full pass 1 (per-modulego test -race -count=1 -tags testenv,-p 1); passes 2–3 plus targeted merchant/wgm ×5 iterations in progress — we'll follow up only if something trips.Ops note:
-racelinking ofmerchantOOM-killed on a memory-constrained box under parallel package build;-p 1resolves it. Unlikely to matter on CI runners.(Changelog coverage gaps for #359/#353: filed as review on #373.)
- added a commit that references this issue
on Sep 7, 2026 Status roundup from Amperstrand (2026-09-08, live re-verification)
Progress since our Sep-1 verification pass:
- ✅ test: de-flake race suite for v0.6.0 #374 merged (
96bf6a2) — sleep→waitForconversions. Note from our review comments on that PR:TestSetOnFirstReachableForDegraded_FiredOnceis a different class (registration resetshadReachableMint→ the next proactive check deterministically queues a spuriousgo cb()→ the bare zero-assertion races the detached goroutine). Fresh measurement on96bf6a2: merchant module 4/4 green so far, 5th in flight (~188 s each). The race remains latent — a green streak doesn't clear a scheduling-dependent assertion race, so the gate should expect occasional reds until it's addressed. - ✅ ci(spec): spec-quote drift checking in CI (copy of #357 with reviewer fixes) #376 merged (
3fa8336) — our ci(spec): spec-quote drift checking in CI + vacuous-pass fix + drift repair #357 content carried with reviewer fixes. We'll mark ci(spec): spec-quote drift checking in CI + vacuous-pass fix + drift repair #357 superseded. - ✅ docs(changelog): backfill [Unreleased] for v0.6.0 #373 updated — both gaps from our review addressed (fix: stop injecting test mint in release builds #359 + feat(wgm): vendor IE discovery + calibrated score boost (rebased onto main) #353 entries, stale feat(wgm): discovery logger — structured scan history + CLI summary #312 wording fixed). Verified against main; approval review submitted from our side.
⚠️ ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370 content is still not onmain(verified again today: noactions/cache@v5/ per-target SDK cache lines inbuild-package.ymlonmain). Our Sep-1 flag stands: the §6 pre-tag condition is still half-satisfied. Suggested: cherry-pick/re-merge the 16 lines from3f53827before tag prep. (Per our dry-run, the pipeline builds the full artifact matrix green without it — the cost is runner-minutes and hang-exposure, not correctness.)- ⏳ Tag still not pushed (correct — steps above incomplete).
Release-cut checklist as of now: ① re-land #370's diff → ② merge #373 → ③ race-gate ×2–3 on fresh main (mind the FiredOnce class; also remember the documented
./...command only covers the root module — iterate*/go.mod) → ④ tag.- ✅ test: de-flake race suite for v0.6.0 #374 merged (
- added a commit that references this issue
on Sep 8, 2026 v0.6.0 release notes — draft for review
Prepared by the release-planning worker (kanban
t_8e133184, v0.6.0 step 4/4)
fromCHANGELOG.md[Unreleased]plusgit log v0.5.0..upstream/main
(81 commits, 80 merged PRs). Basev0.5.0(2026-07-03) →upstream/main@
d6e4e0964e147fa6f9f670e1966872f683941304(2026-09-09).Draft only — nothing was tagged, pushed or published. The tag is a
maintainer action. After the notes below: the exact tag commands, the
stray-tag hazard table, and the pre-flight findings from today's live
verification (one of which —#370not being onmain— affects the release
itself).TollGate v0.6.0 (tollgate-wrt) — release notes
Status: DRAFT for maintainer review (not published).
Channel:stable— tagv0.6.0(barevMAJOR.MINOR.PATCH; CI maps it to
c=stableon the NIP-94 events).
Base:v0.5.0(2026-07-03) →upstream/main@
d6e4e0964e147fa6f9f670e1966872f683941304(2026-09-09) — 81 commits, 80
merged PRs.v0.6.0is the first release cut from upstreammainsincev0.5.0.
It is the payment-correctness release: the three blockers that kept a paying
customer from reaching the internet — the Cashu wallet's swap-counter race
that bricked a wallet permanently on a transient mint failure, the missing
NDS session trigger after autopay, and a mint rate-limit answer reported as a
generic payment failure — are all fixed and were verified end-to-end on
physical GL-MT3000 hardware (payment → NDS gate opens → internet access).Around that core: the captive portal no longer proxies through NoDogSplash
(it gets its own uhttpd instance on:2051, with the real client MAC carried
in the request instead of guessed from ARP), a wallet panic no longer takes
the whole daemon and every active session with it, dependencies move off a
personal fork onto the officialOpenTollGate/gonuts-tollgatev0.11.1, and
the CI/release pipeline that produces the artifacts was repaired (it had been
red onmainsince Aug 26) and hardened with trigger hygiene and per-target
SDK caching.At a glance
- Payment path unblocked. NDS session trigger after autopay (Router-to-router autopay implicitly depends on nodogsplash client registration — breaks protocol compatibility #88), mint
429 →mint-rate-limited(No HTTP 429 handling: Cashu payments fail when mint rate-limits (coinos.io) #260), and port 2121 restricted to LAN
interfaces (Architecture: move port 2121 to loopback-only (defense-in-depth) #226) are all merged and E2E-verified on GL-MT3000 hardware at
b528e1d. - Wallet safety. The gonuts swap-counter race that permanently bricked a
wallet is fixed; spending-condition-locked (P2PK/HTLC) tokens are rejected;
payment-goroutine panics are contained and reported as a signed
payment-processing-failednotice instead of killingtollgate-wrt. - Cashu dependency:
gonuts-tollgaterenamedOrigami74→
OpenTollGate(v0.10.0, URL normalisation, DLEQ keyset lookup, NUT-13
big.Int, 429/5xx retry), now at official v0.11.1 with the personal-fork
replacedirective removed. - Captive portal served directly. Dedicated uhttpd on
0.0.0.0:2051; NDS
keeps a sub-1 KB stub page pre-auth. Client MAC travels with the request. - Sessions survive. Quote persistence across restarts, race-free and
fsync-safe, plus a 24 h NDS session ceiling so a purchased session is not
deauthed by NDS's 20-minute default. - Discovery. Vendor-IE decoding from beacon frames plus structured scan
history in/etc/tollgate/discovery_log.jsonland
tollgate-cli upstream known. - CI pipeline fixed and hardened. Red package builds are green again
(Node 20 for the apk portal build, updated buildinfo tests,src/merchant
in the test matrix), the portal is built once per run, and push builds are
restricted tomain+v*with docs-only changes ignored and superseded
runs cancelled. - Security. Exposed deployment backup purged from history (with residual
exposure documented), wildcard CORS removed, backend API LAN-only, 28 CVEs
patched viax/cryptov0.54.0, 1 MB response-body caps, SSRF guards.
What's new
Payments and wallet safety
The swap-counter race in
gonuts-tollgatev0.7.1 was the worst failure mode
in the tree: the keyset counter advanced only after a successful swap, so one
transient mint failure (timeout, DNS hiccup, 5xx) left it stuck, every retry
reused it, the mint answered NUT-02 code 10002 and the wallet was bricked
with no self-recovery. Fixed in v0.7.4 (counter advances before the swap,
blinded messages regenerated on retry); the dependency is now at v0.11.1.tollwallet.Receive()checks each proof's secret for spending conditions
before crediting a user: P2PK and HTLC-locked tokens are rejected with
ErrLockedToken, closing a "free internet with tokens the gateway can never
spend" path found in the Layer 3 cashu audit (fixes
#324).
merchant.Fund()decodes V3 as well as V4 tokens (fixes
#325).
Mint HTTP 429 maps tomint-rate-limitedwith a human-readable message
instead of a generic payment failure
(#346,
#260).A panic inside the wallet layer during
PurchaseSession'sReceivecall
(a mint returning malformed keysets) used to take the whole daemon down with
every active session on it. The goroutine now recovers and pushes an explicit
payment processing panicked: …error into the existing result channel, so
the caller gets a signedpayment-processing-failednotice immediately
(#360).Mint URLs are matched with
tollwallet.MintURLMatches()in
calculateAllotment(), so a trailing slash, upper-case host or normalised
path no longer fails an otherwise valid payment
(#250,
#251).The post-payment NDS session trigger (router-to-router usage tracking)
validates the upstreamGatewayIPbefore its port-80 HTTP GET, rejecting
loopback, link-local and unspecified addresses — a malicious or corrupt
advertisement can no longer coax the downstream into probing the local box
(#347,
#315,
#88).Sessions and quotes
Lightning quotes persist to
quotes.jsonin the wallet directory and reload
on startup, and monitoring is relaunched for unpaid quotes, so a restart no
longer leaves a paying customer staring at "Waiting for payment"
(#248).
Persistence is race-free and crash-safe: records are deep-copied under the
lock instead of shared by pointer, and the temp file isfsynced before the
rename, with five concurrency tests as regression guards
(#269).NoDogSplash's
sessiontimeoutis 86400 (24 h) andauthidletimeout3600, so
NDS's 1200-second default can no longer deauth a client mid-session; the Go
backend remains the sole authority on purchased session lifetime
(#363).Captive portal
Portal serving is decoupled from NoDogSplash.
/etc/nodogsplash/htdocsis no
longer a symlink to the SPA: NDS serves a tiny stub (splash.html) that
JS-redirects to port 2051, with a<noscript>fallback built from the LAN IP
and CIDR suffixes stripped (192.168.1.1/24used to produce a malformed URL
on routers that store the address in CIDR form — found on a GL.iNet MT3000).
The stub preserves the query string, so the?clientmac=…parameter NDS
hands out survives the redirect
(#363).A second uhttpd instance (
config uhttpd portal) serves the SPA on
0.0.0.0:2051/[::]:2051with directory listings disabled and the NDS
users_to_routerallow list extended to 2051 (idempotent). The installer
removes anhtdocsthat exists as a regular file rather than failing
silently (#348).Clients now come with a MAC attached: the backend accepts a
macfield in
Lightning invoice and Cashu payment requests and amacquery parameter for
invoice polling and/whoami, so the splash page passes the MAC it already
knows instead of the backend falling back to ARP/DHCP lookup behind the
router's reverse proxy. Handlers that returned 400/500 when no MAC could be
resolved now log and fall back to00:00:00:00:00:00
(#358).The captive-portal assets are built from source in CI instead of being
committed as minified JS
(#335).Discovery and upstream
The wireless gateway manager decodes the TollGate vendor-specific Information
Element from beacon frames — implemented from the wire-format spec with
round-trip tests and an encoder overflow check — and boosts a discovered AP's
score accordingly; the config schema gains an optionalvendor_ie_discovery
field (#353).Every scan cycle records each discovered AP (BSSID, SSID, signal, radio,
TollGate flag, price/step) to/etc/tollgate/discovery_log.jsonl, log-rotated
and persistent across reboots, with an in-memory registry tracking signal
range, sample count and latest pricing across scans;
tollgate-cli upstream knownprints that view andupstream scanreports
is_tollgate,price_per_stepandstep_sizefor real TollGate APs
(#312).
The NDS enforcement bridge uses portable WAN detection
(#297)
and the fw4/nftables enforcement path was repaired
(#283).Degraded mode
A wallet that fails to initialise no longer parks the router: the merchant
falls back to degraded mode and recovers dynamically, with a matching portal
UI, and a regression test pins the fallback
(#298,
#309).Identity
New
src/identitymodule: NIP-06 12-word BIP39 mnemonic derivation, HKDF
(RFC 5869) attribute derivation for IPv4/MAC/password replacing raw SHA-256,
and a loopback-only/identity/reveal-seedendpoint with a 1 KB body limit.
14 tests (#331,
#321).Operator tooling and docs
scripts/token-recovery/parses/etc/tollgate/tokens-to-recover.txt, checks
each proof's state at the mint via NUT-07/v1/checkstate, and recovers
UNSPENT value through the wallet;-dry-runreports recoverable/spent/pending
counts without touching the wallet — for funds stranded by the gate-side
exit-status-1 class of failures
(#354).docs/operator-guide.mddocuments everytollgateCLI subcommand (service,
wallet, private network, upstream Wi-Fi, config, health) with flags, example
output and troubleshooting
(#188);
man pages cover the command tree
(#187).A Docker-based integration lab (
tests/cloud-lab/) brings up a cdk-mintd
FakeWallet mint, upstream and reseller TollGate containers against a
fake-ndsctl shim, and a client container (cdk-cli + nak + pytest) with
smoke-payment, mint-failure and two-router autopay suites
(#362).Behavior changes worth flagging
- The portal lives on port 2051. NoDogSplash still answers pre-auth on
port 80 with a stub page; the SPA itself is served by uhttpd on 2051. If you
allow-listed or firewalled portal ports, allow 2051. - The backend API on port 2121 is LAN-only. A new nftables include
(30-backend-firewall.nft) blocks the API on non-LAN interfaces. WiFi
clients keep reaching the payment endpoint through the NDS
users_to_routerrules; WAN-side and upstream clients can no longer probe
it (#345,
#226). - CORS no longer echoes a wildcard.
Access-Control-Allow-Origin: *is
gone; the origin is echoed only for local/private origins and for pages the
router itself serves on another port, withVary: Originadded. POSTs with
content types other thantext/plainorapplication/jsonreturn 415
(#349). - NDS timeouts are 24 h / 1 h (session / idle), by design — the Go backend
decides session lifetime. - Setup rerun on upgrade. The setup script's version marker
(SETUP_VERSION) is bumped, so upgrading re-runs the full setup and
installs the stub + portal instance. Existing management-WiFi credentials
are preserved: the private SSID/PSK is reused from
wireless.private_radio0and only generated when none is configured. /tmp/tollgate-setup.logis root-only (0600) — it contains the
generated management-WiFi password.- Rate limiting is configurable via
TOLLGATE_RATE_LIMIT_RPM
(#338). - Config schema version is unchanged (
v0.0.8);vendor_ie_discoveryis
optional and defaults tofalse. Sat amounts are standardised and the
advertisement payload was corrected with an in-place config migration
(#310).
Notable bug fixes
- Payment-goroutine panics no longer kill the daemon
(#360). - The local build script no longer injects the test mint into release builds:
GitBranchwas leftunknown, whichIsDevBuild()treated as a feature
branch, adding a dummy-invoice test mint to every locally built.ipk
(#359). - Splash redirect preserves query parameters, so MAC-based session logic sees
the real client MAC; NoDogSplash no longer overrides purchased session
duration (#363). /usagereturns 200 instead of 500 when there is no session
(#316).- HTTP response bodies are limited to 1 MB across LNURL resolve, invoice
fetch, gateway probes and the usage tracker, preventing OOM on
resource-constrained routers
(#274). - Notice event codes and the advertisement
tipstag match TIP-01/02 instead
of implementation-specific codes and non-existent TIP numbers
(#240). - Wireless config is read gracefully when
/etc/config/wirelessis absent;
the deadfirewall-tollgateinclude (silently rejected by fw4) is gone
along with two Makefile references to it that broke package builds
(#196,
#235). - AP setup recovers on reinstall/upgrade
(#216);
merchant profit-share payouts are owner-first and robust
(#168);
the valve supports a post-payment redirect with configurable auth delay
(#200).
Security
- Exposed deployment backup purged from history. A router deployment
backup committed by accident in fix: accept client MAC from request body/query param #358 (deploy-backup-20260730/) contained
the merchant private identity key, an ecashwallet.dband spendable
recovery tokens.mainwas rewritten on 2026-08-27 and force-pushed;
SECURITY.mdrecords the incident and the residual exposure, and key
rotation is tracked in
#364.
If you ran any build between those dates, treat the affected key material as
compromised. - Wildcard CORS removed, backend API restricted to LAN interfaces
(#349,
#345). - P2PK/HTLC-locked tokens rejected, 1 MB response-body cap
(#330,
#274). - SSRF guards: LNURL callback URL validation
(#198),
TOCTOU-safe validation via a customDialContext
(#245),
upstream gateway IP validation. - RFC 1918 isolation — WiFi clients blocked from upstream private networks
(#234);
UCI key/value validated against control characters
(#222);
config file permissions tightened 0644 → 0600
(#221);
error responses sanitised so internals are not leaked
(#202). - Supply chain:
x/cryptov0.38.0 → v0.54.0 andx/netv0.40.0 → v0.57.0
(28 CVEs patched), plus a dependency-sync drift checker across the 14
go.modfiles (#307,
#223).
Dependencies
gonuts-tollgate: module path renamedOrigami74→OpenTollGateand
bumped to v0.10.0 (URL normalisation, swap-counter race, DLEQ keyset
lookup, NUT-13 big.Int, 429/5xx retry)
(#304),
via v0.8.0 (V4 short keyset ID resolution
(#286))
and v0.9.0 (#292,
go.sum corrected in
#293),
V2 keyset swap crash fix at v0.7.6
(#281).- The personal-fork
replacedirective is gone now that the official
OpenTollGate v0.11.1 tag exists; both modules re-tidied against it
(#361). - Go toolchain:
GO_VERSION1.25 in CI
(#244).
Internal changes (CI, tests)
- Spec-quote drift is checked in CI against current cashubtc/nuts HEAD; one
drifted NUT-03 quote was fixed and a NUT-05 quote added
(#376). src/tollwalletpins NUT-00HashToCurveoutput against the canonical
cross-implementation vectors (gonuts/btcec ↔ cashu-core-lite/k256 ↔
coincurve/Python), because divergence there makes NUT-07 checkstate report
spent proofs as unspent
(#351);
src/tollgate_protocolwent from 0 % to 15 validator tests
(#337);
post-swap-fee crediting is characterised by tests
(#342).- New
src/sysexec/safe-exec wrapper (Runnerinterface, context, timeout,
retry) — foundation for the 37exec.Commandcall sites
(#265);
newsrc/tollwalletWalletPortinterface with aGonutsWalletadapter
(#299). - NUT spec quotes added as greatspectations Layer 1
(#327). - The merchant race suite was de-flaked for this release: two
time.Sleep-instead-of-poll tests on async callbacks became a polling
waitForhelper (test-only); 10/10 modules green, 3 of 3 full
-race -count=1 -tags testenviterations, pre- and post-fix
(#374). [Unreleased]accumulated 10 missing entries so this release's changelog
is complete (#373).src/merchantjoined the test matrix andconfig_managerbuildinfo tests
were updated for the five extra production mints
(#365);
package-apkuses Node 20 instead of the SDK container's Node 12, which had
been failing the portal build on every push since Aug 26
(#366);
the portal is built once per run instead of per matrix leg, removing ~15
builds and per-leg commit drift
(#368);
build triggers are restricted tomain+v*tags with docs-only paths
ignored and redundant runs cancelled
(#369);
NIP-94 1063 events are published directly in the build step
(#230).- The tree is
gofmt-clean (16 files,#355, plus the pre-commit gofmt check
#343), so the documented pre-PR gate prints nothing. - Known gap (see maintainer note below): the
#370SDK build-tree cache
and job timeouts are not onmain— the tag build runs without them. - The Nostr CI workflow was ported to
.ngit/act/workflowsfor ngit CI
(#377).
Known issues
tollgate wallet drain cashucan lose funds
(#375,
open). Reproduced on a GL.iNet MT3000 over SSH without a PTY: the command
printsOperation cancelled.and exits 0 without draining, and--jsoncan
destroy funds on a wallet whose per-mint registry holds a duplicate or stale
entry. This is the CLI path, not the payment path. Check the issue before
draining a wallet that holds funds; if you drain manually, work from the
non-JSON output and verify the mint's balance afterwards.- NoDogSplash pre-auth is still fragile for unmappable clients. Stock NDS
5.0.2 answers HTTP 500 (its own error page) on every request from a client it
cannot map to a MAC (ip neighmiss). The SPA loads from uhttpd regardless,
which is the mitigation; the NDS-side behaviour is upstream and unaddressed. src/cli,src/upstream_detectorandsrc/upstream_session_managerare
still outside the standalone go-test matrix pending the same module rewrite
src/merchantreceived
(#365).- A latent race remains in the merchant test suite.
TestSetOnFirstReachableForDegraded_FiredOnceis a different class from the
de-flaked tests (registration resetshadReachableMint, so the next
proactive check deterministically queues a spuriousgo cb()and the bare
zero-assertion races the detached goroutine). Green runs are the normal case;
the race is latent, not gone — measured on96bf6a2. - The NDS gate-open retry path is still load-bearing.
ndsctl authcan
return exit 1 for an already-authenticated client; the fix treats that as
success, and the token-recovery tool remains the safety net for any residual
exit-status-1 failures.
Verification status (read this before trusting a build)
- Validated on hardware (GL-MT3000): the three payment blockers
(Router-to-router autopay implicitly depends on nodogsplash client registration — breaks protocol compatibility #88 / No HTTP 429 handling: Cashu payments fail when mint rate-limits (coinos.io) #260 / Architecture: move port 2121 to loopback-only (defense-in-depth) #226) end-to-end — payment → NDS gate opens → internet access
confirmed atb528e1d; the captive-portal stub/redirect path and the
no-CIDR LAN IP case; NDS behaviour on stock NDS 5.0.2. - Verified in CI/on the commit to be tagged:
go test -race -count=1 -tags testenv ./...3× over 10/10 modules, pre- and post-de-flake, on the
post-test: de-flake race suite for v0.6.0 #374 tree;gofmt -lclean. - Covered by unit/race tests only: payment-goroutine panic containment,
quote persistence and crash safety, spending-condition rejection, mint error
mapping, the discovery logger, the identity module,sysexecand
WalletPortfoundations, and the CI changes. - Not verified on a router for this release: the version
single-source/VERSIONchange and the setup-rerun behaviour — the
v0.6.0-alpha2preparation is the vehicle for that verification and is still
an open PR at the time of writing. - Pipeline dry-run: the post-ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369 workflow was exercised end-to-end from
db8af35in a publish-gated variant (Blossom → actions artifacts,
publish-metadatadisabled, build steps byte-identical) — full artifact
matrix built. Reported by Amperstrand on
#339.
Upgrade notes
- Back up the wallet directory and
/etc/config/tollgatebefore
upgrading. The deployment backup that leaked in August is a reminder of
what that data is worth. - Package filenames are
tollgate-wrt_v0.6.0_<arch>.ipk/…_<arch>.apk
(the tag name verbatim; UPX variants add a-upx-…suffix before the
extension). .apk(OpenWrt 25.x):apk add --allow-untrusted tollgate-wrt_v0.6.0_<arch>.apk; apk-tools sees the normalised version
0.6.0-r0..ipk(OpenWrt 24.10 and earlier):opkg install tollgate-wrt_v0.6.0_<arch>.ipk; the control-file version isv0.6.0.- Do not reuse
v0.6.0-alpha1or anyv0.7.0-alpha*package. Those tags
are fork-only (except the upstreamv0.6.0-alpha1re-point) and point at
branches that were never merged; the alpha1 GitHub release has 0 assets. - Expect a setup rerun after installing. Existing WiFi/portal
configuration is preserved, including management-WiFi credentials; check
/tmp/tollgate-setup.log(root-only) afterwards. - If you reached the backend API from outside the LAN, that path is closed
(port 2121 is LAN-only now). Use the portal path WiFi clients use, or reach
the router's shell. - Config schema version is unchanged (
v0.0.8) — no manual migration.
Getting v0.6.0
-
Packages: CI builds
.ipkand.apkper architecture on the tag and
uploads each artifact to 5 Blossom mirrors; every artifact is announced as a
NIP-941063event (n=tollgate-wrt,v=v0.6.0,c=stable) on the project
relays. Download from anyurltag and verify the sha256 against the
event'sxtag before installing:nak req -k 1063 -a 5075e61f0b048148b60105c1dd72bbeae1957336ae5824087e52efa374f8416a \ --tag n=tollgate-wrt --tag v=v0.6.0 --limit 50 \ wss://relay.damus.io wss://nos.lol wss://nostr.mom -
From source:
scripts/build-sdk-package.shcross-compiles the binaries
and stages the canonicalpackaging/recipe into the OpenWrt SDK, producing
either format. -
Announcements:
RELEASE-NOTES.mdand thev0.6.0section of
CHANGELOG.mdare the authoritative description of the release; the per-PR
detail lives in the changelog. -
Reporting problems: open an issue at
github.com/OpenTollGate/tollgate-module-basic-go
with the router model, the package version, and/tmp/tollgate-setup.logif
setup is involved. Read the known-issues section first.
Contributors
Thank you to everyone who wrote, reviewed and tested this release:
Amperstrand, felixfelix-bot, and the reviewers and testers on
#339
and the individual PRs.Maintainer tag checklist — v0.6.0
Operator action. Not executed by the preparing agent. Nothing was tagged,
nothing was pushed, no branch was created.SHA to tag:
d6e4e0964e147fa6f9f670e1966872f683941304(upstream/mainHEAD,
2026-09-09). Re-resolve it at tag time —mainmoves.0. Go / no-go
- The tag name is free:
git ls-remote --tags upstream | awk '{print $2}' | grep -x 'refs/tags/v0.6.0'
must print nothing. Verified 2026-09-13: upstream has nov0.6.0
tag (onlyv0.6.0-alpha1, which is re-pointed at414650310b, the
docs(changelog): backfill [Unreleased] for v0.6.0 #373 merge commit — do not reuse or move it). - The release content is on
main: changelog backfill PR docs(changelog): backfill [Unreleased] for v0.6.0 #373 MERGED
(2026-09-08,4146503) and race-suite PR test: de-flake race suite for v0.6.0 #374 MERGED (2026-09-07,
96bf6a2). - PR docs(release): single-source the version and prepare v0.6.0-alpha2 #380 (version single-source) is a hard dependency — see findings.
If it merges first:VERSIONat the repo root must read exactly
v0.6.0, andRELEASE-NOTES.md's title line must containv0.6.0, or
CI's new guard step refuses the tag (scripts/check-version-sync.sh v0.6.0is the local pre-check). If it has not merged: there is no
VERSIONfile and no guard onmain— the tag name alone decides
package_versionandrelease_channel.
1. Exact tag commands
cd ~/repos/tollgate-module-basic-go git fetch upstream --tags --prune SHA=$(git rev-parse upstream/main) # expect d6e4e09… — re-check git log -1 --format='%H %ci %s' "$SHA" # (1) This clone holds a STALE LOCAL tag named v0.6.0 (899321ffc4, 2026-08-27, # NOT on upstream/main — it predates the 2026-08-27 history rewrite). # `git tag v0.6.0 "$SHA"` would fail with "tag already exists", and # `git push upstream v0.6.0` would push that stale commit. Clear it first. git rev-parse v0.6.0^{commit} # expect 899321ffc4… (stale) git tag -d v0.6.0 # local only; touches no remote # (2) Annotated tag on the upstream main commit — never on HEAD, never on the fork. git tag -a v0.6.0 -m "tollgate-wrt v0.6.0" "$SHA" git rev-parse v0.6.0^{commit} # must equal "$SHA" # (3) Push EXACTLY this one tag. Requires maintainer credentials — the # felixfelix-bot token gets 403 on OpenTollGate. git push upstream refs/tags/v0.6.0 # (4) NEVER run `git push upstream --tags`. That single command publishes every # stray tag in the HAZARD table below as an unretractable Nostr release.
Post-push verification (abbreviated from
docs/release-process.md§4):git ls-remote --tags upstream | grep 'refs/tags/v0.6.0' # tag is upstream git rev-parse v0.6.0^{commit} ; git rev-parse upstream/main # must match gh run list --repo OpenTollGate/tollgate-module-basic-go \ --workflow build-package.yml --limit 5 gh release view v0.6.0 --repo OpenTollGate/tollgate-module-basic-go nak req -k 1063 -a 5075e61f0b048148b60105c1dd72bbeae1957336ae5824087e52efa374f8416a \ --tag n=tollgate-wrt --tag v=v0.6.0 --tag c=stable --limit 50 \ wss://relay.damus.io wss://nos.lol wss://nostr.mom
Every architecture in the declared matrix must be present (a partial set reads
as a broken feed);vmust be the tag name verbatim andcmust bestable;
download at least one artifact and check its sha256 against the event'sx
tag before announcing.2. HAZARD — stray tags in local checkouts
The workflow trigger is the glob
tags: ["v*"], not a regex: every tag
starting withvfiresbuild-package.yml. Thec=channel is what the
regex decides (^v[0-9]+\.[0-9]+\.[0-9]+$→stable,-alpha/-beta→
those, everything else →dev) — adevpublish is still a full Blossom
upload plus permanent kind-1063 events. Anyv*tag also keeps the full UPX
compression matrix (14 matrix legs), so a stray push burns a full-size build.Stray local tag Commit On upstream/main?If it were pushed v0.6.0899321ffc4(2026-08-27)no (pre-rewrite tree) WOULD publish pre-rewrite artifacts as c=stablev0.6.1-post-mergeb528e1d734yes c=devpublish of an old commitv0.7.0-alpha97640f763f1no (unmerged fork branch) c=devpublish of an orphan branchv0.7.0-alpha101e70bcac3byes c=devpublish of an old commitv0.6.0-endo-handover946a06915dyes c=devpublish of an old commitv0.6.0-alpha1414650310bexists upstream never re-push or move it Publishing has no draft stage:
publish-metadatasigns and publishes the
NIP-941063events directly withNSEC_HEXtowss://relay.damus.io,
wss://nos.lol,wss://nostr.mom,wss://relay1.orangesync.tech,
wss://relay2.orangesync.tech, after uploading each package to 5 Blossom
mirrors (blossom.primal.net,blossom.psbt.me,
blossom1.orangesync.tech,blossom2.orangesync.tech,drive.cashu.email;
BLOSSOM_MIN_SUCCESS=1). Events are permanent — deletion requests are
best-effort. There is no way to unpublish a mistaken tag push.3. Pre-flight findings (verified live, 2026-09-13)
#370is not onmain(still true today).gh pr view 370says
MERGED, butgit merge-base --is-ancestor 3f53827 upstream/main→ false.
git diff upstream/main 3f53827 -- .github/workflows/build-package.ymlis
exactly 16 added lines: 4 ×timeout-minutes(compile 30, portal 15,
ipk 30, apk 90) + anactions/cache@v5block caching/builder/dl,
/builder/staging_dir,/builder/build_dirper SDK target. None of it is
onmain: the only cache onmainisactions/setup-go's module cache,
and the onlytimeout-minutesispublish-metadata: 15. Cause: ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370's base
was theci/trigger-hygienebranch (ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369), notmain; ci: build-matrix trigger hygiene (main/v* only + concurrency + docs-ignore) #369 landed on
mainas a squash (db8af35) 54 s before ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370's merge, so ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370's merge
went to the already-merged branch. Corroborates Amperstrand's 2026-09-01
flag on this issue.
Release impact: the tag build runs without the SDK-tree cache and job
timeouts, so the apk legs fall back to the 360-minute default; the#370
entry in the[Unreleased]changelog (docs(changelog): backfill [Unreleased] for v0.6.0 #373) — and PR docs(release): single-source the version and prepare v0.6.0-alpha2 #380's
RELEASE-NOTES.md— describe changes that are not onmain. Suggested fix
before the tag: land3f53827's 16 lines onmain(fresh PR cherry-picking
them is enough), then the changelog/notes claims become true.SETUP_VERSIONis still a hand-written literal onmain
(packaging/files/etc/uci-defaults/99-tollgate-setup:SETUP_VERSION="v0.6.2"
— a version that never matched a release). docs(release): single-source the version and prepare v0.6.0-alpha2 #380 replaces it with a
__TOLLGATE_VERSION__placeholder substituted from the packaged version.
Until docs(release): single-source the version and prepare v0.6.0-alpha2 #380 merges, av0.6.0package ships a setup script whose marker says
v0.6.2. Functionally it still triggers the full setup rerun; it just
disagrees with the package version.- PR docs(release): single-source the version and prepare v0.6.0-alpha2 #380 does not contain ci(apk): cache SDK build trees per target + job timeouts (stacked on #369) #370's content either (
git merge-base --is-ancestor 3f53827 fork/release/v0.6.0-alpha2-prep→ false), so the
"SDK build tree is cached and every heavy job has a timeout" bullet in its
RELEASE-NOTES.mdis inaccurate on that branch too. VERSIONdoes not exist onmain— it is added by docs(release): single-source the version and prepare v0.6.0-alpha2 #380. A tag cut
before docs(release): single-source the version and prepare v0.6.0-alpha2 #380 merges has no version-vs-tag guard; a tag cut after must match
VERSIONbyte-for-byte.- Board bookkeeping: the
RACE-TEST-V060card is still in the review lane
on the board, but its deliverable PR test: de-flake race suite for v0.6.0 #374 is merged onmain(96bf6a2) —
the release content is present and the card can be closed.
- Payment path unblocked. NDS session trigger after autopay (Router-to-router autopay implicitly depends on nodogsplash client registration — breaks protocol compatibility #88), mint
- added a commit that references this issue
on Sep 13, 2026 33 remaining items
- added 2 commits that reference this issue
on Sep 22, 2026 v0.6.0-alpha4 is CUT — tag pushed and verified; only DQ05 remains
- chore(release): prepare v0.6.0-alpha4 #469 merged (12:31:53Z,
fd582973 chore(release): prepare v0.6.0-alpha4) — carrying the user's midday rebase onto the post-chore(deps): bump gonuts-tollgate v0.11.2 → v0.12.0 (melt/swap-retry counter reservation + #480 landing) #506/chore(deps): tidy src/cli's indirect gonuts pin to v0.12.1 #515 main, with the merged-mainmake go-battery16/16-green verification from the comment above. - Annotated tag
v0.6.0-alpha4pushed to upstream from this side (same GitHub-push path as alpha3): tag object881478bb→ commitfd582973== chore(release): prepare v0.6.0-alpha4 #469's merge commit, ancestor of main,VERSION== tag name, name previously unused — every runbook §3/§4 check green.
Remaining is DQ05-only now (per the sequence above, minus the tag which is done): mirror push —
mainplus thev0.6.0-alpha4tag, skipping the never-publishedalpha1/2/3— thenscripts/ngit-ci-release.sh v0.6.0-alpha4 alpha fd5829737745e5da0f3adca256b40a8d12d65f9e. The publication audit follows from this side once the announcements exist (or their verified absence, per the #445 gate).- chore(release): prepare v0.6.0-alpha4 #469 merged (12:31:53Z,
Third release blocker diagnosed — and the v0.6.0-alpha4 tag is stale against the current release definition
1. The node-pin defect (deterministic stage-1 red). The 13:05:32Z stage-1 failure on
427c8e38(ERROR: node v20.20.2 != pinned v22.17.0) is not a flake: the lane's build-portal job hardcodesnode-version: '20'whilepackaging/build-inputs.jsonpins 22.17.0. That commit is an ancestor of the taggedfd582973— any drive at the current tag fails stage 1 deterministically.2. #477 is the fix and is now land-ready. It conflicted on current main; I rebased it (
230095e6, force-pushed, conflicts resolved keeping main's PORTAL_REF comment + folding the changelog entry into[Unreleased]): node pinned to 22.17.0, an npm-vs-manifest verification step added, YAML validated. It needs one approving review — requested.3. The tag is stale against the release definition. Main now carries the team's updated
[v0.6.0-alpha4] - 2026-09-22section (the packaging-pre-release framing from #513's lane) and re-dated RELEASE-NOTES — but myv0.6.0-alpha4tag (pushed 13:06Z onfd582973) predates the #513/#478/#491 wave and carries the node defect. Driving at it would publish artifacts that match neither the changelog nor a green pipeline. Before DQ05 acts, the tag must be resolved: either delete the never-published tag and re-tag the final release commit (it was never mirrored, never announced — zero consumers), or keep the name and cut alpha5. Same decision class as alpha3→alpha4; team's call.Sequence from here: #477 review+merge → tag resolution (re-tag on the final commit) → DQ05 (mirror push + drive) → publication audit.
Track-2 completion (2026-09-22 afternoon)
Merged (window, ruleset restored + verified): #491 (epoch token-emit — the lane's last gating defect fix) and #469 (alpha4 prep, post-#491 rebase).
mainnow carriesVERSION=v0.6.0-alpha4+ the #480 fix + the epoch fix. The release now waits on DQ05 alone (mirror push → tag → drive); after publish we'll run the read-only gate verification against the events.Verification coverage now spans every open PR. This round: #513 (request-changes — live QEMU validation of pre-auth :443 green end-to-end, but a real idempotence defect found on-router: the duplicate-check regex can't match uci's quoted single-line list rendering → one duplicate rule per rerun; plus mandatory rebase — main already shipped a different alpha4) and the four light PRs (#492 land-with-followups, #508 land, #493 land-with-followups — its audit-gap flags note #507/#511 as the open invariants, #512 land — full lane green on a
--no-cachebuild after catching a cached-image silent-foreign-binary trap).Fund-safety pair flipped to land-ready: #507 and #511 — cli tidy landed (#515), CHANGELOG entries added at their branch heads, both verification comments updated to land-with-followups.
Correction to "waits on DQ05 alone": #477 must merge first — the node-pin defect is still in the main tip
Verified just now (13:4xZ):
upstream/main(d622688b) still carriesnode-version: '20'in.ngit/act/workflows/build-package-binaries.yml(line 333) against the manifest's 22.17.0 pin — the defect behind the live 13:05:32Z stage-1 failure (ERROR: node v20.20.2 != pinned v22.17.0). A DQ05 drive at any current commit fails stage 1 deterministically.#477 is the fix and is ready: rebased onto the current tip (
230095e6, MERGEABLE), node pinned to 22.17.0 + an npm-vs-manifest verification step. It is Amperstrand-authored and awaits one approving review (c03rad0r requested).Corrected sequence: #477 review+merge → tag resolution (per the comment above — the current
v0.6.0-alpha4tag onfd582973predates the wave and carries this same defect) → then DQ05 (mirror push → tag → drive) → publication audit. The epoch fix (#491) was the last shard-generator defect; this is the last stage-1 one.Third blocker FIXED — the final fold is up as PR #523; DQ05 sequence corrected
- ci(ngit): build the stage-1 portal from the pinned toolchain, not floating main/node #477 merged (16:06:05Z) — verified on main:
node-version: '22.17.0'at the lane's build-portal step with the npm verification. The third and last stage-1 blocker is gone. - PR chore(release): fold the post-#477 wave into v0.6.0-alpha4 #523 (c03rad0r review requested): the release-content fold — the five
[Unreleased]entries accumulated since the alpha4 redefinition are folded into[v0.6.0-alpha4](24 entries): the v4cashuBshort-keyset token fix (fix(packaging): repin the captive-portal bundle to the keyset-agnostic decode + guard the shipped bytes #517's repin tod699367— coinos/minibits customers could not pay), ci(ngit): build the stage-1 portal from the pinned toolchain, not floating main/node #477's toolchain pin, fix(packaging): repin the captive-portal bundle to the keyset-agnostic decode + guard the shipped bytes #517's bundle regeneration + mint guard, and ci(ngit): token-emit the package-job SOURCE_DATE_EPOCH expression #491's shard epoch fix. RELEASE-NOTES gains the v4 fix bullet.check-version-syncgreen.
The corrected DQ05 sequence (one review stands between here and it):
- chore(release): fold the post-#477 wave into v0.6.0-alpha4 #523 review + merge
- Delete the stale
v0.6.0-alpha4tag (fd582973— never mirrored, never announced, predates the wave, carries the node defect) - Annotated tag
v0.6.0-alpha4on chore(release): fold the post-#477 wave into v0.6.0-alpha4 #523's merge commit - Mirror push (
main+ the tag, skipalpha1/2/3) +ngit-ci-release.shdrive - Publication audit (both publisher keys, ≥2 mirrors, sha256 vs
x)
- ci(ngit): build the stage-1 portal from the pinned toolchain, not floating main/node #477 merged (16:06:05Z) — verified on main:
- added a commit that references this issue
on Sep 22, 2026 v0.6.0-alpha4 is CUT on the final content — only DQ05 remains
- chore(release): fold the post-#477 wave into v0.6.0-alpha4 #523 merged (16:16:16Z,
eaf5e292) — the release-content fold is on main: the 24-entry[v0.6.0-alpha4]section (including the v4cashuBshort-keyset token fix), the notes bullet,check-version-syncgreen. - The stale tag is resolved: the never-mirrored, never-announced
v0.6.0-alpha4tag (881478bb→fd582973, which carried the node-pin defect and predated the wave) was deleted, and the annotated tag re-cut on chore(release): fold the post-#477 wave into v0.6.0-alpha4 #523's merge commit — verified: tag object6737ddb9→ commiteaf5e2928c585422b49e0bd0c88d36f31a47da6b== the fold's merge commit, ancestor of main,VERSION== tag name.
The remaining sequence is DQ05-only (maintainer key):
- Mirror push:
main(noweaf5e292) + thev0.6.0-alpha4tag — skipalpha1/2/3 - Drive:
scripts/ngit-ci-release.sh v0.6.0-alpha4 alpha eaf5e2928c585422b49e0bd0c88d36f31a47da6b - The publication audit follows from this side (both publisher keys, ≥2 mirrors, sha256 vs
xtags)
- chore(release): fold the post-#477 wave into v0.6.0-alpha4 #523 merged (16:16:16Z,
Feed note (pre-empting confusion): the 16:18:34Z stage-1 failure on
dbd0777cis the node-pin defect's last gasp, not a fourth blocker —dbd0777cis #518's merge, which predates #477's (22edca2e, 16:06:05Z); its tree still carriednode-version: '20'. The taggedeaf5e292descends from #477's fix, so the first post-#477 stage-1 run — which will be the DQ05 drive itself — should be green for the first time. Expect red pre-#477 backlog runs (bf4ca69d,dbd0777c) until then; they are history, not signal.Final pre-alpha4 merge window — complete (2026-09-22 evening)
15 PRs merged across three window passes (ruleset lifted/restored and verified after each):
The window set: #518 (#516 uci dedupe fix — the accumulation disease, with live 27×2051/4×443 router evidence), #507 (pending-proof reclaimer), #486 (mint re-admission), #512 (outage-refusal lane), #492 (schema-defaults parity), #485 (lab hygiene + compose layering), #484 (two-router skip), #476 (external-mint lane), #470 (placeholder guard), #474 (UPX pin), #477 (portal toolchain pin), #479 (T16 docs), #493 (fund-safety invariants doc), #508 (CONTRIBUTING nym/brand docs) — plus #517 (portal repin, maintainer-merged mid-window).
The one straggler: #511 (late-Receive reconciliation) — GitHub's mergeability cache has reported CONFLICTING for hours while
git merge-treeproves the tree CLEAN at every pushed head, including after an empty-commit recomputation trigger. Content is conflict-free and race-proven; note posted on its thread with the workaround.mainnow carries everything alpha4 should be: the #480 restart-brick fix (library v0.12.1), the epoch token-emit, pre-auth :443, the uci dedupe fix, the reclaimer, late-mint admission, aggressive downgrade recovery, route-metric fix, prepay defaults, the full lab/test hardening set, and the finalized[v0.6.0-alpha4]changelog.VERSION = v0.6.0-alpha4.The human sequence is exactly three steps from a published alpha: (1) resolve #511 via UI if desired (optional — it can ride alpha5), (2) tag
v0.6.0-alpha4at the merge commit, (3) DQ05: mirror push +scripts/ngit-ci-release.sh v0.6.0-alpha4 alpha <sha>. On publish, we run the read-only gate verification against the events.Remaining open: #511 (cache-stuck, clean), #524/#522/#519 (new from the concurrent session — unverified yet), #487/#488 (fork-blocked rebases), #425/#391 (post-release track).
Milestone: stage 1 is GREEN on #477's merge — the fix is proven on the runner
The coordinator's backlog reached
22edca2e(#477's merge commit) andbuild-package-binaries.ymlsucceeded (17:03:47Z) — the first green stage-1 run, confirming the node-pin fix works end-to-end on the actual runner (go-testgreen there too). When the DQ05 drive runs at the taggedeaf5e292(a descendant), stage 1 passes.The
repro-check.ymlred on the same commit (17:09:08) is a lane flake, not a regression — evidence: the error isSOURCE_DATE_EPOCH not set and no git history(the act-workspace epoch starvation), the same error appears on unrelated commits (d622688 13:53Z), and the same commit's earlier run resolved the epoch fine (d622688 13:48:56:SOURCE_DATE_EPOCH=1790081077 ✅). The resolve step is nondeterministic on this runner; the release pipeline does not depend on the repro-check lane — stage 1 → shards → announce is the gate.DQ05 remains the only gate: mirror push (
main@eaf5e292+ thev0.6.0-alpha4tag) → drive → audit.Lane status triage (2026-09-22 ~19:00 UTC), so the reds aren't mistaken for code:
go-test.yml— green on every push.repro-check.yml— red on every push; fixed by open ci(repro): resolve SOURCE_DATE_EPOCH before the SDK Go audit #519 (verified independently: the fix's green path replicated locally, the failure live on current main; changelog bullet added on the branch at 885b9e3). Merge clears it on the next push.test.yml— red at 18:00 / 18:33 / 18:54, infrastructure, not code: the captured signature isproxy.golang.org/golang.org/x/time/@v/v0.15.0.zip: net/http: TLS handshake timeout → FAIL [setup failed]in themain-testjob — a cold module-cache download hitting flaky runner egress. Contrast:go-test.ymlruns the same root-module graph green with a cache hit. Control from a dev host: coldGOMODCACHEdownload of the exact module +go test -count=1 -tags testenv .both green on the same tree. A re-run (ideally until the setup-go cache warms) should clear it; nothing to fix in-tree. If the flakiness recurs, the cheap hardening is enabling the setup-go module cache onmain-testlike the other jobs.- Release drive: 0 tag-scoped runs, 0 kind-1063 artifacts for v0.6.0-alpha4 — the pipeline has not been driven yet. Tag currently at eaf5e29.
Sequence to shipping alpha4 stands: merge #519 → next push turns repro-check green (and test.yml on a lucky/cache-warm run) → drive the lane with the operator key →
verify_publication.sh v0.6.0-alpha4 alpha -closes the audit.While-you-were-waiting verification wave #3 (2026-09-22 evening) — all 8 new PRs covered
Every PR from the concurrent session's latest batch now has an evidence pass:
PR Disposition Key finding #519 repro-check epoch fix land-with-followups failure mechanism proven (ngit checkouts have NO .git— epoch guard dies); fix verified both ways + full live SDK audit passes at head (SDK Go pins match the live OpenWrt feeds); sole gap: missing CHANGELOG bullet. This un-breaks a main CI lane red since #448.#532 fee-precheck bound request-changes mechanism sound (3s wall-clock bound), composes with #447; CHANGELOG links wrong PR ( 529→532)#531 SSID privacy (replaces #487) request-changes gate byte-identical to #487's verified content, deployed SSIDs preserved (no churn); rebase-artifact duplicate heading #530 rootfs test tier land-with-followups tier works exactly as claimed (134/0) — but a planted #516-class regression still passes: zero nodogsplash allow-list assertions (the exact class the tier exists for); add the stability assertion #528 fleet credentials (fixes #509) request-changes fail-closed gate verified on the real pytest surface; but the same defaults survive in three sibling files + standalone flash_routers.pybypasses the gate; BLOSSOM_URL env-only, no sha256 pin#522 mint-matrix lane request-changes one-char regression found live ( ${pair%%=}→%%=*; as-committed, every matrix 6-errors); with the fix, 6/6 green locally + FUND-FAIL skip + no-matrix gate proven#524 SSID contract (replaces #489) request-changes full contract verified (all four drift classes red, wiring exercised); one repo-rule fix: CHANGELOG entry inside the tagged alpha4 section Cross-cutting: the replacement trio (#531/#529/#524) successfully routes around the fork workflow-scope block — the queue is fully in-repo again. #529 (epoch stamp, replaces #488) is MERGEABLE and its predecessor was verified land — recommend the same treatment after a freshness pass.
Release unchanged: alpha4 events still zero, mirror still stale — the DQ05 tag+drive remains the only step. All wave-#3 blockers above are one-liners except #522's char fix and #530's missing assertion.
v0.6 release-content validation — evidence dossier + state (from the quality campaign session)
Content validated: main
2226a04c(= #553 merged; tagv0.6.0-alpha4+ 13 commits). Full campaign, all tiers green:make go-battery16/16 modules (gofmt/vet/build/race) · schema-lint 64 entries PASS · build-purity 3/0- Offline uci-defaults: 96/0 (band 22, nds-443 19, private-subnet 12, same-version-allowlist 43)
- Reproducibility: both binaries byte-identical across two clean roots (caveat: system Go 1.25.8 that run; the .ipk itself built on the pinned 1.26.8)
- Happy-path (test(happy-path): add a published-artifact happy-path regression suite #544) vs locally built .ipk: 23/23, zero fail, zero known — after fix(ln): canonicalise the client-supplied mint URL so the Lightning lane can sell time #553 closed the Lightning-lane issue (was 22/23 known=1). A/B evidence: 400
failed to create lightning invoice→ 200 + quote issued. On fix(ln): canonicalise the client-supplied mint URL so the Lightning lane can sell time #553. - Cloud-lab e2e: all 8 lanes green (rejection-safety, renewal 14/0, smoke 6/6, mint-failure, swap-fees 5/5, two-router 3/3, keyset-rotation, external-mints 3/3 live)
- Packaging: artifact-contents PASS, portal-bundle-contract OK, shellcheck error-level clean
Release decision state: fold on current main → **
v0.6.0-alpha5\** (Option C — fresh version, no second tag deletion). Trigger: #546 stalled approved-but-conflicting; shipped-bytes content at2226a04` is complete regardless (#554/#556 change zero runtime bytes). Fold PR awaits a go + the review batch landing.Queue state: #554/#556 mergeable awaiting review; my five previously-conflicting PRs (#522 #524 #530 #531 #535) rebased onto
2226a04c, each re-validated on its new base (rootfs tier 470/0), all mergeable. #546 awaits felix's rebase (note on the PR). DQ05 still the publication blocker — mirror frozen, zero kind-1063.Minor finds: (1)
scripts/__pycache__/ngit-gen-shards.cpython-312.pycis tracked on main — one-line cleanup candidate. (2)local-build-ipk.shrequiredmake portal-buildfirst or it silently shipped a JS-less portal — guard + test now in #556. (3) Happy-path suite's known-issues entry for the Lightning lane was deleted by #553 itself — suite now hard-fails if it regresses.
v0.5.0 was released Jul 3. Main is now 45+ commits ahead with critical payment fixes:
Requesting a v0.6.0 release tag so downstream tools (net4sats wizard) can pull a known-good binary from upstream rather than forks.