package offsite import ( "time" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) // R-70 delivery-state detector — ONE implementation, all consumers read it (customer card, the // monitor checker's stuck event + R-71c self-heal, and any future surface). Derived from the two // signals the hub already holds and previously read nowhere: // - the one_time_secrets row (exists? consumed_at?) — the delivery ledger; // - report offsite-object presence — the box's own testimony that an offbox target is applied // (the report builder attaches `offsite` only when a target is configured). // // Origin: DIAG-f10-demo-hp-offsite-2026-07-23 — demo-hp sat 2 days in consumed_awaiting_apply // (the burned-credential shape) while the operator card said "Provisioned" from static copy. // DeliveryState is the customer's offsite last-mile state. type DeliveryState string const ( // DeliveryApplied — the latest report carries an offsite status object: the tier is live on // the box. Wins over every secret-row shape (precedence rule; an applied box with a stale // staged secret is APPLIED — see StaleStagedSince). DeliveryApplied DeliveryState = "applied" // DeliveryConsumedAwaitingApply — the one-time password was consumed but no report since has // ever shown an offbox target: the burned-credential shape (normal for seconds, wrong for // hours; the F10 box sat here for 2 days). DeliveryConsumedAwaitingApply DeliveryState = "consumed_awaiting_apply" // DeliveryStagedAwaitingConsume — a secret is staged and not yet consumed: delivery pending // (normal for minutes while the box re-pulls config, wrong for days). DeliveryStagedAwaitingConsume DeliveryState = "staged_awaiting_consume" // DeliveryNoSecret — offsite is enabled in the config but no secret row exists at all: a // mis-state (e.g. a RESET purged the row without deprovisioning). Rendered needs-attention. DeliveryNoSecret DeliveryState = "no_secret" ) // DeliveryStatus is the derived last-mile state plus the timestamps every consumer must surface // ("mióta" — R-70's whole point is that nothing about this plumbing stays invisible). type DeliveryStatus struct { State DeliveryState // Since anchors the state's age: consumed_at for consumed_awaiting_apply, created_at for // staged_awaiting_consume, the latest report time for applied, zero for no_secret. Since time.Time // StaleStagedSince is non-zero ONLY in the applied+stale-staged edge (the demo-felhom shape): // the box is applied via an earlier generation while an unconsumed secret sits staged. The // caller renders an info line; the self-heal must NEVER touch this shape (R-39(a)). StaleStagedSince time.Time // ReportsSinceConsume / OffsiteReportsSinceConsume count reports received after consumed_at // (consumed_awaiting_apply only; zero otherwise). The R-71c trigger requires // ReportsSinceConsume >= N with OffsiteReportsSinceConsume == 0 — "consecutive without". ReportsSinceConsume int OffsiteReportsSinceConsume int } // DeliveryStateFor derives the customer's offsite delivery state. Callers gate on the descriptor // (offsite enabled) themselves — this function only reads the ledger and the reports; age math is // the caller's (against its own clock seam). func DeliveryStateFor(st *store.Store, customerID string) (DeliveryStatus, error) { found, receivedAt, hasOffsite, err := st.LatestReportOffsitePresence(customerID) if err != nil { return DeliveryStatus{}, err } secret, err := st.GetOneTimeSecretInfo(customerID) if err != nil { return DeliveryStatus{}, err } // Precedence: applied wins. The box's own report is the strongest evidence there is. if found && hasOffsite { status := DeliveryStatus{State: DeliveryApplied, Since: receivedAt} if secret != nil && secret.ConsumedAt.IsZero() { status.StaleStagedSince = secret.CreatedAt // demo-felhom shape: applied + stale staged } return status, nil } if secret == nil { return DeliveryStatus{State: DeliveryNoSecret}, nil } if !secret.ConsumedAt.IsZero() { total, withOffsite, err := st.CountReportsOffsiteSince(customerID, secret.ConsumedAt) if err != nil { return DeliveryStatus{}, err } return DeliveryStatus{ State: DeliveryConsumedAwaitingApply, Since: secret.ConsumedAt, ReportsSinceConsume: total, OffsiteReportsSinceConsume: withOffsite, }, nil } return DeliveryStatus{State: DeliveryStagedAwaitingConsume, Since: secret.CreatedAt}, nil }