Commit Graph
149 Commits
Author SHA1 Message Date
archipelagoandClaude Opus 5 5c9d5dc424 docs(state): record the on-node verification outcome and what stayed open
Names the four open items explicitly, including the one that is easy to
lose: the reconcile fix is deployed but unexercised, so the node's 401
proves the template and not the delivery path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:22:49 -04:00
archipelagoandClaude Opus 5 5a21b58f69 docs(security): record what actually closed :8334, and what it does not prove
The node is closed and verified 401 with origin-scoped CORS. But an
unrelated bitcoin-ui rebuild at 18:36 cleared the stale conf before the
reconcile fix was deployed at 19:06, so the 401 proves a05956c4's
template and NOT the delivery path f6b5245b adds.

Window 14 closed (exposure gone, verified). Window 15 opened for the
delivery path, which is deployed but never exercised — bitcoin-ui is
still in the uninstall marker, so this node depends on that untested
path the next time its config has to change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:19:29 -04:00
archipelagoandClaude Opus 5 2684fa7cd1 docs(windows): close window 13 — host_secrets observed on a real node
system.stats on archi-dev-box returns host_secrets with verdict 'per-node'
and three evidence lines (machine-id anchor 2026-04-09; every SSH host key
and the TLS key newer than the anchor). Previously proven against the file
contract in unit tests only.

Honest limitation: this is one node, not the dev pair — archy-x250-dev has
been offline for two days, so the second node is unreachable, not skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:01:34 -04:00
archipelagoandClaude Opus 5 7f40fa1e93 docs(windows): record the live bitcoin-ui RPC exposure as window 14
Verified live on archi-dev-box, code fix committed in f6b5245b but not
deployed. Deliberately logged as open rather than fixed: the defect that
matters to an operator is the running node, not the source tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 18:45:33 -04:00
archipelagoandClaude Opus 5 a850bb6cdd docs(10-06): summary — Phase 10 complete, with the gate-effectiveness caveat
All five KEY-05 layers landed. Records the two things a reader would
otherwise get wrong:

- the 2 boot_reconciler test failures in the full-suite run are
  parallel-load flakes (4/4 pass in isolation), not regressions;
- layer (b)'s clippy gate is LIVE but not yet EFFECTIVE, because 42
  pre-existing warnings already fail the same -D warnings step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 17:00:14 -04:00
archipelagoandClaude Opus 5 689c4cca1a docs(10-04): complete fleet host-secret detection and rotation plan
SUMMARY for 10-04, plus three WINDOWS.md entries (11-13) so the unverified
items stay visible at ship time: the rotation never exercised on real
hardware, host_secrets never observed in a live system.stats, and the C-3
finding itself — three live nodes still on shared SSH host keys, two of them
also sharing a TLS private key, none of them rotated.

STATE.md and ROADMAP.md deliberately not touched: both carry other agents'
uncommitted work in this shared tree and the orchestrator owns them for this
wave.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:11:58 -04:00
archipelagoandClaude Opus 5 78b3ec879b docs(01-18): Task 1 deploy verified on archi-dev-box; six-fix sign-off still open
Deployed the frontend to archi-dev-box only (--frontend-only, no fleet,
no alpha-tester, no Tailscale, no OTA, no release) and proved all six
UIFIX fixes are in the bundle the node actually serves.

- Resolved the live chunk set from sw.js first: /opt/archipelago/web-ui/
  assets keeps every prior deploy's hashed chunks, so a naive disk grep
  returns hits from dead chunks and would have produced a false pass
  (threat T-01-83, and it was a real trap here).
- Fetched each live chunk over HTTP from http://archi-dev-box and grepped
  it: all eight probe strings for UIFIX-01..06 PRESENT.
- Real Chromium boot check on the node at 1440x900 and 390x740: app
  mounts, 0 console errors, 0 page errors, 0 failed requests.
- archy-x250-dev recorded as an explicit gap: offline, last seen 2d ago,
  no MagicDNS record; still has neither this plan set's nor phase 2's
  frontend.

Task 2's six numbered checks are all recorded NOT VERIFIED. They need an
authenticated session on the node (UI returns 401 / redirects to /login,
and no credential was guessed against a node holding real funds), and two
of them are not testable as the node stands: it owns exactly one
purchased item (image/jpeg) and has zero video and zero audio content
anywhere, so the purchased-video, purchased-music and picture-in-picture
checks have nothing to open.

No source file modified, no fix applied inline, and STATE/ROADMAP/
REQUIREMENTS deliberately left untouched - UIFIX-01..06 are NOT closed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 14:44:01 -04:00
archipelagoandClaude Opus 5 0214114c7b docs(10-02): summary — probe built and committed, C-6 still UNVERIFIED
Task 1 done. Tasks 2 and 3 are blocked on unmet preconditions and were
NOT auto-approved: no fleet node runs 10-01's gate (installed binary
predates 879de59e and lacks the refusal string), and no second machine
was available to probe from. Nothing moved UNVERIFIED -> VERIFIED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 14:26:57 -04:00
archipelagoandClaude Opus 5 cdad880629 docs(10,01): record the summaries for the four completed plans
Demo images / Build & push demo images (push) Successful in 3m15s
Written by the previous session's executors for 01-17, 10-01, 10-03 and
10-05, all of which are complete and whose code is already committed. The
session was cut off by a dropped SSH connection before these were staged,
so they were sitting untracked. Recording them so the phase history is not
lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 14:16:46 -04:00
archipelagoandClaude Opus 5 c5a82cba06 fix(credentials): mark the encrypted store so a random nonce cannot fake plaintext
The on-disk format was detected by sniffing the first byte for `[` or `{`.
Encrypted blobs begin with a random 12-byte nonce, so roughly 1 in 128
saves produced a valid encrypted file whose first byte was 0x5B or 0x7B;
those were misread as plaintext JSON, failed `String::from_utf8`, and the
store became permanently unreadable. This was surfacing as a flaky
`test_list_credentials_no_filter`, but it is a real data-loss bug: a node
whose ciphertext happened to start with one of those bytes could not load
its credentials.

