Documentation
¶
Overview ¶
Package upstream implements parent-proxy chaining with failover and circuit-breaker protection (ADR-0002 extraction; engine was upstream.go in package main).
Culvert can route traffic through one or more parent HTTP proxies with automatic failover. When all upstreams are down the proxy falls back to direct connections. The package owns the pool state machine (round-robin selection, health flags, per-proxy circuit breakers) and the health-check loop; package main keeps the singleton, the transport wiring (applyUpstreamProxy), and persistence via admin_settings.
Configuration (config.yaml):
upstream:
proxies:
- url: "http://parent1.corp.com:3128"
- url: "http://parent2.corp.com:3128"
health_interval: "30s"
circuit_breaker:
threshold: 5 # failures before opening circuit
timeout: "60s" # how long circuit stays open before half-open probe
Index ¶
- Constants
- Variables
- func ClassifyProbe(resp *http.Response, err error) (status, reason string)
- func FormatSummary(entries []Entry) string
- func IsULID(id string) bool
- func NewManagedID() string
- func RunHealthCheckLoop(ctx context.Context, pool *Pool, interval time.Duration)
- func ValidateEffective(yaml, managed []ManagedEntry) error
- type Attribution
- type CircuitBreaker
- func (cb *CircuitBreaker) Allow() bool
- func (cb *CircuitBreaker) Failures() int64
- func (cb *CircuitBreaker) OpenedAt() time.Time
- func (cb *CircuitBreaker) Params() (threshold int, timeout time.Duration)
- func (cb *CircuitBreaker) RecordFailure() bool
- func (cb *CircuitBreaker) RecordSuccess()
- func (cb *CircuitBreaker) State() string
- type Config
- type Document
- type DuplicateAuthorityError
- type Effective
- type Entry
- type Health
- type InvalidEntryError
- type Keyring
- type ManagedEntry
- type Pool
- func (p *Pool) BeginManualProbe(now time.Time) (ok bool, code string, retryAfter time.Duration)
- func (p *Pool) CBParams() (threshold int, timeout time.Duration)
- func (p *Pool) Configure(entries []Entry, cbThreshold int, cbTimeout time.Duration) error
- func (p *Pool) DirectFallback() (active bool, total int64)
- func (p *Pool) Document() Document
- func (p *Pool) Effective() Effective
- func (p *Pool) EffectiveEntries() []ManagedEntry
- func (p *Pool) Enabled() bool
- func (p *Pool) EndManualProbe()
- func (p *Pool) Entries() []Entry
- func (p *Pool) HealthCheck(source string) ProbeSummary
- func (p *Pool) Key() (key *Keyring, reason string)
- func (p *Pool) LegacyManagedEntries() []Entry
- func (p *Pool) List() []Status
- func (p *Pool) ManualProbeInFlight() bool
- func (p *Pool) Next() *Proxy
- func (p *Pool) ProbeConfig() (configured bool, interval time.Duration)
- func (p *Pool) ProxyFunc() func(*http.Request) (*url.URL, error)
- func (p *Pool) Restore(st PoolState)
- func (p *Pool) SetDocument(doc Document) error
- func (p *Pool) SetKey(k *Keyring, reason string)
- func (p *Pool) SetProbeInterval(d time.Duration)
- func (p *Pool) SetProxies(entries []Entry) error
- func (p *Pool) Snapshot() PoolState
- func (p *Pool) YAMLEntries() []ManagedEntry
- type PoolState
- type ProbeState
- type ProbeSummary
- type Proxy
- type Sealed
- type Source
- type Spec
- type Status
Constants ¶
const ( CredentialNone = "none" // no credential material CredentialConfigured = "configured" // sealed material, unwrappable, authority matches CredentialUnusable = "unusable" // ciphertext present, node-local key cannot unwrap it CredentialMismatch = "mismatch" // credential authority ≠ entry authority // CredentialRequiresReplacement (2F-D, C12) is the DISTINCT durable state // of an entry whose credential was OMITTED by a sanitized artifact (a // backup archive or an export) and has not been set again on this node: // no material exists (unlike unusable/mismatch) but the entry is known // to need one, so it is ineligible and never sent unauthenticated. Only // an explicit T2 replace or T3 clear resolves it. CredentialRequiresReplacement = "requiresReplacement" )
Derived credential states (C4).
const ( ProbeUnprobed = "unprobed" ProbeHealthy = "healthy" ProbeUnhealthy = "unhealthy" )
Probe statuses.
const ( ReasonNone = "none" ReasonConnectFailed = "connect_failed" ReasonTimeout = "timeout" ReasonProxyAuthFailed = "proxy_auth_failed" ReasonProbeHTTPError = "probe_http_error" )
Probe reasons.
const ( ProbePeriodic = "periodic" ProbeManual = "manual" )
Probe sources.
const ( ModeNoPool = "no_pool" ModeChained = "chained" ModeNoEligibleParent = "no_eligible_parent" ModeDirectFallback = "direct_fallback" )
Effective modes (C11).
const ( ManualProbeInFlight = "probe_in_flight" ManualProbeRateLimited = "probe_rate_limited" )
Manual-probe refusal codes (bounded; surfaced as the 429 body's code).
const DocumentSchema = 1
DocumentSchema is the current v2 document schema.
const KeyFileName = ".upstream_cred_key"
KeyFileName is the node-local credential key file, beside admin_settings.
const ManualProbeWindow = 10 * time.Second
ManualProbeWindow is the minimum spacing between two ACCEPTED manual probe runs (a repeat inside it is refused with 429).
const ProbeTimeout = probeTimeout
ProbeTimeout is the per-entry probe bound, exported so the admin frontend's manual-probe deadline (frontend/src/api/upstream.ts PROBE_PER_ENTRY_MS) can be pinned to it in lockstep; the engine itself reads probeTimeout.
Variables ¶
var ErrCredentialMismatch = errors.New("upstream credential: bound to a different entry or authority")
ErrCredentialMismatch is the bounded reason a sealed credential does not belong to the (entry, authority) it is attached to.
var ErrKeyMissing = errors.New("upstream credential key: not found")
ErrKeyMissing reports that no key file exists.
var FallbackAlertHook func(detail string)
FallbackAlertHook is a TEST-ONLY seam: when non-nil it receives the direct-fallback transition instead of the asynchronous production alert.
var ProbeTransport func(authority *url.URL) http.RoundTripper
ProbeTransport is a TEST-ONLY seam: when non-nil it supplies the round-tripper the health probe uses for a given parent (identified by its CREDENTIAL-FREE authority URL — the seam never sees a password), so a test can inject a deterministic probe outcome without a network. Production leaves it nil.
Functions ¶
func ClassifyProbe ¶ added in v1.0.218
ClassifyProbe maps a probe outcome to (status, reason). It is the ONE classifier: dial/TLS error → connect_failed, deadline → timeout, HTTP 407 → proxy_auth_failed, 2xx/3xx → healthy, any other status → probe_http_error.
func FormatSummary ¶
FormatSummary returns a log-friendly summary like "2 proxies (parent1:3128, parent2:3128)".
func NewManagedID ¶ added in v1.0.218
func NewManagedID() string
NewManagedID mints a server-generated ULID (collision-checked by the caller against every managed and YAML id).
func RunHealthCheckLoop ¶
RunHealthCheckLoop runs pool.HealthCheck at the given interval until ctx is cancelled, stopping the underlying ticker on exit. Returns immediately for a nil pool or a non-positive interval. P1.3 / S4.UpstreamHealth.
func ValidateEffective ¶ added in v1.0.218
func ValidateEffective(yaml, managed []ManagedEntry) error
ValidateEffective checks the complete effective pool (YAML-owned + managed): every entry normalizes, identities are unique across both sets, and the canonical authorities are unique across YAML/YAML, managed/managed and YAML/managed. Errors are typed so callers report bounded reasons only.
Types ¶
type Attribution ¶ added in v1.0.156
type Attribution struct {
// contains filtered or unexported fields
}
Attribution records which pool proxy the transport selected for one request. Create with WithAttribution; ProxyFunc fills it during RoundTrip.
func WithAttribution ¶ added in v1.0.156
func WithAttribution(ctx context.Context) (context.Context, *Attribution)
WithAttribution returns a child context carrying a fresh attribution slot, plus the slot itself.
func (*Attribution) Record ¶ added in v1.0.156
func (a *Attribution) Record(err error)
Record feeds a completed request's outcome into the selected proxy's circuit breaker. Nil-safe on both the receiver and the slot's proxy.
A context.Canceled error is deliberately NOT charged to the proxy (our client went away). Timeouts DO count. The error is never rendered — only its bounded reason class (a transport error can embed the proxy URL).
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
CircuitBreaker tracks consecutive failures for an upstream proxy.
func (*CircuitBreaker) Allow ¶
func (cb *CircuitBreaker) Allow() bool
Allow returns true if the circuit permits a request.
func (*CircuitBreaker) Failures ¶ added in v1.0.48
func (cb *CircuitBreaker) Failures() int64
Failures returns the current consecutive-failure count. Exported for admin API / diagnostics surfacing; not used on the request path.
func (*CircuitBreaker) OpenedAt ¶ added in v1.0.48
func (cb *CircuitBreaker) OpenedAt() time.Time
OpenedAt returns when the circuit last tripped open (zero Time if it has never opened, or has since been reset by RecordSuccess). Exported for admin API / diagnostics surfacing; not used on the request path.
func (*CircuitBreaker) Params ¶
func (cb *CircuitBreaker) Params() (threshold int, timeout time.Duration)
Params returns the breaker's configured threshold and timeout. Exported for the main-side persistence-contract tests (SetProxies must inherit Configure's params) and for diagnostics; not used on the request path.
func (*CircuitBreaker) RecordFailure ¶
func (cb *CircuitBreaker) RecordFailure() bool
RecordFailure increments the failure count and opens the circuit if the threshold is reached. It returns true exactly when this call transitioned the circuit into the open state (closed/half-open → open), so callers can log/alert once per trip instead of once per failure. Failures recorded while already open refresh openedAt (extending the open window) and return false.
func (*CircuitBreaker) RecordSuccess ¶
func (cb *CircuitBreaker) RecordSuccess()
RecordSuccess resets the failure count and closes the circuit.
func (*CircuitBreaker) State ¶
func (cb *CircuitBreaker) State() string
State returns the current circuit state name.
type Config ¶
type Config struct {
Proxies []Entry `yaml:"proxies" json:"proxies"`
HealthInterval string `yaml:"health_interval" json:"healthInterval"` // Go duration
CircuitBreaker struct {
Threshold int `yaml:"threshold" json:"threshold"` // failures before open
Timeout string `yaml:"timeout" json:"timeout"` // Go duration
} `yaml:"circuit_breaker" json:"circuitBreaker"`
}
Config is the "upstream" section of config.yaml.
type Document ¶ added in v1.0.218
type Document struct {
Schema int `json:"schema"`
Revision int64 `json:"revision"`
Entries []ManagedEntry `json:"entries"`
}
Document is the durable v2 representation of the MANAGED entries (upstream_proxies_v2). YAML-owned entries are not part of it.
type DuplicateAuthorityError ¶ added in v1.0.218
type DuplicateAuthorityError struct{ Count int }
DuplicateAuthorityError reports duplicate canonical authorities in the complete effective pool. It carries a COUNT only (never an authority, username or credential).
func (*DuplicateAuthorityError) Error ¶ added in v1.0.218
func (e *DuplicateAuthorityError) Error() string
type Effective ¶ added in v1.0.218
type Effective struct {
Mode string `json:"mode"`
Entries int `json:"entries"`
Eligible int `json:"eligible"`
FallbackTotal int64 `json:"fallbackTotal"`
// Since is the RFC3339 instant the current mode was first observed.
Since string `json:"since"`
}
Effective is the backend-derived data-plane truth.
type Entry ¶
type Entry struct {
URL string `yaml:"url" json:"url"`
}
Entry is one parent proxy from config.yaml (credential-free URL).
type Health ¶ added in v1.0.218
type Health struct {
Status string `json:"status"`
Reason string `json:"reason"`
LastProbeAt string `json:"lastProbeAt,omitempty"`
Source string `json:"source,omitempty"`
}
Health is the contracted per-entry health shape on the read model.
func HealthOf ¶ added in v1.0.218
func HealthOf(st ProbeState) Health
HealthOf projects a probe state onto the contracted health shape.
type InvalidEntryError ¶ added in v1.0.218
InvalidEntryError names the offending entry by index/id, never by URL.
func (*InvalidEntryError) Error ¶ added in v1.0.218
func (e *InvalidEntryError) Error() string
type Keyring ¶ added in v1.0.218
type Keyring struct {
// contains filtered or unexported fields
}
Keyring holds the loaded node-local key.
func OpenKey ¶ added in v1.0.218
OpenKey loads the key from dir. create=false is the READ path: a missing key is ErrKeyMissing and nothing is written. create=true mints a fresh key ONLY when none exists (the caller has proven no ciphertext exists).
func (*Keyring) KeyID ¶ added in v1.0.218
KeyID is the public identifier of the loaded key (first 16 hex of its SHA-256), recorded on every sealed credential.
type ManagedEntry ¶ added in v1.0.218
type ManagedEntry struct {
ID string `json:"id"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username,omitempty"`
Revision int64 `json:"revision"`
Source Source `json:"source"`
Credential *Sealed `json:"credential,omitempty"`
// RequiresReplacement is the durable marker behind the
// CredentialRequiresReplacement state (2F-D, C12): set by the backup
// sanitizer (a restored entry that used to hold a credential) and by an
// import whose export declared a credential that could not be
// inherited; meaningful only while Credential is nil; cleared by a T2
// replace or a T3 clear. Ignored by the frozen predecessor (lenient
// decoder) and never carried by the credential-free legacy list.
RequiresReplacement bool `json:"requiresReplacement,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
// contains filtered or unexported fields
}
ManagedEntry is one parent proxy (managed or YAML-owned).
func YAMLEntries ¶ added in v1.0.218
func YAMLEntries(entries []Entry) ([]ManagedEntry, error)
YAMLEntries converts YAML-seeded legacy URLs into read-only YAML-owned entries. An inline password in a YAML URL is RETAINED in memory only (yamlSecret — never persisted, returned or audited); an unparseable URL is an InvalidEntryError.
func (*ManagedEntry) Authority ¶ added in v1.0.218
func (e *ManagedEntry) Authority() string
Authority is the entry's canonical, credential-free authority.
func (*ManagedEntry) AuthorityHash ¶ added in v1.0.218
func (e *ManagedEntry) AuthorityHash() string
AuthorityHash is the entry's canonical authority hash.
func (*ManagedEntry) DisplayURL ¶ added in v1.0.218
func (e *ManagedEntry) DisplayURL() string
DisplayURL is the API/legacy-GET form: `scheme://host:port` with NO userinfo at all (the username is a separate field).
func (*ManagedEntry) HasInlineSecret ¶ added in v1.0.218
func (e *ManagedEntry) HasInlineSecret() bool
HasInlineSecret reports whether a YAML entry carries an in-memory inline credential (never the secret itself).
func (*ManagedEntry) LegacyURL ¶ added in v1.0.218
func (e *ManagedEntry) LegacyURL() string
LegacyURL is the credential-free `scheme://[username@]host:port` form persisted in the downgrade-compatible legacy list (admin_settings upstream_proxies) so a pre-v2 binary still sees the username.
func (*ManagedEntry) Spec ¶ added in v1.0.218
func (e *ManagedEntry) Spec() Spec
Spec returns the entry's authority inputs.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool manages the effective set of parent proxies (YAML-owned + managed) with failover. The zero value is a usable empty pool.
func (*Pool) BeginManualProbe ¶ added in v1.0.218
BeginManualProbe admits or refuses a manual probe run at instant now. When refused, code names the reason and retryAfter bounds the wait. An admitted run MUST be closed with EndManualProbe.
func (*Pool) CBParams ¶
CBParams returns the circuit-breaker parameters remembered from the last Configure.
func (*Pool) Configure ¶
Configure sets the YAML-owned entries and the circuit-breaker parameters (startup / YAML-reload path). Managed entries are untouched. An invalid or duplicate YAML set fails closed: the previous effective pool stays.
func (*Pool) DirectFallback ¶ added in v1.0.156
DirectFallback reports whether the pool is currently failing open to direct egress and how many requests have done so since startup.
func (*Pool) Effective ¶ added in v1.0.218
Effective computes the backend-derived mode: no_pool (empty), chained (≥1 eligible), no_eligible_parent (0 eligible, no request fell back yet), direct_fallback (0 eligible and a request fell back).
func (*Pool) EffectiveEntries ¶ added in v1.0.218
func (p *Pool) EffectiveEntries() []ManagedEntry
EffectiveEntries returns copies of every effective entry (YAML first).
func (*Pool) EndManualProbe ¶ added in v1.0.218
func (p *Pool) EndManualProbe()
EndManualProbe releases the single-flight slot of an admitted run.
func (*Pool) Entries ¶
Entries returns the effective pool as credential-FREE legacy entries (YAML first). It never carries a password.
func (*Pool) HealthCheck ¶
func (p *Pool) HealthCheck(source string) ProbeSummary
HealthCheck probes every credential-eligible parent with the shared classifier and stores the bounded outcome. Credential-ineligible entries (unusable, mismatch, requiresReplacement) are not probed and keep their state. Every probe is bounded by probeTimeout (5 s per entry). The count-only summary is what a manual run audits.
func (*Pool) Key ¶ added in v1.0.218
Key returns the loaded key (nil when unavailable) and the bounded reason.
func (*Pool) LegacyManagedEntries ¶ added in v1.0.218
LegacyManagedEntries returns the MANAGED entries as credential-free legacy URLs (the downgrade-compatible representation persisted beside the v2 document).
func (*Pool) List ¶
List returns the effective pool statuses for the UI/API. URLs are credential-free authorities.
func (*Pool) ManualProbeInFlight ¶ added in v1.0.218
ManualProbeInFlight reports whether an admitted manual probe run is executing. The read model exposes it so a client whose POST answer outran its deadline can resolve the run against the appliance (no run in flight + the entries' health advanced) instead of sizing a deadline from a possibly stale entry count (PR-C15 R7-B).
func (*Pool) Next ¶
Next returns the next ELIGIBLE upstream proxy using round-robin selection (C11). Returns nil if none is eligible (caller falls back to direct).
func (*Pool) ProbeConfig ¶ added in v1.0.218
ProbeConfig reports whether a periodic probe loop is configured and its interval.
func (*Pool) ProxyFunc ¶
ProxyFunc returns an http.Transport-compatible proxy selector. When an eligible parent exists, it returns that parent's authenticated URL (built here and nowhere else); it returns nil (direct connection) otherwise.
If the request context carries an Attribution slot (WithAttribution), the selected proxy is recorded there so the caller can feed the request's outcome back into that proxy's circuit breaker (CHAOS-11).
func (*Pool) Restore ¶ added in v1.0.218
Restore resets the pool to a captured state and rebuilds the effective pool from it (probe/breaker state starts fresh).
func (*Pool) SetDocument ¶ added in v1.0.218
SetDocument publishes a new MANAGED document after validating the whole effective pool (YAML + managed): duplicate authorities or invalid entries are refused with a typed error and the running pool stays unchanged.
func (*Pool) SetKey ¶ added in v1.0.218
SetKey installs the node-local credential key (nil with a bounded reason when it is unavailable) and re-derives every credential state.
func (*Pool) SetProbeInterval ¶ added in v1.0.218
SetProbeInterval records the periodic probe cadence (0 = none) for the read model's top-level probe block.
func (*Pool) SetProxies ¶
SetProxies is the LEGACY credential-free replacement of the managed set from URLs (import / compatibility paths). A URL carrying a password is refused; YAML-owned authorities are skipped (YAML owns them).
func (*Pool) Snapshot ¶ added in v1.0.218
Snapshot captures the pool's configuration (not its probe/breaker state).
func (*Pool) YAMLEntries ¶ added in v1.0.218
func (p *Pool) YAMLEntries() []ManagedEntry
YAMLEntries returns copies of the YAML-owned entries.
type PoolState ¶ added in v1.0.218
type PoolState struct {
YAML []ManagedEntry
Doc Document
Key *Keyring
KeyErr string
CBThreshold int
CBTimeout time.Duration
}
PoolState is a full Pool snapshot for test hermeticity (pair Snapshot with Restore around a test that mutates the process-global pool).
type ProbeState ¶ added in v1.0.218
type ProbeState struct {
Status string `json:"status"`
Reason string `json:"reason"`
CheckedAt string `json:"checkedAt,omitempty"`
// Source distinguishes a periodic health-loop verdict from an operator
// triggered manual check (empty while unprobed).
Source string `json:"source,omitempty"`
}
ProbeState is an entry's last probe outcome.
type ProbeSummary ¶ added in v1.0.218
type ProbeSummary struct {
Probed int `json:"probed"`
Healthy int `json:"healthy"`
Unhealthy int `json:"unhealthy"`
Skipped int `json:"skipped"` // credential-ineligible, not probed
}
ProbeSummary is the bounded, count-only outcome of one HealthCheck run (what the manual-probe audit line carries — never an authority, URL or transport error).
type Proxy ¶
type Proxy struct {
// Entry, URL and credState are IMMUTABLE after publication: rebuildLocked
// constructs a fresh Proxy per entry on every publication and never
// writes a published one, so a selected proxy is a complete, coherent
// generation for the whole in-flight operation (request, probe,
// attribution) that holds it.
Entry ManagedEntry
// URL is the credential-FREE authority URL (display, legacy status). The
// authenticated URL is built only by authenticatedURL, per selection.
URL *url.URL
// CB is shared by pointer across generations of the same (id, authority)
// — the breaker carries its own mutex, so continuity is race-safe.
CB *CircuitBreaker
// contains filtered or unexported fields
}
Proxy represents one parent proxy in the chain: its entry (never a credential-bearing URL), probe state and circuit breaker.
func (*Proxy) CredentialState ¶ added in v1.0.218
CredentialState returns the derived credential state.
func (*Proxy) Probe ¶ added in v1.0.218
func (up *Proxy) Probe() ProbeState
Probe returns the entry's last probe outcome.
type Sealed ¶ added in v1.0.218
type Sealed struct {
// EntryID is the immutable entry the credential was sealed FOR; it is
// bound cryptographically (AAD) and structurally, so ciphertext moved
// onto another entry — even one with the same authority — is mismatch.
EntryID string `json:"entryId"`
AuthorityHash string `json:"authorityHash"`
Ciphertext string `json:"ciphertext"` // base64(nonce || AES-GCM(pw, aad=entryID||0||authorityHash))
KeyID string `json:"keyId"`
SetAt string `json:"setAt"`
SetBy string `json:"setBy,omitempty"`
}
Sealed is a credential at rest: ciphertext under the node-local key, bound to the entry id AND the authority it was set for. It never carries plaintext.
type Spec ¶ added in v1.0.218
Spec is the client-facing shape of an entry's authority inputs.
func Normalize ¶ added in v1.0.218
Normalize validates and canonicalizes an authority specification: scheme lower-cased and restricted to http/https (the approved C4 grammar), host lower-cased, trailing-dot stripped and IDNA-encoded (bracketed IPv6 literals accepted), effective port defaulted per scheme, username free of ':' / '@' / '/'.
func SpecFromURL ¶ added in v1.0.218
SpecFromURL parses a legacy `scheme://[user[:pass]@]host[:port]` URL into a normalized Spec plus the plaintext password it carried (empty when none). Path, query and fragment are refused unless empty or "/".
func (Spec) Authority ¶ added in v1.0.218
Authority is the canonical `scheme://username@host:port` (username part omitted when empty). It never carries a password.
func (Spec) AuthorityHash ¶ added in v1.0.218
AuthorityHash is the hex SHA-256 of the canonical authority.
type Status ¶
type Status struct {
ID string `json:"id"`
URL string `json:"url"` // legacy field: scheme://host:port, NO userinfo (username is its own field)
Authority string `json:"authority"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username,omitempty"`
Source string `json:"source"`
Revision int64 `json:"revision"`
// CredentialState is derived (C4): none | configured | unusable | mismatch.
CredentialState string `json:"credentialState"`
Probe ProbeState `json:"probe"` // compatibility alias of health
Health Health `json:"health"` // contracted health truth (status/reason/lastProbeAt/source)
Healthy bool `json:"healthy"` // legacy: probe == healthy
Eligible bool `json:"eligible"`
Circuit string `json:"circuit"`
// Failures is the current consecutive-failure count tracked by the
// circuit breaker (resets to 0 on RecordSuccess).
Failures int64 `json:"failures"`
// OpenedAtMs is when the circuit last tripped open, as Unix
// milliseconds; 0 when the circuit is closed.
OpenedAtMs int64 `json:"openedAtMs,omitempty"`
// RetryAfterMs is the remaining time (ms) until the breaker allows a
// half-open probe; 0 when the circuit is not open.
RetryAfterMs int64 `json:"retryAfterMs,omitempty"`
}
Status is returned by the admin API (credential-free).