docs(13): add pattern map

This commit is contained in:
archipelago
2026-08-03 09:54:47 -04:00
parent 72b07fbe9c
commit ff2cb5aa0e
@@ -0,0 +1,430 @@
# Phase 13: AIUI — Conversational Node Control & Content Surfaces - Pattern Map
**Mapped:** 2026-08-03
**Files analyzed:** 24 (net-new + modified, both repos)
**Analogs found:** 17 / 24 (7 have no strong precedent — flagged below)
**Scope note:** this phase spans two repos: `/home/archipelago/Projects/archy` (Rust `core/`,
Vue `neode-ui/`) and `/home/archipelago/Projects/AIUI` (Vue, branch `development`). File paths
below are absolute-repo-relative and prefixed accordingly.
---
## File Classification
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|---|---|---|---|---|
| `core/archipelago/src/assistant/mod.rs` | service | request-response (loop) | `core/archipelago/src/mesh/listener/assist.rs` | role-match (Q&A→tool-loop, no precedent for the loop itself) |
| `core/archipelago/src/assistant/tools.rs` | model/schema | transform | `core/archipelago/src/api/rpc/mesh/assistant.rs` (config shape) | weak — **no existing curated-tool-registry precedent in this codebase** |
| `core/archipelago/src/assistant/backends/ollama.rs` | service | request-response | `core/archipelago/src/mesh/listener/assist.rs::call_ollama` | exact (endpoint/shape differs, HTTP client pattern identical) |
| `core/archipelago/src/assistant/backends/claude.rs` | service | request-response | `core/archipelago/src/mesh/listener/assist.rs::call_claude` | exact (endpoint/shape differs, HTTP client pattern identical) |
| `core/archipelago/src/assistant/backends/routstr.rs` | service | request-response + payment | `core/archipelago/src/swarm/payment.rs::auto_pay_token` (payment half) + `call_claude` (HTTP half) | partial — **no existing OpenAI-compatible client in this codebase; net-new** |
| `core/archipelago/src/assistant/loop_.rs` | service | event-driven (multi-turn) | none in this codebase | **no analog — first tool-calling loop; see AI-SPEC §3 for the sketch instead** |
| `core/archipelago/src/assistant/confirm.rs` | service | pub-sub (pending-queue) | `neode-ui/src/services/contextBroker.ts` install-app confirm flow (cross-repo, browser-side half only) | partial — Rust-side pending-queue has no precedent |
| `core/archipelago/src/assistant/history.rs` | model/storage | CRUD | `core/archipelago/src/streaming/session.rs` (data_dir-scoped persisted state) | role-match |
| `core/archipelago/src/api/rpc/assistant_chat.rs` | route (RPC handler) | request-response | `core/archipelago/src/api/rpc/mesh/assistant.rs` | exact |
| `core/archipelago/src/api/rpc/dispatcher.rs` (modified) | route (registry) | request-response | itself — extend `"mesh.assistant-*"` block at ~445 | exact |
| `core/archipelago/src/music/mod.rs` | service | batch/CRUD | `core/archipelago/src/content_server.rs` (catalog load/scan shape) | role-match |
| `core/archipelago/src/music/index.rs` | model/storage | CRUD | `core/archipelago/src/content_server.rs::load_catalog` | role-match |
| `core/archipelago/src/music/tags.rs` | utility | transform | none — new `lofty`-based extractor | **no analog — net-new dependency, gate behind checkpoint:human-verify per RESEARCH.md** |
| `neode-ui/src/services/contextBroker.ts` (modified) | service (browser bridge) | pub-sub (postMessage) | itself — extend existing `handleMessage` switch and the install-app confirm block (lines 140-196) | exact |
| `neode-ui/src/types/aiui-protocol.ts` (modified) | model (protocol types) | transform | itself — extend `AIActionType` union | exact |
| `neode-ui/src/components/ToolConfirmModal.vue` (new) | component | event-driven | `neode-ui/src/components/NostrSignConsent.vue` | exact (Teleport-to-body approve/deny modal) |
| `neode-ui/src/composables/archyContentAdapter.ts` (new) | utility (adapter) | transform | none in neode-ui — **net-new**, shape target is AIUI's `Film`/`Song`/`Podcast` types | no analog — see AIUI content types below |
| `neode-ui/src/api/assistant-client.ts` (new, optional) | service (RPC client wrapper) | request-response | `neode-ui/src/api/filebrowser-client.ts` (scoped-token pattern) | role-match — **do not copy the `streamUrl` JWT-in-query leak (line 176)** |
| `scripts/build-aiui.sh` (new) | config/build script | batch | `scripts/deploy-to-target.sh` (AIUI rsync section, `setup-aiui-server.sh`) | role-match |
| `AIUI: packages/app/src/composables/useAI.ts` (modified) | service (chat client) | streaming | itself — replace `streamClaude`/`streamOpenRouter` direct-to-proxy calls | exact (modify in place) |
| `AIUI: packages/app/src/composables/useArchy.ts` (modified) | service (bridge client) | request-response | itself — extend `buildArchyContext()`/postMessage senders | exact |
| `AIUI: packages/app/src/composables/contentExtraction.ts` (modified/deprecated for Archy content) | utility (transform) | transform | itself — `updatePanelFromText` regex path stays for non-Archy content, bypassed for Archy-sourced grids | exact (partial deprecation) |
| `AIUI: packages/app/src/components/content/FilmGrid.vue` / `SongGrid.vue` (consumers, unmodified props) | component | CRUD (prop-fed) | itself — no code change, just a new data source feeding existing props | exact — **props unchanged, D-12** |
| `AIUI: packages/core/src/types/content.ts` (read, not modified) | model (types) | transform | itself — the target shape `archyContentAdapter.ts` must produce | exact reference |
---
## Pattern Assignments
### `core/archipelago/src/assistant/backends/ollama.rs` (service, request-response)
**Analog:** `core/archipelago/src/mesh/listener/assist.rs` (lines 429-451, `call_ollama`)
**What to copy — HTTP client construction:**
```rust
// Source: core/archipelago/src/mesh/listener/assist.rs:429-451 (VERIFIED, read in full)
async fn call_ollama(model: &str, prompt: &str) -> anyhow::Result<String> {
let client = reqwest::Client::builder().timeout(OLLAMA_TIMEOUT).build()?;
let body = serde_json::json!({
"model": model,
"prompt": prompt,
"stream": false,
});
let resp = client.post(OLLAMA_URL).json(&body).send().await?;
// ... no `tools` field, no multi-turn loop — /api/generate, not /api/chat
}
```
**What must change (do NOT copy as-is):**
- Endpoint: `/api/generate``/api/chat` (tool-calling requires the chat endpoint).
- Request body needs a `messages` array (not bare `prompt`) and a `tools` array
(`[{"type":"function","function":{"name","description","parameters"}}]`).
- Response parsing needs `message.tool_calls` extraction; Ollama gives tool calls no `id`
synthesize one (monotonic counter within the turn), per AI-SPEC §3 Pitfall 3.
- Do not reuse `OLLAMA_TIMEOUT` (60s, airtime-tuned for mesh) — define new constants in
`assistant/` per AI-SPEC §3 Pitfall 6.
**Error handling:** `assist.rs::call_claude`'s `anyhow::Result` propagation and `run_assist`'s
catch-and-fall-back-to-next-backend pattern is the model for the D-04 backend chain (Ollama →
Claude → Routstr fallback on error).
---
### `core/archipelago/src/assistant/backends/claude.rs` (service, request-response)
**Analog:** `core/archipelago/src/mesh/listener/assist.rs` (`call_claude`) +
`core/archipelago/src/api/rpc/mesh/assistant.rs` (key-file read pattern)
**Key location pattern to copy** (lines 27-30 of `assistant.rs`):
```rust
// Source: core/archipelago/src/api/rpc/mesh/assistant.rs:27-30 (VERIFIED)
let claude_available =
tokio::fs::metadata(self.config.data_dir.join("secrets/claude-api-key"))
.await
.is_ok();
```
Reuse `data_dir/secrets/claude-api-key` as the key path — do not introduce a second key
location. `call_claude`'s single-user-message Messages API POST is the HTTP-shape starting
point; extend it with `tools: [...]`, `tool_choice: {"type":"auto","disable_parallel_tool_use":true}`
(AI-SPEC §3 Pitfall 5), and `max_tokens: 2048` (raised from mesh's `512`).
---
### `core/archipelago/src/assistant/backends/routstr.rs` (service, request-response + payment)
**No direct analog for the HTTP client** (first OpenAI-compatible client in this codebase).
Compose from two existing pieces:
**Payment half — copy verbatim as the reusable primitive** (`core/archipelago/src/swarm/payment.rs:77-101`):
```rust
// Source: core/archipelago/src/swarm/payment.rs:77-101 (VERIFIED, read in full)
pub async fn auto_pay_token(
data_dir: &Path,
policy: &PaymentPolicy, // budget_sats + max_fee_sats
accepted_mints: &[String],
price_sats: u64,
) -> Result<Option<String>> {
if !policy.affords(price_sats) { return Ok(None); } // hard cap, D-05
match ecash::build_payment_token(data_dir, accepted_mints, price_sats, policy.max_fee_sats).await {
Ok(token) => Ok(Some(token)),
Err(e) => Ok(None), // never errors on a wallet/mint problem — origin always wins
}
}
```
Call this exactly as-is for D-05's budget cap; `loop_.rs` must treat `None` as "stop and ask,"
never retry.
**Nostr discovery half:** `core/archipelago/src/nostr_discovery.rs::build_nostr_client` (Tor-proxy
aware) — reuse this builder rather than constructing a second `nostr-sdk` client; subscribe to
kind `38421` events for provider discovery.
**HTTP half:** model the `reqwest::Client` construction on `call_claude`'s pattern (same crate,
same TLS/socks features already in `Cargo.toml`), but the request/response shape is net-new
(OpenAI `tools`/`tool_calls` JSON-string-encoded arguments — see AI-SPEC §3 Pitfall 2, do not
confuse with Ollama's already-parsed object).
---
### `core/archipelago/src/assistant/loop_.rs` (service, event-driven multi-turn loop)
**No analog exists in this codebase** — this is confirmed (RESEARCH.md, AI-SPEC.md) to be the
first tool-calling loop ever written here. Do not attempt to derive it from `run_assist`
(single-shot) or from Pine's HA intents (hardcoded read-only, no loop). Build directly from the
`run_loop`/`execute_tool` sketch in `13-AI-SPEC.md` §3/§4 — that IS the pattern source for this
file; there is no in-repo precedent to extract instead.
**Concurrency discipline to copy from `assist.rs`'s own doc comment:**
```
// "Spawned off the radio loop so it never blocks" — VERIFIED, assist.rs's own doc comment.
```
Apply the same discipline: never hold a shared lock (e.g. `state.assistant.write().await`)
across the confirm-gate `.await`, which can block for human-response-time.
---
### `core/archipelago/src/assistant/confirm.rs` (service, D-11 pending-confirmation queue)
**Analog (browser-side half only, cross-repo):** `neode-ui/src/services/contextBroker.ts:140-196`
— the install-app confirm flow (`CustomEvent('aiui:install-request')` / `aiui:install-response`,
60s timeout). This is the closest existing anti-spoofing confirm pattern in the whole codebase
and is explicitly named in CONTEXT.md as the model to extend:
```typescript
// Source: neode-ui/src/services/contextBroker.ts:140-196 (VERIFIED, read in full)
window.dispatchEvent(new CustomEvent('aiui:install-request', {
detail: { requestId: id, appId, marketplaceUrl, version },
}))
const responseHandler = (e: Event) => {
const detail = (e as CustomEvent).detail as { requestId: string; confirmed: boolean }
if (detail.requestId !== id) return
window.removeEventListener('aiui:install-response', responseHandler)
// ... proceed or decline
}
window.addEventListener('aiui:install-response', responseHandler)
setTimeout(() => window.removeEventListener('aiui:install-response', responseHandler), 60000)
```
**Do NOT reuse `aiui:install-request`/`aiui:install-response` directly** — CONTEXT.md and
RESEARCH.md both specify a new, distinct event pair (`aiui:tool-confirm-request` /
`aiui:tool-confirm-response`), because D-11 requires the pending-action text be RPC-fetched
(node-authored), never postMessage-carried (which the iframe could forge). The Rust-side
`confirm.rs` queue itself (keyed by `req_id`/`call_id`, in-memory only, never persisted across
a daemon restart per AI-SPEC §4 "State Management") has no existing analog — build per the
AI-SPEC sketch.
---
### `core/archipelago/src/api/rpc/assistant_chat.rs` (route, request-response)
**Analog:** `core/archipelago/src/api/rpc/mesh/assistant.rs` (full file — `handle_mesh_assistant_status`,
`handle_mesh_assistant_configure`)
**RPC handler shape to copy:**
```rust
// Source: core/archipelago/src/api/rpc/mesh/assistant.rs:13-16 (VERIFIED)
impl RpcHandler {
pub(in crate::api::rpc) async fn handle_mesh_assistant_status(
&self,
) -> Result<serde_json::Value> {
```
Follow the same `impl RpcHandler` + `pub(in crate::api::rpc) async fn handle_*` + `Result<serde_json::Value>`
convention for `handle_assistant_chat`, `handle_assistant_confirm_tool`, `handle_assistant_list_tools`,
`handle_assistant_history`.
**Registration pattern** (`core/archipelago/src/api/rpc/dispatcher.rs:445-446`):
```rust
"mesh.assistant-status" => self.handle_mesh_assistant_status().await,
"mesh.assistant-configure" => self.handle_mesh_assistant_configure(params).await,
```
Add new `"assistant.chat"`, `"assistant.confirm-tool"`, `"assistant.list-tools"`,
`"assistant.history"` entries adjacent to this block. **Verify session/CSRF/RBAC gating applies
automatically** — every method in this dispatch table already passes through
`api/rpc/mod.rs:264-330`'s session-cookie + CSRF + `role.can_access()` check before reaching the
`match`; no bespoke auth needed (per Open Question 4 in RESEARCH.md, confirm this applies rather
than assume).
---
### `core/archipelago/src/music/index.rs` (model/storage, CRUD)
**Analog:** `core/archipelago/src/content_server.rs::load_catalog` (catalog-scan-and-persist shape)
— read this function's on-disk index load/save pattern under `data_dir` and follow the same
convention for the music index (own subdirectory under `data_dir`, per D-13's discretion on
exact location).
---
### `neode-ui/src/components/ToolConfirmModal.vue` (component, D-11 trusted-chrome modal)
**Analog:** `neode-ui/src/components/NostrSignConsent.vue` (full file, 70 lines) — the
project's canonical Teleport-to-body approve/deny modal, structurally identical to what D-11
needs.
**Structure to copy:**
```vue
<!-- Source: neode-ui/src/components/NostrSignConsent.vue:1-20 (VERIFIED) -->
<template>
<Teleport to="body">
<Transition name="modal">
<div
v-if="show"
class="fixed inset-0 z-[3000] flex items-center justify-center p-4"
@click="deny"
>
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
<div ref="modalRef" @click.stop class="glass-card p-6 max-w-md w-full relative z-10">
<div class="flex items-start justify-between gap-4 mb-4">
<h3 class="text-xl font-semibold text-white">Nostr Signing Request</h3>
<button @click="deny" class="p-2 rounded-lg hover:bg-white/10 ..." aria-label="Close" />
</div>
<!-- request-specific detail rendering here -->
<div class="flex gap-3">
<button @click="deny" class="glass-button flex-1 py-2.5 rounded-lg text-sm font-medium">Deny</button>
<button @click="approve" class="glass-button flex-1 py-2.5 rounded-lg text-sm font-medium text-orange-400 border-orange-400/30">Approve</button>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
```
**Content difference from the analog:** D-11 requires the confirmation text be **RPC-fetched
from the node's own pending-action description** (via `assistant.list-tools`/pending-confirmation
poll or the `chat:response` channel that carries a `tool:confirm-request` payload), never
model-authored text and never a postMessage-carried string from AIUI. Wire this modal's props
from `contextBroker.ts`'s new confirm-handling code, not from anything AIUI sends.
---
### `neode-ui/src/services/contextBroker.ts` (modified — new `chat:*`/`tool:confirm-*` message types)
**Analog:** itself — the existing `handleMessage` switch (imports at lines 1-13) and the
install-app confirm block (lines 140-196, shown above under `confirm.rs`).
**Imports pattern already in file** (lines 1-13):
```typescript
// Source: neode-ui/src/services/contextBroker.ts:1-13 (VERIFIED)
import type { Ref } from 'vue'
import type {
AIUIRequest, ArchyResponse, AIContextCategory,
ArchyContextResponse, ArchyActionResponse,
} from '@/types/aiui-protocol'
import { useAIPermissionsStore } from '@/stores/aiPermissions'
import { useAppStore } from '@/stores/app'
import { useContainerStore, BUNDLED_APPS } from '@/stores/container'
import { rpcClient } from '@/api/rpc-client'
import { fileBrowserClient } from '@/api/filebrowser-client'
```
New `assistant-client.ts` (or direct `rpcClient.call('assistant.chat', ...)`) follows this same
import convention. The class-level origin check (`this.allowedOrigin`, constructor lines 26-33)
and the `postToIframe` helper are the transport primitives every new message type must use —
do not add a second postMessage channel.
---
### `neode-ui/src/api/assistant-client.ts` (new, role-match to filebrowser-client.ts)
**Analog:** `neode-ui/src/api/filebrowser-client.ts` — the scoped-token pattern D-01 follows
for minting short-lived, purpose-scoped credentials via an authenticated RPC.
**Known leak to fix, NOT propagate** (`neode-ui/src/api/filebrowser-client.ts:172-176`):
```typescript
// Source: neode-ui/src/api/filebrowser-client.ts:172-176 (VERIFIED)
async streamUrl(path: string): Promise<string> {
// ...
return `${this.baseUrl}/api/raw${safePath}?auth=${token}`
// ^^^^^^^^^^^^^ JWT in URL query string —
// lands in browser history, server access
// logs, Referer headers.
}
```
For any new content/tool streaming URL construction in this phase, do NOT copy this
`?auth=${token}` concatenation. Prefer header-based auth where the consumer can set headers
(`fetchBlobUrl()`-style, per RESEARCH.md Pitfall 5); where a bare `<audio>`/`<video src>` is
unavoidable, scope the token single-resource/single-use rather than reusing the general
FileBrowser session token shape.
---
### `neode-ui/src/composables/archyContentAdapter.ts` (new, no analog — net-new adapter)
**No existing adapter in neode-ui.** Target shape is AIUI's own types
(`/home/archipelago/Projects/AIUI/packages/core/src/types/content.ts`, read directly):
```typescript
// Source: AIUI packages/core/src/types/content.ts:7-53 (VERIFIED, read in full)
export interface Film {
// id, title, ... (lines 7-19)
posterUrl: string
// ...
sources: FilmSource[] // line 20
}
export interface FilmSource { /* type: 'plex'|'nextcloud'|... , url, ... */ }
export interface Song {
// ...
coverUrl?: string // line 50
sources?: SongSource[] // line 53
}
```
**Source shape to map FROM** (`core/archipelago/src/content_server.rs`, `ContentItem`):
```rust
// id, filename, mime_type, size_bytes, description, access, availability, added_at
```
This is Pitfall 4 in RESEARCH.md — there is genuinely no shape overlap; the adapter is
hand-written mapping logic, not a pass-through. Pin the mapping with fixture-based tests
(`archyContentAdapter.test.ts`, listed as a Wave 0 gap in RESEARCH.md's Validation Architecture).
`FilmGrid.vue`/`SongGrid.vue` themselves need **zero code changes** — D-12 is explicit that only
the data source behind the existing props changes.
---
### `AIUI: packages/app/src/composables/useAI.ts` (modified — replace direct-proxy calls)
**Analog:** itself. The current `streamClaude`/`streamOpenRouter` functions call
`${BASE}api/claude/v1/messages` / `${BASE}api/openrouter` directly (the port-3142
`claude-api-proxy.py` passthrough, verified live and unauthenticated in RESEARCH.md). Replace
these call sites with the new `chat:request`/`chat:response` postMessage exchange to
`contextBroker.ts`, matching the shape `useArchy.ts` already uses for its existing
`readFile`/`tailLogs` postMessage calls (grep `useArchy.ts` for its existing postMessage-send
pattern and mirror it — do not invent a third transport convention on the AIUI side).
---
### `scripts/build-aiui.sh` (new, config/build script)
**Analog:** `scripts/deploy-to-target.sh` (AIUI rsync section) and `scripts/setup-aiui-server.sh`
— both already encode the `VITE_BASE_PATH=/aiui/` requirement and the rsync-to-node path.
D-15 requires this be made deliberate (enforced by the script, not remembered) with a
post-deploy check that fetches a live asset — model the fetch-and-verify step on the project's
general "grep the built bundle for new strings before shipping" convention from `CLAUDE.md`
("Frontend build — verify dist changed" feedback note), translated into an automated `curl`
check rather than a manual grep.
---
## Shared Patterns
### Session/CSRF/RBAC gating (applies to every new `assistant.*` and `content.*` RPC)
**Source:** `core/archipelago/src/api/rpc/mod.rs:264-330`
**Apply to:** `assistant_chat.rs`, all new dispatcher entries.
Every RPC method reaching the `match` in `dispatcher.rs` already passed session-cookie + CSRF +
`role.can_access(&method)` checks upstream — no bespoke auth code needed in the new handlers
themselves, only correct registration in the existing table.
### Anti-spoofing confirm gate (D-11, applies to every destructive tool)
**Source:** `neode-ui/src/services/contextBroker.ts:140-196` (browser half) +
`13-AI-SPEC.md §4`'s `execute_tool` sketch (Rust half, no in-repo precedent).
**Apply to:** `confirm.rs`, `ToolConfirmModal.vue`, `assistant_chat.rs`'s confirm-tool handler.
### Backend-key-at-rest pattern (D-01/D-04)
**Source:** `core/archipelago/src/api/rpc/mesh/assistant.rs:27-30` (`data_dir/secrets/claude-api-key`)
**Apply to:** `backends/claude.rs` — reuse the exact key path; do not introduce a parallel key
location (this is also the fix for the port-3142 proxy's separate `ANTHROPIC_API_KEY` — see
Open Question 1 in RESEARCH.md, which the plan must explicitly resolve).
### Teleport-to-body modal (project-mandated pattern, repeatedly reinforced in CLAUDE.md)
**Source:** `neode-ui/src/components/NostrSignConsent.vue`
**Apply to:** `ToolConfirmModal.vue` — full-screen backdrop, `Teleport to="body"`, never
rendered inside the iframe.
### Scoped-token minting via authenticated RPC (never a long-lived credential in a URL)
**Source:** `neode-ui/src/api/filebrowser-client.ts` (pattern good) / same file (`streamUrl`,
leak to avoid)
**Apply to:** `assistant-client.ts` and any new content/tool streaming URL construction.
### Budget-capped payment, never errors, degrades to `None`
**Source:** `core/archipelago/src/swarm/payment.rs::auto_pay_token`
**Apply to:** `backends/routstr.rs` — reuse verbatim, do not reimplement Cashu token building.
---
## No Analog Found
| File | Role | Data Flow | Reason |
|------|------|-----------|--------|
| `core/archipelago/src/assistant/loop_.rs` | service | event-driven multi-turn | First tool-calling agent loop in this codebase (confirmed by RESEARCH.md/AI-SPEC.md); build from the AI-SPEC §3/§4 sketch directly, not from an in-repo analog. |
| `core/archipelago/src/assistant/tools.rs` | model/schema | transform | No curated-tool-registry precedent exists; D-06 explicitly rejects deriving it from `dispatcher.rs`. Build from AI-SPEC §4b.1's `ToolDef`/`schemars` sketch. |
| `core/archipelago/src/assistant/backends/routstr.rs` (HTTP client half) | service | request-response | No OpenAI-compatible client exists in this codebase; wire format is CITED (medium confidence) from `docs.routstr.com`, not independently verified — RESEARCH.md recommends a live-relay spike before hand-writing this file. |
| `core/archipelago/src/music/tags.rs` | utility | transform | New `lofty` dependency, no existing audio-tag-extraction code in this codebase; gate `cargo add lofty` behind `checkpoint:human-verify` per RESEARCH.md's package-legitimacy note. |
| `neode-ui/src/composables/archyContentAdapter.ts` | utility (adapter) | transform | No shape-mapping precedent between Archy's `ContentItem` and any external metadata-rich type; must be hand-written and fixture-pinned (Pitfall 4). |
| Iframe sandbox enforcement mechanism (`Chat.vue` `sandbox`/CSP change, file TBD by the plan) | config | — | Open Question 2 in RESEARCH.md is explicitly unresolved — no existing sandbox/CSP-scoping code to copy; the plan must pick a mechanism (iframe `sandbox` attribute vs. `connect-src` scoping vs. accepted residual risk) before a file/pattern can be assigned. |
| Port-3142 `claude-api-proxy.py` retirement/gating (file TBD by the plan — nginx config edit, script edit, or deletion) | config | — | Open Question 1 in RESEARCH.md is explicitly unresolved (delete vs. gate vs. defer); no pattern to extract until the plan decides which. |
---
## Metadata
**Analog search scope:** `core/archipelago/src/{mesh,api/rpc,swarm,streaming}/`,
`neode-ui/src/{services,components,composables,api,types}/`,
`/home/archipelago/Projects/AIUI/packages/{app,core}/src/`
**Files scanned:** ~20 read/grepped directly across both repos
**Pattern extraction date:** 2026-08-03