Problem. An installer adds a small fixed set of exact permission allow rules to a developer settings file the user also edits by hand. Those rules may already exist because the user added them, or because an earlier install did. Repair must converge without duplicating entries, and uninstall must remove only what the installer introduced. User deny entries must never be weakened or removed.
Constraints. Settings is a shared mutable document with no transaction store. Rule identity is textual, so two identical strings cannot be told apart by content, and provenance must be recorded out of band. Install may run repeatedly, and uninstall may run after the file has drifted.
Design under consideration. Keep a separate ownership journal recording, per rule, whether the installer created the entry or only observed a pre-existing one, plus a generation marker. Write journal intent before mutating settings. On uninstall remove only entries the journal marks installer-created and still matching what was written. Never touch deny entries.
Central unknown. The process can crash between journaling intent and updating settings, or after updating settings but before marking the journal committed. That gives two failure shapes: a claim of ownership for a rule never written, and a written rule with no ownership record. The first risks deleting a user rule later; the second risks orphans uninstall never cleans up.
Open questions. Which write ordering and which reconciliation rule is safest when journal and settings disagree. Whether an unverified ownership claim should decay to not-owned so ambiguity resolves toward leaving user data alone. Whether a fingerprint captured at write time can distinguish an installer rule from an identical user rule added later. What repair should do with a rule that is present but unclaimed.
Seeking prior experience with shared-config ownership tracking that survives partial writes without ever deleting user entries.
Resolution of the open questions, reached by reasoning. No implementation was built and no tests or crash-injection runs were executed. Existing community guidance on this pattern was consulted and agrees on the external journal, write-ahead ordering, patching instead of restoring, compare before remove, and never editing denies. The points below are the ones I did not find already stated.
- Why journal first is the safe ordering, stated as an asymmetry of harm. The two crash shapes are not equally bad. Journal first and settings never land gives a claim with no rule, which the rule that an absent entry is never owned retires harmlessly at the next run. Settings first and journal never lands gives a rule with no claim, which is an orphaned grant that uninstall can never remove, because inferring ownership from content is exactly what the design forbids. One failure costs a discarded bookkeeping row; the other silently leaks a standing permission. So the pending record is written and flushed first, settings second, commit third.
- One unifying rule covers both directions of ambiguity. The journal may only ever authorize a change to the settings file; it may never infer one. Ambiguity withdraws authorization. For a pending add, withdrawing authorization means dropping the claim, so the entry becomes user owned. For a pending remove, it means not removing. In both cases the action on the file is to do nothing. That is why the same policy can be applied in both directions without a special case, and it is also why an unverified claim should decay toward not owned rather than toward owned.
- The unavoidable cost of that rule is grant leakage, and reporting is not a sufficient answer on its own. If the journal always yields, some entries the installer really did add end up unowned forever. The escape hatch should be an explicit adopt operation, user invoked, that transfers an unclaimed but matching entry back into the journal as owned. Explicit opt in is the only safe way to recover an orphan, because automatic adoption is indistinguishable from stealing a user entry.
- A write time fingerprint cannot establish authorship, only integrity. For an exact string rule the fingerprint is a function of the string, so an identical user added rule produces an identical fingerprint. The fingerprint is a sound negative test, meaning a changed value proves ownership has lapsed, but it is never a positive test of who wrote the entry. Authorship has to come from the journal plus a happens before argument, namely that the value was absent at snapshot time and our write landed.
- Whole batch landing is one bit, not a per entry fact. When settings are replaced by a single atomic rename, the write either landed or it did not. Per entry observations are only evidence for inferring that one bit. With a set of about six planned entries, any single visible planned change proves the batch landed, and a mixed result proves the batch landed and was then partly edited by the user. The genuinely undecidable case is narrow: it needs every planned entry to be absent afterwards, which is also the case where doing nothing is correct anyway. Note also that matching the expected result hash is a sound positive test, but failing to match it proves nothing, because the host application may reformat or rewrite the file on its own.
- Deny is a standing veto, so it must be re-evaluated on every run rather than frozen at install time. Existing guidance treats a later added deny at uninstall time. The gap is repair. If the user adds a deny that covers an entry the installer owns, repair should remove the owned allow and keep the deny, rather than leaving a contradictory pair in place. This stays inside the rule of touching only what you own, and it respects the stronger and more recent user intent. Denies themselves are never added, edited, reordered or removed.
- Prefer set semantics over multiplicity for duplicates. An alternative design adds the installer copy unconditionally and records how many copies existed before, so uninstall removes one copy. That is fragile, because a host that deduplicates a permission list destroys the count and the installer then removes a user copy. Observing a pre-existing value and recording it as not owned, without writing a second copy, has no such failure mode.
- Bind the journal to a resolved target, not to a name. A developer can have more than one settings scope, and a configuration directory can be moved or symlinked. Each journal record should carry the resolved absolute target and the scope it describes, and the installer should refuse to act and report when today's resolution does not match the record. Otherwise a journal can authorize deletions in a file it never described.
- What repair should do with an entry that is present but unclaimed. Nothing to the file. It is either a user entry that happens to coincide or an orphan from a lost journal, and the two are indistinguishable by construction. Classify as user owned, report it, and leave adoption to the explicit operation in point 3.
- A generation marker earns its place at upgrade, not at install. When the managed set changes between installer versions, entries dropped from the set but still marked owned should be removed, because they are ours and no longer wanted. Without a generation marker the installer cannot tell a retired managed entry from an entry it never managed.
Suggested verification, none of which has been run: crash injection between the journal flush and the rename and between the rename and the commit, each repeated with and without a later user edit; a second install over an existing install; a deny added after install, checked at both repair and uninstall; a deleted journal with the entries still present; and a host driven reformat of the settings file between runs, to confirm that a failed result hash match never by itself triggers a removal.
Uninstall refinement. A manual edit to an owned rule transfers ownership to the developer: remove an owned effect only when its recorded value and recorded occurrence count are both still present, otherwise change nothing. Unrelated settings survive because uninstall patches the live document instead of restoring a snapshot, and refuses to rewrite a file it cannot reproduce byte for byte. Each entry left behind becomes a reported conflict naming the recorded value, the current state and the resulting owner; that report lives in the pending record so an interrupted run can still explain itself. Resumption compares recorded counts with current ones: the expected count means the write landed and only cleanup remains, the original count means nothing landed and the plan is rebuilt, any other count means a concurrent edit, which withdraws authority to remove.