status: additive raw Metrics block on Box — private to the bucket, never published to fleet.json
Adds Box.Metrics (*BoxMetrics: mem total/avail, load1, disk free, release, tunnel state) as the one deliberate, documented exception to this package's band-everything privacy rule. CollectBox attaches it only when at least one field is known, mirroring the "unknown stays absent" rule every other Box field follows, so an old parser and a can't-answer box are indistinguishable on the wire. Excluded from fleet.json by construction (FleetBox copies Box's fields one at a time and never names Metrics) — dryrun.go's leak scan now proves it, requiring the raw fixture values inside status/boxes/*.json and forbidding them everywhere else DryRun publishes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 files changed,
+201
−3
internal/status/dryrun.go+64 −1internal/status/snapshot.go+79 −2internal/status/status_test.go+58 −0
diff --git a/internal/status/dryrun.go b/internal/status/dryrun.go| index 288254cab..4c46cbc26 100644 |
| --- a/internal/status/dryrun.go |
| +++ b/internal/status/dryrun.go |
| @@ -46,6 +46,25 @@ var dryRunNeedles = []string{ |
| "0.95", // fixture DiskFrac, verbatim |
| } |
| +// dryRunMetricsNeedles are the distinctive raw values Task 5's BoxMetrics |
| +// carries — real numbers and strings that Box.Metrics is the ONE deliberate |
| +// exception privileged to publish (see snapshot.go's package doc), and which |
| +// therefore MUST appear in the published box object. Unlike dryRunNeedles |
| +// above (banned everywhere, box object included), these are allowed inside |
| +// status/boxes/*.json and banned only from everything else DryRun publishes |
| +// — see the scope split in DryRun's scan loop below. Each value is |
| +// deliberately distinctive (the repeated 42s, a value with no legitimate |
| +// reason to appear in a template or a fleet-wide aggregate) so a false |
| +// negative from an incidental digit collision is effectively impossible. |
| +var dryRunMetricsNeedles = []string{ |
| + "4242424242", // fixture MemTotalBytes, verbatim |
| + "4242424241", // fixture MemAvailBytes, verbatim |
| + "4242424240", // fixture DiskFreeBytes, verbatim |
| + "42.42", // fixture Load1, verbatim |
| + "dryrun4242sha", // fixture Release, verbatim |
| + "reconnecting", // fixture Tunnel, verbatim (a real, valid Tunnel value) |
| +} |
| + |
| // ScanForLeaks reports which of banned's strings appear anywhere in j. It is |
| // the single implementation both DryRun's self-check and |
| // contract_test.go's scanForLeaks test helper run — the test package can't |
| @@ -89,6 +108,14 @@ func dryRunFixtures() ([]registry.Route, Probes) { |
| lag := int64(120) |
| adopting := true // a known reading, so adoption_running publishes non-null and gets scanned |
| expiredCanaries := 2 // ditto for expired_canaries — a known count, not the unexercised nil path |
| + |
| + // Task 5's raw Metrics fixtures: distinctive values (see |
| + // dryRunMetricsNeedles) that MUST land in the published box object and |
| + // MUST NOT land anywhere else DryRun publishes — the positive half of |
| + // proving Box.Metrics's private exception actually stays private. |
| + memTotal, memAvail, diskFree := int64(4242424242), int64(4242424241), int64(4242424240) |
| + load1 := 42.42 |
| + |
| probes := Probes{ |
| MachineID: "ip-10-0-0-7", |
| Region: "us-east-1", |
| @@ -99,7 +126,13 @@ func dryRunFixtures() ([]registry.Route, Probes) { |
| ExpiredCanaries: &expiredCanaries, |
| // Every hibernating route reports "listening" so Awake/Asleep publish |
| // non-null — an unexercised null field is a field DryRun can't scan. |
| - Listening: func(string) *bool { v := true; return &v }, |
| + Listening: func(string) *bool { v := true; return &v }, |
| + MemTotalBytes: &memTotal, |
| + MemAvailBytes: &memAvail, |
| + Load1: &load1, |
| + DiskFreeBytes: &diskFree, |
| + Release: "dryrun4242sha", |
| + Tunnel: "reconnecting", |
| } |
| return routes, probes |
| } |
| @@ -198,6 +231,13 @@ func DryRun(dir string, out io.Writer) error { |
| return fmt.Errorf("dry run: nothing published under status/ — publish path is broken") |
| } |
| + // boxKeySeen tracks whether the scan actually reached a status/boxes/ |
| + // object carrying the Metrics fixtures — the positive half of the |
| + // Metrics scope check below, the same reason appPublished exists for |
| + // the opt-in app: a leak-scan that finds nothing proves nothing about |
| + // whether the thing that SHOULD be there actually is. |
| + boxKeySeen := false |
| + |
| for _, key := range keys { |
| body, _, err := s.Get(ctx, key) |
| if err != nil { |
| @@ -209,6 +249,25 @@ func DryRun(dir string, out io.Writer) error { |
| return fmt.Errorf("dry run: %s leaked %v", key, leaks) |
| } |
| + // dryRunMetricsNeedles are scoped: status/boxes/*.json is Box.Metrics's |
| + // one privileged, private home (see snapshot.go's package doc), so |
| + // the raw fixture values are EXPECTED there and must NOT be scanned |
| + // out — that isn't a leak, it's the field doing its job. Every other |
| + // key DryRun publishes (fleet.json, index.html, status.css) is |
| + // exactly what BuildFleet/PublishFleet expose beyond the bucket, so |
| + // those raw values must never appear there. |
| + if strings.HasPrefix(key, "status/boxes/") { |
| + if leaks := ScanForLeaks(body, dryRunMetricsNeedles); len(leaks) == 0 { |
| + // None of the fixtures made it in at all — Metrics isn't |
| + // being attached, which means this check isn't exercising |
| + // anything (a false "no leak"). |
| + return fmt.Errorf("dry run: %s: expected raw Metrics fixtures %v, found none — Box.Metrics not attached?", key, dryRunMetricsNeedles) |
| + } |
| + boxKeySeen = true |
| + } else if leaks := ScanForLeaks(body, dryRunMetricsNeedles); len(leaks) > 0 { |
| + return fmt.Errorf("dry run: %s leaked raw Metrics fixture %v — Box.Metrics must never reach the public feed", key, leaks) |
| + } |
| + |
| // Page assets (index.html, status.css) aren't JSON and simply fail |
| // to decode here — silently skipped, since the band-digit check |
| // only means anything for objects that actually carry Load/Capacity |
| @@ -221,6 +280,10 @@ func DryRun(dir string, out io.Writer) error { |
| } |
| } |
| + if !boxKeySeen { |
| + return fmt.Errorf("dry run: no status/boxes/*.json object was scanned — Metrics scope check never ran") |
| + } |
| + |
| fmt.Fprintln(out, "dry-run: no leaks") |
| return nil |
| } |
diff --git a/internal/status/snapshot.go b/internal/status/snapshot.go| index 378e55f4d..b8afea2a3 100644 |
| --- a/internal/status/snapshot.go |
| +++ b/internal/status/snapshot.go |
| @@ -4,6 +4,23 @@ |
| // HERE, in the process that does the counting and banding, not downstream — |
| // a merge step or a page template can only leak what this package already |
| // let through. |
| +// |
| +// Box.Metrics is the one deliberate exception to that rule, not a hole in |
| +// it: a raw, unbanded block (real byte counts, a load average, the running |
| +// build, tunnel state) that CollectBox attaches to the Box it returns, which |
| +// WriteBox lands in status/boxes/<label>.json alongside everything else this |
| +// package computes. That file is bucket-private — the deployment bucket |
| +// blocks all public access (infra/modules/deployment-buckets), and the edge |
| +// process's fixed three-object menu (internal/edge/status.go) only ever |
| +// serves "/", "/fleet.json", and "/status.css"; a request for |
| +// status/boxes/<label>.json 404s the same as any other unlisted path. Only |
| +// fleet-status machinery (loadBoxes in fleet.go) and operators with bucket |
| +// credentials ever read it. The public merge step, fleet.go's BuildFleet, |
| +// copies Box's fields into FleetBox one at a time by field name and simply |
| +// never names Metrics — so the banded-words-only commitment on fleet.json |
| +// holds by construction, not by a filter that could be forgotten. See |
| +// dryrun.go's leak fixtures, which assert a raw metric value survives into |
| +// the box object but never into fleet.json or index.html. |
| package status |
| import ( |
| @@ -43,6 +60,37 @@ type Probes struct { |
| // zero; 0 is itself a real, confident answer when every canary route on |
| // the box is fresh, or there are none at all. |
| ExpiredCanaries *int |
| + |
| + // MemTotalBytes, MemAvailBytes, Load1, and DiskFreeBytes are the raw |
| + // numbers MemFrac/DiskFrac above are already computed FROM — CollectBox |
| + // still bands MemFrac/DiskFrac into Load/Capacity as always, but also |
| + // carries these through unbanded into Box.Metrics, the package's one |
| + // documented private exception (see the package doc comment). nil means |
| + // unknown, same as every other pointer field here. |
| + MemTotalBytes *int64 |
| + MemAvailBytes *int64 |
| + Load1 *float64 |
| + DiskFreeBytes *int64 |
| + // Release is the running binary's version.Build (a short sha or similar), |
| + // and Tunnel is one of ""|"connected"|"reconnecting" — both land in |
| + // Box.Metrics unbanded, like the four numeric fields above. "" means |
| + // unknown/not-applicable for both, never a guess. |
| + Release string |
| + Tunnel string |
| +} |
| + |
| +// BoxMetrics is the raw, PRIVATE block: real numbers, never banded. It lives |
| +// in status/boxes/<label>.json — which only fleet-status machinery and |
| +// operators read — and is deliberately NOT copied into FleetBox, so the |
| +// publicly served fleet.json keeps the banded-words-only commitment. See the |
| +// package doc comment for why this block is safe to exist at all. |
| +type BoxMetrics struct { |
| + MemTotalBytes *int64 `json:"mem_total_bytes,omitempty"` |
| + MemAvailBytes *int64 `json:"mem_avail_bytes,omitempty"` |
| + Load1 *float64 `json:"load1,omitempty"` |
| + DiskFreeBytes *int64 `json:"disk_free_bytes,omitempty"` |
| + Release string `json:"release,omitempty"` // running binary's version.Build |
| + Tunnel string `json:"tunnel,omitempty"` // ""|"connected"|"reconnecting" |
| } |
| // RouteCounts is aggregate-only: no host, no socket path, just how many. |
| @@ -68,7 +116,14 @@ type Replication struct { |
| // Box is one box's self-report: what CollectBox produces and WriteBox |
| // serializes. Every field here has already had its needles pulled — there is |
| -// no hostname, machine id, or raw ratio anywhere in this type. |
| +// no hostname, machine id, or raw ratio anywhere in this type — with exactly |
| +// one deliberate exception: Metrics, the raw/private block documented on the |
| +// package comment and on BoxMetrics itself. Every OTHER field on Box is |
| +// still bound by the no-raw-numbers rule; Metrics is not "one more field", |
| +// it's the one place that rule is intentionally suspended, gated by staying |
| +// bucket-private (see the package doc) and excluded from FleetBox by |
| +// construction (fleet.go's BuildFleet copies fields one at a time and never |
| +// names Metrics). |
| type Box struct { |
| GeneratedAt time.Time `json:"generated_at"` |
| Label string `json:"label"` |
| @@ -91,6 +146,12 @@ type Box struct { |
| // is exactly the kind of identifying detail this package's whole design |
| // refuses to carry, but how many is aggregate and safe. |
| ExpiredCanaries *int `json:"expired_canaries"` |
| + // Metrics is the raw/private exception documented above and on |
| + // BoxMetrics — omitted entirely (not a present-but-empty object) when |
| + // CollectBox has nothing to report, so an old parser reading a box this |
| + // package wrote before Metrics existed, and a box that genuinely can't |
| + // answer any of the six fields, produce the identical absent-key shape. |
| + Metrics *BoxMetrics `json:"metrics,omitempty"` |
| } |
| // replHealthyWithin is the lag under which replication counts as healthy. |
| @@ -246,7 +307,7 @@ func CollectBox(routes []registry.Route, p Probes, now time.Time) Box { |
| healthy = &h |
| } |
| - return Box{ |
| + box := Box{ |
| GeneratedAt: now, |
| Label: Label(p.MachineID), |
| Region: p.Region, |
| @@ -257,6 +318,22 @@ func CollectBox(routes []registry.Route, p Probes, now time.Time) Box { |
| AdoptionRunning: p.AdoptRunning, |
| ExpiredCanaries: p.ExpiredCanaries, |
| } |
| + |
| + // Metrics attaches only when something is known — an all-unknown Probes |
| + // 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 six 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 != "" { |
| + box.Metrics = &BoxMetrics{ |
| + MemTotalBytes: p.MemTotalBytes, MemAvailBytes: p.MemAvailBytes, |
| + Load1: p.Load1, DiskFreeBytes: p.DiskFreeBytes, |
| + Release: p.Release, Tunnel: p.Tunnel, |
| + } |
| + } |
| + |
| + return box |
| } |
| // WriteBox marshals a Box indented (human-diffable in the bucket) and writes |
diff --git a/internal/status/status_test.go b/internal/status/status_test.go| index f38b6d0ab..b3d907a25 100644 |
| --- a/internal/status/status_test.go |
| +++ b/internal/status/status_test.go |
| @@ -215,6 +215,64 @@ func TestWriteBoxRetriesOnceOnCASConflict(t *testing.T) { |
| } |
| } |
| +// ---- Task 5: raw Metrics block (private to the bucket) -------------------- |
| + |
| +// TestCollectBoxCarriesRawMetrics proves CollectBox attaches an unbanded |
| +// Metrics block straight through from Probes when the six fields are known — |
| +// the one deliberate exception to this package's band-everything rule (see |
| +// snapshot.go's package doc). |
| +func TestCollectBoxCarriesRawMetrics(t *testing.T) { |
| + mt, ma, df := int64(4056547328), int64(2028273664), int64(9999999) |
| + l1 := 0.42 |
| + b := status.CollectBox(nil, status.Probes{ |
| + MachineID: "m", MemTotalBytes: &mt, MemAvailBytes: &ma, Load1: &l1, DiskFreeBytes: &df, |
| + Release: "abc1234", Tunnel: "connected", |
| + }, time.Now()) |
| + m := b.Metrics |
| + if m == nil || *m.MemTotalBytes != mt || *m.Load1 != l1 || m.Release != "abc1234" || m.Tunnel != "connected" { |
| + t.Fatalf("metrics block wrong: %+v", m) |
| + } |
| +} |
| + |
| +// TestCollectBoxOmitsMetricsWhenAllUnknown proves the all-unknown case |
| +// really omits the block rather than attaching an empty-but-present |
| +// BoxMetrics{} — the same "unknown stays absent" rule every other Box field |
| +// follows, and what makes an old parser's absent-key shape and a |
| +// can't-answer box's shape identical on the wire. |
| +func TestCollectBoxOmitsMetricsWhenAllUnknown(t *testing.T) { |
| + b := status.CollectBox(nil, status.Probes{MachineID: "m"}, time.Now()) |
| + if b.Metrics != nil { |
| + t.Fatalf("metrics block should be nil when nothing is known, got %+v", b.Metrics) |
| + } |
| +} |
| + |
| +// TestBoxBackCompatParse: yesterday's Box shape (no metrics key) must parse |
| +// today, and a FUTURE box's unknown keys (inside or alongside metrics) must |
| +// not break today's parse either — additive wire shape, per CLAUDE.md's |
| +// "consumer = everything that PARSES the shape" rule. |
| +func TestBoxBackCompatParse(t *testing.T) { |
| + yesterday := []byte(`{"generated_at":"2026-08-03T10:00:00Z","label":"ada-stargazing", |
| + "routes":{"static":1,"resident":2,"hibernating":3,"awake":null,"asleep":null}, |
| + "load":"comfortable","capacity":"plenty", |
| + "replication":{"healthy":true,"lag_seconds":5}, |
| + "adoption_running":true,"expired_canaries":0}`) |
| + var b status.Box |
| + if err := json.Unmarshal(yesterday, &b); err != nil { |
| + t.Fatalf("yesterday's shape failed to parse: %v", err) |
| + } |
| + if b.Metrics != nil { |
| + t.Fatal("absent metrics must decode as nil, not zero-value") |
| + } |
| + tomorrow := []byte(`{"generated_at":"2026-08-05T10:00:00Z","label":"ada-stargazing", |
| + "routes":{"static":0,"resident":0,"hibernating":0,"awake":null,"asleep":null}, |
| + "load":null,"capacity":null,"replication":{"healthy":null,"lag_seconds":null}, |
| + "adoption_running":null,"expired_canaries":null, |
| + "metrics":{"mem_total_bytes":1,"some_future_field":true}}`) |
| + if err := json.Unmarshal(tomorrow, &b); err != nil { |
| + t.Fatalf("tomorrow's shape failed to parse: %v", err) |
| + } |
| +} |
| + |
| // ---- Task 3: fleet merge + publisher -------------------------------------- |
| // teststore is a fresh DirStore over a scratch directory — every fleet test |