v0.201.0 — a correct recovery code is never called wrong again (CAMPAIGN-11) — MinAgent 0.125.0
gates / gates (push) Successful in 9s
gates / gates (push) Successful in 9s
R-216: the offsite key recovery is a coupled feature and now says so. featureProbes +
featureMinAgent 0.125.0 + a Supports gate at the unlock entry point, FAILING CLOSED — an
agent that cannot answer is named as such instead of the customer's code being blamed.
Measured live: a 404 from agent 0.120.0 came back as "we did not accept your recovery
code, check that all ten words", in 0.134 s, against a perfect code.
R-218: delete the repo-password short-circuit in needsOffsiteCredential. The declaration
stops when the TIER WORKS, not when a key exists — installing a key is the recovery
screen's whole job, so succeeding at recovery was switching off the mechanism that would
have delivered the coordinates to use it.
R-219: the unlock finishes the job — place the key, bring the tier up, then list. Without
it the promised listing could never render on the shape the screen exists for.
R-217: an unreadable store no longer claims to have opened with unattributable content
(the OffsiteInventory{} zero value). Opened / empty / unreadable are three states.
R-222: a code that is right about a RETAINED earlier package is named, not blamed. States
what the hub knows and promises nothing — no read path exists.
R-215: GET /recovery is gated on the same predicate as the interception.
Five red-proofs, each demonstrated failing and restored.
This commit is contained in:
@@ -35,6 +35,26 @@ const FeatureGuestMemoryResize Feature = "guest_memory_resize"
|
||||
// an empty field means legacy.
|
||||
const FeatureBackupAgeState Feature = "backup_age_state"
|
||||
|
||||
// FeatureOffsiteKeyRecovery is the customer-facing off-site key recovery (agent v0.125.0, R-199
|
||||
// links 7–8): POST /escrow/recover-offsite-password fetches this host's sealed bundle, unseals it
|
||||
// with R and returns the single repository-password field.
|
||||
//
|
||||
// ⚠ THIS GATE FAILS CLOSED, and it is the ONLY feature in this table that does. Read §7.1 of the
|
||||
// R-216 fix before "correcting" it back to the package default.
|
||||
//
|
||||
// The package default is fail-OPEN: SupportUnknown proceeds, because for every other coupled feature
|
||||
// a wrong "unsupported" would block something harmless while a down agent already speaks through the
|
||||
// normal error paths. **That default is what produced R-216.** Measured live on 2026-08-05
|
||||
// (CAMPAIGN-11 Phase 1): an agent 0.120.0 answered the recovery route with 404, the unlock attempt
|
||||
// went ahead anyway, and the customer was told — in Hungarian, on the one screen whose whole purpose
|
||||
// is to be believed about backups — that their perfectly correct recovery code was not accepted and
|
||||
// they should check their typing. A correct code, refused in 0.134 s, blamed on the customer.
|
||||
//
|
||||
// So here: anything other than SupportYes means the screen says THE MACHINE cannot ask yet. The
|
||||
// unlock is never attempted when it cannot complete, because the failure of an attempt that could
|
||||
// never have worked is attributed to the code.
|
||||
const FeatureOffsiteKeyRecovery Feature = "offsite_key_recovery"
|
||||
|
||||
// SupportState is a probe verdict. The zero value is SupportUnknown (fail-open: unknown never
|
||||
// refuses — the existing agent-error paths speak honestly when the agent is down).
|
||||
type SupportState int
|
||||
@@ -92,12 +112,27 @@ var featureProbes = map[Feature]func(ctx context.Context, p SupportProber) error
|
||||
_, err := gm.GuestMemory(ctx)
|
||||
return err
|
||||
},
|
||||
// The recovery route is a POST that performs work and consumes a recovery code — it cannot be
|
||||
// probed. Like the memory prober's negative case this returns a sentinel that classifies to
|
||||
// SupportUnknown, so the decision falls to the VERSION path above.
|
||||
//
|
||||
// The row must exist even though it cannot probe: SupportsWithSource looks up featureProbes
|
||||
// FIRST and returns "unregistered"/SupportUnknown on a table gap, before the version path runs.
|
||||
// A featureMinAgent row without a featureProbes row is therefore never consulted at all.
|
||||
FeatureOffsiteKeyRecovery: func(ctx context.Context, p SupportProber) error {
|
||||
return errNoRecoveryProbe
|
||||
},
|
||||
}
|
||||
|
||||
// errNoMemoryProbe classifies to SupportUnknown (not a *StatusError 404), so a prober that cannot be
|
||||
// asked never reads as "unsupported".
|
||||
var errNoMemoryProbe = errors.New("agentapi: prober does not support the guest-memory probe")
|
||||
|
||||
// errNoRecoveryProbe classifies to SupportUnknown: the off-site key recovery route is a POST that
|
||||
// consumes a recovery code and so cannot be probed, leaving the VERSION path to decide. Its caller
|
||||
// fails CLOSED on Unknown — see FeatureOffsiteKeyRecovery.
|
||||
var errNoRecoveryProbe = errors.New("agentapi: the offsite key recovery route cannot be probed")
|
||||
|
||||
// featureMinAgent maps each coupled feature to the MINIMUM agent version that carries its coupled
|
||||
// semantics (the CHANGELOG `MinAgent:` header value). Used by Supports when the agent's version is
|
||||
// KNOWN (the v0.82.0 X-Felhom-Agent-Version channel) — a direct comparison, no probe traffic. A
|
||||
@@ -107,8 +142,16 @@ var featureMinAgent = map[Feature]string{
|
||||
FeatureGuestMemoryResize: "0.90.0",
|
||||
// R-88 Part 2: /backup/due carries age_state, distinguishing "never backed up" from "cannot tell".
|
||||
FeatureBackupAgeState: "0.105.0",
|
||||
// R-199 links 7–8: POST /escrow/recover-offsite-password. R-216 — this row is the whole reason a
|
||||
// correct recovery code can no longer be reported as wrong on an agent that cannot answer.
|
||||
FeatureOffsiteKeyRecovery: "0.125.0",
|
||||
}
|
||||
|
||||
// MinAgentFor returns the declared minimum agent version for a feature ("" when the feature has no
|
||||
// row). Read-only accessor over featureMinAgent so a refusal can NAME the version it needs instead of
|
||||
// hard-coding the number a second time at the call site.
|
||||
func MinAgentFor(f Feature) string { return featureMinAgent[f] }
|
||||
|
||||
// AgentVersionReporter is optionally implemented by a SupportProber (*Client is one): it reports
|
||||
// the last strictly-validated agent version seen on its traffic ("" = unknown → probe fallback).
|
||||
type AgentVersionReporter interface {
|
||||
|
||||
Reference in New Issue
Block a user