carlos / platform Public

status: the layout report on Box.Metrics (plan 4, task 5, step 1)

RowLayout and LayoutReport, and Probes/BoxMetrics.Layout to carry them
through CollectBox, hostname-keyed like RouteVersions and private for
the same reason: BuildFleet never copies Metrics into the public feed.
Contract allowlist updated per amendment R15; fixtureProbes exercises
both the mode and the rows map so TestFleetSchemaAllowlisted proves no
leak (finding S14).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d16a8aeb2357e3a84bcb8fff6dd8a13496c54962 parent 82d2254
3 files changed, +117 −2
  • internal/status/contract_test.go +25 −1
  • internal/status/snapshot.go +42 −1
  • internal/status/snapshot_test.go +50 −0
diff --git a/internal/status/contract_test.go b/internal/status/contract_test.go
index 2aac4256b..a1638b3da 100644
--- a/internal/status/contract_test.go
+++ b/internal/status/contract_test.go
@@ -72,13 +72,23 @@ var allowedBoxKeys = map[string][]string{
// objects rather than plain strings: "metrics.upstreams.*[]" pins the
// per-upstream fields, so a future field on UpstreamState is a deliberate
// publication decision too, not a silent ride-along.
+ // "metrics.layout" (Task 5, isolated-exec plan 4) is the box's layout
+ // report: which mode it last converged to, and — hostname-keyed, same
+ // reasoning as metrics.route_versions and metrics.upstreams just above —
+ // every instance-kind route's word from the layout pass's own Classify.
+ // Safe on the identical structural ground: BuildFleet's field-by-field
+ // copy never names "metrics", so no hostname here can reach FleetBox or
+ // fleet.json regardless of what this map holds.
"": {"generated_at", "label", "region", "routes", "load", "capacity", "replication", "adoption_running", "config_path_running", "restart_path_running", "expired_canaries", "metrics"},
"routes": {"static", "resident", "hibernating", "awake", "asleep"},
"replication": {"healthy", "lag_seconds"},
- "metrics": {"mem_total_bytes", "mem_avail_bytes", "load1", "disk_free_bytes", "release", "tunnel", "route_versions", "upstreams"},
+ "metrics": {"mem_total_bytes", "mem_avail_bytes", "load1", "disk_free_bytes", "release", "tunnel", "route_versions", "upstreams", "layout"},
"metrics.route_versions": {wildcardAnyKey},
"metrics.upstreams": {wildcardAnyKey},
"metrics.upstreams.*[]": {"addr", "up", "since", "fails", "last_error"},
+ "metrics.layout": {"mode", "rows"},
+ "metrics.layout.rows": {wildcardAnyKey},
+ "metrics.layout.rows.*": {"layout", "reason"},
}
// allowedFleetKeys are THE schema for Fleet's JSON output. Same friction as
@@ -288,10 +298,24 @@ func fixtureProbes() status.Probes {
},
}
+ // One flat row and one foreign row, so both the bare "layout" field
+ // (mode) and the wildcarded, host-keyed "rows" map — including a row
+ // that carries a reason — are actually exercised by the allowlist walk,
+ // the same reasoning fixtureProbes' own comment gives for upstreams.
+ layoutReport := &status.LayoutReport{
+ Mode: "flat",
+ Rows: map[string]status.RowLayout{
+ "contract-layout.internal": {Layout: "flat"},
+ "contract-foreign.internal": {Layout: "foreign",
+ Reason: `db "x" socket "y" are neither the flat nor the nested defaults`},
+ },
+ }
+
return status.Probes{
MachineID: "test-machine-id",
Region: "us-west-1",
Upstreams: upstreams,
+ Layout: layoutReport,
MemFrac: &memFrac,
DiskFrac: &diskFrac,
Listening: listening,
diff --git a/internal/status/snapshot.go b/internal/status/snapshot.go
index dd757d7dd..2fd1b06ee 100644
--- a/internal/status/snapshot.go
+++ b/internal/status/snapshot.go
@@ -130,6 +130,39 @@ type Probes struct {
// answer (no file yet, stat failed), means unknown and the host is left
// out of Metrics.RouteDBBytes rather than published as 0.
DBBytes func(dbPath string) *int64
+
+ // Layout is the box's layout report (isolated-exec design §8.4): which
+ // mode the box last converged to and, per instance-kind route, the word
+ // the layout pass's own Classify gave it. The caller (internal/host's
+ // statusLoop) builds it from the armed layout.Pass and the routes it
+ // already listed; nil means no pass is armed on this box, which
+ // CollectBox carries straight through as "nothing to report" rather
+ // than guess at a mode. Hostname-keyed, like Aliases and RouteVersions
+ // above, which is why it lands in Box.Metrics and nowhere else.
+ Layout *LayoutReport
+}
+
+// RowLayout is one instance-kind route's layout, as the box's layout pass
+// classified it (activator.Classify): Layout is one of flat, nested,
+// fenced, conflict, foreign or external, and Reason carries the detail for
+// anything but flat and nested — a path, or another row's host — which is
+// why it is never shown on the console's instance page (see
+// instancepage.go's handleAppInstance): a member sees the word, an operator
+// reading status/boxes/<label>.json sees the reason too.
+type RowLayout struct {
+ Layout string `json:"layout"`
+ Reason string `json:"reason,omitempty"`
+}
+
+// LayoutReport is one box's whole layout picture: Mode is the intent the
+// box last converged to ("none" before any is recorded, "unreadable" when
+// the last read of it failed or named a mode this binary does not know,
+// otherwise "flat" or "nested"), and Rows is every instance-kind route on
+// the box, keyed by host. It carries real hostnames, so — like RowLayout
+// above — it is private: BoxMetrics.Layout, never a field on Box itself.
+type LayoutReport struct {
+ Mode string `json:"mode"`
+ Rows map[string]RowLayout `json:"rows"`
}
// UpstreamState is one upstream's health as the edge's prober currently sees
@@ -248,6 +281,13 @@ type BoxMetrics struct {
// readers. A host whose file could not be sized is absent, never 0; the
// whole map is omitted when no route could be sized.
RouteDBBytes map[string]int64 `json:"route_db_bytes,omitempty"`
+ // Layout is Probes.Layout carried straight through — the box's layout
+ // report, hostname-keyed like RouteVersions and RouteDBBytes above, and
+ // private for the identical reason: BuildFleet copies Box's fields into
+ // FleetBox one at a time by name and never names Metrics, so this can
+ // never reach the public fleet.json. nil (the field absent) means the
+ // box has no layout pass armed, not "every route reports flat".
+ Layout *LayoutReport `json:"layout,omitempty"`
}
// AliasReading is one alias hostname's status as CollectBox saw it this
@@ -616,7 +656,7 @@ func CollectBox(routes []registry.Route, p Probes, now time.Time) Box {
p.DiskFreeBytes != nil || p.Release != "" || p.Tunnel != "" ||
p.InstanceID != "" || p.Platform != "" || p.SystemChannel != "" || p.System != nil ||
len(routeVersions) > 0 || len(aliasReadings) > 0 || len(p.Upstreams) > 0 ||
- len(routeDBBytes) > 0 {
+ len(routeDBBytes) > 0 || p.Layout != nil {
box.Metrics = &BoxMetrics{
MemTotalBytes: p.MemTotalBytes, MemAvailBytes: p.MemAvailBytes,
Load1: p.Load1, DiskFreeBytes: p.DiskFreeBytes,
@@ -632,6 +672,7 @@ func CollectBox(routes []registry.Route, p Probes, now time.Time) Box {
// alongside another known field above, and omitempty drops it.
Upstreams: p.Upstreams,
RouteDBBytes: routeDBBytes,
+ Layout: p.Layout,
}
}
diff --git a/internal/status/snapshot_test.go b/internal/status/snapshot_test.go
index e9518cdeb..9745875d5 100644
--- a/internal/status/snapshot_test.go
+++ b/internal/status/snapshot_test.go
@@ -48,3 +48,53 @@ func TestBoxMetricsCarryThePlatformSelfUpdateState(t *testing.T) {
t.Fatalf("fleet.json leaked the private metrics: %s", fb)
}
}
+
+// TestCollectBoxCarriesTheLayoutReport pins Task 5's Probes.Layout: it
+// round-trips through CollectBox into Box.Metrics.Layout and onto the wire
+// under its json names, and a Probes with none produces no "layout" key at
+// all — the same "unknown stays absent, never a guess" rule every other
+// Metrics field follows.
+func TestCollectBoxCarriesTheLayoutReport(t *testing.T) {
+ at := time.Date(2026, 9, 25, 9, 0, 0, 0, time.UTC)
+ report := &LayoutReport{
+ Mode: "nested",
+ Rows: map[string]RowLayout{
+ "h.example.com": {Layout: "nested"},
+ "f.example.com": {Layout: "foreign", Reason: `db "x" socket "y" are neither the flat nor the nested defaults`},
+ },
+ }
+ b := CollectBox(nil, Probes{Layout: report}, at)
+ if b.Metrics == nil || b.Metrics.Layout == nil {
+ t.Fatalf("metrics.layout missing: %+v", b.Metrics)
+ }
+ if b.Metrics.Layout.Mode != "nested" {
+ t.Errorf("mode = %q, want nested", b.Metrics.Layout.Mode)
+ }
+ if row := b.Metrics.Layout.Rows["h.example.com"]; row.Layout != "nested" || row.Reason != "" {
+ t.Errorf("h.example.com row = %+v", row)
+ }
+ if row := b.Metrics.Layout.Rows["f.example.com"]; row.Layout != "foreign" || row.Reason == "" {
+ t.Errorf("f.example.com row = %+v", row)
+ }
+ out, err := json.Marshal(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, want := range []string{`"layout":{"mode":"nested"`, `"h.example.com":{"layout":"nested"}`, `"f.example.com":{"layout":"foreign","reason":`} {
+ if !strings.Contains(string(out), want) {
+ t.Errorf("json lacks %s: %s", want, out)
+ }
+ }
+
+ // A Probes with no Layout at all publishes no "layout" key — not the
+ // field present-and-null, absent entirely, same as every other Metrics
+ // field with nothing to report.
+ b2 := CollectBox(nil, Probes{Release: "v1"}, at)
+ out2, err := json.Marshal(b2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(string(out2), `"layout"`) {
+ t.Fatalf("layout key present with no Layout probe: %s", out2)
+ }
+}