diff --git a/.planning/MEDIA-AND-INDEEHUB-SCOPE.md b/.planning/MEDIA-AND-INDEEHUB-SCOPE.md new file mode 100644 index 00000000..a93cf10e --- /dev/null +++ b/.planning/MEDIA-AND-INDEEHUB-SCOPE.md @@ -0,0 +1,98 @@ +# Media, IndeeHub & AIUI quality — scope from on-device evidence + +**Written 2026-08-06, end of session.** Every item below was observed on archi-dev-box or +read from source — none is inferred. This is the input for a proper research + plan pass, +not the plan itself. + +## A. The content-card parser is the "idiotic responses" bug + +Operator-visible symptom: asking for Bitcoin films produced good model prose, then cards +that were **wrong**: + +- `Banking on Bitcoin` captioned with *The Rise and Rise of Bitcoin*'s description +- `Cryptopia` captioned with *The Bitcoin Standard*'s +- `Documentaries:` and `Narrative Films:` rendered as if they were titles +- `The Social Network` captioned with *Related Financial/Tech Films:* + +Cause is `updatePanelFromText` (useContentPanel.ts) pairing title *n* with description +*n-1* and not excluding section headers. **The model was not at fault** — the card layer +mangled correct prose. Fix the parser before touching prompts. + +Deeper question for the research pass: a regex over prose is the wrong contract entirely. +The model should return **structured** recommendations (tool call / JSON), and the grid +should render those. D-12 already says node content is the source of truth for these +buckets; text-scraping is the legacy path that should shrink, not be patched forever. + +## B. IndeeHub — three independent faults + +1. **Content source.** Films are `projects` in IndeeHub's NestJS API + (`GET /api/projects` via its own nginx; port 4000 is not host-mapped; `/graphql` is the + SPA catch-all, NOT an API). On this node `/api/projects/count` = **`{"count":0}`** — + the public library is genuinely empty. `content.owned-list` (content.rs) has **no** + IndeeHub linkage; `owned` is Archipelago's own paid-content store. So AIUI has never had + a path to IndeeHub content and would render nothing even if wired. +2. **Signer / auth.** `GET /api/projects/private` → 401 + `{"message":"Cognito authentication is disabled. Use Nostr login."}`. Private films need + a **Nostr session**. `/api/auth/nostr/session` 401s through the gate (see C). An adapter + must therefore authenticate as the user — which lands on the phase's non-negotiable: + keys stay out of the browser and the model, so this belongs node-side behind a capability + grant. Same shape as follow-on Phase C (Nostr first-class). +3. **Relay is down independently.** `/relay` returns **502 direct on loopback**, bypassing + the gate — IndeeHub's own nginx cannot reach the relay container. `wss://relay.damus.io` + also fails from that page. Not a gate fault. + +## C. The app gate breaks apps that own their auth — FLEET-WIDE, highest priority + +Verified: `http://:7778/manifest.json` → **401 + the gate's login HTML**. + +- **A PWA manifest is fetched WITHOUT credentials** unless the tag sets + `crossorigin="use-credentials"`. The cookie is never sent, so the gate 401s it *even when + fully logged in*. This hits **every gated app with a PWA manifest**, not just IndeeHub. +- The app's service worker serves the cached shell, so the SPA boots ("Backend connected at + /api — real mode active") and only then does every network call 401 — which is why it + looks like an app bug rather than a gate bug. +- The gate also intercepts the app's own `/api/auth/nostr/session`, so IndeeHub can never + establish its own session. "Nostr login failed" / "Sovereign identity generation failed" + are all this one cause. + +Same class as the `.125` cookie-strip that broke every companion UI. The gate needs a +stated policy for (a) credential-less subresource fetches the browser sends by design and +(b) app-owned auth endpoints once a valid gate session exists. **Each exemption is a hole in +a security control and needs its own written justification** — do not batch-fix this. + +## D. AI Data Access grants do not survive — wrong storage layer + +`aiPermissions.ts` persists to `localStorage` (`archipelago-ai-permissions`). No logout path +clears it (only SystemDangerZone, by design). **localStorage is per-origin**, and a node has +many: `192.168.63.240`, `100.69.68.39`, `.local`, the Tailscale name. Granting on one +and returning via another shows everything off — which is exactly what "turns them all off" +looks like, and what made a films search look broken tonight. + +These grants are a property of the NODE ("what may the AI read"), not of one browser at one +address. They belong node-side behind an RPC, with localStorage as an offline fallback and a +migration so existing local grants are not silently dropped. + +## E. Also observed, unowned + +- `/api/app-catalog` → **502**, repeatedly, on the dashboard. +- AIUI web search blocked by CSP (`connect-src http://:*/aiui/`) — confirms the + already-recorded 13-09 decision that the web-search setting must drive the CSP node-side. +- `Failed to scroll to index N after 10 attempts` — ChatWindow scroll bug, cosmetic but loud. +- `strfry.png` / `.svg` 404 — missing app icon. + +## Suggested sequencing (to be challenged by the research pass) + +1. **C** — fleet-wide, user-visible, security-critical. Blocks any app with its own login. +2. **D** — one RPC; unblocks every AI content path and stops false "broken" reports. +3. **A** — parser fix now, structured-output contract as the real answer. +4. **B** — needs C and D first; the signer question is a design decision, not a task. + +## Nostr-first framing (per feedback_nostr_first_solutions) + +Worth researching rather than assuming: IndeeHub already speaks Nostr for identity, and the +node already holds Nostr identity material. A single node-side signer serving both the +dashboard and gated apps (NIP-07-style bridge, already precedented by `nostr-provider.js`) +would address B-2, the app-auth half of C, and Phase C's zaps at once. Media identity/ +distribution over Nostr (NIP-94/NIP-71 style events, Blossom for blobs) is the obvious +frame for "all the media types" and should be evaluated against the current +`content.*` RPC model before more sources are bolted onto it.