docs(13-08): summary — confirm gate + trusted chrome complete, checkpoint approved
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
077a098dbf
commit
a245e5d29f
@@ -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 |
|
||||
|
||||
|
||||
@@ -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**
|
||||
|
||||
|
||||
+13
-9
@@ -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 `<decisions>` 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,
|
||||
|
||||
+242
@@ -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 <marker>` 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).
|
||||
Reference in New Issue
Block a user