carlos / platform Public

pricing: revise the statements plan against the codex review

Four P1s, all verified against the code before accepting.

Closure was not immutable. The plan guarded it in the caller, but
UpsertStatement runs its own CAS loop that reads only the ETag and
never the body, so one console could close a month while another
overwrote it. The guard moves inside that loop, checked against the
same read that produced the ETag, and a statement we cannot parse is
now an error rather than something to overwrite.

Census absence was treated as evidence of platform ownership, which
regressed the case it was meant to leave alone: censusPass writes once
a day and returns early after, so a route created at noon is legitimately
missing from a midnight snapshot while the registry knows its owner
perfectly well. An unresolved lookup now falls through to the registry.
The asymmetry is the point: a deleted route is in the census and keeps
its owner, a created one is not and resolves live.

Fleet box-hours no longer claim the metrics source. They come from
per-fleet objects this sweep never reads, so complete platform metrics
would have vouched for fleet days that never arrived; with no source
they are never complete, which is the honest answer.

Per-source days now require every discovered box to have reported,
rather than any of them, so a healthy box cannot vouch for a silent
one. A storage day carrying an unswept tenant store no longer counts
as complete, since the fold adds a zero for it that reads as no bytes.

Nine line anchors were off by a few and are corrected; one told the
implementer to replace the wrong line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev aa938bf4f19ba1a614840ff9c9ec3b91f3275d20 parent c30d059
2 files changed, +370 −43
  • docs/superpowers/plans/2026-09-08-invoice-grade-statements.md +270 −43
  • docs/superpowers/plans/CODEX-FINDINGS-invoice-grade-statements.md +100 −0