Writes now carry a fixed `ARCHYCRED1` marker, which cannot collide with a
random nonce, so detection of the current format is exact.

Legacy unmarked files are detected by SUCCESSFUL AEAD DECRYPTION rather
than by another byte sniff. A verifying Poly1305 tag under the node key is
a cryptographic discriminator (~2^-128 false-positive rate), strictly
stronger than any structural guess — which is why the deferred item's
suggested "keep the first-byte sniff as the legacy fallback" was not the
shape adopted. Plaintext JSON remains the last resort, and is still
reachable on a node that has no node key at all.

An undecodable file now errors instead of returning an empty store, so a
transiently unreadable file is never silently replaced by an empty one
that the next save would commit to disk (CLAUDE.md: migrations never
destroy data). Legacy files upgrade on write, never on read.

Tests drive the collision deterministically via an explicit nonce rather
than waiting on the 1-in-128 draw, and cover all three on-disk
populations, the read-path-does-not-rewrite guarantee, and tamper
rejection. 28 passed, 0 failed.

Closes the 10-01 deferred item.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 14:15:01 -04:00
archipelagoandClaude Opus 5 6ed876376a docs(todo): onboarding step to name your node (sets the real hostname)
Backend already exists: server.set-name runs hostnamectl set-hostname and
regenerates the TLS cert with a SAN for the new name. This is a UI step.

Records the hazard that decides the design: renaming changes both the mDNS
.local name and the TLS cert, so a rename mid-flow can drop the user's session
in the middle of onboarding — potentially between seed generation and seed
verification. Placement is therefore a design decision, with three options laid
out (last-before-Done, first, or collect-early-apply-late).

Also flags RFC-1123 slugification (users will type "Dorian's Node"), whether
the rename propagates to the Reticulum display name and mesh surfaces, the
reconnection UX, and whether the step is skippable.

Sequenced after the in-flight regenerate_tls_cert atomicity fix, since renaming
is exactly the path that fix protects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 11:39:10 -04:00
archipelagoandClaude Opus 5 c502ff0e0a docs(todo): migrate VPS2 IP to domain across registry references
98 operational files still carry 146.59.87.168 — the domain was only adopted
for the git remote, not for container registry references. Bulk is app
manifests' image: lines, plus .gitmodules, both CI workflows, the signed
catalog.json, and two Android companion files with compiled constants. The 117
hits in .planning/ are historical records and stay.

Not a find-and-replace: the domain serves Gitea over HTTPS:443 while images are
pulled from :3000 over plain HTTP, and podman treats host:3000 and domain as
different registries — so every node re-pulls under the new name and any node
that can't resolve or trust the new host fails to pull. It also invalidates the
signed catalog (needs a re-sign ceremony) and the APK ships compiled constants.

Rollout order: registry serving on the domain → manifests → catalog re-sign →
APK rebuild. Steps 2-4 are actively breaking until step 1 holds.

Analysis from the concurrent agent's session before it ended; recorded so it is
not lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 08:45:35 -04:00
archipelagoandClaude Opus 5 5a11fa7588 docs(todo): capture companion 0.5.27 handover — node/web-side clipboard + QR work
Companion build 0.5.27 (versionCode 47) shims navigator.clipboard natively, so
in-app copy/paste is fixed with zero web changes — but the contract must not be
clobbered (no unconditional re-define, no Object.freeze).

Still open web-side: main.ts's fake readText() makes SendBitcoinModal's Paste
button render and silently no-op in plain-HTTP browsers; 30 writeText call
sites across three inconsistent patterns, ~10 of which toast 'Copied!'
regardless of success; scanner prewarm/torch/constraints/no-reinit.

Also records three factual corrections to docs/qr-scanner-snappiness-handover.md
(ZXing not ML Kit; FORMAT_QR_CODE + KEEP_ONLY_LATEST already in place; do NOT
drop to 720p — 1080p is a deliberate 0.5.22 fix for dense bolt11 QRs).

Routed at Phase 11: the signed-PSBT paste affordance and the scanner items are
the same surface as WALLET-05.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 08:27:03 -04:00
archipelagoandClaude Opus 5 90ce4bcc46 docs(10): correct F-10a's own overstatement — x3dh sites are identifiers, not key material
The F-10a scope correction committed hours earlier asserted semantics its
evidence did not support. The KEY-05 planner caught it against the code:

- mesh/x3dh.rs:100/:114 are u32 prekey IDENTIFIERS (spk_id, otk_id), not key
  agreement material. The X25519 secrets come from
  crypto::generate_x25519_ephemeral() at :99/:113 and were never in scope.
- session.rs's 16 raw matches read as 16 production token sites; #[cfg(test)]
  begins at :470, so it is 4 production + 12 test.
