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 ¶
- type EditFile
- func (e *EditFile) AuditSummary(argsJSON string) string
- func (e *EditFile) Info(context.Context) (*tool.ToolInfo, error)
- func (e *EditFile) InvokableRun(ctx context.Context, _ string) (*tool.ToolResult, error)
- func (e *EditFile) PrepareCall(_ context.Context, executionID uuid.UUID, argsJSON string) (tool.Request, tool.PreparedArtifact, error)
- func (e *EditFile) WriteTarget(argsJSON string) (string, bool, error)
- type FileCreateConflictError
- type FileMutatorOption
- type IrregularFileError
- type LeaseUnhealthyError
- type StaleFileError
- type WriteFile
- func (w *WriteFile) AuditSummary(argsJSON string) string
- func (w *WriteFile) Info(context.Context) (*tool.ToolInfo, error)
- func (w *WriteFile) InvokableRun(ctx context.Context, _ string) (*tool.ToolResult, error)
- func (w *WriteFile) PrepareCall(_ context.Context, executionID uuid.UUID, argsJSON string) (tool.Request, tool.PreparedArtifact, error)
- func (w *WriteFile) WriteTarget(argsJSON string) (string, bool, error)
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 ¶
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 ¶
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 ¶
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 readForPreview 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.