agent v0.86.0: DR-tier-by-default — capability inactive state (GatedBy/GateActive, pbsdr gate via DRConfigured) + F-3 root-run provision parent ownership
Claude-Session: https://claude.ai/code/session_01NptTCFtu7dz2Ru89qHRagN
This commit is contained in:
@@ -8,19 +8,30 @@ import (
|
||||
// Status is one capability's live result — the wire shape the agent attaches to its hub report
|
||||
// (HostReport.Capabilities). The hub mirrors this struct field-for-field and keys its alert on
|
||||
// Critical+degraded. Reason is empty when ok.
|
||||
//
|
||||
// "inactive" (v0.86.0, DR-tier-by-default): a config-GATED capability whose plumbing is HEALTHY
|
||||
// (binary present, sudo granted) but whose gating feature is disabled by configuration. Distinct
|
||||
// from degraded on purpose — disabled ≠ broken; the hub renders it as a neutral chip, never red.
|
||||
// Broken plumbing (binary missing / grant denied) stays DEGRADED even when the gate is off: an
|
||||
// un-migrated box must never look deliberately disabled.
|
||||
type Status struct {
|
||||
Name string `json:"name"`
|
||||
Feature string `json:"feature"`
|
||||
Critical bool `json:"critical"`
|
||||
Status string `json:"status"` // "ok" | "degraded"
|
||||
Status string `json:"status"` // "ok" | "degraded" | "inactive"
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
StatusOK = "ok"
|
||||
StatusDegraded = "degraded"
|
||||
StatusInactive = "inactive"
|
||||
)
|
||||
|
||||
// ReasonInactive is the fixed reason string for the inactive state (the hub + operator docs
|
||||
// reference it verbatim).
|
||||
const ReasonInactive = "disabled by configuration"
|
||||
|
||||
// Runner is the minimal exec seam the probe needs (satisfied by proxmox.ExecRunner). The probe
|
||||
// runs `sudo -n -l -- <binary> <args…>` LITERALLY — a sudo POLICY LIST that never executes the
|
||||
// command — so the Runner MUST be a DIRECT runner (RunnerDirect), not the sudo-prepending one
|
||||
@@ -31,9 +42,13 @@ type Runner interface {
|
||||
|
||||
// Prober checks the manifest against the live host. Exists defaults to an os.Stat check on the
|
||||
// absolute binary path (what `command -v` would resolve for an absolute path) when nil.
|
||||
// GateActive answers "is the feature behind this gate id configured on?" for GATED capabilities
|
||||
// (Capability.GatedBy). nil, or a gate it answers true for, keeps the historical behavior; false
|
||||
// downgrades a HEALTHY probe to StatusInactive (broken plumbing stays degraded regardless).
|
||||
type Prober struct {
|
||||
Runner Runner
|
||||
Exists func(path string) bool // nil → os.Stat
|
||||
Runner Runner
|
||||
Exists func(path string) bool // nil → os.Stat
|
||||
GateActive func(gate string) bool // nil → every gate treated active
|
||||
}
|
||||
|
||||
// Probe lists every manifest capability against the sudo policy and checks its binary exists,
|
||||
@@ -72,6 +87,11 @@ func (p Prober) Probe(ctx context.Context) []Status {
|
||||
case p.Runner != nil && !p.granted(ctx, c):
|
||||
s.Status, s.Reason = StatusDegraded, "sudo policy denied"
|
||||
}
|
||||
// Config gate (v0.86.0): only a HEALTHY probe is downgraded to inactive — a degraded one
|
||||
// stays degraded (missing binary/grant = un-migrated or mis-installed box, never "off").
|
||||
if s.Status == StatusOK && c.GatedBy != "" && p.GateActive != nil && !p.GateActive(c.GatedBy) {
|
||||
s.Status, s.Reason = StatusInactive, ReasonInactive
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
@@ -85,13 +105,16 @@ func (p Prober) granted(ctx context.Context, c Capability) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Summarize returns (okCount, total, degraded) for logging. degraded lists every non-ok status.
|
||||
// Summarize returns (okCount, total, degraded) for logging. degraded lists DEGRADED statuses
|
||||
// only — inactive is a deliberate, healthy state and must not land in the error log (it is
|
||||
// counted via len(statuses)-ok-len(degraded) by callers that want it).
|
||||
func Summarize(statuses []Status) (ok, total int, degraded []Status) {
|
||||
total = len(statuses)
|
||||
for _, s := range statuses {
|
||||
if s.Status == StatusOK {
|
||||
switch s.Status {
|
||||
case StatusOK:
|
||||
ok++
|
||||
} else {
|
||||
case StatusDegraded:
|
||||
degraded = append(degraded, s)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user