Per-line tax codes, Task 0: today's calculation bytes, digests and request hashes pinned as literals before any change
Seven months priced on the unchanged code at 29ea6676 (a legacy month; a legacy month with two codes; a price-book month with no Days; one code over two plans; a quantity change; a partly covered month; a waived month), each with its frozen JSON, digest and per-line operation kind and request hash, recorded as text and never regenerated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+131
−0
internal/console/billingtaxshape_fixtures_test.go+131 −0
CI cancelled — superseded by a newer push — run details
diff --git a/internal/console/billingtaxshape_fixtures_test.go b/internal/console/billingtaxshape_fixtures_test.go| new file mode 100644 |
| index 000000000..d31fd424b |
| --- /dev/null |
| +++ b/internal/console/billingtaxshape_fixtures_test.go |
| @@ -0,0 +1,131 @@ |
| +package console |
| + |
| +// Today's bytes, pinned before per-line tax codes exist (design |
| +// docs/superpowers/specs/2026-10-03-per-line-tax-codes-design.md, plan |
| +// Task 0). Each entry is the frozen calculation a month prices to, its |
| +// digest, and the request hash and operation kind of each of its lines, |
| +// recorded as literal text from the unchanged code at 29ea6676 and never |
| +// regenerated: a change that moves one byte of a one-code month, or one |
| +// request hash, fails here. |
| + |
| +import ( |
| + "crypto/sha256" |
| + "encoding/hex" |
| + "encoding/json" |
| + "fmt" |
| + "testing" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/billingstripe" |
| + "github.com/carlosframework/platform/internal/plans" |
| + "github.com/carlosframework/platform/internal/usage" |
| +) |
| + |
| +type taxShapeLine struct{ kind, hash string } |
| + |
| +type taxShapeFixture struct { |
| + json, digest string |
| + lines []taxShapeLine |
| +} |
| + |
| +var taxShapeFixtures = map[string]taxShapeFixture{ |
| + "legacy_one_plan": {json: `{"statement_object":{"key":""},"v":1,"arithmetic":"base_units_floor_v1","currency":"eur","statement":{"v":1,"account":"acme","month":"2026-09","meters":{"durability_writes_1k":1000},"covered_days":0,"days_in_month":30,"updated_at":"2026-10-04T00:00:00Z","closed":true},"statement_sha256":"714145745690a96ba8f7a64310bf336169c705c3bf9a539072691fa98a63bea3","allowance":{"month":"2026-09","terms_version":"2026-09-07-v1","house":false,"resources":{"awake_seconds":306000,"storage_byte_months":2000000000,"transfer_bytes":2000000000,"writes":0},"assignments":[{"effective_at":"2026-09-01T00:00:00Z","revision":1,"terms_version":"2026-09-07-v1","plan":"personal","band":"A","quantity":1,"house":false,"status":"active","paid_through":"2027-09-01T00:00:00Z"}]},"usage":{"awake_seconds":0,"storage_byte_months":0,"transfer_bytes":0,"writes":1000000},"overage":{"AwakeMicroEUR":0,"StorageMicroEUR":0,"TransferMicroEUR":0,"WritesMicroEUR":23000000,"TotalMicroEUR":23000000},"invoice":{"SubscriptionMicroEUR":0,"OverageMicroEUR":23000000,"TotalMicroEUR":23000000,"TotalCents":2300,"Waived":false},"lines":[{"Meter":"writes","AmountCents":2300}],"tax_code":""}`, digest: "4986f5c1b3526718c62aaf49f54fd76815630008078a1a9f4a193d44be41a626", lines: []taxShapeLine{{"line-writes", "d02c8fb1a3117fde66754fe63eb093d62cc00ce0b04d10ed36b9581eaf6fa1f5"}}}, |
| + "legacy_two_codes": {json: `{"statement_object":{"key":""},"v":1,"arithmetic":"base_units_floor_v1","currency":"eur","statement":{"v":1,"account":"acme","month":"2026-09","meters":{"store_egress_gb":20},"covered_days":0,"days_in_month":30,"updated_at":"2026-10-04T00:00:00Z","closed":true},"statement_sha256":"fdb612ab4f304cb3fa0359fd7a94eb2ed54cfcea128ffdd202245585ae0accfe","allowance":{"month":"2026-09","terms_version":"2026-09-07-v1","house":false,"resources":{"awake_seconds":3406800,"storage_byte_months":27600000000,"transfer_bytes":16933333334,"writes":160000},"assignments":[{"effective_at":"2026-09-01T00:00:00Z","revision":1,"terms_version":"2026-09-07-v1","plan":"personal","band":"A","quantity":1,"house":false,"status":"active","paid_through":"2027-09-01T00:00:00Z"},{"effective_at":"2026-09-15T00:00:00Z","revision":2,"terms_version":"2026-09-07-v1","plan":"business","band":"A","quantity":10,"house":false,"status":"active","paid_through":"2027-09-01T00:00:00Z"}]},"usage":{"awake_seconds":0,"storage_byte_months":0,"transfer_bytes":20000000000,"writes":0},"overage":{"AwakeMicroEUR":0,"StorageMicroEUR":0,"TransferMicroEUR":1269599,"WritesMicroEUR":0,"TotalMicroEUR":1269599},"invoice":{"SubscriptionMicroEUR":0,"OverageMicroEUR":1269599,"TotalMicroEUR":1269599,"TotalCents":127,"Waived":false},"lines":[{"Meter":"transfer_bytes","AmountCents":127}],"tax_code":""}`, digest: "acd02a8f85bdb9c8d750aad2a1091b74e87565eda719ac8dbcb90b3fa10d3997", lines: []taxShapeLine{{"line-transfer_bytes", "b7200c5384f931acd57fe376ac761ce422de95c8bc4a68f3f353b7c2982aa1e9"}}}, |
| + "book_one_plan_no_days": {json: `{"statement_object":{"key":""},"v":2,"arithmetic":"base_units_floor_v1","currency":"usd","statement":{"v":1,"account":"bab","month":"2026-09","meters":{"instance_hours":1000,"storage_gb_month":40},"covered_days":0,"days_in_month":30,"updated_at":"0001-01-01T00:00:00Z","closed":true},"statement_sha256":"83d2a01200b4377ced8c93f2fdd398de7ae238c02074b2ed2642cd466a35b260","allowance":{"month":"2026-09","terms_version":"2026-09-07-v1","house":false,"resources":{"awake_seconds":306000,"storage_byte_months":2000000000,"transfer_bytes":2000000000,"writes":0},"assignments":[{"effective_at":"2026-08-10T00:00:00Z","revision":1,"terms_version":"2026-09-07-v1","plan":"personal","band":"A","quantity":1,"house":false,"status":"active","paid_through":"2027-08-10T00:00:00Z","price_book":"2026-10-01-draft1","currency":"usd"}],"price_book":"2026-10-01-draft1","currency":"usd"},"usage":{"awake_seconds":3600000,"storage_byte_months":40000000000,"transfer_bytes":0,"writes":0},"lines":[{"Meter":"awake_seconds","AmountCents":1098},{"Meter":"storage_byte_months","AmountCents":483}],"tax_code":"","price_book":"2026-10-01-draft1","charges":{"currency":"usd","awake_micro":10980000,"storage_micro":4833600,"transfer_micro":0,"writes_micro":0,"total_micro":15813600},"total":{"currency":"usd","total_micro":15813600,"total_minor":1581,"waived":false}}`, digest: "1fc712ad08a79488c2d449bd933b4afe321b1848da6cc4b8fdcb4f43b2c4d4d2", lines: []taxShapeLine{{"line-awake_seconds", "2a5f582df8e30c4e609b2281891eeccc8b90cd8df0bc9fc50725d3de8fe60f7e"}, {"line-storage_byte_months", "45733fc5ce305ec3082ce7be2c367d116cef32a176544693fc7656528f065f92"}}}, |
| + "book_two_plans_one_code": {json: `{"statement_object":{"key":""},"v":2,"arithmetic":"base_units_floor_v1","currency":"usd","statement":{"v":1,"account":"bab","month":"2026-09","meters":{"instance_hours":1000,"storage_gb_month":40},"covered_days":0,"days_in_month":30,"updated_at":"0001-01-01T00:00:00Z","closed":true},"statement_sha256":"83d2a01200b4377ced8c93f2fdd398de7ae238c02074b2ed2642cd466a35b260","allowance":{"month":"2026-09","terms_version":"2026-09-07-v1","house":false,"resources":{"awake_seconds":469200,"storage_byte_months":3600000000,"transfer_bytes":3066666667,"writes":0},"assignments":[{"effective_at":"2026-08-10T00:00:00Z","revision":1,"terms_version":"2026-09-07-v1","plan":"personal","band":"A","quantity":1,"house":false,"status":"active","paid_through":"2027-08-10T00:00:00Z","price_book":"2026-10-01-draft1","currency":"usd"},{"effective_at":"2026-09-15T00:00:00Z","revision":2,"terms_version":"2026-09-07-v1","plan":"community","band":"A","quantity":10,"house":false,"status":"active","paid_through":"2027-08-10T00:00:00Z","price_book":"2026-10-01-draft1","currency":"usd"}],"price_book":"2026-10-01-draft1","currency":"usd"},"usage":{"awake_seconds":3600000,"storage_byte_months":40000000000,"transfer_bytes":0,"writes":0},"lines":[{"Meter":"awake_seconds","AmountCents":1044},{"Meter":"storage_byte_months","AmountCents":463}],"tax_code":"","price_book":"2026-10-01-draft1","charges":{"currency":"usd","awake_micro":10436000,"storage_micro":4630080,"transfer_micro":0,"writes_micro":0,"total_micro":15066080},"total":{"currency":"usd","total_micro":15066080,"total_minor":1507,"waived":false}}`, digest: "62fa092d9bdfe7f4480349fda58c981a768c51cbb5fa61303994ecb446689c80", lines: []taxShapeLine{{"line-awake_seconds", "974eb9715ad4b0363243771a04646cc96bfcbd348e4152edd8d106690816293b"}, {"line-storage_byte_months", "f977333ee32b78812d6c8e60565e7271e0d37fc3378735ef78d2c9e04563595e"}}}, |
| + "book_quantity_change": {json: `{"statement_object":{"key":""},"v":2,"arithmetic":"base_units_floor_v1","currency":"usd","statement":{"v":1,"account":"bab","month":"2026-09","meters":{"instance_hours":1000,"storage_gb_month":40},"covered_days":0,"days_in_month":30,"updated_at":"0001-01-01T00:00:00Z","closed":true},"statement_sha256":"83d2a01200b4377ced8c93f2fdd398de7ae238c02074b2ed2642cd466a35b260","allowance":{"month":"2026-09","terms_version":"2026-09-07-v1","house":false,"resources":{"awake_seconds":677280,"storage_byte_months":5533333334,"transfer_bytes":4426666667,"writes":0},"assignments":[{"effective_at":"2026-08-10T00:00:00Z","revision":1,"terms_version":"2026-09-07-v1","plan":"community","band":"A","quantity":10,"house":false,"status":"active","paid_through":"2027-08-10T00:00:00Z","price_book":"2026-10-01-draft1","currency":"usd"},{"effective_at":"2026-09-15T00:00:00Z","revision":2,"terms_version":"2026-09-07-v1","plan":"community","band":"A","quantity":12,"house":false,"status":"active","paid_through":"2027-08-10T00:00:00Z","price_book":"2026-10-01-draft1","currency":"usd"}],"price_book":"2026-10-01-draft1","currency":"usd"},"usage":{"awake_seconds":3600000,"storage_byte_months":40000000000,"transfer_bytes":0,"writes":0},"lines":[{"Meter":"awake_seconds","AmountCents":974},{"Meter":"storage_byte_months","AmountCents":439}],"tax_code":"","price_book":"2026-10-01-draft1","charges":{"currency":"usd","awake_micro":9742400,"storage_micro":4384159,"transfer_micro":0,"writes_micro":0,"total_micro":14126559},"total":{"currency":"usd","total_micro":14126559,"total_minor":1413,"waived":false}}`, digest: "1ca5a47697007e49fe53de3cd75dc1c3ca873d30fde7c76bad91d4254104ddd8", lines: []taxShapeLine{{"line-awake_seconds", "e6f1730fdc7521f9d6cd890dbf00482846bcfbd29814b2cfaf6776c19b01ca4f"}, {"line-storage_byte_months", "88e4580a0cec931b85cd0a7b87e7a832430af4d17073610d5a66c52144ab2e2a"}}}, |
| + "book_partial": {json: `{"statement_object":{"key":""},"v":2,"arithmetic":"base_units_floor_whole_paid_days_v1","currency":"usd","statement":{"v":1,"account":"bab","month":"2026-09","meters":{"instance_hours":300},"covered_days":0,"days":{"2026-09-01":{"instance_hours":10},"2026-09-02":{"instance_hours":10},"2026-09-03":{"instance_hours":10},"2026-09-04":{"instance_hours":10},"2026-09-05":{"instance_hours":10},"2026-09-06":{"instance_hours":10},"2026-09-07":{"instance_hours":10},"2026-09-08":{"instance_hours":10},"2026-09-09":{"instance_hours":10},"2026-09-10":{"instance_hours":10},"2026-09-11":{"instance_hours":10},"2026-09-12":{"instance_hours":10},"2026-09-13":{"instance_hours":10},"2026-09-14":{"instance_hours":10},"2026-09-15":{"instance_hours":10},"2026-09-16":{"instance_hours":10},"2026-09-17":{"instance_hours":10},"2026-09-18":{"instance_hours":10},"2026-09-19":{"instance_hours":10},"2026-09-20":{"instance_hours":10},"2026-09-21":{"instance_hours":10},"2026-09-22":{"instance_hours":10},"2026-09-23":{"instance_hours":10},"2026-09-24":{"instance_hours":10},"2026-09-25":{"instance_hours":10},"2026-09-26":{"instance_hours":10},"2026-09-27":{"instance_hours":10},"2026-09-28":{"instance_hours":10},"2026-09-29":{"instance_hours":10},"2026-09-30":{"instance_hours":10}},"days_in_month":30,"updated_at":"0001-01-01T00:00:00Z","closed":true},"statement_sha256":"ac46545c8f8032543ead4033f16c4b2d80f855b4b2a6e1fe8b7ffe0c753cc956","allowance":{"month":"2026-09","terms_version":"2026-09-07-v1","house":false,"resources":{"awake_seconds":214200,"storage_byte_months":1400000000,"transfer_bytes":1400000000,"writes":0},"assignments":[{"effective_at":"2026-09-10T00:00:00Z","revision":1,"terms_version":"2026-09-07-v1","plan":"personal","band":"A","quantity":1,"house":false,"status":"active","paid_through":"2027-08-10T00:00:00Z","price_book":"2026-10-01-draft1","currency":"usd"}],"partial":true,"covered":[{"from":"2026-09-10T00:00:00Z","to":"2026-10-01T00:00:00Z"}],"price_book":"2026-10-01-draft1","currency":"usd"},"usage":{"awake_seconds":756000,"storage_byte_months":0,"transfer_bytes":0,"writes":0},"lines":[{"Meter":"awake_seconds","AmountCents":181}],"tax_code":"","counted_days":["2026-09-10","2026-09-11","2026-09-12","2026-09-13","2026-09-14","2026-09-15","2026-09-16","2026-09-17","2026-09-18","2026-09-19","2026-09-20","2026-09-21","2026-09-22","2026-09-23","2026-09-24","2026-09-25","2026-09-26","2026-09-27","2026-09-28","2026-09-29","2026-09-30"],"price_book":"2026-10-01-draft1","charges":{"currency":"usd","awake_micro":1806000,"storage_micro":0,"transfer_micro":0,"writes_micro":0,"total_micro":1806000},"total":{"currency":"usd","total_micro":1806000,"total_minor":181,"waived":false}}`, digest: "4a25f2e4805ff9df143aa4c0a460fe0dbe28ecb9b800ce01fbe086984c820686", lines: []taxShapeLine{{"line-awake_seconds", "4fcc5f950368ea34aef655ad954d9e26dc608d64ca42e576e1b8cce1794fe1d0"}}}, |
| + "book_waived": {json: `{"statement_object":{"key":""},"v":2,"arithmetic":"base_units_floor_v1","currency":"usd","statement":{"v":1,"account":"bab","month":"2026-09","meters":{},"covered_days":0,"days_in_month":30,"updated_at":"0001-01-01T00:00:00Z","closed":true},"statement_sha256":"84d005a2404d9e73775118aa37f4e19dbb9bf795799d197f0c36187447479c45","allowance":{"month":"2026-09","terms_version":"2026-09-07-v1","house":false,"resources":{"awake_seconds":306000,"storage_byte_months":2000000000,"transfer_bytes":2000000000,"writes":0},"assignments":[{"effective_at":"2026-08-10T00:00:00Z","revision":1,"terms_version":"2026-09-07-v1","plan":"personal","band":"A","quantity":1,"house":false,"status":"active","paid_through":"2027-08-10T00:00:00Z","price_book":"2026-10-01-draft1","currency":"usd"}],"price_book":"2026-10-01-draft1","currency":"usd"},"usage":{"awake_seconds":0,"storage_byte_months":0,"transfer_bytes":0,"writes":0},"lines":null,"tax_code":"","price_book":"2026-10-01-draft1","charges":{"currency":"usd","awake_micro":0,"storage_micro":0,"transfer_micro":0,"writes_micro":0,"total_micro":0},"total":{"currency":"usd","total_micro":0,"total_minor":0,"waived":true}}`, digest: "dd9a0f359bd1b0d2852909f90ee333112a7f546b9d6d0462ce7b06a453a96acd", lines: []taxShapeLine{}}, |
| +} |
| + |
| +// taxShapeInputs are the statements and histories the fixtures were |
| +// recorded from; the same construction as the generator that recorded |
| +// them. |
| +func taxShapeInputs() map[string]struct { |
| + st usage.Statement |
| + h []plans.Assignment |
| +} { |
| + type cs = struct { |
| + st usage.Statement |
| + h []plans.Assignment |
| + } |
| + terms := plans.PriceTerms{TermsVersion: plans.CurrentTermsVersion, PriceBook: plans.CurrentPriceBook, Currency: plans.USD} |
| + sep15 := time.Date(2026, 9, 15, 0, 0, 0, 0, time.UTC) |
| + out := map[string]cs{} |
| + st, h := calculationFixture(plans.PlanPersonal, 1) |
| + st.Meters[usage.MeterDurabilityWrites1k] = 1000 |
| + out["legacy_one_plan"] = cs{st, h} |
| + st2, h2 := calculationFixture(plans.PlanPersonal, 1) |
| + n := h2[0] |
| + n.Revision, n.EffectiveAt, n.Plan, n.Quantity = 2, sep15, plans.PlanBusiness, 10 |
| + st2.Meters[usage.MeterStoreEgressGB] = 20 |
| + out["legacy_two_codes"] = cs{st2, append(h2, n)} |
| + book := func() usage.Statement { |
| + return usage.Statement{V: 1, Account: "bab", Month: "2026-09", Meters: map[string]float64{usage.MeterInstanceHours: 1000, usage.MeterStorageGBMonth: 40}, DaysInMonth: 30, Closed: true} |
| + } |
| + out["book_one_plan_no_days"] = cs{book(), monthHistory(terms, false)} |
| + hh := monthHistory(terms, false) |
| + m := hh[0] |
| + m.Revision, m.EffectiveAt, m.Plan, m.Quantity = 2, sep15, plans.PlanCommunity, 10 |
| + out["book_two_plans_one_code"] = cs{book(), append(hh, m)} |
| + hq := monthHistory(terms, false) |
| + hq[0].Plan, hq[0].Quantity = plans.PlanCommunity, 10 |
| + q := hq[0] |
| + q.Revision, q.EffectiveAt, q.Quantity = 2, sep15, 12 |
| + out["book_quantity_change"] = cs{book(), append(hq, q)} |
| + pst := usage.Statement{V: 1, Account: "bab", Month: "2026-09", Meters: map[string]float64{usage.MeterInstanceHours: 300}, Days: map[string]map[string]float64{}, DaysInMonth: 30, Closed: true} |
| + for d := 1; d <= 30; d++ { |
| + pst.Days[fmt.Sprintf("2026-09-%02d", d)] = map[string]float64{usage.MeterInstanceHours: 10} |
| + } |
| + ph := monthHistory(terms, false) |
| + ph[0].EffectiveAt = time.Date(2026, 9, 10, 0, 0, 0, 0, time.UTC) |
| + out["book_partial"] = cs{pst, ph} |
| + w := book() |
| + w.Meters = map[string]float64{} |
| + out["book_waived"] = cs{w, monthHistory(terms, false)} |
| + return out |
| +} |
| + |
| +// The code a fixture's first plan maps to, as the approved configuration |
| +// has it; the fixtures' request hashes were recorded with these. |
| +var taxShapeCodes = map[string]string{plans.PlanPersonal: "txcd_10102001", plans.PlanCommunity: "txcd_10102001", plans.PlanBusiness: "txcd_10102000"} |
| + |
| +func TestTodaysCalculationBytesArePinned(t *testing.T) { |
| + start := time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC) |
| + inputs := taxShapeInputs() |
| + if len(inputs) != len(taxShapeFixtures) { |
| + t.Fatalf("%d inputs, %d fixtures", len(inputs), len(taxShapeFixtures)) |
| + } |
| + for name, want := range taxShapeFixtures { |
| + in, ok := inputs[name] |
| + if !ok { |
| + t.Fatalf("%s: no input", name) |
| + } |
| + calc, err := priceBillingCalculation(in.st, in.h) |
| + if err != nil { |
| + t.Fatalf("%s: %v", name, err) |
| + } |
| + raw, err := json.Marshal(calc) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if string(raw) != want.json { |
| + t.Errorf("%s: the calculation's bytes changed:\n got %s\nwant %s", name, raw, want.json) |
| + } |
| + sum := sha256.Sum256(raw) |
| + if digest := hex.EncodeToString(sum[:]); digest != want.digest { |
| + t.Errorf("%s: digest %s, want %s", name, digest, want.digest) |
| + } |
| + if len(calc.Lines) != len(want.lines) { |
| + t.Fatalf("%s: %d lines, want %d", name, len(calc.Lines), len(want.lines)) |
| + } |
| + code := taxShapeCodes[in.h[0].Plan] |
| + for i, line := range calc.Lines { |
| + p := billingstripe.InvoiceItemParams{Customer: "cus_fixture", Invoice: "in_fixture", Meter: line.Meter, TaxCode: code, Description: billingInvoiceLineDescription(line.Meter, "2026-09"), CalculationDigest: want.digest, AmountCents: line.AmountCents, PeriodStart: start.Unix(), PeriodEnd: start.AddDate(0, 1, 0).Unix(), Currency: calc.Currency} |
| + kind := "line-" + line.Meter |
| + if kind != want.lines[i].kind || billingRequestHash(kind, p) != want.lines[i].hash { |
| + t.Errorf("%s line %d: kind %s hash %s, want %s %s", name, i, kind, billingRequestHash(kind, p), want.lines[i].kind, want.lines[i].hash) |
| + } |
| + } |
| + } |
| +} |