diff --git a/docs/superpowers/plans/2026-09-08-invoice-grade-statements.md b/docs/superpowers/plans/2026-09-08-invoice-grade-statements.md
index 8741ecb22..b32a3f3e3 100644
--- a/docs/superpowers/plans/2026-09-08-invoice-grade-statements.md
+++ b/docs/superpowers/plans/2026-09-08-invoice-grade-statements.md
@@ -25,14 +25,16 @@
### Task 1: Per-source day coverage
-`CoveredDays` counts a day as covered when *any* of metrics, edge or storage produced an object (`internal/console/economicspass.go:356`, `:403`, `:429`, accumulated at `:433-435`). So a storage sweep that ran on a day when edge collection failed still marks that day covered, and a statement reporting full coverage can be missing an entire day of egress. That is fine for modelling and wrong for billing.
+`CoveredDays` counts a day as covered when *any* of metrics, edge or storage produced an object (`internal/console/economicspass.go:355`, `:398`, `:429`, accumulated at `:433-435`). So a storage sweep that ran on a day when edge collection failed still marks that day covered, and a statement reporting full coverage can be missing an entire day of egress. That is fine for modelling and wrong for billing.
This task records coverage per source and lets a caller ask about one meter.
+**A day counts only when every discovered box of that source reported it** (codex 3). A per-source count that means "any box" lets a healthy box conceal a silent one: an account whose app lives on the silent box loses a day while the statement reads complete. Strict counting fails safe — it refuses to call a month complete rather than billing a short one. The known bound is that a decommissioned box, which `ListDirs` still discovers because it once wrote, blocks completeness until it is pruned; that failure direction is "we do not bill", which is the right way round, and pruning belongs with the later billing plan.
+
**Files:**
-- Modify: `internal/usage/usage.go` (append after the meter block ending at :42)
+- Modify: `internal/usage/usage.go` (append after the meter block ending at :39)
- Modify: `internal/usage/statement.go:19-27` (the `Statement` struct)
-- Modify: `internal/console/economicspass.go` (`usageSweep` at :241-259, the day loop at :343-437, `statementsPass` write at :800-820)
+- Modify: `internal/console/economicspass.go` (`usageSweep` at :241-258, the day loop at :341-435, `statementsPass` write at :800-820)
- Test: `internal/usage/passes_test.go`, `internal/console/economicspass_test.go`
**Interfaces:**
@@ -51,11 +53,14 @@ In `internal/usage/passes_test.go`, append:
```go
func TestMeterSource(t *testing.T) {
cases := map[string]string{
- MeterCPUCoreHours: SourceMetrics,
- MeterMemGBHours: SourceMetrics,
- MeterInstanceHours: SourceMetrics,
- MeterFleetBoxHoursServer: SourceMetrics,
- MeterFleetBoxHoursDevice: SourceMetrics,
+ MeterCPUCoreHours: SourceMetrics,
+ MeterMemGBHours: SourceMetrics,
+ MeterInstanceHours: SourceMetrics,
+ // Fleet box-hours come from fleetMeters, which reads per-fleet
+ // objects this sweep never looks at. No source here means never
+ // complete, which is the honest answer until they get their own.
+ MeterFleetBoxHoursServer: "",
+ MeterFleetBoxHoursDevice: "",
MeterEgressGB: SourceEdge,
MeterRequests1k: SourceEdge,
MeterBackhaulGB: SourceEdge,
@@ -102,7 +107,7 @@ Expected: FAIL to compile — `undefined: SourceMetrics`, `undefined: MeterSourc
- [ ] **Step 3: Add the sources and `MeterSource`**
-In `internal/usage/usage.go`, after the meter const block that ends at line 42:
+In `internal/usage/usage.go`, after the meter const block that ends at line 39:
```go
// Collection sources — the pass-visible daily object a meter's value is
@@ -116,12 +121,17 @@ const (
)
// MeterSource names the collection source a meter derives from, or "" for a
-// meter no daily object produces. Fleet box-hours are derived from the same
-// metrics rollups as everything else in SourceMetrics.
+// meter whose completeness this sweep cannot vouch for.
+//
+// The fleet box-hour meters return "" deliberately: fleetMeters
+// (console/economicspass.go) derives them from per-fleet objects that
+// sweepUsage never reads, so platform metrics coverage says nothing about
+// them. Claiming SourceMetrics would let complete platform data vouch for
+// fleet days that never arrived. They need their own coverage signal, which
+// belongs with the plan that bills fleet boxes.
func MeterSource(meter string) string {
switch meter {
- case MeterCPUCoreHours, MeterMemGBHours, MeterInstanceHours,
- MeterFleetBoxHoursServer, MeterFleetBoxHoursDevice:
+ case MeterCPUCoreHours, MeterMemGBHours, MeterInstanceHours:
return SourceMetrics
case MeterEgressGB, MeterRequests1k, MeterBackhaulGB, MeterBackhaulRequests1k:
return SourceEdge
@@ -224,6 +234,65 @@ func TestStatementsPassCountsSourcesSeparately(t *testing.T) {
t.Error("egress must not be complete with 1 of 3 edge days")
}
}
+
+// One box reporting must never vouch for a box that went silent.
+func TestStatementsPassSourceDaysNeedsEveryBox(t *testing.T) {
+ fx := newConsoleFixture(t)
+ ctx := context.Background()
+ now := time.Date(2026, 8, 1, 23, 0, 0, 0, time.UTC)
+
+ // Two edge boxes are discoverable; only one reports 1 August.
+ putStoreJSON(t, fx.rel, usage.EdgeDayKey("box1", "2026-07-31"), usage.EdgeDay{})
+ putStoreJSON(t, fx.rel, usage.EdgeDayKey("box2", "2026-07-31"), usage.EdgeDay{})
+ putStoreJSON(t, fx.rel, usage.EdgeDayKey("box1", "2026-08-01"), usage.EdgeDay{})
+
+ if err := fx.srv.statementsPass(ctx, now); err != nil {
+ t.Fatal(err)
+ }
+ raw, _, err := fx.rel.Get(ctx, usage.StatementKey(fixtureAcct, "2026-08"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var st usage.Statement
+ if err := json.Unmarshal(raw, &st); err != nil {
+ t.Fatal(err)
+ }
+ if got := st.SourceDays[usage.SourceEdge]; got != 0 {
+ t.Errorf("edge source days = %d, want 0 — box2 reported nothing on 1 Aug", got)
+ }
+ if st.CoveredDays != 1 {
+ t.Errorf("CoveredDays = %d, want 1 — box1 alone still covers for modelling", st.CoveredDays)
+ }
+}
+
+// A storage day that could not size a tenant store is covered, never complete.
+func TestStatementsPassUnsweptStorageIsNotComplete(t *testing.T) {
+ fx := newConsoleFixture(t)
+ ctx := context.Background()
+ now := time.Date(2026, 8, 1, 23, 0, 0, 0, time.UTC)
+
+ putStoreJSON(t, fx.rel, usage.StorageDayKey("2026-08-01"), usage.StorageDay{
+ V: 1, Date: "2026-08-01",
+ Apps: map[string]usage.AppStorage{
+ fixtureAcct + "/" + fixtureApp: {BucketBytes: 1000, StoreUnswept: true},
+ },
+ })
+
+ if err := fx.srv.statementsPass(ctx, now); err != nil {
+ t.Fatal(err)
+ }
+ raw, _, err := fx.rel.Get(ctx, usage.StatementKey(fixtureAcct, "2026-08"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var st usage.Statement
+ if err := json.Unmarshal(raw, &st); err != nil {
+ t.Fatal(err)
+ }
+ if got := st.SourceDays[usage.SourceStorage]; got != 0 {
+ t.Errorf("storage source days = %d, want 0 — a store went unswept", got)
+ }
+}
```
- [ ] **Step 7: Run it to verify it fails**
@@ -234,44 +303,76 @@ Expected: FAIL — `st.SourceDays` is nil, so the metrics assertion reports 0.
- [ ] **Step 8: Carry per-source counts through the sweep**
-In `internal/console/economicspass.go`, add one field to `usageSweep` (after `CoveredDays, PeriodDays int` at :255):
+In `internal/console/economicspass.go`, add one field to `usageSweep` (after `CoveredDays, PeriodDays int` at :251):
```go
- // SourceDays counts, per usage.Source*, the days in [from, to] that
- // produced that source's object on any box. CoveredDays is the OR of
- // these; this is the per-source detail billing needs.
+ // SourceDays counts, per usage.Source*, the days in [from, to] on which
+ // EVERY discovered box of that source produced its object. CoveredDays
+ // asks "did anything report"; this asks "did nothing go missing", which
+ // is the only version a bill can rest on — one healthy box must never
+ // vouch for a silent one.
SourceDays map[string]int
```
-Initialise it in the `sweep := &usageSweep{...}` literal at :308:
+Initialise it in the `sweep := &usageSweep{...}` literal at :307:
```go
SourceDays: map[string]int{},
```
-In the day loop, replace line 344 (`covered := false`) with:
+In the day loop, replace line 342 (`covered := false`) with — note the line number, 341 is `date := usage.DayOf(d)` and must be left alone:
```go
covered := false
- srcSeen := map[string]bool{}
+ metricsDays, edgeDays := 0, 0
+ storageOK, censusOK := false, false
```
-Add a `srcSeen` line beside each of the three existing `covered = true` statements:
+Then, beside each existing `covered = true`, count the box that reported:
-- at `:356` (metrics arm, after `covered = true`): `srcSeen[usage.SourceMetrics] = true`
-- at `:403` (edge arm, after `covered = true`): `srcSeen[usage.SourceEdge] = true`
-- at `:429` (storage arm, inside the `jerr == nil` branch, after `covered = true`): `srcSeen[usage.SourceStorage] = true`
+- at `:355` (metrics arm, after `covered = true`): `metricsDays++`
+- at `:398` (edge arm, after `covered = true`): `edgeDays++`
+- at `:429` (storage arm, inside the `jerr == nil` branch, after `covered = true`): replace with the unswept guard below
-Then add a census arm immediately before the `if covered {` block at :433, so `app_months` has a completeness signal too:
+The storage arm needs more than a flag (codex 4). A `StorageDay` that decodes cleanly can still be missing tenant-store occupancy: `AppStorage.StoreUnswept` marks a store the pass could not size, and the fold adds `BucketBytes + StoreBytes` with the latter left at zero (`internal/usage/storage.go:5-11`). Thirty-one such days would report storage complete while understating every tenant bucket. So replace the body of the `jerr == nil` branch at :428-430 with:
+
+```go
+ if jerr := json.Unmarshal(raw, &sd); jerr == nil {
+ covered = true
+ storageOK = true
+ for _, as := range sd.Apps {
+ if as.StoreUnswept {
+ // A store the pass could not size contributes a
+ // zero that reads as "no bytes". The day is
+ // covered for modelling, never complete for
+ // billing.
+ storageOK = false
+ break
+ }
+ }
+ }
+```
+
+Then add a census arm immediately before the `if covered {` block at :433, so `app_months` has a completeness signal too, and accumulate strictly:
```go
if _, _, gerr := s.cfg.Releases.Get(ctx, usage.CensusDayKey(date)); gerr == nil {
- srcSeen[usage.SourceCensus] = true
+ censusOK = true
covered = true
}
- for src := range srcSeen {
- sweep.SourceDays[src]++
+ // Strict: every discovered box must have reported this day.
+ if len(metricsBoxes) > 0 && metricsDays == len(metricsBoxes) {
+ sweep.SourceDays[usage.SourceMetrics]++
+ }
+ if len(edgeBoxes) > 0 && edgeDays == len(edgeBoxes) {
+ sweep.SourceDays[usage.SourceEdge]++
+ }
+ if storageOK {
+ sweep.SourceDays[usage.SourceStorage]++
+ }
+ if censusOK {
+ sweep.SourceDays[usage.SourceCensus]++
}
```
@@ -334,7 +435,7 @@ The fix follows a pattern this repo already has. `CensusDay` exists, in its own
- Produces:
- `usage.CensusUnit{Account, App string}`
- `usage.CensusDay.Units map[string]CensusUnit`
- - `func (s *Server) dayOwner(ctx context.Context, date, unit string, routes []registry.Route) (acct, app string)` — unexported, used by `sweepUsage`.
+ - `func (s *Server) dayOwnerFunc(ctx context.Context, date string, routes []registry.Route) func(string) (string, string)` — unexported, used by `sweepUsage`'s day loop.
- [ ] **Step 1: Write the failing test**
@@ -383,7 +484,63 @@ func TestSweepKeepsOwnerAfterRouteDeleted(t *testing.T) {
}
```
-Note on the fixture: `fixtureAcct` is `release.OpsAccount` (`economicspass_test.go:41-44`), which is also `unitOwner`'s fallback. If that made the test pass either way, change the census entry and the assertion to a non-ops account name and assert the ops statement does **not** carry the hours.
+**The fixture account will make this test pass either way, so do not use it.** `fixtureAcct` is `release.OpsAccount` (`economicspass_test.go:41-44`), which is exactly what `unitOwner` returns when it finds nothing — so a broken implementation and a correct one produce the same statement. Use a distinct account instead: seed the census `Units` entry with `{Account: "acme", App: fixtureApp}`, assert `usage.StatementKey("acme", "2026-08")` carries the 0.5, and assert the ops statement carries **zero** `cpu_core_hours`. Without both halves the test proves nothing.
+
+Add a second test for the created-after-snapshot case the fallback protects:
+
+```go
+// A route created after the day's census was written must still be
+// attributed to its owner, not to ops.
+func TestSweepFallsBackForRouteMissingFromCensus(t *testing.T) {
+ fx := newConsoleFixture(t)
+ ctx := context.Background()
+
+ // The census exists for the day but predates this route.
+ putStoreJSON(t, fx.rel, usage.CensusDayKey("2026-08-01"), usage.CensusDay{
+ V: 1, Date: "2026-08-01",
+ Accounts: map[string]usage.CensusAccount{},
+ Units: map[string]usage.CensusUnit{"carlos-app@other.example.com": {Account: "acme", App: "other"}},
+ })
+ putStoreJSON(t, fx.rel, metrics.DayKey("box1", "2026-08-01"), metrics.DayRollup{
+ Hours: []metrics.HourRollup{{
+ Units: map[string]map[string]metrics.Stat{
+ "carlos-app@late.example.com": {"cpu": {Avg: 0.5, N: 60}},
+ },
+ }},
+ })
+
+ reg, err := registry.Open(fx.regPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := reg.Upsert(registry.Route{
+ Host: "late.example.com", Kind: "instance", Status: "ready",
+ App: "late", Account: "acme", Version: "v1",
+ }); err != nil {
+ t.Fatal(err)
+ }
+ reg.Close()
+
+ if err := fx.srv.statementsPass(ctx, time.Date(2026, 8, 1, 23, 0, 0, 0, time.UTC)); err != nil {
+ t.Fatal(err)
+ }
+ raw, _, err := fx.rel.Get(ctx, usage.StatementKey("acme", "2026-08"))
+ if err != nil {
+ t.Fatalf("acme statement missing — the late route fell to ops: %v", err)
+ }
+ var st usage.Statement
+ if err := json.Unmarshal(raw, &st); err != nil {
+ t.Fatal(err)
+ }
+ if got := st.Meters[usage.MeterCPUCoreHours]; !approxF(got, 0.5) {
+ t.Errorf("acme cpu_core_hours = %v, want 0.5", got)
+ }
+}
+```
+
+Confirm the account field name and how `release.RouteAccount` normalises it before writing the `registry.Route` literal:
+
+Run: `grep -n 'func RouteAccount' internal/release/*.go`
- [ ] **Step 2: Run it to verify it fails**
@@ -468,16 +625,23 @@ func (s *Server) dayOwnerFunc(ctx context.Context, date string, routes []registr
if cu, ok := cd.Units[unit]; ok {
return cu.Account, cu.App
}
- // The census exists and knows its units, so a unit absent from
- // it genuinely had no route that day.
- return release.OpsAccount, ""
}
+ // Absence from the census is NOT evidence of platform ownership
+ // (codex 2). censusPass writes once per day and returns early
+ // afterwards (economicspass.go:99), so a route created after that
+ // day's snapshot is legitimately missing from it while the live
+ // registry knows exactly who owns it. Falling through to the
+ // registry keeps the win — a DELETED route is present in the
+ // census and keeps its owner — without inventing a regression for
+ // a CREATED one.
acct, app, _ := unitOwner(routes, unit)
return acct, app
}
}
```
+The asymmetry is the whole point, and it is worth being explicit about why it is safe. A route that existed at snapshot time is in the census, so deleting it later cannot move its usage. A route created after snapshot time is absent from the census but present in the registry, so it resolves correctly too. The only case still wrong is a route both created *and* deleted between two snapshots, which loses its usage to ops exactly as it does today — no better, no worse, and it needs the collection-time attribution the meters plan carries.
+
Immediately after `date := usage.DayOf(d)` in the day loop (`:343`), add:
```go
@@ -529,7 +693,7 @@ old behaviour, so nothing regresses."
`statementsPass` derives one month from `now` (`internal/console/economicspass.go:748-752`) and never revisits it. Host metrics only roll an hour up *after* it ends (`internal/host/metrics_loop.go:179-208`), so the last hours of a month land in the next month, when nothing is updating the old one. A bill drawn from that statement is short by up to a day.
-`billPass` already solved the same problem for Cost Explorer pulls with a two-period loop and a day-3 grace (`:853-870`, `billCEDayGrace = 3` at `:824`). This task gives `statementsPass` the same shape, and then closes the month so it stops moving under a bill.
+`billPass` already solved the same problem for Cost Explorer pulls with a two-period loop and a day-3 grace (`:853-870`, `billCEDayGrace = 3` at `:827`). This task gives `statementsPass` the same shape, and then closes the month so it stops moving under a bill.
**Files:**
- Modify: `internal/usage/statement.go` (the `Statement` struct)
@@ -647,7 +811,7 @@ In `internal/usage/statement.go`, after `SourceDays`:
- [ ] **Step 4: Give `statementsPass` two periods**
-In `internal/console/economicspass.go`, add beside `billCEDayGrace` (:824):
+In `internal/console/economicspass.go`, add beside `billCEDayGrace` (:827):
```go
// statementCloseDayGrace is the day of the following month after which the
@@ -696,25 +860,88 @@ Inside it, compute `daysInMonth` from the period's own month rather than from `n
and use `start`/`end` where the old body used `monthStart`/`nowUTC` for the sweep and fold calls.
-- [ ] **Step 5: Skip a month that is already closed, and set `Closed` when closing**
+- [ ] **Step 5: Make closure immutable inside the CAS loop**
+
+A check in the caller cannot hold (codex 1). `UpsertStatement` runs its own read-modify-write: it Gets purely for the ETag and never inspects the body (`internal/usage/statement.go:47-60`). So writer A can read an open statement, writer B can close it, and A's Put still succeeds against B's ETag on retry — overwriting a closed month. A failed preliminary Get in the caller would skip the guard too. Two consoles run this pass, so this is a live race, not a theoretical one.
-In the write loop inside `statementsForMonth`, before the upsert:
+Enforce it where the ETag is taken. In `internal/usage/statement.go`, replace the loop body:
```go
- if raw, _, err := s.cfg.Releases.Get(ctx, usage.StatementKey(acct, month)); err == nil {
- var prev usage.Statement
- if json.Unmarshal(raw, &prev) == nil && prev.Closed {
- continue // a closed month never changes again
+ for attempt := 0; attempt < 3; attempt++ {
+ raw, etag, err := s.Get(ctx, key)
+ if err != nil {
+ if !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ etag = ""
+ } else {
+ // Closure is enforced HERE, against the same read that
+ // produced the ETag we are about to write against — a check
+ // in the caller races with another console's close.
+ var prev Statement
+ if jerr := json.Unmarshal(raw, &prev); jerr != nil {
+ return fmt.Errorf("usage: %s: %w", key, jerr)
+ }
+ if prev.Closed {
+ return nil // a closed month never changes again
}
}
+ if _, err := s.Put(ctx, key, body, etag); err == nil {
+ return nil
+ } else if !errors.Is(err, release.ErrCASConflict) {
+ return err
+ }
+ }
```
-and add to the `usage.Statement{...}` literal:
+Note the parse error is now returned rather than ignored: a statement we cannot read is not a statement we may overwrite.
+
+Then add to the `usage.Statement{...}` literal in `statementsForMonth`:
```go
Closed: closing,
```
+No caller-side guard is needed, and none should be added — one place enforces this.
+
+- [ ] **Step 5b: Test the race directly**
+
+In `internal/usage/passes_test.go`:
+
+```go
+func TestUpsertStatementRefusesClosedMonth(t *testing.T) {
+ s := release.NewDirStore(t.TempDir())
+ ctx := context.Background()
+
+ closed := Statement{V: 1, Account: "acme", Month: "2026-08", Closed: true,
+ Meters: map[string]float64{MeterInstanceHours: 10}}
+ if err := UpsertStatement(ctx, s, closed); err != nil {
+ t.Fatal(err)
+ }
+ // A writer that read the month before it closed now tries to write.
+ late := Statement{V: 1, Account: "acme", Month: "2026-08",
+ Meters: map[string]float64{MeterInstanceHours: 999}}
+ if err := UpsertStatement(ctx, s, late); err != nil {
+ t.Fatalf("a refused write is not an error: %v", err)
+ }
+ raw, _, err := s.Get(ctx, StatementKey("acme", "2026-08"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var got Statement
+ if err := json.Unmarshal(raw, &got); err != nil {
+ t.Fatal(err)
+ }
+ if got.Meters[MeterInstanceHours] != 10 {
+ t.Errorf("closed statement moved to %v — closure is not immutable", got.Meters[MeterInstanceHours])
+ }
+}
+```
+
+Run: `GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod GOPROXY=off go test ./internal/usage/ -run TestUpsertStatementRefusesClosedMonth -count=1 -v`
+
+Expected: FAIL before the change (999 overwrites 10), PASS after.
+
- [ ] **Step 6: Run the tests to verify they pass**
Run: `GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod GOPROXY=off TMPDIR=/tmp go test ./internal/console/ -run TestStatementsPass -count=1 -v`
@@ -749,7 +976,7 @@ Billing reads only closed statements."
**Spec coverage.** Build step 1 (previous-month catch-up and closure) is Task 3. Step 2 (ownership at collection time) is Task 2. Step 3 (invoice-grade collection: per-meter completeness) is Task 1's `SourceDays` and `MeterComplete`.
-**One part of step 3 is deliberately not here.** The spec also names durable usage writes: `usageFlush` passes `drain()` as an argument so counters are emptied whether or not the write succeeds, and the caller only logs the failure (`internal/host/usage_loop.go:24`, doc comment at `:51` conceding the window is lost). Fixing that means a retry queue on the box, in `internal/host`, with its own cross-compile exposure — a different subsystem from the console-side pass this plan touches, and one whose analytics half already has a `Requeue` precedent to follow. It belongs in the meters plan alongside the two new collectors, not here. **Until it lands, `MeterComplete` can report a day covered whose counters were silently dropped**, so it is necessary but not yet sufficient for billing.
+**One part of step 3 is deliberately not here.** The spec also names durable usage writes: `usageFlush` passes `drain()` as an argument so counters are emptied whether or not the write succeeds, and the caller only logs the failure (`internal/host/usage_loop.go:24`, doc comment at `:49-50` conceding the window is lost). Fixing that means a retry queue on the box, in `internal/host`, with its own cross-compile exposure — a different subsystem from the console-side pass this plan touches, and one whose analytics half already has a `Requeue` precedent to follow. It belongs in the meters plan alongside the two new collectors, not here. **Until it lands, `MeterComplete` can report a day covered whose counters were silently dropped**, so it is necessary but not yet sufficient for billing.
**Placeholders:** none. Every step names the file, the line, and the code.
diff --git a/docs/superpowers/plans/CODEX-FINDINGS-invoice-grade-statements.md b/docs/superpowers/plans/CODEX-FINDINGS-invoice-grade-statements.md
new file mode 100644
index 000000000..5edc269e0
--- /dev/null
+++ b/docs/superpowers/plans/CODEX-FINDINGS-invoice-grade-statements.md
@@ -0,0 +1,100 @@
+**The plan at `c30d059` is not executable as written.** These findings come from static comparison with that commit’s code; I did not modify files or run the proposed implementation.
+
+1. **[P1] Task 3 Step 5 — the closure guard does not make a closed statement immutable.**
+
+ The guard reads `Closed` before calling [`UpsertStatement`](internal/usage/statement.go:41), which independently reads the latest ETag and retries conflicts without inspecting the existing body. Writer A can observe an open statement, writer B can close it, and A can then overwrite B’s closed statement. A transient failure of the preliminary GET also bypasses the guard.
+
+ **Fix:** enforce closure inside `UpsertStatement`’s CAS loop. On every attempt, decode the body associated with that attempt’s ETag and return without writing if it is closed. Refuse read/decode errors other than absence. Add a test that inserts a closing write between another writer’s read and write.
+
+2. **[P1] Task 2 Step 5 — absence from the first census does not mean a unit had no owner that day.**
+
+ `censusPass` writes once and subsequently returns immediately when the object exists ([`economicspass.go:99`](internal/console/economicspass.go:99)). If the census contains existing routes at midnight and a customer creates another route at noon, `dayOwnerFunc` sends that new route’s afternoon usage to ops—even while the live registry correctly identifies its owner. This is a regression from `unitOwner`, and closing the statement makes it permanent.
+
+ **Fix:** preserve ownership for routes appearing after the snapshot through collection-time attribution or durable ownership events. An unresolved census lookup must remain unknown and prevent the affected meter from being considered invoice-complete; it cannot establish platform ownership. Add a test that writes the census, creates a non-ops route, and records its usage afterward.
+
+3. **[P1] Task 1 Steps 3 and 8–9 — fleet completeness is taken from the wrong objects.**
+
+ Both fleet meters map to `SourceMetrics`, but `sweepUsage` counts only platform-root metrics. [`fleetMeters`](internal/console/economicspass.go:644) reads separate `fleets/<fleet>/metrics/boxes/...` objects. Consequently, complete platform metrics can make missing fleet hours report complete; complete fleet metrics with no platform metrics report incomplete.
+
+ More generally, copying one deployment-wide “any box reported” count into every account’s statement lets one reporting box conceal another required box’s missing day.
+
+ **Fix:** carry coverage from the readers that actually produce each meter. Fleet coverage must come from `fleetMeters`, independently of platform metrics, and completeness must account for the relevant account’s required producers. Test complete fleet/missing platform data and the inverse.
+
+4. **[P1] Task 1 Step 8 — storage objects can explicitly contain incomplete measurements.**
+
+ A successfully decoded `StorageDay` is always counted as complete, even when an app carries [`StoreUnswept: true`](internal/usage/storage.go:5). The existing fold nevertheless adds `BucketBytes + StoreBytes`; failed store sizing leaves the latter at zero. Thirty-one such objects therefore yield `MeterComplete(MeterStorageGBMonth) == true` despite missing tenant-store occupancy.
+
+ **Fix:** propagate `StoreUnswept` into storage completeness. At minimum, with the proposed global counts, an unswept app must prevent that day counting as complete storage coverage; account-specific coverage can avoid blocking unaffected accounts. Add an unswept-storage test. This is separate from the deferred box-side durable-write fix.
+
+5. **[P1] Task 2 Step 1 and Task 3 Step 1 — the test code cannot compile after the prescribed implementation.**
+
+ [`metrics.DayRollup.Hours`](internal/metrics/rollup.go:34) is `map[string]metrics.HourAgg`, not a slice. Neither `metrics.HourRollup` nor `metrics.Stat` exists. All three new nonempty rollup literals need this shape:
+
+ ```go
+ Hours: map[string]metrics.HourAgg{
+ "09": {Units: map[string]map[string]metrics.Agg{
+ "carlos-app@a.example.com.service": {
+ "cpu": {Avg: 0.5, N: 60},
+ },
+ }},
+ },
+ ```
+
+ Both new `approxF(got, 0.5)` calls also fail. The existing [`approxF`](internal/console/economicspass_test.go:336) is a closure local to `TestStatementsPassGolden`, takes three arguments, and returns nothing.
+
+ **Fix:** use the real rollup shapes and `math.Abs(got-0.5) > 1e-9`, using the already-imported `math`. Otherwise Tasks 2 and 3 cannot reach green commits.
+
+6. **[P1] Task 3 Step 4 — the extracted body retains undefined `nowUTC`.**
+
+ The instructions replace `monthStart`/`nowUTC` in sweep and fold calls, but [`UpdatedAt: nowUTC`](internal/console/economicspass.go:813) remains outside its scope. The [`fleetMeters` call](internal/console/economicspass.go:762) also needs an explicit replacement; it currently uses both old variable names.
+
+ **Fix:** prescribe all four replacements:
+
+ ```go
+ s.sweepUsage(ctx, start, end, routes)
+ s.foldStorageAndCensus(ctx, start, end, meters)
+ s.fleetMeters(ctx, start, end)
+ // Statement literal:
+ UpdatedAt: now,
+ ```
+
+ The other dependencies are sound: `routes` is passed; `accounts` is declared inside the moved body; `a.Sqid` is a field access, not a missing variable; both helper methods remain available through `s`. `reg` is only needed by the outer method—`fleetMeters` opens its own registry.
+
+7. **[P2] Task 2 Step 1 — the test passes without the ownership implementation once its compile errors are repaired.**
+
+ `fixtureAcct` is ops, so the old unmatched-unit fallback produces the asserted `0.5` hours. Adding the census fields alone gets the test past its intended initial compile failure without testing historical ownership.
+
+ **The proposed remedy is correct, but must be mandatory and concrete:** use a valid non-ops SQID such as `"bae"` in both census maps and the statement lookup; assert `0.5` hours there and zero CPU hours in the ops statement. No new console account is necessary: census folding adds `"bae"` to the meter/account universe.
+
+ Also replace `"carlos-app@a.example.com"` with [`activator.UnitName("a.example.com")`](internal/activator/backend_unit.go:67), which includes `.service`. The current manually seeded key is neither the derived unit name nor the exec name. Exercise `censusPass` before deleting the route so the test verifies the producer as well as the consumer.
+
+8. **[P2] Task 1 Step 8 — malformed census data counts as complete.**
+
+ The new census arm counts every successful GET without decoding its body. The actual census fold decodes and skips malformed JSON. A malformed census can therefore contribute no `app_months` while contributing a complete census day.
+
+ **Fix:** decode `usage.CensusDay` successfully before counting `SourceCensus`, and use consistent validation in the coverage and accumulation paths. Add a malformed-census test.
+
+9. **[P3] Several exact edit citations and one advertised interface are incorrect.**
+
+ At `c30d059`:
+
+ | Plan reference | Actual location or interface |
+ |---|---|
+ | Meter constant block ends at `usage.go:42` | Ends at **39**; line 42 defines `DayOf` |
+ | `CoveredDays, PeriodDays int` at `economicspass.go:255` | **251** |
+ | Replace `covered := false` at `:344` | **342**; line 344 starts the metrics-box loop |
+ | Metrics `covered = true` at `:356` | **355** |
+ | Edge `covered = true` at `:403` | **398**; line 403 normalizes an empty account |
+ | `date := usage.DayOf(d)` at `:343` | **341** |
+ | `billCEDayGrace = 3` at `:824` | **827** |
+ | Task 2 interface promises `dayOwner(...)` | Step 5 implements `dayOwnerFunc(...)` |
+ | Lost-window comment at `usage_loop.go:51` | **49–50**; `drain()` call is **25**, immediately after the cited signature |
+
+ **Fix:** update these anchors and the interface list. The instruction to replace line 344 is particularly hazardous because literal execution removes the wrong statement.
+
+Two specifically requested checks **are sound** after the compile fixes:
+
+- **`TestStatementsPassClosesAfterGrace`:** the fixture creates the live ops account, and the account universe includes every live account. An empty August statement therefore exists on September 5. The test correctly checks sequential closure; it does not cover finding 1’s race.
+- **Previous-month end:** `firstOfThisMonth.AddDate(0, 0, -1)` is correct because sweep, storage/census folding, and fleet metering include both endpoint days. `billPass` uses the first of this month because Cost Explorer’s end is exclusive.
+
+The remaining named statement/census fields, route fields, meter constants, fixture helpers, and activator signatures match the code. Task 1’s basic red/green sequence is sound; Tasks 2 and 3 require the corrections above.
\ No newline at end of file