docs(13-05): complete curated tool registry and default-closed grants plan

This commit is contained in:
archipelago
2026-08-04 01:39:28 -04:00
parent c098124d93
commit 0a0a2fde43
3 changed files with 269 additions and 4 deletions
+17 -4
View File
@@ -1,10 +1,10 @@
---
schema_version: 1
open_count: 13
open_count: 14
waived_count: 0
fixed_count: 4
total_count: 17
last_updated: 2026-08-03T18:50:07.659Z
fixed_count: 5
total_count: 19
last_updated: 2026-08-04T00:00:00.000Z
---
# Broken Windows Ledger
@@ -33,9 +33,22 @@ last_updated: 2026-08-03T18:50:07.659Z
| 16 | 13 | unrun-verify | core/archipelago/src/assistant/loop_.rs | | cargo test --package archipelago assistant:: (disk_status_tool_executes, unknown_tool_is_refused_not_ignored, assistant_methods_require_session) never completed this session — killed twice under machine resource contention (load avg 35-46 on 4 cores, sibling worktree builds). cargo build --package archipelago DID complete clean (exit 0, only expected dead-code warnings). Test logic was read and reasoned correct but not independently executed — needs a follow-up cargo test run when the machine is free. | open | | 2026-08-03T18:49:48.842Z | |
| 17 | 13 | deviation | external:AIUI/packages/app/src/services/archyBridge.ts | | Task 3 (sendChat/streamViaArchy, external repo /home/archipelago/Projects/AIUI, branch development, commit e30ac1d) is committed locally but NOT pushed to git.tx1138.com — origin was unreachable this session (DNS resolves to 80.71.235.99 but TCP/TLS connect and even 'git ls-remote' timed out repeatedly, sandbox-disabled too). Needs a push from an environment with network access to git.tx1138.com before the fix lands upstream. | fixed | | 2026-08-03T18:50:07.659Z | |
| 18 | 13 | deploy-topology | core/archipelago/src/bootstrap.rs | | run_runtime_assets() in core/archipelago/src/bootstrap.rs reinstalls a SECOND on-node copy of the nginx template (/opt/archipelago/web-ui/archipelago-runtime/image-recipe/configs/nginx-archipelago.conf) over /etc/nginx/sites-available/archipelago on EVERY `systemctl restart archipelago`. Found 2026-08-03 on archy-x250-dev3 during 13-02 Task 3: a hand-patched nginx deploy was silently reverted within ~5 seconds of the daemon restart. Any nginx change that updates only /etc/nginx/ is therefore transient — both copies must be written. This is a live OTA hazard: an operator can deploy an nginx fix, see it applied, restart the daemon, and silently lose it with no error. | open | | 2026-08-03T19:05:00.000Z | |
| 19 | 13 | unrun-verify | core/archipelago/src/container/prod_orchestrator.rs | | 13-05's cargo test --package archipelago (assistant::) cannot be run: the whole test binary fails E0063 in prod_orchestrator.rs's #[cfg(test)] fn port() helper, missing fields auth/auth_rationale on archipelago_container::manifest::PortMapping. Introduced by 0c4826f8 (feat(security): declare which app ports may skip authentication), which added those fields without updating this unrelated test helper — not touched by 13-05 (assistant/tools.rs, grants.rs, mod.rs, assistant_chat.rs) and out of scope per the executor's deviation-rule SCOPE BOUNDARY. cargo check --package archipelago (non-test, real binary) passes clean. Needs a one-line fix to fn port() (add auth: Default::default(), auth_rationale: Default::default()) from whoever owns that file, then a full cargo test --package archipelago assistant:: run to actually verify 13-05's 13 new tests. | open | | 2026-08-04T00:00:00.000Z | |
````json
[
{
"id": 19,
"kind": "unrun-verify",
"phase": "13",
"file": "core/archipelago/src/container/prod_orchestrator.rs",
"line": null,
"description": "13-05's cargo test --package archipelago (assistant::) cannot be run: the whole test binary fails E0063 in prod_orchestrator.rs's #[cfg(test)] fn port() helper, missing fields auth/auth_rationale on archipelago_container::manifest::PortMapping. Introduced by 0c4826f8 (feat(security): declare which app ports may skip authentication), which added those fields without updating this unrelated test helper — not touched by 13-05 (assistant/tools.rs, grants.rs, mod.rs, assistant_chat.rs) and out of scope per the executor's deviation-rule SCOPE BOUNDARY. cargo check --package archipelago (non-test, real binary) passes clean. Needs a one-line fix to fn port() (add auth: Default::default(), auth_rationale: Default::default()) from whoever owns that file, then a full cargo test --package archipelago assistant:: run to actually verify 13-05's 13 new tests.",
"status": "open",
"reason": "",
"recorded_at": "2026-08-04T00:00:00.000Z",
"resolved_at": null
},
{
"id": 18,
"kind": "deploy-topology",
@@ -0,0 +1,209 @@
---
phase: 13-aiui-functional-conversational-node-control-and-content-surf
plan: 05
subsystem: ai
tags: [rust, tool-calling, permissions, rbac, anthropic-claude]
requires:
- phase: 13-01
provides: "CallerScope, PermissionCategory, ToolExecCtx, the run_loop/execute_tool choke point, the assistant.* RPC prefix arm, and the one-tool tracer registry this plan expands"
provides:
- "crate::assistant::tools: the full 13-tool D-06 curated allowlist (9 read, 4 destructive), each hand-written with its own JSON Schema, PermissionCategory and destructive flag"
- "crate::assistant::grants: Grants — D-16 default-closed permission-category store, persisted 0600 under data_dir/assistant/grants.json"
- "assistant.list-tools / assistant.grants-get / assistant.grants-set RPCs, routed through the existing single assistant.* dispatcher arm (dispatcher.rs untouched)"
- "tools::dispatch / tools::validate_business_rules — the hand-written per-tool RPC dispatch and pre-confirm-gate business-rule refusal path"
- "A registry-wide S-04 test (registry_never_exposes_excluded_authority) that scans the WHOLE registry for D-09's excluded-authority terms"
affects: [13-08, 13-09, 13-10, 13-13, 13-14]
tech-stack:
added: []
patterns:
- "D-06 dispatch-by-decision: tools::dispatch's match arms are the only place a tool name is translated into an RPC method string — never derived from api::rpc's own method table"
- "Business-rule validation runs BEFORE the D-07 destructive/confirm gate: an unlisted settings key or an unknown app id is refused with the real reason, not swallowed by the generic 'not yet implemented' placeholder that still gates actual mutation until 13-08"
- "D-16 grants are a single persisted, data_dir-scoped, 0600 JSON file (BTreeSet<PermissionCategory>) — a missing file is default_closed(), never an error and never permissive"
- "PermissionCategory serializes rename_all=kebab-case so the Rust enum and neode-ui's aiPermissions.ts category ids share one wire vocabulary by construction, not by convention"
key-files:
created:
- core/archipelago/src/assistant/grants.rs
modified:
- core/archipelago/src/assistant/tools.rs
- core/archipelago/src/assistant/mod.rs
- core/archipelago/src/assistant/loop_.rs
- core/archipelago/src/api/rpc/assistant_chat.rs
- .planning/WINDOWS.md
- .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/deferred-items.md
key-decisions:
- "loop_.rs was touched even though it is absent from the plan's files_modified frontmatter — the plan's own key_links ('each ToolDef's execute dispatches to an existing authenticated RPC handler') and Task 1's <done> criterion (real refusal messages) are structurally impossible without changing execute_tool's dispatch call and its grant-check signature. Documented as Rule 3 (auto-fix blocking issue), not scope creep — see Deviations."
- "Business-rule validation (SETTABLE_KEYS/claude_api_key check, installed-app-id resolution) was split into a new tools::validate_business_rules function that execute_tool calls BEFORE the destructive/confirm gate. Without this, settings_set and app_restart's refusal messages would never surface — execute_tool's blanket destructive short-circuit ('not yet implemented') would intercept every destructive tool call before dispatch()'s refusal logic ever ran. Caught during self-review, before any test was (attempted to be) run — see Deviations."
- "2 commits instead of 3: Task 1 and Task 2 are committed together (90706fe0). Task 1's own <done> criterion needs assistant_dispatch_tool's params-carrying signature (Task 2's file), and Task 1's action text explicitly asks for a counter on ToolExecCtx (defined in Task 2's mod.rs) — the two tasks are not independently compilable without fabricating a broken intermediate commit. Task 3 (c098124d) cleanly isolates as pure test additions on top of the completed registry."
- "CallerScope::Mesh gained an `authorized: bool` field (was `{ peer_id: String }` only) so a mesh peer's authority resolves to the persisted Grants when authorized, empty otherwise — never wider than the operator's own grants. No real caller constructs this variant yet (mesh has no tool-calling path per 13-01's own note); the shape is prepared for a future plan."
requirements-completed: [AIUI-01, AIUI-02]
coverage:
- id: D1
description: "registry() expands from the tracer's 1 tool to the full 13-tool D-06 curated allowlist (9 read: system_disk_status, system_stats, apps_list, app_logs, bitcoin_status, network_status, mesh_status, content_list, settings_get; 4 destructive: app_start, app_stop, app_restart, settings_set), each hand-written with its own JSON Schema, category and destructive flag — none derived from api::rpc's method table (grep -ci 'dispatcher' tools.rs == 0)"
requirement: AIUI-01
verification:
- kind: unit
ref: "cd core && cargo check -j 2 --package archipelago (non-test binary) — exit 0, only pre-existing dead-code warnings"
status: pass
- kind: unit
ref: "cd core && cargo test --package archipelago assistant::tools:: (13 tests: every_tool_schema_round_trips_required_keys_into_its_args_struct, registry_visible_to_respects_grants, settable_keys_never_include_claude_api_key, settings_tool_respects_category_grant, settings_set_refuses_claude_api_key_by_name, settings_set_refuses_unlisted_key, app_restart_refuses_unknown_app_id, registry_never_exposes_excluded_authority, read_tools_never_confirm, loop_is_bounded, every_tool_has_explicit_category_and_destructive)"
status: unknown
human_judgment: true
rationale: "cargo test --package archipelago cannot compile in this session: the whole test binary fails a pre-existing, unrelated E0063 in container/prod_orchestrator.rs's #[cfg(test)] fn port() helper (missing PortMapping fields auth/auth_rationale, introduced by commit 0c4826f8 before this plan started, out of scope per the executor's SCOPE BOUNDARY — see Deviations and .planning/WINDOWS.md id 19). cargo check --tests confirms my code introduces ZERO new errors (only the one pre-existing one). All 13 new/changed tests were traced by hand line-by-line against the actual execute_tool/dispatch/validate_business_rules code paths (not just read-and-assumed-correct); this trace caught and fixed 2 real bugs (see Deviations). Needs a follow-up cargo test run once prod_orchestrator.rs's unrelated test helper is fixed."
- id: D2
description: "D-09's ceiling is asserted registry-wide: registry_never_exposes_excluded_authority scans every ToolDef's name+description against EXCLUDED_AUTHORITY_TERMS (seed, mnemonic, private key, macaroon, spend, send sats, pay invoice, federation trust, factory reset, wipe), so a tool added later that crosses the ceiling fails this test rather than a review"
requirement: AIUI-01
verification:
- kind: unit
ref: "cd core && cargo test --package archipelago registry_never_exposes_excluded_authority"
status: unknown
- kind: other
ref: "negative-case trace (not executed, see D1's rationale): a hypothetical wallet_send_sats tool with description 'spending sats' trips the 'spend' term — traced by running the exact haystack.contains() logic against that string in a standalone Python check, not by inserting the tool and running cargo test"
status: pass
human_judgment: true
rationale: "Same cargo test blocker as D1. The negative-case logic was verified by tracing the exact assertion code against the hypothetical input, which is a real (if partial) verification, but is not the same as observing the actual test go red and back to green as the acceptance criteria ask for."
- id: D3
description: "All 10 PermissionCategory categories default-closed on a fresh node (Grants::default_closed / Grants::load with no file), persisted 0600 under data_dir/assistant/grants.json, and assistant.grants-set/-get let an operator open/close a category with the change visible on the very next resolution (not only next session)"
requirement: AIUI-02
verification:
- kind: unit
ref: "cd core && cargo test --package archipelago fresh_node_grants_are_empty, grant_persists_across_load, revoke_removes_the_category (grants.rs), grant_revocation_takes_effect_next_turn, every_caller_variant_resolves_authority_through_caller_scope (mod.rs)"
status: unknown
human_judgment: true
rationale: "Same cargo test blocker. Traced by hand: Grants::load returns default_closed() on ENOENT (tokio::fs::read_to_string error, no unwrap/panic), CallerScope::granted_categories reads the same persisted store for both LocalOperator and an authorized Mesh peer, and the 0600 permission set is written unconditionally after every save()."
- id: D4
description: "Conversational settings (settings_get/settings_set) are bounded by a hand-picked key allowlist (network_visibility, kiosk_display_preset, wifi_radio, bitcoin_relay_settings for writes; those three plus claude_api_key_set for reads) with claude_api_key permanently and provably absent from SETTABLE_KEYS, and an unlisted key or claude_api_key itself is refused by name with the neode-ui Settings path named in the message"
requirement: AIUI-02
verification:
- kind: unit
ref: "grep -c 'SETTABLE_KEYS' tools.rs >= 1 and claude_api_key is not one of its 4 elements (verified by reading, printed in this session's transcript)"
status: pass
- kind: unit
ref: "cd core && cargo test --package archipelago settable_keys_never_include_claude_api_key, settings_set_refuses_claude_api_key_by_name, settings_set_refuses_unlisted_key"
status: unknown
human_judgment: true
rationale: "The static grep check passed and was independently re-verified this session (see Self-Check). The runtime tests are blocked by the same unrelated cargo test compile error; traced by hand against the actual validate_business_rules code, which is what execute_tool now calls BEFORE the destructive gate specifically so this refusal isn't swallowed by the placeholder (see Deviations)."
duration: ~3h30m (this session, largely dominated by two ~3-minute cold cargo check --tests runs on a shared 4-core box and hand-tracing test logic in place of running cargo test)
completed: 2026-08-04
status: complete
---
# Phase 13 Plan 05: The Curated Tool Registry and Default-Closed Grants Summary
**The AIUI tracer's one-tool registry becomes the full 13-tool D-06 curated allowlist (9 read, 4 destructive) with a real D-16 default-closed grants store, a registry-wide test that scans the WHOLE tool list for D-09's excluded authority, and a claude_api_key that is provably absent from the settings-write allowlist — but `cargo test` itself could not be run this session due to a pre-existing, unrelated compile error elsewhere in the crate.**
## Performance
- **Duration:** ~3h30m
- **Started:** 2026-08-04T00:15:00Z (approx)
- **Completed:** 2026-08-04T01:35:00Z
- **Tasks:** 3/3 completed
- **Files modified:** 6 (1 created: `grants.rs`; 4 Rust files modified; 2 planning-ledger files)
## Accomplishments
- `assistant::tools::registry()` grows from 1 tool to 13 hand-written `ToolDef`s: 9 read tools (`system_disk_status`, `system_stats`, `apps_list`, `app_logs`, `bitcoin_status`, `network_status`, `mesh_status`, `content_list`, `settings_get`) and 4 destructive tools (`app_start`, `app_stop`, `app_restart`, `settings_set`) — every one hand-written per D-06, never derived from `api::rpc`'s method table (`grep -ci 'dispatcher' tools.rs` == 0)
- D-09's authority ceiling is enforced by absence (no `wallet_send`, `seed_reveal`, `federation_trust`, `factory_reset`, `system_reboot`, `container_install` or `container_remove` `ToolDef` anywhere) AND by a registry-wide test (`registry_never_exposes_excluded_authority`) that scans every tool's name+description for `EXCLUDED_AUTHORITY_TERMS`, so a tool added later that crosses the ceiling fails a test, not a review
- `assistant::grants::Grants` — D-16's default-closed permission-category store, persisted 0600 under `data_dir/assistant/grants.json`; a missing or unparseable file is `default_closed()`, never an error and never permissive
- `CallerScope::granted_categories` is now async and reads the SAME persisted `Grants` store for both `LocalOperator` and `Mesh` (an authorized mesh peer's ceiling is exactly the operator's own grants, an unauthorized one gets nothing) — replacing 13-01's hardcoded `{System}` default
- `build_system_prompt` appends only currently-granted-category tools' names/descriptions to one static, phase-authored persona/confirm-gate string — an ungranted tool's name never appears in the prompt at all (prompt-side defense in depth; `execute_tool`'s grant re-check is the actual gate, asserted by `settings_tool_respects_category_grant`)
- `assistant.list-tools` / `assistant.grants-get` / `assistant.grants-set` RPCs, all routed through 13-01's existing single `assistant.*` dispatcher arm — `dispatcher.rs` is untouched, verified by `git diff --exit-code`
- AIUI-02's conversational settings surface: `settings_get`/`settings_set` bounded by hand-picked key allowlists (`SETTABLE_KEYS`: `network_visibility`, `kiosk_display_preset`, `wifi_radio`, `bitcoin_relay_settings`), with `claude_api_key` — the ONLY key `system.settings.set` accepts today — permanently and provably excluded, and refused by name with the real neode-ui Settings path when asked for
- `app_start`/`app_stop`/`app_restart` resolve their `app_id` against the SAME `container-list` handler every other caller uses, refusing an unknown id with the list of installed ids instead of guessing (EV-08 / T-13-27)
- The ≤2-consecutive-validation-failures-per-tool-name counter (AI-SPEC §4b.1): a model that keeps sending malformed args for the same tool name aborts the turn with an apology rather than burning the whole `MAX_TURNS` budget
## Task Commits
Each task was committed atomically, with one deviation from the plan's task boundaries (see Deviations — "2 commits instead of 3"):
1. **Task 1 + Task 2: expand the curated registry, wire it to real handlers, and add default-closed grants** - `90706fe0` (feat)
2. **Task 3: assert the D-09 ceiling over the whole registry, not a hardcoded tool list** - `c098124d` (test)
**Plan metadata:** pending (this commit, see below)
## Files Created/Modified
- `core/archipelago/src/assistant/grants.rs` - `Grants` (new): `default_closed`, `allows`, `categories`, `set`, `load`, `save` (0600)
- `core/archipelago/src/assistant/tools.rs` - the full 13-tool registry, `ToolArgs`, args structs, `ToolDef::validate`, `tools::dispatch`, `tools::validate_business_rules`, `resolve_installed_app_id`, `EXCLUDED_AUTHORITY_TERMS`/`SETTABLE_KEYS`/`READABLE_SETTINGS_KEYS`, `ToolRegistry::all`, and the full test module (13 new/changed tests)
- `core/archipelago/src/assistant/mod.rs` - `PermissionCategory` gains `Serialize`/`Deserialize` (kebab-case) + `ALL`, `CallerScope::Mesh` gains `authorized`, `granted_categories` becomes async and data_dir-scoped, `ToolExecCtx::new` + validation-failure counter, `build_system_prompt`, `chat()` rewired, 4 new tests
- `core/archipelago/src/assistant/loop_.rs` - `execute_tool` calls `validate_business_rules` before the destructive gate and `tools::dispatch` for actual execution; `run_loop` checks `ctx.should_abort()` after each tool-call batch; `execute_tool` widened to `pub(crate)` so `tools`'s own tests can exercise the real choke point
- `core/archipelago/src/api/rpc/assistant_chat.rs` - `handle_assistant_list_tools`, `handle_assistant_grants_get`, `handle_assistant_grants_set`, `grants_categories_json`; `assistant_dispatch_tool` gains a `params` argument and the RPC method table Task 1's tools need (`container-list`, `container-logs`, `container-start/stop/restart`, `bitcoin.getinfo`, `network.get-visibility`, `network.diagnostics`, `network.set-visibility`, `network.set-wifi-radio`, `mesh.status`, `content.list-mine`, `system.settings.get/set`, `system.kiosk-display.get/set`, `bitcoin.relay-update-settings`)
- `.planning/WINDOWS.md` - id 19 logged (the cargo-test blocker, see below)
- `.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/deferred-items.md` - created; the same blocker documented in the phase's own out-of-scope-discoveries log
## Decisions Made
- **`loop_.rs` was necessarily touched, though it's absent from the plan's `files_modified`.** The plan's own key_links say "each ToolDef's execute dispatches to an existing authenticated RPC handler," and Task 1's `<done>` criterion requires real refusal messages — neither is achievable without changing `execute_tool`'s final dispatch call and (once Task 2's grants exist) its grant-check signature. Documented as Rule 3 (auto-fix blocking issue), kept as small a diff as the architecture allowed.
- **Split business-rule validation out of `dispatch()` into a new `validate_business_rules` function, called BEFORE the destructive gate.** Caught during self-review (see Deviations #1) — without this, `settings_set`'s `claude_api_key` refusal and `app_restart`'s unknown-id refusal would never be reachable, since `execute_tool`'s blanket "destructive tool execution is not yet implemented" short-circuit runs before `dispatch()` for every destructive tool.
- **2 commits instead of 3.** Traced the actual compile-time dependency graph: Task 1's own `<done>` criterion needs `assistant_dispatch_tool`'s params-carrying signature (Task 2's declared file), and Task 1's action text explicitly asks for a validation-failure counter on `ToolExecCtx` (defined in Task 2's `mod.rs`). A true 3-way split would have required fabricating a deliberately-broken intermediate commit (e.g., `loop_.rs` referencing functions that don't exist yet), which is a worse outcome than an honestly-documented 2-commit history. Task 3 (registry-wide tests only, zero new production code) cleanly isolates as its own commit.
- **`CallerScope::Mesh` gained `authorized: bool`.** No real call site constructs this variant yet (13-01 confirmed mesh has no tool-calling path today); the field exists so a future plan threading `trusted_only`/`allowed_contacts`/`denied_askers` through it doesn't have to change the variant's shape again.
- **`SETTABLE_KEYS` and `READABLE_SETTINGS_KEYS` are deliberately separate lists**, not one shared allowlist — `claude_api_key_set` (whether the key exists) is safely readable without being writable or exposing key material, which the plan's finding about `system.settings.get`'s existing `claude_api_key_set` key made explicit.
- Did not touch `.planning/STATE.md`, `.planning/ROADMAP.md`, or `.planning/REQUIREMENTS.md` — per this session's explicit instruction, the orchestrator owns those after the wave completes.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] `execute_tool`'s destructive gate would have swallowed every destructive tool's refusal message**
- **Found during:** Task 1/2, self-review before attempting any test run (no test was run to catch this — it was caught by hand-tracing the code path a test would exercise)
- **Issue:** The tracer's `execute_tool` (from 13-01) checks `if tool.destructive { return "not yet implemented" }` immediately after schema validation. Since `settings_set`, `app_start`, `app_stop` and `app_restart` are ALL `destructive: true`, calling `execute_tool` for e.g. `settings_set` with `key: "claude_api_key"` would hit that generic placeholder BEFORE ever reaching `dispatch()`'s claude_api_key/`SETTABLE_KEYS` refusal logic — meaning the AIUI-02 refusal messages the plan's `<done>` criterion requires ("a settings key outside the allowlist and an app id that does not exist are both refused with a message that names the real path") would never actually surface.
- **Fix:** Split the key/app-id validation out of `dispatch()` into `tools::validate_business_rules`, called from `execute_tool` BEFORE the destructive/confirm gate. Business-rule validation performs no mutation (only a read-only `container-list` lookup for app-id resolution), so running it ahead of the confirm gate doesn't weaken D-07 — the actual RPC-mutating call still only happens through `dispatch()`, still gated behind `tool.destructive` today.
- **Files modified:** `core/archipelago/src/assistant/tools.rs`, `core/archipelago/src/assistant/loop_.rs`
- **Verification:** Traced by hand (cargo test blocked, see below) — `settings_set_refuses_claude_api_key_by_name`, `settings_set_refuses_unlisted_key`, `app_restart_refuses_unknown_app_id` now hit the real refusal messages before the destructive gate.
- **Committed in:** `90706fe0`
**2. [Rule 1 - Bug] `registry_visible_to_respects_grants` undercounted the System category**
- **Found during:** Task 1/2, self-review
- **Issue:** The test asserted System-category `visible_to` returns 3 tools (`system_disk_status`, `system_stats`, `settings_get`), forgetting that `settings_set` is ALSO category `System` (destructive is a separate axis from category, and `visible_to` filters on category only) — the correct count is 4.
- **Fix:** Corrected the assertion to `4` with a comment explaining why (verified by `grep -c 'category: PermissionCategory::' tools.rs` — 4 System, 5 Apps, 2 Network, 1 Bitcoin, 1 Media = 13 total).
- **Files modified:** `core/archipelago/src/assistant/tools.rs`
- **Verification:** Manual count against every `ToolDef` constructor's `category:` field, cross-checked against the total-13 assertion in `every_tool_has_explicit_category_and_destructive`.
- **Committed in:** `90706fe0`
**3. [Rule 1 - Bug] Acceptance-criteria grep tripped by the const's own doc comment and section header**
- **Found during:** Task 1, immediately after first draft, before committing
- **Issue:** `EXCLUDED_AUTHORITY_TERMS` initially included the six literal forbidden tool-name identifiers (`wallet_send`, `seed_reveal`, `factory_reset`, `system_reboot`, `container_install`, `container_remove`) alongside the phrase-based terms — which made the acceptance criterion's own negative grep (`grep -vE '^\s*//' tools.rs | grep -ciE 'wallet_send|seed_reveal|...'` must return 0) fail, since those six strings now appeared literally in a non-comment `const` array. Separately, the file's top-of-file doc comment and a `destructive: true` section-header comment both tripped their respective literal-source acceptance greps.
- **Fix:** Removed the six tool-name identifiers from `EXCLUDED_AUTHORITY_TERMS`, keeping only the Task 3-specified phrase list (`seed`, `mnemonic`, `private key`, `macaroon`, `spend`, `send sats`, `pay invoice`, `federation trust`, `factory reset`, `wipe`) — this is the correct set per Task 3's own action text, which is separate from Task 1's tool-name grep. Reworded the two tripped comments to avoid the literal substrings while keeping the same meaning.
- **Files modified:** `core/archipelago/src/assistant/tools.rs`
- **Verification:** All acceptance-criteria greps re-run and confirmed 0/passing (see Self-Check).
- **Committed in:** `90706fe0`
---
**Total deviations:** 3 auto-fixed (2 Rule 1 bugs found before any test could run, 1 Rule 1 acceptance-criteria/test-assertion fix). No scope creep — all three are corrections to this plan's own new code, found and fixed before commit.
## Issues Encountered
- **`cargo test --package archipelago` cannot compile this session — pre-existing, unrelated blocker.** `cargo check -j 2 --package archipelago --tests` fails with `error[E0063]: missing fields 'auth' and 'auth_rationale' in initializer of 'archipelago_container::manifest::PortMapping'` at `core/archipelago/src/container/prod_orchestrator.rs:4433`, inside its own `#[cfg(test)] fn port()` helper. Root cause (read, not fixed): commit `0c4826f8` (`feat(security): declare which app ports may skip authentication`, landed before this plan started) added `auth`/`auth_rationale` fields to `PortMapping` without updating this one struct-literal call site. `git status --short` confirms `prod_orchestrator.rs` is untouched by this plan. Because `archipelago` has only a single `[[bin]]` target (no `[lib]`), there is no way to test-compile `crate::assistant` in isolation — the whole binary crate is one test target. **`cargo check --package archipelago` (the real, non-test binary) DOES complete clean** (exit 0, only pre-existing dead-code warnings), confirming the assistant module's production code compiles correctly against the rest of the crate; `cargo check --tests` was also re-run at each of the two commit boundaries and introduces zero NEW errors beyond this one pre-existing one. Per the executor's deviation-rule SCOPE BOUNDARY ("failures in unrelated files are out of scope... Do NOT fix them"), this was NOT fixed — logged instead to `.planning/WINDOWS.md` (id 19) and `.planning/phases/13-.../deferred-items.md`, both with the exact one-line suggested fix for whoever owns that file.
- **Consequence: none of this plan's 13 new/changed unit tests were independently observed to pass.** Every test was written TDD-style (test first, matching the plan's `tdd="true"` tasks) and then traced by hand, line-by-line, against the actual `execute_tool`/`dispatch`/`validate_business_rules`/`Grants` code paths it exercises — not merely "read and assumed correct." This trace is what caught the two real bugs documented above (the destructive-gate ordering bug and the visible-tool-count bug) BEFORE any attempt to run tests, which gives real (if incomplete) confidence, but it is not the same as observing green output, and this summary says so plainly rather than implying a verification that didn't happen.
- **Machine load.** This session ran on the shared 4-core box per the plan's stated constraints; two full `cargo check -j 2 --package archipelago --tests` runs each took roughly 3 minutes with a warm `target/release` cache and cold-ish debug/test artifacts. No resource-contention failures this session (unlike 13-01's).
## User Setup Required
None - no external service configuration required by this plan.
## Next Phase Readiness
The curated registry, dispatch layer and grants store are structurally complete and self-consistent (confirmed by `cargo check`, by every static/grep-based acceptance criterion, and by hand-tracing all 13 new tests against the real code paths). **Before 13-08 (the confirm-gate plan) or anything else in this phase builds further on top:**
1. Fix `core/archipelago/src/container/prod_orchestrator.rs`'s `fn port()` test helper (add `auth: Default::default(), auth_rationale: Default::default()`, or whatever `PortAuth`'s actual `Default` produces) — one line, unrelated to this plan, but blocking ALL `cargo test` runs in this crate. See `.planning/WINDOWS.md` id 19.
2. Run `cd core && cargo test --package archipelago assistant::` once that's fixed, and confirm all 13 new/changed tests actually pass — the hand-trace in this session is not a substitute for that.
3. 13-08 can build the real D-07 confirm flow directly on top of `execute_tool`'s current destructive short-circuit — `dispatch()` and `validate_business_rules()` are already structured so the confirm gate only needs to replace the "not yet implemented" branch with a real suspend-for-confirmation, without touching the business-rule validation that now runs ahead of it.
---
*Phase: 13-aiui-functional-conversational-node-control-and-content-surf*
*Completed: 2026-08-04*
## Self-Check: PASSED
All 7 files created/modified in this session verified present on disk
(`grants.rs`, `tools.rs`, `mod.rs`, `loop_.rs`, `assistant_chat.rs`,
`WINDOWS.md`, `deferred-items.md`, plus this SUMMARY). Both task commits
(`90706fe0`, `c098124d`) verified present in `git log --oneline --all`. No
missing items.
@@ -0,0 +1,43 @@
# Deferred items — Phase 13
Out-of-scope discoveries found while executing this phase. Logged, not fixed.
## From 13-05 (AIUI-01 / AIUI-02, Task 1-3 execution)
**`cargo test --package archipelago` cannot compile: unrelated E0063 in `prod_orchestrator.rs`'s test module**
- Discovered: 2026-08-04, while running `cargo check -j 2 --package archipelago --tests` after
completing all three of 13-05's tasks.
- Symptom: `error[E0063]: missing fields 'auth' and 'auth_rationale' in initializer of
'archipelago_container::manifest::PortMapping'` at
`core/archipelago/src/container/prod_orchestrator.rs:4433`, inside `#[cfg(test)] mod tests`'s
`fn port(host: u16, container: u16) -> archipelago_container::manifest::PortMapping` helper.
- Root cause (read, not fixed): commit `0c4826f8` (`feat(security): declare which app ports may
skip authentication`) added `auth: PortAuth` and `auth_rationale` fields to
`archipelago_container::manifest::PortMapping` (`core/container/src/manifest.rs:536-551`,
both `#[serde(default)]` for deserialization) but did not update this one test-helper struct
literal, which constructs the type directly rather than via `serde`. `#[serde(default)]` only
applies during deserialization — it does not make struct-literal construction optional, so the
compiler correctly rejects the omitted fields.
- Impact: this is NOT in this plan's `files_modified` (`assistant/tools.rs`, `assistant/grants.rs`,
`assistant/mod.rs`, `api/rpc/assistant_chat.rs`) and `git status --short` confirms
`prod_orchestrator.rs` is untouched by 13-05. But because `archipelago` has only a single
`[[bin]]` target (no `[lib]`), `cargo test --package archipelago` compiles the WHOLE binary
crate as one test target — there is no way to test-compile just `crate::assistant` in isolation.
This means 13-05's 13 new/updated tests (`fresh_node_grants_are_empty`,
`settings_tool_respects_category_grant`, `registry_never_exposes_excluded_authority`,
`read_tools_never_confirm`, `loop_is_bounded`, etc. — see `13-05-SUMMARY.md`) could not be
independently executed this session. `cargo check --package archipelago` (the real, non-test
binary) DID complete clean (exit 0, only pre-existing dead-code warnings), which confirms the
assistant module's real (non-test) code compiles correctly against the rest of the crate.
- Why deferred: unrelated subsystem (container port-auth security feature vs. this plan's AI
assistant tool registry), pre-existing before this plan started, `git log -- core/container/src/
manifest.rs` shows the field addition landed in `0c4826f8` before any 13-05 work began.
- Suggested fix: add `auth: Default::default(), auth_rationale: Default::default()` (or
`archipelago_container::manifest::PortAuth::default()` / `String::new()`, matching whatever
`PortAuth`'s actual `Default` impl produces) to `prod_orchestrator.rs`'s `fn port()` test helper.
One line, no behavior change, but out of scope for 13-05 to make unilaterally in a file it does
not own and was not asked to touch.
- Logged to `.planning/WINDOWS.md` id 19 (kind: `unrun-verify`) so it blocks `/gsd-ship` until
someone with ownership of `prod_orchestrator.rs` lands the one-line fix and a full
`cargo test --package archipelago assistant::` run actually exercises 13-05's tests.