polyc-tools 2026.9.0

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 `promote` tool (POLY-223).
//!
//! The sibling of `invite` (`#698`/`#700`), `revoke` (`#713`/`#714`), and
//! `demote` (`#715`) that completes the admin-management set: an admin
//! asking in natural language — "make @someone an admin", "promote @sam",
//! "@Vitor should be an admin" — should be handled as an admin-role grant,
//! 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 already-linked requirement, and the durable audit trail.
//!
//! Like `invite`/`revoke`/`demote` 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 `demote` — but the promotion itself always
//! requires an explicit human approval naming the exact target, same as a
//! grant or a removal.
//!
//! This is granting a PERSON the ADMIN ROLE only — distinct from `invite`
//! (granting them access to Polychrome at all). Promote never admits anyone:
//! the target must already be `linked` (have completed the invite/link
//! ceremony) before their admin role can be granted — use `invite` first for
//! anyone who is not yet linked.
use polyc_llm::ToolSpec;
use serde_json::json;

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

/// Every promote 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`/`demote` — the shared extraction
/// helper on every edge relies on this.
pub const ARG_TARGET_USER_ID: &str = "target_user_id";

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

/// `promote` spec — an admin grants a person the 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 promote_spec() -> ToolSpec {
    ToolSpec::new(
        TOOL_NAME,
        "For an admin only: make a specific person an ADMIN — not inviting them to Polychrome \
         (that's invite) and not a wallet. Use it when an admin asks to promote, make someone an \
         admin, or grant someone the admin role — for example \"promote @sam\" or \"@Vitor \
         should be an admin\". 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 target must \
         already have used Polychrome and completed their invite — if they haven't, use invite \
         first; this never grants someone access to Polychrome as a side effect, only the admin \
         role once they already have access. This refuses to promote anyone who is already 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("Make someone an admin (admin)")
}