Skip to content

feat(file): the removal family splits by blast radius; tolerance covers absence only - #637

Merged
David-Noble-at-work merged 2 commits into
developfrom
mutator-surface
Aug 24, 2026
Merged

David-Noble-at-work merged 2 commits into
developfrom
mutator-surface

Conversation

@David-Noble-at-work

Copy link
Copy Markdown
Contributor

Phase 5 of any-entry-claims.md. Closes #621. Feature: #616.

file.remove(target, prune, boundary, on_missing) and file.remove_all(...) now take the taxonomy
interface — identical signatures over one shared core, differing by the non-empty-directory guard alone.
file.unlink is removed: it and remove were one operation, differing only in the claim's type.
file.backup(source, backup_suffix) claims its source, the surface's last unclaimed mutation. Policy
parameters go last
throughout.

The removal family now splits by blast radius, never by kind — the standard library's split, arrived
at for the same reasons: os.Remove tries unlink then rmdir rather than asking the kind, which is why Go
never needed an Unlink, and os.RemoveAll opens by calling os.Remove.

The finding, and it was nearly data loss

TestExecute_RefusesReplacedLink pins a real safety property: a deployed link the user replaced with a
hand-written file must be refused, not deleted. That came free from unlink's kind check. With
remove kind-indifferent, the run succeeded — deleting the user's file.

Claiming the deployed kind was the right instinct but not sufficient on its own: writ decommission runs
under on_missing=ignore (the ruled posture, so a hand-removed target decommissions as recorded
history), and tolerance forgave the mismatch too.

Ruled: tolerance covers absence, never a kind mismatch. Ignore means the goal already holds —
coherent about an empty path, incoherent about a surprise. op.ErrClaimKindMismatch and the optional
op.KindMismatcher seam carry the distinction, consulted only after a claim has already failed to
verify, so it answers the one question a boolean predicate cannot: nothing is there or something else
is there
. A kind mismatch now stops under every policy.

Writ decommission claims the kind it deployed — a link claims a link — which is what makes its tolerance
safe, and which extends the protection to copies, a case the old unlink-based check never covered.

Tests moved rather than died

The "not a symlink" dispatch check became a claim pin: a link claim over a regular file mismatches,
and over an empty path it does not. The non-empty-directory test now claims the directory honestly.
test_file_unlink.star became test_file_remove_link.star, asserting what proves nothing was lost — the
link goes and its target stays.

New: test_judgment_remove_any_kind.star removes a regular file, an empty directory, a link, and —
through remove_all — a link to a populated tree, asserting that neither removal followed; a follow
would be unmistakable, since every file under it would vanish. Go pins cover the
mismatch-stops-under-Ignore rule at the executor and the mismatch-versus-absence distinction at the claim.

Gate: make test 103 ok / 0 fail; make vet clean under darwin, windows, and linux; gofmt -l clean.

@David-Noble-at-work David-Noble-at-work self-assigned this Aug 24, 2026
@David-Noble-at-work
David-Noble-at-work merged commit cfe461f into develop Aug 24, 2026
8 checks passed
@David-Noble-at-work
David-Noble-at-work deleted the mutator-surface branch August 24, 2026 06:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The rest of the mutator surface: remove, unlink retirement, remove_all, backup

1 participant