carlos / platform Public

economics: backhaul is the inter-region leg, a real model line (#288 review)

The home box meters a hopped request's bytes once, priced as egress;
the edge→client leg is what that price approximates, and the extra
cost is the home→edge inter-region transfer, which the model had no
line for. RateCard gains inter_region_gb_usd (default 0.02; a saved
scenario without it reads 0, never a silent price), the page's row is
'Backhaul (inter-region leg)' added to the model total and still in no
account's cost or revenue — pinned by a PriceAccount identity test.
Also: JSON errors on the JSON door, zero-backhaul accounts omitted from
the report, the CLI uses the package stdout seam and newTable, the
meter doc no longer claims the meters stay out of statements.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev aab5d35fbe205a24f3510c4a1e8441d539ca9433 parent 09e6fe6
9 files changed, +105 −62
  • economics.go +21 −20
  • economics_backhaul_test.go +6 −5
  • internal/console/economicsbackhaul_test.go +23 −4
  • internal/console/economicspass.go +27 −20
  • internal/console/templates/economics.html +8 −4
  • internal/console/templates/economics_accounts.html +1 −1
  • internal/economics/scenario.go +10 −1
  • internal/usage/usage.go +7 −6
  • main.go +2 −1
diff --git a/economics.go b/economics.go
index f611b9d8e..982362921 100644
--- a/economics.go
+++ b/economics.go
@@ -25,7 +25,6 @@ import (
"sort"
"strconv"
"strings"
- "text/tabwriter"
"time"
)
@@ -146,22 +145,22 @@ const economicsBackhaulUsage = `usage: carlos economics backhaul [-period mtd|la
Shows what serving through region edges costs the platform this period:
the part of every account's egress that was backhauled from its home box
(usage.EdgeRow's hop subset — inside the egress totals, never on top),
-priced at the rate card's egress $/GB and ABSORBED — it is in nobody's
-bill (#284). Operator only; a non-operator's token gets the same bare 404
+priced at the rate card's inter-region $/GB (the extra leg; the home box's
+bytes are already egress) and ABSORBED — it is in nobody's bill (#284). Operator only; a non-operator's token gets the same bare 404
an unknown route gets.
`
type backhaulReport struct {
- Period string `json:"period"`
- CoveredDays int `json:"covered_days"`
- PeriodDays int `json:"period_days"`
- EgressGB float64 `json:"egress_gb"`
- BackhaulGB float64 `json:"backhaul_gb"`
- BackhaulReq1k float64 `json:"backhaul_requests_1k"`
- ShareOfEgress float64 `json:"share_of_egress"`
- EgressGBUSD float64 `json:"egress_gb_usd"`
- AbsorbedUSD float64 `json:"absorbed_usd"`
- Accounts []backhaulAccount `json:"accounts"`
+ Period string `json:"period"`
+ CoveredDays int `json:"covered_days"`
+ PeriodDays int `json:"period_days"`
+ EgressGB float64 `json:"egress_gb"`
+ BackhaulGB float64 `json:"backhaul_gb"`
+ BackhaulReq1k float64 `json:"backhaul_requests_1k"`
+ ShareOfEgress float64 `json:"share_of_egress"`
+ InterRegionGBUSD float64 `json:"inter_region_gb_usd"`
+ AbsorbedUSD float64 `json:"absorbed_usd"`
+ Accounts []backhaulAccount `json:"accounts"`
}
type backhaulAccount struct {
@@ -211,22 +210,24 @@ func runEconomicsBackhaul(args []string) {
log.Fatal("carlos economics backhaul works through a console, and this shell is in direct bucket mode\n" +
"(CARLOS_DEPLOYMENT_BUCKET/CARLOS_DEPLOYMENT_DIR is set). Unset the store env and run: carlos auth login -console <url>.")
}
+ printUsing(stdout, sess)
rep, err := sess.economicsBackhaul(context.Background(), *period)
if err != nil {
log.Fatalf("economics backhaul: %v", err)
}
- printBackhaulReport(os.Stdout, rep)
+ printBackhaulReport(stdout, rep)
}
func printBackhaulReport(stdout io.Writer, rep backhaulReport) {
- fmt.Fprintf(stdout, "backhaul %s (%d/%d days covered): %.2f GB of %.2f GB egress (%.1f%%), %.1fk requests — ≈ $%.2f at $%.3f/GB, absorbed\n",
- rep.Period, rep.CoveredDays, rep.PeriodDays, rep.BackhaulGB, rep.EgressGB, rep.ShareOfEgress*100, rep.BackhaulReq1k, rep.AbsorbedUSD, rep.EgressGBUSD)
- w := tabwriter.NewWriter(stdout, 0, 4, 2, ' ', 0)
+ fmt.Fprintf(stdout, "backhaul %s (%d/%d days covered): %.2f GB of %.2f GB egress (%.1f%%), %.1fk requests — inter-region leg ≈ $%.2f at $%.3f/GB, absorbed\n",
+ rep.Period, rep.CoveredDays, rep.PeriodDays, rep.BackhaulGB, rep.EgressGB, rep.ShareOfEgress*100, rep.BackhaulReq1k, rep.AbsorbedUSD, rep.InterRegionGBUSD)
+ if len(rep.Accounts) == 0 {
+ fmt.Fprintln(stdout, "no account backhauled anything this period")
+ return
+ }
+ w := newTable(stdout)
fmt.Fprintln(w, "ACCOUNT\tNAME\tEGRESS GB\tBACKHAUL GB\tSHARE\tREQ (k)\tABSORBED")
for _, a := range rep.Accounts {
- if a.BackhaulGB == 0 && a.BackhaulReq1k == 0 {
- continue
- }
fmt.Fprintf(w, "%s\t%s\t%.2f\t%.2f\t%.1f%%\t%.1f\t$%.2f\n", a.Account, a.Name, a.EgressGB, a.BackhaulGB, a.ShareOfEgress*100, a.BackhaulReq1k, a.AbsorbedUSD)
}
w.Flush()
diff --git a/economics_backhaul_test.go b/economics_backhaul_test.go
index 29a4d7b6a..1077f86ae 100644
--- a/economics_backhaul_test.go
+++ b/economics_backhaul_test.go
@@ -9,19 +9,20 @@ func TestPrintBackhaulReport(t *testing.T) {
var b strings.Builder
printBackhaulReport(&b, backhaulReport{
Period: "mtd", CoveredDays: 3, PeriodDays: 10, EgressGB: 14, BackhaulGB: 5, BackhaulReq1k: 7,
- ShareOfEgress: 5.0 / 14, EgressGBUSD: 0.09, AbsorbedUSD: 0.45,
+ ShareOfEgress: 5.0 / 14, InterRegionGBUSD: 0.02, AbsorbedUSD: 0.10,
Accounts: []backhaulAccount{
{Account: "bab", Name: "ops", EgressGB: 10, BackhaulGB: 2.5, BackhaulReq1k: 7, ShareOfEgress: 0.25, AbsorbedUSD: 0.225},
- {Account: "bad", EgressGB: 0},
},
})
out := b.String()
- for _, want := range []string{"backhaul mtd (3/10 days covered): 5.00 GB of 14.00 GB egress (35.7%)", "$0.45 at $0.090/GB, absorbed", "bab", "ops", "25.0%"} {
+ for _, want := range []string{"backhaul mtd (3/10 days covered): 5.00 GB of 14.00 GB egress (35.7%)", "inter-region leg ≈ $0.10 at $0.020/GB, absorbed", "bab", "ops", "25.0%"} {
if !strings.Contains(out, want) {
t.Errorf("missing %q in:\n%s", want, out)
}
}
- if strings.Contains(out, "bad") {
- t.Errorf("an account with no backhaul was listed:\n%s", out)
+ var empty strings.Builder
+ printBackhaulReport(&empty, backhaulReport{Period: "7d", PeriodDays: 7})
+ if !strings.Contains(empty.String(), "no account backhauled anything") {
+ t.Errorf("empty report printed a bare header:\n%s", empty.String())
}
}
diff --git a/internal/console/economicsbackhaul_test.go b/internal/console/economicsbackhaul_test.go
index c32185392..e158d5a11 100644
--- a/internal/console/economicsbackhaul_test.go
+++ b/internal/console/economicsbackhaul_test.go
@@ -17,7 +17,7 @@ import (
func TestBackhaulReportMaths(t *testing.T) {
v := &EconomicsView{
Period: "mtd", CoveredDays: 3, PeriodDays: 10,
- Scenario: economics.Scenario{RateCard: economics.RateCard{EgressGBUSD: 0.09}},
+ Scenario: economics.Scenario{RateCard: economics.RateCard{EgressGBUSD: 0.09, InterRegionGBUSD: 0.02}},
Names: map[string]string{"bab": "ops"},
Meters: map[string]map[string]float64{
"bab": {usage.MeterEgressGB: 10, usage.MeterBackhaulGB: 2.5, usage.MeterBackhaulRequests1k: 7},
@@ -29,13 +29,14 @@ func TestBackhaulReportMaths(t *testing.T) {
if rep.EgressGB != 14 || rep.BackhaulGB != 5 || rep.BackhaulReq1k != 7 {
t.Fatalf("totals = %+v", rep)
}
- if math.Abs(rep.ShareOfEgress-5.0/14) > 1e-9 || math.Abs(rep.AbsorbedUSD-0.45) > 1e-9 || rep.EgressGBUSD != 0.09 {
+ if math.Abs(rep.ShareOfEgress-5.0/14) > 1e-9 || math.Abs(rep.AbsorbedUSD-0.10) > 1e-9 || rep.InterRegionGBUSD != 0.02 {
t.Fatalf("share/absorbed = %v/%v", rep.ShareOfEgress, rep.AbsorbedUSD)
}
- if len(rep.Accounts) != 3 || rep.Accounts[0].Account != "bab" || rep.Accounts[1].Account != "bae" || rep.Accounts[2].Account != "bad" {
+ // bad has egress but no backhaul: counted in the totals, no row.
+ if len(rep.Accounts) != 2 || rep.Accounts[0].Account != "bab" || rep.Accounts[1].Account != "bae" {
t.Fatalf("order = %+v", rep.Accounts)
}
- if rep.Accounts[0].Name != "ops" || math.Abs(rep.Accounts[0].ShareOfEgress-0.25) > 1e-9 || math.Abs(rep.Accounts[1].ShareOfEgress-0.625) > 1e-9 || rep.Accounts[2].ShareOfEgress != 0 {
+ if rep.Accounts[0].Name != "ops" || math.Abs(rep.Accounts[0].ShareOfEgress-0.25) > 1e-9 || math.Abs(rep.Accounts[1].ShareOfEgress-0.625) > 1e-9 || math.Abs(rep.Accounts[0].AbsorbedUSD-0.05) > 1e-9 {
t.Fatalf("accounts = %+v", rep.Accounts)
}
}
@@ -88,3 +89,21 @@ func TestAPIEconomicsBackhaulGate(t *testing.T) {
t.Fatalf("report = %+v", rep)
}
}
+
+// The whole "priced nowhere" claim in one assertion: an account prices
+// identically with and without the backhaul meters present.
+func TestBackhaulMetersNeverPriceAnAccount(t *testing.T) {
+ sc := economics.DefaultScenario()
+ base := map[string]float64{usage.MeterEgressGB: 40, usage.MeterRequests1k: 900, usage.MeterMemGBHours: 800, usage.MeterAppMonths: 3}
+ with := map[string]float64{}
+ for k, v := range base {
+ with[k] = v
+ }
+ with[usage.MeterBackhaulGB] = 25
+ with[usage.MeterBackhaulRequests1k] = 500
+ a := economics.PriceAccount(sc, "bab", base, 12.5)
+ b := economics.PriceAccount(sc, "bab", with, 12.5)
+ if a != b {
+ t.Fatalf("backhaul meters changed an account's price:\n%+v\n%+v", a, b)
+ }
+}
diff --git a/internal/console/economicspass.go b/internal/console/economicspass.go
index f8f1cf37c..08df9644b 100644
--- a/internal/console/economicspass.go
+++ b/internal/console/economicspass.go
@@ -1393,21 +1393,26 @@ func (s *Server) handleAPIEconomicsBill(w http.ResponseWriter, r *http.Request)
// backhaulReport is GET /api/economics/backhaul: what serving through region
// edges costs the platform this period (#288). The backhaul meters are the
-// hop SUBSET of egress (usage.EdgeRow) — inside the egress totals, never on
-// top of them — priced at the rate card's egress $/GB and stated as
-// absorbed: not in any account's cost or revenue (#284's ruling). Operator
-// only, same bare-404 posture as the bill endpoint.
+// hop SUBSET of egress (usage.EdgeRow): bytes the HOME box sent to a region
+// edge, which the edge then sent on to the client. The home box's metered
+// bytes are already priced as egress; the extra leg is the inter-region
+// transfer, priced here at RateCard.InterRegionGBUSD and stated as
+// absorbed: a platform cost, in no account's cost or revenue (#284's
+// ruling). Accounts with no backhaul are omitted. Operator only, the bill
+// door's bare-404 posture for a token without the bit; the route itself is
+// discoverable (an unauthenticated GET gets cliUser's 401), which is the
+// same as every other /api door.
type backhaulReport struct {
- Period string `json:"period"`
- CoveredDays int `json:"covered_days"`
- PeriodDays int `json:"period_days"`
- EgressGB float64 `json:"egress_gb"`
- BackhaulGB float64 `json:"backhaul_gb"`
- BackhaulReq1k float64 `json:"backhaul_requests_1k"`
- ShareOfEgress float64 `json:"share_of_egress"` // 0..1; 0 when no egress
- EgressGBUSD float64 `json:"egress_gb_usd"` // the rate it is priced at
- AbsorbedUSD float64 `json:"absorbed_usd"`
- Accounts []backhaulAccount `json:"accounts"` // backhaul desc, account asc on ties
+ Period string `json:"period"`
+ CoveredDays int `json:"covered_days"`
+ PeriodDays int `json:"period_days"`
+ EgressGB float64 `json:"egress_gb"`
+ BackhaulGB float64 `json:"backhaul_gb"`
+ BackhaulReq1k float64 `json:"backhaul_requests_1k"`
+ ShareOfEgress float64 `json:"share_of_egress"` // 0..1; 0 when no egress
+ InterRegionGBUSD float64 `json:"inter_region_gb_usd"` // the rate the extra leg is priced at
+ AbsorbedUSD float64 `json:"absorbed_usd"`
+ Accounts []backhaulAccount `json:"accounts"` // backhaul desc, account asc on ties
}
type backhaulAccount struct {
@@ -1422,7 +1427,7 @@ type backhaulAccount struct {
func buildBackhaulReport(v *EconomicsView) backhaulReport {
rep := backhaulReport{Period: v.Period, CoveredDays: v.CoveredDays, PeriodDays: v.PeriodDays,
- EgressGBUSD: v.Scenario.RateCard.EgressGBUSD, Accounts: []backhaulAccount{}}
+ InterRegionGBUSD: v.Scenario.RateCard.InterRegionGBUSD, Accounts: []backhaulAccount{}}
for acct, meters := range v.Meters {
a := backhaulAccount{Account: acct, Name: v.Names[acct],
EgressGB: meters[usage.MeterEgressGB], BackhaulGB: meters[usage.MeterBackhaulGB],
@@ -1430,16 +1435,19 @@ func buildBackhaulReport(v *EconomicsView) backhaulReport {
if a.EgressGB > 0 {
a.ShareOfEgress = a.BackhaulGB / a.EgressGB
}
- a.AbsorbedUSD = a.BackhaulGB * rep.EgressGBUSD
+ a.AbsorbedUSD = a.BackhaulGB * rep.InterRegionGBUSD
rep.EgressGB += a.EgressGB
rep.BackhaulGB += a.BackhaulGB
rep.BackhaulReq1k += a.BackhaulReq1k
+ if a.BackhaulGB == 0 && a.BackhaulReq1k == 0 {
+ continue // egress counted, row omitted: no backhaul, nothing to say
+ }
rep.Accounts = append(rep.Accounts, a)
}
if rep.EgressGB > 0 {
rep.ShareOfEgress = rep.BackhaulGB / rep.EgressGB
}
- rep.AbsorbedUSD = rep.BackhaulGB * rep.EgressGBUSD
+ rep.AbsorbedUSD = rep.BackhaulGB * rep.InterRegionGBUSD
sort.Slice(rep.Accounts, func(i, j int) bool {
if rep.Accounts[i].BackhaulGB != rep.Accounts[j].BackhaulGB {
return rep.Accounts[i].BackhaulGB > rep.Accounts[j].BackhaulGB
@@ -1471,9 +1479,8 @@ func (s *Server) handleAPIEconomicsBackhaul(w http.ResponseWriter, r *http.Reque
v, err := s.LoadEconomics(r.Context(), r.URL.Query().Get("period"))
if err != nil {
log.Printf("console: api economics backhaul: %v", err)
- http.Error(w, "economics unavailable", http.StatusServiceUnavailable)
+ jsonError(w, http.StatusServiceUnavailable, "economics unavailable")
return
}
- w.Header().Set("Content-Type", "application/json")
- _ = json.NewEncoder(w).Encode(buildBackhaulReport(v))
+ writeJSON(w, http.StatusOK, buildBackhaulReport(v))
}
diff --git a/internal/console/templates/economics.html b/internal/console/templates/economics.html
index 99395623c..3cc92bd3c 100644
--- a/internal/console/templates/economics.html
+++ b/internal/console/templates/economics.html
@@ -164,8 +164,9 @@
{{$s3storage := mul (sumMeter .Economics.Meters "storage_gb_month") .Economics.Scenario.RateCard.StorageGBMonthUSD}}
{{$s3requests := mul (sumMeter .Economics.Meters "requests_1k") .Economics.Scenario.RateCard.RequestsPer1kUSD}}
{{$egress := mul (sumMeter .Economics.Meters "egress_gb") .Economics.Scenario.RateCard.EgressGBUSD}}
+{{$backhaul := mul (sumMeter .Economics.Meters "backhaul_gb") .Economics.Scenario.RateCard.InterRegionGBUSD}}
{{$overheads := overheadUSD .Economics.Scenario .Economics.PeriodDays}}
-{{$modelTotal := add $ec2 $ebs $s3storage $s3requests $egress $overheads}}
+{{$modelTotal := add $ec2 $ebs $s3storage $s3requests $egress $backhaul $overheads}}
<h2>Model vs. bill</h2>
<div class="tablewrap">
<table>
@@ -176,7 +177,7 @@
<tr><td>S3 requests</td><td id="model-s3-requests">{{usd $s3requests}}</td></tr>
<tr><td>Egress</td><td id="model-egress">{{usd $egress}}</td></tr>
{{$backhaulGB := sumMeter .Economics.Meters "backhaul_gb"}}
- <tr class="muted"><td>&nbsp;&nbsp;of which backhaul (absorbed, inside egress — not added)</td><td><span id="model-backhaul">{{usd (mul $backhaulGB .Economics.Scenario.RateCard.EgressGBUSD)}}</span> · {{printf "%.2f" $backhaulGB}} GB{{with sumMeter .Economics.Meters "egress_gb"}}{{if gt . 0.0}} · {{printf "%.1f" (mul (div $backhaulGB .) 100.0)}}%{{end}}{{end}}</td></tr>
+ <tr><td>Backhaul (inter-region leg) <span class="small">{{printf "%.2f" $backhaulGB}} GB{{with sumMeter .Economics.Meters "egress_gb"}}{{if gt . 0.0}} · {{printf "%.1f" (mul (div $backhaulGB .) 100.0)}}% of egress{{end}}{{end}} · absorbed, in no account's bill</span></td><td id="model-backhaul">{{usd $backhaul}}</td></tr>
<tr><td>Overheads</td><td id="model-overheads">{{usd $overheads}}</td></tr>
<tr><th>Model total</th><th id="model-total">{{usd $modelTotal}}</th></tr>
</table>
@@ -378,13 +379,14 @@
var s3storage = sumMeter(payload.meters, "storage_gb_month") * sc.rate_card.storage_gb_month_usd;
var s3requests = sumMeter(payload.meters, "requests_1k") * sc.rate_card.requests_per_1k_usd;
var egress = sumMeter(payload.meters, "egress_gb") * sc.rate_card.egress_gb_usd;
- var modelTotal = ec2 + ebs + s3storage + s3requests + egress + overheadTotal;
+ var backhaul = sumMeter(payload.meters, "backhaul_gb") * (sc.rate_card.inter_region_gb_usd || 0);
+ var modelTotal = ec2 + ebs + s3storage + s3requests + egress + backhaul + overheadTotal;
setText("model-ec2", usd(ec2));
setText("model-ebs", usd(ebs));
setText("model-s3-storage", usd(s3storage));
setText("model-s3-requests", usd(s3requests));
setText("model-egress", usd(egress));
- setText("model-backhaul", usd(sumMeter(payload.meters, "backhaul_gb") * sc.rate_card.egress_gb_usd));
+ setText("model-backhaul", usd(backhaul));
setText("model-overheads", usd(overheadTotal));
setText("model-total", usd(modelTotal));
@@ -446,6 +448,7 @@
<div class="knob-row"><label for="knob-storage-gb-month-usd">S3 storage $/GB-month</label><input type="number" step="0.001" min="0" id="knob-storage-gb-month-usd" value="{{.Economics.Scenario.RateCard.StorageGBMonthUSD}}"></div>
<div class="knob-row"><label for="knob-requests-per-1k-usd">S3 requests $/1k</label><input type="number" step="0.001" min="0" id="knob-requests-per-1k-usd" value="{{.Economics.Scenario.RateCard.RequestsPer1kUSD}}"></div>
<div class="knob-row"><label for="knob-egress-gb-usd">Egress $/GB</label><input type="number" step="0.001" min="0" id="knob-egress-gb-usd" value="{{.Economics.Scenario.RateCard.EgressGBUSD}}"></div>
+<div class="knob-row"><label for="knob-inter-region-gb-usd">Inter-region $/GB (backhaul leg)</label><input type="number" step="0.001" min="0" id="knob-inter-region-gb-usd" value="{{.Economics.Scenario.RateCard.InterRegionGBUSD}}"></div>
<h3>Overheads</h3>
<div class="tablewrap">
@@ -813,6 +816,7 @@
sc.rate_card.storage_gb_month_usd = numVal("knob-storage-gb-month-usd");
sc.rate_card.requests_per_1k_usd = numVal("knob-requests-per-1k-usd");
sc.rate_card.egress_gb_usd = numVal("knob-egress-gb-usd");
+ sc.rate_card.inter_region_gb_usd = numVal("knob-inter-region-gb-usd");
document.querySelectorAll(".knob-overhead").forEach(function (el) {
var i = parseInt(el.getAttribute("data-index"), 10);
if (sc.rate_card.overheads && sc.rate_card.overheads[i]) {
diff --git a/internal/console/templates/economics_accounts.html b/internal/console/templates/economics_accounts.html
index d7888a267..ae1d769ad 100644
--- a/internal/console/templates/economics_accounts.html
+++ b/internal/console/templates/economics_accounts.html
@@ -83,7 +83,7 @@
<th scope="col">CPU-h</th>
<th scope="col">GB-h</th>
<th scope="col">Egress GB</th>
- <th scope="col" title="the part of egress served through a region edge and backhauled from the home box — inside Egress GB, absorbed by the platform">Backhaul GB</th>
+ <th scope="col" title="the part of Egress GB that left its home box for a region edge first — the inter-region leg is a platform cost, absorbed, in no account's bill">Backhaul GB</th>
<th scope="col">Storage GB-month</th>
<th scope="col">Cost</th>
<th scope="col">Would-be revenue</th>
diff --git a/internal/economics/scenario.go b/internal/economics/scenario.go
index 911c0ddd9..ea6dd1743 100644
--- a/internal/economics/scenario.go
+++ b/internal/economics/scenario.go
@@ -31,7 +31,15 @@ type RateCard struct {
StorageGBMonthUSD float64 `json:"storage_gb_month_usd"`
RequestsPer1kUSD float64 `json:"requests_per_1k_usd"`
EgressGBUSD float64 `json:"egress_gb_usd"`
- Overheads []LineItem `json:"overheads"`
+ // InterRegionGBUSD is what a byte costs to move between the platform's
+ // own regions — the backhaul leg (#288): a request served through a
+ // region edge leaves the home box for that edge (this rate) and then
+ // the edge for the client (EgressGBUSD, which is what the home box's
+ // metered bytes are priced at). Additive; a scenario saved before it
+ // existed reads as 0 and the model line shows $0 until the operator
+ // sets it — never a silent default price for a cost that is real.
+ InterRegionGBUSD float64 `json:"inter_region_gb_usd,omitempty"`
+ Overheads []LineItem `json:"overheads"`
}
// Allocation says how a box's shared cost is split across its tenants.
@@ -136,6 +144,7 @@ func DefaultScenario() Scenario {
StorageGBMonthUSD: 0.023,
RequestsPer1kUSD: 0.005,
EgressGBUSD: 0.09,
+ InterRegionGBUSD: 0.02,
Overheads: []LineItem{{Name: "domains + DNS", USDMonth: 5}},
},
Allocation: Allocation{Policy: "blend", CPUWeight: 0.5},
diff --git a/internal/usage/usage.go b/internal/usage/usage.go
index 72f6092f0..7a981a110 100644
--- a/internal/usage/usage.go
+++ b/internal/usage/usage.go
@@ -23,12 +23,13 @@ const (
MeterInstanceHours = "instance_hours"
MeterFleetBoxHoursServer = "fleet_box_hours_server"
MeterFleetBoxHoursDevice = "fleet_box_hours_device"
- // MeterBackhaulGB is RESERVED, not yet billed: the egress a request
- // spent crossing a region on a backhaul hop (EdgeRow.HopBytesOut). The
- // ruling that added the counters (2026-08-24) deliberately left the
- // pricing to Paul and shipped them dormant — no statement, no rate-card
- // line, bills byte-identical to before. The name is reserved here
- // because meter names are frozen once merged: reserving beats renaming.
+ // MeterBackhaulGB is the hop SUBSET of MeterEgressGB (usage.EdgeRow's
+ // HopBytesOut): bytes that left a home box for a region edge before
+ // reaching the client. It rides in statements and the economics sweep
+ // as data (#288) and is priced in NO account's cost, revenue or bill —
+ // #284's ruling is absorb. A billing engine iterating meters generically
+ // must skip it; PriceAccount names its meters and does. The platform's
+ // own cost for it is the inter-region leg, RateCard.InterRegionGBUSD.
MeterBackhaulGB = "backhaul_gb"
// MeterBackhaulRequests1k is the request-count twin of MeterBackhaulGB:
// requests that crossed a region before being served. Informational,
diff --git a/main.go b/main.go
index 7d7cd5dbf..2d7419a65 100644
--- a/main.go
+++ b/main.go
@@ -207,7 +207,8 @@ Your deployment — stand up and administer boxes, routes and accounts
revoke a capability, hibernate it, change its backing
instances with no sub-verb: list THIS box's registry routes
release-keygen mint the root signing key, or a scoped signing key
- economics record the month's AWS bill for the economics dashboard
+ economics bill: record the month's AWS bill; backhaul: what region
+ edges cost us this period
status -live: what every box last reported; -dry-run: leak check
On the host — what systemd runs; rarely typed by hand