Task 1: registry() grows from the tracer's single system_disk_status tool to
the full 13-tool D-06 curated allowlist (9 read tools, 4 destructive write
tools), each hand-written with its own JSON Schema, PermissionCategory and
destructive flag -- nothing derived from api::rpc's method table. Adds
EXCLUDED_AUTHORITY_TERMS (D-09's excluded authority, scanned by Task 3's
registry-wide test), SETTABLE_KEYS/READABLE_SETTINGS_KEYS (AIUI-02's
hand-picked settings surface, claude_api_key permanently absent from
SETTABLE_KEYS), tools::dispatch (per-tool RPC dispatch) and
tools::validate_business_rules (allowlisted-key / installed-app-id
validation that runs before the destructive/confirm gate so a plainly-wrong
request is refused with the real reason instead of the generic
"not yet implemented" placeholder). assistant_dispatch_tool gains a params
argument and the RPC method table Task 1's tools need.
Task 2: grants.rs adds Grants (D-16 default-closed permission-category
store, persisted 0600 under data_dir/assistant/grants.json; a missing file
is default_closed(), never permissive). CallerScope::granted_categories
becomes async and reads the persisted store instead of a hardcoded default;
CallerScope::Mesh gains an `authorized` field so a mesh peer's ceiling is
never wider than the operator's own grants. ToolExecCtx gains the AI-SPEC
S-13 consecutive-validation-failure counter (>2 failures for the same tool
name aborts the turn with an apology, checked in run_loop). build_system_prompt
appends only currently-granted-category tools' names/descriptions -- an
ungranted tool never appears in the prompt string (defense in depth; the
execute_tool grant re-check is the actual gate). assistant_chat.rs adds
assistant.list-tools / assistant.grants-get / assistant.grants-set, all
routed through the existing single assistant.* dispatcher arm (dispatcher.rs
untouched, verified by git diff --exit-code).
dispatcher.rs is not touched -- all new RPC surface goes through 13-01's
assistant.* prefix arm.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
D-01/D-02/D-06 tracer slice: a new crate::assistant module (CallerScope,
PermissionCategory, ToolExecCtx, chat()) runs a multi-turn tool-calling loop
(run_loop/execute_tool, MAX_TURNS=8) against a curated single-tool registry
(system_disk_status, hand-written JSON Schema — no schemars) via a Claude
Messages API backend. execute_tool is the single choke point: unknown tools
are refused not ignored, D-16 category grants are re-checked even though the
system prompt already omits ungranted tools, and every real tool dispatches
through the SAME handle_system_disk_status RPC handler every other
authenticated caller uses (assistant_dispatch_tool bridge in
api/rpc/assistant_chat.rs) — never an AI-only backdoor.
assistant.chat is registered in dispatcher.rs as a single guarded
`m if m.starts_with("assistant.")` arm reached only after the existing
session-cookie + CSRF + role.can_access() gate in api/rpc/mod.rs — asserted
directly by assistant_methods_require_session against the live
UNAUTHENTICATED_METHODS list (visibility only widened to pub(crate) for that
assertion; the list's contents are untouched, per the Phase-10 hard
constraint).
Key read from data_dir/secrets/claude-api-key — the same path
mesh/rpc/mesh/assistant.rs already probes — never a second key location.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>