feat(hub): v0.57.0 reinstall-of-existing-customer arc — claim/offsite/escrow continuity
F2 claim re-issue on clean-slate re-enroll (ReissueForReenroll, host-enroll mint path, single-bump, reset code; hub never stores the password so fork B). F3 offsite re-issue on re-enroll (ReissueOffsiteForCustomer, same machinery as the manual button). 2.3 escrow honesty (red-proofed): re-issuing offsite marks the escrow stale (MarkEscrowStale), withholds the mismatched restic hash from auto-confirm, DR checklist shows stale not done. Events: claim_reissued_reenroll / offsite_reissued / escrow_stale. Controller + scripts unchanged (source contradicted both premises): the controller reads escrow prereqs live from the agent; the installer can't know the descriptor-provisioned storage id. F4 root fix is agent-side -> ROADMAP R-22; demo unblocked live (Part 0 ACL grant). VALIDATION doc F2 erratum + F3/F4 dispositions. Green gate + Scenario-C red-proof pass.
This commit is contained in:
@@ -159,6 +159,33 @@ func (e *Engine) RequestReset(cc *store.CustomerConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ReissueForReenroll handles the clean-slate reinstall of a CLAIMED customer (F2, v0.57.0): the box
|
||||
// (host + in-guest controller) was wiped and re-enrolls, so the fresh controller has NO password
|
||||
// while the hub-side claim is set. This rotates + emails a RESET code (rides Resend's rotation
|
||||
// semantics — a single generation bump, single active code) so the customer gets a fresh code
|
||||
// automatically instead of hunting for the manual "request new code" button. The new hash reaches
|
||||
// the fresh controller through the existing report ACK.
|
||||
//
|
||||
// No-op for an UNCLAIMED customer — that is the first-provision path where EnsureIssued already
|
||||
// owns the first code; re-enrolling before the first claim must NOT rotate. The CALLER guarantees
|
||||
// single-shot by invoking this only on a genuinely fresh host record (the host-enroll mint path,
|
||||
// which fires exactly once per reinstall). Returns (generation, reissued, error).
|
||||
func (e *Engine) ReissueForReenroll(cc *store.CustomerConfig) (gen int, reissued bool, err error) {
|
||||
cs, err := e.Store.GetClaim(cc.CustomerID)
|
||||
if err != nil {
|
||||
return 0, false, fmt.Errorf("claim: reading state: %w", err)
|
||||
}
|
||||
if cs == nil || !cs.Claimed() {
|
||||
return 0, false, nil // unclaimed → first-provision path; nothing to re-issue
|
||||
}
|
||||
gen, err = e.rotateAndSend(cc, EmailReset)
|
||||
if err != nil {
|
||||
return gen, true, err // reissued=true so the caller records the attempt even on email failure
|
||||
}
|
||||
e.logf("[INFO] [claim] reset code re-issued (gen %d) for %s on box re-enrollment (clean-slate reinstall)", gen, cc.CustomerID)
|
||||
return gen, true, nil
|
||||
}
|
||||
|
||||
// MarkClaimed records a controller-reported successful claim and sends the one-time confirmation
|
||||
// email on the unclaimed→claimed transition (idempotent — repeated reports are no-ops).
|
||||
func (e *Engine) MarkClaimed(cc *store.CustomerConfig) error {
|
||||
|
||||
Reference in New Issue
Block a user