d18075925d
The exemption added in 6a8a7b31 was broader than the invariant it stood on. "Tool
declares a SOURCE_PATH" is a claim about the parameter list; the safe property is
"every byte written derives from an existing source object rather than from
model-supplied content". A future transform or import tool could name a source and
still write model-controlled output, and would have inherited the exemption.
ToolCapability.CONTENT_FROM_SOURCE now carries that provenance claim explicitly.
file_copy declares it; ReadBeforeWriteRule.appliesTo stands down only for calls that
do, so ToolCallAssessor skips the rule rather than the rule skipping itself. The
capability is recorded on the invocation event like every other one, so replay
classifies a call by what it actually claimed instead of re-deriving it from
parameters.
Tool availability is by declared tool name, not capability-set containment, so the
extra capability does not narrow which stages can reach file_copy.
Tests: the exemption is asserted through ToolCallAssessor, plus a source-naming tool
WITHOUT the provenance capability that stays gated. ./gradlew check green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.7 KiB
3.7 KiB
core/toolintent — AGENTS.md
Purpose
Plane-2 tool-call intent validation: evaluates proposed tool calls against workspace policy rules before execution, records the assessment as an event, and provides WorldProbe for recording environment observations needed by rules.
Ownership
CORREX kernel team. This module enforces Hard Invariant #9 for the tool-call path.
Local Contracts
ToolCallAssessor— evaluates a proposed tool call against all activeToolCallRules; returns aToolCallAssessmentRecord.ToolCallRule— interface for a single validation rule. Built-in rules:PathContainmentRule— tool must write within the workspace root.ReadBeforeWriteRule— file must be read before being overwritten.StaleWriteRule— detects write to a file that has changed on disk since last read.NetworkHostRule— egress must be on the allowlist.ManifestContainmentRule— write target must appear in the write manifest.WriteScopeRule— enforces declared write scope.ReferenceExistsRule— referenced entities must exist.ExecInterpreterRule— interpreter must be in the allowed list.CycleExitRule(incore:validation) — imported separately; not defined here.
WorkspacePolicy— aggregates rules and configuration for a workspace.WorldProbe— performs environment checks (filesystem, network) and records the observations as events immediately (Hard Invariant #9). Never callWorldProbeduring replay.EgressAllowlist— current egress allowlist; rebuilt fromEgressAllowlistProjection(incore:events).ParamValueExtractor— extracts typed parameter values from tool call arguments.candidatePathStrings= every path-like argument (ParamRole.PATH+ParamRole.SOURCE_PATH), used by the containment/existence gates;writeTargetPathStrings= only the paths a call MUTATES (ParamRole.PATH), used by the write-target gates. A tool declaring none of those roles falls back to sniffing path-like strings, soshellis unaffected.RiskMapping— maps rule violations to risk levels forcore:risk.SessionContext— session-scoped context passed to rules during evaluation.
Work Guidance
- Hard Invariant #9: all
WorldProbecalls record observations as events. Replay reads those recorded events — it must not callWorldProbeagain. - Hard Invariant #5: every tool call must be assessed before execution. Assessment result is recorded as
ToolCallAssessmentEventsincore:events. - New rules implement
ToolCallRuleand are registered inWorkspacePolicy. Do not add rule logic directly toToolCallAssessor. - Resolve every model-supplied path through
ToolPath.resolve(core:tools) — the one canonical normalization rule, shared with the filesystem tools. A rule that resolves paths itself will judge a different path than the tool operates on (the~bug:~/xresolved to<workspace>/~/x, so a real home-directory file was reported as a non-existent in-workspace file and the out-of-workspace prompt never fired). ReadBeforeWriteRuleexempts calls declaringToolCapability.CONTENT_FROM_SOURCE— every byte written comes from an existing source object, so there is no model-authored content to clobber with, and requiring a read of a copied binary is unsatisfiable. The exemption keys on that declared PROVENANCE, never on the presence of aSOURCE_PATHparameter: a transform or import tool may name a source and still write model-controlled output, and must stay gated. It lives inappliesTo, soToolCallAssessorskips the rule entirely.
Verification
./gradlew :core:toolintent:test --rerun-tasks
Child DOX Index
No child AGENTS.md (leaf module).