archy/docs/REMAINING-ISSUES-PLAN.md
archipelago 705e2436ba chore(ops,docs): first-boot containers, image versions, design docs, android remote-input
- first-boot-containers + image-versions for fmcd/fedimint
- dual-ecash, meshroller-integration, and remaining-issues design docs
- Android remote-input two-finger scroll + external-open handling

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:22:02 -04:00

225 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Remaining issues — implementation plans
Written 2026-06-17. Covers the open Gitea issues not closeable in the single-box
dev env. Each plan lists the files to touch, the approach, and how to verify
(most need .116 + .198, a companion phone, or funded wallets). Issues #3 (VPN)
and #5 (OpenWRT/TollGate) are intentionally out of scope per the user.
Status of the rest at time of writing:
- **#31** group chat over Tor — dedup-by-`msg_id` fix already shipped (open only
for a 2-node Tor confirmation). See its Gitea comment.
- **#43** install on .70 — blocked: .70 unreachable. Plan below is a code-side
hardening that doesn't depend on .70's logs.
---
## #46 — Pay for peer files (local wallet OR invoice+QR to seller)
> **Status (2026-06-17): Phase 1 DONE & compiles** (LN invoice + QR + release).
> Seller: `content_invoice.rs` entitlement store, `GET /content/{id}/invoice`
> + `/invoice-status/{hash}`, invoice-paid path in `serve_content`
> (`X-Invoice-Hash`), LND `create_invoice`/`invoice_is_settled`. Buyer:
> `content.request-invoice` / `.invoice-status` / `.download-peer-invoice` +
> `PeerFiles.vue` picker modal + QR + poll. Phases 2 (on-chain) and 3 (local
> LN/on-chain methods) remain; needs live funded-wallet verify. Issue left open.
**Goal.** At the paid-download step in Cloud → peer files, let the buyer choose
how to pay: (a) their local wallet (ecash today; LN/on-chain later), or (b) get
an invoice with a QR drawn on the **selling** node's wallet, pay from any
external wallet, and have the file release on confirmation.
**What exists already**
- Buyer ecash auto-pay: `content.download-peer-paid` (mints ecash, downloads
atomically) — wired in `neode-ui/src/views/PeerFiles.vue` `downloadFile()`.
- Payer-side builder: `streaming.prepare-payment` RPC + `wallet/ecash.rs`
(`build_payment_token`, cross-mint), `swarm/payment.rs`.
- Free streaming download: `/api/peer-content/:onion/:id` (Range-capable).
- LND invoice RPC: `lnd.createinvoice`; ecash balance: `wallet.ecash-balance`.
**Backend work**
1. **Seller-side invoice RPC** (new), e.g. `content.request-invoice`
`{ onion, content_id }` → asks the *selling* node (over the existing
`/archipelago/...` peer transport, same path machinery as
`content.download-peer-paid`) to produce a payment request for `price_sats`:
- LN: `lnd.createinvoice` on the seller, return `bolt11` + `payment_hash`.
- on-chain: `lnd.newaddress` on the seller, return `address` + `amount`.
- Seller records a pending entitlement keyed by `payment_hash`/address →
content_id → buyer.
2. **Payment confirmation + release**: seller polls its own LND
(`lnd.lookup-invoice` / address watch); on settle, marks the entitlement
paid. Buyer side polls `content.invoice-status { payment_hash }` → when paid,
downloads via the existing `/api/peer-content` (gate now passes because the
entitlement is satisfied). Reuse the streaming gate in `streaming/` — add an
"invoice-paid" path alongside the ecash-token path.
3. Keep `content.download-peer-paid` (local-ecash) as the (a) fast path.
**Frontend work** (`PeerFiles.vue`)
1. Before a paid download, open a small **payment-method picker** modal:
- "Pay from this node's wallet" → existing ecash flow (show balance; if
insufficient, the LN/on-chain local options when those land).
- "Pay from another wallet (QR)" → call `content.request-invoice`, render the
`bolt11`/address as a **QR** (add a tiny QR lib or reuse one already in the
bundle — check `package.json`), show amount + a live "waiting for
payment…" state polling `content.invoice-status`, then auto-download.
2. Reuse the existing `purchaseError`/`downloading` state + `triggerDownload`.
**Verify**: .116 (seller) + .198 (buyer), a funded regtest/LN wallet. Buyer
picks QR, pays from a 3rd wallet, file releases. Then the local-ecash path.
**Effort**: large (multi-day). Phase it: (1) LN-invoice + QR + release, (2)
on-chain, (3) local LN/on-chain methods.
---
## #18 — Companion app: "open in external browser" apps don't work
> **Status (2026-06-17): DONE & compiles (Rust + TS); Android unbuilt here.**
> Reverse relay hop added: `external_open_tx` channel, kiosk publishes
> `{"t":"o","url"}` on `/ws/remote-relay` (URL-validated), forwarded to the
> companion's `/ws/remote-input`. `requestExternalOpen()` in `remote-relay.ts`
> wired into all four `appLauncher.ts` external-open sites; `InputWebSocket.kt`
> + `RemoteInputScreen.kt` open it via `ACTION_VIEW`. Issue closed; live pairing
> test pending.
**Goal.** Apps configured to open in a new/external browser should launch on the
**phone** when driven from the companion controller, using the phone-default-
browser request pattern.
**What exists**
- Relay protocol in `neode-ui/src/api/remote-relay.ts` — message cases `m`
(move cursor), `c` (click), `s` (scroll, just fixed in #7). Click resolves the
element under the virtual cursor via `deepElementFromPoint`.
- The kiosk side runs the dashboard; "open external" apps currently try to
`window.open` on the **kiosk**, which the phone never sees.
**Approach**
1. **Detect external-open intent on the kiosk**: when a click lands on an
element that would open externally (anchor with `target=_blank` / an app
flagged `opensExternally`, or an intercepted `window.open`), instead of
opening locally, send a new relay message to the phone:
`{ t: 'open-url', url }` over the `/ws/remote-relay` channel (the kiosk is the
relay server side — find where it sends frames back to the companion).
2. **Companion (phone) side** handles `open-url` by doing `window.open(url,
'_blank')` / `location.href = url` so it opens in the phone's default browser.
- If the companion is the **Android APK** (separate codebase, see
`Android/` + memory `feedback_companion_apk_not_in_update`), add an
intent-based handler there; if it's a mobile web client, handle in JS.
3. Intercept `window.open` on the kiosk dashboard globally (a small shim that,
when remote-relay is active, forwards to the phone instead of opening).
**Verify**: phone + kiosk paired; tap an "open external" app from the companion;
it opens in the phone browser.
**Effort**: medium; needs the companion device + possibly an APK change.
---
## #50 — Integrate Meshroller into our mesh features
> **Decision made 2026-06-17: seam (a) — Rust-native lift.** Full design with
> verified seam anchors (message types, dispatch, send API, event/trust gates,
> Ollama call) is in **`docs/meshroller-integration-design.md`**. Summary below.
Source: https://gitea.l484.com/clasko/Meshroller
**Phase 0 — review (DONE 2026-06-17)**
- Reviewed. Meshroller is a single ~29KB Python script (`meshroller.py`): a
daemon that bridges a **Meshtastic** radio (via the `meshtastic` Python serial
module, `SerialInterface`) to an **Ollama** LLM (`qwen2.5-coder`). It has
trusted-node auth, scheduled/queued messaging, and command handling on mesh
channels. It is a **daemon**, not firmware or a library.
- **License**: in-house (our own developer) — no third-party license blocker.
- **Hardware/transport reality**: it rides **Meshtastic serial + a local
Ollama**. Our radio is **Meshcore** (Heltec V3) and our mesh stack targets
meshcore. The `meshtastic` module does NOT speak meshcore, so the script
cannot drive our radio unmodified.
- **Decision needed (architecture)**: per user, integration **must work with
meshcore**. Two seams:
- (a) Lift Meshroller's *behaviors* (LLM bridge, trusted-node auth, scheduled
messaging, command parser) into our Rust mesh stack as typed message kinds —
native to meshcore, no Python/Meshtastic dependency. Preferred for meshcore.
- (b) Package the Python daemon as a container app and add a meshcore serial
backend to it (keeps the script, but requires writing meshcore I/O the
`meshtastic` module doesn't provide).
This choice is the remaining gate; the rest of Phase 1 below stands.
**Phase 1 — choose the seam**
- Our mesh stack: `core/archipelago/src/mesh/` (`mod.rs` `MeshService`,
`listener/`, `protocol.rs`, `types.rs`). Decide:
- If Meshroller is a *protocol/feature on the same radio* → implement it as a
typed message kind in our `MeshMessageType` + `listener/dispatch.rs`
(mirrors how block headers / alerts are handled).
- If it's a *separate transport/daemon* → wrap it behind our transport router
(`transport/`) like FIPS/LAN/Tor.
- Reuse the event seam (`MeshEvent`) so the UI gets pushes (same path we just
wired for #48).
**Phase 2 — UX** (ties into `project_mesh_telegram_plan`)
- A dead-simple onboarding + usage flow in the Mesh tab. Define the 12 killer
actions and design the setup wizard.
**Verify**: 2 radios (the .116 Meshcore + a second).
**Effort**: multi-day; gated on the Phase 0 review + a license/architecture
decision.
---
## #15 — netbird app doesn't work (LOW PRIORITY)
> **Status (2026-06-17): DIAGNOSED LIVE on .198 + FIXED (option A shipped); login works.**
> THE real blocker: the dashboard needs a **secure context** —
> `window.crypto.subtle is unavailable` over plain http, so OIDC PKCE threw
> before login. Fix: proxy now serves **HTTPS** (self-signed cert at install,
> `8087:443`, all origins `https://`); frontend opens netbird in a **new tab**
> (self-signed-HTTPS iframe is blocked). Layered fixes also in `stacks.rs`:
> nginx `resolver <gateway>` + variable upstreams (IP-cache 502; `resolver
> local=on`/`${NGINX_LOCAL_RESOLVERS}` FAIL on nginx:1.27-alpine), LAN-IP
> canonical origin + CORS + multi-origin redirect URIs, `/nb-auth`+`/nb-silent-auth`
> SPA fallback (were 404), and a stale-store note (wipe to re-init). Also found:
> `conmon died` zombie containers (recreate fixes; #53). Validated on .198,
> registration+login succeed. Trusted-cert/iframe (option B) = #56;
> registry-app migration = #52. Existing nodes need a clean reinstall.
**Diagnose first** (likely a container/config issue, like other app fixes):
1. On a node: `podman logs <netbird container>` — capture the actual failure.
2. Check the app manifest + install path (`container/` install, env, ports,
the four iframe-sync places per memory `feedback_gitea_iframe_setup` if it
has a UI).
3. netbird needs a management URL / setup key — confirm whether the app expects
config we don't provide, or a host capability (TUN device / NET_ADMIN) the
rootless-podman setup lacks.
**Likely fix**: either supply the missing env/setup-key UI, or add the required
container capability. Low priority — schedule after the above.
---
## #43 — Install errors at DID-creation + password screens (.70); FIPS slow
`.70` is unreachable, so we can't read its logs. Code-side hardening that helps
regardless:
> **Status (2026-06-17): hardening DONE & compiles.** Root cause was a
> non-idempotent `seed.generate` that overwrote node keys under the client's
> retry storm on slow first boot. Fixed: idempotent generate + retry-safe
> verify (`seed_rpc.rs`), transient-vs-genuine error handling in
> `OnboardingSeedGenerate/Verify.vue`, and a non-blocking FIPS status on
> `OnboardingDone.vue`. Issue closed; full closure wants a fresh install on a
> reachable node + re-test on .70.
1. **Onboarding error surfacing** — in the seed/DID + password onboarding views
(`OnboardingSeed*`, the password step) and their RPC handlers
(`seed.generate` / `seed.verify` / `auth.setup`), make a *successful*
operation never show an error toast, and make genuinely-failed ops show the
real message + a retry — so cosmetic errors (op actually succeeded) stop
alarming users. Audit the promise/catch paths for races where a slow backend
resolves after a timeout fires.
2. **FIPS start delay** — confirm `spawn_post_onboarding_fips_activate`
(`api/rpc/seed_rpc.rs`) isn't blocking onboarding; it already runs detached.
Consider surfacing "FIPS starting…" status instead of letting it look stuck.
**Verify**: a fresh ISO install on a reachable node (.198 or a scratch box),
watch the DID + password screens; then re-test on .70 once reachable.
**Effort**: smallmedium (the hardening); full closure needs a repro node.