Files
zero/internal/tools/capabilities.go
d66ad71532 chore: remove dead wrapper functions and unused test seams (#905)
* chore: remove dead wrapper functions and unused test seams

These five functions are unreachable from any cmd/* main even when tests are included (deadcode -test ./...), so they are genuinely dead on every platform:

- TraceEventKeys: one-line alias of ParseTraceEventKeys, no callers
- ValidateToolCapabilities: one-line alias of ValidateCapabilities, no callers
- GetLocalDiffStats and WatchPRState: test seams in internal/tui/export_test.go that no test calls
- ScanSSEData: non-context SSE scanner in internal/providers/providerio, fully superseded by ScanSSEDataWithContext

The five remaining -test findings are //go:build !linux stubs (landlock/seccomp/linux_helper) that are live on Linux, so they are left in place.

Co-Authored-By: cairn-code <[email protected]>

* Bump Go to 1.26.6 so govulncheck and Windows smoke pass.

The 1.26.5 standard library still reports GO-2026-6218 and the Punycode
label check that fail required CI; 1.26.6 matches main and clears them.

---------

Co-authored-by: euxaristia <[email protected]>
Co-authored-by: cairn-code <[email protected]>
2026-08-18 21:44:13 +08:00

165 lines
5.7 KiB
Go

package tools
import "strings"
// EffectClass classifies a tool's side effects for concurrency safety.
// Concurrent execution (a later PR) must only consider tools that are both
// ThreadSafe and EffectReadOnly; this package only declares metadata.
// ThreadSafe is force-cleared for every non-ReadOnly effect at read time
// (see normalizeCapabilities).
type EffectClass int
const (
// EffectUnknown is the fail-closed default for unclassified tools.
// Never concurrency-eligible; always serialized.
EffectUnknown EffectClass = iota
// EffectReadOnly reads local or remote state without mutating it.
// The only effect class that may set ThreadSafe=true after audit.
EffectReadOnly
// EffectProcessRead observes a retained process, PTY, or terminal.
EffectProcessRead
// EffectWorkspaceWrite mutates the local repository or workspace.
// Includes one-shot shell commands that can rewrite the workspace.
EffectWorkspaceWrite
// EffectExternalWrite mutates state outside the local workspace.
EffectExternalWrite
// EffectInteractive uses browser, PTY/terminal, prompts, or other
// session-bound interaction where ordering matters.
EffectInteractive
)
// String returns a stable diagnostic name for the effect class.
func (e EffectClass) String() string {
switch e {
case EffectUnknown:
return "unknown"
case EffectReadOnly:
return "read_only"
case EffectProcessRead:
return "process_read"
case EffectWorkspaceWrite:
return "workspace_write"
case EffectExternalWrite:
return "external_write"
case EffectInteractive:
return "interactive"
default:
return "invalid"
}
}
// Valid reports whether e is one of the defined effect classes.
func (e EffectClass) Valid() bool {
return e >= EffectUnknown && e <= EffectInteractive
}
// ToolCapabilities is the concurrency-safety contract for a tool.
// ResourceKeys, when set, returns deterministic conflict keys for a call's
// arguments. Returning no keys does NOT make a tool parallel-safe.
type ToolCapabilities struct {
Effect EffectClass
ThreadSafe bool
ResourceKeys func(args map[string]any) []string
}
// CapabilityProvider is the optional interface tools implement to declare
// effect metadata. Tools that do not implement it are treated as EffectUnknown
// and not thread-safe (fail-closed).
type CapabilityProvider interface {
Capabilities() ToolCapabilities
}
// UnknownCapabilities is the fail-closed default for tools without metadata.
func UnknownCapabilities() ToolCapabilities {
return ToolCapabilities{
Effect: EffectUnknown,
ThreadSafe: false,
}
}
// normalizeCapabilities is the single read path for capability metadata.
// ThreadSafe is cleared unless Effect is a valid EffectReadOnly — mutators,
// interactive tools, process readers, unknown, and invalid effects must never
// appear concurrency-eligible to a future batch planner that only checks
// ThreadSafe && Effect.
func normalizeCapabilities(caps ToolCapabilities) ToolCapabilities {
if !caps.Effect.Valid() || caps.Effect != EffectReadOnly {
caps.ThreadSafe = false
}
return caps
}
// CapabilitiesOf returns a tool's declared capabilities, or the fail-closed
// unknown default when the tool does not implement CapabilityProvider.
func CapabilitiesOf(tool Tool) ToolCapabilities {
if tool == nil {
return UnknownCapabilities()
}
if provider, ok := tool.(CapabilityProvider); ok {
return normalizeCapabilities(provider.Capabilities())
}
return UnknownCapabilities()
}
// (baseTool).Capabilities exposes the capabilities field set at construction,
// normalized so ThreadSafe cannot surface on non-ReadOnly effects.
// Tools that embed baseTool inherit this method.
func (tool baseTool) Capabilities() ToolCapabilities {
return normalizeCapabilities(tool.capabilities)
}
// declaredCapabilities returns the constructor-set metadata without runtime
// normalization. Catalog validation uses this so a mis-declared
// ThreadSafe=true on a mutator is still reported even though CapabilitiesOf
// clears it for callers.
func (tool baseTool) declaredCapabilities() ToolCapabilities {
return tool.capabilities
}
// declaredCapabilityProvider is implemented by tools that can report their
// raw constructor metadata (baseTool embedders).
type declaredCapabilityProvider interface {
declaredCapabilities() ToolCapabilities
}
// Registry.Capabilities looks up a registered tool's capabilities by name.
// Unknown tools return the fail-closed default (not an error) so callers can
// treat missing tools as non-concurrent without panicking.
func (registry *Registry) Capabilities(name string) ToolCapabilities {
if registry == nil {
return UnknownCapabilities()
}
tool, ok := registry.Get(name)
if !ok {
return UnknownCapabilities()
}
return CapabilitiesOf(tool)
}
// ValidateCapabilities checks a single capability record for consistency.
// It returns a non-empty slice of human-readable problems when invalid.
// Note: CapabilitiesOf already normalizes ThreadSafe away for non-ReadOnly
// effects; validation still rejects the raw combination so catalog tests
// catch mis-declared constructors.
func ValidateCapabilities(name string, caps ToolCapabilities) []string {
var problems []string
name = strings.TrimSpace(name)
if name == "" {
name = "<unnamed>"
}
if !caps.Effect.Valid() {
problems = append(problems, name+": invalid effect value")
}
if caps.Effect == EffectUnknown && caps.ThreadSafe {
problems = append(problems, name+": ThreadSafe=true is forbidden with EffectUnknown")
}
// Mutating / interactive / process-read effects are never concurrency-eligible.
switch caps.Effect {
case EffectWorkspaceWrite, EffectExternalWrite, EffectInteractive, EffectProcessRead:
if caps.ThreadSafe {
problems = append(problems, name+": ThreadSafe=true is not permitted for effect "+caps.Effect.String())
}
}
return problems
}