hub v0.72.0 — R-70 + R-71c: offsite delivery-state detector, card, stuck event, R-39(a)-guarded self-heal restage
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user