Design question: an installer adds a small fixed set of exact permission allow rules to a user-editable settings document, and must later support repair and uninstall without clobbering rules the user already had or explicit deny entries the user added. The core difficulty is provenance. The settings document alone cannot say who inserted a given rule, so a separate ownership journal is needed, recording per rule: which document scope, which list, the exact rule string, whether the rule was already observed present before mutation, and a commit state.
The interesting failure window is a crash between persisting the journal and updating settings. Ordering journal-first yields entries claiming rules that may never have landed; ordering settings-first yields orphan rules with no record at all. Known reasoning so far: write-ahead ordering with a pending-then-committed two-phase marker, atomic replace for both files, and a durability barrier before mutating settings. Recovery must classify each pending entry by whether the rule is now present and whether it was present before.
Open questions: how to resolve genuine ambiguity when a rule is present, was not present before, and the commit marker is missing, since the user could have added it concurrently; whether repair should re-assert a rule the user deliberately deleted or honor a tombstone; and whether a deny that shadows an owned allow should ever be treated as a repairable defect. Concurrent external edits between read and write are a further constraint.
Related prior conversations Resolution for the case where the user edits an installed entry in place and also edits unrelated regions before removal. Invariant reasoning; nothing executed.
The gating comparison must be narrow: exact recorded value, in the recorded collection, at the recorded scope. Any document-level freshness check may annotate the report but must never gate a mutation. The reason is that unrelated edits are the normal case, not the exceptional one, so a document-level check is stale on essentially every real removal. Gating on it both blocks legitimate cleanup for irrelevant reasons and destroys its own signal, because a confirmation that fires every time is accepted reflexively and carries no information when the rare genuine case arrives.
In-place modification then needs no detection machinery. Under value identity, editing an entry is deleting one value and adding a different one, so the recorded value reads as absent and the different value carries no claim. This bounds honest reporting: with list members identified by value there is no path distinct from the value, so edited and deleted are indistinguishable. Report that the recorded value is not present and may have been edited or deleted; do not claim to have detected modification. Similarity may add an advisory line and must never widen removal.
Key insight for crash recovery: removal's target state is authorship-free. Install's target embeds an authorship claim and is not re-derivable after a crash, but removal's target is simply absence at a scope, which anyone can observe. So a dangling removal intent whose recorded value is now absent needs no decision at all — our write landing and a hand deletion prescribe the same action, so the claim closes. Only the branch where the intent is pending and the recorded value is present stays undecidable, since an editor buffer loaded before the run can reintroduce the identical line after a successful write. That branch gets an explicit confirmation naming the specific entry, and it is rare enough to keep its signal.
Outcomes that must stay distinct: effect gone and claim retired having acted; effect present but attribution lost; and inability to observe. The third must never collapse into either of the first two, or a transient parse failure or lock timeout silently becomes permanent non-removal. On removal there is no later run to retry into, so it escalates to a bounded retry then an explicit failure naming what remains.
Also settled: prune an emptied container only on a container claim, never on emptiness, since emptiness is a state rather than a provenance; write nothing when a scope's plan is empty, because a no-op write can lose a concurrent save and churns the state later recoveries read; and per-scope critical sections make an interrupted run resumable rather than restartable, with no plan carried across a re-read.
Held with less confidence, as a harm judgement rather than an invariant: narrow comparison will delete a byte-identical entry that a third party introduced after the user hand-deleted ours. I accept a rare recoverable loss of one permission entry over a common permanent failure to clean up. Also unresolved and uncomfortable: correct cleanup must leave the never-adopt markers behind forever, so a successful removal necessarily leaves something that looks like residue, and a user who tidies it away silently restores full adoption behaviour.
An independent adversarial pass overturned two positions I stated confidently earlier in this thread. Recording the corrections; reasoning on both sides, nothing executed by either party.
Retraction one, and it is the important one. I argued that a provisional claim recorded absent-before, found present now after a crash, should be promoted to owned, on the grounds that the only competing history is someone typing the identical string inside a narrow crash window. That is false once the compare-and-swap retry loop is taken into account. A failed compare-and-swap is positive evidence that another writer is active right now. If the provisional record is rewritten per attempt, then between a failed attempt and the next one the record says absent-before while a concurrent writer may have introduced the identical value. Crash there and recovery promotes a value we never wrote, which a later removal then deletes without asking. The window is not rare, it is correlated with contention. Two fixes: a failed attempt must invalidate its provisional record before re-reading, and the ambiguous state must route to the existing present-but-unattributed outcome with per-entry confirmation rather than silent promotion.
Retraction two, a false dilemma I built on top of that. I justified promotion by claiming the alternative was permanent residue removable only by hand-editing the bookkeeping. My own outcome vocabulary already contained the answer: present, attribution lost, mutate only on explicit per-entry confirmation. That resolves the state without either silent deletion or unremovability, so the dilemma never existed. It also removes a genuine self-inconsistency, since promotion derived authorship from file content, which I had separately declared unrecoverable in principle.
Three further defects found that I had missed. First, I over-generalized the rule that document-level staleness must never gate a mutation. That rule is about which entry may be removed. Applied to the write itself it forbids the compare-and-swap that stops a whole-document replacement from clobbering a concurrent save, and I had specified compare-and-swap only on the install path, leaving removal as a plain atomic replace. Read, human saves unrelated edits, rename lands, edits gone. Staleness must never authorize removing an entry, and must always gate replacing the document.
Second, per-scope critical sections plus a single bookkeeping document is unsound, exactly as raised elsewhere in this problem space. Two runs holding disjoint scope locks both read-modify-write the shared record and one claim or marker is lost while its settings write landed.
Third, the never-adopt marker is unrepresentable on the install path. Prior state has two values, present and absent, with no way to encode present-but-marked. Install after a completed removal followed by a version-control revert therefore reaches a state where the tool can neither adopt, nor claim, nor prompt. The non-destructive resolution is that install observing a marked value present must neither adopt nor claim it, leave it alone, and report it as present but unattributed. The same non-interactive gap applies to removal: a confirmation-required branch has no answer in an unattended context, so it needs a defined default of preserve-and-fail plus a release valve that names the specific entry it cannot prove it owns, never a generic force flag.
What survived the pass: denies never touched with shadowing recomputed at report time rather than stored as a prior state; narrow entry-selection comparison; claim-driven rather than desired-set-driven removal; taking the lock even when the plan is empty; and the authorship-free character of removal's target state with respect to the settings document, where the failure was in the bookkeeping terminal state rather than the observation.