Recipe: Safe File Access and Atomic Write¶
1. Scenario and Non-Scenario¶
Applicable: you need to constrain, within Node, an externally-provided path to within some root directory, and atomically write an ordinary file to a contained path, rolling back on failure without leaving residue.
Not applicable: needs to put file-selection business rules into Foundation (business rules are owned by the caller); needs to write to an uncontained path.
2. Architecture Choice and Capability ID¶
Preferred capabilities: foundation.harness.path-containment (path classification and contained resolution) and foundation.harness.atomic-write (atomic write within contained paths). Both implement only the mechanism, owning no semantics.
3. Preconditions and Trust Boundary¶
- The caller provides an explicit root directory (containment boundary).
- Trust boundary: all resolution results must fall within the root directory; out-of-bounds, symlink escape, and realpath escape are all refused.
4. Minimal Code¶
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { resolveContained, writeFileAtomic } from "skill-family-harness-node";
const root = mkdtempSync(join(tmpdir(), "sf-safe-"));
const target = resolveContained(root, "nested/config.json"); // contained absolute path
await writeFileAtomic(target, JSON.stringify({ ok: true }));
The code above shows the basic order of first containing the path, then atomically writing; writeFileAtomic uses temp-file + fsync + rename, rolling back on failure.
5. Expected Output and Evidence¶
resolveContained returns an absolute path within the root; writeFileAtomic writes the completed file, whose bytes are consistent with the input. Evidence: packages/skill-family-harness-node/test/containment.test.mjs and test/atomic.test.mjs.
6. Safety / Failure Negative Case¶
// Negative case: attempting to escape the root -> SFC2004 (path-traversal)
import { resolveContained } from "skill-family-harness-node";
resolveContained(root, "../escape.json"); // throws HarnessError, details.kind = "path-traversal"
Out-of-bounds, symlink-escape, and realpath-escape all map to SFC2004 with a stable details.kind; atomic write throws on an out-of-bounds path or illegal data type and rolls back the temp file.
7. Copy-Paste Verification Command¶
node --test packages/skill-family-harness-node/test/containment.test.mjs \
packages/skill-family-harness-node/test/atomic.test.mjs
8. Business Logic the Caller Continues to Own¶
- Which paths are business-allowed selection rules (not within Foundation).
- The business correctness of the written content.
9. Upgrade and Rollback Notes¶
- Path containment and atomic write are contained mechanisms; on failure they roll back without leaving half-products, so no external rollback is needed.
- Upgrade follows the
skill-family-harness-nodeversion;HARNESS_EXCLUSIONSintroduces no remote or git writes.