docs: resume artifact — AIUI surfaces, the peer-files correction, SearXNG

Carries the full task list with per-item status, the three commits'
rationale, the live measurements that overturned the earlier
peers-have-no-content conclusion, the browser-verification recipe, and
the binary-drift blocker that must clear before the ISO.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-07 06:00:54 -04:00
co-authored by Claude Opus 5
parent 9cf1c12213
commit bca18c03d2
@@ -0,0 +1,292 @@
# RESUME — 2026-08-07 (afternoon). AIUI surfaces + demo prep.
**Read this first.** Then `.planning/RESUME-2026-08-07-aiui-demo.md` (the previous
handoff, still the source for the traps list), then
`.planning/RESUME-2026-08-06-media-loop.md` (the fix→deploy→test loop).
Branch `gsd/phase-13-aiui-functional-conversational-node-control-and-content-surf`
@ `b1c5d138`. Working tree: clean except `neode-ui/shot.tmp.mjs` (a throwaway
Playwright driver, see "Browser verification" below — delete or keep, it is not
committed).
**Context: AIUI is being demoed soon and a clean ISO must be cut.** The operator's
priority order is #16, #15, #9, #17.
**The operator's standing instruction for this work:** fix → test → check the
browser with screenshots → deploy → debug → fix, in a loop, without stopping to
ask questions. And: *"if you think you know better like the peer files you are
completely wrong"* — see "The peer-files correction" below. Do not re-derive the
old conclusion.
---
## THE TASK LIST — rebuild this in the session task tool on resume
`[x]` = done, deployed AND verified on the node.
- [x] **1. Unify AI Data Access toggles with assistant tool grants**`55155f2d`
- [ ] **2. Verify AI grants persist across refresh through the real UI path**
- [ ] **3. Add `app_install` / `app_uninstall` tools** (behind the 13-08 confirm gate)
- [ ] **4. `!archy` / `!ai` over mesh must action commands** with text responses
- [x] **5. Cap `content.browse-all-peers`**`75919a20`, then rebuilt as Cloud's fan-out
- [~] **6. Settings link when a request needs an ungranted permission** — node + broker
+ chrome banner all landed in `9abc1623`; **NOT yet seen in a browser** (needs a
turn that actually hits an ungranted category — try revoking `media` and asking
for content). Finish by confirming the banner renders and its button lands on
the AI Data Access section.
- [ ] **7. App lifecycle defects** — fedimint guardian installs but doesn't work; BTCPay
uninstall-with-wipe reinstalls with an account still enabled; Bitcoin Knots and other
apps disappeared; fedimint gateway died at 88%; reconciler `chown postgres-btcpay`
failures (same volume-ownership family as the IndeeHub relay fix)
- [ ] **8. LND UI + filebrowser 401s** — every `:18083/proxy/lnd/*` and
`/app/filebrowser/api/resources/`. Node-owned `*-ui` apps need session passthrough;
memory says that rides DISK manifests because the catalog refuses build-source.
**Untouched this session.** Note the 403s still in the browser console (below) may
be this same family — worth checking before assuming a separate cause.
- [~] **9. AIUI must answer with content + context surfaces, NOT JUST prose**
*(operator correction: the prose answer stays, it is wanted; what was missing is
the surfaces alongside it)*. Largely delivered by the work below and verified in a
browser. Remaining: only `content_list`/`apps_list` are surface-producing tools, so
turns about system/network/bitcoin still answer in prose only. Decide whether those
deserve context surfaces too.
- [~] **10. AIUI slow background image + console noise** — web-search CSP spam and the
web-search 403 are FIXED (`b1c5d138`). Still open, all observed live in the console:
wavlake.com + itunes.apple.com blocked by CSP (song cover enrichment — this is the
known 13-09 CSP issue; memory says the decision is that the web-search setting
should drive the CSP, moved node-side), three 403s, two 402 Payment Required, a 502,
a 404, and the sw.js SSL registration failure (self-signed cert). The slow
background image itself was not investigated.
- [ ] **11. Cmd/Ctrl+K → AIUI must carry the query into the expanded chat** — I read the
whole path (`SpotlightSearch.vue:294``Chat.vue` `askedAt` watcher → `flushAsk`
`chat:prefill``archyBridge.onPrefill` buffering → `ChatInput.vue:224`) and it
is **fully wired and looks correct**, including the cold-frame buffer. It
deliberately prefills-and-focuses rather than auto-sending. **Not reproduced, not
verified in a browser.** Do that before changing any code — the report may predate
the fix.
- [x] **12. Node certificate settings section** container/layout — `75919a20`
- [ ] **13. HTTPS dynamically on EVERY address** alongside Tailscale (LAN done; must
re-apply as addresses change, and the bare hostname must resolve)
- [ ] **14. Nostr signer + service worker over HTTPS** (operator: lower priority than AIUI)
- [x] **15. Content-surface header** — the stale-title half is DONE and browser-verified
("Loading…" during the turn, "Nothing found" on an empty result, correct count
after). **The header-overlap half is NOT confirmed:** at 1600×950 the close button
(`absolute top-3 right-3`) does not collide with anything — the tab row already
carries `pr-12` (`ChatPage.vue:38`). I never reproduced the overlap. **Check a
narrow/mobile viewport** before editing CSS; that is the most likely place it bites.
- [x] **16. Populate the content surface for own shared content + rich chat previews**
DONE and browser-verified. See below.
- [ ] **17. Cut a clean ISO for the demo** — `UNBUNDLED=1 bash
image-recipe/build-debian-iso.sh` (the default env silently builds the wrong
full-bundle variant); verify the frontend INSIDE the ISO.
**BLOCKER: rebuild the release binary first** — see "Binary drift" below.
---
## What shipped this session
| What | Commit |
|---|---|
| Content surface renders what the assistant found (4 defects) | `9abc1623` |
| SearXNG JSON 403 — AIUI web search never worked | `c810b514` |
| Node content outranks the prose surface; web-search path | `b1c5d138` |
### `9abc1623` — the content surface, four separate defects, one symptom
The symptom was always the same: a correct prose answer beside an empty grid.
1. **The assistant's curated RPC bridge had an arm only for `content.list-mine`.**
`assistant/tools.rs` mapped the `peers`, `purchased` and `films` scopes onto
`content.browse-all-peers` / `content.owned-list` / `content.indeehub-projects`
— three real, dispatcher-registered handlers that `assistant_dispatch_tool`
(`api/rpc/assistant_chat.rs`) had never heard of. Every non-`own` scope died on
its catch-all with "no such handler". **The tool never ran.** Regression test
added: `every_content_scope_reaches_a_real_dispatch_handler`.
2. **`content.browse-all-peers` threw away completed work.** It wrapped the whole
fan-out in one `timeout(overall, ...).unwrap_or_default()`, which DISCARDED
every finished batch the moment the budget expired. One slow peer turned a
partly-successful browse into `peers_reached: 0, peers_unreachable: 16`. Now it
accumulates per batch and checks a deadline between batches, so partial results
always survive; budget 20s → 45s (two batches of 8 at a 10s per-peer timeout had
literally zero headroom).
3. **`assistant.chat` returned only `{ text }`.** The structured tool results were
dropped inside the loop. The turn now carries them through as `surfaces`,
captured RAW in `loop_::execute_tool`'s `Ok(v)` arm — deliberately BEFORE
`wrap_tool_result_if_untrusted`, because that boundary exists to stop peer text
being read as instructions by the MODEL, and this copy goes to a renderer that
treats every field as inert data and never re-enters the prompt.
4. **The adapter classified images as `'excluded'` and dropped them.** A node
sharing mostly photos rendered as an empty grid while AIUI's `panelImages` /
`ImageGrid` sat unused. Images now have a bucket end to end
(`archyContentAdapter` → `contextBroker` → `archyBridge` → `useArchy` →
`useContentPanel`), with the paid-lock and extension-fallback handling that
audio and video already had.
Also in that commit: the "Loading…" / "Nothing found" panel headings; a system-prompt
paragraph telling the model to call the content tool and summarise rather than
re-list what the cards already show; and `refused_categories` on the chat response so
the trusted chrome can offer the AI settings screen (task 6).
### `c810b514` — SearXNG JSON was 403, so AIUI web search never worked
The operator asked whether "Web search via your private SearXNG instance" is true.
**It is true** — `/aiui/api/web-search` proxies to `127.0.0.1:8888/search`, the local
container, and nothing leaves via a third-party API. But SearXNG defaults to
`formats: [html]`, so its JSON API answered **403**, and JSON is the only thing AIUI
speaks. Both seed sites (`scripts/first-boot-containers.sh` and
`api/rpc/package/install.rs`) omitted `search.formats`.
**Measured after the fix, live:** 28 results for "bitcoin halving", from Brave (20)
and DuckDuckGo (8). Google self-suspends ("access denied") and Startpage hits a
CAPTCHA — normal for a self-hosted instance, and it costs little because Brave runs
its own independent index. **The result quality is fine; the 403 was the whole
problem.** No case for replacing SearXNG on this evidence.
**Existing nodes need manual repair** (the commit only fixes what new installs get):
add to `/var/lib/archipelago/searxng/settings.yml`
```yaml
search:
formats:
- html
- json
```
then restart the app. **archi-dev-box is already repaired.**
### `b1c5d138` — the prose surface was winning, and the web-search path was wrong
- `setArchyContent` put the node's grids on the tab bar, then `updatePanelFromText`
**replaced** the bar with tabs inferred from the reply text. "show me my own shared
content" landed on an **"AI Brief"** — a prose restatement of the answer already on
the left — with the populated image grid no longer reachable. Guarding the `panel*`
arrays was not enough: they held the right data while the tab bar had discarded the
way to see it. Archy tabs now lead and the title follows the leading tab.
- Archy tabs are ordered by **bucket size**. A node with 13 photos and 2 tracks opened
on Songs and titled itself "2 Songs" for a 15-item answer.
- `searchWeb` hardcoded `/api/web-search` while every other call is built from
`BASE_URL`. Under `/aiui/` it asked the HOST for a path only the AIUI-scoped nginx
location serves → 403 from the node's API gate, plus a CSP refusal.
- The embedded path now skips client-side web search entirely: `streamViaArchy` sends
only the user's text, so the system prompt those results were folded into is never
transmitted. It was a round trip and a console error per turn whose output provably
reached no model. **Web search for the embedded path belongs node-side, next to the
other tools** — that is task #10's "web search through node chat" note.
---
## The peer-files correction — READ THIS
The previous session concluded: *"Your 16 federated peers are not serving content…
peers_reached: 0 is correct. No AIUI work will make peer films appear until those
peers answer. This is a fleet problem, not a UI one."*
**That was wrong, and the operator said so.** Measured live on archi-dev-box:
- A direct `content.browse-all-peers` RPC returned **real peer items** (a peer's
`Music/Architects of Tomorrow.mp3`) on one call and **0 reached / 16 unreachable**
on the very next — the discard-on-timeout bug in defect 2 above.
- Through the assistant after the fixes: **4 of 16 peers reached, 7 items from 2
peers**, including paid tracks at 10,000 sats.
The peers were serving content the whole time. Two code bugs (a missing dispatch arm
and a timeout that threw away completed work) produced a number that looked exactly
like a fleet outage. **Do not re-diagnose this as infrastructure.**
---
## Browser verification — how it was done, and how to repeat it
This box **is** archi-dev-box (`hostname` confirms; LAN `192.168.63.240`, Tailscale
`100.69.68.39`). Everything can be tested locally.
**Authenticated RPC from the shell** —
`/tmp/claude-1000/.../scratchpad/rpc.mjs` (regenerate if the scratchpad is gone):
POST `https://192.168.63.240/rpc/v1`, `auth.login` with `{"password":"ThisIsWeb54321@"}`,
carry the `session` + `csrf_token` cookies and send `X-CSRF-Token`.
**The API is NOT on 7777 or 8101** — go through nginx on 443.
**Playwright driver** — `neode-ui/shot.tmp.mjs` (uncommitted, run it from
`neode-ui/`, which is where `playwright` is installed; the aiui package has it only
under pnpm's store). It logs in, opens `/dashboard/chat`, dismisses the Remote
Companion modal (**Escape first — its button uses a curly apostrophe, so
`has-text("I've installed it")` never matches**), finds the `/aiui/` frame, types
into the real composer, and screenshots. It also installs an `addInitScript` probe
that logs every `chat:response` with its surface bucket counts — that probe is what
proved the pipeline end to end.
**Latest verified run:**
```
heading before: "16 Images"
heading during turn: "Loading…"
heading after: "13 Images"
[PROBE] chat:response success=true surfaces=1
detail=[{"tool":"content_list","scope":"own","films":0,"songs":2,"images":13}]
```
Screenshots in the scratchpad: `13-answered.png` shows prose on the left and a
populated Songs/Images grid on the right — the thing that was empty before.
---
## Binary drift — DO THIS BEFORE THE ISO
`core/target/release/archipelago` was built BEFORE the `install.rs` SearXNG change,
and the currently deployed `/usr/local/bin/archipelago` is that same binary. It has
all the surface/peer fixes (verified live) but **not** the SearXNG seed fix.
```
cd core && CARGO_INCREMENTAL=0 cargo build --release -p archipelago # ~9 min
```
Confirm it took: `strings core/target/release/archipelago | grep -A2 "limiter: false"`
should now show the `formats` lines. Then deploy per the loop below and cut the ISO.
## Deploy loop (archi-dev-box = this box)
```
# frontend
cd neode-ui && npm run build
sudo rsync -a --exclude 'aiui/' --exclude 'archipelago-runtime/' \
web/dist/neode-ui/ /opt/archipelago/web-ui/
# AIUI (build-aiui.sh HANGS after succeeding — wrap in `timeout 540 ... || true`
# and check dist/index.html's mtime; the dist is complete)
bash scripts/build-aiui.sh
sudo rsync -a --delete aiui/packages/app/dist/ /opt/archipelago/web-ui/aiui/
# binary
sudo cp -f /usr/local/bin/archipelago /opt/archipelago/rollback/archipelago.bak
sudo install -m 755 core/target/release/archipelago /usr/local/bin/archipelago
sudo systemctl restart archipelago
```
## Test state
- **Rust:** `cargo test --bin archipelago assistant::` → **122 passed, 0 failed.**
(`-p archipelago --lib` fails with "no library targets" — it is a bin crate.)
- **neode-ui:** adapter + broker + views → **71 passed, 0 failed.** I also fixed a
**pre-existing** failure in `toolConfirm.test.ts` (it asserted `rpcClient.call` was
never called, but `ContextBroker.start()` hydrates AI permissions over RPC; narrowed
to "no `assistant.*` call", which is the property actually under test).
- **AIUI:** **348 passed, 3 failed — all three PRE-EXISTING**, confirmed by stashing
my changes and re-running. They are `seed-conversations.test.ts` (seed-songs content
types), `seedExtraction.test.ts` (extracts 10 songs), and `useAI.test.ts`
(`webSearch` flag in the request body — this one may now be *related* to the
embedded-mode gate; re-check it, it was failing before but for a different reason).
## Traps — do not repeat
- **I removed a running container** by running `podman restart searxng` directly. The
orchestrator owns lifecycle: it saw "stopping" and the container vanished. Recover
with the RPC `container-start` and params **`{"app_id": "..."}`** (not `{"name":...}`).
Do not drive podman by hand for app containers.
- `es.json` reformatted wholesale when edited with `json.dump` (470-line diff). Insert
keys textually, preserving the file's own formatting.
- Verify on the NODE, not from source (nginx template vs `/etc/nginx/sites-enabled/`).
- rustls does NOT check key/cert pairing — the check in `appgate/tls.rs` is load-bearing.
- AIUI must build with `VITE_BASE_PATH=/aiui/` or you get a black page.
- Never `rm -rf /opt/archipelago/web-ui/*` — it destroys `aiui/`.
- Restarting `archipelago` does NOT kill app containers (they live in `/user.slice`).
- Release build is ~9 min. Budget for it.
## Phase 13 GSD
14/15. **13-15 only** (device-close, blocking human-verify). Operator verified checks
1, 3 and 4. **Check 2 was the holdout — "no film content exists on this node to
display".** That premise should be re-tested now: peer content demonstrably reaches
the surface, and the images bucket means the node's own catalogue renders too. Then
write `13-UAT.md`, fill `13-VALIDATION.md`, close.