Skip to content

v0.6.0 pre-tag readiness — test plan, approved-idle PRs, and owed decisions #665

Description

@Amperstrand

v0.6.0 pre-tag readiness — test plan, improvements, and owed feedback

Consolidated from the full open-issue/PR sweep (2026-10-08). Baseline: final-main Go battery 16/16 modules green (release lane), train merged, rc1 not yet tagged.

Owner decisions owed before the tag

  1. feat(setup): the gateway serves NTP pre-auth — time before payment (#627) #648 freeze contradiction — changelog entry sits under [v0.6.0-rc1] while the release lane holds the PR out of the RC. In → lane re-verifies the RC gate with it; out → move the entry to [Unreleased].
  2. fix(merchant): close a NoDogSplash authorisation this module never made, on the usage sweep #619 — last note says "deferred past rc1, maintainer decision needed". Confirm the deferral so the review queue reflects it.
  3. feat(upstream): cold-start reseller bootstrap — forward the first proof whole (#239 phase 2a) #629 / feat(hostmode): ndsctl shim CLI surface + nftables backend (LINUX-HOST-5 + LINUX-HOST-6) #630 — feature PRs (cold-start reseller bootstrap; hostmode ndsctl shim + nftables). Neither is rc1-scoped by the freeze logic; both need explicit park-to-next-cycle calls.
  4. docs(wallet): derive NUT-07/NUT-09 for any bearer instrument (research + runnable demo) #631 nit before merge — its changelog entry carries a "(PR pending — link filled in when upstream PR opens)" placeholder.

Approved-and-idle — merging these strengthens the RC

Test plan before release

RC acceptance (per the release lane's own gate):

Conformance, reproducibly: pytest --tip TIP-01 --tip TIP-02 on PRTA main — the story/TIP selection machinery (PRTA#20) turns the "green conformance re-run" into a defined command.

Live battery: the commissioned read-only API run (PRTA#12) when the bench link is back.

Issue-driven regression tests worth adding before the tag:

Improvements addressable now

Explicitly NOT pre-release scope

#503 (Go leg of the conformance matrix — new infra), #504/#505 (P2 audits), upstream TIP-02 min_steps resolution (OpenTollGate/tollgate#20), #550/#521 CI proposals.

Activity

  1. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    rc1 tag-readiness summary — what the owner's word triggers

    Status: READY. The internal test baseline is pinned at 09ea3af51e52 (re-pinned after #700; RELEASE-NOTES records the pin and its policy). Full gate on that exact tree: every leg PASS, conformance fast subset 5/5 for the first time (READY FOR HARDWARE: YES).

    The gate evidence (make release-check VERSION=v0.6.0-rc1 on 09ea3af)

    Leg Verdict
    Go battery (16 modules, gofmt/vet/build/-race) PASS
    Dependency/import consistency PASS
    Contract (schema lint + build purity + doc facts) PASS
    Packaging (24 shell suites + release pipeline) PASS
    Concurrent duplicate invariant (#639) PASS
    Ambiguous swap output-reuse invariant (#640) PASS
    Payment/service-or-recovery invariant (#403 + #502) PASS
    Conformance fast subset — all five scenarios PASS (duplicate-concurrent, duplicate-sequential, mint-alias, kill-boundary, swap-timeout — the last two green for the first time via #700)
    Release matrix PASS
    Reproducibility (binaries x86_64 byte-identical) PASS
    Version consistency PASS

    What the owner's word triggers (docs/release-process.md, exactly)

    1. Tag v0.6.0-rc1 — annotated tag on upstream main at the pinned commit 09ea3af5… (post-docs(release): re-pin the internal test baseline — 09ea3af5 (the #502 convergence included) #701 docs commits are pin-neutral; tag the tree the gates ran on). Never the fork, never a reused name.
    2. ngit stage 1 (build-package-binaries.yml at the tag) → stage 2 (the eleven budgeted shards via scripts/ngit-ci-release.sh v0.6.0-rc1 rc <commit>) → announce (build-package-announce.yml — fires only when every shard succeeded).
    3. Publication gate: scripts/verify_publication.sh v0.6.0-rc1 rc — every declared (arch, format) announced on ≥2 mirrors with matching sha256; non-zero exit = the version did not reach the channel.
    4. Tester intake: open the pinned issue per docs/tester-intake.md §1 Appendix A, linked beside the rc-tester guide, as part of the announcement.

    Known state at tag time (honest list)

  2. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Pre-release master update — 2026-10-10 (supersedes the checklist above)

    State

    rc1 freeze merged (#659); gate READY (internal battery green on re-pin 09ea3af5, #701). Docs set complete (#667, #631 ✓ merged). Approvals: #681/#682/#687 have felix tagged for cross-identity approval (owner delegation). The only code-side blockers are those three merges + the tag.

    NOW (pre-tag)

    1. Merge pending approvals: deps: security sweep — clears all 18 dependabot alerts (x/crypto critical chain) #681 (dep sweep — clears the 7 criticals), fix(discovery): recognize both brands' SSIDs so Net4sats can be an upstream — rebase of #618 #682 (dual-brand SSID), ci(toolchain): two paths, one truth — manifest-driven parity gates for the SDK and shortcut build paths #687 (toolchain parity gates). Then re-run release-check.sh once on the merged tip.
    2. Tag v0.6.0-rc1 + ngit stage1→stage2→announce + verify_publication.sh (owner word; lane-staged).
    3. Testing — how:
      • PRTA (automated): pytest --tip TIP-01 --tip TIP-02 (conformance selection, now on main); pytest tests/unit (baseline green, 2 known environmental fails); the commissioned read-only API battery when the bench link returns (/tmp/opencode/prta-live-runbook.md, watcher armed); make story-report for the coverage view.
      • Manual (bench, one session clears four queues): USB-eth replug → the live commissioned run fires; upgrade 0.5.x → rc1 AND rollback both lanes (watch fork-Add lightning support, balance page, and local SDK packaging helper #93's preinst jq — fix-or-accept first); hardware acceptance per RELEASE-NOTES (x1860/MT3000); PRTA#7 credential rotation (runbook posted).
    4. Pre-tag hygiene done: secrets estate (hooks + CI backstop org-wide, dead-config class fixed), deps (fork + upstream swept), parity gates (ci(toolchain): two paths, one truth — manifest-driven parity gates for the SDK and shortcut build paths #687).

    POST-RELEASE (0.6.x/0.7.0 — do not gate the tag on these)

    Bottom line: three approval clicks + one re-run + your word = the tag.

  3. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Release-readiness pointer: #703 (fund-safety program — deliberately not a blocker) carries a pre-release decision list distilled from the full incident map, relevant to this issue's plan:

    1. ship feat(cli): wallet recover — NUT-07 liveness check for journaled drain tokens #549 (read-only, battery-green);
    2. pin audit — verify the pinned gonuts tag carries the Priority: P0 — newly discovered fund-safety bug in the melt path of the pinned gonuts-tollgate v0.11.2. Wallet-level fix belongs in OpenTollGate/gonuts-tollgate; this issue tracks it from the TollGate side. #494/Priority: P0 — gonuts-tollgate v0.11.2 counter desync after any 10002-retry; weakens the #266 fix. Wallet-level fix in gonuts; tracked here for the pin bump. #495/Priority: P0 — the #480 fix exists on a branch but is NOT in the pinned dependency; production remains vulnerable, and the class needs a full writer audit, not just the merge. #496-class fixes and the wallet: swap-timeout retry re-exposes derivation outputs (measured by the #535 conformance lane on current main) — the #257/#266/#480 brick class #640 fix, and re-run the test(cloud-lab): conformance fast-subset lane over the PRTA fault proxy (#503) #535 conformance fast-subset lane on the RC, attaching evidence here;
    3. Priority: P1 — stuck-proof hygiene: reclaim machinery exists but is never invoked by the daemon. #500 boot-time reconciliation as a pre-release candidate only (include iff the conformance lane is green on it);
    4. Priority: P0 — research-first: the swap crash window can silently destroy received value; the correct fix shape (pre-persisted swap intents) must be designed, not improvised. #497 explicitly not rushed pre-tag — its P0 stands, but the saga-shaped fix must be designed, not improvised under a deadline (that is how the half-fixes in Priority: P1 — fund-safety program: map every incident class against CDK's saga model; land safe wins pre-release, deep-dive after (not a release blocker) #703's Class A table were born);
    5. an informational zero-code NUT-13 derivation read-audit (Conduition disclosure vs gonuts) — surfaces as its own P0 only if it finds something live.
  4. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Hardware acceptance — first evidence in

    The x86-64 VM leg is green (PR #704, lane tests/vm-campaign/): pristine OpenWrt 25.12.0 x86-64 on the ai-legion QEMU lane, artifact tollgate-wrt-0.6.0_rc1-r0.apk (sha256 d1b1ba5e…, local pinned-SDK build) — 10/10 software-exercisable checks PASS: install with dependency closure, post-install convergence (hostname tollgate-5762, network restart), tollgate version = v0.6.0-rc1 with stamped build time, payment API :2121 listening, config+wallet.db+portal written, NoDogSplash on 2050, NTP pre-auth rule live, same-version reinstall idempotent.

    Still open on the acceptance list: reboot persistence (needs the persistent-overlay VM variant — snapshot=on discards by design), the payment-flow scenarios against the lab mint (real payment, second payment, concurrent duplicate, usage exhaustion, gate-failure/recovery, kill-mid-payment recovery — the #502 journal's first on-target run), and the mipsel router confirmation (x1860 seat decision).

    #682 merged (battery-verified green earlier today; formal review — author is felixfelix-bot, no self-review conflict). #618/#620 closed as superseded.

  5. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Pre-tag pin audit: GREEN — and one NUT-13 finding filed as #705 (not a blocker). Evidence from gonuts-tollgate v0.13.0 (the pinned tag) — tag-diff v0.11.2...v0.13.0 plus source verification:

    Incident class Fix in v0.13.0 Source evidence
    #480/#496 — restart replays swap outputs via alias buckets fix(storage): canonical mint-URL bucket keys + one-time alias merge + monotonic counter guard normalizeMintURL in bolt.go/client.go; wallet_writer_audit_test.go TestAddMintRewriteNeverRewindsCounter names #480/#496
    #495 — counter trails the 10002-retry range fix(wallet): reserve the swapWithRetry range before re-exposure wallet.go:906 — IncrementKeysetCounter(retryReq.keyset.Id, len(outputs)) before the retry POST, with a comment describing the exact #495 failure mode
    #494 — melt blank outputs exposed before increment fix(wallet): reserve melt blank-output range before PostMeltBolt11 wallet.go:1129 increments before the melt request is built/sent at :1133
    #640 — ambiguous swap retried blindly fix(client): a state-changing POST whose answer never arrived is never re-sent (#35) client.go:64-83 AmbiguousOutcomeError — reconcile via NUT-07/quote state first, never re-send the same body; checkstate exempted (httpPostReadOnly) because a transport error there is unambiguous
    bonus — #500 sweeper concurrency fix(wallet): serialize ReclaimUnspentProofs' swap against concurrent wallet operations mutex across derive→increment→swap

    NUT-13 read-audit (informational item from #703): the derivation is the disclosure-vulnerable BIP32 shape (m/129372'/0'/{id mod 2^31-1}'/{counter}'/{0,1}, nut13.go:24-116), and the repo's own short-term mitigation CheckCollidingKeysets (nut13.go:119) is dead code — zero call sites. Exposure is gated by our operator-curated mints and no auto-transfer, so moderate, not a release blocker; filed as #705 (wire the guard into every keyset-registration writer, plus ID verification; HMAC derivation deliberately deferred — it invalidates existing determinism). This also closes the ~8 s dead-mint curiosity from the #549 evaluation: checkstate intentionally rides the retry ladder as the read-only exception.

    Still open for the tag: the #535 conformance fast-subset lane re-run on the RC (needs the cloud-lab) — the one measured-evidence item this audit couldn't produce from source alone.

  6. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Session update: mipsel artifact + VM payment-scenario findings

    mipsel artifact built: tollgate-wrt-0.6.0_rc1-r0.apk (mipsel_24kc, pinned SDK ramips-mt7621-25.12.0) — sha256 7a98f2f9d5943c2bbe4b729f75e7c3028b489e08900c3c42ad662632d2b5b84e. Ready for the x1860 lane the moment a seat frees.

    VM payment scenarios — blocked, root-caused, documented. The three on-target payment scenarios (real payment, concurrent duplicate, kill-recovery) require a client the NDS pre-flight recognizes. On a QEMU VM without wireless, ndsctl auth returns empty (no wireless client), and the pre-flight refuses with client-not-registered — correct behavior on a real router, but a structural blocker for VM-only payment testing. The payment scenarios belong on the mipsel router lane, which is the actual supported-hardware representative. The VM lane's proven value is the install/upgrade/reinstall/idempotence matrix — all green (10/10, PR #704).

    The full debugging record (12 infrastructure issues solved and documented: QEMU memory, serial socket, console quoting, dual-NIC, HTTP port conflict, dropbear password, cdk-cli wallet mounting, the identity-resolution chain) is in tests/vm-campaign/SESSION-NOTES-2026-10-07.md — the next run starts from a known-good recipe.

  7. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Final rc1 artifact matrix + VM campaign verdict

    Artifact matrix — 10 of 12 rows green (bcm2709/arm_cortex-a7+neon-vfpv4 pending script fix)

    All built from the pinned two-era SDK chain (25.12.0/apk + 24.10.8/ipk, go1.26.8, portal bundles staged):

    Target Arch Format sha256 (first 16) Status
    x86-64 x86_64 apk d1b1ba5e9d87914e ✅
    x86-64 x86_64 ipk a29a98779970020b ✅
    mediatek-filogic aarch64_cortex-a53 apk 2eb9580f9cc6a1f7 ✅
    mediatek-filogic aarch64_cortex-a53 ipk 4bc3edb977d42010 ✅
    ramips-mt7621 mipsel_24kc apk 7a98f2f9d5943c2b ✅
    ramips-mt7621 mipsel_24kc ipk 5437f45a0cda1767 ✅
    ath79-generic mips_24kc apk 292e4167f0c9c982 ✅
    ath79-generic mips_24kc ipk 17ffa8addfb11073 ✅
    bcm27xx-bcm2711 aarch64_cortex-a72 apk 51f31f2642288b8 ✅
    bcm27xx-bcm2711 aarch64_cortex-a72 ipk 02ed1ea147ae3b46 ✅
    bcm27xx-bcm2709 arm_cortex-a7+neon-vfpv4 apk — ⚠️ build compiles, copy fails (arch-name mismatch in build-sdk-package.sh: arm_cortex-a7 vs the SDK's arm_cortex-a7_neon-vfpv4 directory)
    bcm27xx-bcm2709 arm_cortex-a7+neon-vfpv4 ipk — ⚠️ same

    The bcm2709 gap is a packaging-script fix (the EXPECTED_ARCH for bcm2709 must be arm_cortex-a7_neon-vfpv4), not a code or platform issue — the Go binary cross-compiles cleanly for GOARCH=arm GOARM=7.

    VM campaign — 13/13 checks green (definitive run)

    Check Result
    VM boots OpenWrt 25.12.0 ✅
    Artifacts staged (8 packages) ✅
    Fresh install (204 pkgs, 41.4 MiB) ✅
    Payment API responds (kind-10021 advertisement) ✅
    tollgate version → v0.6.0-rc1 ✅
    /etc/tollgate/config.json written ✅
    Portal files on /www/tollgate/ ✅
    NoDogSplash running ✅
    NTP pre-auth nft rule (udp/123) ✅
    CLI socket present ✅
    Same-version reinstall RC=0 ✅
    Service running after reinstall ✅
    Hostname minted (tollgate-XXXX) ✅

    Payment scenarios: structurally belong on the mipsel router lane (the NDS pre-flight requires a portal-connected wireless client, which a QEMU VM cannot provide). Documented in the session notes.

    Full gate (unchanged from the last report)

    make release-check VERSION=v0.6.0-rc1 on the pinned baseline 09ea3af5: every leg PASS, conformance 5/5, READY FOR HARDWARE: YES.

    Summary for the tag decision

  8. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Conformance lane re-run on the RC candidate: GREEN across the fast subset — plus one regression found and fixed on the way in, and one pre-existing test red on main that needs eyes before the tag.

    1. #535 conformance fast-subset lane (ai-legion, build e5e7945 + the Dockerfile patch below, gonuts pin v0.13.0, generated 2026-10-07T09:20:09Z):

    duplicate-post-concurrent              pass  no-double-count=pass no-fund-loss=pending no-output-reuse=pass service-or-refund=pass
    duplicate-post-sequential              pass  no-double-count=pass no-fund-loss=pending no-output-reuse=pass service-or-refund=pass
    mint-alias-spellings                   pass  no-fund-loss=pass restart-converges=pass
    pay-kill-post-receive-pre-session      pass  no-double-count=pass no-fund-loss=pending no-output-reuse=pass restart-converges=pass service-or-refund=pass
    swap-timeout-retry                     pass  no-fund-loss=pending no-output-reuse=pass restart-converges=pass service-or-refund=pass
    

    no-output-reuse green on swap-timeout-retry + both duplicates + the kill window = the #257/#266/#480/#640 brick class measured closed on v0.13.0; no-double-count green on duplicate-post-concurrent = the #639 class measured closed. no-fund-loss=pending is the designed first-class verdict blocked on the #502 payment-record store — not a skip.

    Host-local shims the run needed on ai-legion (for whoever re-runs there): build-time container DNS is broken on that host → CLOUD_LAB_EXTRA_COMPOSE with build.network: host; resident services hold host ports 8085/2121 → republished 18085/12121 with the runner's readiness poll adjusted. Host quirks, not repo defects.

    2. Regression found by the lane: cloud-lab client builds broke on main in #687 — ARG GO_VERSION placed stage-scoped in Dockerfile.client, so FROM golang:${GO_VERSION}... interpolates empty (golang:-bookworm). Every client-container lane was dead since 2026-10-06. Fix: #716 (one-line move, verified by this green run).

    3. Pre-existing red on main that contradicts this issue's "final-main Go battery 16/16 green" baseline: TestLateReceiveOutcomeIsRecordedWhenReceiveCompletesAfterTheDeadline — #700's own test — fails deterministically on clean e5e7945 and clean 68ad514 (current main), both isolated (go test -run ... -tags testenv) and in full make go-battery context: the late-PAID record carries the generic ERROR wording instead of the #502 owed-entitlement contract the test asserts (late_receive_outcome_test.go:258–264). Either the maintainer lane's green predates #700, or something env-dependent diverges. Worth resolving before the tag — as-is, make go-battery cannot go 16/16 on current main in this environment. (Found while verifying #715; that PR's lane output documents the same.)

    Remaining before the tag (unchanged from the checklist above): the four owner decisions (#648/#619/#629/#630/#631), the approved-idle merges, and the bench upgrade+rollback pass — which per #707/#715 should run with fail-loud postinst in the image.

  9. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Correction + root cause on the "pre-existing red" from my earlier report — this changes the tag-decision inputs:

    TestLateReceiveOutcomeIsRecordedWhenReceiveCompletesAfterTheDeadline was green at birth and is broken by #681 (the dependency security sweep), not env-dependent and not #700's own merge:

    ref commit result
    09ea3af #700 (the pinned release baseline) ok
    c60ea07 #681 deps sweep FAIL
    e5e7945 #701 (docs-only, RELEASE-NOTES.md ±4 lines) FAIL
    68ad514 current main FAIL

    git log 09ea3af..c60ea07 contains exactly #701 (docs) and #681 — elimination lands on the sweep. Repro in any checkout: cd src/merchant && go test -count=1 -tags testenv -run TestLateReceiveOutcomeIsRecordedWhenReceiveCompletesAfterTheDeadline .

    Implication for the "software gates all green / tag trigger = owner word" summary: green holds on the pre-sweep baseline 09ea3af5; main HEAD does not pass its own battery. So the tag choice is now explicitly: (a) tag 09ea3af5 — genuinely green end-to-end and artifact-matched, but without the 18-dependabot clearance incl. the x/crypto critical chain; (b) fix-forward — diagnose which bumped module changes the late-receive record's wording path, fix, re-run the battery, rebuild the artifact matrix, tag main; (c) tag 09ea3af5 and require the fix before v0.6.0-final. Owner call — flagging so the "READY FOR HARDWARE: YES" line is read with this asterisk.

  10. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Owner decisions from the pre-tag round (2026-10-07) — the ledger

    Decision Call State
    D1 tag base (c) — tag the sealed 09ea3af5 baseline now; the #681 sweep regression is required fixed before v0.6.0-final, not before rc1 diagnosis started (below)
    D2 merges #716 → #549 → #715 approved by owner blocked on one approving review each — the protect main ruleset requires 1 write-access approval and prevent-overrides blocks admin bypass; review requested from @c03rad0r (approved #681 today). bcm2709 fix is up as #717, same queue
    D3 bcm2709 fix pre-tag #717 open (one-word EXPECTED_ARCH=arm_cortex-a7_neon-vfpv4); rebuild the two rows from the pinned chain once merged
    D4 x1860 seat approved — the mipsel router lane gets the seat for the three payment scenarios pre-tag (artifact 7a98x2f9… is staged and waiting) fleet to run
    D5 #630 hostmode parked to next cycle posted on #630
    D6 #500 in rc1 excluded (sealed baseline); moves with #703 posted on #500
    D7 fork #93 / #96 both accepted for rc1, fix-before-final posted on the fork threads

    Two new facts for the ledger:

    1. The tag push itself is gated on D2's approvals — everything else in the rc1 sequence is ready. Whoever pushes the tag should also re-check the CI publish matrix: bcm2709 has no CI row (script-only path), so a tag push publishes the 10/12 matrix unless fix(packaging): bcm2709 EXPECTED_ARCH is arm_cortex-a7_neon-vfpv4 — restore the two rc1 matrix rows #717 + a CI-row addition land first — for rc1 the local matrix + Nostr publish flow per the earlier sequence covers it; for final the CI row should exist.
    2. 4 new dependabot alerts appeared on main after the sweep (2 high, 1 moderate, 1 low — surfaced by the push that opened fix(packaging): bcm2709 EXPECTED_ARCH is arm_cortex-a7_neon-vfpv4 — restore the two rc1 matrix rows #717). The sweep cleared 18; these are new/transitive. They fold into the same before-final bucket as the deps: security sweep — clears all 18 dependabot alerts (x/crypto critical chain) #681 regression.
  11. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    The #681 asterisk on D1 is resolved — root cause: racy test assertion, not a sweep regression. Fix: #718.

    Bisection: green at 09ea3af, red from c60ea07 on; pin-down isolation of x/net, x/crypto, and the yaml module move on the red tree — none flips it; the path under test logs the COMPLETED record (merchant.go:266) and then the unconditional owed-grant ERROR (owed_grant.go:228) in fixed order, and the test sampled "the last non-deadline line" — passing only when the snapshot landed between the two. #681's goroutine-timing shift exposed it. With the selection naming the record it wants, the test is green 5/5 on the unmodified sweep tree and 3/3 under -race.

    Consequences for the tag decision: no dependency rollback is owed before v0.6.0-final — the sweep stands; #718 restores main's green battery (4-line test fix, in the review queue with #716/#549/#715/#717). The new 4 dependabot alerts (2 high, 1 moderate, 1 low, surfaced during the #717 push) remain the only before-final dependency work, and they are ordinary alerts, not regressions.

  12. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Triage of today's new threads — and the tag-blocking verdict: none of them block the rc1 tag

    Thread What it is Blocks the tag?
    #706 (SSID leading !) feature, self-declared "merge after the current release" No — parked by design
    #707 (postinst fail-loud) duplicate of #715 — closed in its favor No (and resolved)
    #715 (postinst fail-loud) the survivor: fix + changelog + fork coordination No — but merge before the bench upgrade+rollback pass, per its own rationale
    #708 (two-era analysis) decision record; ipk stays (recorded in #710) No — documentation
    #710 (two-era SDK split) the era-correct build path (25.12/apk + 24.10.8/ipk) Not the tag — the CI optics on it. Until it merges, a tag push runs build-package.yml from the sealed baseline tree, where the ipk lane fails by construction → red ipk rows on the rc1 tag's CI run. Cosmetic while rc1 publishes via the locally-built pinned matrix; must be green before v0.6.0-final.
    #711 (doctrine docs) AGENTS.md + reproducible-builds two-era doctrine No — docs
    #712 (CI unification) explicitly sequenced post-rc1 No — by design
    #713 (build-surface contract) doctrine made load-bearing; pre-verified: contract test + build-purity green on its tree (comment on the PR); branch carries #710's manifest → merge #710 first No — release-infra hardening for final

    The actual pre-tag items are unchanged and still exactly two:

    1. One review pass — every PR above plus fix(cloud-lab): Dockerfile.client ARG GO_VERSION must be global — client builds broke in #687 #716/fix(packaging): bcm2709 EXPECTED_ARCH is arm_cortex-a7_neon-vfpv4 — restore the two rc1 matrix rows #717/test(merchant): late-receive outcome selection raced the owed-grant ERROR line — the #681 "regression" was a racy assertion #718 sit behind the protect main ruleset (1 write-access approval; admin bypass blocked by prevent-overrides). Suggested merge order once approved: fix(cloud-lab): Dockerfile.client ARG GO_VERSION must be global — client builds broke in #687 #716 (unblocks lanes) → test(merchant): late-receive outcome selection raced the owed-grant ERROR line — the #681 "regression" was a racy assertion #718 (main battery green again) → feat(cli): wallet recover — NUT-07 liveness check for journaled drain tokens #549 → fix(packaging): postinst fails loud when nodogsplash was orphan-removed #715 (before bench) → fix(packaging): bcm2709 EXPECTED_ARCH is arm_cortex-a7_neon-vfpv4 — restore the two rc1 matrix rows #717 (bcm2709 rows) → packaging: two pinned SDK eras — 25.12/apk (primary) + 24.10.8/ipk (installed base) #710 → docs: the two-path build doctrine — AGENTS.md canonical statement + two-era reproducibility table #711 → builds: enforce the two-path doctrine — surface contract test, one-pass matrix split, rails #713. The tag itself (sealed baseline 09ea3af5) needs none of them mechanically.
    2. The x1860/mipsel payment run — seat approved, artifact staged; the only on-hardware money-path evidence before the tag.

    Fleet note: three quick verification agents are still running (#706/#708/#712 status passes); the three heavy ones died on a broken model-fallback chain — their verification was done inline instead (this comment + the #713 datapoint + #707/#715 dedup).

  13. Amperstrand commented on Oct 7, 2026

    @Amperstrand
    CollaboratorAuthor

    Second deterministic merchant red on main — discovered while verifying the deps PR (#719). TestPurchaseSessionGuardHoldsThroughTheOutcomeUnknownWindow fails in isolation on clean origin/main (cb4db7f): the third submission should pass the duplicate guard and grant, but stays payment-received-grant-pending (kind-21023). Same module as the #718 racy test, and the last merchant-touching commits are #681 (sweep) / #700 / #619 — so it joins the #681-timing-suspicion class until diagnosed. It is NOT covered by #718's fix (different assertion mechanism — this one looks like the guard's hold window genuinely over-running, which could be a real #502-intent bug rather than a test race; needs its own look).

    Ledger impact: main's merchant suite currently has two deterministic-in-this-env reds (one root-caused + fix queued, one new); both belong to the before-v0.6.0-final bucket alongside the rc1 tag (which correctly points at 09ea3af5, where both tests pass).

  14. Amperstrand commented on Oct 8, 2026

    @Amperstrand
    CollaboratorAuthor

    rc1 gate verdict — consolidated by the orchestrator (from the release lane's artifacts)

    Artifact matrix: COMPLETE — all 6 architectures × both formats under /tmp/tollgate-build-artifacts/ on ai-legion-small: apk on the 25.12.0 SDK era, ipk correctly on the 24.10.8 era (the two-era design working in production; the earlier ipk-lane failure was the pre-#710 mismatch). Staged: tollgate-wrt-0.6.0_rc1-r0.{apk,ipk} per arch.

    VM campaign (ai-legion, QEMU): PARTIAL — campaign-logs/rc1-run.log: vm-boots-25.12 PASS · host-pkg-server-reachable PASS · fresh-install-one-transaction PASS · closure-staged FAIL (labeled FAIL on a count check whose own output reads staged=8 want=8 — suspected assertion-parse artifact, needs the lane's confirmation) — then the log stalls (7 lines; VMs exited). Remaining campaign steps (upgrade/rollback legs etc.) unrun.

    Verdict: rc1 is artifact-ready; the VM validation leg is incomplete. Two honest options for the owner's word:

    • Tag now with the VM leg explicitly marked pending (the artifact + install legs passed; the bench session can complete validation post-tag), or
    • Wait for the lane to finish the campaign (the closure-staged parse question + remaining legs) — bounded work, the QEMU rig is staged and proven.

    The lane owns the detail; this consolidation is from its logs and artifacts. Approvals note: the PR queue (#707/#710/#711/#713/#727-#740 family) awaits felix/c03rad0r clicks — none gate the tag mechanically, but #707 (upgrade-safety postinst) and #738 (ipk version ordering) are semantically tag-relevant.

  15. Amperstrand commented on Oct 8, 2026

    @Amperstrand
    CollaboratorAuthor

    v0.6.0-rc1 gate verdict — software legs GREEN, hardware leg pending (commissioning session, 2026-10-08)

    Artifact matrix

    12-row two-era matrix (apk on 25.12.0 SDK, ipk on 24.10.8 SDK). Ten rows verified as
    built bytes with sha256 in the build tree:

    arch apk (25.12) ipk (24.10.8)
    x86-64 d1b1ba5e… a29a9877…
    mediatek-filogic (aarch64_cortex-a53) 2eb9580f… 4bc3edb9…
    bcm27xx-bcm2711 (aarch64_cortex-a72) 51f31f26… 02ed1ea1…
    ramips-mt7621 (mipsel_24kc) 7a98f2f9… 5437f45a…
    ath79-generic (mips_24kc) 292e4167… 17ffa8ad…
    bcm27xx-bcm2709 (arm_cortex-a7_neon-vfpv4) reported complete by operator reported complete by operator

    All ten rows above verified as built bytes in this workspace; full sha256s in the run
    evidence (/tmp/tollgate-build-artifacts, campaign log dir on ai-legion).
    bcm2709: the commissioning session reports both formats complete; the bytes are not
    present in this workspace, and the one-line EXPECTED_ARCH fix (#717) is still open —
    the owner should confirm the bcm2709 rows from that session's evidence before the tag.

    QEMU VM campaign (ai-legion, pristine 25.12.0 x86-64, snapshot=on)

    GREEN — 13 PASS, 0 product FAIL, 1 SKIP. Pristine OpenWrt 25.12.0 x86-64 boot
    (snapshot=on), one-transaction dependency-closure install (8 apks, APK_RC=0), then:
    version v0.6.0-rc1 commit 659de8a7 · service running · API :2121 listening · portal
    files present · CLI socket live at /var/run/tollgate.sock · config.json +
    /etc/tollgate/wallet.db written · NDS up on :2050 · NTP pre-auth nft rule present ·
    same-version reinstall idempotent (service survives). Artifact bytes verified as the
    release-baseline matrix apk (d1b1ba5e…). Two recorded FAILs were campaign-harness
    defects (console-prompt echo polluting one assert; a stale /etc socket path) — both
    superseded by marker-based re-checks, noted in the evidence file.

    Evidence: ~/tollgate-vm/campaign-logs/rc1-result.txt + full console transcript
    rc1-console.log on ai-legion (boot → one-transaction closure install → check table).
    Reboot-persistence remains skipped-by-design on this lane (snapshot=on boots are
    pristine; it needs the persistent-disk variant of the place).

    Other gates already on record (this and the prior session)

    What the owner's word triggers

    The software legs are green on the pinned baseline. The owner's go-ahead triggers:

    1. merge the three ready branches (crash-lane assertion, conformance ids, test(contract): FROM-ARG scope fence for cloud-lab Dockerfiles #728 ARG fix)
      and re-run the release lane gates they touch (minutes);
    2. confirm the bcm2709 matrix rows (or accept them from the commissioning session's
      evidence);
    3. tag v0.6.0 from the release lane's pinned commit — CI cross-compiles, publishes
      kind-1063 artifact events per arch/format on the Nostr mirrors, and the changelog
      [Unreleased] block finalizes into release notes;
    4. the hardware bench acceptance (x1860/MT3000) stays the post-tag, pre-announce leg —
      it is the one gate this program could not run unattended (routers unreachable from
      ai-legion at commissioning time).

    No action in this verdict tags or publishes anything by itself.

  16. 11 remaining items

  17. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Issue dispositions — v0.6.0 decision packet (all 67 open issues)

    All 67 open issues in OpenTollGate/tollgate-module-basic-go, classified against the six
    disposition classes. Evidence per line; main @ 4614ac2; rc1 tagged 2026-10-05, feature-frozen.
    PR verdict citations: lane C = 2026-10-09 PR sweep (exception comments posted on #718, #713, #741, #711); lane A/B = stop-ship and
    release-train review lanes (only #771/#778 verdicts posted as of this ledger; the rest are
    pending — noted per row; the packet should collect them before go/no-go).

    Verdict key: A/B/C = lane; ✅ READY-class, ⚠ needs work, ❌ blocked-class.


    STOP-SHIP-CANDIDATE (6)

    issue title class evidence owner action
    #754 NDS pre-auth allowlist ineffective (nft prio −1) STOP-SHIP Bench-verify 2026-10-08: STILL BROKEN on rc1 (pristine 25.12.0 VM, rc1 apk 659de8a7, packet-level repro). No fix PR. Also gates #749's fix: #769's allow entries may be dead-on-arrival under this ordering (lane-A was commissioned to answer; verdict pending) Fix or explicitly accept before stable; sequence with #769 review
    #755 firewall sections silently skipped on renamed zones STOP-SHIP Bench-verify 2026-10-08: STILL BROKEN — reproduced; one sub-claim partially refuted for 25.12 fw4 (scope slightly narrower than filed). No fix PR Fix or accept; same cluster as #756/#757
    #756 99-tollgate-setup rebinds wifi to 'lan' unconditionally STOP-SHIP Bench-verify 2026-10-08: STILL BROKEN — class 1 (unconditional rebind) reproduced exactly; classes 2–3 not reproducible in the radio-less VM. No fix PR Fix or accept
    #757 nftables.d guards hardcode br-lan STOP-SHIP Bench-verify 2026-10-08: STILL BROKEN — reproduced with packet-level attribution (portal side cannot reach backend on renamed bridge). No fix PR Fix or accept; blocks #754-family verification on non-default configs
    #720 free-mint path grants 199 steps for 100 paid STOP-SHIP (borderline) Failing cloud-lab test on main @ 68ad5144 (2026-10-07, post-rc1 filing; rc1 carries it — no merchant/swap commits since). Funds-adjacent over-grant (~2×), free-mint path only, generous direction. S2 per filer; not bench-campaign-verified this sweep — classified from filing; no contrary evidence found Explicit accept-or-fix call before stable
    #721 /ln-invoice settlement grants gate to the poller, not the quote owner STOP-SHIP (borderline) Reproduced on main @ 68ad5144 (two-client harness, verbatim log in filing); present in rc1. "Misleading success" access-honesty class (S2/S3 per filer); no fix PR; no recorded acceptance Explicit accept-or-fix call before stable

    FIX-IN-FLIGHT (21)

    issue title class evidence owner action
    #719 crash-window: no value recovery after restart FIX-IN-FLIGHT PR #771 (Closes #719). Lane-A: CHANGES-REQUESTED — mechanism fund-safe (no derivation re-exposure; intent+counter one-txn), but recovery claim must be scoped (NUT-19 TTL), fork v0.14.0 tag+repin before stable, crash-injection lane green run is a merge condition Author applies lane-A items; repin to tag
    #748 payment rate limit breaks test flows FIX-IN-FLIGHT PR #778 (Closes #748). Lane-A: CHANGES-REQUESTED (mechanical) — prod-unreachability verified, 10 RPM default pinned; needs changelog fragment conversion + one reword Author: fragment + reword; then merge
    #747 mint-health 5-min-poll backoff FIX-IN-FLIGHT PR #777 (Fixes #747); migration from fork #87. Lane-A verdict pending Collect lane-A verdict
    #749 pre-auth DNS UDP/53 blocked FIX-IN-FLIGHT PR #769 (Fixes #749). Lane-A verdict pending; flagged risk: #754's prio −1 ordering may make the allow entries dead-on-arrival — lane-A's cross-check decides Collect lane-A verdict; sequence after #754 call
    #731 late-receive outcome picker flake FIX-IN-FLIGHT PR #774 (Closes #731/732/733). Lane-A verdict pending. Battery (10-08): target test green on integrated tree; sibling flake noted Collect lane-A verdict
    #732 first-reachable FiredOnce intermittent FIX-IN-FLIGHT PR #774. Lane-A pending —
    #733 killed-attempt convergence flake FIX-IN-FLIGHT PR #774. Lane-A pending —
    #746 entry_ui port-table contract test FIX-IN-FLIGHT PR #765 (Closes #746). Lane-B verdict pending; stacked follow-up #773 (feature-shaped SSOT — freeze question open per lane-B brief) Collect lane-B verdict; decide #773 freeze status
    #750 TIP cross-vectors, Go leg FIX-IN-FLIGHT PR #779 (Closes #750); test-only. Lane-B pending Collect lane-B verdict
    #767 four drift fences for tests/contract FIX-IN-FLIGHT PR #775 (Closes #767). Lane-B pending. NB: #772 was discovered by this charter Collect lane-B verdict
    #726 go-battery needs ndsctl on PATH (guard test) FIX-IN-FLIGHT PR #776 (Closes #726, #770). Competing PR #729 (Fixes #726) — zero file overlap (#776: guard harness + test_helpers + happy-path; #729: new ndsctl_seam_test.go): complementary seams, owner picks winner(s). Issue's own correction comment confirms filing was right. Lane-B pending on #776 Pick #776 vs #729; collect lane-B verdict
    #770 guard test fails on any host w/o ndsctl FIX-IN-FLIGHT Closed by keyword in #776. Lane-B pending Rides #776
    #583 wallet never initializes on jffs2-overlay FIX-IN-FLIGHT ⚠ PR #741 — lane-C: DRIFTED (src/main.go overlap with #730; merge clean but battery-green evidence stale; rebase/re-run merchant lane). No closing keyword → manual close Rebase #741 first; manual close after merge
    #505 flash durability & corruption-recovery audit FIX-IN-FLIGHT PR #743 — lane-C: READY. Audit addendum in issue (2026-10-08). No closing keyword → manual close Merge; close manually
    #504 multi-process wallet access FIX-IN-FLIGHT PR #742 — lane-C: READY. Audit posted 2026-10-08. Manual close Merge; close manually
    #550 dev-env: dangling replace + install.json mutation FIX-IN-FLIGHT PR #736 — lane-C: READY (half fixed by #557 per issue comment; guards are the durable half). Manual close Merge; close manually
    #723 rebrand gutter trips on uhttpd comment FIX-IN-FLIGHT (not red on main today) Trigger comment (uhttpd.luci in 92-tollgate-admin-setup) gone from main (grep @4614ac2 empty); main's tokenizer still comment-blind (latent class). PR #734 — lane-C READY — hardens tokenizer + adds regression controls (diff verified vs main's version). No closing keyword → manual close Merge #734; close #723 manually
    #724 cloud-lab Dockerfile mid-file ARG FIX-IN-FLIGHT PR #728 — lane-C: READY, behind=0 (rebased on 4614ac2). No closing keyword → manual close Merge; close manually
    #582 first purchase from unregistered client refused FIX-IN-FLIGHT PR #737 — lane-C: READY. No closing keyword → manual close Merge; close manually
    #520 ngit CI test.yml lane dropped/phantom-failing FIX-IN-FLIGHT PR #740 — lane-C: READY (repo-side split; coordinator-side remainder documented in issue). No closing keyword → manual close Merge; close manually
    #497 P0 swap crash window (research umbrella) FIX-IN-FLIGHT (half) Actionable half = PR #771 (closes #719); lane-A verified the mechanism fund-safe but recovery beyond NUT-19 TTL needs the NUT-09 restore fallback — owner call whether pre-stable or post-release; remainder folds into #703 Decide restore-fallback timing

    FIX-READY-UNCOMMITTED (1)

    issue title class evidence owner action
    #768 upstream prober TEMPORARY portal trigger pokes non-TollGate gateways FIX-READY-UNCOMMITTED Worktree /home/user/src/fix-768 verified this turn: branch fix/768-upstream-portal-trigger off 4614ac2, 2 modified (tollgate_prober.go, upstream_session_manager.go) + 5 untracked (2 changelog fragments, workaround docs+registry, unit test, contract test). Issue comment: verified, PR to follow, awaiting maintainer go Owner decision: commit+PR or hold

    POST-0.6.0 (24)

    Labeled release:post-0.6.0 (3) — labels verified present and consistent:

    issue title class evidence
    #702 recovery semantics: owned-stranded vs handed-out POST-0.6.0 Label; design analysis posted 2026-10-08, encoding question still open (own comment)
    #703 fund-safety program vs CDK saga model POST-0.6.0 Label; pre-release item 2 complete per comment (2026-10-07)
    #705 NUT-13 residue guard dead code POST-0.6.0 Label; fork fix 1eae260 exists post-v0.13.1 (per #771 lane-A research) — wiring rides next fork tag

    Unlabeled but clearly out of freeze scope (21):

    issue title class evidence
    #85 Nostr router discovery design POST-0.6.0 Owner: "Postponing — large feature" (2026-07-28)
    #226 port 2121 loopback-only POST-0.6.0 Architecture enhancement; referenced PR #317 closed unmerged (2026-07-30) — needs new work if pursued
    #239 reseller zero-float bootstrap POST-0.6.0 Owner: "Postponing" (2026-07-28)
    #311 AP selection research POST-0.6.0 Research roadmap; last activity 2026-07-28
    #313 capacity advertisement design POST-0.6.0 Design questions; last activity 2026-07-28
    #352 goimports hook gap POST-0.6.0 Hygiene-only, 0 comments since filing
    #414 payout/drain ignore mint input fees POST-0.6.0 Pre-existing fee-economics gap; swap side bounded in-repo by #525 (per #534); folds into #703
    #415 fee-charging signet mint lane POST-0.6.0 Test-lane proposal "parked for maintainer decision" (2026-09-21)
    #417 keyset final_expiry kills balances POST-0.6.0 Wallet-rotation hygiene; audit cross-ref (2026-10-07); spec-correct mint behavior
    #422 clientd rapid-repay drain on unknown MAC POST-0.6.0 clientd component (separate tooling); found by scenario battery
    #423 clientd burned tokens, no reclaim POST-0.6.0 Recovery path has design track = #702 (comment 2026-10-07)
    #424 nutshell balance reads 0 after send POST-0.6.0 Upstream nutshell behavior; documented in filing
    #483 renewal-policy follow-ups POST-0.6.0 Double-charge fixed in alpha4 and green through rc1 (comment 2026-10-06); remainder are follow-ups
    #498 ambiguous-outcome class at TollGate layer POST-0.6.0 Folded into #703 incident map (comment 2026-10-07). Label-gap candidate
    #499 enforcement state is process-memory POST-0.6.0 Known gap (AGENTS.md says so verbatim); #703 program
    #500 stuck-proof reclaim never invoked POST-0.6.0 Owner-confirmed excluded from rc1 (comment 2026-10-07); moves with #703. Label-gap candidate
    #501 canonical-identity completeness audit POST-0.6.0 P1 program work downstream of #433
    #503 Go leg of shared conformance matrix POST-0.6.0 Program twin (Rust repo #15); fast-subset lane measuring (2026-10-05)
    #534 offline keyset-fee accessor POST-0.6.0 Fork half merged (gonuts-tollgate#30, post-v0.13.0); wiring needs fork tag ≥ v0.14.0 — candidate to ride #771's repin
    #685 nits from #631 POST-0.6.0 "Zero blockers"; post-release conveniences (comment 2026-10-07)
    #712 unify CI package-ipk onto local-build-ipk.sh POST-0.6.0 Internal CI cleanup; interim divergence-warning marker present on main (verified 2026-10-07)

    STALE/SUPERSEDED (5)

    issue title class evidence owner action
    #772 guard test fails deterministically (dup) STALE/SUPERSEDED Same test + root cause as #726/#770 (ndsctl-on-PATH). Its own named one-line fix (installRenewalNdsctl) is already in flight on #774's branch (ec759a0, cross-ref'd in its last comment); no PR carries a closing keyword for #772 Close as dup of #726 when #776 or #774 lands
    #708 packaging era split SUPERSEDED Implemented by #710 (merged 62519ab); issue's own comment: "closes when #710 merges"; #710 body has no closing keyword → no auto-close Manual close
    #552 tollgate-wrt uninstallable on 25.12 STALE (likely resolved) Phase 0.1 measured resolution WORKS on current 25.12.5 feeds with apk-tools 3 (comment 2026-10-06); rc1 apk installs+runs on pristine 25.12.0 bench rig (#754-757 campaign). Uncertainty note: I did not re-run the install matrix; classify from evidence Confirm + close (or keep open if 24.10/ipk lane still affected)
    #371 full SDK needed for apk releases? STALE (answered) Equivalence spike completed (comment 2026-09-17: SDK-free equivalent); practical split landed via #708/#710. Question answered; repo repurposed nothing Close or repurpose as tracking
    #398 NDS 5.0.2 on OpenWrt 22.03 DNAT breakage STALE-leaning (uncertain) Filed against 22.03.3 + v0.6.0-alpha1; current packaging eras are 24.10/ipk + 25.12/apk (#710). Uncertain: no owner statement on 22.03 support; 0 comments Owner: confirm 22.03 out of support → close

    DECISION-OWED (10)

    issue title class evidence owner action
    #665 v0.6.0 pre-tag readiness (packet target) DECISION-OWED The umbrella tracker; this ledger feeds its consolidated comment Post go/no-go with stop-ship list below
    #339 release request: cut v0.6.0 DECISION-OWED rc1 out 2026-10-05; stable cut gated on stop-ship disposition Cut when stop-ship list clears
    #457 archive or generate dead GH Actions twin DECISION-OWED Recommendation exists (archive; comment 2026-10-06); twin dead since 2026-08-27 org-wide Execute archive
    #751 GPL license text: lanes disagree DECISION-OWED Team decision per migration header; no PR Decide + document
    #752 swap rejects fee-less small tokens (CU107) DECISION-OWED "Investigate:" — no triage yet, no repro on rc1 Triage (may be mint-side spec behavior)
    #753 FTF release assets lack sha256 digests DECISION-OWED Installer tamper-check has nothing to verify; #762's scanner covers Nostr-event trust, not FTF digests Decide digest publication path
    #758 retire the Amperstrand fork DECISION-OWED Phase 3 resolved via #759 (READY, lane-C); #759 has no closing keyword → remaining phases need manual execution/close Execute remaining retirement steps
    #761 ngit stage-2 key unlocated DECISION-OWED Keyfile exists on no machine; #763 comment reduces blocking (commit-anchored publishing works with any key) Decide key strategy vs #763
    #763 commit-anchored release design DECISION-OWED Addressed to felix's lane; no reply yet felix's take → spec repo
    #725 conformance lane vs PRTA matrix drift DECISION-OWED (cross-repo) Lane asserts 5 fast-subset ids; PRTA main carries 3/5, feature branches 0/5 (I1..I7 scheme) — "gate cannot run for any release until the two repos realign". #535 branch ran with its own realignment (2026-10-05) but main's lane still refuses vs public refs Decide which repo bends (extend PRTA matrix vs trim fast-subset); not product-blocking but blocks the conformance gate

    Stop-ship list for v0.6.0 stable — honest recommendation

    Hard gate (bench-proven broken on rc1, dynamically reproduced, no fix PRs):

    Decision-owed list (for the packet)

    #665 (packet itself) · #339 (cut execution) · #457 (archive GH twin) · #751 (GPL lanes) ·
    #752 (CU107 triage) · #753 (FTF digests) · #758 (fork-retirement execution; #759 won't
    auto-close it) · #761 (ngit stage-2 key) · #763 (commit-anchored design) · #725 (PRTA
    matrix realignment) · #768 (commit the verified fix or hold) · #497-remainder (NUT-09
    restore fallback: pre-stable or post-release).

    Count summary

    class count
    STOP-SHIP-CANDIDATE 6 (#754-757 hard; #720/#721 borderline)
    FIX-IN-FLIGHT 21 (verdicts: A posted 2, A pending 5 issues, B pending 5, C READY 9, C DRIFTED 1)
    FIX-READY-UNCOMMITTED 1 (#768)
    POST-0.6.0 24 (3 labeled + 21 unlabeled out-of-scope)
    STALE/SUPERSEDED 5 (#772, #708, #552, #371, #398 — 2 with uncertainty notes)
    DECISION-OWED 10
    total 67 ✓

    Systematic flags for the packet

    1. No battery-era PR carries closing keywords (test(contract): FROM-ARG scope fence for cloud-lab Dockerfiles #728/test(packaging): read the uhttpd section vocabulary from active code only #734/chore(dev-env): guard the tree against dangling replaces and cloud-lab runtime state #736/fix(merchant): accept first purchases from present-but-unlisted clients #737/fix(ngit-ci): split the 16-job test lane and unhide its silent jobs (#520) #740/fix(merchant): name the storage-mmap failure class instead of blaming the mints (#583) #741/fix(wallet): enforce the single-writer invariant at boot and on the CLI socket (#504) #742/fix(config,cli): durable config writes + offline wallet.db corruption check (#505 audit) #743/ci(gitleaks): one config, two lanes — in-repo .gitleaks.toml, OpenTollGate/.github scanner, ngit build-of-record job (#758) #759/release: the trust model + the fake-release detector (scanner, drill fixture live, doctrine doc) #762 et al. — verified). Issues rebrand-literal-gutter: uhttpd section-vocabulary check trips on a COMMENT (uhttpd.luci in 92-tollgate-admin-setup:178) #723/cloud-lab Dockerfile.client: mid-file ARG invisible to FROM — client and killer images unbuildable on docker/buildkit 29 (golang:-bookworm) #724/ngit CI: test.yml lane is being dropped or phantom-failing at the coordinator level (operator investigation needed) #520/dev-env: dangling local go.mod replace + cloud-lab mutates tracked install.json cause false-negative verification runs #550/First payment from a present-but-unregistered client is refused (client-not-registered) — extend the renewal presence-proof to first purchases #582/Wallet never initializes on jffs2-overlay devices (squashfs NOR targets): bbolt requires shared mmap; silent degraded mode + misleading "no reachable mints" #583/Priority: P2 — multi-process wallet access assumptions: the daemon and the CLI can open the same bbolt wallet concurrently. #504/Priority: P2 — flash durability & corruption-recovery audit for wallet.db and new durable stores (G06/G09 add writes; this issue bounds the write cost and the blast radius). #505/Packaging era split: 25.12 SDK cannot emit ipk — two pinned SDKs (format-selected), or drop ipk #708/Retire the Amperstrand fork — OpenTollGate/tollgate-module-basic-go becomes the single source of truth (migration executed, plan inside) #758-phase3 will NOT auto-close on merge — manual closes owed, or owners add keywords before merging.
    2. Wallet never initializes on jffs2-overlay devices (squashfs NOR targets): bbolt requires shared mmap; silent degraded mode + misleading "no reachable mints" #583 is the only lane-C contradiction: fix PR fix(merchant): name the storage-mmap failure class instead of blaming the mints (#583) #741 DRIFTED (src/main.go moved under it via fix(api): serve the :2121 discovery advertisement as application/json #730); merge is mechanically clean but the battery's green run is stale for it — rebase before merge.
    3. go-battery needs an ndsctl on PATH: TestPurchaseSessionGuardHoldsThroughTheOutcomeUnknownWindow fails on bare hosts (passes with stub) #726 family needs a winner: test(merchant): stage the cloud-lab ndsctl seam for the guard harness — the battery stops needing an ndsctl on the host (#726) #776 (closes go-battery needs an ndsctl on PATH: TestPurchaseSessionGuardHoldsThroughTheOutcomeUnknownWindow fails on bare hosts (passes with stub) #726+merchant suite: TestPurchaseSessionGuardHoldsThroughTheOutcomeUnknownWindow fails on any host without an ndsctl binary — the duplicate-guard harness stubs the pre-flight but not the gate #770) vs test(merchant): stage the cloud-lab fake ndsctl seam for tests that reach the real valve #729 (fixes go-battery needs an ndsctl on PATH: TestPurchaseSessionGuardHoldsThroughTheOutcomeUnknownWindow fails on bare hosts (passes with stub) #726) — zero file overlap, complementary seams; merchant: TestPurchaseSessionGuardHoldsThroughTheOutcomeUnknownWindow fails deterministically off-router — grant-side ndsctl exec is never stubbed #772 is the duplicate nobody closes. Owner picks; close merchant: TestPurchaseSessionGuardHoldsThroughTheOutcomeUnknownWindow fails deterministically off-router — grant-side ndsctl exec is never stubbed #772 as dup.
    4. Label gaps: Priority: P1 — stuck-proof hygiene: reclaim machinery exists but is never invoked by the daemon. #500 and Priority: P1 — ambiguous-outcome class at the TollGate layer. Independent of gonuts fixes. #498 qualify for release:post-0.6.0 (owner-confirmed / program-folded) but are unlabeled; tollgate-clientd: burned tokens on rejected payments (no reclaim path) #423 folds into Recovery semantics: owned-stranded vs handed-out (clawback) — encode the class in code, give clawback its own verb and policy #702 likewise. Labels not touched this turn (read-only).
    5. Pending verdicts: lane-A owes fix(merchant): event-driven mint-health transitions — recovery in seconds, not poll cycles (#747) #777/fix(merchant): de-flake the suite trio — late-outcome picker, first-reachable re-arm, owed-grant retry clock (#731, #732, #733) #774/fix(packaging): allow pre-auth DNS on UDP, not only TCP — users_to_router udp/53 #769 reviews; lane-B owes all seven. The go/no-go packet should not finalize FIX-IN-FLIGHT rows for those until collected.
  18. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Owner decision record + hardware test plan — v0.6.0 release night (2026-10-09)

    Decisions taken live with the release owner, following the issue-disposition ledger posted
    earlier tonight. State at decision time: main @ 4614ac2 (unchanged since the battery),
    nothing merged. Lane-A verdicts landed since the ledger: #769 CLEAN (the
    dead-on-arrival question answered — NOT DOA: #754's kill chain is forward-hook only, #769's
    input-path allow executes; "land it"), #777 CHANGES-REQUESTED (core sound; drop its
    bonus #732 fix), #774 CHANGES-REQUESTED (all three fixes sound, assertions strengthened;
    changelog malformed). Lane-B: 0/7 posted.

    Decisions

    # topic decision
    1 #754-757 cluster Split: fix #754 + #757 (stop-ship lane; fix shapes in the bench comments); #755/#756 accepted with release-notes caveat if unfixed by rc2
    2 #720 over-grant 30-min triage; fix only if trivial; else accept documented
    3 #721 gate-to-poller triage; fix if small; else accept documented
    4 #768 GO: commit + PR the verified worktree fix into the 0.6.0 train
    5 NUT-09 restore fallback post-release; scope #771's changelog claim to the NUT-19-replay reality and merge without it
    6 de-flake overlap #774 carries #731+#732; #718 closed as superseded; #777 drops its bonus fix and rebases on #774
    7 stable-cut gate rc2 → bench-green on the fixed #754/#757 → stable; lane-B verdicts gate their own merges, not the rc2 tag
    8 #753 digests publish sha256 alongside FTF assets (part of the stable-cut publishing)
    9 #751 GPL text ship in both lanes (pre-stable packaging PR)
    10 #761/#763 adopt #763 commit-anchored direction; the stage-2 key question dissolves; felix's spec take still owed
    11 #457 GH Actions twin keep the twin (owner call, diverging from the archive recommendation) — no action
    12 #725 PRTA matrix extend PRTA with the 2 missing fast-subset ids (cross-repo; not cut-blocking)
    13 #758 close condition stays open until the fork repo is archived read-only

    Execution queue (dependency order)

    1. Merge fix(packaging): allow pre-auth DNS on UDP, not only TCP — users_to_router udp/53 #769 (CLEAN, fixes users_to_router allows tcp/53 only — UDP DNS from captive clients fails, breaking hostname resolution #749).
    2. fix(merchant): de-flake the suite trio — late-outcome picker, first-reachable re-arm, owed-grant retry clock (#731, #732, #733) #774 changelog fixes (fragment conversion, drop duplicate header, note supersedes
      test(merchant): late-receive outcome selection raced the owed-grant ERROR line — the #681 "regression" was a racy assertion #718) → close test(merchant): late-receive outcome selection raced the owed-grant ERROR line — the #681 "regression" was a racy assertion #718 → fix(merchant): event-driven mint-health transitions — recovery in seconds, not poll cycles (#747) #777 drops bonus fix, rebases on fix(merchant): de-flake the suite trio — late-outcome picker, first-reachable re-arm, owed-grant retry clock (#731, #732, #733) #774.
    3. fix(main): the payment rate limit gets a test-context bypass — suites run unthrottled, the shipped default unchanged (#748) #778 mechanical revisions (fragment + enforcement-claim reword).
    4. fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 path: scope changelog claim + port.go comment; tag gonuts-tollgate v0.14.0;
      repin the four go.mods; crash-injection lane green on the repinned branch (= merge
      condition).
    5. upstream prober's TEMPORARY captive-portal trigger fires before advertisement validation, repeats every 30s poll — port-80 pokes at gateways that aren't TollGates #768: author commits fix/768-upstream-portal-trigger (7 verified files) + PRs.
    6. Stop-ship fix lane: NDS pre-auth allowlist ineffective: nds_enforce_forward (nft prio −1) rejects before the allow chain can mark/accept #754 (allowlist must live where it executes — input-hook allows)
    7. Triage spikes, 30-min boxes: swap-fees: free-mint path grants 199 steps for 100 paid (allotment 11,940,000 ms vs 6,000,000 expected) #720 (allotment math), /ln-invoice settlement grants the gate to the quote-status POLLER, not the quote owner — owner later reads access_granted=true with no gate #721 (grant keyed on quote owner).
    8. Merge the lane-C READY stack in battery order; builds: enforce the two-path doctrine — surface contract test, one-pass matrix split, rails #713 rebases (jq-matrix form over packaging: two pinned SDK eras — 25.12/apk (primary) + 24.10.8/ipk (installed base) #710's
      two-era main).
    9. Collect lane-B verdicts as they land.
    10. Pre-stable packaging: Packaging: the two release lanes disagree on shipping the GPL license text — team decision needed #751 GPL text both lanes; FreedomTechFeed release assets carry no sha256 digests — installer tamper-check has nothing to verify #753 digest publication in the cut.
    11. Tag rc2 → bench re-verify NDS pre-auth allowlist ineffective: nds_enforce_forward (nft prio −1) rejects before the allow chain can mark/accept #754/nftables.d guards hardcode br-lan — backend unreachable from portal side on renamed bridges (30-backend-firewall, 31-admin-board) #757 (+ spot-check the tollgate firewall sections silently skipped when zones renamed from 'lan' (no error, no enforcement) #755/99-tollgate-setup rebinds wifi to 'lan' unconditionally and orphans replaced bridges (split-plane mgmt/portal DUTs) #756 caveat scope) →
      stable.
    12. Post-release: NUT-09 fallback (fork), PRTA matrix extension, Design: commit-anchored releases — anyone builds/publishes, tags become signed metadata (publish-then-tag), disagreement detection; addressed to the felix lane #763 spec take (felix).

    Physical test plan (what hardware exists, what each queued item needs)

    Inventory source: Amperstrand/conwrt-bench registry (places.json + inventory.jsonl, read
    2026-10-09; health notes run through 2026-09-25 — two weeks stale). Live reservation
    state must be checked from ai-legion (export LG_COORDINATOR=ai-legion:20408; labgrid-client places && labgrid-client who) — the client is not installed on the box this
    sweep ran from. Reserve through places; commit registry changes after any state change.

    Usable DUT seats:

    place device / target OpenWrt state (per registry) best for
    ap-lan4 WS-AP3915i, ipq40xx (arm_cortex-a7) 24.10.2 reference unit — full channels, key auth, adopted; also hosts lan2's serial bridge the ipk/24.10 lane (installed base per #710), #756 wifi-rebind (has radios — the VM cannot), #741 jffs2 mmap fix (AP3915i runs jffs2 overlay — QEMU/x86 ext4 cannot reproduce #583)
    ap-lan2 WS-AP3915i, ipq40xx 25.12.5 adopted-ish (tcp22/80 open @ .102.51), serial live (:4002); ⚠ dirty overlay + one spontaneous-reboot/watchdog incident on record the apk/25.12 lane — physical confirmation of #754/#757/#749 on the same release the bench-verify VM used
    ap-lan5 WS-AP3915i, ipq40xx 24.10.2 v6-only reach, blank root pw (recorded exposure), TFTP-lifeline dependent spare only
    (lan8) NR7101 ramips/mt7621 (mips) apk SNAPSHOT near-vanilla, not lab-enrolled (keys/addressing TODO) not tonight-ready
    off-bench GL-MT3000 (filogic/aarch64), COVR-X1860 (mt7621), EX5700, ASUS Lyra (ipq40xx) mixed field units; note a GL-MT3000 on 25.12.5 was used for a recent tollgate-wrt install check (packaging/Makefile note) ask owner; GL-MT3000 = clean aarch64/apk candidate

    Not DUTs: ERX (house core), GS1900-8HP/-24E + gs108t (bench infra), ap-lan3
    (network-dark since 09-25, recovery blocked on owner hands + serial splice), ap-lan6
    (dark since ever).

    Need → seat mapping for tonight's queue:

    Caveats: registry health is 2 weeks old (ap-lan2 stability is the main doubt — serial
    watch via :4002 recommended during any run); live holders unknown from this box; acquire/
    release through labgrid per AGENTS.md, and the registry commit rule applies after any flash/
    state change.

  19. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Hardware plan CORRECTION — live labgrid state (2026-10-09, night)

    Corrects the hardware table in the decision-record comment above: that table was built from
    conwrt-bench registry notes (2 weeks stale) and got three things wrong. Live state pulled
    from ai-legion (labgrid-client places/who) just now supersedes it.

    Corrections to the earlier table

    1. The x1860s are NOT off-bench field units — both are enrolled labgrid places:
      x1860-1 (PRTA ALPHA, .105.52, bench lan5/VLAN1005) and x1860-2 (PRTA BRAVO,
      .104.52, bench lan4/VLAN1004), mt7621/mipsel, 24.10.2-era, NetworkService-only mgmt
      contract (no power/serial). x1860-1 is HELD (root@ai-legion since 2026-10-08 15:44 —
      likely an automated PRTA session; reconcile before touching). x1860-2 is free.
    2. "ap-lan4 reference unit healthy" was stale — that device (79b1) is now
      ws3915i-79b1 "PRTA charlie": chronic staller (windows close, power cycle reopens).
      The healthy 25.12.5 unit is ws3915i-ea7f (.102.51) — a deployed observer whose
      registry line literally reads "test-DUT promotion pending owner call". That owner call
      is the cheapest way to get a 25.12/apk physical DUT tonight.
    3. The lab is bigger than the registry notes suggested: 5 ER6P PoE DUT seats
      (er6p-p1..p5, VLAN 400-404, all free), a vlab-default-owrt/vlab-default-client
      virtual pair, an android-emulator slot (HELD), numo-phone (moto g POS, USB on
      ai-legion), tollgate-s3-hil, cyd-tollgate.

    The phone — yes, and it's load-bearing

    Do / recover ledger (corrected)

    item action blocker
    nr7101-router acquire place (modem/SIM exclusivity; never power-cycle — :7002 modem bridge is INFRA), apk-upgrade tollgate to rc2 when tagged, phone E2E per conwrt-bench docs/nr7101-tollgate-runbook.md §9.4-9.5 mint liveness check; phone bedtime window
    ws3915i-ea7f owner call: promote observer → test DUT (25.12/apk firewall verification seat) owner decision (this comment is the ask)
    test mint verify ai-legion-small :8383 /v1/info from an allowed host SSH hop / ufw
    phone confirm phone schedule vs Family-Link bedtime; or tap USB-debug auth on numo-phone physical/human
    stock switch #2 (13.3) mgmt still DEAD (blocks cold-cycle cures for the stalled ws3915i units) owner hands / coordinated reboot
    ws3915i-79b1 (charlie) usable with babysitting (power cycle reopens stalled windows) —
    x1860-1 HELD by root since 10-08 — reconcile with holder first holder
    router-beta / ap-lan3-class serial-splice repair owner physical hands — not tonight

    Do we need the two x1860s? — No for the release gates, one cheap exception

  20. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Hardware state addendum — physical lane DOWN tonight; QEMU carries rc2 verification (2026-10-09, late)

    Live-executed checks since the correction comment above (all from ai-legion, read-only
    except one bounded acquire attempt):

    Findings

    1. Bench switch (GS1900-8HP @ 192.168.13.2) is unreachable (no route) — its rig is
      runtime-only and documented to die on its self-reboots. Every VLAN-100x seat behind it
      is dark from every vantage I have
      : x1860-1/.105.52, x1860-2/.104.52 (pinned-source
      pings from 104.10/105.10 fail), router-alpha/.105.51. Stock switch Fix ecash path #2 (13.3) mgmt also
      dead (http). Recovery = owner hands + the Mac-side re-arm runner
      (scripts/gs1900-bench-arm.sh in conwrt-bench) once the switch is back.
    2. NR7101 pre-flight failed twice: labgrid acquire errors with a stale place stanza
      (Match ai-legion/ap-lan7/NetworkService has no matching remote resource — the place
      definition needs the bench lane's cleanup), and direct SSH to .12.124 is
      connection-refused (ICMP answers; DHCP stability "pending Ensure that the proffit shares add up to 1 #35"; mgmt path needs
      on-device/wireless attention). Not remotely usable tonight.
    3. Test mint is ALIVE — ai-legion-small:8383 answers /v1/info over IPv6:
      cdk-mintd/0.18.1, pubkey 02525ef44c05…. Caution: this pubkey differs from the
      registry's 2026-09-25 record (03d902f3…)
      — the mint was re-keyed/upgraded since.
      Any router wallet/accepted_mints configured against the old pubkey (NR7101's was) must
      be re-accepted before phone E2E, or payments will fail against the wrong mint.
    4. Working tonight: ai-legion itself (QEMU lane = the bench-verify rig), the mint, the
      WiFi-side vantage, numo/emulator assets (with their own constraints).

    Consequence for the release queue

    No lab state was changed: the one acquire attempt failed cleanly; nothing was held,
    flashed, or written.

  21. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Rel-test status from lane rel-769 (v6-captive follow-up): rc1's IPv6 captivity is now dynamically verified on-VM, and both test frameworks gained regression coverage — branches ready.

    Both branches are local on ai-legion (worktrees ~/src/tgmbg-v6tests, ~/src/prta-v6assert), not yet pushed — say the word and they go up as PRs.

  22. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    v0.6.0-rc1 upgrade/rollback acceptance lane — mipsel ipk on real hardware (bench x1860-2, OpenWrt 24.10.7, labgrid DUT). All four legs GREEN.

    Leg 1 — v0.5.0 mipsel ipk rebuild (baseline artifact). Rebuilt from tag v0.5.0 with the era toolchain (GOTOOLCHAIN=go1.24.2, staged mtimes @0, SOURCE_DATE_EPOCH=0, BUILD_TIME pinned to the tag commit): sha256 787f068c0a8f72f30e4545331fb8976790055e974ea99d65b2fd5435cacdb95b, byte-identical across two independent rebuilds; on-target tollgate version reports v0.5.0 / e60a3e6c / go1.24.2.

    Leg 2 — 0.5.0 baseline provisioning. Fresh install healthy: service running, :2121 serving the kind-10021 advertisement, portal listeners up, wallet_ok: true, wallet 6 sats. Noted: v0.5.0's Depends: luci is uninstallable on a luci-less minimal/offline system (opkg aborts at feed download); rc1's dropped-luci dep set installs clean on the same DUT — itself acceptance evidence for the dependency change.

    Leg 3 — upgrade to rc1. Plain opkg install accepted v0.5.0 → v0.6.0-rc1 (rc=0). Transaction watch clean: zero package removals (neither built ipk carries a Replaces: control field; only rc1-superseded files cleaned). Post-upgrade: service/portal healthy; config migrated v0.0.8 → v0.0.9 with the v0.0.10 entry_ui flip correctly gated (feed half absent; 92-tollgate-admin-setup errors benignly without rpcd on this luci-less image); admin board on :8090/:8443 serving. Offline settle: :2121 refuses with an explicit "starting" state while the merchant initializes (~4 min with DNS dead), then serves — worth documenting for RC testers on offline routers. Wallet: rc1 reports 0 sats across 0 mints while offline — display semantics (keyset-dependent), not data loss, see leg 4.

    Leg 4 — rollback to v0.5.0. opkg install --force-downgrade (the same ipk from leg 1), rc=0, preinst passed, service healthy — and the wallet shows 6 sats again. The round-trip 6 → rc1(0-shown) → 6 on the same wallet.db proves funds survive both directions. sessions.json byte-stable across all states. No SEV.

    #93 (preinst bare-jq) — reproduced on-DUT. v0.5.0's shipped preinst calls bare jq and exit 1s; with jq absent at preinst time (opkg satisfied, runtime broken) the v0.5.0 transaction dies at preinst: line 5: jq: not found → preinst script returned status 1 → Aborting installation (clean abort, prior package intact). rc1's no-op preinst + Depends: jq reinstall from staged files in one transaction: rc=0. The rc1 design resolves this class exactly as intended.

    #738 (rc1→final upgrade path) — confirmed closed under opkg semantics. On-DUT:

    opkg compare-versions v0.6.0-rc1 '<<' v0.6.0  → false
    opkg compare-versions v0.6.0-rc1 '>>' v0.6.0  → true   (rc1 ranks ABOVE final)
    opkg compare-versions v0.5.0   '<<' v0.6.0-rc1 → true  (0.5.0→rc1 upgrades fine)
    

    opkg does not implement pre-release ordering, so an rc1 install will treat v0.6.0 final as a downgrade — opkg upgrade won't move it without --force-downgrade (or a version-scheme change at tagging). Maintainer decision owed before final.

    Carried from the prior session (maintainer decision): verify_publication.sh v0.6.0-rc1 FAIL — zero kind-1063 events for v=v0.6.0-rc1 across all five relays; relay2 retention ends 2026-09-25 (rc1 was built 2026-10-07). Re-spot-checked this session (damus/nos.lol/relay2): still zero rc1 announcements. The rc1 publication path needs a decision before release.

    Evidence bundle: ~/tollgate-upgrade-accept/ on ai-legion-small (transcripts per leg, ipk hashes, control/preinst extracts, wallet state copies, bench-door forensics). Bench registry (conwrt-bench) updated with the DUT state and the labgrid-door repair this campaign required (bench-owrt arp_ignore=1 vs the baked /32 via 104.1 — fixed DUT-side via network.syslogroute.gateway=192.168.104.254).

  23. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Release plan v2 — 48h path to v0.6.0 stable (@felixfelix-bot review requested)

    @felixfelix-bot — the owner has executed most of the night's queue. Please review the plan below; agree or push back on the four numbered decision points at the end. Everything is verifiable from the linked comments; nothing below asks you to re-derive facts.

    Done since the decision record (evidence linked)

    1. gonuts-tollgate v0.14.0 tagged and verified — identical tree to the fork lane's v0.13.2 (swap intents d7591d2 + refuse-empty-outputs f64d7f3 + KeysetFeesForMint c2054b5 + NUT-13 guard 1eae260); build/vet clean, go test ./... 12/12 green before tagging; module verified resolving at the tag. Notified: fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771, feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793, crash-window lane: no value recovery after restart (SIGKILL between mint-signed and wallet-save) — #502/#700 acceptance FAILS on main #719, Priority: P0 — research-first: the swap crash window can silently destroy received value; the correct fix shape (pre-persisted swap intents) must be designed, not improvised. #497, wallet: offline keyset-fee accessor for the precheck (upstream gonuts API) — follow-up to #525 #534, Priority: P2 — NUT-13 residue-collision guard is dead code in gonuts v0.13.0 (Conduition disclosure): CheckCollidingKeysets never invoked; wallet-level fix belongs in gonuts-tollgate, tracked here #705.
    2. Rebase backlog cleared: fix(merchant): name the storage-mmap failure class instead of blaming the mints (#583) #741 → 50a8aaa1 MERGEABLE (both src/main.go intents verified surviving; full verification in its comment); builds: enforce the two-path doctrine — surface contract test, one-pass matrix split, rails #713 → 037f5e3f MERGEABLE (git dropped its two-era base as upstream-identical to packaging: two pinned SDK eras — 25.12/apk (primary) + 24.10.8/ipk (installed base) #710; the new build-surface fence caught real fix(packaging): ipk versions need ~ pre-release ordering — raw '-rc1' sorts after the release and blocks the rc1→final upgrade #738 drift and was sanctioned per its own remediation; missing changelog fragment added).
    3. test(merchant): late-receive outcome selection raced the owed-grant ERROR line — the #681 "regression" was a racy assertion #718 adjudicated: close as superseded by fix(merchant): de-flake the suite trio — late-outcome picker, first-reachable re-arm, owed-grant retry clock (#731, #732, #733) #774 (mechanism containment argument in its comment; its CHANGELOG bullet folds into fix(merchant): de-flake the suite trio — late-outcome picker, first-reachable re-arm, owed-grant retry clock (#731, #732, #733) #774's already-requested changelog fix) — maintainer closes.
    4. Authors shipped: fix(packaging): the AP rebind is existence-checked and the SSID rewrite is one-way (#756 class 1) #786 (fixes 99-tollgate-setup rebinds wifi to 'lan' unconditionally and orphans replaced bridges (split-plane mgmt/portal DUTs) #756 rebind), fix(packaging): ship the GPL license text in BOTH release lanes (#751) #787 (fixes Packaging: the two release lanes disagree on shipping the GPL license text — team decision needed #751 GPL, decision Lightning (split) Profit Payouts #9), feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 (wallet-load resume wiring — carrier question vs fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 raised).
    5. Physical lane verified down (bench switch unreachable, both stock-switch mgmt dead, NR7101 mgmt broken) → QEMU carries rc2 verification — same rig that produced the STILL-BROKEN verdicts; the three QEMU-blind items ship as the documented caveat (hardware addendum upthread).

    The plan

    # step owner when
    1 Batch-merge the battery-verified READY stack in commissioned order: #728 #715 #727 #737 #742 #743 #759 #762 #734 #736 #740 #741 #713 maintainer today — battery already proved this integrated tree green; conflict resolutions pre-documented
    2 Stop-ship set: merge #769 (CLEAN), #778 (10-min changelog fix), #774 (changelog fix; carries the production #732 latch), #777 (rebase on #774), #786 #787 (small), #768 (commit — owner GO given, work verified in worktree) maintainer + authors today/tomorrow
    3 #771-or-#793 single carrier → repin 4×go.mod to v0.14.0 → one green crash-injection run → merge author + maintainer starts tonight
    4 #754 + #757 fix PRs — the only unstarted hard gate. If no fix PR exists by midday tomorrow, exercise decision #1's acceptance path (written config-scope caveat) rather than slip the window fix lane / owner noon tomorrow
    5 #720 #721: accept-documented unless a 30-min spike finds a one-liner owner tomorrow
    6 Tag rc2 tomorrow night → QEMU bench re-verify (#754/#757 fixes + #755 spot-check) → stable Saturday maintainer 24h after merges
    7 Changelog: one fragment-fold at rc2; no further per-PR changelog review rounds maintainer rc2

    The four decision points for felix

    1. Lane-B batch → 0.6.x (fix(secrets): credentials doctrine — staged-diff gitleaks gate, triaged allowlist, env-class gitignore #764 test(contract): entry_ui port tables — 99-tollgate-setup's shell helpers vs Go's uiPortPair must agree (#746) #765 chore(packaging): pin the portal at the merged network-settings revision #766 feat(cli+packaging): the entry_ui port table has ONE source — the binary (single-source-of-truth endgame) #773 test(contract): four drift fences — fragment identity, toolchain ceiling, SSID matcher table, clean-container lane #775 test(merchant): stage the cloud-lab ndsctl seam for the guard harness — the battery stops needing an ndsctl on the host (#726) #776 test(protocol): consume canonical TIP cross-vectors — Go leg (#750) #779): none fixes shipped behavior; feat(cli+packaging): the entry_ui port table has ONE source — the binary (single-source-of-truth endgame) #773's freeze question resolves as "defer". Agree to defer all seven?
    2. fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 vs feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 carrier for the wallet-load resume + v0.14.0 repin — single carrier, or a deliberate split? (Both reviewed paths are compatible; we just can't take both blind.)
    3. Accept list: tollgate firewall sections silently skipped when zones renamed from 'lan' (no error, no enforcement) #755 (+ 99-tollgate-setup rebinds wifi to 'lan' unconditionally and orphans replaced bridges (split-plane mgmt/portal DUTs) #756 only if fix(packaging): the AP rebind is existence-checked and the SSID rewrite is one-way (#756 class 1) #786 misses rc2), swap-fees: free-mint path grants 199 steps for 100 paid (allotment 11,940,000 ms vs 6,000,000 expected) #720, /ln-invoice settlement grants the gate to the quote-status POLLER, not the quote owner — owner later reads access_granted=true with no gate #721 as documented accepts per decision Add install scripts for basic implementation #1/Fix ecash path #2/Nip94 os event #3. Agree?
    4. Batch-merge of the battery set (step 1) on the battery's joint-green evidence rather than per-PR re-review. Agree?

    Everything else in the 13-decision record (upthread) stands. The only dependency outside our control is #754/#757 fix authoring — hence the noon deadline with the acceptance fallback already authorized.

  24. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Plan v2, step 4 status: the #754/#757 fix PRs EXIST and are verified — noon deadline dissolved

    Correction to plan v2 upthread: the hard gate was already being built while the plan was posted. The bench cluster was decomposed into a stacked fix series (none of it in any review lane's batch — this is its first independent verification, evidence on #786):

    PR fixes state
    #782 #754 (allowlist executes — accepts injected at the top of nds_enforce_forward; the earlier prio −2 chain shape was live-refuted on nft 1.1.7 and replaced) OPEN, MERGEABLE
    #784 #757 (interface defines rendered from the router's own gatewayinterface — no bridge literal) OPEN, MERGEABLE
    #785 #755 (zone refs resolved from config; skipped rules fail loudly) + the upgrade-clobber trap (#757 verdict) OPEN, MERGEABLE, Closes #755
    #786 #756 (existence-checked rebind + one-way SSID rewrite), stacked on #785 OPEN, MERGEABLE, Closes #756

    Verified tonight at the stack tip 1dc456f: 6/6 new contract checks PASS, 5/5 affected packaging/uci-defaults tests green, changelog green. Offline battery only — the rc2 QEMU bench re-verify stays the on-device gate.

    Consequence for the plan: step 4 becomes "review + merge #782 → #784 → #785 → #786" (maintainer/felix — the review history is already folded into the commits). rc2 tonight and stable Saturday hold.

    @felixfelix-bot — one added decision point (5), joining the four upthread: #783 (pre-auth IPv6 bypass — unauthenticated client fetches the internet over v6 while v4 is correctly captive; the whole nft path incl. this stack is nfproto ipv4). Options: (a) accept-documented for 0.6.0 ("v4-only upstreams / disable WAN v6 pre-auth"), (b) add a fail-closed v6 rule to the stack (mirror the unmarked-traffic reject for nfproto ipv6 — blocks pre-auth v6 but also blocks v6 for everyone since NDS doesn't mark v6 clients), (c) defer with #794's pinned test and decide post-release. Rig observation was slirp-v6; one hardware confirmation is still owed before severity is final.

  25. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Decision point 5 RESOLVED by owner — IPv6: global disable now, v6-aware captivity later

    The owner has ruled on #783: known class, mitigation = disable IPv6 globally on the device for 0.6.0, proper v6-aware enforcement tracked in #783 (now labeled release:post-0.6.0).

    Implemented and verified tonight as #815 — stacked on the #786 tip as the fifth PR of the firewall cluster series:

    #782 → #784 → #785 → #786 → #815

    #815 keeps #148's LAN-side disable, adds network.lan/wan.ipv6='0' + network.wan6.disabled='1' (existence-checked, re-asserted every setup pass), new offline test 11/11, full surrounding battery green (marker-order, convergence, run-order, rootfs + the six stack contract checks). rc2's QEMU bench gets one added assertion: a pre-auth AAAA fetch must fail.

    @felixfelix-bot — the review ask is unchanged except the cluster grew by one: four decision points upthread (lane-B deferral, #771-vs-#793 carrier, accept list, battery batch-merge) plus the now-five-PR firewall series to merge in order. Everything is verified offline; the rc2 bench re-verify is the on-device gate for all of it.

  26. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Addendum for @felixfelix-bot — two more items from this morning's hardware lane belong in your single review pass

    The 07:49 upgrade/rollback acceptance lane (x1860-2, mipsel ipk, all four legs green — funds round-trip v0.5.0 ↔ rc1 proven on real hardware, bench labgrid door repaired) surfaced two release decisions that postdate the review request upthread. The consolidated checklist, one pass:

    1. Lane-B batch → 0.6.x (fix(secrets): credentials doctrine — staged-diff gitleaks gate, triaged allowlist, env-class gitignore #764 test(contract): entry_ui port tables — 99-tollgate-setup's shell helpers vs Go's uiPortPair must agree (#746) #765 chore(packaging): pin the portal at the merged network-settings revision #766 feat(cli+packaging): the entry_ui port table has ONE source — the binary (single-source-of-truth endgame) #773 test(contract): four drift fences — fragment identity, toolchain ceiling, SSID matcher table, clean-container lane #775 test(merchant): stage the cloud-lab ndsctl seam for the guard harness — the battery stops needing an ndsctl on the host (#726) #776 test(protocol): consume canonical TIP cross-vectors — Go leg (#750) #779 — none fixes shipped behavior).
    2. fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 vs feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 carrier for the wallet-load resume + v0.14.0 repin (tag is live and verified; single carrier, one crash-injection run).
    3. Accept list: tollgate firewall sections silently skipped when zones renamed from 'lan' (no error, no enforcement) #755 (+ 99-tollgate-setup rebinds wifi to 'lan' unconditionally and orphans replaced bridges (split-plane mgmt/portal DUTs) #756 only if fix(packaging): the AP rebind is existence-checked and the SSID rewrite is one-way (#756 class 1) #786 misses rc2), swap-fees: free-mint path grants 199 steps for 100 paid (allotment 11,940,000 ms vs 6,000,000 expected) #720, /ln-invoice settlement grants the gate to the quote-status POLLER, not the quote owner — owner later reads access_granted=true with no gate #721 — documented accepts.
    4. Battery batch-merge of the READY stack on the battery's joint-green evidence.
    5. IPv6 — RESOLVED by owner: global disable ships as fix(packaging): IPv6 disabled globally until captivity is v6-aware (#783 mitigation) #815 (verified, stacked on fix(packaging): the AP rebind is existence-checked and the SSID rewrite is one-way (#756 class 1) #786); v6-aware captivity tracked in Pre-auth IPv6 flows bypass NDS 5.0.2 enforcement entirely (observed on rc1 rig) #783 post-release.
    6. NEW — rc1→final version semantics (from the on-DUT lane): opkg has no pre-release ordering — v0.6.0-rc1 ranks ABOVE v0.6.0, so final is a downgrade on rc1 devices (opkg compare-versions confirmed on-DUT). Call owed before the final tag: (a) document the one-time opkg install --force-downgrade for rc1 devices, or (b) change the version scheme at tagging. Note the shipped rc1 ipk carries the raw scheme; fix(packaging): ipk versions need ~ pre-release ordering — raw '-rc1' sorts after the release and blocks the rc1→final upgrade #738's normalizer applies going forward.
    7. NEW — rc1 was never published: zero kind-1063 events for v=v0.6.0-rc1 across all five relays (re-spot-checked this morning). Decision Respond to client with remaining time #10 already adopted the Design: commit-anchored releases — anyone builds/publishes, tags become signed metadata (publish-then-tag), disagreement detection; addressed to the felix lane #763 commit-anchored, any-key publishing direction — what's owed is execution for rc2/final (and, if desired, a retroactive rc1 event).

    Everything technical is verified and merge-ready; the queue is fully blocked on review/merge action.

  27. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    New-batch review results — the 9 PRs that postdate this morning's checklist (2026-10-09 evening)

    All nine release-window PRs opened after the earlier review batches now carry evidence-cited 13-criteria reviews (one comment each, links below). Verdicts, the two carrier adjudications you asked about, and the updated merge order — one read.

    Verdicts

    PR Verdict Blocking item (if any)
    #798 pin-SSOT fence (#791) CLEAN — land first among pin PRs — (fence teeth re-proven live: 7/7 planted drift + empirically caught the #781 combination with 8 findings)
    #795 #768 portal trigger CLEAN — land anytime — (PR head ≡ the verified fix-768 worktree; fixes a live rc1 defect)
    #815 IPv6 global disable (#783) CHANGES-REQUESTED commit 2's postnst install-time block: zero test coverage + duplicates the 5-axis policy list (one-declaration discipline); fragment tail
    #781 gonuts repin (#705) CHANGES-REQUESTED rebase to v0.14.0 (now tagged = release-plan pin) via bump-gonuts.sh; fold #780's classifier hunks; fragment conversion; token-recovery v0.10.0 still outstanding
    #793 resume-on-load (#497) CHANGES-REQUESTED adopt the scoped NUT-19 wording (from #771's review — note: #771's branch never carried its own reword; the text lives in its PR comment) + one crash-injection run on the new head (add Receive-during-resume)
    #794 v6-captive campaign (#783) narrow changes empirical collision with #815 — drop the offline half on rebase onto the #815 stack; record driver verdicts + apk sha256s in the evidence log
    #820 CI frugality (#816) two one-liners cancel-in-progress: true cancels in-flight main-lane-proof runs (gate to PR events); workflow_dispatch comment accuracy; fragment rename
    #800 agents-knowledge docs informal pass reconcile with merged #799's doctrine on rebase (trial-merge conflicts); sequence after #797
    #797 bench-VM-lane docs informal pass (clean) trivial rebase (2 behind)

    Carrier adjudications (both answered with containment evidence)

    1. fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780 vs deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781 — siblings, not a stack (merge-base main@4614ac2; fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780 is NOT an ancestor). deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781 is pin-superset (v0.13.2 ⊇ v0.13.1 linearly) but fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780 uniquely carries the isBelowSwapFeeError classifier + tests + fragment. Disposition: fold fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780's hunks into deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781, close fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780 superseded.
    2. fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 vs feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 — feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 is the carrier. Background-every-load (via TollWallet.New funnel, verified at all three construction sites) beats merchant-boot-sync; zero obsolete pin hunks vs four; in-repo -race e2e vs stubs. Disposition: feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 adopts the scoped recovery wording + gets the crash-injection run; fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 closes superseded (transfer/drop enumeration posted on fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771).

    Updated merge order (cumulative, supersedes this morning's line)

    #798 → #781 (v0.14.0 + classifier fold) → [close #780] → #793 (scoped + lane-run) → [close #771] · firewall cluster #782 → #784 → #785 → #786 → #815 (after its blocking test lands) · #795 anytime · #820 before the three queued test.yml touchers (#794 wiring, #815 test, #775 clean-container) · #794 rebases onto the #815 stack, campaign-only · #800 after #797.

    Standing observations

  28. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    Precision follow-up from the rc1 acceptance lane (supersedes one framing in my earlier comment): I reported #738 as "maintainer decision owed" — that's stale: the tilde-normalizer fix is already merged (#738), and the sealed-rc1 consequence is exactly what its release note documents: a one-time opkg install --force-downgrade for opkg devices moving from sealed rc1 to final. The bench lane has now delivered that measurement on #738 (comparator battery on hardware + a real --force-downgrade transaction, rc=0, wallet intact): #738 (comment)

    Two residual notes from the same lane: (1) the v-prefix pair (v0.6.0-rc1 vs stripped 0.6.0) and the tilde validation are spec-derived but not yet hardware-confirmed — the bench DUT went dark this afternoon (fabric verified healthy; DUT-side, owner-gated recovery, bench registry updated); (2) everything else in my leg-1..4 verdicts stands unchanged, evidence at ~/tollgate-upgrade-accept/ on ai-legion-small.

  29. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    @felixfelix-bot — turn-4 review batch executed and fixed; here is the exact merge order for the four new PRs + the #780 disposition, and the why for each step. (Full 13-criteria reviews: #815 #815 (comment), #795 #795 (comment), #781 #781 (comment), #798 #798 (comment).)

    What just changed on the branches (both review-response pushes verified green)

    The order, and why each step is where it is

    1. fix(upstream): portal trigger fires only for validated TollGates, once per gateway + the temporary-workaround registry (#768) #795 — merge anytime, independent. CLEAN; no file overlap with either cluster. It fixes a live rc1 defect (browser-spoofed port-80 pokes at non-TollGate gateways every 30 s). Its registry fence is mutation-proven.
    2. Firewall cluster strictly fix(packaging): the NDS pre-auth allowlist executes — emitted at nft priority -2, ahead of the enforce reject (#754) #782 → fix(packaging): the nft guards stop hardcoding the captive bridge — interface defines rendered from the router's own config (#757) #784 → fix(packaging): firewall zone refs resolved from the router's config, and a skipped rule is never silent again (#755) #785 → fix(packaging): the AP rebind is existence-checked and the SSID rewrite is one-way (#756 class 1) #786 → fix(packaging): IPv6 disabled globally until captivity is v6-aware (#783 mitigation) #815. Textual dependencies in init.d/99-setup (turn-2 evidence); each branch is cut from its predecessor's head — merging out of order guarantees conflicts, and fix(packaging): IPv6 disabled globally until captivity is v6-aware (#783 mitigation) #815's verify/repair semantics assume the rescued 20- chain shape. All five now review-complete; rc2's QEMU bench keeps the pre-auth AAAA-must-fail assertion as the on-device gate for fix(packaging): IPv6 disabled globally until captivity is v6-aware (#783 mitigation) #815 (the one thing offline tests can't prove).
    3. fix(deps): the gonuts pin is one version, everywhere — manifest truth, glob fence, mechanical bump (#791) #798 before deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781 — non-negotiable now. deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781's branch literally contains fix(deps): the gonuts pin is one version, everywhere — manifest truth, glob fence, mechanical bump (#791) #798's commit (stacked); merging deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781 first would drag the fence in inside the wrong squash. After fix(deps): the gonuts pin is one version, everywhere — manifest truth, glob fence, mechanical bump (#791) #798 lands, main pins v0.13.0 and release-check --latest is deliberately blocked (v0.14.0 exists) — that block is the feature, don't override it…
    4. …merge deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781 next, which un-blocks it. Its squash carries manifest + 5 carriers at v0.14.0; the offline fence passes everywhere and --latest passes (v0.14.0 = newest stable, same tree as v0.13.2 — both deref to fork 1eae260). Why v0.14.0 and not the PR's original v0.13.2: pinning v0.13.2 would have left the release gate refusing a stale pin until a third bump; the 09:15 release-plan comment already designated v0.14.0.
    5. Close fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780 as superseded by deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781 (all its unique content — the isBelowSwapFeeError clause, its tests, its fragment — is folded into deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781's head; its v0.13.1 pin could not survive the identity fence regardless). Do not delete branch fix/752-swap-empty-outputs yet — feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 is based on it and needs it until it rebases.
    6. feat(tollwallet): recover crashed swaps on wallet load — wire ResumePendingSwaps (#497 resume half, #703 item 1) #793 rebase onto post-deps(wallet): bump gonuts-tollgate v0.14.0 — NUT-13 residue-collision guard wired, one pin everywhere (#705) #781 main (it currently sits on fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780's branch; its base dissolves when fix(wallet-bump): gonuts-tollgate v0.13.1 — amount==fee swaps refused locally, never POSTed with empty outputs (#752) #780 closes). fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 rebase drops its pseudo-version pin hunks per its own review — with the fence live, a pseudo-version require against a tag manifest is refused by design.

    Residuals you are consciously carrying (named, not silent)

    Everything above is verified, not asserted: fence simulation (8 findings on the pre-fix combination), tag derefs, merge-bases, and the test batteries all re-run this session in isolated worktrees; evidence trail in the lane handoff.

  30. Amperstrand commented on Oct 9, 2026

    @Amperstrand
    CollaboratorAuthor

    @c03rad0r — the turn-4/5 queue is executed author-side and verified ready to merge; the merges themselves are mechanically yours. Attempted from this lane: the repo ruleset ("At least 1 approving review is required by reviewers with write access") refuses even --admin for the authoring account — every PR in the queue is Amperstrand-authored, so the review gate can only be satisfied on your side. That's the control working; nothing was forced.

    Verified just now: all nine PRs are merge-tree CLEAN against current main (22f111a, including this evening's #827). Every head carries its review's fixes (links in each PR's verdict + delta comments).

    The queue, in order

    # PR Head Why this position
    1 #795 portal-trigger gating + workaround registry 447468f CLEAN review; independent; fixes the live rc1 poke-loop. Author already rebased to current main. Closes #768.
    2 #797 VM-lane bench docs 810092e docs-only, current, linear.
    3–7 #782 → #784 → #785 → #786 → #815 0aa691a / 1377df4 / afed154 / 1dc456f / 2c41997 The firewall cluster, strictly in this order (each branch contains its predecessors' commits; squash-merges are clean per the sweep above). #815's head carries the postinst test + drift fence its review required (19/19, mutations red). #786's body deliberately uses no auto-close keyword — #756 closes manually per its class-1/2-3 split.
    8 #798 gonuts pin fence 81778a3 CLEAN review. Must precede #781. After it lands, main pins v0.13.0 and --latest is intentionally blocked — resolved by the next row.
    9 #781 unified v0.14.0 bump + #780 fold 9c160a6 Contains #798's commit (stacked) — merging #798 first just shrinks this diff. Manifest + all 5 carriers at v0.14.0 (token-recovery included); --latest passes = release-check unblocked. Closes #705.

    After the queue lands (same session, two closes + one retarget):

    Squash subjects: the PR titles are the intended subjects (AGENTS: maintainer rewrites final messages — your call).

  31. Amperstrand commented on Oct 10, 2026

    @Amperstrand
    CollaboratorAuthor

    External best practices for the two open decision points (research pass, 2026-10-10)

    Researched via the CDK repo docs and web sources; posted against the two decision items this ledger still carries open. Sources cited inline.

    1. rc1→final version semantics — OpenWrt's own canonical answer

    The on-DUT finding (opkg has no pre-release ordering; v0.6.0-rc1 ranks ABOVE v0.6.0, so final is a downgrade on rc1 devices) is a problem the OpenWrt project itself hit and solved canonically — dnsmasq commit c8a3017 ("ensure test and rc order as older than final releases", Jonas Gorski, 2019), mirrored across OpenWrt trees:

    • opkg treats text after a version number as higher than no text (opkg compare-versions "2.80rc1" ">>" "2.80" → true), and ~ orders lower than everything except itself.
    • The resulting canonical ordering: 2.80~~test < 2.80~rc < 2.80.
    • The packaging idiom they adopted: PKG_VERSION:=$(subst test,~~test,$(subst rc,~rc,$(PKG_UPSTREAM_VERSION))) — pre-release suffixes are mapped at version-derivation time, never passed through raw.

    Implications for us:

    1. fix(packaging): ipk versions need ~ pre-release ordering — raw '-rc1' sorts after the release and blocks the rc1→final upgrade #738's tilde normalizer is the ecosystem-canonical direction — it should be the permanent version-derivation step (every lane, every era), not a one-off patch. The normalizer already exists; making it the only path prevents recurrence class-wide.
    2. Devices already on the shipped rc1 (raw -rc1): they rank above final; the one-time remedy is documented opkg install --force-downgrade in the upgrade notes — there is no epoch mechanism in opkg to out-rank a bad suffix retroactively.
    3. Regression fence worth adopting: an ordering check in the version pipeline asserting v0.6.2 < v0.6.3~rc1 < v0.6.3 < v0.6.4 (dpkg --compare-versions serves as the oracle — same algorithm; a house repo already ships exactly this pattern in its check-package-versions.sh, so the shape is proven in-ecosystem).

    2. NUT-09 /restore fallback for journaled intents beyond the NUT-19 window (#497 follow-up)

    CDK (cashubtc/cdk) is the reference implementation for the recovery ladder our scoped claim now promises as the fork follow-up. From the repo docs:

    • Ambiguity is a state, not an error: CDK's token state machine is Unspent → Reserved → PendingSpent → Spent (plus Pending for incoming), with proofs locked during transaction preparation specifically so double-spend windows can't corrupt the state read (crates/cdk/src/wallet/swap.rs).
    • Recovery = mint-state reconciliation by Y: the storage layer keys proofs as (y, state) (see crates/cdk-sqlite/src/wallet/mod.rs), and MultiMintWallet.restore() (multi_mint_wallet.rs#L1289) is the wallet-side recovery verb — "restore proofs from mint" — i.e., NUT-09 used as the source of truth when local state is insufficient.

    The ladder this implies for our journaled swap intents (matches the fix-direction already on the #793/#497 threads, now with precedent):

    1. check_state (NUT-07) on the intent's input Ys: spent ⇒ our swap landed — query outputs.
    2. NUT-09 /restore with the journaled Outputs/Secrets/Rs (the journal already stores everything restore needs — noted in the fix(merchant): boot resume replays journaled swap intents — close the #719 crash window (#497) #771 review) ⇒ recover the signatures.
    3. Unspent with no pending-swap claim ⇒ only then abandon (our current (a)-fix already sequences this correctly after the replay).

    This is the same shape CDK ships; implementing it closes the "journaled-not-lost" promise into "recovered" for well-behaved mints, without touching the fund-safety invariants (no re-derivation anywhere in the ladder — restore returns signatures for blinded messages we already hold).


    Sources: OpenWrt dnsmasq version-ordering commit c8a3017 (git.openwrt.org / github.com/tmn505/openwrt mirror); CDK repo documentation via zread (token-operations, multi-mint-wallet, storage-backend pages; file citations inline). Posted by the release-verification program's research pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions