package store import ( "database/sql" "fmt" "strconv" "strings" "time" ) // OffsiteKey is the registrar's record of the box key the hub installed pinned (decision 69). type OffsiteKey struct { CustomerID string Fingerprint string InstalledAt time.Time ConfirmedAt time.Time // zero = the box has not confirmed it yet } // RecordOffsiteKeyInstalled records (last-write-wins) the key the hub just installed; confirmation resets. func (s *Store) RecordOffsiteKeyInstalled(customerID, fp string) error { _, err := s.db.Exec(` INSERT INTO offsite_keys (customer_id, fingerprint, installed_at, confirmed_at) VALUES (?, ?, datetime('now'), NULL) ON CONFLICT(customer_id) DO UPDATE SET fingerprint = excluded.fingerprint, installed_at = datetime('now'), confirmed_at = NULL`, customerID, fp) return err } // RecordOffsiteKeyConfirmed marks the installed key confirmed by the box; false when fp is not the key on record. func (s *Store) RecordOffsiteKeyConfirmed(customerID, fp string) (bool, error) { res, err := s.db.Exec(`UPDATE offsite_keys SET confirmed_at = datetime('now') WHERE customer_id = ? AND fingerprint = ?`, customerID, fp) if err != nil { return false, err } n, _ := res.RowsAffected() return n > 0, nil } // GetOffsiteKey returns the record, or (nil, nil) when none exists. func (s *Store) GetOffsiteKey(customerID string) (*OffsiteKey, error) { var k OffsiteKey var inst string var conf sql.NullString err := s.db.QueryRow(`SELECT customer_id, fingerprint, installed_at, confirmed_at FROM offsite_keys WHERE customer_id = ?`, customerID). Scan(&k.CustomerID, &k.Fingerprint, &inst, &conf) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, err } k.InstalledAt = parseSQLiteTime(inst) if conf.Valid { k.ConfirmedAt = parseSQLiteTime(conf.String) } return &k, nil } // OffsiteWindowOpen reports whether a clean-up window is open for the customer right now (decision 68): // a window row not closed and not past its closes_by. Errors read as "closed" — the key check then // alarms on a window line, which is the safe side. func (s *Store) OffsiteWindowOpen(customerID string) bool { var n int err := s.db.QueryRow(`SELECT COUNT(*) FROM offsite_windows WHERE customer_id = ? AND closed_at IS NULL AND closes_by > datetime('now')`, customerID).Scan(&n) return err == nil && n > 0 } // OffsiteWindow is one clean-up window (decision 68). type OffsiteWindow struct { ID int64 CustomerID string OpenedAt time.Time ClosesBy time.Time ClosedAt time.Time CountBefore int CountAfter int BoxResult string CloseReason string // MaxRemove is the cap this window was opened with (R-833: an operator grant may raise it for one // window). 0 on rows written before v0.129.0 — readers fall back to the default cap then. MaxRemove int } // OpenOffsiteWindowRow records a window the hub just opened, with the cap it was opened under. func (s *Store) OpenOffsiteWindowRow(customerID string, closesBy time.Time, countBefore, maxRemove int) (int64, error) { res, err := s.db.Exec(`INSERT INTO offsite_windows (customer_id, opened_at, closes_by, count_before, max_remove) VALUES (?, datetime('now'), ?, ?, ?)`, customerID, closesBy.UTC().Format("2006-01-02 15:04:05"), countBefore, maxRemove) if err != nil { return 0, err } return res.LastInsertId() } // CloseOffsiteWindowRow closes a window (idempotent: an already-closed row is not touched). func (s *Store) CloseOffsiteWindowRow(id int64, countAfter int, boxResult, reason string) (bool, error) { res, err := s.db.Exec(`UPDATE offsite_windows SET closed_at = datetime('now'), count_after = ?, box_result = ?, close_reason = ? WHERE id = ? AND closed_at IS NULL`, countAfter, boxResult, reason, id) if err != nil { return false, err } n, _ := res.RowsAffected() return n > 0, nil } // GetOffsiteWindow returns one window row, or (nil, nil). func (s *Store) GetOffsiteWindow(id int64) (*OffsiteWindow, error) { var w OffsiteWindow var opened, closesBy string var closed, boxRes, reason sql.NullString var before, after, maxRm sql.NullInt64 err := s.db.QueryRow(`SELECT id, customer_id, opened_at, closes_by, closed_at, count_before, count_after, box_result, close_reason, max_remove FROM offsite_windows WHERE id = ?`, id). Scan(&w.ID, &w.CustomerID, &opened, &closesBy, &closed, &before, &after, &boxRes, &reason, &maxRm) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, err } w.OpenedAt, w.ClosesBy = parseSQLiteTime(opened), parseSQLiteTime(closesBy) if closed.Valid { w.ClosedAt = parseSQLiteTime(closed.String) } w.CountBefore, w.CountAfter = int(before.Int64), int(after.Int64) w.BoxResult, w.CloseReason = boxRes.String, reason.String w.MaxRemove = int(maxRm.Int64) return &w, nil } // LastOffsiteWindowOpened returns when the customer's most recent window was opened (zero = never). func (s *Store) LastOffsiteWindowOpened(customerID string) time.Time { var v sql.NullString if err := s.db.QueryRow(`SELECT MAX(opened_at) FROM offsite_windows WHERE customer_id = ?`, customerID).Scan(&v); err != nil || !v.Valid { return time.Time{} } return parseSQLiteTime(v.String) } // ExpiredOffsiteWindows lists windows still open past their closes_by. func (s *Store) ExpiredOffsiteWindows() ([]OffsiteWindow, error) { rows, err := s.db.Query(`SELECT id, customer_id FROM offsite_windows WHERE closed_at IS NULL AND closes_by <= datetime('now')`) if err != nil { return nil, err } defer rows.Close() var out []OffsiteWindow for rows.Next() { var w OffsiteWindow if err := rows.Scan(&w.ID, &w.CustomerID); err != nil { return nil, err } out = append(out, w) } return out, rows.Err() } const offsiteWindowsEnabledKey = "offsite_prune_windows_enabled" // OffsiteWindowsEnabled — the operator switch for WEEKLY windows (decision 68). Off by default: the // interim is "nothing prunes" until the operator turns the weekly window on. func (s *Store) OffsiteWindowsEnabled() bool { return s.getSetting(offsiteWindowsEnabledKey) == "on" } // SetOffsiteWindowsEnabled flips the weekly switch. func (s *Store) SetOffsiteWindowsEnabled(on bool) error { v := "" if on { v = "on" } return s.setSetting(offsiteWindowsEnabledKey, v) } // GrantOffsiteWindowOnce lets the customer's NEXT window request through regardless of the weekly // cadence (operator one-shot). func (s *Store) GrantOffsiteWindowOnce(customerID string) error { return s.setSetting("offsite_window_grant:"+customerID, "1") } // GrantOffsiteWindowOnceMax is the operator's one-shot grant that ALSO raises the removal cap for that // one window (R-833: after a long gap the honest backlog exceeds half the snapshots and the default cap // refuses every window). The raised cap is consumed with the grant; the next window has the default. func (s *Store) GrantOffsiteWindowOnceMax(customerID string, maxRemove int) error { if maxRemove <= 0 { return fmt.Errorf("max_remove must be positive, got %d", maxRemove) } return s.setSetting("offsite_window_grant:"+customerID, "max:"+strconv.Itoa(maxRemove)) } // TakeOffsiteWindowGrant consumes a one-shot grant: granted is true when one was present, and // maxRemove is the operator's raised cap for that window (0 = none, use the default). func (s *Store) TakeOffsiteWindowGrant(customerID string) (granted bool, maxRemove int) { k := "offsite_window_grant:" + customerID v := s.getSetting(k) switch { case v == "1": case strings.HasPrefix(v, "max:"): n, err := strconv.Atoi(strings.TrimPrefix(v, "max:")) if err != nil || n <= 0 { n = 0 // a malformed value still grants the window, at the default cap } maxRemove = n default: return false, 0 } _ = s.setSetting(k, "") return true, maxRemove } // ForceOffsiteWindowDueForTest back-dates a window's closes_by. TEST-ONLY. func (s *Store) ForceOffsiteWindowDueForTest(id int64) error { _, err := s.db.Exec(`UPDATE offsite_windows SET closes_by = datetime('now', '-1 minute') WHERE id = ?`, id) return err } // OffsiteAbandon is one household request to delete a set-aside off-site copy (decision 74, R-823). The // hub deletes it only after DueAt, and only if nobody cancelled it. type OffsiteAbandon struct { ID int64 CustomerID string Path string RequestedAt time.Time DueAt time.Time CancelledAt time.Time CancelledBy string DeletedAt time.Time LastError string } func (a *OffsiteAbandon) State() string { switch { case a == nil: return "none" case !a.DeletedAt.IsZero(): return "deleted" case !a.CancelledAt.IsZero(): return "cancelled" } return "pending" } const abandonCols = `id, customer_id, path, requested_at, due_at, cancelled_at, cancelled_by, deleted_at, last_error` func scanAbandon(sc interface{ Scan(...any) error }) (*OffsiteAbandon, error) { var a OffsiteAbandon var req, due string var canc, by, del, lerr sql.NullString if err := sc.Scan(&a.ID, &a.CustomerID, &a.Path, &req, &due, &canc, &by, &del, &lerr); err != nil { return nil, err } a.RequestedAt, a.DueAt = parseSQLiteTime(req), parseSQLiteTime(due) if canc.Valid { a.CancelledAt = parseSQLiteTime(canc.String) } if del.Valid { a.DeletedAt = parseSQLiteTime(del.String) } a.CancelledBy, a.LastError = by.String, lerr.String return &a, nil } // LatestOffsiteAbandon returns the customer's most recent request for path ("" = any), or (nil, nil). func (s *Store) LatestOffsiteAbandon(customerID, path string) (*OffsiteAbandon, error) { q := `SELECT ` + abandonCols + ` FROM offsite_abandon_requests WHERE customer_id = ?` args := []any{customerID} if path != "" { q += ` AND path = ?` args = append(args, path) } a, err := scanAbandon(s.db.QueryRow(q+` ORDER BY id DESC LIMIT 1`, args...)) if err == sql.ErrNoRows { return nil, nil } return a, err } // CreateOffsiteAbandon records a request due at dueAt. func (s *Store) CreateOffsiteAbandon(customerID, path string, dueAt time.Time) (int64, error) { res, err := s.db.Exec(`INSERT INTO offsite_abandon_requests (customer_id, path, requested_at, due_at) VALUES (?, ?, datetime('now'), ?)`, customerID, path, dueAt.UTC().Format("2006-01-02 15:04:05")) if err != nil { return 0, err } return res.LastInsertId() } // CancelOffsiteAbandon cancels every PENDING request of the customer; returns how many. func (s *Store) CancelOffsiteAbandon(customerID, by string) (int, error) { res, err := s.db.Exec(`UPDATE offsite_abandon_requests SET cancelled_at = datetime('now'), cancelled_by = ? WHERE customer_id = ? AND cancelled_at IS NULL AND deleted_at IS NULL`, by, customerID) if err != nil { return 0, err } n, _ := res.RowsAffected() return int(n), nil } // DueOffsiteAbandons lists pending requests whose due time has passed. func (s *Store) DueOffsiteAbandons() ([]*OffsiteAbandon, error) { rows, err := s.db.Query(`SELECT ` + abandonCols + ` FROM offsite_abandon_requests WHERE cancelled_at IS NULL AND deleted_at IS NULL AND due_at <= datetime('now')`) if err != nil { return nil, err } defer rows.Close() var out []*OffsiteAbandon for rows.Next() { a, err := scanAbandon(rows) if err != nil { return nil, err } out = append(out, a) } return out, rows.Err() } // MarkOffsiteAbandonDeleted / MarkOffsiteAbandonError record the sweep's outcome. func (s *Store) MarkOffsiteAbandonDeleted(id int64) error { _, err := s.db.Exec(`UPDATE offsite_abandon_requests SET deleted_at = datetime('now'), last_error = NULL WHERE id = ?`, id) return err } func (s *Store) MarkOffsiteAbandonError(id int64, msg string) error { _, err := s.db.Exec(`UPDATE offsite_abandon_requests SET last_error = ? WHERE id = ?`, msg, id) return err } // ForceOffsiteAbandonDueForTest back-dates a request. TEST-ONLY. func (s *Store) ForceOffsiteAbandonDueForTest(id int64) error { _, err := s.db.Exec(`UPDATE offsite_abandon_requests SET due_at = datetime('now', '-1 minute') WHERE id = ?`, id) return err }