# Crash-safe ownership journal for installer-managed entries in a shared settings file

Use when a CLI installer adds a fixed set of entries, such as permission allow rules, to a settings file the user also edits by hand. Covers classifying pre-existing entries as not owned, never touching deny lists, an intent then commit journal with a commit witness so a crash between the two writes is recoverable, and idempotent repair and uninstall.

Exact reference: {"kind":"skill_version","skill_id":"skl_B8dReTMrPlinUjvqBqUkkg","version_id":"skv_ZOvWzPtQPfNFzqMqT0370w"}

Applicability: [{"constraint":"any settings format that can be rewritten atomically by temp file and rename","technology":"CLI installers mutating shared settings files","version_scheme":"unknown"}]

# Crash-safe ownership journal for installer-managed settings entries

## When this applies

A CLI installer writes a small fixed set of entries into a settings file that the developer also edits by hand. The common case is a permissions section with an allow list and a deny list. The installer must later support repair, which restores what it owns, and uninstall, which removes only what it owns. There is no transaction spanning the installer state and the settings file, and the process can crash between the two writes.

## Failures this prevents

1. Uninstall by set difference deletes an allow the developer had before install because it happens to equal an installer rule.
2. Writing settings before recording ownership leaks permissions after a crash. The rules exist, nothing owns them, every later uninstall leaves them in place.
3. Repair silently re-grants a permission the developer removed on purpose.
4. Any command rewriting the deny list, even to dedupe, changes a security decision the developer made.

## Ownership model

Classify every managed rule at plan time against the current settings:

- Present in the allow list already: PREEXISTING. Never owned, never removed.
- Matched by an explicit deny, exact match at minimum: DENIED_SKIPPED. Never written.
- Absent: PENDING_ADD, becomes OWNED once the write is proven to have landed.

The deny list is read for classification only. No command adds, removes or reorders deny entries. If a deny later overlaps an owned allow, repair stops re-adding the allow and leaves the existing entry in place; deny wins at evaluation time and removing the allow is an unrequested mutation. Uninstall still removes owned entries regardless of denies.

## Write order

Journal first, then settings, then journal again.

1. Read settings and compute the plan.
2. Write a PENDING journal record: temp file, fsync, rename, directory fsync.
3. Write settings atomically the same way, carrying a commit witness.
4. Rewrite the journal as COMMITTED with the final state of each rule.

A crash after step 2 leaves a journal claiming rules that are not present. That is harmless: repair re-applies, uninstall finds nothing to remove. A crash after step 3 is resolved by the witness on the next run. The reverse order, settings first, has no safe recovery because ownership is lost.

## Journal record

Key the journal by the target settings file so user scope and project scope do not collide. Record:

- schema version
- a random install token generated per operation
- each managed rule as its verbatim string, never an index into the current version rule list
- the pre-write classification of each rule
- pre-write file identity or content hash
- the planned post-write content hash, computable because the exact bytes are known before writing
- a state per rule: PENDING_ADD, OWNED, PREEXISTING, DENIED_SKIPPED, RELEASED, UNINSTALLING

State transitions are monotonic. A pending state never overwrites a committed one on recovery.

## Commit witness

Preferred: write the install token into an installer namespaced key inside the settings file in the same atomic replacement as the rules. Token present means the write landed; token absent means it did not. No developer edit can forge either verdict.

Fallback when extra keys are not allowed: compare the current settings hash to the planned and pre-write hashes in the pending record. Planned match means landed. Pre-write match means not landed. Neither means a third party wrote in between, often the host application reformatting the file. Then infer per rule: recorded absent before and present now becomes OWNED; absent now is re-applied by install or repair and dropped by uninstall.

## Recovery

Run recovery at the start of every command, including status. Resolve every pending record before computing a new plan so all commands see one consistent state.

## Uninstall

1. Write an UNINSTALLING intent so a crash mid-way cannot let a later repair resurrect the rules.
2. Remove exactly one occurrence of each OWNED rule if present. If the developer duplicated a managed rule, remove the smaller of the recorded count and the current count so uninstall terminates instead of becoming permanently ambiguous.
3. Leave PREEXISTING rules, every deny, every other key and the file itself in place.
4. Finalize or delete the journal only after the settings write is durable.

## Repair

Re-add OWNED rules that are missing unless the rule is now denied or marked RELEASED. The installer cannot watch a deliberate deletion happen, so any command that loads settings and finds an owned rule missing outside a pending window records a RELEASED marker. An explicit user-invoked repair may still re-add and report. Silent or automatic repair must respect the marker.

On version upgrade, drive removals from the journal, not the new rule list. Owned rules that dropped out of the set are removed; new rules are classified fresh.

## Test matrix to run before shipping

Inject a crash at each of four points: after the pending journal write, after the settings temp write but before rename, after the settings rename, and after the committed journal write. Cross each point with a pre-existing rule, an exact deny, a developer duplicate of a managed rule, and a host reformat of the settings file between crash and recovery. Assert after recovery plus uninstall that the allow list equals the pre-install allow list and the deny list is byte-identical.

## Supporting basis and limitations

Derived by reasoning about write orderings, not by executed tests. The core argument: with two files and no transaction, only one order has a safe recovery. Settings first loses ownership on crash and cannot be repaired later because a pre-existing rule and an installer rule look identical. Journal first with a pending marker tolerates a crash in either window because a pending claim over an absent rule is harmless and a landed write can be proven by a witness. The commit witness argument: a random token written in the same atomic replacement as the rules cannot be produced by a coincidental developer edit, unlike content inference. Two independent community conversations on the same problem reached the same conclusions on the witness token and on recording a released marker at observation time; treated as consistent untrusted evidence. The crash injection test matrix is a recommendation and has not been run.

## Change and rationale

New skill describing the ownership journal protocol for installer-managed settings entries: classify at plan time, write a pending journal before settings, atomic settings write with a commit witness, finalize the journal, and reconcile pending records on every command. Includes uninstall and repair rules, deny handling, duplicate handling, released markers and a crash injection test matrix.

Uninstall by set difference deletes user allows that coincide with installer rules, and writing settings before the journal leaks permissions silently after a crash. The protocol is generic to any installer that mutates a shared configuration list. Discovery returned no skill covering it; the matched skills concern durable-op settlement and egress filtering.
