Repository navigation
CONDITIONS
Baseline. This re-reviews 767d3948 against the PASS on ac4a047f (this PR), on arch 8640f85e. The checkout has no arch-map.md, and no findings.json was staged. The staged precedent text is cut off at 400 characters, so I rebuilt the baseline from git. I checked that since.diff equals git diff ac4a047f 767d3948, that pr.diff equals git diff 35074ab5 767d3948, and that ac4a047f is an ancestor of the head. The compare has two merges of main (1d15d927, 767d3948). git show --cc shows that neither merge has a resolution hunk, and neither touches crates/bep/src/keys.rs or crates/bep/src/main.rs. The one task commit is 2417b928. The ruling on ac4a047f stands for everything else.
Governs: Gatehouse §6.10, un-enrolled lifecycle (specs/authn-authz/gatehouse-spec.md line 839). The sealing key and the CA key are "never regenerated silently". An explicit regeneration is a replacement: "outstanding members sealed to the old key are dead and running boxes are re-created to re-mint". BEP-060 (spec 24, not in the arch checkout) puts replacement on an explicit operator command.
Ruling. 2417b928 has two parts. (1) ReplaceError::RemoveNoKey reports a failed cleanup for a role with no key held as "no key", not "old key still in use". It conforms. (2) recover_interrupted() now returns Interrupted::Unnamed for PublishedKid::Absent with a lone Slot::Old key. replace_outcome() then refuses before any cleanup, and the message names no command. In the same state load_with() refuses the start as a lost key, and StartError::Unusable prints "Run bep --replace-key sealing to make a new one." The commit's own test an_old_key_left_alone_with_no_identity_is_kept reaches this state. So the start names a command that always refuses, and no bep command leaves the state. The commit justifies the refusal with "the key left aside can be the key in use". Line 839 answers that case: on an explicit replacement, members sealed to the old key are dead. That is the reason the replacement is explicit. The refusal protects nothing the design keeps, and it removes the one recovery the design gives. The Unread refusal is different and stays: there the operator can correct the identity file and run the command again.
Precedent. C1 brings back the Absent behaviour that the PASS on ac4a047f approved. A start keeps the lone key aside, and an explicit replacement removes it. This is not a departure. The a015b288 CONDITIONS rule (a replaced key never comes back under the label) stays satisfied, because C1 removes the key and does not restore it.
Not checked. I did not compile or run the tests. The macOS keychain relabel is the same as on ac4a047f and is not in this compare.
Conditions
- C1:
crates/bep/src/keys.rsrecover_interrupted(): thePublishedKid::Absentarm returnsOk(false)again, as onac4a047f, in place ofErr(Interrupted::Unnamed). Thenload_with()still keeps the loneSlot::Oldkey aside and removes nothing (itsOk(false)arm maps toOk(None)), andreplace_outcome()removes that key in its cleanup and makes a new one, because the operator asked for a replacement. RemoveInterrupted::Unnamed,ReplaceError::Unnamed, theReplaceError::Unnamedarm ofreplace_failure()incrates/bep/src/main.rs, and the testreplace_key_names_a_key_set_aside_that_no_identity_names. Changean_old_key_left_alone_with_no_identity_is_keptso that it asserts:load_with(.., |_| PublishedKid::Absent)refuses withmissing == [Role::Sealing]and keeps theSlot::Oldkey, and thenreplace_outcome(.., &PublishedKid::Absent)returns a new key underSlot::Held, withSlot::Oldempty. Rename it to match. KeepReplaceError::RemoveNoKeyand its message and tests from2417b928. They conform.
Ruled at 767d394 against gominimal/arch@8640f85e193d by claude-opus-5-5; arch_rulings 688, job ec422452-76e8-4e84-8795-b69469a56401.