274 lines
14 KiB
Markdown
274 lines
14 KiB
Markdown
# File Operations Simplification
|
|
|
|
## Status
|
|
|
|
Accepted feature roadmap. This document defines the desired filesystem policy
|
|
and target state. It is not an implementation plan and does not describe
|
|
implemented behavior. It refines the filesystem conclusions in
|
|
[`audit.md`](audit.md).
|
|
|
|
## Goal
|
|
|
|
Make Narratio's filesystem code proportionate to its real operating model:
|
|
single-invocation work under a session lock in a trusted, group-owned workspace
|
|
containing intentionally shareable D&D material. Use the Go standard library
|
|
directly for ordinary filesystem work and retain custom code only where it
|
|
expresses a concrete Narratio requirement.
|
|
|
|
The change should materially reduce production code, platform-specific code,
|
|
test machinery, full-tree copying, and the number of filesystem guarantees that
|
|
maintainers must preserve.
|
|
|
|
## Underlying Issue
|
|
|
|
`internal/fileops` has accumulated a general confined-filesystem abstraction,
|
|
two directory-promotion implementations, adversarial race hooks and inode
|
|
identity rechecks, handle-relative no-replace promotion, explicit file and
|
|
directory syncing, platform capability errors, universal symlink and regular-
|
|
file restrictions, and tests for each mechanism.
|
|
|
|
Those mechanisms are internally coherent, but they solve a stronger problem
|
|
than Narratio has. Narratio does not operate on a hostile multi-writer tree, and
|
|
its ordinary workspace data is not private. The session lock prevents normal
|
|
concurrent mutation by Narratio, while run IDs make durable extract destinations
|
|
unique. Treating every current mechanism as a permanent policy has therefore
|
|
created a self-reinforcing contract: implementation detail drives tests and
|
|
documentation, which in turn appear to require the implementation detail.
|
|
|
|
This roadmap resets that contract around required outcomes. It does not assume
|
|
that a replacement must preserve identical mechanics or failure behavior.
|
|
|
|
## Policy Decisions
|
|
|
|
### Operating and threat model
|
|
|
|
- Campaign and session workspaces are trusted operational inputs, not hostile
|
|
filesystems. Narratio does not defend ordinary reads and writes against an
|
|
attacker racing directory entries during an invocation.
|
|
- Workspace transcripts, artifacts, manifests, logs, and configuration are
|
|
intended to be shareable. World readability is acceptable. Group-owned,
|
|
group-writable operation remains a requirement.
|
|
- API keys are the exception. Narratio must not write credentials into runtime
|
|
artifacts or diagnostics, and any filesystem path that can contain API-key
|
|
material retains appropriately strict handling.
|
|
- Narratio must still prevent accidental destructive action or restore outside
|
|
the selected application-owned root. Trusted input does not justify an
|
|
unbounded delete or an escaped restore target.
|
|
|
|
### Guarantees to retain
|
|
|
|
- Corruption-sensitive single-file updates use same-directory temporary files
|
|
and replacement so readers do not observe partially written content. This
|
|
applies to such state as manifests and generated configuration; it is an
|
|
atomic-visibility requirement, not a power-loss durability promise.
|
|
- Files downloaded from remote storage are completely received and validated
|
|
before they become the canonical local file.
|
|
- Adapter and configuration input remains bounded by centralized, discoverable
|
|
constants. Limits remain deliberately far above expected workloads and
|
|
produce unmistakable errors naming both the limit and the enforcing adapter.
|
|
- Workspace files and directories preserve the repository's group-owned,
|
|
group-writable mode policy, including after temporary-file replacement or
|
|
directory rename.
|
|
- Cleanup and restore operate relative to an explicitly opened root and reject
|
|
targets outside that scope. Recursive removal uses the standard-library root
|
|
primitive available at the maintained Go baseline.
|
|
- Restore treats leaf or ancestor symlinks as non-regular conflicts rather than
|
|
following them to classify or overwrite a target. This is a restore boundary,
|
|
not a universal rule for ordinary workspace access.
|
|
- Staging cleanup is limited to paths that Narratio created and verified as
|
|
descendants of the applicable invocation or session workspace.
|
|
|
|
### Guarantees to retire
|
|
|
|
- Narratio does not promise that a successful write or directory install has
|
|
been forced through every operating-system and storage cache before return.
|
|
File and directory `fsync` calls, directory-sync capability probes, and
|
|
unsupported-durability errors are not part of the target contract.
|
|
- Narratio does not defend against a concurrent adversary replacing a file or
|
|
directory after it has been inspected. Inode identity snapshots, `SameFile`
|
|
rechecks, and race-injection hooks are not part of the target contract.
|
|
- Ordinary workspace operations do not universally reject symlinks or require
|
|
every source to be a regular file. Components may validate a concrete external
|
|
contract where needed, but there is no package-wide no-follow policy.
|
|
- Directory installation does not require a custom atomic no-replace primitive
|
|
or an exact cross-platform race guarantee. The session lock, absent-destination
|
|
check, and unique run ID are the intended concurrency controls.
|
|
- “Durable” in Narratio documentation means managed persistent application
|
|
state. It does not mean guaranteed survival of every acknowledged update
|
|
across immediate power loss.
|
|
|
|
## Required Changes
|
|
|
|
### Narrow the filesystem API
|
|
|
|
Replace the broad `internal/fileops` facade with direct `os`, `io`, and
|
|
`path/filepath` operations at ordinary call sites. Retain only small helpers
|
|
that enforce a shared product rule rather than wrapping the standard library.
|
|
Expected shared concerns are:
|
|
|
|
- corruption-resistant single-file replacement with explicit final mode;
|
|
- bounded reads or copies with centralized limits and contextual errors;
|
|
- application workspace file and directory modes; and
|
|
- narrowly scoped root-relative cleanup and restore operations.
|
|
|
|
The target is not a one-for-one rewrite of existing exported functions. Remove
|
|
unused or single-purpose wrappers when the calling package can express the
|
|
operation clearly with the standard library. Keep platform-specific code only
|
|
if it is necessary for the small atomic-file replacement helper to provide the
|
|
supported cross-platform behavior.
|
|
|
|
Do not add a general filesystem framework. Same-directory atomic replacement is
|
|
a generic abstraction, but a small local helper is the preferred target unless
|
|
implementation research identifies a maintained cross-platform dependency that
|
|
removes more code than it adds and preserves the required mode behavior.
|
|
|
|
### Simplify ordinary reads, writes, and copies
|
|
|
|
Use ordinary standard-library path operations for trusted workspace data.
|
|
Preserve contextual errors and the existing centralized safety limits, but do
|
|
not route every access through an `os.Root`, reopen files to prove identity, or
|
|
reject symlinks merely because the current facade does so.
|
|
|
|
Callers that consume a defined external format may still validate type, size,
|
|
checksum, structure, or placement because their own contract requires it. Those
|
|
checks should live with the consumer and must not reconstitute a universal
|
|
FileOps policy indirectly.
|
|
|
|
### Replace file updates without power-loss machinery
|
|
|
|
Use a minimal same-directory temporary-file flow for corruption-sensitive file
|
|
updates: create, write, close, apply the intended mode, and replace the canonical
|
|
path. Ensure temporary files are removed after pre-commit failures. Preserve
|
|
context cancellation checks where a caller can still abandon an update before
|
|
replacement.
|
|
|
|
Remove file and parent-directory syncing, sync-related platform probes and
|
|
sentinel errors, and tests that exist only to assert power-loss guarantees. If a
|
|
small operating-system-specific replace shim is still required, keep it private
|
|
to this helper and test its observable replacement behavior rather than its
|
|
internal syscall sequence.
|
|
|
|
### Simplify download installation
|
|
|
|
Keep remote downloads in a temporary file until transfer, size, checksum, and
|
|
format validation succeeds. Install the validated file using the same minimal
|
|
replacement helper or a direct rename when the destination is known absent.
|
|
Remove standalone download-install APIs that have no production caller and
|
|
avoid duplicating replacement policy between download and ordinary writes.
|
|
|
|
### Replace Notarius directory promotion
|
|
|
|
Have Notarius write its output bundle to a unique staging directory within the
|
|
invocation workspace. Validate that the reported bundle is inside the requested
|
|
output root and satisfies the Notarius integration contract. Normalize required
|
|
workspace modes, confirm that the unique durable destination is absent, and use
|
|
`os.Rename` to move the validated directory directly to that destination.
|
|
|
|
Delete the production and test-only directory tree-copy implementations,
|
|
promotion hooks, handle-relative no-replace code, directory syncing, and their
|
|
platform files. A destination collision must return a clear ordinary error; it
|
|
does not require adversarial race recovery.
|
|
|
|
The successfully renamed bundle becomes the sole retained artifact tree. The
|
|
pre-promotion Notarius tree is not duplicated for diagnostics. Existing receipt
|
|
and log records remain the troubleshooting evidence, and failed staging trees
|
|
are cleaned up only after their scope is verified.
|
|
|
|
### Keep destructive and restore operations root-scoped
|
|
|
|
Retain `os.Root` at boundaries where containment is the requirement. Use the
|
|
maintained Go version's root-relative recursive removal for cleanup. Continue to
|
|
require proper-descendant relationships and application-owned eligibility
|
|
before destructive operations.
|
|
|
|
Rework restore classification and installation around a session root. Inspect
|
|
targets without following leaf or ancestor symlinks, classify such targets as
|
|
non-regular conflicts in both force and non-force plans, and hash only a target
|
|
verified within that root. Do not add inode-stability or hostile-race rechecks.
|
|
|
|
### Preserve group-owned workspace operation and protect secrets
|
|
|
|
Centralize the intended workspace modes in one discoverable location and apply
|
|
them consistently to created directories, ordinary files, atomic replacements,
|
|
downloads, and renamed Notarius bundles. Do not tighten general workspace
|
|
permissions on the assumption that transcripts or artifacts are sensitive.
|
|
|
|
Audit API-key ingress separately from general workspace operations. Secret
|
|
values must remain out of files unless an existing explicit credential contract
|
|
requires a file, must never enter logs or errors, and must not inherit permissive
|
|
workspace modes. The simplification must not weaken existing credential
|
|
redaction or environment handling.
|
|
|
|
### Align documentation and tests with outcomes
|
|
|
|
Revise the canonical architecture, operations, troubleshooting, and focused
|
|
internal documents to remove power-loss durability, universal confinement,
|
|
universal no-follow, and atomic directory-promotion claims. Keep volatile modes,
|
|
paths, limits, and operational behavior in their existing canonical owners
|
|
rather than duplicating them across documents.
|
|
|
|
Delete tests for removed algorithms, syscall ordering, race hooks, directory-
|
|
sync capability behavior, and guarantees no longer offered. Retain or add
|
|
focused tests for:
|
|
|
|
- successful and failed atomic file replacement on every supported platform;
|
|
- cleanup of temporary files before replacement;
|
|
- bounded input and clear adapter-specific limit errors;
|
|
- group-owned file and directory modes;
|
|
- validated download installation;
|
|
- validated Notarius bundle rename, collision, and cleanup behavior;
|
|
- root-scoped cleanup and refusal of escaped destructive targets;
|
|
- restore handling of leaf and ancestor symlinks; and
|
|
- API-key redaction and any credential-file permission contract.
|
|
|
|
Tests should assert externally meaningful results and failure boundaries, not
|
|
recreate the removed implementation through new private hooks.
|
|
|
|
## Compatibility and Migration
|
|
|
|
This work changes internal guarantees rather than CLI, configuration, manifest,
|
|
or external artifact schemas. Existing valid workspaces should remain usable.
|
|
Users may observe fewer failures on filesystems that cannot sync directories,
|
|
faster extract installation, less temporary disk consumption, and ordinary
|
|
symlink behavior outside explicitly scoped restore and destructive operations.
|
|
|
|
The removal of the duplicate pre-promotion Notarius output tree is an intentional
|
|
operational change and must be reflected in the operations and troubleshooting
|
|
documentation. No compatibility layer should preserve the old FileOps API solely
|
|
for internal callers; migrate callers and delete obsolete surfaces together.
|
|
|
|
## Target End State
|
|
|
|
At completion:
|
|
|
|
- ordinary workspace file operations are clear standard-library code located
|
|
near their domain owners;
|
|
- shared filesystem code is small and limited to atomic file replacement,
|
|
bounded I/O and modes, plus explicitly root-scoped destructive or restore
|
|
helpers;
|
|
- directory promotion, tree-copy duplication, adversarial race defenses,
|
|
directory syncing, and unsupported-durability machinery no longer exist;
|
|
- extract validates and renames one Notarius staging bundle without copying the
|
|
complete tree or retaining a duplicate source tree;
|
|
- manifests and other corruption-sensitive files are never intentionally
|
|
exposed partially, without claiming power-loss durability;
|
|
- accidental out-of-root deletion and restore remain prevented;
|
|
- group-owned workspaces remain first-class, and API keys remain the only data
|
|
requiring secret-specific filesystem treatment;
|
|
- tests cover the reduced observable contract rather than removed mechanisms;
|
|
and
|
|
- canonical documentation accurately distinguishes persistent application state,
|
|
atomic visibility, trusted-workspace assumptions, and destructive-operation
|
|
confinement.
|
|
|
|
## Out of Scope
|
|
|
|
- Changing CLI commands, configuration schemas, artifact schemas, or manifest
|
|
formats.
|
|
- Adding multi-process writes to one session or removing the session lock.
|
|
- Defending trusted workspaces against malicious concurrent mutation.
|
|
- Adding encryption or restrictive permissions for shareable campaign data.
|
|
- Redesigning remote publish commit ordering or object-store concurrency.
|
|
- Adding user-configurable size limits in this feature; constants should remain
|
|
easy to promote to configuration later.
|