claim_and_redeem retried every redeem failure indefinitely, including a
terminal one: mint error 11001 "Token Already Spent" (a claim replayed by a
relay-watermark edge case, or already redeemed by an earlier run). On
archy-x250-pa3 this pinned pending_claims at 1 forever and hammered
mint.minibits.cash's swap endpoint every ~6s, with the UI permanently
showing "a payment arrived but couldn't be redeemed yet".
- mint_client: expose the NUT error-code-11001 message as
ALREADY_REDEEMED_MSG so callers can recognize it without duplicating the
string.
- minibits: drop (not retry) a redeem failure that matches
is_already_redeemed — the value was already swept, so retrying can never
succeed.
- fetch_relay_dms: query the primary relay.minibits.cash alone first,
falling back to the public relay.damus.io/nos.lol only if it's
unreachable, and page past a 200-DM backlog instead of silently
stranding older DMs behind an un-advanced watermark.
This fix already existed on feat/minibits-lnurl-receive (4e410d7, 489995c,
2026-09-09) but that branch was never merged into main, which has its own
independently-diverged minibits.rs — so the bug shipped again in
1.8.16-alpha. Ported directly onto main's current implementation this time.
Immediate unblock on archy-x250-pa3: cleared the one poisoned
pending_claims entry from wallet/minibits.json by hand (already-redeemed,
zero value at risk) and restarted archipelago.service; confirmed via
journalctl that polling is quiet again.
See docs/incident-2026-09-15-minibits-already-redeemed.md for the full
writeup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a verification checklist (service running, nodogsplash bound to
br-tollgate not br-lan via the rendered config not just UCI, LAN/SSH
untouched, mint probes succeeding) plus notes on the dev-build test-mint
injection and the default-route race between a router's LAN interface
and the node's other uplinks before the router's own WAN/WISP is live.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
Archipelago's Connect form only authenticates with an existing password;
it has no flow for setting one on a fresh, passwordless router. On the
node's kiosk display there's no visible tab bar, so Ctrl+T to open a new
tab to LuCI is the way to set it before Connect will work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
Observed live on archy-x250-pa3: right after WAN reconnects (fresh
boot or WAN reconfigure), the first Install attempt can fail with
"apk update failed ... router may have no internet access" purely
because the WiFi-uplink STA association hasn't finished yet — it's
not a real error, just retry a few seconds later. Also cross-referenced
the now-fixed /usr/bin/opkg hardcoding bug for anyone hitting it on an
older build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
Step 4 described a single "Provision TollGate" action that prompts for
price/step/mint upfront. The real UI (OpenWrtGateway.vue) doesn't work
that way: "Install TollGate" is a one-click action with no config form
that installs with defaults, and price/step/mint/enabled are only
editable afterward via a separate "Edit" panel. Caught while walking
through a live install.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
Worked example for the Beryl AX (GL-MT3000, mediatek/filogic) verified
against the OpenWrt wiki and firmware selector: exact sysupgrade image
filename, GL.iNet UI / LuCI flash path, post-flash SSH state, and the
U-Boot recovery procedure if the flash goes sideways.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
Walks a node operator through pairing an OpenWrt router over SSH,
running the WAN/WISP wizard, and provisioning TollGate pay-as-you-go
WiFi — plus an RPC/architecture reference for developers. Distills
the openwrt crate, RPC handlers, and Vue panel into user-facing steps
that didn't exist anywhere in docs/ before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
LND normally reconnects channel peers after a restart, but not reliably:
after long or repeated downtime (an app update, a node reboot,
reconciler churn) the peer link can stay down for hours while BOTH
endpoints keep the channel flagged disabled in the routing graph. The
node looks perfectly healthy, the wallet shows balance, and every
payment in either direction fails "no route to the recipient" —
observed live on framework-pt (2026-09-01): its only channel sat
disabled on both policy sides for ~17 hours after the LND 0.21.2
update, while shorty had 583k spendable and the user was told, by a
mis-mapped modal, that they had 'no payment channel'.
The channel graph is desired state — every open channel should have a
live peer connection. A daemon-side watchdog now enforces it:
- every 2 minutes, list channels + peers over LND REST
- for each channel whose remote peer is not connected, look the peer's
advertised addresses up in the public graph and dial one
- per-peer retries throttled to 10 minutes so an unreachable peer is
not hammered; 'already connected' counts as done; a peer with no
advertised address is logged once per pass (cannot be dialed)
- no-ops quietly on nodes without LND (missing macaroon) and while a
wallet is locked (503 body has no channels)
Unit tests pin the selection against the live REST shapes
(remote_pubkey in /v1/channels vs pub_key in /v1/peers).
v1.8.10 CHANGELOG + What's New entries staged so the next release run
is clean first time.
Root-cause table, fix inventory, regression-test inventory and the
deploy/live-verification checklist for today's fleet incident — written
as the working record while the fixes land, so the deploy + verify steps
can be checked off against real nodes rather than memory. CHANGELOG
carries the user-facing notes for the release these fixes ship in.
Full state at session end (all public surfaces verified byte-identical
at 0.5.28/vc48; only node web-bundle redeploys outstanding), the feature
map, and the operational playbook next sessions need: Tor SOCKS proxy
for Gitea API/curl (the 'unreachable API' was a missing proxy flag),
token scopes, protected-main ship flow via -ship branch + PR + API merge,
stale local main lineage, the foundation server's two surfaces, demo CI
auto-redeploy, build/test commands, and the open items.
For the archi-dev-box agent: companion 0.5.28 is on main (PR #149) and
Gitea raw serves it (verified byte-identical, v1+v2+v3). Remaining: the
foundation server's static /packages mirror (the real-node QR download
URL — currently 0.5.27), node web-bundle redeploys (same as the
2026-07-23 flow), and confirming the demo stack flipped after CI's
webhook redeploy. Exact commands, expected shasum, and final verify
block included.
The companion-agent queue from the 2026-08-30 handoff, complete:
- Backup & Restore: hub sub-page, SAF export/import sealed in the
node's ADR-005 envelope (Argon2id + ChaCha20-Poly1305, byte-compatible
with core backup.rs), merge-only restore, no cloud.
- Remote Signer: the phone is the NIP-46 bunker — nsec generate/import,
nostrconnect:// QR pairing (scanner + OS deep link), per-request
approve/deny card, NIP-44 v2 transport with NIP-04 receive fallback,
wire-faithful to rust-nostr's reference bunker. Crypto pinned to the
official NIP-44 + BIP-340 vectors; e2e harness included.
- #61 residual: banner + manual intro trigger + overlay all gate on
isCompanionApp() (web-side, vitest-covered).
- Hub modal: new sub-pages like Nodes/FIPS, 70% height cap, node ULA
display/copy in the Nodes list, fipssh Termux helper (npub→ULA is a
pure public-key function — verified against the fips crate).
Issues #61 (comment), #128, #139 closed on the tracker.
Verified against the fips crate source: the mesh ULA is a pure function
of the PUBLIC key — fd || sha256(x-only pubkey)[0..15] — so the npub is
the durable address and needs no resolver. Android/tools/fipssh wraps
ssh for Termux: 'fipssh user@npub1…' derives the ULA (pure-python
bech32 + sha256, checksum-validated, typo protection) and execs ssh
over the companion's split tunnel; --resolve prints the ULA alone.
The derivation is pinned by a new Rust test
(npub_derives_the_same_mesh_ula_as_the_fips_identity, 3 seeds against
fips::Identity) and the helper's output was verified byte-identical
against a live fips identity pair. SSH-over-mesh handover updated with
an addendum: node docs/UI can advertise npub-based addressing, no
node-side DNS needed for this case.
Today's field test: Termux over the companion's split tunnel reaches the
node's fips0 ULA and gets RST — the mesh path works end to end, port 22
is refused by the node (fips0 default-deny, no 22 in the fips.d drop-ins;
sshd IPv6 listening unverified). The interim manual unblock (a
source-restricted 90-ssh.nft drop-in) is documented, but the real ask is
a first-class 'SSH over mesh' settings toggle in the node UI, with the
drop-in lifecycle owned by the daemon, a source-scope decision (paired
phones vs any mesh peer), sshd preflights, and an acceptance checklist.
Companion side is done (device-wide split tunnel + the node ULA now
displayed/copyable in the hub Nodes page) — the node agent is downstream.
Field feedback on 0.5.28: the standalone Backup/Signer screens were hard
to read over the synthwave background, back left the app instead of the
menu, and they broke the hub's one-container interaction model. Both are
now hub sub-pages exactly like Nodes/FIPS:
- BackupSection / SignerSection (ui/components) render inside the NESMenu
panel with the menu's own dark glass surface, scrim, and palette — the
readability and theming problem disappears with the standalone surface.
- The header back arrow returns to the hub card page (same as Nodes).
- The panel height cap drops from 92% to 70% of the screen — ~15%
breathing margin top and bottom; content scrolls inside.
- The pairing QR scanner is hosted by NESMenu OUTSIDE the panel
(QrGlassModal is a full-screen Box, not a Dialog — inside the panel's
scroll it would clip), and decoded nostrconnect:// URIs funnel into the
signer section through the same latch as the deep link.
- The nostrconnect:// deep link now routes to the session and pops the
hub open on the signer sub-page (SignerLaunch singleton) instead of a
dedicated route; standalone screens and routes removed.
- BunkerManager.refreshState is now a proper suspend fun (was
runBlocking on the caller's dispatcher).
Docs updated to the new locations. Rebuilt for on-device testing
(v0.5.28-debug/vc48, same signing cert).
Companion 0.5.28 (versionCode 48), the companion-agent queue items:
#128 Backup & Restore — the phone side of losing your phone or wiping it
to cross a border. Hub card → SAF export/import of an encrypted .json:
everything the app holds (servers+passwords, FIPS identity/peers, signer
key) sealed in the node's ADR-005 envelope (Argon2id + ChaCha20-Poly1305,
native backup.rs — same blob layout as the node's, node-shaped envelopes
decrypt too). Restore is merge-only: servers upsert npub-first, identity
and signer key adopt only when absent, peers union by npub. No cloud, no
telemetry — the file goes wherever the user saves it.
#139 Remote Signer — the phone IS the NIP-46 bunker. Generate/import a
nostr key, scan a nostrconnect:// QR (in-app scanner or deep link), and
approve/deny each sign_event request from a legible card (kind label,
content, tags, time) — nothing signs without a human. Wire-faithful to
rust-nostr's reference bunker (connect-carrying-secret handshake, NIP-44
v2 transport with NIP-04 receive fallback, kind-24133 responses);
get_public_key/describe/ping handled, everything else 'not authorized'.
Session state in BunkerManager, UI in SignerScreen, hub card wired.
Plus NativeCore (JNI object for the new native surface), FipsPreferences
peers-merge for restore, nostrconnect:// intent filter, and the release
docs (companion-backup-restore.md, companion-nip46-remote-signer.md).
Also Android/tools/nip46-test-client.py: a pure-Python NIP-46 client that
plays the node's login role (QR, handshake, get_public_key, sign_event)
and verifies the phone's signature with an independent BIP-340 — the
end-to-end test for the feature until node-side lands. Its crypto matches
the official NIP-44 + BIP-340 vectors byte-for-byte, the same vectors the
Rust core passes, so the two interop by construction.
Built + smoke: assembleDebug v0.5.28/vc48, same signing cert as the
served 0.5.27 (d622e07e…644d) so it updates in place.
The QR-decoder option doc was written on an offline machine with the
Maven coordinates and wrapper API flagged as from-memory. Verified
against Maven Central + the wrapper source: artifact is
io.github.zxing-cpp:android:3.1.1 (current release), Format.QR_CODE is
nested inside BarcodeReader (not a top-level BarcodeFormat), options are
a constructor-argument data class, and read(ImageProxy) handles the
Y-plane/cropRect/rotation itself. Sketch updated accordingly; the option
itself stays NOT-actioned pending the move-to-the-code decision trigger.
The RELEASE_NOTES_BACKLOG gate for cutting the next release, closed out:
- Eight sections backfilled to the curated standard, from the Settings
What's New blocks, the old-lineage release commits, and the hotfix
diffs: v1.7.44 (was four raw commit-hash lines), v1.7.47/48/64/65
(thin), and v1.7.50/51/107 (real tagged releases whose sections were
missing entirely — v1.7.107 restored verbatim from the curated copy
at 35e9c624 that later went missing).
- Mechanical inventory across all 92 sections in range: every section
now has ≥3 curated bullets, zero raw-hash entries.
- What's New modal regenerated for the three restored versions
(sync-whats-new --check passes, 92 versions present).
- Manifest-notes-only confirmed by construction: the manifest reads its
changelog from CHANGELOG.md and check-release-manifest.sh rejects raw
or thin entries before publishing.
Evidence trail for the backfills is recorded in
docs/RELEASE_NOTES_BACKLOG.md.
Hands the companion-owned work to the companion agent with precise
pointers (Android/ source, served APK at 0.5.27/vc47 + the deploy
pipeline from the 2026-07-23 handoff, the ArchipelagoNative bridge and
isCompanionApp gating pattern) and the queue: the ungated
CompanionBanner/intro-trigger residual of #61 (Discover.vue:156,
useCompanionIntro's openCompanionIntro), GrapheneOS backup/restore (#128,
reusing the node's ADR-005 backup envelope), and the NIP-46 remote-signer
phone side (#139, with the signer-login research doc as background).
Tracker labels applied earlier: #128 and #139 carry 'companion-agent'.
kdump + rasdaemon on every node, per docs/kdump-rasdaemon-design.md with
the approved decisions: hang capture ON (a wedged kiosk dumps and reboots
itself instead of sitting dead), crashkernel=256M, backfill ships with
this release, phase-2 UI surfacing deferred.
Host fixups (docs/system-level-ota-design.md) are the general answer to
'deliver system-level updates OTA': curated OS packages, sysctl drop-ins,
service enablement and the GRUB crashkernel line, carried by the signed
binary and applied idempotently at startup — non-fatal by construction
(offline/locked-dpkg nodes converge on a later boot), skipped on dev
boxes and non-Debian hosts. This formalizes the polkit/audio repair
precedents into a channel with a stated policy: pinned packages and
parameter intent only, never dist-upgrade automation; the ISO bakes the
identical end state into fresh installs (next commit).
The one runtime limitation is honest: crashkernel memory can only be
reserved at boot, so the fixup writes GRUB, runs update-grub, and logs
that it takes effect on the next reboot.
tests/lifecycle/os-audit.sh gains section D — a graded baseline check:
FAIL if capture never landed, WARN if written but awaiting reboot, PASS
when reserved, policy live and rasdaemon recording. Section D runs
independently of RPC health: a wedged backend must not mask that the
node also stopped capturing evidence.
Verification: host_fixups unit tests 4/4; cargo fmt clean; full suite
runs in the release gate (create-release) and the archi-dev-box
lifecycle gate before the tag.
Gives stable names to what issue #134 showed gets conflated: Trusted peer
(invite-verified, operator decision), Discovered peer (learned from a
Trusted peer's advertisement, hard-capped at Observer — TRUST IS NOT
TRANSITIVE), Routing hint (what a Discovered peer actually contributes:
reachability, not trust), and Peer advertisement (the mechanism itself,
a feature not a leak).
Records the two rules that make the model sound (trust requires a
traceable operator decision; discovery is transitive, trust is not), why
advertisement exists (one invite makes a node reachable to the trusted
set without granting anything), and the deferred open questions: the
'don't advertise my peers' privacy toggle and UI tier vocabulary.
Design for capturing post-mortem and hardware-error evidence on fleet
nodes: kdump (crashkernel=256M, dump to /var/crash on the unencrypted
root — never the LUKS data partition, so the crash kernel never handles
key material; makedumpfile-compressed, keep-2 retention) and rasdaemon
(EDAC/ECC events into sqlite on the same root).
Deliberately phased: phase 1 = capture on the image + bootstrap backfill
for existing nodes (kernel cmdline can't travel by OTA; takes effect on
next reboot); phase 2 = a read-only system.diagnostics surface in the
UI, only after a fleet node has produced a real dump.
Four decisions flagged in the doc: hang-capture on/off (recommended ON
— a wedged kiosk is useless anyway, and this turns every freeze into
evidence + self-reboot), crashkernel size, backfill timing, and phase-2
scope. Implementation touchpoints listed (Dockerfile.rootfs,
auto-install.sh:1810 cmdline, kdump-tools config, bootstrap, lifecycle
gate assertions).
Flags the app update policy already noted as unresolved in
app-developer-guide.md, adds a section for SSH access strategy, and
starts an app wishlist (Cashu wallet, phoenixd) for packaging.
Nodes offer an update when the signed catalog pins something newer than
what's running, and that machinery is fine. The missing step was the one
before it: nothing told *us* when upstream shipped. A pin could sit at
fedimintd v0.10.0 for months while every node in the fleet correctly and
confidently reported "up to date".
The reason nothing could tell us is that a manifest records only our
mirror — `source.archipelago-foundation.org/lfg2025/fedimintd:v0.10.0`
says nothing about the project it was mirrored from. So this adds an
optional `app.upstream` block naming the real source, and a script that
asks each one what it has released.
Running it answers the question that prompted this. Of 58 apps, 28 are
behind, including LND v0.18.4-beta against v0.21.2-beta, Bitcoin Core
28.4 against 31.1, and fedimintd/gatewayd v0.10.0 against v0.10.1.
Two choices worth stating. An app with no `upstream` block is reported
as UNTRACKED rather than skipped — a silent skip is how this stayed
invisible, and before this commit all 58 were silently skipped. And a
suggestion prefers our own tag variant: telling someone pinned to
`postgres:16.13-alpine` that the newest tag is `18.6-trixie` is true and
useless, because swapping the base image is a different decision from
bumping a version.
Five apps are deliberately left untracked (barkd, immich-postgres,
indeedhub-minio, lightning-stack, pine-whisper): I could not establish
their upstream with confidence, and a wrong `repo` produces a confident
wrong verdict, which is worse than an honest gap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ecash gets its own BIP-39 mnemonic derived from the node master seed:
still covered by the node's recovery phrase, but portable into any NUT-13
wallet without exposing the master seed — so 'back up my ecash' is not the
same action as 'expose the key to everything'.
Surfaced exactly like the Lightning seed: the shared SeedRevealPanel, on
the app detail page and in Settings → Backup, behind the same
verify_reveal_auth password re-entry. Standard BIP-39, so the SeedQR tab
works (no aezeed flag).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scoped from the framework-pt keyset incident: adopt the `cashu` crate
(MIT, the crate CDK is built on) for the token codec, keyset ids, crypto,
DLEQ and NUT-13, while keeping ecash.rs's on-disk contract, MintClient's
Tor seam and error table, and our multi-mint routing. Records why the
full cdk WalletDatabase shim is the wrong trade over live funds, and how
ecash backup can derive from the node's existing seed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Some apps carry a complete account system and are broken by an upstream
challenge: git clients speak basic-auth (not browser cookies), and a
BTCPay checkout link handed to a customer must open for that customer.
Both were behind the gate's login page — the "non-browser clients need an
access token" gap disclosed in five consecutive releases.
- New manifest port policy `auth: open`: the daemon still fronts the port
exactly like `gated` (loopback pin, external binds, frame-header fixes,
app-down retry page, Tor upstream) but serves it without the login
challenge. Requires auth_rationale, same burden of proof as `none`.
Gitea 3001 and BTCPay 23000 declare it.
- Runtime operator override per app (security.set-app-gate → app-configs/
<id>.json "gateEnabled"), surfaced as Settings → app → Access control.
Wins over the manifest in both directions and applies on the next
request — no restart, and it works today on catalog-covered apps whose
signed manifest still says `gated`.
- The gate resolves policy per-request from the live port map, so a
toggle takes effect without waiting for the 60s rebind sweep. "Off"
never releases the port: gated apps are loopback-pinned, so releasing
would strand them, not open them.
- security.app-gate-status now reports gate_enabled + any override.
- New guard test pins the `auth: open` set (both entries reviewed); the
`auth: none` count moves 25 → 26, absorbing pre-existing drift from the
phoenixd onboarding (loopback JSON API with its own generated password).
- Docs: the manifest spec's ports row documented only host/container/
protocol — bind, auth, auth_rationale and session_passthrough were
undocumented. Added a full "Ports & the app gate" section plus a
developer-guide entry telling app authors to enforce their own auth
regardless, since the operator can flip the gate either way.
Verified live on archi-dev-box from an external address: gated → 401 gate
page; override off → Gitea 200 own page, BTCPay 302 to its own login,
git-over-HTTP info/refs 200; override on → 401 again; clear → default.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Companion tap/scroll/type now works INSIDE cross-origin app iframes and
kiosk tabs. The web relay synthesizes untrusted DOM events in the top
document, which can never cross an origin boundary — so apps served
through the appgate were dead to the remote. The kiosk Chromium now
exposes a loopback-only CDP port (default origin check intact, no
--remote-allow-origins) and a backend bridge (api/handler/cdp.rs)
dispatches validated companion input as Input.dispatchKeyEvent /
dispatchMouseEvent / mouseWheel — trusted events that hit-test through
any frame, move real focus, and insert text like a physical device.
- Session keeper self-heals across kiosk Chromium restarts; inert on
nodes without a kiosk unit (falls back to the existing relay path).
- The kiosk relay subscriber self-tags (?kiosk=1) and the backend mutes
its key/click/scroll messages while the bridge is live, so input never
applies twice; cursor moves still flow for the on-screen cursor.
- While companion input is active the native OS pointer is hidden
(cursor:none, auto-restores 30s after the last event) so the dead
physical-mouse cursor doesn't sit next to the virtual one.
- docs/tv-input-iframe-apps.md scope note updated: gamepad keys stay on
uinput; CDP is for companion pointer/typing only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- alby-hub + phoenixd icons re-set with the standard 12% inner margin
(they shipped edge-to-edge; every other icon carries whitespace).
- scripts/normalize-app-icon.py: wraps any third-party SVG mark onto the
house canvas — the system applies the tile plate (archy-app-icon)
automatically but deliberately no runtime inset, so the margin must be
baked; the guide now says exactly that.
- MarketplaceAppDetails: the icon now carries archy-app-icon like the
store tiles — the treatment no longer stops at the detail page.
- v1.8.2 changelog: third curated bullet (the ceremony gate requires 3).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An app whose frame never loads while its backend reports Running (the
embed-refusal signature: frame-busting JS, top-level-origin apps,
SameSite=Strict logins — everything the gate's header stripping cannot
fix) is remembered in localStorage; every later launch opens a tab
straight from the click (user gesture, so no popup blocker), and
opensInTab() gives it the tab-launch icon. A successful iframe load
clears the memory and entries expire after 7 days, so nodes that gain
embedding (gate improvements) get re-probed instead of being remembered
broken forever. Dev guide updated; v1.8.2 changelog + What's New curated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixd: the orchestrator treats bind paths containing a dot as file
mounts and never creates their source dir, so the image's default
/phoenix/.phoenix target crash-looped the unit (statfs: no such file).
Datadir moved to /data via PHOENIX_DATADIR; data_uid 1000:1000 matches the
image's phoenix user — without it phoenixd dies on phoenix.conf
'Permission denied'. Both verified end-to-end on archi-dev-box: orch
install OK, seed.dat + db on the host, authenticated /getinfo answers.
alby-hub: launch flips to embedded — pairs with the gate change that
neutralizes upstream frame blocking.
Dev guide: iframe embedding rules (who blocks framing and why the gate
may strip it; when open_in_new_tab is legitimate; test in the embedded
session, never a tab).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>