1133aade73
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
98 lines
4.4 KiB
Go
98 lines
4.4 KiB
Go
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
|
|
}
|