- wallet/bdhke.rs is 2 production of 4 (#[cfg(test)] at :143) — and those two
  ARE genuine key material: generate_secret() :133 and
  random_blinding_factor() :139.

The Medium rating still holds, on narrower grounds: bdhke's two production
sites plus storage_crypto.rs:39's AEAD nonce. It no longer rests on x3dh.

Struck rather than silently rewritten. F-10 was corrected on the grounds that
understatement misleads the next reader; overstatement does the same, and this
table managed both within a day.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 07:30:43 -04:00
archipelagoandClaude Opus 5 dba3a30af9 docs(10-06): plan KEY-05 crate-wide defaulted-RNG enforcement
Adds 10-06-PLAN.md covering KEY-05 (F-10a / R-16). 10-01..10-05 untouched.

Six tasks, sequenced so CI stays green at every intermediate commit:
classify all 43 call sites with file:line evidence; a tracer that wires the
sealed KeyGenRng allowlist, the degenerate-entropy predicate and the CSPRNG
readiness ledger end-to-end through the mnemonic seam; two migration tasks;
a blocking human checkpoint for cargo-deny scope and legitimacy; then the
gates are enabled last and observed failing a real build.

The clippy ban is a compile failure under the existing -D warnings CI step,
so core/clippy.toml is deliberately not committed until every site --
including test code, since --all-targets counts it -- has migrated.

Wave 2: shares seed.rs with 10-05 and api/rpc/auth.rs with 10-01.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 07:24:43 -04:00
archipelagoandClaude Opus 5 06e0e6954e fix(01-16): recreate the gateway when its credential was rotated (FED-07)
The checkpoint on archi-dev-box proved rotation alone doesn't close FED-07:
the credential file went unique while the RUNNING container kept serving the
compromised one, because the Quadlet path rewrites a unit without restarting
it and fedimint-gateway is classified restart-sensitive, so drift was detected
and deliberately ignored on every tick.

Rotation now records the app id, and the drift check consumes that flag to
recreate even a restart-sensitive app, with a WARN naming the reason. This
mirrors the published-port carve-out a few lines above, which already makes
the same trade for the same reason: a container that is already broken (there)
or already compromised (here) is not protected by leaving it running.

Restart-sensitivity protects working services. A gateway answering to a
credential published in this repository is not working, it is compromised, and
gateway admin can drain Lightning liquidity — indefinite exposure loses to a
few seconds of restart. Rotating-but-only-alerting was rejected: the
monitoring system fires on metric thresholds only, so it would have needed new
event-alert plumbing to deliver something strictly weaker.

Re-verified on the same node, same scenario: rotation at 06:39:23, recreate at
06:39:27, PID 3923125 -> 148426, running credential now matches the file,
container healthy with the same name and ports, gatewayd.db intact at 18 files
with IDENTITY present, 32 containers untouched, no repeat rotation.

3 new tests. Also lands the missing 01-19 and 01-20 SUMMARYs: both had code
committed 2026-07-31 but no summary and no roadmap tick, so they read as
unstarted. Phase 1 is 11/20. FED-09 carries 15h of Tor uptime and 0
permission-fixes across 542 doctor runs on archi-dev-box.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 07:20:20 -04:00
archipelagoandClaude Opus 5 5cf44c9a58 docs(10): correct F-10's scope and add KEY-05 — crate-wide CSPRNG enforcement
The audit recorded F-10 as two call sites in container/secrets.rs. The real
defaulted-RNG surface is 41 sites across 15 files: session.rs (16),
pine_ha.rs (6), wallet/bdhke.rs (4 — ecash key material), mesh/x3dh.rs (2 —
key-agreement material), storage_crypto.rs (1 — AEAD nonce), +10 more.

Nothing is broken today: rand::random()/thread_rng() are ChaCha12 seeded from
getrandom(2). What changes is blast radius — F-10's Low rating rested on
'per-app credentials rather than the master key hierarchy', which does not
survive the true scope. Re-rated Medium as F-10a.

Records why the original audit missed it: F-10 was reached by tracing the
manifest-secrets path, and no step enumerated defaulted-RNG use across the
crate independently of the traced paths.

F-10's original text is left unedited so the correction is auditable rather
than retroactive. R-13 superseded by R-16; tracker item replaced.

Adds KEY-05 to Phase 10: sealed allowlist trait at key-gen seams, clippy
disallowed-methods ban (compile-time, CI-enforced), cargo-deny on duplicate
rand majors, degenerate-entropy runtime check, persisted CSPRNG-readiness
verdict. Also retires the false 'impl CryptoRng for CountingRng' at
seed.rs:656.

Records the user's execution gate: Phase 10 does not start until the
concurrent Phase 1 agent is finished and their changes are synced. KEY-05 is
unplanned — the existing 5 plans predate it and a 6th is required.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 07:01:00 -04:00
archipelagoandClaude f9b0659c4a wip: save session context — BotFights demo-prep work, off-plan (Phase 09 already complete)
Handoff for the reactive demo-day session that followed 09-06/09-07
(both already complete). Covers: security audit (6 IDOR fixes across
the botfight repo), Cashu payout claim UI, existing-bot AI-config UI,
botfights 1.2.11 built+deployed to both demo nodes, catalog
signed+published.

Also: discovered and fixed 4 botfight-repo commits that were local-only
and never pushed to origin — pushed as part of this handoff step
(botfight @ d00e792..10d4209 -> origin/main).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-02 06:36:48 -04:00
archipelagoandClaude Opus 5 23ae86c13a docs(state): record Phase 10 planned and ready to execute
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:15:30 -04:00
archipelagoandClaude Opus 5 1623b4f764 docs(quick-260731-upz): close out the entropy audit — research, summary, follow-up todo
The executor was instructed to leave docs artifacts to the orchestrator; this
commits them: the research that drove the audit, the task summary, and the
archi-dev-box test-node todo raised during the same session.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:13:34 -04:00
archipelagoandClaude Opus 5 56f7b367ff docs(01-16): checkpoint FAILED on archi-dev-box — rotation never reaches the container
Ran Task 2's blocking checkpoint on a real node. The rotation works and was
proven end to end: it fired ~15s after restart, wrote a fresh unique
credential (0600, service-owned), logged exactly one line naming the .pw path
with no value in it, left every other secret and the gateway's data untouched,
and kept the container's name and ports.

But the assumption the plan rests on is WRONG, and the checkpoint is what
caught it. 25 minutes after rotating, /proc/<pid>/environ showed the running
gatewayd still using the PRE-ROTATION credential while the file and podman
secret held the new one. The orchestrator explains itself in its own logs:

  Quadlet unit drift-synced — file rewritten, .service NOT restarted
      (operator restart picks up new config)
  container drift detected during boot reconcile;
      leaving running restart-sensitive app untouched

Two deliberate guards: the Quadlet path never restarts a unit it rewrites, and
fedimint-gateway is classified restart-sensitive so drift is detected on every
tick and then ignored — logged at 15:51, 15:53, 15:54, 15:56 and counting.

So on a real affected node the credential file becomes unique while the
gateway keeps answering to the compromised one until an unrelated reboot, and
the operator reading the .pw gets a password the gateway rejects — T-01-77
inverted. FED-07 is NOT closed and this plan alone cannot close it.

Not hand-rolled around, per the plan's own instruction. The fix needs a design
decision: whether a compromised credential is the case that should override
restart-sensitivity, or whether rotation must raise an operator-facing
"restart required" alert instead of logging into the void.

Incidentally disproved: restarting archipelago does NOT kill containers here
(29/29 then 31/31 survived; "Adopted 31 existing container(s)"). The service
is system.slice/KillMode=control-group while containers live in
user-1000.slice/…/libpod-*. The CLAUDE.md SIGKILL rule predates Quadlet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:04:12 -04:00
archipelagoandClaude Opus 5 095664a8a7 docs(01-16): hold FED-07's on-node checkpoint, correct its requirement status
FED-07 was marked Complete when 01-11 landed, which was premature: the
requirement text explicitly includes "existing installs with the default
password get a migration path", and that migration has never been exercised on
a node. Corrected to code-complete/verification-pending.

Checkpoint step 1 was run read-only on archi-dev-box: the node is CLEAN (hash
present, 600, service-owned, not the shipped default) and has NO gateway
container — the app is installed but nothing runs and its data dir is empty.
So rotation cannot fire naturally here, and the steps that matter most (data
survives the recreate, new credential authenticates, old one rejected) have
nothing to exercise without installing and seeding first.

Deferred deliberately rather than run unattended: 30 containers are up with
4-8 days uptime (IndeeHub, Immich, BTCPay, netbird, strfry, …), the
archipelago system service is active, and restarting it SIGKILLs containers
until Quadlet is the default.

The todo carries the full context plus two adjacent findings: fedimint-gateway
is missing from handle_package_credentials (so a rotated password has no UI
retrieval path), and photoprism ships a fixed admin password in its manifest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:38:38 -04:00
archipelagoandClaude Opus 5 9e2d2ef236 feat(01-16): rotate existing installs off the shipped gateway credential (FED-07)
01-11 stopped new installs from ever taking a shipped credential, but did
nothing for the nodes that already did — those gateways still answer to a
password published in this repository.

rotate_compromised_gateway_credential() detects an EXACT match against the
denylist and replaces the pair; absent, unique, or merely unrecognised values
are left alone and return false. That distinction is the point: an operator
who deliberately set their own credential also has an "unrecognised" one, and
rotating it would be the same class of harm as leaving the default in place.

It hangs off resolve_dynamic_env beside ensure_generated_secrets, gated on the
gateway's app id, so an affected node heals on its next reconcile tick. There
is deliberately no teardown here: the new hash changes the resolved secret env,
which changes secret_env_hash, which the drift check reads as a container-label
mismatch — so the platform's own recreate path rebuilds the gateway around its
unchanged data directory, ports, volumes and name.

Rotation is self-terminating (the value written is not on the denylist, so the
next tick is a no-op) and errors propagate rather than being swallowed, because
the atomic write leaves the previous credential intact on failure.

Bcrypt generation was factored out of ensure_one into write_bcrypt_pair, which
both generation and rotation call — 01-11's SUMMARY claimed such a helper
existed but the arm was still inline, and rotation cannot reuse
ensure_gateway_credential because its idempotent fast path returns early
exactly when the file is present, which is the case rotation acts on.

Also fixes cargo fmt drift left by 42652547 in install.rs.

Verified: 6 new tests, secrets suite 16/16; full suite 1008 passed with one
known wall-clock flake (green 4/4 in isolation). NOT verified on a node —
Task 2's blocking checkpoint has not been run, so FED-07 stays open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:17:56 -04:00
archipelagoandClaude Opus 5 d238bad012 docs(roadmap): add WALLET-05 — make the PSBT air-gap round trip real
The two-scan dance (node shows unsigned PSBT as animated QR, signer signs,
node scans the signed PSBT back, finalize + broadcast) is ~80% plumbed and 0%
usable. Verified gaps:

- No UI: lnd.create-psbt / lnd.finalize-psbt and their rpc-client.ts:417
  wrappers are called by nothing but unit tests.
- No animated-QR encoder: qrcode/qrloop are deps and the inbound path
  (useAnimatedQRDecoder + WalletScanModal) works, but nothing encodes a PSBT.
- Wrong format for real signers: qrloop is Ledger's; Passport/SeedSigner speak
  BC-UR (ur:crypto-psbt), Coldcard Q speaks BBQr. BC-UR is the priority given
  the existing Passport-Prime-compatible SeedQR work.

Gated on 10-05: create-psbt funds from LND's own wallet, so until LND is
watch-only against the external signer the offline device signs inputs whose
keys the node already holds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:23:47 -04:00
archipelagoandClaude Opus 5 6c821730b0 docs(roadmap): add Phase 11 — Wallet Experience & LND UI Parity
First-run wallet-type chooser, seed handling reusing the shipped SeedQR +
seed-words components, and evidence-based umbrelOS LND UI parity.

Gated on Phase 10's 10-05: the set of wallet types WALLET-01 can offer is a
direct consequence of the watch-only verdict that plan produces, and 10-05 also
deletes the dead Core wallet path so this phase never represents it in the UI.

Records the already-shipped inventory (channels panel, send/receive/scan/
settings modals, SeedRevealPanel, LndSeedBackupPrompt, utils/seedqr.ts) so the
parity matrix closes real gaps instead of rebuilding existing surfaces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:19:36 -04:00
archipelagoandClaude Opus 5 2781bd5a6c docs(10): correct D-03's gate signal — key_exists is true on every booted node
D-03 named NodeIdentity::key_exists as one of the two gate signals. Server::new
(server.rs:63-72) calls load_or_create on both branches, and load_or_create
(identity.rs:48-51) generates and writes a random temporary node key when none
exists — so key_exists is true on any node that has booted once, onboarded or
not. A gate keyed on it would refuse seed.generate on a fresh node and brick
onboarding fleet-wide.

The flaw came from the audit's own suggested remediation (§214-221) and was
repeated in the planning brief; the planner caught it against the code.

D-03's intent (two signals, OR-ed, fail safe on drift) is unchanged. Corrected
signal set: is_setup() / is_onboarding_complete() / seed_exists(), pinned by a
test rather than a comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:07:06 -04:00
archipelagoandClaude Opus 5 04c4d3460c docs(10): plan Key-Material Hardening — 5 plans, 2 waves (KEY-01..KEY-04)
Closes the three exploitable findings from the 2026-07-31 entropy/seed audit.

Wave 1 (parallel):
- 10-01 KEY-01: shared onboarding gate refuses seed.generate/seed.restore/
  seed.save-encrypted/backup.restore-identity/auth.setup on a provisioned node,
  plus an auth.onboardingComplete guard and retry-budget-derived rate limits.
  Independently shippable (D-11): no depends_on, no shared files.
- 10-03 KEY-02: first-boot secret regeneration retries with backoff then fails
  closed; rootfs tar ships identity-free so failure degrades to "no key".
- 10-05 KEY-03: delete the uncalled bitcoin.init-wallet-from-seed xprv-import
  path (D-07b); make LND's PSBT round trip first-class with a key-origin report.

Wave 2:
- 10-02 (deps 10-01) KEY-01/KEY-04: on-node C-6 exposure measurement, live
  refusal proof, fresh-node onboarding non-regression.
- 10-04 (deps 10-03) KEY-02/KEY-04: fleet detection of image-baked host secrets,
  guarded rotation behind a D-06 decision checkpoint, C-3 two-node verification.

Planning-time scoping correction recorded in 10-01: D-03 names
NodeIdentity::key_exists as the on-disk "onboarded" signal, but server.rs:63-71
writes a temporary node_key on every boot, so that signal is true on fresh nodes
and would brick first-boot onboarding. D-03's dual-signal intent is preserved
with is_setup / is_onboarding_complete / seed_exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:04:57 -04:00
archipelagoandClaude Opus 5 97b8fa485d docs(10): KEY-03 final scope — delete Core wallet path, harden LND PSBT
Core's wallet is outdated and used by nothing, so bitcoin.init-wallet-from-seed
is deleted outright rather than migrated: uncalled, authenticated and
password-gated, never ran on archi-dev-box, and its only job is deriving and
stringifying the master BIP-84 xprv.

D-07's parity-proof migration and its one-way checkpoint are withdrawn — there
is no wallet to migrate. A small discovery check folds into KEY-04; a wallet
found there is a finding to stop on, not an auto-migration trigger.

PSBT is already solved by LND and already implemented: lnd.create-psbt
(WalletKit FundPsbt) and lnd.finalize-psbt (finalize + broadcast), both
rate-limited, on LND v0.18.4-beta. KEY-03 becomes: delete the Core path and
make that flow first-class, tested and documented, including that the PSBT
carries the BIP-32 key-origin data a hardware signer needs.

Records the standing honesty constraint that Lightning channel/revocation/HTLC
keys are not air-gappable at all, and defers the BDK+ElectrumX cold vault to
its own phase (D-07c).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 06:43:22 -04:00
archipelagoandClaude Opus 5 cb021a991c docs(10): record KEY-03 scoping correction — Core wallet path is uncalled
Bitcoin Core's wallet is legacy and unused: bitcoin.init-wallet-from-seed has
no caller outside its dispatcher registration, the wallet UI is LND-only
(lnd.sendcoins/estimatefee/getinfo), archi-dev-box has no bitcoin/wallets/ dir
so the handler's named descriptor wallet was never created there, and the
endpoint is authenticated + password-gated so F-13 was never remotely
reachable.

F-13 is therefore latent, not live. D-07's migration premise is unproven, so
KEY-03 is re-scoped discovery-first: check the fleet for any wallet this
handler created before planning any migration. Migration + checkpoint stay,
conditional on discovery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 06:38:20 -04:00
archipelagoandClaude Opus 5 90884e6259 feat(01-02): chat mutations mutate demo state instead of acking (FED-04)
Demo images / Build & push demo images (push) Successful in 4m21s
Reactions, replies, read-receipts, edits, deletes, forwards and channel sends
shared one bare `{ ok: true, sent: true }` case, so none of them rendered on
the demo — the UI derives reaction chips and reply quotes from the message
store, and there was nothing in it to derive from.

Each now mirrors its daemon counterpart. Reactions/replies/receipts push typed
messages carrying the { sender_pubkey, sender_seq } target key Mesh.vue's
reactionIndex and replyTargetPreview read. Edits rewrite the text and set
edited_at; deletes tombstone IN PLACE (plaintext, typed_payload.deleted,
message_type 'delete') because that is what mesh/mod.rs apply_local_delete
does — it does not remove the row.

Edits and deletes go through a per-session overrides overlay keyed by
sender_seq, because mesh.messages rebuilds its seed array on every read, so
in-place mutation would only ever work for messages sent this session.

mesh.refresh and mesh.reboot-radio stay acknowledgements on purpose — the
daemon's handlers have no message-store effect either — with a comment saying
so, so a later reader does not "fix" them into divergence.

Also completes the phase bookkeeping for 01-02/03/11/12/13/14/15 and lands the
orphaned 01-12/01-14 SUMMARYs.

Verified: parity harness 17/17 live assertions; full frontend suite 102 files
/ 822 tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 06:37:00 -04:00
archipelago 9c2ec82bdb docs(10): capture phase context 2026-08-01 06:32:22 -04:00
archipelagoandClaude Opus 5 579398981f feat(01-03): paid tick renders the branded screensaver ring (FED-06)
Demo images / Build & push demo images (push) Failing after 1m14s
Both paid-tick surfaces now show the EQ-segment ring instead of a CSS ripple
burst (send modal) and a plain circle (scan modal), via a new `badge` size
variant at 160px/192px with matching --viz-radius. A transform scale of the
compact variant was ruled out in 01-UI-SPEC.md because it would scale segment
stroke width and blur along with the geometry.

Both surfaces use the identical composition — a badge-sized relative container
with the checkmark core absolutely centred over the ring — so the two ticks
cannot drift apart visually.

ScreensaverRing also gains the prefers-reduced-motion guard it never had, on
the component rather than per call site, so the screensaver and
SystemDangerZone variants are covered too.

Also records live-browser verification for 01-13's scroll cue (three
viewports, plus a geometry probe of the hide condition).

