filemutation

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package filemutation implements the shared mechanics of the two direct mutation tools, WriteFile and EditFile: single-step preparation and canonicalization, workspace containment, atomic publication, optimistic file-freshness concurrency, and permit-scoped cross-loop serialization. The public writefile and editfile packages are thin facades over this package.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EditFile

type EditFile struct {
	// contains filtered or unexported fields
}

EditFile edits a workspace-contained file by exact-string replacement under the loop's optimistic-concurrency policy. It depends only on the workspace root (least privilege), the loop's shared observation map, and an OPTIONAL session workspace coordinator: an edit requires a complete prior read of this path whose hash still equals the file's current on-disk hash. When a coordinator is bound the commit runs under a SHARED session-mutation + canonical-PATH permit (serializing same-real-file edits across loops, excluded by a Bash/checkpoint permit).

func NewEditFile

func NewEditFile(root string, obs tool.WorkspaceObservations, opts ...FileMutatorOption) *EditFile

NewEditFile constructs an EditFile bound to the workspace root and the loop's shared observation map (supplied by Files, one per loop binding). A WithMutationCoordinator option binds the session workspace coordinator; without it the tool runs coordinator-free (the standalone/bare path). A WithHostWrites option lets an absolute target resolve outside the workspace instead of being rejected.

func (*EditFile) AuditSummary

func (e *EditFile) AuditSummary(argsJSON string) string

AuditSummary returns a redacted, content-free one-line summary: the path only (never the old/new substrings, which can carry secrets). An unparseable args document yields a generic summary.

func (*EditFile) Info

func (e *EditFile) Info(context.Context) (*tool.ToolInfo, error)

Info returns EditFile's self-description. Name MUST equal "EditFile". The description swaps to editFileHostWritesDesc when hostWrites is set, mirroring writefile.go's writeFileDesc/writeFileHostWritesDesc swap.

func (*EditFile) InvokableRun

func (e *EditFile) InvokableRun(ctx context.Context, _ string) (*tool.ToolResult, error)

InvokableRun executes the PREPARED artifact bound to this call — the raw argsJSON is never reparsed, so mutating it after preparation changes nothing. Without its typed artifact the effectful tool fails closed. It applies the edit and returns a diff preview, or a tool-result error string for every failure mode. Never a Go error, never echoing the full file body.

func (*EditFile) PrepareCall

func (e *EditFile) PrepareCall(_ context.Context, executionID uuid.UUID, argsJSON string) (tool.Request, tool.PreparedArtifact, error)

PrepareCall decodes and validates the untrusted arguments ONCE, resolves the canonical edit target ONCE, and returns the typed request — a direct filesystem.write requirement whose Scope and Match are the canonical resolved path, empty grant pair — plus the typed artifact InvokableRun executes. Invalid input fails here and never reaches the permission gate.

For an UNCONTAINED target ONLY, the request also carries a paired filesystem.read requirement for the SAME canonical path (see pairedReadRequirement): EditFile always performs an in-process read via readForEdit before writing back, and a prior host read/write must never silently authorize this one — every host read tied to an edit gets its own fresh gate decision. A contained target is unchanged: exactly one requirement (write only).

func (*EditFile) WriteTarget

func (e *EditFile) WriteTarget(argsJSON string) (string, bool, error)

WriteTarget returns the CANONICAL prepared edit path as the serialization key (an edit is a write), derived by the same preparation step that emits the requirement Scope — preparation is the single source of the scheduling key. ok is true for a well-formed call; a non-nil err (bad args/escape) tells the runner to treat the call as invalid.

type FileCreateConflictError

type FileCreateConflictError struct {
	// Path is the workspace-relative path as the model supplied it.
	Path string
}

FileCreateConflictError reports that a create-without-observation lost the atomic no-replace publication race: the destination already exists (another writer created it, or it appeared between the absence check and the link). No bytes were clobbered. The message carries no hash or content.

func (*FileCreateConflictError) Error

func (e *FileCreateConflictError) Error() string

type FileMutatorOption

type FileMutatorOption func(*fileMutatorConfig)

FileMutatorOption configures a structured file mutator (WriteFile/EditFile) at construction (functional-options pattern), preserving the coordinator-free constructors used by the unit tests.

func WithHostWrites

func WithHostWrites() FileMutatorOption

WithHostWrites lets an absolute write/edit target resolve OUTSIDE the workspace instead of being rejected at prepare time -- the write-side counterpart to readfile.WithHostReads()/grep.WithHostReads()/glob.WithHostReads(). It grants nothing itself: an uncontained resolved target still emits a filesystem.write requirement, and the caller's bound access source (the sandbox.Profile) makes the actual Allow/Deny/Gated decision. A relative "../" traversal is never widened by this option.

func WithMutationCoordinator

func WithMutationCoordinator(coord tool.WorkspaceCoordinator) FileMutatorOption

WithMutationCoordinator binds the session workspace coordinator so the mutator's commit runs under a PathMutation permit and verifies lease health. A nil or typed-nil coordinator is ignored (the tool stays coordinator-free).

