feat(content): owned-content persistence + Fedimint paid downloads, fmcd caps fix, FIPS warm-path perf

Buyer-side paid downloads now persist: purchases are cached on disk
(content_owned.rs) keyed by (seller onion, content_id), the gallery shows
an "Owned" badge unblurred, and items view/play in-app from the local
cache with no re-payment or reliance on a browser download (which
silently failed on the mobile companion). New RPCs content.owned-list /
content.owned-get. Validated e2e .116<-.198 (paid 100 sats via Fedimint,
166KB jpeg returns, survives restart).

fedimint-clientd manifest: restore the standard container capability set
(CHOWN/DAC_OVERRIDE/FOWNER/SETUID/SETGID) so fmcd's startup chown of an
existing-federation /data succeeds instead of dying EPERM (#7). Confirmed
the orchestrator applies these to the running container.

FIPS perf: tighten the supervisor warm-path keepalive 45s -> 25s so peer
paths stay inside the ~30-60s NAT cold window. Dials now reliably land on
FIPS instead of re-punching and falling back to Tor. Measured to the same
peer: cloud browse 18-22s -> 0.4s; full Fedimint paid download 29s -> 11s
(residual is the seller-side guardian reissue round-trip).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-20 18:58:52 -04:00
co-authored by Claude Opus 4.8
parent b0c9bd2a0c
commit db7d424bff
12 changed files with 544 additions and 26 deletions
@@ -169,6 +169,34 @@ pub async fn ensure_default_federation(data_dir: &Path) -> Result<()> {
Ok(c) => c,
Err(_) => return Ok(()), // clientd not configured yet
};
// Fast path: if fmcd already reports a joined federation, do NOT re-issue the
// POST /v2/admin/join. That call re-syncs federation config against the
// guardians and adds seconds of latency — and ensure_default_federation runs
// on every wallet.fedimint-list / spend / reissue, so the join was being paid
// on each balance refresh (the "mints take ages to load" report). The cheap
// GET /v2/admin/info is enough to confirm membership; just reconcile the local
// registry against the live joined set and return.
let joined = client.joined_federation_ids().await;
if !joined.is_empty() {
let mut reg = load_registry(data_dir).await?;
let mut changed = false;
for id in joined {
if !reg.federations.iter().any(|f| f.federation_id == id) {
reg.federations.push(JoinedFederation {
federation_id: id,
name: Some("Archipelago Federation".to_string()),
});
changed = true;
}
}
if changed {
save_registry(data_dir, &reg).await?;
}
return Ok(());
}
// Cold start only: nothing joined yet, so join the default federation once.
let federation_id = match client.join(DEFAULT_FEDERATION_INVITE).await {
Ok(id) => id,
Err(e) => {