write 0.1.0
Replace a file atomically, only if it holds what the caller says it holds (--if-sha256) or does not exist (--create); idempotent, locked, with --dry-run.
write [--root DIR] (--create | --if-sha256 HEX) [--content-sha256 HEX] [--dry-run] [--max-bytes N] [--max-diff-lines N] (--stdin | --content-file PATH) [--format json|text] PATH
- output
- one JSON document
- schema
- write.v1
- reversibility
irreversible-bounded- guarantees
deterministic,idempotent,atomic,requires_precondition,dry_run- concurrency
- locked: of writers racing with the same precondition, exactly one wins and the others are refused (M7)
- confinement
beneath: with --root, paths are checked lexically against it and then opened beneath it one component at a time, following no symbolic link (path.symlink), so nothing outside the root is reachable through one; without it, a reader opens the path as given, links and all, and a writer still follows no link in the file's own name- reads the environment
- no
- reads the clock
- no
- compiler
- de4e52fd8180
- for an agent
- SKILL.md (
write skill)
Authority
Derived by cancho authority from the source, embedded in the binary, and checked against tools.toml's ceiling in CI. Every label is bounded.
- args
- dir_read
- dir_write
- err_write
- file_read
- file_write
- fs_read("")
- heap
- io_read
- io_write
What the row cannot say:
- path extent: a path from the command line is checked at run time, so the row says fs_read("")
- what a symlink reaches
- what standard output carries
- resource use beyond the tool's own caps (memory, wall time)
- per-invocation behaviour such as --dry-run, which the row cannot see
Operands
| Operand | Role | How many | Meaning |
|---|---|---|---|
PATH | path-write | 1 | the file to create or replace |
Flags
| Flag | Kind | Role | Default | Meaning |
|---|---|---|---|---|
--root | path | root | resolve PATH and --content-file relative to this directory and refuse paths outside it | |
--create | bool | guard | the path must not exist yet (if it holds exactly the new content, nothing is written and changed is false) | |
--if-sha256 | hex64 | guard | the file's current content must hash to this SHA-256 | |
--content-sha256 | hex64 | none | the new content must hash to this SHA-256, checked before anything is written | |
--dry-run | bool | guard | check everything and report planned_actions, exit 9, write nothing | |
--stdin | bool | none | read the new content from standard input | |
--content-file | path | path-read | read the new content from this file | |
--max-bytes | nat | none | 67108864 | the largest new content accepted, and the largest old content diffed (ceiling 1073741824) |
--max-diff-lines | nat | none | 200 | the most changed lines the diff shows (ceiling 100000) |
--format | choice:json/text | none | json | json for a program, text for a person |
Limits
| Limit | Default | Ceiling |
|---|---|---|
max-bytes | 67,108,864 | 1,073,741,824 |
max-diff-lines | 200 | 100,000 |
Exit codes
| Code | Name | Meaning |
|---|---|---|
| 0 | SUCCESS | what was asked was done; zero matches is success |
| 1 | GENERAL_ERROR | the operating system failed mid-operation, or the tool caught a bug in itself |
| 2 | INVALID_ARGS | the invocation is malformed or names something of the wrong kind; nothing was read |
| 3 | NOT_FOUND | a named input does not exist |
| 4 | PERMISSION_DENIED | the operating system refused, or the tool's own confinement did |
| 5 | CONFLICT | state is not what the caller said it would be |
| 8 | PRECONDITION_FAILED | the answer is no, or a limit was reached |
| 9 | DRY_RUN | a --dry-run completed; planned_actions lists what an apply would do |
Rules (28)
Match on rule, not on the message. A retry repair is an argv that can be run as it stands, and never widens what the tool may touch.
| Rule | Exit | Repair | When |
|---|---|---|---|
args.unknown-flag | 2 | sometimes | a flag that is not in the tool's table |
args.missing-value | 2 | never | a flag that takes a value was given none |
args.bad-value | 2 | never | a flag's value is not of the flag's kind |
args.unexpected-value | 2 | never | a flag that takes no value was given one |
args.duplicate-flag | 2 | never | the same flag was given twice |
args.conflict | 2 | never | two flags that exclude each other were both given |
args.required-flag | 2 | never | a flag the tool needs was not given |
args.missing-operand | 2 | never | an operand the tool needs was not given |
args.too-many-operands | 2 | never | more operands than the tool takes |
path.empty | 2 | never | an empty path |
path.dotdot | 2 | sometimes | a path with a `..` component |
path.absolute | 2 | always | an absolute path where one relative to --root is wanted |
path.outside-root | 4 | never | an absolute path that is not under --root |
path.too-long | 2 | never | a path longer than 4096 bytes |
path.symlink | 4 | never | a path that runs through a symbolic link, which the tool does not follow |
io.not-found | 3 | never | a named input does not exist |
io.not-a-directory | 3 | never | a path runs through something that is not a directory |
io.is-a-directory | 2 | never | a directory where a file is wanted |
io.permission-denied | 4 | never | the operating system refused access |
io.read-failed | 1 | never | the operating system failed a read |
io.write-failed | 1 | never | the operating system failed a write, or a write was short |
io.rename-unsupported | 8 | never | the filesystem cannot refuse an existing name atomically, so move did not rename |
limit.input-too-large | 8 | sometimes | an input longer than --max-bytes |
precondition.required | 2 | never | a write that states no belief about the file |
precondition.hash-mismatch | 5 | never | the file's hash is not the one --if-sha256 named |
precondition.content-mismatch | 5 | never | the new content's hash is not the one --content-sha256 named |
conflict.exists | 5 | never | --create was given and the path exists |
conflict.locked | 5 | never | another writer holds the path's lock |
Evidence
each gate is a test in cancho-tools/tests/conformance, run by CI on the commit this binary was built from:
- M1 schema conformance
- M2 determinism
- M3 rule coverage and hint soundness
- M4 fault injection
- M5 differential against GNU
- M6 authority
- M7 mutation behaviour
- M8 confinement
- M9 memory flatness
Agent in the loop: not run: no claim that this tool is more agent-friendly than the incumbents is made (cancho docs/agent-toolbox.md section 7.2).