Verified: 5 new tests; full suite 102 files / 822 tests green; npm run build
clean with viz-ring-badge present in the built bundle. The live visual
no-clipping observation Task 2 asks for is explicitly NOT done — recorded as
deferred to plan 01-07's consolidated sign-off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 06:07:10 -04:00
archipelagoandClaude Opus 5 5f366f7589 fix(01-13): on-brand scroll cue makes the onboarding tickbox findable (UIFIX-03)
Demo images / Build & push demo images (push) Successful in 4m25s
On a short viewport the seed-confirmation tickbox sits below the fold inside
the step's scrolling area while Continue stays pinned and disabled in the
fixed footer — onboarding reads as broken rather than incomplete.

The cue is a sticky-bottom scrim and glass pill inside the scroll region, and
its visibility comes from real geometry: scrollHeight vs clientHeight for
overflow, then a getBoundingClientRect comparison of the tickbox's bottom
against the container's. On a tall screen the element does not render at all,
so those screens are unchanged. Rects rather than offsetTop because offsetTop
is relative to the nearest positioned ancestor — here the outer card, not the
scroll container.

It is wayfinding only: activating it scrolls the tickbox into view and never
sets confirmed, focuses Continue, or auto-ticks, which a test pins.

Listener setup was moved onto both onMounted paths — the sessionStorage
restore path returned early, so a user navigating back would have had no cue.

