polyc-tools 2026.8.3

The in-process tool core for polychrome agents: local executors (coding, web fetch, wallet, ...), the tool registry, and MCP composition. The networked connectors live in polyc-connectors.
//! Spec for the agent-evaluable admin `demote` tool (`#715`).
//!
//! The sibling of `invite` (`#698`/`#700`) and `revoke` (`#713`/`#714`) that
//! completes the admin-management set: an admin asking in natural language —
//! "remove @someone's admin role", "demote @someone", "@someone shouldn't be
//! an admin anymore" — should be handled as an admin-role removal, not
//! mis-answered by the model. This tool lets the agent recognize that intent
//! and hand it to the control plane, which enforces the admin gate, the
//! last-admin guard, and the durable audit trail.
//!
//! Like `invite`/`revoke` it has no in-process implementation: the
//! conversation sandbox can't reach the persona store. The harness advertises
//! it via the same control-plane proxy; the control plane runs it
//! (admin-gated). The agent only ever sees a codeless confirmation — there is
//! no secret here, same as `revoke` — but the demotion itself always requires
//! an explicit human approval naming the exact target, same as a grant or a
//! removal.
//!
//! This is removing a PERSON's ADMIN ROLE only — distinct from `revoke`
//! (removing their access to Polychrome entirely) and from `wallet_unlink` (a
//! person's own spending wallet). Only the admin-role REMOVAL is
//! agent-evaluable; granting the admin role has no agent-evaluable tool
//! (`#715`'s explicit out-of-scope: promotion is the next sibling, not this
//! one).

use polyc_llm::ToolSpec;
use serde_json::json;

/// The `demote` tool name.
pub const TOOL_NAME: &str = "demote";

/// Every demote tool name, for allowlist checks and dispatch (one, today).
pub const ALL: &[&str] = &[TOOL_NAME];

/// The required argument: the target's provider-native user id, taken from
/// the mention markup already in the agent's input.
///
/// Same field name as `invite`/`revoke` — the shared extraction helper on
/// every edge relies on this.
pub const ARG_TARGET_USER_ID: &str = "target_user_id";

/// Every demote tool spec.
#[must_use]
pub fn all_specs() -> Vec<ToolSpec> {
    vec![demote_spec()]
}

/// `demote` spec — an admin removes a person's ADMIN ROLE only.
///
/// Admin-only: the control plane refuses a non-admin caller and changes
/// nothing. Not read-only (it mutates the target's `Permissions.admin`) and
/// not egress. No secret is ever produced — the tool result is a plain,
/// codeless confirmation.
#[must_use]
pub fn demote_spec() -> ToolSpec {
    ToolSpec::new(
        TOOL_NAME,
        "For an admin only: remove a specific person's ADMIN ROLE — not their access to \
         Polychrome (that's revoke) and not a wallet. Use it when an admin asks to demote, \
         un-admin, or take away someone's admin role — for example \"demote @sam\" or \"@Vitor \
         shouldn't be an admin anymore\". Pass the target's user id EXACTLY as it appears in the \
         mention markup in the message (the id inside `<@...>`), never a typed-out name. The \
         person keeps their ordinary access to Polychrome after this — use revoke separately if \
         their access should be removed too. This refuses to demote the last remaining admin (a \
         deployment can never be left with zero admins), and refuses to demote anyone who isn't \
         currently an admin. If the person asking isn't an admin, it returns a refusal rather \
         than changing anyone's role.",
        json!({
            "type": "object",
            "properties": {
                ARG_TARGET_USER_ID: {
                    "type": "string",
                    "description": "The target's provider-native user id, taken \
                        verbatim from the mention markup (`<@U...>`) in the \
                        message — not a display name or handle."
                }
            },
            "required": [ARG_TARGET_USER_ID],
            "additionalProperties": false
        }),
    )
    .titled("Remove someone's admin role (admin)")
}