v0.116.0: observability pass — always-on debug ring + leveled sweep + agent tab + self-log pull — MinAgent: 0.81.0
Capture layer: LogBuffer always exists; logger = MultiWriter(LevelFilterWriter (stdout, logging.level), ring) so DEBUG detail exists remotely without a config flip while docker logs keep respecting the level. New internal/logx leveled helpers. Report ACK gains controller_log_requested (additive); next report ships controller_log_tail (128KB, consume-once, app-tail wire byte-compatible). Debug page: Vezérlő|Ügynök tabs; agent tab proxies agent /debug/logs with the pre-0.83 notice on typed 404. Sweep: netstorage_job phases, netprobe, handler validation refusals + orphan WARN, SupportsWithSource gate line, agentapi per-call DEBUG, migrate phase lines, tier2/offbox unswallowed persists. Red-proofs: filter-disabled, drain-removed, dropped-phase-line all FAIL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/logx"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
)
|
||||
|
||||
@@ -109,13 +110,21 @@ type SupportCache struct {
|
||||
// window, probe on miss). The probe runs OUTSIDE the lock — concurrent misses may double-probe
|
||||
// (harmless: the probe is one cheap GET).
|
||||
func (sc *SupportCache) Supports(ctx context.Context, p SupportProber, f Feature) SupportState {
|
||||
state, _ := sc.SupportsWithSource(ctx, p, f)
|
||||
return state
|
||||
}
|
||||
|
||||
// SupportsWithSource is Supports plus the DECISION SOURCE ("version" | "probe-cache" |
|
||||
// "probe" | "unregistered") — the v0.116.0 observability extension so the gate line can
|
||||
// say HOW the verdict was reached. Behavior is byte-identical to v0.115.0 Supports.
|
||||
func (sc *SupportCache) SupportsWithSource(ctx context.Context, p SupportProber, f Feature) (SupportState, string) {
|
||||
probe, ok := featureProbes[f]
|
||||
if !ok {
|
||||
return SupportUnknown // unregistered feature — never refuse on a table gap
|
||||
return SupportUnknown, "unregistered" // unregistered feature — never refuse on a table gap
|
||||
}
|
||||
if vr, hasVer := p.(AgentVersionReporter); hasVer {
|
||||
if state, decided := supportsByVersion(vr.AgentVersion(), f); decided {
|
||||
return state
|
||||
return state, "version"
|
||||
}
|
||||
}
|
||||
sc.mu.Lock()
|
||||
@@ -125,7 +134,7 @@ func (sc *SupportCache) Supports(ctx context.Context, p SupportProber, f Feature
|
||||
}
|
||||
if e, hit := sc.entries[f]; hit && nowFn().Sub(e.at) < supportTTL {
|
||||
sc.mu.Unlock()
|
||||
return e.state
|
||||
return e.state, "probe-cache"
|
||||
}
|
||||
sc.mu.Unlock()
|
||||
|
||||
@@ -138,14 +147,17 @@ func (sc *SupportCache) Supports(ctx context.Context, p SupportProber, f Feature
|
||||
sc.entries[f] = supportEntry{state: state, at: nowFn()}
|
||||
sc.mu.Unlock()
|
||||
}
|
||||
return state
|
||||
return state, "probe"
|
||||
}
|
||||
|
||||
// Supports probes (cached, TTL 5m, both polarities) whether the connected agent provides the
|
||||
// feature. 2xx ⇒ Yes. 404 ⇒ No. Anything else ⇒ Unknown (never "too old"). The web layer drives
|
||||
// the same machinery through its netAgent seam (Server.netFeatures) so tests can fake the probe.
|
||||
func (c *Client) Supports(ctx context.Context, f Feature) SupportState {
|
||||
return c.features.Supports(ctx, c, f)
|
||||
state, source := c.features.SupportsWithSource(ctx, c, f)
|
||||
logx.Debugf(c.logger, "[agentapi] Supports(%s) = %s (source=%s agent_version=%q)",
|
||||
f, state, source, c.AgentVersion())
|
||||
return state
|
||||
}
|
||||
|
||||
// supportsByVersion decides a feature by version comparison alone. decided=false (unknown/garbage
|
||||
|
||||
Reference in New Issue
Block a user