Verified: 6 new tests plus the full frontend suite green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 05:54:40 -04:00
archipelagoandClaude Opus 5 46bf5a7870 fix(01-15): hand the video off to a custodial PiP session (UIFIX-05)
Entering picture-in-picture read as the lightbox being dismissed, and the
session died with it: both Teleport and KeepAlive move their subtree on
deactivation, which the PiP spec treats as removal.

MediaLightbox now listens for the video's own enterpictureinpicture event —
so PiP entered by the browser's native control behaves identically to the
toolbar button — and follows a fixed order: adopt, animate, then emit close.
Adopting first is what makes the element survive the unmount the emit
triggers; the invariant is documented in place so a refactor cannot reorder
it innocently.

The backdrop animates a handoff on the PiP path only, closing on transitionend
with a bounded 350ms fallback for browsers that skip the transition and for
the reduced-motion path where the duration is zero and the event never fires.

Verified: 5 new tests plus the full frontend suite green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 05:53:38 -04:00
archipelagoandClaude Opus 5 4265254700 fix(01-11): remove every shipped Fedimint gateway credential (FED-07)
Six code paths configured the Lightning gateway with a bcrypt hash committed
to this repository — and one deploy path with a plaintext password literal —
whenever the per-install secret was missing. Anyone holding a copy of the repo
held the admin credential for every gateway that ever took a fallback.

