v0.290.0: the clean-up guard skips same-day superseded young snapshots instead of refusing (R-824), refuses above the weekly cap; a due set-aside deletion is handed to the hub's 7-day wait (decision 74, R-823)
gates / gates (push) Successful in 27s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-04 07:27:38 +02:00
parent c4bf730637
commit c1a73b24b3
10 changed files with 309 additions and 68 deletions
+42
View File
@@ -158,6 +158,48 @@ func (c HubWindowClient) Close(ctx context.Context, res backup.OffsiteWindowResu
return err
}
// --- HubAbandonClient: the box's half of the hub's set-aside deletion (decision 74) ---
type HubAbandonClient struct{ Registrar HubRegistrar }
func (c HubAbandonClient) Request(ctx context.Context, path string) (time.Time, error) {
raw, err := c.Registrar.post(ctx, "abandon-request", map[string]string{"path": path})
if err != nil {
return time.Time{}, err
}
var r struct {
State string `json:"state"`
DueAt string `json:"due_at"`
}
if err := json.Unmarshal(raw, &r); err != nil || r.State != "pending" {
return time.Time{}, fmt.Errorf("hub abandon-request: unexpected answer (state %q)", r.State)
}
t, err := time.Parse(time.RFC3339, r.DueAt)
if err != nil {
return time.Time{}, fmt.Errorf("hub abandon-request: bad due_at")
}
return t, nil
}
func (c HubAbandonClient) Status(ctx context.Context) (string, error) {
raw, err := c.Registrar.post(ctx, "abandon-status", nil)
if err != nil {
return "", err
}
var r struct {
State string `json:"state"`
}
if err := json.Unmarshal(raw, &r); err != nil || r.State == "" {
return "", fmt.Errorf("hub abandon-status: malformed")
}
return r.State, nil
}
func (c HubAbandonClient) Cancel(ctx context.Context) error {
_, err := c.Registrar.post(ctx, "abandon-cancel", nil)
return err
}
// --- KeyscanScanner: capture the box host key (x/crypto/ssh, no binary) → fingerprint + known_hosts line ---
type KeyscanScanner struct {