From a245e5d29fdb17005f82082301622dbf9cfdc363 Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 5 Aug 2026 15:18:34 -0400 Subject: [PATCH] =?UTF-8?q?docs(13-08):=20summary=20=E2=80=94=20confirm=20?= =?UTF-8?q?gate=20+=20trusted=20chrome=20complete,=20checkpoint=20approved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 3 (checkpoint:human-verify, blocking) approved by the operator after a full on-device pass on archi-dev-box: deny/approve/read-only/fail-safe-timeout all verified with a real Claude 4.5 Haiku backend against a real container. cargo assistant:: 29/29 green (incl. declined_action_never_reprompts_same_turn), vitest toolConfirm/contextBroker/chatAiuiEmbed 40/40 green. STATE.md/ROADMAP.md/ REQUIREMENTS.md updated (9/15 plans, AIUI-01/AIUI-04 marked complete for this plan's contribution). Next: wave 4 (13-10, then 13-11). Co-Authored-By: Claude Fable 5 --- .planning/REQUIREMENTS.md | 8 +- .planning/ROADMAP.md | 4 +- .planning/STATE.md | 22 +- .../13-08-SUMMARY.md | 242 ++++++++++++++++++ 4 files changed, 261 insertions(+), 15 deletions(-) create mode 100644 .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-08-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index cd48075e..0994e6df 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -77,10 +77,10 @@ declared exit criteria (multinode pass + workstreams B/C/F), `.planning/codebase ### AIUI — Conversational Node Control & Content Surfaces (AIUI) — added 2026-08-03 -- [ ] **AIUI-01**: Human-language node control — a typed request in AIUI chat ("restart bitcoin", "how much space is left", "who's connected") reaches a real node action and returns a real result, over a permissioned tool-calling bridge rather than raw RPC +- [x] **AIUI-01**: Human-language node control — a typed request in AIUI chat ("restart bitcoin", "how much space is left", "who's connected") reaches a real node action and returns a real result, over a permissioned tool-calling bridge rather than raw RPC - [ ] **AIUI-02**: Conversational settings — the system settings surfaced across neode-ui become reachable by conversation, scoped to what the user has granted - [ ] **AIUI-03**: Content surfaces made real — AIUI's designed-but-empty content views render live node data (peer files, music, IndeeHub movies, owned/paid content); audio belongs to the global bottom-bar player and media streams via Range requests, never base64 blobs -- [ ] **AIUI-04**: Sandboxed by construction, permissioned by the user — secrets never reach the browser or the model context; the chat gets an explicit, user-granted, default-closed, revocable capability scope; destructive and identity-touching operations are human-confirmed; tool authority never derives from peer-controlled content (BLOCKER) +- [x] **AIUI-04**: Sandboxed by construction, permissioned by the user — secrets never reach the browser or the model context; the chat gets an explicit, user-granted, default-closed, revocable capability scope; destructive and identity-touching operations are human-confirmed; tool authority never derives from peer-controlled content (BLOCKER) - [ ] **AIUI-05**: Delivery and build — AIUI reaches nodes on a delivery path an operator can actually receive updates through, with `VITE_BASE_PATH=/aiui/` enforced by the build script so a hand-built bundle cannot ship a black page - [ ] **AIUI-06**: Verified on device — in the real embedded iframe on archi-dev-box, mobile included, not only in the local `dev:mock` loop @@ -153,10 +153,10 @@ Which phases cover which requirements. Updated during roadmap creation. | MKT-02 | Phase 8 | Pending | | MKT-03 | Phase 8 | Pending | | MKT-04 | Phase 8 | Pending | -| AIUI-01 | Phase 13 | Pending | +| AIUI-01 | Phase 13 | Complete | | AIUI-02 | Phase 13 | Pending | | AIUI-03 | Phase 13 | Pending | -| AIUI-04 | Phase 13 | Pending | +| AIUI-04 | Phase 13 | Complete | | AIUI-05 | Phase 13 | Pending | | AIUI-06 | Phase 13 | Pending | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 653b1702..6183f9bd 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -345,7 +345,7 @@ Plans: **Depends on:** Independent of Phases 1–12 for its UI and content work. Its security model must not contradict Phase 10 (Key-Material Hardening) — coordinate rather than widen. AIUI's own source lives in a **separate repository** (`git.tx1138.com/lfg2025/AIUI`, branch `development`, cloned at `~/Projects/AIUI`), so this phase spans two repos and needs push access to both. -**Plans:** 8/15 plans executed +**Plans:** 9/15 plans executed Plans: @@ -364,7 +364,7 @@ Plans: **Wave 3** - [x] 13-07-PLAN.md — Music index + music.* RPCs + freshness (AIUI-03) -- [ ] 13-08-PLAN.md — D-11 confirm gate: node-authored, nonce-bound, rendered in trusted chrome (AIUI-01/04) +- [x] 13-08-PLAN.md — D-11 confirm gate: node-authored, nonce-bound, rendered in trusted chrome (AIUI-01/04) **Wave 4** diff --git a/.planning/STATE.md b/.planning/STATE.md index f6c3e33a..7279e89c 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,15 +5,15 @@ milestone_name: milestone current_phase: 13 current_phase_name: aiui-functional-conversational-node-control-and-content-surf status: executing -stopped_at: "13-08 AT TASK 3 BLOCKING HUMAN-VERIFY GATE 2026-08-05. Tasks 1+2 COMPLETE and verified on current HEAD (ae042db9): fc09d7a2 is the commit of record for both (its tools.rs/grants.rs/backends diffs confirmed rustfmt-only); cargo assistant:: 28/28, toolConfirm.test.ts 10/10, contextBroker+chatAiuiEmbed 28/28, vue-tsc clean, dispatcher.rs untouched, all acceptance greps pass, everything pushed to gitea-ai. NO SUMMARY yet — plan closes only after the operator runs Task 3's on-device verification (deploy to archi-dev-box dev pair, inspect the confirmation dialog per 13-08-PLAN.md Task 3 how-to-verify: full-viewport backdrop over the iframe, app id verbatim, effect + non-effect named, deny/approve behavior, distinct dialogs per app, no dialog on reads, no execution after service restart). Resume-signal: operator types 'approved' + pastes the dialog text, or describes what read wrong; then continuation executor writes 13-08-SUMMARY.md and closes the plan. DO NOT self-approve. Next after 13-08: 13-10/13-11 (wave 4). Executor gotcha stands: never plainly background cargo — foreground with 600s timeout or detached+poll." -last_updated: "2026-08-05T15:50:00.000Z" +stopped_at: 13-08 COMPLETE 2026-08-05, next wave 4 (13-10, then 13-11 -- check 13-11 for stale /home/archipelago/Projects/AIUI absolute paths per D-19 before executing it) +last_updated: "2026-08-05T19:17:57.167Z" last_activity: 2026-08-05 -last_activity_desc: 13-08 Tasks 1+2 verified complete (ae042db9); stopped at Task 3 blocking human-verify gate +last_activity_desc: 13-08 complete (D-11 confirm gate + trusted-chrome dialog, checkpoint approved on-device) progress: total_phases: 13 completed_phases: 2 total_plans: 60 - completed_plans: 46 + completed_plans: 47 percent: 15 --- @@ -29,11 +29,11 @@ See: .planning/PROJECT.md (updated 2026-07-29) ## Current Position Phase: 13 (aiui-functional-conversational-node-control-and-content-surf) — EXECUTING -Plan: 8 of 15 complete (13-01, 13-02, 13-03, 13-04, 13-05, 13-06, 13-09) — next: waves 3+ +Plan: 9 of 15 complete (13-01, 13-02, 13-03, 13-04, 13-05, 13-06, 13-07, 13-08, 13-09) — next: wave 4 (13-10, 13-11) Status: Ready to execute -Last activity: 2026-08-04 — 13-04 complete (D-13 music model, lofty gate, tag extraction 7/7 green) +Last activity: 2026-08-05 — 13-08 complete (D-11 confirm gate + trusted-chrome dialog, checkpoint approved on-device) -Progress: [████████░░] 77% +Progress: [████████░░] 78% ## Performance Metrics @@ -65,6 +65,7 @@ Progress: [████████░░] 77% | Phase 02 P11 | ~150min | 3 tasks | 8 files | | Phase 01 P01 | n/a-continuation | 2 tasks | 1 files | | Phase 13 P07 | ~3h45m | 2 tasks | 5 files | +| Phase 13 P08 | ~7h45m (elapsed, w/ session restart) | 3 tasks | 10 files | ## Accumulated Context @@ -131,6 +132,9 @@ Decisions are logged in PROJECT.md (10 locked ADRs in the `` block + - [Phase ?]: 01-01: record_peer_transport and update_node routed through FEDERATION_STORE_LOCK via *_inner; tombstone-write-failure test added; full-suite verify blocked by a concurrent agent's uncommitted install.rs edit (unrelated file, not fixed per scope boundary) - [Phase ?]: UIFIX-02: connected-nodes card height tracks row sibling via xl:flex-1 xl:basis-0 (zero-basis flex-grow) instead of flex-auto, with an xl:min-h-[40rem] floor for a short sibling (discovery disabled), tuned from an initial 20rem guess per Dorian's live feedback - [Phase ?]: 13-07: media_roots = filebrowser/Music + purchased-content (both D-13 sources as local roots); music.reindex incremental:true wires refresh_incremental to a production caller; one comparator everywhere with TrackId (source,path) tiebreak +- [Phase ?]: [Phase 13, 13-08]: System prompt rewritten to make the model call destructive tools directly rather than text-asking for confirmation — the original wording ('every write requires a human confirmation') read as 'collect consent in text first', which skipped the tool call and dropped the user's typed 'confirmed' into a void on the next stateless turn +- [Phase ?]: [Phase 13, 13-08]: CONFIRM_TIMEOUT raised 120s->300s and the full HTTP timeout chain (rpcClient, assistant.chat 420s, AIUI bridge 180s->430s) raised past it, so transport can no longer time out a human reading the confirm dialog before the confirm gate itself does +- [Phase ?]: [Phase 13, 13-08]: Declined actions remembered per-turn in ToolExecCtx, keyed by the same action_key the approval nonce binds; execute_tool refuses a re-ask for that exact action before the gate reopens, closing a retry loop where a declined action kept re-prompting (T-13-50 mechanized) ### Pending Todos @@ -208,8 +212,8 @@ The 5x lifecycle gate was NOT run. ## Session Continuity -Last session: 2026-08-05 (operator bypass-permissions relaunch) -Stopped at: 13-08 continuation executor — Task 1 re-verified green on current HEAD (28/28 +Last session: 2026-08-05T19:17:34.921Z +Stopped at: 13-08 COMPLETE 2026-08-05, next wave 4 (13-10, then 13-11 -- check 13-11 for stale /home/archipelago/Projects/AIUI absolute paths per D-19 before executing it) `assistant::` tests incl. `approval_nonce_binds_to_exact_action` individually; dispatcher.rs untouched; fc09d7a2's tools.rs/grants.rs/backends/mod.rs diffs confirmed rustfmt-only, no behavior change). Task 2 (ToolConfirmModal.vue, contextBroker.ts, Chat.vue mount, diff --git a/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-08-SUMMARY.md b/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-08-SUMMARY.md new file mode 100644 index 00000000..1aee90a0 --- /dev/null +++ b/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-08-SUMMARY.md @@ -0,0 +1,242 @@ +--- +phase: 13-aiui-functional-conversational-node-control-and-content-surf +plan: 08 +subsystem: ai-assistant-safety +tags: [rust, vue, confirm-gate, nonce, teleport, tool-calling, security, timeout] + +requires: + - phase: 13-aiui-functional-conversational-node-control-and-content-surf + provides: "13-05's curated tool registry, ToolDef.destructive flag, and default-closed grants (AIUI-01/02)" +provides: + - "D-11 confirm gate: destructive tool calls suspend execute_tool until a human approves a node-authored, nonce-bound dialog" + - "ConfirmGate (assistant/confirm.rs): in-memory pending-confirmation queue, no persistence path, nonce over hash(tool_name, validated_args)" + - "ToolConfirmModal.vue: Teleport-to-body trusted-chrome dialog, RPC-fetched text only, zero path from the iframe's message channel" + - "assistant.confirm-tool / assistant.pending RPC methods routed through 13-01's existing assistant. arm" + - "Declined-action memory: a re-ask for the same action in the same turn is refused before the gate reopens the dialog" + - "Timeout chain fix: CONFIRM_TIMEOUT 300s, rpcClient/assistant.chat/AIUI bridge timeouts all now exceed the human confirmation wait" +affects: [13-10, 13-11, 13-12, 13-13, 13-14, 13-15] + +tech-stack: + added: [] + patterns: + - "Trusted-chrome confirmation dialogs: Teleport(body) + full-screen backdrop, text RPC-fetched from the node, never accepted from the iframe's postMessage channel (NostrSignConsent.vue lineage)" + - "Nonce-bound confirm/execute parity: approval nonce is minted over hash(tool_name, validated_args) post-validate, so a mismatched or replayed nonce is refused arithmetically rather than by convention" + - "Declined-action memory keyed by the same canonical action_key the nonce binds, checked before the confirm gate opens, so a retrying model cannot re-surface a just-declined dialog in the same turn" + +key-files: + created: + - core/archipelago/src/assistant/confirm.rs + - neode-ui/src/components/ToolConfirmModal.vue + - neode-ui/src/services/__tests__/toolConfirm.test.ts + modified: + - core/archipelago/src/assistant/loop_.rs + - core/archipelago/src/assistant/mod.rs + - core/archipelago/src/api/rpc/assistant_chat.rs + - neode-ui/src/services/contextBroker.ts + - neode-ui/src/views/Chat.vue + - aiui/packages/app/src/services/archyBridge.ts + - scripts/verify-aiui-deploy.sh + +key-decisions: + - "System prompt rewritten: the model must call the destructive tool directly and let the node present the trusted dialog, never text-ask the user for confirmation itself — the original wording read as 'collect consent in text first', which both skipped the tool call and, on the next (stateless, pre-history) turn, dropped the user's typed 'confirmed' into a void" + - "CONFIRM_TIMEOUT raised 120s -> 300s, and the full HTTP timeout chain (rpcClient default, assistant.chat RPC, AIUI bridge fetch) raised past it so a human reading the dialog can no longer time out the transport before the confirm gate itself does" + - "Declined actions are remembered per-turn in ToolExecCtx, keyed by the same action_key the approval nonce binds, and execute_tool refuses a re-ask for that exact action before the gate reopens — closes a retry loop where a declined action kept re-prompting" + - "verify-aiui-deploy.sh's curl | grep -q pipeline was a false negative: grep -q exits on first match and SIGPIPEs curl mid-transfer under pipefail, which curl reports as a failure even against a correctly-deployed asset (the marker sat at ~27% into a 416KB chunk) — fixed by fetching to a temp file first, then grepping the file" + +requirements-completed: [AIUI-01, AIUI-04] + +coverage: + - id: D1 + description: "A destructive tool call suspends the loop before execution; only a nonce matching the exact validated action executes it; a mismatched or replayed nonce is refused; a daemon restart drops rather than resurrects a pending action" + requirement: "AIUI-04" + verification: + - kind: unit + ref: "core/archipelago/src/assistant/confirm.rs#destructive_tool_requires_confirm,approval_nonce_binds_to_exact_action,restart_drops_pending_not_executes,timeout_declines_and_does_not_execute,confirm_wait_holds_no_shared_lock" + status: pass + human_judgment: false + - id: D2 + description: "The confirmation dialog renders in neode-ui's trusted chrome (Teleport to body, full-screen backdrop) with text fetched from the node over RPC; no path exists from the iframe's message channel to open, restyle, or resolve it" + requirement: "AIUI-04" + verification: + - kind: unit + ref: "neode-ui/src/services/__tests__/toolConfirm.test.ts#iframe_message_cannot_open_or_resolve_confirmation (12 tests total, all passing)" + status: pass + - kind: manual_procedural + ref: "Task 3 on-device verification, archi-dev-box, 2026-08-05 — operator: 'that was shown perfectly'" + status: pass + human_judgment: true + rationale: "AI-SPEC §1b: whether the dialog is genuinely un-restylable by the iframe and whether the copy clears the clear-signing bar is a visual/trust judgment a security-minded author systematically under-catches in their own copy — confirmed by an independent human on real hardware, not just by grep/unit assertions" + - id: D3 + description: "Deny leaves the target container untouched and the chat reports the decline honestly; approve executes exactly that action and the chat reports it; a read-only question raises zero dialogs; two different apps produce visibly different dialog text" + requirement: "AIUI-01" + verification: + - kind: manual_procedural + ref: "Task 3 on-device transcript: immich deny (container up 50+min unchanged, chat: 'declined... No changes have been made'), botfights approve (journalctl RESTART/RESTART complete, container Up seconds later), disk-space question (zero dialogs, plain refusal to Settings) — screenshots 13-08-task3-chat-deny-approve.png, 13-08-task3-readonly-no-dialog.png" + status: pass + human_judgment: true + rationale: "End-to-end write-path correctness on a real node with a real model is a device-level behavioral judgment, not something a unit test can assert" + - id: D4 + description: "A confirmation that times out, or whose transport times out first, declines rather than silently executing or hanging the turn" + requirement: "AIUI-04" + verification: + - kind: unit + ref: "core/archipelago/src/assistant/confirm.rs#timeout_declines_and_does_not_execute; neode-ui/src/services/__tests__/toolConfirm.test.ts (aiui:tool-confirm-expired handling, 2 new cases)" + status: pass + - kind: manual_procedural + ref: "Fail-safe proof (incidental, pre-fix): operator's first approve attempt landed after the then-120s timeout — 13:37:12 assistant.confirm-tool refused req_id=confirm-1 no such pending confirmation; nothing executed" + status: pass + human_judgment: false + +duration: ~7h45m (elapsed across two operator sessions with a mid-flight checkpoint restart; active executor+UAT time was substantially less) +completed: 2026-08-05 +status: complete +--- + +# Phase 13 Plan 08: Confirm Gate — Trusted Chrome for Destructive Actions Summary + +**Nonce-bound confirm/execute parity (`assistant/confirm.rs`) rendered through a Teleport-to-body `ToolConfirmModal.vue` that reads its text only from the node's own `assistant.pending` RPC response, verified end-to-end on archi-dev-box with a real Claude 4.5 Haiku backend restarting a real container only after human approval.** + +## Performance + +- **Duration:** ~7h45m wall-clock across the full plan arc (`db11c625` 07:12 → `077a098d` 14:58, 2026-08-05), spanning a mid-flight operator session restart at Task 1; this continuation session's own scope was verification + summary only +- **Tasks:** 3/3 (Task 1 auto/tdd, Task 2 auto/tdd, Task 3 checkpoint:human-verify/blocking — APPROVED) +- **Files modified:** 8 core files across the plan's `files_modified` list, plus 2 additional files touched by UAT-driven fixes (`scripts/verify-aiui-deploy.sh`, `aiui/packages/app/src/services/archyBridge.ts`) + +## Accomplishments + +- `ConfirmGate` in `core/archipelago/src/assistant/confirm.rs`: in-memory-only pending-confirmation queue (no `fs::write`/persist path — grep-verified 0 hits), nonce minted over `hash(tool_name, validated_args)` post-`ToolDef::validate`, so approval binds to exactly what will execute rather than to what the model asked for. +- `execute_tool`'s `destructive` branch (`loop_.rs`) now suspends on `ctx.confirm.request(...)` before `tool.execute`, holding no shared lock across the await — other RPCs (`mesh.assistant-status`, etc.) are unaffected while a human decides. +- `ToolConfirmModal.vue`: modeled on `NostrSignConsent.vue`, `Teleport to="body"`, full-screen backdrop, plain interpolation (no `v-html`), text fetched over `assistant.pending` on the authenticated RPC session — zero code path accepts a description from the iframe's `postMessage` channel. +- New, distinct event pair `aiui:tool-confirm-request` / `aiui:tool-confirm-response` in `contextBroker.ts` (not the pre-existing install-app pair), plus `aiui:tool-confirm-expired` added mid-plan so the modal closes itself when the node's own timeout fires instead of hanging open. +- On-device UAT (Task 3) surfaced and fixed six real defects (below) that no unit test caught, then re-verified all of them on the real node before the operator approved. +- Full test suites confirmed green on this continuation: `cargo test --package archipelago assistant::` — **29/29 passing**, including `declined_action_never_reprompts_same_turn` (net growth from 28 to 29 tests in-plan, since the declined-action-memory fix added its own regression test); `npx vitest run toolConfirm.test.ts contextBroker.test.ts chatAiuiEmbed.test.ts` — **40/40 passing** (3 test files). + +## Task Commits + +Task 1 and Task 2 were both re-verified complete on continuation and their commit of record is `fc09d7a2` (rustfmt-only diffs confirmed against the RED baseline `db11c625`; see `ae042db9`). The plan then went through six UAT-driven deviation commits before Task 3's checkpoint was approved: + +1. **Task 1: The gate (confirm.rs, nonce binding, in-memory only)** — RED `db11c625`, GREEN commit of record `fc09d7a2` +2. **Task 2: The trusted chrome (ToolConfirmModal.vue, contextBroker.ts, Chat.vue mount)** — commit of record `fc09d7a2` +3. `cfbbd268` — test: satisfy `vue-tsc -b` build-mode checks in test files (`npm run build` is stricter than flat `--noEmit`) +4. `830b77af` — fix(13-09): `verify-aiui-deploy.sh` false-negative fixed (fetch-to-temp-then-grep, replacing `curl | grep -q`) +5. `44f552cc` — fix(13-08): system prompt rewritten to call tools directly rather than text-asking for confirmation +6. `31f9a4d5` — fix(13-08): `CONFIRM_TIMEOUT` 120s → 300s + `aiui:tool-confirm-expired` event closes the modal on node-side timeout +7. `44c864ac` — docs: deferred-items.md entry for the AIUI-over-host background regression (mobile/companion) +8. `2d1f09d8` — fix(13-08): declined-action memory (`declined_action_never_reprompts_same_turn`) + full timeout-chain fix (`assistant.chat` 420s, AIUI bridge 180s→430s) +9. `077a098d` — docs(13-08): Task 3 on-device evidence screenshots (deny/approve transcript, read-only no-dialog) + +**Plan metadata:** this commit (`docs(13-08): summary — confirm gate + trusted chrome complete, checkpoint approved`) + +## Files Created/Modified + +- `core/archipelago/src/assistant/confirm.rs` — `ConfirmGate`, `PendingConfirmation`, `Confirmed`, `mint_nonce`, `build_description`, `CONFIRM_TIMEOUT` (new) +- `core/archipelago/src/assistant/loop_.rs` — `execute_tool`'s `destructive` branch filled in; declined-action pre-gate check added +- `core/archipelago/src/assistant/mod.rs` — system prompt rewrite; `ToolExecCtx` declined-action memory; `assistant.chat` timeout raised to 420s +- `core/archipelago/src/api/rpc/assistant_chat.rs` — `handle_assistant_confirm_tool`, `handle_assistant_pending` +- `neode-ui/src/components/ToolConfirmModal.vue` — new trusted-chrome modal (new) +- `neode-ui/src/services/contextBroker.ts` — `handleToolConfirmRequest`, `aiui:tool-confirm-request/-response/-expired` events, RPC bridge timeout raised 180s→430s +- `neode-ui/src/views/Chat.vue` — modal mounted as iframe sibling; `aiui:tool-confirm-expired` handler closes the modal +- `neode-ui/src/services/__tests__/toolConfirm.test.ts` — new suite (new), 12 tests +- `aiui/packages/app/src/services/archyBridge.ts` — bridge fetch timeout raised to cover the confirm wait +- `scripts/verify-aiui-deploy.sh` — false-negative fix (curl-to-temp-file, then grep) + +## Decisions Made + +See `key-decisions` in frontmatter — system-prompt rewrite (direct tool call, no text-consent), timeout chain raised end-to-end past `CONFIRM_TIMEOUT`, declined-action memory keyed by the nonce's own action identity, and the `verify-aiui-deploy.sh` pipefail root cause. + +## Deviations from Plan + +### Auto-fixed Issues (UAT-driven, Task 3) + +**1. [Rule 1 - Bug] `vue-tsc -b` build-mode type errors in test files** +- **Found during:** pre-deploy build for Task 3 (`npm run build` is stricter than the flat `--noEmit` check Task 2's acceptance criteria used) +- **Fix:** corrected test-file type issues in `toolConfirm.test.ts`, `chatAiuiEmbed.test.ts`, `archyContentAdapter.test.ts` +- **Committed in:** `cfbbd268` + +**2. [Rule 1 - Bug] `verify-aiui-deploy.sh` reported false negatives against a correctly-deployed asset** +- **Found during:** Task 3 deploy-verification step +- **Issue:** `curl ... | grep -q ` under `pipefail`: `grep -q` exits on first match and SIGPIPEs `curl` mid-transfer; `curl` reports that as a failure exit code even though the marker (sitting ~27% into a 416KB chunk) was genuinely present. Failed 3/3 against a known-good deploy. +- **Fix:** fetch to a temp file first, then `grep` the file. Verified against a deliberate negative control (a chunk without the marker) to confirm the fix doesn't just mask real failures. +- **Committed in:** `830b77af` + +**3. [Rule 1 - Bug] System prompt caused the model to text-ask for confirmation instead of calling the tool** +- **Found during:** Task 3, first live restart attempt — the model never called `assistant.pending`/the destructive tool at all; it asked the user "shall I restart X?" in chat text, and because turns are stateless (history lands in 13-10), the user's typed "confirmed" reply had nothing to bind to and was dropped. +- **Fix:** rewrote the system-prompt language from "every write requires a human confirmation" (which read as "collect consent in text first") to instruct the model to call the destructive tool directly and let the node present the trusted dialog — never text-ask. +- **Committed in:** `44f552cc` + +**4. [Rule 1 - Bug] Confirm dialog timed out mid-read (operator's own UAT hit this)** +- **Found during:** Task 3 — operator's genuine first approve attempt landed after the then-120s `CONFIRM_TIMEOUT` elapsed while reading the dialog +- **Fix:** `CONFIRM_TIMEOUT` 120s → 300s; added `aiui:tool-confirm-expired` dispatched on both poll and turn-end so `Chat.vue` closes the modal cleanly on an expiry instead of leaving a dead dialog open +- **Verification:** 2 new vitest tests added; suite went 21/21 → 31/31 green (later 40/40 once Task 3's other suites are counted) +- **Committed in:** `31f9a4d5` + +**5. [Rule 1 - Bug] Declined actions kept re-prompting in the same turn; separately, transport timeouts fired before the human-speed confirm wait could complete** +- **Found during:** Task 3 — the model retried a just-declined action, reopening the dialog repeatedly (mechanizes T-13-50's habituation threat rather than mitigating it) +- **Fix (a):** `ToolExecCtx` now remembers declined actions per-turn keyed by `confirm::action_key` (the same canonical identity the nonce binds); `execute_tool` refuses a re-ask for that exact action *before* the confirm gate reopens. New test: `declined_action_never_reprompts_same_turn`. +- **Fix (b):** the full HTTP timeout chain was shorter than the human confirm wait: `rpcClient`'s 15s default was aborting every confirmable turn client-side. `assistant.chat` raised to 420s; the AIUI bridge fetch raised 180s→430s (host-side timeout fires first, by design); per-model-call `ASSISTANT_HTTP_TIMEOUT` left at 180s (that's the model-call budget, not the human-wait budget); nginx was already 600s. +- **Committed in:** `2d1f09d8` + +--- + +**Total deviations:** 5 auto-fixed (all Rule 1 — bugs surfaced by real on-device UAT, none of them caught by the plan's own unit-test acceptance criteria). No architectural changes; no scope creep — every fix is inside this plan's own `files_modified` list plus the two adjacent files (`verify-aiui-deploy.sh`, `archyBridge.ts`) directly implicated by the deploy-verification and timeout-chain fixes. +**Impact on plan:** all five fixes were necessary for the write path to function correctly end-to-end on a real node with a real model; without them the confirm gate was either unreachable (system-prompt bug), unusable (timeout-too-short), or exploitable via habituation-by-retry (declined-action re-prompt). The plan's own acceptance criteria (unit tests, greps) all passed *before* Task 3 — these are exactly the class of gap Task 3's checkpoint exists to catch. + +## Auth Gates + +None. + +## Task 3 On-Device Evidence (checkpoint:human-verify, gate="blocking" — APPROVED) + +Operator ran the full Task 3 how-to-verify sequence on archi-dev-box (this machine, 127.0.0.1), backend Claude 4.5 Haiku (visible in the operator's own screenshot). Operator's words on approval: **"that was shown perfectly"**. + +- **Deny path:** immich restart denied → container untouched (`podman ps` showed it Up 50+ minutes, unchanged); chat honestly reported "The restart request for immich was declined. No changes have been made." +- **Approve path:** botfights restart approved → `journalctl` shows `14:52:30 RESTART: botfights` → `RESTART complete`; container `Up` seconds later. +- **Read-only / no-tool path:** "How much diskspace is left" produced zero dialogs; plain-text refusal pointing to Settings (correctly not treated as a tool-eligible destructive request). +- **Fail-safe proof (incidental, pre-timeout-fix):** the operator's first approve attempt landed after the then-120s timeout — `13:37:12 assistant.confirm-tool refused req_id=confirm-1 no such pending confirmation` — nothing executed. This is the exact behavior S-09/T-13-51 require and it held even before the timeout was lengthened for usability. +- **Screenshots committed** (`077a098d`): `13-08-task3-chat-deny-approve.png`, `13-08-task3-readonly-no-dialog.png`. +- Two different apps (immich, botfights) produced visibly distinguishable dialog text, satisfying S-08 on real hardware, not just in the distinct-resources unit test. + +## Acceptance Criteria: Honest Outcome + +- `cd core && cargo test --package archipelago assistant::` — **29/29 pass**, all seven Task-1-named tests present and passing, including `declined_action_never_reprompts_same_turn` (added mid-plan, not in the original seven). +- `grep -q 'pub struct PendingConfirmation' core/archipelago/src/assistant/confirm.rs` — present. +- `grep -ciE 'fs::write|save|persist|data_dir' core/archipelago/src/assistant/confirm.rs` — 0 (structural, no persistence path). +- `confirm.request` appears before `tool.execute` in `loop_.rs`'s `execute_tool` — confirmed by the original executor's read; unchanged by this plan's later fixes. +- `git diff --exit-code -- core/archipelago/src/api/rpc/dispatcher.rs` — clean, `dispatcher.rs` untouched throughout. +- **Flip-the-flag negative test** (`restart_app`'s `destructive` flag flipped to `false`, confirming `destructive_tool_requires_confirm` goes red, then restored): performed by the original executor and recorded in `fc09d7a2`'s commit history per `ae042db9`'s continuation-verification note — not independently re-run by this continuation session, since Tasks 1/2 were confirmed as commits of record (rustfmt-only diffs) rather than re-implemented. +- `npx vitest run toolConfirm.test.ts contextBroker.test.ts chatAiuiEmbed.test.ts` — **40/40 pass** (3 test files: 12 + 19 + 9). +- `npx vue-tsc --noEmit` — clean (per Task 2's own verification; the stricter `vue-tsc -b` build-mode issue found in Task 3 was in test files only, fixed in `cfbbd268`). +- On-device: deny/approve/read-only/restart-mid-confirmation all behave per spec — see evidence above. Item 9 of Task 3's how-to-verify ("restart archipelago.service while a confirmation is open") is covered structurally by S-09's no-persistence-path grep plus the `restart_drops_pending_not_executes` unit test; the operator's fail-safe timeout incident (above) is independent additional evidence that an unresolved pending confirmation resolves as declined rather than executing. + +## Deploy State (archi-dev-box only — no fleet, no OTA) + +- Binary at `/usr/local/bin/archipelago` (backup `.bak-pre-1308`). +- `neode-ui` + AIUI dists deployed to `/opt/archipelago/web-ui`. The AIUI delegation bundle was **stale pre-phase-13** — this was the actual root cause of chat initially bypassing tools entirely before the system-prompt fix was even in play. Live-chunk-verified via `sw.js` per the fixed `verify-aiui-deploy.sh`, not by grepping the node's `assets/` graveyard. +- Grants file `/var/lib/archipelago/assistant/grants.json` created **manually** with `{"categories":["apps"]}` — there is no UI calling `assistant.grants-set` yet; that lands in a later phase-13 plan. +- Deferred (recorded in `deferred-items.md`, not fixed this plan): AIUI paints its own opaque background over the host default on mobile + companion. Regression window is the 2026-08-05 in-repo AIUI build (post-D-19 migration). Desktop unaffected. Suggested owner: fold into 13-10/13-11 or a verify-work gap plan. + +## Known Model-Quality Observations (not defects, not fixed) + +- Claude 4.5 Haiku's post-approve reply described the action in future tense ("is now restarting… dialog should appear") after the action had already completed. Cosmetic phrasing issue, not a correctness or safety gap — worth tightening when 13-10 lands node-side history and turn-aware phrasing. + +## Issues Encountered + +None beyond the six UAT-driven fixes documented above, all resolved within this plan's own scope. + +## Next Phase Readiness + +- D-11 confirm gate is production-quality and proven end-to-end on real hardware with a real model: no write reaches the node without a human approving a node-authored, nonce-bound, un-spoofable dialog. +- **13-10** (D-04 Ollama tool-calling chain + D-08 node-side history) is the next wave-4 plan. It directly addresses two things this plan's UAT surfaced: (a) the stateless-turn gap that made the model's early text-consent bug possible (history landing there closes that class of failure structurally, not just via the prompt fix), and (b) the post-action future-tense phrasing observation above. +- **13-11** (SongGrid + share-mime fix) has a documented D-19 stale-absolute-path risk (`/home/archipelago/Projects/AIUI`) per STATE.md's accumulated-context note — check it for stale `../AIUI` references before executing, same class of bug `830b77af`'s sibling commits in the 13-09 track already fixed elsewhere. +- Grants UI (calling `assistant.grants-set` from the UI rather than the manual `grants.json` file used for this plan's UAT) is not yet built; tracked as a later phase-13 item, not this plan's scope. +- All work is on archi-dev-box only; no fleet OTA has occurred as part of this plan. + +--- +*Phase: 13-aiui-functional-conversational-node-control-and-content-surf* +*Completed: 2026-08-05* + +## Self-Check: PASSED + +All referenced files found (confirm.rs, ToolConfirmModal.vue, toolConfirm.test.ts, both Task 3 +screenshots). All referenced commit hashes found in `git log --oneline --all` (fc09d7a2, cfbbd268, +830b77af, 44f552cc, 31f9a4d5, 44c864ac, 2d1f09d8, 077a098d, db11c625, ae042db9).