files Provider¶
Auth provider: none. The files provider never touches a network or a
credential, so the Auth Engine is skipped entirely.
It still passes through the same lifecycle as every other operation:
Bootstrap → Context → Policy → Execution → Audit. It exists so the runtime
has exactly one dispatch path — there is no "local" shortcut around governance.
Operations¶
| Operation | Transport | Notes |
|---|---|---|
read <path> |
file | |
write <path> <content...> |
file | Overwrites |
append <path> <content...> |
file | |
list <path> |
file | Directory listing |
delete <path> |
file | Denied by the compiled default policy |
runtime files write ./notes.txt hello world
runtime files read ./notes.txt
runtime files append ./notes.txt another line
runtime files list .
runtime --output json files read ./notes.txt
Selective and bulk editing¶
Runtime 0.9.4 adds one direct, governed edit surface for local engineering
files. Omit --dry-run to edit after whole-batch preflight; add it to calculate
the same selection, transformations, validations, counts and diffs without
writing. There is no manifest, saved plan or digest to copy.
runtime files text replace --from master --to main \
--dir ./repos --filename build.yaml \
--expected-files 100 --expected-per-file 1 --dry-run
runtime files yaml set --path 'containers[name=api].image' \
--value 'app:2' --dir ./repos --filename deployment.yaml
runtime --output json files terraform add-list-item \
--block module --label repository --attribute teams \
--value '"platform"' --dir ./repos --include '*.tf'
Every edit takes exactly one --file or --dir. A directory walk is recursive,
lexically ordered and bounded. Narrow it with --filename, repeatable
--include, and repeatable --exclude. Use --expected-files,
--expected-matches, and --expected-per-file when an estate change must match
the reviewed shape exactly.
| Format | Released operations |
|---|---|
| Text | exact replace; optional whole-word or whole-line matching |
| YAML | exact replace, scalar path set, insert-after, comment-out, uncomment; numeric and keyed sequence paths |
| JSON | typed RFC 6901 add, set/replace, remove, test |
| Terraform/HCL | selected block attribute set, single-line list add/remove, required-provider/version update |
| CODEOWNERS | exact-pattern owner add/remove, structural validation |
| Dockerfile | selected-stage base image/tag update, exact replace |
| Markdown | exact replace outside fences, heading-section replace |
Literal and span-based operations preserve unmatched bytes. YAML scalar edits
preserve comments, key order, quoting and unrelated whitespace. Terraform
editing currently supports selected attributes and single-line lists; it is not
an arbitrary HCL refactoring engine. Dockerfile editing preserves unrelated
lines and validates the resulting FROM surface. Ambiguous selectors, invalid
documents, multi-document YAML, unexpected counts, zero matches and malformed
after-images refuse before the first write.
Default text output is the human review surface: one file table, totals, and
bounded diffs. --output json returns typed file states, match counts,
validation, diffs and before/after digests. Raw output is refused for a batch
because it would erase file boundaries.
Commit and recovery boundary¶
File Engine resolves read and write descriptors for the whole selection, reads and validates every pre-image and after-image, and only then begins replacement. Immediately before each atomic same-directory replacement, it rechecks file identity and the content digest. A concurrent change therefore refuses rather than being overwritten.
Atomic replacement protects each file, not a hundred files across different
filesystems. If a later commit fails, Runtime attempts bounded rollback and
records each target as rolled_back, not_committed, or unknown; it never
reports a false all-or-none outcome. It does not run Git, formatters, linters or
external validators.
delete is denied by default¶
Remove the rule from
policy-config.yaml to allow it. For files under
version control, runtime command run git rm is often the better answer — it
is recoverable from history, which a filesystem delete is not.
File access is granted, not assumed¶
Changed in 0.6.0, and it is a breaking change. Earlier releases let the
File Engine read anything on the machine and write almost anywhere. It now
reaches only directories your policy document grants, and a policy with no
file_policy block grants nothing.
The shipped default grants the directory you ran runtime from:
"." follows you
Resolved per request means the grant moves with the working directory of
each command — the same files read succeeds in one directory and is
refused in another. Fine for a first run, confusing once capabilities run
from CI or an agent. Use absolute paths in a policy you keep, and see
choosing roots for how wide to
make them.
Read and write are separate grants. A root in read_roots permits read
and list; a root in write_roots permits write, append and delete.
Neither implies the other, so a capability allowed to summarise a tree cannot
also replace it.
A path has to resolve inside a granted root — it is not matched against a
pattern. .., an absolute path elsewhere and a symlink pointing out of the
root all reach nothing:
$ runtime files read /etc/hosts
files read /etc/hosts is outside every directory policy grants read authority to
(/home/you/project). The path is not evaluated against a pattern — it has to
resolve inside a granted root, so `..` and an absolute path elsewhere reach
nothing
If you are upgrading: a policy document you wrote yourself has no
file_policy block, so every files operation will be refused until you add
one. Copy the block above and narrow the roots to the trees this machine should
touch — an absolute path is usually the better answer for a fleet.
Some files are refused inside any grant¶
These are never reachable through the File Engine, for reads as well as writes, even when they sit inside a granted root:
- Runtime's own
config.yaml,policy-config.yaml,version,specs/,commands/,logs/,audit/,enterprise/andtrust/ - whichever governance document
$RUNTIME_CONFIG_FILEor$RUNTIME_POLICY_FILEselected, wherever it lives - the credential stores your tools authenticate with:
~/.ssh,~/.kube,~/.aws,~/.azure,~/.config/gcloud,~/.config/gh,~/.docker,~/.netrc,~/.git-credentials,~/.gnupg, and the paths$KUBECONFIG,$CLOUDSDK_CONFIGand$GOOGLE_APPLICATION_CREDENTIALSpoint at
Two things follow from how this is enforced. The refusal follows the file,
not the name — a symlink to it, a hard link to it, or moving it somewhere else
all still refuse. And reads are covered, which they were not before: runtime
files read ~/.ssh/id_rsa used to return a private key to whatever asked, which
for an AI-driven workflow means into a context window.
files write is an allowed operation, so without the governance half a single
command could rewrite policy and the next command would run under the new
rules.
Still writable, deliberately: the capabilities directory, cache/, and
anything else inside a granted root. Authoring a capability with files write
is a normal workflow.
Results are bounded¶
| Budget | Default | Narrow it with |
|---|---|---|
Bytes returned by read |
1 MiB | file_policy.max_read_bytes |
Entries returned by list |
1000 | file_policy.max_list_entries |
| Directories below a root | 32 | file_policy.max_depth |
| Files selected for one edit | 1000 | file_policy.max_edit_files |
| Pre/post bytes in one edit | 32 MiB | file_policy.max_edit_batch_bytes |
| Matches in one edit | 10000 | file_policy.max_edit_matches |
| Returned diffs | 1 MiB | file_policy.max_edit_diff_bytes |
| Edit duration | 30 seconds | file_policy.max_edit_seconds |
A document may only narrow these. A larger value is ignored, because a limit a document can raise is a default rather than a limit.
When a result hits its budget the message says so — a truncated file is never presented as a complete one:
Output also passes a redaction pass on the way out: recognisable credential shapes (tokens with a documented prefix, private-key blocks, JWTs) are replaced with a marker. This is defence in depth, not data-loss prevention — a secret with no recognisable shape passes it. What actually keeps credentials out of results is the protected-path list above.
Auth-free capabilities¶
The files provider is the right place to start with capabilities — no token,
no network, no external state:
ls ~/.engineering-runtime/capabilities/files/
runtime capability validate files/notes-roundtrip
runtime capability execute files/notes-roundtrip \
--input path=./hello.txt --input message="first run"
The shipped examples demonstrate the patterns:
| Capability | Demonstrates |
|---|---|
notes-roundtrip |
write → read |
log-rotate |
append + list |
scaffold-service-docs |
multi-write into a directory, then list |
incident-log-lifecycle |
write → append → read |
directory-snapshot-report |
multi-directory list + write a report |
estate-config-update |
YAML + Terraform + CODEOWNERS estate change |
yaml-image-rollout |
keyed-sequence YAML selection |
terraform-provider-upgrade |
selected provider/version constraints |
In a capability:
workflow:
- provider: files
args: [write, "${path}", "${message}"]
- provider: files
args: [read, "${path}"]
Known limitations¶
| Limitation | Workaround |
|---|---|
No mkdir operation. write fails when a parent directory is missing |
Create the path some other way first. In a GitHub workflow, seeding a placeholder file through the Contents API makes Git create the path components; locally, runtime command run git or an existing directory works |
| No encoding primitive. No base64 or hashing operation | Write literal content. Where a platform API requires base64, embed the encoded string directly in the capability |
delete denied by default |
Change policy deliberately, or use git rm |
| No regex or arbitrary script transform | Use exact text or a registered typed adapter |
| No automatic clone, Git stage/commit/push, PR, formatter or linter | Compose those as separate governed capability steps |
| No cross-filesystem atomic transaction | Use preflight, per-file atomic replacement and audited rollback states |
These limits keep File Engine deterministic. A missing transformation is added as a typed adapter operation, never worked around with an unrestricted shell.
Operational examples on this site were verified against Runtime 0.9.8. After bootstrap, the version-exact files in Runtime Home win.