| index 39b18fc08..f5a4ad64b 100644 |
| --- a/internal/status/snapshot.go |
| +++ b/internal/status/snapshot.go |
| @@ -93,6 +93,15 @@ type Probes struct { |
| // unknown/not-applicable for both, never a guess. |
| Release string |
| Tunnel string |
| + |
| + // Aliases is this box's route_aliases rows (registry.RouteAlias), the |
| + // agent's reg.ListAliases() read at statusTick — carried through raw so |
| + // CollectBox can fold each row's DNS/cert stamp into Box.Metrics.Aliases |
| + // alongside RouteVersions above. A failed read is the caller's problem |
| + // (log-and-continue, see agent.go's statusTick), never CollectBox's: nil |
| + // here means "no aliases known this tick," identical on the wire to "no |
| + // alias rows exist." |
| + Aliases []registry.RouteAlias |
| } |
| |
| // BoxMetrics is the raw, PRIVATE block: real numbers, never banded. It lives |
| @@ -118,6 +127,33 @@ type BoxMetrics struct { |
| // no versioned route, same "absent, not empty" rule as the rest of this |
| // block. |
| RouteVersions map[string]string `json:"route_versions,omitempty"` |
| + // Aliases is issue #151's per-hostname DNS/cert status, keyed by the |
| + // alias hostname — the box's own route_aliases rows only (CollectBox is |
| + // called per-box, same scope as RouteVersions above), never a fleet-wide |
| + // join. It lives here for the identical reason RouteVersions does: |
| + // status/boxes/<label>.json is bucket-private (this package's own doc |
| + // comment, and dryrun.go's leak-scan self-check, which guards the PUBLIC |
| + // feed only — fleet.json, index.html, status.css — and deliberately |
| + // EXPECTS raw values inside status/boxes/*.json). BuildFleet copies Box's |
| + // fields into FleetBox one at a time by name and never names Metrics, so |
| + // these hostnames can never reach the public aggregation. Task 9's |
| + // console list door is the intended reader. Omitted entirely (not a |
| + // present-but-empty map) when this box has no alias rows, same |
| + // "absent, not empty" rule as RouteVersions. |
| + Aliases map[string]AliasReading `json:"aliases,omitempty"` |
| +} |
| + |
| +// AliasReading is one alias hostname's status as CollectBox saw it this |
| +// tick: Route is the route it points at (registry.RouteAlias.Host), DNSStatus |
| +// and CertExpiresAt are the box-stamped health columns (Task 2/5), carried |
| +// through unbanded like the rest of BoxMetrics. Route has no omitempty — an |
| +// alias with no route would be a data bug worth seeing, not a field worth |
| +// hiding; DNSStatus/CertExpiresAt omit when unstamped ("" / 0), same |
| +// unknown-stays-absent convention as every other Metrics field. |
| +type AliasReading struct { |
| + Route string `json:"route"` |
| + DNSStatus string `json:"dns_status,omitempty"` |
| + CertExpiresAt int64 `json:"cert_expires_at,omitempty"` |
| } |
| |
| // RouteCounts is aggregate-only: no host, no socket path, just how many. |
| @@ -340,6 +376,21 @@ func CollectBox(routes []registry.Route, p Probes, now time.Time) Box { |
| counts.Resident++ |
| } |
| } |
| + // aliasReadings is issue #151's Metrics.Aliases source, built once here |
| + // (not a second pass) from p.Aliases — nil (not an empty map) until the |
| + // first entry, same "nothing to report" convention as routeVersions |
| + // above, and for the identical reason: an all-alias-less box's Metrics |
| + // attachment check below must see exactly "nothing to report". |
| + var aliasReadings map[string]AliasReading |
| + for _, a := range p.Aliases { |
| + if aliasReadings == nil { |
| + aliasReadings = map[string]AliasReading{} |
| + } |
| + aliasReadings[a.Alias] = AliasReading{ |
| + Route: a.Host, DNSStatus: a.DNSStatus, CertExpiresAt: a.CertExpiresAt, |
| + } |
| + } |
| + |
| if awakeKnown && p.Listening != nil { |
| counts.Awake, counts.Asleep = &awake, &asleep |
| } |
| @@ -382,18 +433,20 @@ func CollectBox(routes []registry.Route, p Probes, now time.Time) Box { |
| } |
| |
| // Metrics attaches only when something is known — an all-unknown Probes |
| - // AND no versioned route publishes no block at all, the same "unknown |
| - // stays absent, never a guess" rule every other field on Box follows, |
| - // and it keeps a box that can't answer any of the seven fields |
| - // indistinguishable on the wire from a box running code that predates |
| - // Metrics entirely. |
| + // AND no versioned route AND no alias reading publishes no block at all, |
| + // the same "unknown stays absent, never a guess" rule every other field |
| + // on Box follows, and it keeps a box that can't answer any of the eight |
| + // fields indistinguishable on the wire from a box running code that |
| + // predates Metrics entirely. |
| if p.MemTotalBytes != nil || p.MemAvailBytes != nil || p.Load1 != nil || |
| - p.DiskFreeBytes != nil || p.Release != "" || p.Tunnel != "" || len(routeVersions) > 0 { |
| + p.DiskFreeBytes != nil || p.Release != "" || p.Tunnel != "" || |
| + len(routeVersions) > 0 || len(aliasReadings) > 0 { |
| box.Metrics = &BoxMetrics{ |
| MemTotalBytes: p.MemTotalBytes, MemAvailBytes: p.MemAvailBytes, |
| Load1: p.Load1, DiskFreeBytes: p.DiskFreeBytes, |
| Release: p.Release, Tunnel: p.Tunnel, |
| RouteVersions: routeVersions, |
| + Aliases: aliasReadings, |
| } |
| } |
| |