type IrregularFileError

type IrregularFileError struct {
	// Path is the workspace-relative path as the model supplied it.
	Path string
}

IrregularFileError reports that a write/edit target's final component is not a plain regular file this loop can observe and rewrite — a final-component symlink (which the read tools refuse to follow) or another non-regular node (directory, device, socket, …). It is DISTINCT from StaleFileError on purpose: telling the model to "read the file again" would dead-end, because a ReadFile of the same path also refuses it (O_NOFOLLOW). The message is actionable and non-secret; it carries no hash or content.

func (*IrregularFileError) Error

func (e *IrregularFileError) Error() string

type LeaseUnhealthyError

type LeaseUnhealthyError struct{ Cause error }

LeaseUnhealthyError reports that a structured mutation was refused because the workspace lease could not be verified healthy at commit time (fail-secure). Its message carries no secret; Cause is the underlying lease-health error.

func (*LeaseUnhealthyError) Error

func (e *LeaseUnhealthyError) Error() string

func (*LeaseUnhealthyError) Unwrap

func (e *LeaseUnhealthyError) Unwrap() error

type StaleFileError

type StaleFileError struct {
	// Path is the workspace-relative path as the model supplied it.
	Path string
}

StaleFileError reports that an existing-file overwrite or edit was refused because this loop lacks a complete, CURRENT observation of the target: it was never read to completion, or it changed on disk since the read (an optimistic- concurrency conflict). The model is told to read the file again. The message NEVER carries a hash, version, or file content.

func (*StaleFileError) Error

func (e *StaleFileError) Error() string

type WriteFile

type WriteFile struct {
	// contains filtered or unexported fields
}

WriteFile writes a workspace-contained file atomically under the loop's optimistic-concurrency policy. It depends only on the workspace root (least privilege — deny rules are evaluated by the gate over the prepared filesystem.write requirement), the loop's shared observation map, and an OPTIONAL session workspace coordinator: overwriting an EXISTING file requires a complete prior read of this path whose hash still equals the file's current on-disk hash; a genuinely ABSENT path may be created without any prior read via an atomic no-replace publication. When a coordinator is bound the commit runs under a SHARED session-mutation + canonical-PATH permit (design §"File-tool optimistic concurrency and binding"), which serializes same-real-file writes ACROSS loops (the private observation map only serializes within one loop).

This optimistic-concurrency policy applies ONLY to a CONTAINED (in-workspace) target. An UNCONTAINED target (WithHostWrites(), an absolute path resolving outside the workspace) never consults or updates the observation map in either direction — see commitUncontained's doc comment for the product decision behind that split.

func NewWriteFile

func NewWriteFile(root string, obs tool.WorkspaceObservations, opts ...FileMutatorOption) *WriteFile

NewWriteFile constructs a WriteFile bound to the workspace root and the loop's shared observation map (supplied by Files, one per loop binding). A WithMutationCoordinator option binds the session workspace coordinator; without it the tool runs coordinator-free (the standalone/bare path). A WithHostWrites option lets an absolute target resolve outside the workspace instead of being rejected.

func (*WriteFile) AuditSummary

func (w *WriteFile) AuditSummary(argsJSON string) string

AuditSummary returns a redacted, content-free one-line summary: the path and byte count only — NEVER the content. An unparseable args document yields a generic summary.

func (*WriteFile) Info

func (w *WriteFile) Info(context.Context) (*tool.ToolInfo, error)

Info returns WriteFile's self-description. Name MUST equal "WriteFile". The description swaps to writeFileHostWritesDesc when hostWrites is set, mirroring readfile.go's readFileDesc/readFileHostReadsDesc swap.

func (*WriteFile) InvokableRun

func (w *WriteFile) InvokableRun(ctx context.Context, _ string) (*tool.ToolResult, error)

InvokableRun executes the PREPARED artifact bound to this call — the raw argsJSON is never reparsed, so mutating it after preparation changes nothing. Without its typed artifact the effectful tool fails closed. Every failure mode is a tool-result error string — never a Go error and never echoing the content.

func (*WriteFile) PrepareCall

func (w *WriteFile) PrepareCall(_ context.Context, executionID uuid.UUID, argsJSON string) (tool.Request, tool.PreparedArtifact, error)

PrepareCall decodes and validates the untrusted arguments ONCE, resolves the canonical write target ONCE, and returns the typed request — one direct filesystem.write requirement whose Scope and Match are the canonical resolved path, empty grant pair — plus the typed artifact InvokableRun executes. Invalid input fails here and never reaches the permission gate.

func (*WriteFile) WriteTarget

func (w *WriteFile) WriteTarget(argsJSON string) (string, bool, error)

WriteTarget returns the CANONICAL prepared write path as the serialization key so the runner groups concurrent writes to the same on-disk file. It is derived by the same preparation step that emits the requirement Scope — preparation is the single source of the scheduling key. ok is true for every well-formed write; a non-nil err (unparseable args or an escape) tells the runner to treat the call as invalid rather than execute it ungrouped.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL