SpiceboxToolkit
Group agentprimitives.authzed.com · Scope Cluster · Short names sbxtk
SpiceboxToolkit is the machine-readable description of one CLI a sandbox tool can be built from: its subcommands, flags, env, parser config and default per-tool authz policy. A SpiceboxToolspec narrows a toolkit down to the specific tool an agent is actually handed.
Cluster-scoped. Reconciled by pkg/controllers/spiceboxtoolkit, which detects collisions with the built-in toolkits and stamps a cli-kind pin baseline onto status.
Spec
| Field | Type | Description |
|---|---|---|
spec.docs | string | Docs is prose about the CLI, surfaced to authors. |
spec.env * | object | Env describes the environment variables the binary reads. It documents and wires them — notably which are sensitive — and is not a filter on the environment the binary actually receives. Mirrors pkg/tools/toolspec/toolkit.Toolkit.Env. |
spec.env.allowed * | []object | Allowed DESCRIBES the environment variables the binary reads. It is not an enforcement boundary: nothing strips a variable for being absent here. The environment a tool call runs with is composed by the ToolCall controller from the broker-resolved credentials, ToolCall.spec.env, SpiceboxSession.spec.defaultEnv, this toolkit's envDefaults and the sandbox container's own env. What an entry drives is keyed off Sensitive: redaction wherever the call is surfaced, which credential a setup flow must obtain, and which names a SpiceboxClass may not shadow through its own envDefaults. Adding a variable here documents and wires it; omitting one does not keep it out of the binary's environment. |
spec.env.allowed[].credential | string | Credential is the name of the credential (in the agent's identity catalog) that fills this env var. When empty, resolution falls back to a name derived from the env var (lowercased, "_"→"-"). Only meaningful when Sensitive is true. |
spec.env.allowed[].description | string | Description is the user-facing explanation of what this variable carries. |
spec.env.allowed[].name * | string | Name is the environment variable name. |
spec.env.allowed[].prompt | string | Prompt is free-text the LLM-fallback setup agent uses as system context when no provider matches. Optional. |
spec.env.allowed[].provider | string | Provider names a provider in the /providers/ library that satisfies this env's auth needs. Used by oap agent setup-identity to dispatch the right setup flow. Optional. |
spec.env.allowed[].sensitive | boolean | Sensitive marks the value as credential material: it is resolved from an identity, masked in output, and never logged. |
spec.env.allowed[].title | string | Title is the short user-facing display name for the credential this env var carries ("GitHub"). Shown as the bold header of a credential-request row. Takes precedence over the provider catalog; when empty the catalog (via Provider) or a humanized credential name is used. Only meaningful when Sensitive is true. |
spec.envDefaults | map[string]string | EnvDefaults is static, non-secret environment this toolkit sets on every invocation of its binary, so a CLI's own hardening travels with the description of that CLI. It is a floor: the runner stamps it onto each ToolCall's spec.env, and the toolcall controller demotes any key still carrying this declared value below SpiceboxSession.spec.defaultEnv and SpiceboxClass.spec.envDefaults, so an operator setting the same key keeps winning. Values are never secret. A key that Env.Allowed marks sensitive is rejected: those come from an AgentIdentity credential, and a static default for the same key would either fail the call (ToolCallEnvShadowsAgent) or replace the credential with a fixed string. Mirrors pkg/tools/toolspec/toolkit.Toolkit.EnvDefaults. |
spec.globalFlags | []object | GlobalFlags are flags accepted before any subcommand. |
spec.globalFlags[].description | string | Description is the user-facing explanation of the flag. |
spec.globalFlags[].long * | string | Long is the flag's long form, without leading dashes. |
spec.globalFlags[].optionalValue | boolean | OptionalValue declares that the binary accepts this flag's value only in the attached --flag=value form (git's PARSE_OPT_OPTARG, pflag's NoOptDefVal), so the parser must not consume the following token as the value — the binary won't. Mirrors pkg/tools/toolspec/toolkit.Flag.OptionalValue; invalid on type "bool". |
spec.globalFlags[].sensitive | boolean | Sensitive keeps the flag's value out of logs and approval prompts. |
spec.globalFlags[].short | string | Short is the single-letter form, without its dash; empty if none. |
spec.globalFlags[].splitOn | string | SplitOn, when non-empty, splits one supplied value into repeated flag occurrences on this separator. |
spec.globalFlags[].type * | string | Type is the value type; "bool" means the flag takes no value. |
spec.globalFlags[].values | []string | Values, when non-empty, restricts the flag to this enumeration. |
spec.name * | string | Name is the toolkit identifier a SpiceboxToolspec references. |
spec.parser * | object | Parser selects how an invocation's argv is validated and bound. |
spec.parser.kind * | string | Kind selects the argv parser: declarative drives off this spec, builtin names a compiled-in parser. (enum: declarative | builtin) |
spec.parser.name | string | Name identifies the builtin parser; unused when Kind is declarative. |
spec.permission | object | Permission is the toolkit-wide default per-tool authz policy. Subcommands may override individually via their own Permission block. Mirrors pkg/tools/toolspec/toolkit.Toolkit.Permission. |
spec.permission.check | object | PermissionCheck names the SpiceDB resource and permission to evaluate. Required when StateImpact is Readonly / Readwrite / External; forbidden when Stateless / Passthrough. |
spec.permission.check.enforceMode | string | EnforceMode controls deny-finality under permissive toolAuthMode. Empty → EnforceInherit (slice-1 default). |
spec.permission.check.extractionPrompt | string | ExtractionPrompt, when set, is an English fragment the runner includes in the extraction LLM's system prompt for this resource type. Tool spec authors write this once per toolspec/MCPServer file. The runner aggregates ExtractionPrompts across all tools whose Check targets the same resourceType (dedup'd, concatenated). The AgentClass's BoundEntityType.ExtractionPrompt overrides entirely if set. |
spec.permission.check.grantBindsArgs | []string | GrantBindsArgs, when set, restricts the slice-2 grant's arguments_hash caveat binding to only the listed top-level arg keys. Default (nil/empty) hashes the full arg map, meaning the grant satisfies only the exact same call. Setting it to e.g. ["repo"] lets a single approval cover every subsequent call against the same repo regardless of other args (pr number, etc.) — useful for readonly tools where the resource is the load-bearing input. The hash is an HMAC-SHA256 under the per-session args-hash key minted by the AgentSession reconciler into the per-session Secret and read by the runner at startup. Caveat contexts therefore cannot be predicted or minted outside the controller+runner trust domain — neither the agent nor a confused deputy with SpiceDB write access can forge a valid binding for arguments that were never requested. Note: hashes of previously-requested (including denied) calls remain observable in session status and channel payloads, so a SpiceDB-write attacker could still replay those; the keying narrows that surface to exactly the calls a human has already seen. See pkg/authz/guardian/grants.ArgsHashFiltered for the filtering contract. NOTE: the hash no longer BINDS the grant. A slot grant is keyed on (instance, permission), which is what makes it safe to reuse across calls: a grant shaped for one permission cannot be spent on another against the same id. The filtered hash is still computed and published on the approval request (ToolApprovalDetails.ArgsHash) so an approver — and the audit log — can see exactly which call was asked about. |
spec.permission.check.permission * | string | Permission is the SpiceDB permission (or relation) name to Check (e.g. "read", "write", "admin"). Static. Pattern-validated at admission for the same reason as ResourceType. Mirrors BoundEntityType.Permission. |
spec.permission.check.resourceIDExpr | string | ResourceIDExpr is a CEL string expression evaluated against args that yields the SpiceDB resource id. Exactly one of ResourceIDTemplate or ResourceIDExpr must be set: the template form for simple {arg} interpolation, the Expr form for nested-arg extraction. |
spec.permission.check.resourceIDHint | string | ResourceIDHint is shown to the AGENT when the resource id cannot be resolved from the call's arguments. That failure is usually the caller's to fix — it named no resource, or named it in a form the check cannot read — and the agent is the only party who can retry. Without a hint the message is the raw resolution error prefixed "internal:", which reads as a system fault and tells it not to bother. Written by whoever authors the check, because only they know what the call should have looked like. Example, for a git push whose remote must be a URL so the repository can be authorized: "name the remote as a full https:// URL, not a shorthand like origin". |
spec.permission.check.resourceIDTemplate | string | ResourceIDTemplate is a string with {arg} placeholders that interpolate from the tool call's args (e.g. "{owner}/{repo}"). Resolved at runtime by ResolveTemplate; validated at AgentClass-reconcile time so every {arg} matches a parameter the tool declares. |
spec.permission.check.resourceIDTransforms | []string | ResourceIDTransforms names registered transforms applied IN ORDER to the resolved template string before SpiceDB sees it: lowercase, remove_spaces, spicedb_object_id, basename, sha256. See transforms.go. |
spec.permission.check.resourceType * | string | ResourceType is the SpiceDB definition the Check runs against (e.g. "github_repo", "channel"). Static — no template interpolation. Pattern-validated at admission because this value is a component of a permsurface.Handle, which appears in approved plan ceilings and audit records. Mirrors BoundEntityType.ResourceType. |
spec.permission.stateImpact * | string | StateImpact describes the policy mode for a tool / subcommand. |
spec.permission.toolName | string | ToolName lets Layer 2's CheckScope evaluate the tool allow/deny axis. Wire from the registered tool name. Empty string skips the Layer 2 tool check (existing behavior). |
spec.siteURL | string | SiteURL is the toolkit-backed service's user-facing homepage. identityd discovers a favicon from this URL for the credential rows backed by this toolkit's sensitive env vars (e.g., a github-token row resolves through the github toolkit). Optional; unset renders the deterministic generated fallback icon. Must be http or https; admission rejects other schemes. |
spec.spicedbSchema | object | SpiceDBSchema declares the SpiceDB resource types this toolkit's permission checks name, so the toolkit ships the definitions its own checks depend on. Mirrors pkg/toolspec/toolkit.Toolkit.SpiceDBSchema; the guardian composes it alongside MCPServer and SpiceDBBootstrap fragments. |
spec.spicedbSchema.rawZed | string | RawZed is appended verbatim to the composed schema after Resources are emitted. Use this for SpiceDB schema features the structured form can't represent: subject-relations (e.g. slack_user#user) and unions (e.g. user | agent). The composer does not parse or validate RawZed beyond schema-load time; malformed text fails the SpiceDB WriteSchema call. |
spec.spicedbSchema.resources | []object | Resources lists structured SpiceDB definitions contributed by this fragment. Each resource is emitted with its declared relations and permissions; the composer dedupes by name across fragments. |
spec.spicedbSchema.resources[].approverPermission | string | ApproverPermission names the permission an approver must hold on an instance for their approval to count. Required when Standing is required, and must be empty when it is session-only — a type nothing governs has no permission to name, and accepting one there would read as governance that is never consulted. Declared rather than assumed. This was hardcoded to "owner" at four call sites, which silently made two very different types look identical to the approval router: crm_company's owner is a real computed permission backed by seeded tuples, while git_repo's owner is a bare relation declared only so the checks are answerable and never populated by anything. The router could not tell governance from an artifact of schema shape, so a push to a git remote resolved to an empty owner-set and became unapprovable forever. Naming it also lifts the assumption that the approving permission is called "owner": a type may route approval through maintainer, admin, or any permission its own schema defines. |
spec.spicedbSchema.resources[].display | object | Display declares how an INSTANCE of this resource type presents on an approval card — an icon, and how to turn its raw value (a URL, typically) into a short label. Absent means the card falls back to the wire type name, exactly as before this field existed. |
spec.spicedbSchema.resources[].display.icon | string | Icon names a glyph from a CLOSED, code-defined registry — never a URL and never free-form. A vendor-specific type (github_repo) may name the vendor's own mark; a host-agnostic type (git_repo) must name a generic one, because claiming a vendor mark would lie the moment an instance points somewhere else (a self-hosted remote). An unrecognized name renders no icon — never a fallback image, never a guess. |
spec.spicedbSchema.resources[].display.label | string | Label names a deriver from a CLOSED, code-defined set that turns an instance's raw value into a short display label — "url_path", "b64url_path", "last_segment", or "none" (the set registered in pkg/authz/plangate's labelDerivers, which is the authority; this list is prose and has drifted from it once). Not a template and not a regex: nothing here can produce a label the code did not author, and the deriver never sees anything an agent did not itself write into the plan (the instance value), so a declaration can shorten a value's presentation but can never fabricate one. An unrecognized name derives nothing, and the card falls back to Name. |
spec.spicedbSchema.resources[].display.name | string | Name is the human name for this resource TYPE — "Git repository", never the wire handle "git_repo". Shown when no per-instance label can be derived (Label is "none", unset, or the deriver produces nothing for this instance's value). |
spec.spicedbSchema.resources[].name * | string | Name is the SpiceDB definition name; the composer dedupes on it. |
spec.spicedbSchema.resources[].permissions | []object | Permissions are the resource's computed permissions. |
spec.spicedbSchema.resources[].permissions[].expr * | string | Expr is the right-hand side of permission <name> = …, pasted verbatim. |
spec.spicedbSchema.resources[].permissions[].name * | string | Name is the permission name. |
spec.spicedbSchema.resources[].permissions[].planningNote | string | PlanningNote is one line of guidance the agent reads while DECLARING a plan, rendered next to the handles it may declare. The knowledge that shapes a good plan is toolkit-specific, so it lives with the toolkit rather than in the runner's generic prompt. git_repo splits its permissions across two instances — read/write key on the checked-out copy, fetch/push on the remote URL — so a phase declaring write without read can change files it cannot open, and the first read interrupts the human with an amendment the plan should have carried from the start. It shapes what the agent DECLARES, never what anything grants: a note is prose the model reads at plan time, and no authorization decision reads it. A phase that ignores its guidance still gets the gate it earned. |
spec.spicedbSchema.resources[].permissions[].title | string | Title is the phrase a human reads on an approval card instead of the permission's handle. perm:push:git_repo is a WIRE FORMAT; asking somebody to decide on it makes the decision slower and worse exactly where care matters most. Declared here because this is where the permission itself is declared, so one declaration serves every channel and the CLI. Absent is fine and common: the card detokenizes the handle instead, which shows no plumbing and invents no English. |
spec.spicedbSchema.resources[].relations | []object | Relations are the resource's relations, emitted verbatim. |
spec.spicedbSchema.resources[].relations[].name * | string | Name is the relation name as it appears in the composed schema. |
spec.spicedbSchema.resources[].relations[].subjectType * | string | SubjectType must be a bare resource-type name (e.g. "user", "hubspot_owner") declared in this same SpiceDBSchema or the implicit "user" type. Wildcards ("user:*") are expressed via the separate Wildcard field. Subject-relation forms ("team#member") are NOT representable. Exactly one SubjectType per Relation — SpiceDB unions like relation viewer: user | team#member cannot be modeled. |
spec.spicedbSchema.resources[].relations[].wildcard | boolean | Wildcard, when true, makes the relation accept ANY subject of SubjectType — emitted as relation <name>: <subjectType>:* in the composed schema. Use sparingly: a wildcard relation effectively grants the underlying-resource permission to every subject of that type, so any per-call gating must come from a different layer (e.g. stateImpact: external on the tool, which routes every call through the approval flow regardless of the SpiceDB Check result). |
spec.spicedbSchema.resources[].standing * | string | Standing declares whether SpiceDB is AUTHORITATIVE for this resource type — that is, whether an approver can be expected to already hold a permission on an instance of it. REQUIRED, with no default. - required SpiceDB governs who may approve. The approver pool is ApproverPermission on the named instance, and an EMPTY pool is a final refusal — nobody can approve what nobody governs. - session-only No local permission governs approval for this type, so the session's own approvers decide and their decision IS the authority. Everything downstream is unchanged: the grant is still written, still expiring, still session-scoped and revocable, and the tool-call Check still runs. There is deliberately NO default. A default is a hole you open by omission: defaulting to session-only silently widens who may approve a type somebody forgot to classify, and defaulting to required makes a forge-governed type permanently unbindable because nothing writes a SpiceDB tuple for every git remote. Neither failure announces itself, so the author states the answer. Same reasoning as AP_CLUSTER_KIND, which also refuses to default. Choosing is a question about the RESOURCE, not about convenience: does a permission on this instance already say who may speak for it? A CRM company with seeded owner tuples: yes, required. A git remote whose permissions live at the forge: no, session-only. A cluster or namespace admin can force any type to required with SettingsLimits.RequireStandingFor, which no fragment may widen past. (enum: session-only | required) |
spec.streamFormat | string | StreamFormat names a registered parser in pkg/tools/toolkitstream/registry. When non-empty AND a subcommand has Mode stream or interactive, the sandbox tool routes the bridge's stdout through the named parser before publishing KindToolSessionEvent envelopes. Empty (default) preserves the raw KindToolSessionDelta path. Mirrors pkg/tools/toolspec/toolkit.Toolkit.StreamFormat. Example: "claude-stream-json". |
spec.subcommands * | []object | Subcommands are the invocations this toolkit describes; anything absent here is not expressible as a tool. |
spec.subcommands[].description | string | Description is the user-facing explanation of what the subcommand does. |
spec.subcommands[].effects * | object | Effects is the declared effect profile driving approval and authz gating. |
spec.subcommands[].effects.creds * | object | Creds is which credentials the invocation needs or rewrites. |
spec.subcommands[].effects.creds.required * | []string | Required names credentials the invocation needs to succeed. |
spec.subcommands[].effects.creds.writes * | []string | Writes names credentials the invocation may overwrite. |
spec.subcommands[].effects.destructive * | boolean | Destructive means the invocation can remove or overwrite state. |
spec.subcommands[].effects.filesystem * | object | Filesystem is what the invocation may touch on disk. |
spec.subcommands[].effects.filesystem.paths * | []string | Paths names the filesystem locations the invocation touches. |
spec.subcommands[].effects.network * | object | Network is where the invocation may reach. |
spec.subcommands[].effects.network.destinations * | []string | Destinations names the hosts the invocation contacts; empty means none. |
spec.subcommands[].effects.reads * | []string | Reads names the resource kinds the invocation reads. |
spec.subcommands[].effects.writes * | []string | Writes names the resource kinds the invocation mutates. |
spec.subcommands[].flags | []object | Flags are the flags this subcommand accepts beyond the global ones. |
spec.subcommands[].flags[].description | string | Description is the user-facing explanation of the flag. |
spec.subcommands[].flags[].long * | string | Long is the flag's long form, without leading dashes. |
spec.subcommands[].flags[].optionalValue | boolean | OptionalValue declares that the binary accepts this flag's value only in the attached --flag=value form (git's PARSE_OPT_OPTARG, pflag's NoOptDefVal), so the parser must not consume the following token as the value — the binary won't. Mirrors pkg/tools/toolspec/toolkit.Flag.OptionalValue; invalid on type "bool". |
spec.subcommands[].flags[].sensitive | boolean | Sensitive keeps the flag's value out of logs and approval prompts. |
spec.subcommands[].flags[].short | string | Short is the single-letter form, without its dash; empty if none. |
spec.subcommands[].flags[].splitOn | string | SplitOn, when non-empty, splits one supplied value into repeated flag occurrences on this separator. |
spec.subcommands[].flags[].type * | string | Type is the value type; "bool" means the flag takes no value. |
spec.subcommands[].flags[].values | []string | Values, when non-empty, restricts the flag to this enumeration. |
spec.subcommands[].mode | string | Mode declares how the runner dispatches this subcommand. Mirrors pkg/tools/toolspec/toolkit.Subcommand.Mode. Values: "" (sync), "stream" (streaming output, no stdin bridge), "interactive" (streaming output + live stdin pipe). |
spec.subcommands[].path * | []string | Path is the subcommand words after the binary, e.g. ["remote","add"]. |
spec.subcommands[].permission | object | Permission overrides the toolkit-level default permission for this subcommand. Required by the AgentClass validator's "enforcing" mode when Effects.Destructive or Effects.Writes flag the subcommand as state-mutating. Mirrors pkg/tools/toolspec/toolkit.Subcommand.Permission. |
spec.subcommands[].permission.check | object | PermissionCheck names the SpiceDB resource and permission to evaluate. Required when StateImpact is Readonly / Readwrite / External; forbidden when Stateless / Passthrough. |
spec.subcommands[].permission.check.enforceMode | string | EnforceMode controls deny-finality under permissive toolAuthMode. Empty → EnforceInherit (slice-1 default). |
spec.subcommands[].permission.check.extractionPrompt | string | ExtractionPrompt, when set, is an English fragment the runner includes in the extraction LLM's system prompt for this resource type. Tool spec authors write this once per toolspec/MCPServer file. The runner aggregates ExtractionPrompts across all tools whose Check targets the same resourceType (dedup'd, concatenated). The AgentClass's BoundEntityType.ExtractionPrompt overrides entirely if set. |
spec.subcommands[].permission.check.grantBindsArgs | []string | GrantBindsArgs, when set, restricts the slice-2 grant's arguments_hash caveat binding to only the listed top-level arg keys. Default (nil/empty) hashes the full arg map, meaning the grant satisfies only the exact same call. Setting it to e.g. ["repo"] lets a single approval cover every subsequent call against the same repo regardless of other args (pr number, etc.) — useful for readonly tools where the resource is the load-bearing input. The hash is an HMAC-SHA256 under the per-session args-hash key minted by the AgentSession reconciler into the per-session Secret and read by the runner at startup. Caveat contexts therefore cannot be predicted or minted outside the controller+runner trust domain — neither the agent nor a confused deputy with SpiceDB write access can forge a valid binding for arguments that were never requested. Note: hashes of previously-requested (including denied) calls remain observable in session status and channel payloads, so a SpiceDB-write attacker could still replay those; the keying narrows that surface to exactly the calls a human has already seen. See pkg/authz/guardian/grants.ArgsHashFiltered for the filtering contract. NOTE: the hash no longer BINDS the grant. A slot grant is keyed on (instance, permission), which is what makes it safe to reuse across calls: a grant shaped for one permission cannot be spent on another against the same id. The filtered hash is still computed and published on the approval request (ToolApprovalDetails.ArgsHash) so an approver — and the audit log — can see exactly which call was asked about. |
spec.subcommands[].permission.check.permission * | string | Permission is the SpiceDB permission (or relation) name to Check (e.g. "read", "write", "admin"). Static. Pattern-validated at admission for the same reason as ResourceType. Mirrors BoundEntityType.Permission. |
spec.subcommands[].permission.check.resourceIDExpr | string | ResourceIDExpr is a CEL string expression evaluated against args that yields the SpiceDB resource id. Exactly one of ResourceIDTemplate or ResourceIDExpr must be set: the template form for simple {arg} interpolation, the Expr form for nested-arg extraction. |
spec.subcommands[].permission.check.resourceIDHint | string | ResourceIDHint is shown to the AGENT when the resource id cannot be resolved from the call's arguments. That failure is usually the caller's to fix — it named no resource, or named it in a form the check cannot read — and the agent is the only party who can retry. Without a hint the message is the raw resolution error prefixed "internal:", which reads as a system fault and tells it not to bother. Written by whoever authors the check, because only they know what the call should have looked like. Example, for a git push whose remote must be a URL so the repository can be authorized: "name the remote as a full https:// URL, not a shorthand like origin". |
spec.subcommands[].permission.check.resourceIDTemplate | string | ResourceIDTemplate is a string with {arg} placeholders that interpolate from the tool call's args (e.g. "{owner}/{repo}"). Resolved at runtime by ResolveTemplate; validated at AgentClass-reconcile time so every {arg} matches a parameter the tool declares. |
spec.subcommands[].permission.check.resourceIDTransforms | []string | ResourceIDTransforms names registered transforms applied IN ORDER to the resolved template string before SpiceDB sees it: lowercase, remove_spaces, spicedb_object_id, basename, sha256. See transforms.go. |
spec.subcommands[].permission.check.resourceType * | string | ResourceType is the SpiceDB definition the Check runs against (e.g. "github_repo", "channel"). Static — no template interpolation. Pattern-validated at admission because this value is a component of a permsurface.Handle, which appears in approved plan ceilings and audit records. Mirrors BoundEntityType.ResourceType. |
spec.subcommands[].permission.stateImpact * | string | StateImpact describes the policy mode for a tool / subcommand. |
spec.subcommands[].permission.toolName | string | ToolName lets Layer 2's CheckScope evaluate the tool allow/deny axis. Wire from the registered tool name. Empty string skips the Layer 2 tool check (existing behavior). |
spec.subcommands[].permissionVariants | []object | PermissionVariants branch this subcommand's authority on its ARGUMENTS: CEL When over the parsed args, first match wins, Permission is the fallback. Mirrors pkg/toolspec/toolkit.Subcommand.PermissionVariants and matches MCPServerTool.PermissionVariants exactly — the same idea should not grow a second vocabulary. |
spec.subcommands[].permissionVariants[].check * | object | Check is the Permission applied when When matches. Same shape as the slice-1 singular Permission's Check, plus the new EnforceMode and ResourceIDExpr fields. |
spec.subcommands[].permissionVariants[].check.check | object | PermissionCheck names the SpiceDB resource and permission to evaluate. Required when StateImpact is Readonly / Readwrite / External; forbidden when Stateless / Passthrough. |
spec.subcommands[].permissionVariants[].check.check.enforceMode | string | EnforceMode controls deny-finality under permissive toolAuthMode. Empty → EnforceInherit (slice-1 default). |
spec.subcommands[].permissionVariants[].check.check.extractionPrompt | string | ExtractionPrompt, when set, is an English fragment the runner includes in the extraction LLM's system prompt for this resource type. Tool spec authors write this once per toolspec/MCPServer file. The runner aggregates ExtractionPrompts across all tools whose Check targets the same resourceType (dedup'd, concatenated). The AgentClass's BoundEntityType.ExtractionPrompt overrides entirely if set. |
spec.subcommands[].permissionVariants[].check.check.grantBindsArgs | []string | GrantBindsArgs, when set, restricts the slice-2 grant's arguments_hash caveat binding to only the listed top-level arg keys. Default (nil/empty) hashes the full arg map, meaning the grant satisfies only the exact same call. Setting it to e.g. ["repo"] lets a single approval cover every subsequent call against the same repo regardless of other args (pr number, etc.) — useful for readonly tools where the resource is the load-bearing input. The hash is an HMAC-SHA256 under the per-session args-hash key minted by the AgentSession reconciler into the per-session Secret and read by the runner at startup. Caveat contexts therefore cannot be predicted or minted outside the controller+runner trust domain — neither the agent nor a confused deputy with SpiceDB write access can forge a valid binding for arguments that were never requested. Note: hashes of previously-requested (including denied) calls remain observable in session status and channel payloads, so a SpiceDB-write attacker could still replay those; the keying narrows that surface to exactly the calls a human has already seen. See pkg/authz/guardian/grants.ArgsHashFiltered for the filtering contract. NOTE: the hash no longer BINDS the grant. A slot grant is keyed on (instance, permission), which is what makes it safe to reuse across calls: a grant shaped for one permission cannot be spent on another against the same id. The filtered hash is still computed and published on the approval request (ToolApprovalDetails.ArgsHash) so an approver — and the audit log — can see exactly which call was asked about. |
spec.subcommands[].permissionVariants[].check.check.permission * | string | Permission is the SpiceDB permission (or relation) name to Check (e.g. "read", "write", "admin"). Static. Pattern-validated at admission for the same reason as ResourceType. Mirrors BoundEntityType.Permission. |
spec.subcommands[].permissionVariants[].check.check.resourceIDExpr | string | ResourceIDExpr is a CEL string expression evaluated against args that yields the SpiceDB resource id. Exactly one of ResourceIDTemplate or ResourceIDExpr must be set: the template form for simple {arg} interpolation, the Expr form for nested-arg extraction. |
spec.subcommands[].permissionVariants[].check.check.resourceIDHint | string | ResourceIDHint is shown to the AGENT when the resource id cannot be resolved from the call's arguments. That failure is usually the caller's to fix — it named no resource, or named it in a form the check cannot read — and the agent is the only party who can retry. Without a hint the message is the raw resolution error prefixed "internal:", which reads as a system fault and tells it not to bother. Written by whoever authors the check, because only they know what the call should have looked like. Example, for a git push whose remote must be a URL so the repository can be authorized: "name the remote as a full https:// URL, not a shorthand like origin". |
spec.subcommands[].permissionVariants[].check.check.resourceIDTemplate | string | ResourceIDTemplate is a string with {arg} placeholders that interpolate from the tool call's args (e.g. "{owner}/{repo}"). Resolved at runtime by ResolveTemplate; validated at AgentClass-reconcile time so every {arg} matches a parameter the tool declares. |
spec.subcommands[].permissionVariants[].check.check.resourceIDTransforms | []string | ResourceIDTransforms names registered transforms applied IN ORDER to the resolved template string before SpiceDB sees it: lowercase, remove_spaces, spicedb_object_id, basename, sha256. See transforms.go. |
spec.subcommands[].permissionVariants[].check.check.resourceType * | string | ResourceType is the SpiceDB definition the Check runs against (e.g. "github_repo", "channel"). Static — no template interpolation. Pattern-validated at admission because this value is a component of a permsurface.Handle, which appears in approved plan ceilings and audit records. Mirrors BoundEntityType.ResourceType. |
spec.subcommands[].permissionVariants[].check.stateImpact * | string | StateImpact describes the policy mode for a tool / subcommand. |
spec.subcommands[].permissionVariants[].check.toolName | string | ToolName lets Layer 2's CheckScope evaluate the tool allow/deny axis. Wire from the registered tool name. Empty string skips the Layer 2 tool check (existing behavior). |
spec.subcommands[].permissionVariants[].when * | string | When is a CEL boolean expression with args in scope. |
spec.subcommands[].positional | []object | Positional declares the subcommand's positional arguments, in order. |
spec.subcommands[].positional[].afterDashDash | boolean | AfterDashDash marks this slot as where post--- arguments start binding, for a CLI that overloads -- as a separator rather than a plain option terminator (git's log [<rev>] [-- <path>…]). Mirrors pkg/tools/toolspec/toolkit.Positional.AfterDashDash; at most one positional per subcommand may set it. |
spec.subcommands[].positional[].name * | string | Name is the argument name the tool schema exposes to the agent. |
spec.subcommands[].positional[].required | boolean | Required rejects an invocation that omits this argument. |
spec.subcommands[].positional[].splitOn | string | SplitOn, when non-empty, splits one supplied value into several argv entries on this separator. |
spec.subcommands[].positional[].type * | string | Type is the value type used to validate and render the argument. |
spec.subcommands[].positional[].values | []string | Values, when non-empty, restricts the argument to this enumeration. |
spec.subcommands[].timeout | string | Timeout overrides the mode-derived per-call execution budget for this subcommand (a Go duration string, e.g. "10m" for a slow clone). Empty applies the mode default: 30m for stream/interactive, 5m for sync. Mirrors pkg/tools/toolspec/toolkit.Subcommand.Timeout; a non-parseable or non-positive value is rejected when the toolkit is loaded. |
spec.target * | object | Target identifies the binary this toolkit drives. |
spec.target.binary * | string | Binary is the executable name as invoked inside the sandbox. |
spec.target.pinnedBinaryHash | string | PinnedBinaryHash, when set, asserts the sha256 of the toolkit binary ("sha256:…"). RECORDED, not verified: in-sandbox measurement needs a sandbox exec primitive that does not exist yet. |
spec.target.versionProbe | object | VersionProbe is how to ask the binary its version; nil skips the check. |
spec.target.versionProbe.args * | []string | Args is the argv appended to the binary to make it print its version. |
spec.target.versionProbe.extract * | object | Extract is how to pull the version string out of that output. |
spec.target.versionProbe.extract.kind * | string | Kind selects the extraction strategy applied to the probe's output. (enum: regex | json | line1) |
spec.target.versionProbe.extract.pattern | string | Pattern is the regex or JSON path Kind applies; unused for line1. |
spec.target.versionRange | string | VersionRange is the semver range the description is valid for; empty accepts any version. |
spec.toolkitRevision * | string | ToolkitRevision is the description's own revision, bumped when the declared surface changes. |
spec.version | string | Version is the upstream CLI version this description was written against. |
Status
Status is controller-owned (observed state).
| Field | Type | Description |
|---|---|---|
status.conditions | []object | Conditions carries Valid; see SpiceboxToolkitConditionValid. |
status.conditions[].lastTransitionTime * | string (date-time) | lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. |
status.conditions[].message * | string | message is a human readable message indicating details about the transition. This may be an empty string. |
status.conditions[].observedGeneration | integer (int64) | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. (min 0) |
status.conditions[].reason * | string | reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. |
status.conditions[].status * | string | status of the condition, one of True, False, Unknown. (enum: True | False | Unknown) |
status.conditions[].type * | string | type of condition in CamelCase or in foo.example.com/CamelCase. |
status.observedGeneration | integer (int64) | ObservedGeneration is the spec generation this status reflects. |
status.pin | object | Pin is the recorded pin baseline in the common PinRecord shape: Digest = pinnedBinaryHash assertion (if set), Version = versionRange (audit metadata). Strength reflects the declared ref: frozen iff PinnedBinaryHash is set, named iff VersionRange is set, else unpinned. |
status.pin.details | map[string]string | Details carries kind-specific extras (toolCount, registry host, …). |
status.pin.digest | string | Digest is the immutable identity: git sha, sha256:… image digest, canonical tool-manifest hash, or binary hash. |
status.pin.kind * | string | Kind is the pinning registry kind name (skill, image, mcp, cli, …). Validated against the registry by controllers, not by a CRD enum, so new kinds register without an API change. |
status.pin.observedAt | string (date-time) | ObservedAt is when the recording controller observed this identity. |
status.pin.strength * | string | Strength is the syntactic pin strength of the declared ref. (enum: frozen | named | unpinned) |
status.pin.version | string | Version is the human-readable identity: tag, serverInfo.version, or version-probe output. |