container::secrets now owns the credential end to end: ensure_gateway_credential
(idempotent, delegates to ensure_one's bcrypt arm) and gateway_bcrypt_hash,
which returns Err when the secret is missing/empty and when the stored value is
on the KNOWN_DEFAULT_GATEWAY_HASHES denylist — so this codebase cannot hand
back the compromised value even to a node already carrying it.

get_app_config was widened to Result so a credential-less install cannot reach
podman run at all; configure_fedimint_lnd takes the resolved hash instead of
re-reading with its own fallback. The four shell paths stop generating
credentials entirely (dropping the htpasswd host dependency) and skip container
creation with a printed reason rather than substituting anything.

Naming converges on the manifest's fedimint-gateway-hash/.pw, with legacy
fedimint-gateway-password values copied forward rather than regenerated so no
node loses a working unique credential. Plan 01-16 owns rotation of installs
already carrying the default.

Verified: cargo build clean; cargo test -p archipelago 999 passed (2
boot_reconciler timing tests failed under concurrent load, green in isolation,
untouched by this diff); bash -n clean on all five scripts; the compromised
literal now appears exactly once in the tree, as the denylist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 05:46:02 -04:00
archipelago cc52719e81 docs(01-01): complete federation node-store lock plan 2026-07-31 22:29:31 -04:00
archipelagoandClaude Opus 5 0c63a8518c docs(quick-260731-upz): plan entropy/seed audit + PSBT signing architecture
Following the confirmed 2026-07-30 Coinkite COLDCARD low-entropy incident,
plan three deliverables as a single 3-task quick plan:

1. docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md — evidence-backed audit of
   every key-material path (Rust/TS/shell), adjudicating research findings
   [ARCHY-1]..[ARCHY-4] with file:line evidence, incl. the one-ISO-many-nodes
   correlation risk and an explicit UNVERIFIED on-node checklist.
2. docs/security/PSBT-SIGNING-ARCHITECTURE.md — descriptor watch-only,
   wsh(sortedmulti) multisig, air-gap transport, honest LND limits
   (channel/revocation/HTLC keys cannot be air-gapped), hot wallet as
   explicitly secondary, migration path, phased rollout.
3. Remediation backlog into docs/UNIFIED-TASK-TRACKER.md + one gated
   hardening fix (explicit-OsRng injection at the mnemonic call site) proven
   by a known-answer test that cannot exist before the change.

Audit-and-spec only — no wallet/signing implementation. Verify gates enforce
file:line evidence density and a secret-shaped-string check on both docs, and
assert no commit authored by this plan touches the concurrent agent's files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:29:23 -04:00
archipelagoandClaude Opus 5 bf9cfc446a docs: capture ISO build handoff for v1.7.119-alpha
Release and OTA assets are live; the installer ISO was blocked three times by
a dirty shared tree. Records the exact command, TMPDIR requirement, the
background-execution lesson, the build-from-HEAD decision and its reasoning,
and which gate stages were already observed passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 21:13:51 -04:00
archipelagoandClaude Opus 5 215e13bff6 docs(phase-02): complete phase execution
Phase 02 (ui-performance) verified passed. 12 plans (8 planned + 4 gap
closure). PERF-01/02/03 all met. Two residuals formally overridden by the
user: Discover's entrance-animation replay (animations are his domain) and
OpenWrtGateway (no device connected, unmeasurable).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:58:35 -04:00
archipelagoandClaude Opus 5 e42f73a3b6 docs(02): final verification status
Reassessed after the OpenWrtGateway override (commit b4350e24) closed the
single open item from the prior human_needed pass. Judged the override on
its merits: well-formed, corroborated by a direct first-person quote from
Dorian this session, with one noted imprecision (the rationale slightly
overstates that no measurement at all is obtainable, when the
disconnected-state UI could technically still be re-measured) that doesn't
change the substance of a legitimate stakeholder scope call. With both
residual, non-poller-fixable costs (Discover's animation replay,
OpenWrtGateway's untestable hardware dependency) now individually accepted
by rationale-backed override, and every other named regression fixed or
substantially recovered, status is passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:57:27 -04:00
archipelagoandClaude Opus 5 b4350e244f docs(02): record user override for unmeasurable OpenWrtGateway surface
No OpenWrt device is connected to this node, so the surface cannot be
exercised and no post-fix measurement is obtainable. The user decided to pass
it for this milestone. Also notes that its earlier baseline/regressed figures
measured a disconnected-device UI rather than the real screen, and records the
residual risk for the next milestone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:42:35 -04:00
archipelagoandClaude Opus 5 8569f5376a docs(02): final verification
Independently re-derived all 02-11/02-12 claims: recomputed every claimed
median directly from 02-PERF-FINAL.json's raw samples (all match), confirmed
the frozen perf harness is still byte-for-byte untouched, re-ran the
keepAliveLifecycle regression suite (19/19) and the full workspace suite
(788/788), and confirmed the three leaked-poller fixes are genuinely wired
in source. Discover's override is well-formed and corroborated across three
independent artifacts. Status: human_needed — OpenWrtGateway is the one
named regression with zero post-fix measurement (crashed for an unrelated
reason) and no override scoped to it; everything else is either verified
fixed, substantially recovered with an honest residual, or formally
overridden by Dorian.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:32:54 -04:00
archipelagoandClaude Opus 5 813d079b9b docs(02-12): mark plan CANCELLED per Dorian's decision — no animation changes
Dorian decided entrance-animation behavior is his domain and is not to be
changed, cancelling this gap-closure plan before any source file shipped.
The composable drafted during investigation was deleted (never imported).
Preserves the investigation findings (blast radius, the ~241ms-1716ms
measured cascade, the first-paint-901ms-of-1095ms evidence, the Apps-revisit
184-267ms calibration, and the pre-phase-2-baseline note) so the analysis
isn't lost or re-done. Discover's revisit cost stands as a formally accepted
deviation at its 02-11 FINAL number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:16:43 -04:00
archipelagoandClaude Opus 5 991e9b5e4c docs(02): record Dorian's formal override for Discover's residual revisit cost
The re-verification required either a fix or an accountable stakeholder
decision. Plan 02-11 fixed four of five measurable surfaces. 02-12 isolated
Discover's remainder to the card-stagger entrance animation replaying on every
revisit; the only fix changes animation behavior, which Dorian rules off-limits
as his domain. Decision recorded with rationale, evidence and attribution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 10:53:39 -04:00
archipelagoandClaude Opus 5 59798ebff5 docs(02-12): plan the KeepAlive entrance-animation-replay gap closure
02-11 named a second, distinct cause of Discover's revisit slowness with
full profiling evidence (card-stagger/home-card-animate classes never
removed from the DOM, so Chromium restarts the CSS entrance animation on
every KeepAlive reattach) but deliberately left it unfixed — the blast
radius (Discover/Apps/Marketplace/Home + Web5 sub-cards) exceeded that
plan's scope. This gap plan fixes every affected site with one shared
composable rather than per-file patches, decouples Home.vue's dual-purpose
animateCards ref so its overlay/EasyHome visibility logic stays untouched,
and ends at a blocking human-verify checkpoint before any SUMMARY is written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:58:40 -04:00
archipelagoandClaude Opus 5 56cc94237b docs(02-review): clarify PWA auto-update provenance in the record
The code-fixer correctly declined this change twice on relayed consent. It was
then made by the orchestrator holding first-hand authorization from Dorian, who
chose forced auto-update with the mid-payment reload risk explicitly stated and
rejected warning UI. Records the quote, and that the implementation reused the
existing kiosk auto-apply path (preserving the cinematic and first-install
guards) rather than flipping build-time skipWaiting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:41:59 -04:00
archipelagoandClaude Opus 5 f514ab515b docs(02-11): complete plan — leaked-poller fix, four-way verdicts, SUMMARY
Phase 02's last open item closed: web5/server/fleet fixed and proven
on archi-dev-box; app-details restored to at/near baseline;
discover's second cause (CSS entrance-animation replay on KeepAlive
reactivation) named and evidenced but not fixed, scoped as a
follow-up; openwrt-gateway not measurable this pass, prior numbers
flagged not retracted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:37:54 -04:00
archipelagoandClaude Opus 5 68d63b1140 docs(01): add FED-09 — doctor's Tor restart loop breaks mesh Tor fallback
container-doctor.sh's fix_tor_permissions() exact-matches mode '700', but Tor
sets its hidden-service dirs to 2700 (setgid). Every 5-minute doctor run
'fixes' 2700->700 and restarts tor@default; Tor resets it and the cycle
repeats, so Tor never builds a usable consensus/HSDir cache. Result on a live
node: 'No more HSDir available to query', onion peers unresolvable, and with
the FIPS direct path also timing out, mesh sends failed entirely.

Planned as 01-20 in wave 1 so it ships in the same release. FIPS direct
connect_fail is a separate concern, handed to FED-03's transport review.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:37:41 -04:00
archipelagoandClaude Fable 5 9ad31a7495 docs(09): record demo-day deviations + Cashu fixed-stake feature scoping
Records everything found/fixed live during 09-07's blocking human-verify
checkpoints today (botfights 1.2.2-1.2.8): iframe embed, native signer
bridge, mode-picker discoverability, proxy-URL leaks, round-jump backfill,
broken profile images (CSP), AI-answer discoverability, and the webhook_test
signature exception. Also records the full threat register worked through
for the user-directed Cashu fixed-stake entry-fee request (21 sats, winner
takes all) — mint configured and verified live (Minibits), actual
token-accepting implementation deliberately NOT done yet, scoped as a
follow-up.

Note: this section was written once already earlier in the session and
appears to have been lost to a shared-tree overwrite before it was
committed (CLAUDE.md "Concurrent agent in shared tree" hazard) — recreated
and committed immediately this time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 09:37:40 -04:00
archipelagoandClaude Opus 5 48a2ff7c16 docs(02-11): four-way re-measure, final per-surface verdicts, second Discover cause
Deployed the poller fix to archi-dev-box and re-ran the frozen harness
(02-PERF-FINAL.json, 5 runs/surface). Web5 fixed (275ms, below both
its 566ms pre-phase-2 baseline and the 300ms pass bar). Server's
regression closed (574ms, below 738ms baseline) though not yet under
300ms. Fleet substantially improved (790ms, down from a 2631ms
regression). AppDetails restored to at/near its own baseline.

Investigated the two open items the coordinator raised:
- OpenWrtGateway: this run's 5/5 samples failed with a Chromium
  "Target crashed" error cascading from an unrelated surface earlier
  in the same harness run — recorded as not-measurable, not written
  in as data. Separately confirmed via source (OpenWrtGateway.vue's
  h1 renders unconditionally, and a "No router configured" RPC error
  deterministically shows a real Connect-to-Router form) that the
  prior baseline/after/remeasure numbers were measuring a genuine,
  substantive disconnected-state UI render, not an empty/error page —
  so the "six confirmed regressions" count is not retracted, but the
  numbers are flagged as reflecting one specific code branch.
- Discover (1389ms, worst remaining, least improved): profiled
  directly and found a SECOND, distinct, phase-2-class cause —
  showStagger/card-stagger entrance-animation classes are baked into
  the DOM at first mount and never programmatically removed (the flag
  is a correctly-scoped once-per-session const, but nothing ever
  re-renders to strip the class), so every KeepAlive detach/reattach
  cycle restarts the CSS animation on reactivation, replaying the full
  entrance cascade on every revisit. Confirmed via a diagnostic
  showing DOM card count doubling transiently on every revisit and an
  extended animationstart/animationend event log. Not fixed this pass
  — the safe fix's blast radius spans 5+ files outside this plan's
  scope (Apps.vue, Marketplace.vue, Home.vue, several Web5 sub-cards)
  and needs its own real-device verification budget, matching the
  precedent 02-02's original KeepAlive rollout needed for this exact
  class of change. Named and evidenced, recommended as a dedicated
  follow-up rather than expanded into this plan under time pressure.

REQUIREMENTS.md's PERF-02/PERF-03 rows updated to the final state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:32:19 -04:00