Documentation
¶
Overview ¶
Package toolwrite answers one question about one platform tool call: does it persist something that outlives the call?
The question has one caller today — the write barrier a managed script's draft run puts in front of platform.call (#1664) — and one property that makes it worth its own package: it is decided by a declared table rather than inferred, so what a draft will and will not do is readable in one file.
Deny by default ¶
A tool no rule here names is reported as writing. The alternative fails in the one direction that matters: a classifier that guesses "read" on a tool it has never seen lets a draft land data, which is the defect this exists to close. Guessing "write" costs an author a refusal that names the tool and tells them how to proceed.
Why a table and not the MCP annotations ¶
MCP carries ReadOnlyHint, and where a toolkit sets it the value here agrees with it. It cannot be the source, for two reasons. Most of the platform's tools set no annotation at all, so reading them would classify most of the surface as unknown. And the platform's management surface is action-based — one tool name covering manage_resource create and manage_table list — so a hint attached to a tool cannot separate the half that writes from the half that does not. The unit of classification is the call, not the tool.
TestEveryRegisteredToolIsClassified (test/structure) refuses a tool the platform registers that no rule here names, so the table cannot quietly go stale behind a new toolkit.
What "writes" means ¶
A call writes when it changes what a later reader of the platform sees: a stored record, a registration, a file, an upstream resource. The machinery every call produces — an audit row, a session's discovery state, a metric — is not a write in this sense. It is produced by refused calls too, and a deployment that counted it would have no read-only surface at all.
Index ¶
Constants ¶
const ( // ToolInvokeEndpoint is the api gateway's HTTP call, classified by the // method it sends. ToolInvokeEndpoint = "api_invoke_endpoint" // ToolGraphQLQuery is the graphql kind's document call, classified by // whether the operation that will execute is a mutation. ToolGraphQLQuery = "graphql_query" )
Tool names this package rules on by something other than a fixed action set. They are literals rather than imports because a classifier that pulled in a toolkit to read one constant would depend on the whole toolkit, and the structural gate already fails when a registered name has no rule.
Variables ¶
This section is empty.
Functions ¶
func Classified ¶
Classified reports whether any rule here names the tool, whatever it decides about a given call. The structural gate asks this of every tool the platform registers; a caller deciding one call asks Classify.
Types ¶
type Classifier ¶
type Classifier struct {
// ResolveMethod resolves an api_invoke_endpoint operation_id to its method.
ResolveMethod MethodResolver
}
Classifier decides one call's class. The zero value works: it classifies the operation_id form of an api gateway call, and every tool no rule here names, as a write. A MethodResolver is how a caller holding the live api connections narrows the first of those.
type Decision ¶
type Decision struct {
// Writes reports whether the call may persist something that outlives it.
Writes bool
// Call names the call the way a refusal names it: the tool, plus the
// action or command that decided it where the tool has one.
Call string
// Declared reports whether this call's class was actually decided, rather
// than fallen back to. A caller distinguishes them because the two
// refusals ask for different things: a declared write asks whether the
// author meant to persist, an undeclared one says the platform cannot tell.
// It is false both for a tool no rule names and for a call whose rule could
// not read the argument it decides on.
Declared bool
}
Decision is what the classifier concluded about one call.
type MethodResolver ¶
MethodResolver reports the HTTP method an api_invoke_endpoint operation_id is invoked with. The api gateway holds the parsed specs that answer it, so the composition root supplies this; a caller with none leaves the operation_id form unresolved, and it is then classified as a write like anything else the table cannot read.