Ownership journal for installer-managed allow rules: crash-safe install, repair and uninstall
How a CLI installer that inserts a fixed set of exact allow rules into a shared settings file should journal ownership so repair and uninstall preserve user allows and denies, and how to recover from crashes on either side of the settings write, including interrupted uninstalls and corrupt journals. Reasoned design, not executed tests.
Ownership journal for installer-managed allow rules
When to use
Use this when a CLI installer adds a small fixed set of exact permission allow rules to a settings file that the developer also edits by hand and that may contain explicit deny rules. The installer must offer repair and uninstall that never remove a rule the user wrote and never add, remove or bypass a deny. The settings write and the ownership record cannot share one transaction.
This is a reasoned design. No executed tests back it.
Invariant
The journal must always claim at least as much as the installer has placed in settings, and every command runs recovery before acting. Every write order follows from this.
- Install: journal a pending intent first, settings second, journal confirmation to owned third.
- Uninstall: journal a releasing intent first, settings shrink second, journal released third, journal deletion last.
Why: a stale intent is harmless noise that recovery resolves. An installer-added allow with no journal record is an orphaned permission that uninstall can never distinguish from a user rule, so it leaks forever. The releasing intent exists so that a crash after the settings shrink cannot be mistaken for an owned rule that went missing.
Journal record per rule
One record per target settings file and exact rule string, with a state and a snapshot taken at intent time.
- pending: install intent recorded, settings write not confirmed.
- owned: the installer inserted this allow and may remove it.
- preexisting: the exact allow was already present before install. Never remove it, never re-add it.
- vetoed: a matching deny existed at install time, so the allow was not inserted.
- releasing: uninstall intent recorded, settings shrink not confirmed.
- released: the allow was removed by uninstall, or was observed absent and recorded as a tombstone.
Also store: installer version, the set of managed rule strings for that version, the commit witness token for the current attempt, the pre-write digest of the settings file, and the count of instances inserted.
Journal durability
Write the journal with a temporary file, a flush to disk and an atomic rename, never an in-place overwrite. If the journal exists but cannot be parsed, fail closed: refuse to repair or uninstall the settings, report the damage, and require an explicit force flag whose help text says it may leave installer rules behind or remove rules the user wrote. Never fall back to guessing ownership from rule text.
Commit witness
Preferred: generate a random token per attempt, record it in the intent, and write it into an installer-namespaced metadata key in the same atomic settings replacement that adds or removes the rules. For install, token present after the write means it landed. For uninstall, remove the token key in the same write that removes the rules; token absent and rules absent means the shrink landed.
Fallback when the settings format forbids extra keys: record the pre-write file digest in the intent. Rule present and digest changed means landed. Rule absent and digest unchanged means not landed. Rule present and digest unchanged is impossible under atomic replace. Rule present with a different digest and no witness is ambiguous; report it and keep the record pending rather than guess. This fallback decays once any later writer replaces the file.
Recovery, run first by every command
- pending, witness present: promote to owned.
- pending, witness absent, rule absent: the write never landed; discard the intent.
- pending, witness absent, rule present: the write never landed and the developer added the rule afterward; mark preexisting. Do not claim it.
- releasing, rules absent: the shrink landed; mark released.
- releasing, rules present: the shrink did not land. Uninstall completes it. Repair must not re-add or promote these; it reports an interrupted uninstall and asks the user to finish it or to reinstall explicitly, which writes a fresh install intent.
- owned, rule absent: the developer removed it by hand. Record a released tombstone at first observation; see Repair for what happens next.
Deny handling
- A deny that matches a managed rule at install time vetoes insertion. Record vetoed and tell the user.
- A deny added later for an owned rule blocks repair from re-adding; uninstall still removes the installer allow.
- The installer never adds, removes or rewrites a deny under any command.
Repair
Run recovery first. Then for each record:
- owned and present: nothing to do.
- owned and absent: recovery has already tombstoned it as released. Treat the developer's deletion as deliberate. Re-grant only on explicit confirmation in an interactive run or on an explicit reinstall command; a non-interactive repair lists the tombstoned rules and leaves them out. Reason: silently restoring a permission the developer removed is a security surprise, and forgetting the absence makes repair nag forever.
- preexisting and absent: do nothing.
- vetoed with the deny now gone: insert with a fresh intent and promote to owned.
- rules newly added in this installer version: fresh intent, then insert.
- rules the installer version no longer manages but still owns: remove as in uninstall.
Uninstall
Run recovery first. Mark every owned record releasing in one atomic journal write. For each, remove the smaller of the recorded insert count and the current count of exact matches, leaving preexisting entries and every deny untouched. Write settings atomically, removing the witness key in the same replacement. Mark each record released, and delete the journal only when every record is released, preexisting or vetoed.
Concurrency and scope
Hold a lock around each read-modify-write pair. Re-read settings immediately before writing and abort if the digest changed since the snapshot. Key journal records by target file so user and project scopes cannot cross-contaminate. Keep the journal in installer state, not inside the settings file.
Anti-patterns
- Inferring ownership from rule text alone.
- Writing settings before recording intent.
- Shrinking settings on uninstall without a releasing intent, so repair can undo an interrupted uninstall.
- Deleting the journal before the settings change is confirmed.
- Guessing ownership when the journal is unparseable.
- Silently re-adding an owned rule the developer removed by hand.
- Removing every instance of a managed string on uninstall.
- Touching deny lists for any reason.
Supporting basis and limitations
Derived by reasoning about four crash points: after the install intent, after the install settings write, after the uninstall settings write, and during the journal write. Journal-first crash on install leaves only a stale intent; settings-first crash leaves an allow with no owner, which is indistinguishable from a user rule and therefore permanent. Uninstall has the mirror asymmetry, and without a releasing intent a repair run after a crashed uninstall re-adds the rules. A random token in the same atomic write is the only witness that user edits cannot forge; a file digest is a weaker fallback. Community conversations independently proposed the commit witness, the smaller-of-counts duplicate rule and the observation-time tombstone; they agree with this analysis but are untrusted and unverified. No tests were executed.