Add reviewed sponsored cohort operator API and CLI
8 files changed,
+2911
−0
billing_cmd.go+3 −0billing_sponsored_cmd.go+845 −0billing_sponsored_cmd_test.go+523 −0docs/site/cli.md+42 −0internal/console/server.go+3 −0internal/console/sponsoredoperator.go+749 −0internal/console/sponsoredoperator_test.go+611 −0internal/console/testdata/sponsored-operator-wire.json+135 −0
CI passed — run details
CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok amadan.net/carlos/platform.git/launchctx 0.012s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
go vet ./...
gofmt -l .: clean
go test ./... github.com/benbjohnson/litestream/s3 -count=1 -timeout 30m
ok github.com/carlosframework/platform 173.512s
ok github.com/carlosframework/platform/cmd/console 2.952s
? github.com/carlosframework/platform/cmd/pricebook-export [no test files]
ok github.com/carlosframework/platform/internal/activator 55.970s
ok github.com/carlosframework/platform/internal/agentsock 0.015s
ok github.com/carlosframework/platform/internal/analytics 0.018s
ok github.com/carlosframework/platform/internal/appconfig 0.714s
ok github.com/carlosframework/platform/internal/apperr 0.064s
ok github.com/carlosframework/platform/internal/applog 0.009s
ok github.com/carlosframework/platform/internal/authority 0.004s
ok github.com/carlosframework/platform/internal/awsbilling 0.006s
ok github.com/carlosframework/platform/internal/awsinv 0.003s
ok github.com/carlosframework/platform/internal/billingstripe 0.205s
ok github.com/carlosframework/platform/internal/caps 0.011s
ok github.com/carlosframework/platform/internal/certwarm 0.002s
ok github.com/carlosframework/platform/internal/cgroup 0.038s
ok github.com/carlosframework/platform/internal/clamd 0.054s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 229.221s
? github.com/carlosframework/platform/internal/controlname [no test files]
? github.com/carlosframework/platform/internal/controlsock [no test files]
ok github.com/carlosframework/platform/internal/dbverify 0.004s
ok github.com/carlosframework/platform/internal/dnsrec 0.003s
ok github.com/carlosframework/platform/internal/docsite 0.004s
ok github.com/carlosframework/platform/internal/domains 1.075s
ok github.com/carlosframework/platform/internal/economics 0.004s
ok github.com/carlosframework/platform/internal/edge 20.403s
ok github.com/carlosframework/platform/internal/edgesfile 0.008s
ok github.com/carlosframework/platform/internal/entitlement 0.009s
ok github.com/carlosframework/platform/internal/fleetapi 0.100s
ok github.com/carlosframework/platform/internal/gate 1.655s
ok github.com/carlosframework/platform/internal/host 7.124s
ok github.com/carlosframework/platform/internal/infraview 0.002s
ok github.com/carlosframework/platform/internal/instancectl 0.416s
ok github.com/carlosframework/platform/internal/instanceexec 0.011s
ok github.com/carlosframework/platform/internal/instancefs 0.002s
ok github.com/carlosframework/platform/internal/lease 5.554s
ok github.com/carlosframework/platform/internal/ledger 0.012s
ok github.com/carlosframework/platform/internal/mail 0.041s
ok github.com/carlosframework/platform/internal/maintenance 0.080s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.509s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.044s
ok github.com/carlosframework/platform/internal/metrics 0.013s
ok github.com/carlosframework/platform/internal/objstore 7.486s
ok github.com/carlosframework/platform/internal/ops 15.429s
ok github.com/carlosframework/platform/internal/ops/admissionmodel 0.081s
ok github.com/carlosframework/platform/internal/plans 0.063s
ok github.com/carlosframework/platform/internal/privatefile 0.005s
ok github.com/carlosframework/platform/internal/provision 0.836s
ok github.com/carlosframework/platform/internal/publicrelease 0.007s
ok github.com/carlosframework/platform/internal/registry 1.390s
ok github.com/carlosframework/platform/internal/relay 0.182s
ok github.com/carlosframework/platform/internal/release 10.980s
ok github.com/carlosframework/platform/internal/resourcebudget 0.317s
ok github.com/carlosframework/platform/internal/s3compat 0.006s
ok github.com/carlosframework/platform/internal/schedule 1.034s
ok github.com/carlosframework/platform/internal/sigfence 1.904s
ok github.com/carlosframework/platform/internal/status 0.024s
ok github.com/carlosframework/platform/internal/steering 0.007s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.005s
ok github.com/carlosframework/platform/internal/tunnelclient 0.005s
ok github.com/carlosframework/platform/internal/unixdial 0.003s
ok github.com/carlosframework/platform/internal/usage 3.474s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.561s
ok github.com/benbjohnson/litestream/s3 2.176s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: cached, digest verified
mc: cached, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.024s
minio contract: PASS
diff --git a/billing_cmd.go b/billing_cmd.go| index ff4c6749e..50ab46ded 100644 |
| --- a/billing_cmd.go |
| +++ b/billing_cmd.go |
| @@ -492,6 +492,9 @@ func runBilling(args []string) { |
| // the whole of the command, from the arguments to the answer, with the |
| // one thing a test cannot have (a logged-in console) handed in. |
| func runBillingWith(args []string, session func(console string) (*apiSession, error), w io.Writer) error { |
| + if len(args) > 0 && args[0] == "sponsored" { |
| + return runBillingSponsoredWith(args[1:], session, w) |
| + } |
| inv, err := parseBilling(args) |
| if err != nil { |
| if errors.Is(err, flag.ErrHelp) { |
diff --git a/billing_sponsored_cmd.go b/billing_sponsored_cmd.go| new file mode 100644 |
| index 000000000..ab437f02b |
| --- /dev/null |
| +++ b/billing_sponsored_cmd.go |
| @@ -0,0 +1,845 @@ |
| +package main |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "encoding/json" |
| + "errors" |
| + "flag" |
| + "fmt" |
| + "github.com/carlosframework/platform/internal/domains" |
| + "io" |
| + "net/http" |
| + "os" |
| + "reflect" |
| + "sort" |
| + "strings" |
| + "unicode/utf16" |
| + "unicode/utf8" |
| +) |
| + |
| +const ( |
| + sponsoredCopyUsageHelpHeading = "Sponsored account enrollment" |
| + sponsoredCopyUsageDocsHeading = "`carlos billing sponsored`" |
| + sponsoredCopyUsageHelpSummary = "Use `preview` to read an account snapshot, `enroll` to submit a saved review, and `status` to read the historical result. Enrollment needs an explicit console from `--console` or `CARLOS_CONSOLE`. The CLI does not create operation IDs, fill choices, rewrite the roster, or choose a target." |
| + sponsoredCopyUsageUsagePreview = "`carlos billing sponsored preview [--console URL] [--json]`" |
| + sponsoredCopyUsageUsageEnroll = "`carlos billing sponsored enroll --roster FILE [--console URL] [--json]`" |
| + sponsoredCopyUsageUsageStatus = "`carlos billing sponsored status --operation 64hex [--console URL] [--json]`" |
| + sponsoredCopyUsageArgumentError = "Invalid sponsored billing arguments." |
| + sponsoredCopyUsageWriteConsoleError = "Enrollment requires an explicit console from `--console` or `CARLOS_CONSOLE`." |
| + sponsoredCopyPreviewScopeOutputHeader = "Complete snapshot for the selected console" |
| + sponsoredCopyPreviewScopeFieldConsole = "Selected console" |
| + sponsoredCopyPreviewScopeFieldObservedAt = "Observed" |
| + sponsoredCopyPreviewScopeFieldCount = "Accounts" |
| + sponsoredCopyPreviewScopeFieldAccount = "Account" |
| + sponsoredCopyPreviewScopeDocs = "The preview is a complete bounded snapshot of the accounts known to the selected console at one observation time. It includes current accounts and tombstones for accounts that are no longer active. It is not a historical census and does not prove which accounts existed on the instruction date.\n\nThe preview can contain more accounts than you intend to enroll. Review every item and record one choice for each submitted item." |
| + sponsoredCopyPreviewSystemOutputLabel = "System account" |
| + sponsoredCopyPreviewSystemDocs = "Account 1, `bab`, is marked as a system account. To enroll it, use `choice: enroll` and `confirm_system_account: true`. For every other choice and account, `confirm_system_account` must be `false`. The preview never chooses `enroll`, `exclude`, or `needs_review` for you." |
| + sponsoredCopyPreviewLimitError = "The complete preview is too large to return. No partial roster was produced." |
| + sponsoredCopyRosterFormatDocsHeading = "Roster file" |
| + sponsoredCopyRosterFormatDocsFile = "The roster file contains `console_url` and `request`. Its `console_url` must be the normalized console URL exactly as selected: no user information, path, query, fragment, trailing slash, or surrounding whitespace. The `request` uses version `1` and requires a 64-character lowercase hexadecimal `operation_id`, a 64-character lowercase hexadecimal `preview_digest`, the exact `decision_reference` `carloku-existing-2026-10-07`, `coverage_acknowledged: true`, a `note`, and an ordered `items` array with 1 to 1000 entries." |
| + sponsoredCopyRosterFormatDocsItems = "Items must be ordered by strictly increasing `account_id`. Each item requires a matching `sqid`, one `choice` from `enroll`, `exclude`, or `needs_review`, a `reason`, and `confirm_system_account`. Account 1 requires explicit confirmation only when its choice is `enroll`." |
| + sponsoredCopyRosterFormatDocsOperation = "Choose a unique operation ID for the file. It is an idempotency key, which lets the same request be recognized again. It is not a secret or cryptographic proof." |
| + sponsoredCopyRosterFormatDocsSend = "The CLI reads the supplied file once and sends the accepted request bytes. It does not sort, deduplicate, trim, repair, rewrite, or fill missing values." |
| + sponsoredCopyCoverageAckDocs = "`coverage_acknowledged: true` records that the operator understands the preview's evidence limits. It is not proof that every historical account was found. It does not select all accounts automatically." |
| + sponsoredCopyConsoleMismatchError = "The roster `console_url` must exactly match the selected console." |
| + sponsoredCopyInvalidRequestError = "The request is invalid." |
| + sponsoredCopyBodyLimitError = "The roster file is too large." |
| + sponsoredCopyResultLimitError = "The enrollment result is too large. No enrollment was recorded." |
| + sponsoredCopyConflictError = "This operation ID is already bound to another request or operator." |
| + sponsoredCopyNotFoundError = "No sponsored enrollment operation was found on this console." |
| + sponsoredCopyReadWriteFailureReadError = "The sponsored billing result could not be read." |
| + sponsoredCopyReadWriteFailureWriteError = "The enrollment may have been recorded. Check historical status, or retry with the same saved file, operation ID, selected console, and authenticated operator." |
| + sponsoredCopyHistoricalResultOutputHeading = "Historical enrollment result" |
| + sponsoredCopyHistoricalResultFieldOperation = "Operation" |
| + sponsoredCopyHistoricalResultFieldActor = "Operator" |
| + sponsoredCopyHistoricalResultFieldRecordedAt = "Recorded" |
| + sponsoredCopyHistoricalResultFieldRequestDigest = "Request digest" |
| + sponsoredCopyHistoricalResultFieldReceiptDigest = "Receipt digest" |
| + sponsoredCopyHistoricalResultFieldReplayed = "Replayed" |
| + sponsoredCopyHistoricalResultDocs = "Enrollment and status output is historical evidence. It identifies the selected console, original operation, original operator, time, request digest, receipt digest, and recorded outcomes. A digest is a calculated reference to submitted or returned data.\n\nThe output does not claim that an account has a current grant or valid current sponsorship. Status is read-only, and another current operator may read an earlier operator's historical receipt." |
| + sponsoredCopyOutcomeSummaryOutputHeading = "Submitted item outcomes" |
| + sponsoredCopyOutcomeSummaryFieldOutcome = "Outcome" |
| + sponsoredCopyOutcomeSummaryDocs = "The result lists every submitted item. Outcomes distinguish newly added and already recorded accounts from operator exclusions, items marked for review, missing accounts, and retired accounts. An operation can add zero memberships." |
| + sponsoredCopyRetryWarning = "The enrollment may have been recorded. Check `carlos billing sponsored status --operation 64hex` on the same console, or retry exactly with the same saved file, operation ID, selected console, and authenticated operator. Do not repost automatically, create a new operation, rewrite the file, or change consoles. A 404 from another, moved, or restored console says nothing about the original attempt." |
| + sponsoredCopyOutputFailureError = "The enrollment result was received, but the complete output could not be written. Do not retry the enrollment. Use `status` to recover the historical evidence." |
| + sponsoredCopyOutputFailureReadError = "The sponsored billing result was received, but the complete output could not be written. Run the read command again." |
| + sponsoredCopyProtocolErrorEnrollError = "The enrollment response was invalid or incomplete. The enrollment may have been recorded. Check historical status or retry exactly with the same saved file, operation ID, selected console, and authenticated operator." |
| + sponsoredCopyProtocolErrorReadError = "The sponsored billing response was invalid or incomplete." |
| + sponsoredCopyAdditionOnlyDocs = "Enrollment is addition-only. `exclude` and `needs_review` record the reviewed outcome; they do not revoke an existing enrollment.\n\nEnrollment does not apply grants or change billing. It does not clear holds, set an expiry, convert accounts to paid use, or establish current sponsorship authority." |
| + sponsoredCopyPersonalDataWarning = "Preview output contains owner email addresses and account facts. You control where command output and manually saved files are shared. The CLI does not automatically save, cache, or upload this data. Diagnostics do not include raw rosters, owner lists, notes, tokens, database details, or corrupt receipt bodies." |
| +) |
| + |
| +const sponsoredWireRequestLimit = 4 << 20 |
| +const sponsoredWireResponseLimit = 8 << 20 |
| + |
| +var errSponsoredWire = errors.New(sponsoredCopyInvalidRequestError) |
| + |
| +type sponsoredWireRequest struct { |
| + Version int `json:"version"` |
| + OperationID string `json:"operation_id"` |
| + PreviewDigest string `json:"preview_digest"` |
| + DecisionReference string `json:"decision_reference"` |
| + CoverageAcknowledged bool `json:"coverage_acknowledged"` |
| + Note string `json:"note"` |
| + Items []sponsoredWireItem `json:"items"` |
| +} |
| + |
| +func (out *sponsoredWireRequest) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireRequest |
| + if err := sponsoredWireObject(raw, true, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "operation_id": {target: &v.OperationID, optional: false, nullable: false}, |
| + "preview_digest": {target: &v.PreviewDigest, optional: false, nullable: false}, |
| + "decision_reference": {target: &v.DecisionReference, optional: false, nullable: false}, |
| + "coverage_acknowledged": {target: &v.CoverageAcknowledged, optional: false, nullable: false}, |
| + "note": {target: &v.Note, optional: false, nullable: false}, |
| + "items": {target: &v.Items, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireItem struct { |
| + AccountID int64 `json:"account_id"` |
| + Sqid string `json:"sqid"` |
| + Choice string `json:"choice"` |
| + Reason string `json:"reason"` |
| + ConfirmSystemAccount bool `json:"confirm_system_account"` |
| +} |
| + |
| +func (out *sponsoredWireItem) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireItem |
| + if err := sponsoredWireObject(raw, true, map[string]sponsoredWireField{ |
| + "account_id": {target: &v.AccountID, optional: false, nullable: false}, |
| + "sqid": {target: &v.Sqid, optional: false, nullable: false}, |
| + "choice": {target: &v.Choice, optional: false, nullable: false}, |
| + "reason": {target: &v.Reason, optional: false, nullable: false}, |
| + "confirm_system_account": {target: &v.ConfirmSystemAccount, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireBilling struct { |
| + Present bool `json:"present"` |
| + House bool `json:"house"` |
| + LiveMode bool `json:"live_mode"` |
| + CustomerPresent bool `json:"customer_present"` |
| + Subscription string `json:"subscription"` |
| + Status string `json:"status"` |
| +} |
| + |
| +func (out *sponsoredWireBilling) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireBilling |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "present": {target: &v.Present, optional: false, nullable: false}, |
| + "house": {target: &v.House, optional: false, nullable: false}, |
| + "live_mode": {target: &v.LiveMode, optional: false, nullable: false}, |
| + "customer_present": {target: &v.CustomerPresent, optional: false, nullable: false}, |
| + "subscription": {target: &v.Subscription, optional: false, nullable: false}, |
| + "status": {target: &v.Status, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireOutbox struct { |
| + Present bool `json:"present"` |
| + LiveMode bool `json:"live_mode"` |
| + SerialOwed bool `json:"serial_owed"` |
| + Revision int64 `json:"revision"` |
| + PublishedRevision int64 `json:"published_revision"` |
| + Digest string `json:"digest"` |
| + State string `json:"state"` |
| +} |
| + |
| +func (out *sponsoredWireOutbox) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireOutbox |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "present": {target: &v.Present, optional: false, nullable: false}, |
| + "live_mode": {target: &v.LiveMode, optional: false, nullable: false}, |
| + "serial_owed": {target: &v.SerialOwed, optional: false, nullable: false}, |
| + "revision": {target: &v.Revision, optional: false, nullable: false}, |
| + "published_revision": {target: &v.PublishedRevision, optional: false, nullable: false}, |
| + "digest": {target: &v.Digest, optional: false, nullable: false}, |
| + "state": {target: &v.State, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireFacts struct { |
| + AccountID int64 `json:"account_id"` |
| + Sqid string `json:"sqid"` |
| + Name string `json:"name"` |
| + StoredCreatedAt int64 `json:"stored_created_at"` |
| + DeletedAt int64 `json:"deleted_at"` |
| + Owners []string `json:"owners"` |
| + Billing sponsoredWireBilling `json:"billing"` |
| + CheckoutPresent bool `json:"checkout_present"` |
| + BillingRetirement bool `json:"billing_retirement"` |
| + Outbox sponsoredWireOutbox `json:"outbox"` |
| + OpenBillingReviews int64 `json:"open_billing_reviews"` |
| + EntitlementReviews int64 `json:"entitlement_reviews"` |
| +} |
| + |
| +func (out *sponsoredWireFacts) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireFacts |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "account_id": {target: &v.AccountID, optional: false, nullable: false}, |
| + "sqid": {target: &v.Sqid, optional: false, nullable: false}, |
| + "name": {target: &v.Name, optional: false, nullable: false}, |
| + "stored_created_at": {target: &v.StoredCreatedAt, optional: false, nullable: false}, |
| + "deleted_at": {target: &v.DeletedAt, optional: false, nullable: false}, |
| + "owners": {target: &v.Owners, optional: false, nullable: false}, |
| + "billing": {target: &v.Billing, optional: false, nullable: false}, |
| + "checkout_present": {target: &v.CheckoutPresent, optional: false, nullable: false}, |
| + "billing_retirement": {target: &v.BillingRetirement, optional: false, nullable: false}, |
| + "outbox": {target: &v.Outbox, optional: false, nullable: false}, |
| + "open_billing_reviews": {target: &v.OpenBillingReviews, optional: false, nullable: false}, |
| + "entitlement_reviews": {target: &v.EntitlementReviews, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWirePreviewRow struct { |
| + Facts sponsoredWireFacts `json:"facts"` |
| + SystemAccount bool `json:"system_account"` |
| + Retired bool `json:"retired"` |
| +} |
| + |
| +func (out *sponsoredWirePreviewRow) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWirePreviewRow |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "facts": {target: &v.Facts, optional: false, nullable: false}, |
| + "system_account": {target: &v.SystemAccount, optional: false, nullable: false}, |
| + "retired": {target: &v.Retired, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWirePreviewPayload struct { |
| + Version int `json:"version"` |
| + Coverage string `json:"coverage"` |
| + Count int `json:"count"` |
| + Rows []sponsoredWirePreviewRow `json:"rows"` |
| +} |
| + |
| +func (out *sponsoredWirePreviewPayload) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWirePreviewPayload |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "coverage": {target: &v.Coverage, optional: false, nullable: false}, |
| + "count": {target: &v.Count, optional: false, nullable: false}, |
| + "rows": {target: &v.Rows, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWirePreview struct { |
| + Version int `json:"version"` |
| + Preview sponsoredWirePreviewPayload `json:"preview"` |
| + PreviewDigest string `json:"preview_digest"` |
| + ObservedAt int64 `json:"observed_at"` |
| +} |
| + |
| +func (out *sponsoredWirePreview) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWirePreview |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "preview": {target: &v.Preview, optional: false, nullable: false}, |
| + "preview_digest": {target: &v.PreviewDigest, optional: false, nullable: false}, |
| + "observed_at": {target: &v.ObservedAt, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireObservation struct { |
| + AccountID int64 `json:"account_id"` |
| + Sqid string `json:"sqid"` |
| + Outcome string `json:"outcome"` |
| + Facts *sponsoredWireFacts `json:"facts"` |
| + FirstEnrollmentOperationID string `json:"first_enrollment_operation_id"` |
| +} |
| + |
| +func (out *sponsoredWireObservation) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireObservation |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "account_id": {target: &v.AccountID, optional: false, nullable: false}, |
| + "sqid": {target: &v.Sqid, optional: false, nullable: false}, |
| + "outcome": {target: &v.Outcome, optional: false, nullable: false}, |
| + "facts": {target: &v.Facts, optional: false, nullable: true}, |
| + "first_enrollment_operation_id": {target: &v.FirstEnrollmentOperationID, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireHistorical struct { |
| + Version int `json:"version"` |
| + Actor string `json:"actor"` |
| + Request sponsoredWireRequest `json:"request"` |
| + RecordedAt int64 `json:"recorded_at"` |
| + Rows []sponsoredWireObservation `json:"rows"` |
| +} |
| + |
| +func (out *sponsoredWireHistorical) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireHistorical |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "actor": {target: &v.Actor, optional: false, nullable: false}, |
| + "request": {target: &v.Request, optional: false, nullable: false}, |
| + "recorded_at": {target: &v.RecordedAt, optional: false, nullable: false}, |
| + "rows": {target: &v.Rows, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireResult struct { |
| + Version int `json:"version"` |
| + HistoricalOnly bool `json:"historical_only"` |
| + RequestDigest string `json:"request_digest"` |
| + ReceiptDigest string `json:"receipt_digest"` |
| + Historical sponsoredWireHistorical `json:"historical"` |
| + Replayed *bool `json:"replayed,omitempty"` |
| +} |
| + |
| +func (out *sponsoredWireResult) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireResult |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "historical_only": {target: &v.HistoricalOnly, optional: false, nullable: false}, |
| + "request_digest": {target: &v.RequestDigest, optional: false, nullable: false}, |
| + "receipt_digest": {target: &v.ReceiptDigest, optional: false, nullable: false}, |
| + "historical": {target: &v.Historical, optional: false, nullable: false}, |
| + "replayed": {target: &v.Replayed, optional: true, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireField struct { |
| + target any |
| + optional, nullable bool |
| +} |
| + |
| +func sponsoredWireObject(raw []byte, strict bool, fields map[string]sponsoredWireField) error { |
| + if !utf8.Valid(raw) { |
| + return errSponsoredWire |
| + } |
| + dec := json.NewDecoder(bytes.NewReader(raw)) |
| + token, err := dec.Token() |
| + if err != nil || token != json.Delim('{') { |
| + return errSponsoredWire |
| + } |
| + seen := map[string]bool{} |
| + for dec.More() { |
| + k, err := dec.Token() |
| + if err != nil { |
| + return errSponsoredWire |
| + } |
| + key, ok := k.(string) |
| + if !ok || seen[key] { |
| + return errSponsoredWire |
| + } |
| + seen[key] = true |
| + var value json.RawMessage |
| + if dec.Decode(&value) != nil { |
| + return errSponsoredWire |
| + } |
| + field, known := fields[key] |
| + if !known { |
| + if strict { |
| + return errSponsoredWire |
| + } |
| + for name := range fields { |
| + if strings.EqualFold(key, name) { |
| + return errSponsoredWire |
| + } |
| + } |
| + continue |
| + } |
| + if bytes.Equal(bytes.TrimSpace(value), []byte("null")) { |
| + if !field.nullable { |
| + return errSponsoredWire |
| + } |
| + continue |
| + } |
| + if _, ok := field.target.(*[]string); ok { |
| + var values []json.RawMessage |
| + if json.Unmarshal(value, &values) != nil { |
| + return errSponsoredWire |
| + } |
| + for _, v := range values { |
| + if len(bytes.TrimSpace(v)) == 0 || bytes.TrimSpace(v)[0] != '"' { |
| + return errSponsoredWire |
| + } |
| + } |
| + } |
| + if json.Unmarshal(value, field.target) != nil { |
| + return errSponsoredWire |
| + } |
| + } |
| + if tok, err := dec.Token(); err != nil || tok != json.Delim('}') { |
| + return errSponsoredWire |
| + } |
| + if dec.Decode(new(json.RawMessage)) != io.EOF { |
| + return errSponsoredWire |
| + } |
| + for key, f := range fields { |
| + if !seen[key] && !f.optional { |
| + return errSponsoredWire |
| + } |
| + } |
| + return nil |
| +} |
| +func sponsoredWireHex(v string) bool { |
| + if len(v) != 64 { |
| + return false |
| + } |
| + for _, c := range v { |
| + if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| +func sponsoredWireText(v string) bool { |
| + if v == "" || len(v) > 256 || !utf8.ValidString(v) || strings.ContainsRune(v, '\uFFFD') { |
| + return false |
| + } |
| + for _, c := range v { |
| + if c < 32 || c == 127 { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| +func (v sponsoredWireRequest) validate() error { |
| + if v.Version != 1 || !sponsoredWireHex(v.OperationID) || !sponsoredWireHex(v.PreviewDigest) || v.DecisionReference != "carloku-existing-2026-10-07" || !v.CoverageAcknowledged || !sponsoredWireText(v.Note) || len(v.Items) < 1 || len(v.Items) > 1000 { |
| + return errSponsoredWire |
| + } |
| + var last int64 |
| + for _, item := range v.Items { |
| + if item.AccountID <= last || item.Sqid != domains.Sqid(item.AccountID) || !sponsoredWireText(item.Reason) || (item.Choice != "enroll" && item.Choice != "exclude" && item.Choice != "needs_review") || item.ConfirmSystemAccount != (item.AccountID == 1 && item.Choice == "enroll") { |
| + return errSponsoredWire |
| + } |
| + last = item.AccountID |
| + } |
| + return nil |
| +} |
| + |
| +type sponsoredInvocation struct { |
| + verb, console, roster, operation string |
| + json, help bool |
| +} |
| + |
| +func parseSponsored(args []string) (sponsoredInvocation, error) { |
| + var v sponsoredInvocation |
| + bad := errors.New(sponsoredCopyUsageArgumentError) |
| + if len(args) == 0 { |
| + return v, bad |
| + } |
| + if args[0] == "--help" || args[0] == "-h" { |
| + if len(args) != 1 { |
| + return v, bad |
| + } |
| + v.help = true |
| + return v, nil |
| + } |
| + v.verb = args[0] |
| + if v.verb != "preview" && v.verb != "enroll" && v.verb != "status" { |
| + return v, bad |
| + } |
| + if billingArgsAreUnambiguous(args[1:]) != nil { |
| + return v, bad |
| + } |
| + fs := flag.NewFlagSet("sponsored", flag.ContinueOnError) |
| + fs.SetOutput(io.Discard) |
| + fs.StringVar(&v.console, "console", "", "") |
| + fs.StringVar(&v.roster, "roster", "", "") |
| + fs.StringVar(&v.operation, "operation", "", "") |
| + fs.BoolVar(&v.json, "json", false, "") |
| + if err := fs.Parse(args[1:]); err != nil { |
| + if errors.Is(err, flag.ErrHelp) { |
| + v.help = true |
| + return v, nil |
| + } |
| + return v, bad |
| + } |
| + if fs.NArg() != 0 { |
| + return v, bad |
| + } |
| + invalid := false |
| + fs.Visit(func(f *flag.Flag) { |
| + switch f.Name { |
| + case "console": |
| + invalid = invalid || strings.TrimSpace(v.console) == "" |
| + case "roster": |
| + invalid = invalid || v.verb != "enroll" || strings.TrimSpace(v.roster) == "" |
| + case "operation": |
| + invalid = invalid || v.verb != "status" || !sponsoredWireHex(v.operation) |
| + } |
| + }) |
| + if invalid || (v.verb == "enroll" && v.roster == "") || (v.verb == "status" && !sponsoredWireHex(v.operation)) { |
| + return v, bad |
| + } |
| + if v.verb == "enroll" && v.console == "" && strings.TrimSpace(os.Getenv("CARLOS_CONSOLE")) == "" { |
| + return v, errors.New(sponsoredCopyUsageWriteConsoleError) |
| + } |
| + return v, nil |
| +} |
| +func sponsoredHelp() string { |
| + return strings.Join([]string{sponsoredCopyUsageHelpHeading, sponsoredCopyUsageHelpSummary, sponsoredCopyUsageUsagePreview, sponsoredCopyUsageUsageEnroll, sponsoredCopyUsageUsageStatus}, "\n\n") + "\n" |
| +} |
| +func sponsoredReadBounded(r io.Reader, limit int64) ([]byte, error) { |
| + raw, err := io.ReadAll(io.LimitReader(r, limit+1)) |
| + if err != nil { |
| + return nil, err |
| + } |
| + if int64(len(raw)) > limit { |
| + return nil, errSponsoredWire |
| + } |
| + return raw, nil |
| +} |
| +func sponsoredReadRoster(path string) (string, []byte, sponsoredWireRequest, error) { |
| + var req sponsoredWireRequest |
| + file, err := os.Open(path) |
| + if err != nil { |
| + return "", nil, req, errors.New(sponsoredCopyInvalidRequestError) |
| + } |
| + defer file.Close() |
| + raw, err := sponsoredReadBounded(file, sponsoredWireResponseLimit) |
| + if err != nil { |
| + if errors.Is(err, errSponsoredWire) { |
| + return "", nil, req, errors.New(sponsoredCopyBodyLimitError) |
| + } |
| + return "", nil, req, errors.New(sponsoredCopyInvalidRequestError) |
| + } |
| + var console string |
| + var body json.RawMessage |
| + if sponsoredWireObject(raw, true, map[string]sponsoredWireField{"console_url": {target: &console}, "request": {target: &body}}) != nil { |
| + return "", nil, req, errSponsoredWire |
| + } |
| + if len(body) > sponsoredWireRequestLimit { |
| + return "", nil, req, errors.New(sponsoredCopyBodyLimitError) |
| + } |
| + if json.Unmarshal(body, &req) != nil || req.validate() != nil { |
| + return "", nil, sponsoredWireRequest{}, errSponsoredWire |
| + } |
| + normalized, err := normalizeConsole(console) |
| + if err != nil || normalized != console { |
| + return "", nil, sponsoredWireRequest{}, errors.New(sponsoredCopyConsoleMismatchError) |
| + } |
| + return console, body, req, nil |
| +} |
| +func (s *apiSession) sponsoredRequest(ctx context.Context, method, path string, body []byte) (int, []byte, error) { |
| + req, err := http.NewRequestWithContext(ctx, method, s.c.base+path, bytes.NewReader(body)) |
| + if err != nil { |
| + return 0, nil, err |
| + } |
| + if body != nil { |
| + req.Header.Set("Content-Type", "application/json") |
| + } |
| + if s.c.token != "" { |
| + req.Header.Set("Authorization", "Bearer "+s.c.token) |
| + } |
| + res, err := s.c.httpc.Do(req) |
| + if err != nil { |
| + return 0, nil, err |
| + } |
| + defer res.Body.Close() |
| + if moved := consoleMoved(s.c.base, res.StatusCode, res.Header.Get("Location")); moved != nil { |
| + return res.StatusCode, nil, offerConsoleMoved(moved) |
| + } |
| + raw, err := sponsoredReadBounded(res.Body, sponsoredWireResponseLimit) |
| + return res.StatusCode, raw, err |
| +} |
| +func sponsoredFactsValid(v *sponsoredWireFacts) bool { |
| + if v == nil || v.AccountID <= 0 || v.Sqid != domains.Sqid(v.AccountID) || v.Owners == nil || !sort.StringsAreSorted(v.Owners) { |
| + return false |
| + } |
| + for _, text := range append([]string{v.Name, v.Billing.Subscription, v.Billing.Status, v.Outbox.Digest, v.Outbox.State}, v.Owners...) { |
| + if !utf8.ValidString(text) { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| +func sponsoredValidatePreview(v sponsoredWirePreview) error { |
| + if v.Version != 1 || v.Preview.Version != 1 || v.Preview.Coverage != "console_known_accounts" || v.Preview.Rows == nil || v.Preview.Count != len(v.Preview.Rows) || v.ObservedAt <= 0 || !sponsoredWireHex(v.PreviewDigest) { |
| + return errSponsoredWire |
| + } |
| + var last int64 |
| + for _, row := range v.Preview.Rows { |
| + if !sponsoredFactsValid(&row.Facts) || row.Facts.AccountID <= last || row.SystemAccount != (row.Facts.AccountID == 1) || row.Retired != (row.Facts.DeletedAt != 0 || row.Facts.BillingRetirement) { |
| + return errSponsoredWire |
| + } |
| + last = row.Facts.AccountID |
| + } |
| + return nil |
| +} |
| +func sponsoredValidateResult(v sponsoredWireResult, operation string, req *sponsoredWireRequest, actor string) error { |
| + h := v.Historical |
| + if v.Version != 1 || !v.HistoricalOnly || !sponsoredWireHex(v.RequestDigest) || !sponsoredWireHex(v.ReceiptDigest) || h.Version != 1 || h.RecordedAt <= 0 || !domains.ValidAddress(h.Actor) || h.Request.OperationID != operation || h.Request.validate() != nil || h.Rows == nil || len(h.Rows) != len(h.Request.Items) { |
| + return errSponsoredWire |
| + } |
| + if req != nil { |
| + if v.Replayed == nil || !reflect.DeepEqual(h.Request, *req) || h.Actor != actor { |
| + return errSponsoredWire |
| + } |
| + } else if v.Replayed != nil { |
| + return errSponsoredWire |
| + } |
| + for i, row := range h.Rows { |
| + item := h.Request.Items[i] |
| + if row.AccountID != item.AccountID || row.Sqid != item.Sqid { |
| + return errSponsoredWire |
| + } |
| + first := row.FirstEnrollmentOperationID |
| + if first != "" && !sponsoredWireHex(first) { |
| + return errSponsoredWire |
| + } |
| + if row.Facts != nil && (!sponsoredFactsValid(row.Facts) || row.Facts.AccountID != row.AccountID || row.Facts.Sqid != row.Sqid) { |
| + return errSponsoredWire |
| + } |
| + outcome := "" |
| + switch { |
| + case row.Facts == nil: |
| + outcome = "excluded_missing" |
| + case row.Facts.DeletedAt != 0 || row.Facts.BillingRetirement: |
| + outcome = "excluded_retired" |
| + case item.Choice == "exclude": |
| + outcome = "excluded_by_operator" |
| + case item.Choice == "needs_review": |
| + outcome = "needs_review" |
| + case first == operation: |
| + outcome = "added" |
| + case first != "": |
| + outcome = "already_recorded" |
| + default: |
| + return errSponsoredWire |
| + } |
| + if row.Outcome != outcome || (row.Outcome != "added" && first == operation) { |
| + return errSponsoredWire |
| + } |
| + } |
| + return nil |
| +} |
| +func sponsoredOutput(w io.Writer, raw []byte, writing bool) error { |
| + n, err := w.Write(raw) |
| + if err != nil || n != len(raw) { |
| + if writing { |
| + return errors.New(sponsoredCopyOutputFailureError) |
| + } |
| + return errors.New(sponsoredCopyOutputFailureReadError) |
| + } |
| + return nil |
| +} |
| +func runBillingSponsoredWith(args []string, session func(string) (*apiSession, error), w io.Writer) error { |
| + inv, err := parseSponsored(args) |
| + if err != nil { |
| + return err |
| + } |
| + if inv.help { |
| + return sponsoredOutput(w, []byte(sponsoredHelp()), false) |
| + } |
| + var target string |
| + var body []byte |
| + var request sponsoredWireRequest |
| + if inv.verb == "enroll" { |
| + target, body, request, err = sponsoredReadRoster(inv.roster) |
| + if err != nil { |
| + return err |
| + } |
| + } |
| + sess, err := session(inv.console) |
| + if err != nil { |
| + return err |
| + } |
| + if inv.verb == "enroll" && target != sess.console { |
| + return errors.New(sponsoredCopyConsoleMismatchError) |
| + } |
| + return runSponsored(context.Background(), sess, inv, body, request, w) |
| +} |
| +func runSponsored(ctx context.Context, sess *apiSession, inv sponsoredInvocation, body []byte, request sponsoredWireRequest, w io.Writer) error { |
| + writing := inv.verb == "enroll" |
| + method, path := http.MethodGet, "/api/billing/sponsored/preview" |
| + if writing { |
| + method, path = http.MethodPost, "/api/billing/sponsored/enrollments" |
| + } else if inv.verb == "status" { |
| + path = "/api/billing/sponsored/enrollments/" + inv.operation |
| + } |
| + status, raw, err := sess.sponsoredRequest(ctx, method, path, body) |
| + if err != nil { |
| + var moved *consoleMovedError |
| + if errors.As(err, &moved) { |
| + return err |
| + } |
| + if writing { |
| + return errors.New(sponsoredCopyReadWriteFailureWriteError) |
| + } |
| + return errors.New(sponsoredCopyReadWriteFailureReadError) |
| + } |
| + if status != http.StatusOK { |
| + return sponsoredRefusal(status, raw, inv.verb, sess.console) |
| + } |
| + var preview sponsoredWirePreview |
| + var result sponsoredWireResult |
| + if inv.verb == "preview" { |
| + if json.Unmarshal(raw, &preview) != nil || sponsoredValidatePreview(preview) != nil { |
| + return errors.New(sponsoredCopyProtocolErrorReadError) |
| + } |
| + } else { |
| + var expected *sponsoredWireRequest |
| + operation := inv.operation |
| + if writing { |
| + expected = &request |
| + operation = request.OperationID |
| + } |
| + if json.Unmarshal(raw, &result) != nil || sponsoredValidateResult(result, operation, expected, sess.address) != nil { |
| + if writing { |
| + return errors.New(sponsoredCopyProtocolErrorEnrollError) |
| + } |
| + return errors.New(sponsoredCopyProtocolErrorReadError) |
| + } |
| + } |
| + var out bytes.Buffer |
| + if inv.json { |
| + var answer any |
| + if inv.verb == "preview" { |
| + answer = struct { |
| + Console string `json:"console_url"` |
| + Preview json.RawMessage `json:"preview"` |
| + }{sess.console, raw} |
| + } else { |
| + answer = struct { |
| + Console string `json:"console_url"` |
| + Result json.RawMessage `json:"result"` |
| + }{sess.console, raw} |
| + } |
| + if err := json.NewEncoder(&out).Encode(answer); err != nil { |
| + if writing { |
| + return errors.New(sponsoredCopyProtocolErrorEnrollError) |
| + } |
| + return errors.New(sponsoredCopyProtocolErrorReadError) |
| + } |
| + } else if inv.verb == "preview" { |
| + fmt.Fprintln(&out, sponsoredCopyPreviewScopeOutputHeader) |
| + fmt.Fprintf(&out, "%s: %q\n%s: %d\n%s: %d\n", sponsoredCopyPreviewScopeFieldConsole, sess.console, sponsoredCopyPreviewScopeFieldObservedAt, preview.ObservedAt, sponsoredCopyPreviewScopeFieldCount, preview.Preview.Count) |
| + for _, row := range preview.Preview.Rows { |
| + item := sponsoredHumanRow(row) |
| + fmt.Fprintf(&out, "%s: %s\n", sponsoredCopyPreviewScopeFieldAccount, item) |
| + if row.SystemAccount { |
| + fmt.Fprintln(&out, sponsoredCopyPreviewSystemOutputLabel) |
| + } |
| + } |
| + fmt.Fprintln(&out, sponsoredCopyPreviewScopeDocs) |
| + fmt.Fprintln(&out, sponsoredCopyPersonalDataWarning) |
| + } else { |
| + h := result.Historical |
| + fmt.Fprintln(&out, sponsoredCopyHistoricalResultOutputHeading) |
| + for _, kv := range [][2]string{{sponsoredCopyPreviewScopeFieldConsole, sess.console}, {sponsoredCopyHistoricalResultFieldOperation, h.Request.OperationID}, {sponsoredCopyHistoricalResultFieldActor, h.Actor}, {sponsoredCopyHistoricalResultFieldRequestDigest, result.RequestDigest}, {sponsoredCopyHistoricalResultFieldReceiptDigest, result.ReceiptDigest}} { |
| + fmt.Fprintf(&out, "%s: %q\n", kv[0], kv[1]) |
| + } |
| + fmt.Fprintf(&out, "%s: %d\n", sponsoredCopyHistoricalResultFieldRecordedAt, h.RecordedAt) |
| + if result.Replayed != nil { |
| + fmt.Fprintf(&out, "%s: %t\n", sponsoredCopyHistoricalResultFieldReplayed, *result.Replayed) |
| + } |
| + fmt.Fprintln(&out, sponsoredCopyOutcomeSummaryOutputHeading) |
| + for _, row := range h.Rows { |
| + item := sponsoredHumanRow(row) |
| + fmt.Fprintf(&out, "%s: %s\n", sponsoredCopyOutcomeSummaryFieldOutcome, item) |
| + } |
| + fmt.Fprintln(&out, sponsoredCopyHistoricalResultDocs) |
| + fmt.Fprintln(&out, sponsoredCopyOutcomeSummaryDocs) |
| + } |
| + return sponsoredOutput(w, out.Bytes(), writing) |
| +} |
| + |
| +func sponsoredRefusal(status int, raw []byte, verb, console string) error { |
| + fallback := sponsoredCopyReadWriteFailureReadError |
| + if verb == "enroll" { |
| + fallback = sponsoredCopyReadWriteFailureWriteError |
| + } |
| + if status == http.StatusNotFound && bytes.Equal(raw, []byte("404 page not found\n")) { |
| + return errors.New(sponsoredCopyReadWriteFailureReadError) |
| + } |
| + var message string |
| + if sponsoredWireObject(raw, true, map[string]sponsoredWireField{"error": {target: &message}}) != nil { |
| + return errors.New(fallback) |
| + } |
| + expected := "" |
| + switch status { |
| + case http.StatusBadRequest: |
| + expected = sponsoredCopyInvalidRequestError |
| + case http.StatusUnauthorized: |
| + expected = "not signed in — run: carlos auth login --console " + console |
| + case http.StatusNotFound: |
| + if verb == "status" { |
| + expected = sponsoredCopyNotFoundError |
| + } |
| + case http.StatusConflict: |
| + if verb == "enroll" { |
| + expected = sponsoredCopyConflictError |
| + } |
| + case http.StatusRequestEntityTooLarge: |
| + expected = "request body too large" |
| + case http.StatusUnsupportedMediaType: |
| + expected = "send a JSON body with Content-Type: application/json" |
| + case http.StatusUnprocessableEntity: |
| + if verb == "preview" { |
| + expected = sponsoredCopyPreviewLimitError |
| + } else if verb == "enroll" { |
| + expected = sponsoredCopyResultLimitError |
| + } |
| + } |
| + if expected != "" && message == expected { |
| + return errors.New(expected) |
| + } |
| + return errors.New(fallback) |
| +} |
| + |
| +func sponsoredHumanRow(row any) []byte { |
| + raw, _ := json.Marshal(row) |
| + var out bytes.Buffer |
| + for _, r := range string(raw) { |
| + if r < 0x7f { |
| + out.WriteByte(byte(r)) |
| + } else if r <= 0xffff { |
| + fmt.Fprintf(&out, `\u%04x`, r) |
| + } else { |
| + hi, lo := utf16.EncodeRune(r) |
| + fmt.Fprintf(&out, `\u%04x\u%04x`, hi, lo) |
| + } |
| + } |
| + return out.Bytes() |
| +} |
diff --git a/billing_sponsored_cmd_test.go b/billing_sponsored_cmd_test.go| new file mode 100644 |
| index 000000000..59e135a5c |
| --- /dev/null |
| +++ b/billing_sponsored_cmd_test.go |
| @@ -0,0 +1,523 @@ |
| +package main |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "crypto/sha256" |
| + "encoding/binary" |
| + "encoding/json" |
| + "errors" |
| + "fmt" |
| + "github.com/carlosframework/platform/internal/console" |
| + "github.com/carlosframework/platform/internal/release" |
| + "io" |
| + "net/http" |
| + "net/http/httptest" |
| + "os" |
| + "path/filepath" |
| + "reflect" |
| + "strings" |
| + "sync/atomic" |
| + "testing" |
| + "time" |
| +) |
| + |
| +func sponsoredClientFixture(t *testing.T) map[string]json.RawMessage { |
| + t.Helper() |
| + raw, e := os.ReadFile("internal/console/testdata/sponsored-operator-wire.json") |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + var out map[string]json.RawMessage |
| + if json.Unmarshal(raw, &out) != nil { |
| + t.Fatal("fixture") |
| + } |
| + return out |
| +} |
| +func sponsoredClientSession(t *testing.T, handler http.HandlerFunc) *apiSession { |
| + t.Helper() |
| + srv := httptest.NewServer(handler) |
| + t.Cleanup(srv.Close) |
| + return &apiSession{console: srv.URL, address: "operator@example.test", c: newConsoleClient(srv.URL, "test-token")} |
| +} |
| +func sponsoredClientRoster(t *testing.T, console string, raw []byte) string { |
| + t.Helper() |
| + path := filepath.Join(t.TempDir(), "roster.json") |
| + b, e := json.Marshal(console) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + data := append([]byte(`{"console_url":`+string(b)+`,"request":`), raw...) |
| + data = append(data, '}') |
| + if e := os.WriteFile(path, data, 0600); e != nil { |
| + t.Fatal(e) |
| + } |
| + return path |
| +} |
| +func TestSponsoredOperatorCLIExactBytesAndDispatch(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + var asked int |
| + var got []byte |
| + var method, path, bearer string |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { |
| + asked++ |
| + method, path, bearer = r.Method, r.URL.Path, r.Header.Get("Authorization") |
| + got, _ = io.ReadAll(r.Body) |
| + w.Write(f["enrollment"]) |
| + }) |
| + file := sponsoredClientRoster(t, sess.console, f["request"]) |
| + before, e := os.ReadFile(file) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + var out bytes.Buffer |
| + e = runBillingWith([]string{"sponsored", "enroll", "--console", sess.console, "--roster", file, "--json"}, func(target string) (*apiSession, error) { |
| + if target != sess.console { |
| + t.Fatal("target") |
| + } |
| + return sess, nil |
| + }, &out) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + if asked != 1 || method != "POST" || path != "/api/billing/sponsored/enrollments" || bearer != "Bearer test-token" || !bytes.Equal(got, f["request"]) { |
| + t.Fatalf("request changed: %q", got) |
| + } |
| + after, _ := os.ReadFile(file) |
| + if !bytes.Equal(before, after) { |
| + t.Fatal("file rewritten") |
| + } |
| + var envelope map[string]json.RawMessage |
| + if json.Unmarshal(out.Bytes(), &envelope) != nil || envelope["console_url"] == nil || envelope["result"] == nil { |
| + t.Fatal("output envelope") |
| + } |
| +} |
| +func TestSponsoredOperatorCLIParsingAndBinding(t *testing.T) { |
| + t.Setenv("CARLOS_CONSOLE", "") |
| + cases := [][]string{{}, {"unknown"}, {"preview", "--roster", "x"}, {"status"}, {"status", "--operation", "no"}, {"preview", "--console", ""}, {"preview", "--json", "--json"}, {"preview", "extra"}, {"enroll", "--roster", "x"}, {"enroll", "--roster", "x", "--console", "x", "--note", "n"}, {"preview", "--console", "--json"}} |
| + for _, args := range cases { |
| + if _, e := parseSponsored(args); e == nil { |
| + t.Errorf("accepted%v", args) |
| + } |
| + } |
| + f := sponsoredClientFixture(t) |
| + for _, target := range []string{"https://example.test/", " https://example.test", "https://user@example.test", "https://example.test/path", "https://example.test?x=1", "https://example.test#x", "https://EXAMPLE.test"} { |
| + file := sponsoredClientRoster(t, target, f["request"]) |
| + calls := 0 |
| + var out bytes.Buffer |
| + e := runBillingSponsoredWith([]string{"enroll", "--roster", file, "--console", "https://example.test"}, func(string) (*apiSession, error) { calls++; return &apiSession{console: "https://example.test"}, nil }, &out) |
| + if e == nil || e.Error() != sponsoredCopyConsoleMismatchError || out.Len() != 0 { |
| + t.Fatal("binding", target, e) |
| + } |
| + _ = calls |
| + } |
| + var out bytes.Buffer |
| + calls := 0 |
| + if e := runBillingWith([]string{"sponsored", "--help"}, func(string) (*apiSession, error) { calls++; return nil, errors.New("test") }, &out); e != nil || calls != 0 || !strings.Contains(out.String(), sponsoredCopyUsageUsageEnroll) { |
| + t.Fatal("nested help", e) |
| + } |
| +} |
| +func TestSponsoredOperatorCLIStrictFileAndCodec(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + raw := string(f["request"]) |
| + bad := []string{"null", raw + "{}", strings.Replace(raw, `"version": 1`, `"version": 1, "version": 1`, 1), strings.Replace(raw, `"version"`, `"Version"`, 1), strings.Replace(raw, `"reason": "existing fixture"`, `"reason": "\ud800"`, 1), strings.Replace(raw, `"note": "reviewed fixture"`, `"note": "�"`, 1), strings.Replace(raw, `"items": [`, `"items": null, "unused": [`, 1), strings.Replace(raw, `"confirm_system_account": false`, `"confirm_system_account": null`, 1)} |
| + for i, b := range bad { |
| + file := sponsoredClientRoster(t, "https://example.test", []byte(b)) |
| + if _, _, _, e := sponsoredReadRoster(file); e == nil { |
| + t.Errorf("accepted%d", i) |
| + } |
| + } |
| + for _, b := range []string{`{"console_url":null,"request":` + raw + `}`, `{"console_url":"https://example.test","console_url":"https://other.test","request":` + raw + `}`, `{"console_url":"https://example.test","request":` + raw + `} {}`, "\xff"} { |
| + path := filepath.Join(t.TempDir(), "file") |
| + if e := os.WriteFile(path, []byte(b), 0600); e != nil { |
| + t.Fatal(e) |
| + } |
| + if _, _, _, e := sponsoredReadRoster(path); e == nil { |
| + t.Fatal("fileaccepted") |
| + } |
| + } |
| +} |
| +func TestSponsoredOperatorCLIUnreadableEnrollmentIsUncertain(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + var request sponsoredWireRequest |
| + if json.Unmarshal(f["request"], &request) != nil { |
| + t.Fatal("fixture") |
| + } |
| + bad := [][]byte{[]byte(`{}`), []byte(`null`), []byte(`{"version":`), bytes.Replace(f["enrollment"], []byte(`"version": 1`), []byte(`"version": 9`), 1), bytes.Replace(f["enrollment"], []byte(`"actor": "operator@example.test"`), []byte(`"actor": "other@example.test"`), 1), bytes.Replace(f["enrollment"], []byte(`"outcome": "added"`), []byte(`"outcome": "granted"`), 1), bytes.Replace(f["enrollment"], []byte(`"note": "reviewed fixture"`), []byte(`"note": "different"`), 1), bytes.Repeat([]byte("x"), sponsoredWireResponseLimit+1)} |
| + for i, b := range bad { |
| + t.Run(string(rune('a'+i)), func(t *testing.T) { |
| + calls := 0 |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { calls++; w.Write(b) }) |
| + var out bytes.Buffer |
| + e := runSponsored(context.Background(), sess, sponsoredInvocation{verb: "enroll", json: true}, f["request"], request, &out) |
| + if e == nil || (!strings.Contains(e.Error(), "may have been recorded")) || calls != 1 || out.Len() != 0 { |
| + t.Fatal("uncertain", e, calls, out.Len()) |
| + } |
| + }) |
| + } |
| +} |
| +func TestSponsoredOperatorCLIStatusCrossActorAndAdditiveResponse(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + var m map[string]json.RawMessage |
| + if json.Unmarshal(f["enrollment"], &m) != nil { |
| + t.Fatal("fixture") |
| + } |
| + delete(m, "replayed") |
| + m["future_field"] = json.RawMessage(`{"opaque":true}`) |
| + body, e := json.Marshal(m) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { w.Write(body) }) |
| + sess.address = "another@example.test" |
| + var out bytes.Buffer |
| + e = runSponsored(context.Background(), sess, sponsoredInvocation{verb: "status", operation: strings.Repeat("1", 64), json: true}, nil, sponsoredWireRequest{}, &out) |
| + if e != nil || !strings.Contains(out.String(), "future_field") { |
| + t.Fatal(e) |
| + } |
| +} |
| + |
| +type sponsoredFailWriter struct{} |
| + |
| +func (sponsoredFailWriter) Write(b []byte) (int, error) { return 1, io.ErrClosedPipe } |
| +func TestSponsoredOperatorCLIOutputFailureAndTransport(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + var req sponsoredWireRequest |
| + json.Unmarshal(f["request"], &req) |
| + calls := 0 |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { calls++; w.Write(f["enrollment"]) }) |
| + e := runSponsored(context.Background(), sess, sponsoredInvocation{verb: "enroll"}, f["request"], req, sponsoredFailWriter{}) |
| + if e == nil || e.Error() != sponsoredCopyOutputFailureError || calls != 1 { |
| + t.Fatal(e, calls) |
| + } |
| + for _, code := range []int{500, 502} { |
| + sess = sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { |
| + w.WriteHeader(code) |
| + w.Write([]byte(`{"error":"fixture"}`)) |
| + }) |
| + var out bytes.Buffer |
| + e = runSponsored(context.Background(), sess, sponsoredInvocation{verb: "enroll"}, f["request"], req, &out) |
| + if e == nil || e.Error() != sponsoredCopyReadWriteFailureWriteError || out.Len() != 0 { |
| + t.Fatal(e) |
| + } |
| + } |
| + for _, delta := range []int{-1, 0, 1} { |
| + raw, e := sponsoredReadBounded(strings.NewReader(strings.Repeat("x", sponsoredWireResponseLimit+delta)), sponsoredWireResponseLimit) |
| + if delta > 0 { |
| + if e == nil || raw != nil { |
| + t.Fatal("overflow") |
| + } |
| + } else if e != nil || len(raw) != sponsoredWireResponseLimit+delta { |
| + t.Fatal("boundary", e) |
| + } |
| + } |
| +} |
| +func TestSponsoredOperatorCLIRealConsole(t *testing.T) { |
| + tmp := t.TempDir() |
| + st, e := console.Open(filepath.Join(tmp, "console.db")) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + t.Cleanup(func() { st.Close() }) |
| + seed, pub64, e := release.GenerateKey() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + key, e := release.ParseSigningKey(seed, "test") |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + pub, e := release.ParsePublicKey(pub64, "test") |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + keymail := httptest.NewServer(http.NotFoundHandler()) |
| + t.Cleanup(keymail.Close) |
| + var srv *console.Server |
| + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv.ServeHTTP(w, r) })) |
| + t.Cleanup(ts.Close) |
| + srv, e = console.NewServer(console.Config{Origin: ts.URL, Keymail: keymail.URL, Registry: filepath.Join(tmp, "registry.db"), Releases: release.NewDirStore(filepath.Join(tmp, "objects")), ReleasePub: pub, Key: key, AppScheme: "http"}, st) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + addr := "operator@example.test" |
| + if e := st.AddMember(console.OpsAccountID, addr, console.RoleOwner, time.Now()); e != nil { |
| + t.Fatal(e) |
| + } |
| + token, _ := e2eDeviceLogin(t, ts.URL, st, addr, "test") |
| + if e := st.SetOperator(addr, true); e != nil { |
| + t.Fatal(e) |
| + } |
| + sess := &apiSession{console: ts.URL, address: addr, c: newConsoleClient(ts.URL, token)} |
| + session := func(string) (*apiSession, error) { return sess, nil } |
| + var previewOut bytes.Buffer |
| + if e := runBillingWith([]string{"sponsored", "preview", "--json"}, session, &previewOut); e != nil { |
| + t.Fatal(e) |
| + } |
| + f := sponsoredClientFixture(t) |
| + var req sponsoredWireRequest |
| + json.Unmarshal(f["request"], &req) |
| + req.Items[0].AccountID = 1 |
| + req.Items[0].Sqid = "bab" |
| + req.Items[0].ConfirmSystemAccount = true |
| + request, _ := json.Marshal(req) |
| + file := sponsoredClientRoster(t, ts.URL, request) |
| + var first json.RawMessage |
| + for i := 0; i < 2; i++ { |
| + var out bytes.Buffer |
| + if e := runBillingWith([]string{"sponsored", "enroll", "--console", ts.URL, "--roster", file, "--json"}, session, &out); e != nil { |
| + t.Fatal(e) |
| + } |
| + var wrapper struct { |
| + Result sponsoredWireResult `json:"result"` |
| + } |
| + if json.Unmarshal(out.Bytes(), &wrapper) != nil || wrapper.Result.Replayed == nil || *wrapper.Result.Replayed != (i == 1) { |
| + t.Fatal("ack") |
| + } |
| + b, _ := json.Marshal(wrapper.Result.Historical) |
| + if i == 0 { |
| + first = b |
| + } else if !bytes.Equal(first, b) { |
| + t.Fatal("changed replay") |
| + } |
| + } |
| + conflict := req |
| + conflict.Note = "changed fixture" |
| + conflictRaw, _ := json.Marshal(conflict) |
| + conflictFile := sponsoredClientRoster(t, ts.URL, conflictRaw) |
| + var rejected bytes.Buffer |
| + if e := runBillingWith([]string{"sponsored", "enroll", "--console", ts.URL, "--roster", conflictFile}, session, &rejected); e == nil || e.Error() != sponsoredCopyConflictError || rejected.Len() != 0 { |
| + t.Fatal("real conflict", e) |
| + } |
| + if e := runBillingWith([]string{"sponsored", "status", "--operation", strings.Repeat("f", 64)}, session, &rejected); e == nil || e.Error() != sponsoredCopyNotFoundError || rejected.Len() != 0 { |
| + t.Fatal("real missing status", e) |
| + } |
| + var status bytes.Buffer |
| + if e := runBillingWith([]string{"sponsored", "status", "--operation", req.OperationID, "--json"}, session, &status); e != nil { |
| + t.Fatal(e) |
| + } |
| +} |
| +func TestSponsoredOperatorCLIDocsAndFixture(t *testing.T) { |
| + raw, e := os.ReadFile("docs/site/cli.md") |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + for _, s := range []string{sponsoredCopyUsageDocsHeading, sponsoredCopyUsageUsagePreview, sponsoredCopyUsageUsageEnroll, sponsoredCopyUsageUsageStatus, sponsoredCopyRosterFormatDocsFile, sponsoredCopyPersonalDataWarning, sponsoredCopyRetryWarning} { |
| + if !bytes.Contains(raw, []byte(s)) { |
| + t.Fatal("docs missing", s) |
| + } |
| + } |
| + fixture := sponsoredClientFixture(t) |
| + var req sponsoredWireRequest |
| + json.Unmarshal(fixture["request"], &req) |
| + if req.validate() != nil { |
| + t.Fatal("fixture") |
| + } |
| + var preview sponsoredWirePreview |
| + json.Unmarshal(fixture["preview"], &preview) |
| + if sponsoredValidatePreview(preview) != nil { |
| + t.Fatal("previewfixture") |
| + } |
| + var result sponsoredWireResult |
| + json.Unmarshal(fixture["enrollment"], &result) |
| + if sponsoredValidateResult(result, req.OperationID, &req, result.Historical.Actor) != nil { |
| + t.Fatal("receiptfixture") |
| + } |
| + if !reflect.DeepEqual(req, result.Historical.Request) { |
| + t.Fatal("wire drift") |
| + } |
| +} |
| + |
| +func TestSponsoredOperatorCLILostResponseNoRepost(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + var req sponsoredWireRequest |
| + json.Unmarshal(f["request"], &req) |
| + var calls atomic.Int32 |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { |
| + calls.Add(1) |
| + io.Copy(io.Discard, r.Body) |
| + conn, _, e := w.(http.Hijacker).Hijack() |
| + if e != nil { |
| + t.Error(e) |
| + return |
| + } |
| + conn.Close() |
| + }) |
| + var out bytes.Buffer |
| + e := runSponsored(context.Background(), sess, sponsoredInvocation{verb: "enroll"}, f["request"], req, &out) |
| + if e == nil || e.Error() != sponsoredCopyReadWriteFailureWriteError || calls.Load() != 1 || out.Len() != 0 { |
| + t.Fatal("lost response", e, calls.Load()) |
| + } |
| +} |
| +func TestSponsoredOperatorCLIResponseShapes(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + for _, body := range [][]byte{[]byte(`{}`), []byte(`null`), append(append([]byte{}, f["preview"]...), []byte(`{}`)...), bytes.Replace(f["preview"], []byte(`"owners": []`), []byte(`"owners": [null]`), 1), bytes.Replace(f["preview"], []byte(`"rows": [`), []byte(`"rows": null, "unknown": [`), 1), bytes.Replace(f["preview"], []byte(`"system_account": false`), []byte(`"system_account": null`), 1)} { |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { w.Write(body) }) |
| + var out bytes.Buffer |
| + if e := runSponsored(context.Background(), sess, sponsoredInvocation{verb: "preview"}, nil, sponsoredWireRequest{}, &out); e == nil || out.Len() != 0 { |
| + t.Fatal("bad preview accepted") |
| + } |
| + } |
| +} |
| + |
| +func TestSponsoredOperatorCLIRefusalCopyAllowlist(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + var req sponsoredWireRequest |
| + json.Unmarshal(f["request"], &req) |
| + for _, verb := range []string{"preview", "enroll"} { |
| + for _, status := range []int{400, 403, 404, 409, 413, 415, 422} { |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { |
| + w.WriteHeader(status) |
| + w.Write([]byte(`{"error":"private-owner@example.test roster note fixture"}`)) |
| + }) |
| + var out bytes.Buffer |
| + e := runSponsored(context.Background(), sess, sponsoredInvocation{verb: verb}, f["request"], req, &out) |
| + want := sponsoredCopyReadWriteFailureReadError |
| + if verb == "enroll" { |
| + want = sponsoredCopyReadWriteFailureWriteError |
| + } |
| + if e == nil || e.Error() != want || out.Len() != 0 { |
| + t.Errorf("%s/%d reflected unapproved error: %v", verb, status, e) |
| + } |
| + } |
| + } |
| +} |
| + |
| +func TestSponsoredOperatorCLIConsoleMoved(t *testing.T) { |
| + old := quietConsoleOffers |
| + quietConsoleOffers = true |
| + t.Cleanup(func() { quietConsoleOffers = old }) |
| + for _, verb := range []string{"preview", "enroll"} { |
| + t.Run(verb, func(t *testing.T) { |
| + var first, second atomic.Int32 |
| + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { second.Add(1); w.WriteHeader(500) })) |
| + defer target.Close() |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { |
| + first.Add(1) |
| + w.Header().Set("Location", target.URL+r.URL.Path) |
| + w.WriteHeader(308) |
| + }) |
| + f := sponsoredClientFixture(t) |
| + var req sponsoredWireRequest |
| + json.Unmarshal(f["request"], &req) |
| + var out bytes.Buffer |
| + err := runSponsored(context.Background(), sess, sponsoredInvocation{verb: verb}, f["request"], req, &out) |
| + var moved *consoleMovedError |
| + if !errors.As(err, &moved) || first.Load() != 1 || second.Load() != 0 || out.Len() != 0 { |
| + t.Fatal("move boundary", err, first.Load(), second.Load(), out.String()) |
| + } |
| + }) |
| + } |
| +} |
| +func TestSponsoredOperatorCLIPositiveRefusals(t *testing.T) { |
| + for _, tc := range []struct { |
| + status int |
| + verb, message string |
| + }{ |
| + {400, "enroll", sponsoredCopyInvalidRequestError}, |
| + {401, "preview", ""}, {401, "enroll", ""}, {401, "status", ""}, |
| + {404, "status", sponsoredCopyNotFoundError}, |
| + {409, "enroll", sponsoredCopyConflictError}, |
| + {413, "enroll", "request body too large"}, |
| + {415, "enroll", "send a JSON body with Content-Type: application/json"}, |
| + {422, "preview", sponsoredCopyPreviewLimitError}, |
| + {422, "enroll", sponsoredCopyResultLimitError}, |
| + {404, "preview", "404 page not found\n"}, {404, "enroll", "404 page not found\n"}, {404, "status", "404 page not found\n"}, |
| + } { |
| + t.Run(fmt.Sprintf("%s/%d", tc.verb, tc.status), func(t *testing.T) { |
| + message := tc.message |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { |
| + w.WriteHeader(tc.status) |
| + if strings.HasSuffix(message, "\n") { |
| + io.WriteString(w, message) |
| + } else { |
| + json.NewEncoder(w).Encode(map[string]string{"error": message}) |
| + } |
| + }) |
| + if tc.status == 401 { |
| + message = "not signed in — run: carlos auth login --console " + sess.console |
| + } |
| + want := message |
| + if strings.HasSuffix(message, "\n") { |
| + want = sponsoredCopyReadWriteFailureReadError |
| + } |
| + var out bytes.Buffer |
| + err := runSponsored(context.Background(), sess, sponsoredInvocation{verb: tc.verb, operation: strings.Repeat("1", 64)}, nil, sponsoredWireRequest{}, &out) |
| + if err == nil || err.Error() != want || out.Len() != 0 { |
| + t.Fatal("safe refusal", err, want, out.String()) |
| + } |
| + }) |
| + } |
| +} |
| +func TestSponsoredOperatorCLIHumanRowsASCII(t *testing.T) { |
| + f := sponsoredClientFixture(t) |
| + name := "name\x7f\u0085\u009f\u202e\u2066é😀" |
| + for _, verb := range []string{"preview", "status"} { |
| + var raw []byte |
| + if verb == "preview" { |
| + var v sponsoredWirePreview |
| + json.Unmarshal(f["preview"], &v) |
| + v.Preview.Rows[0].Facts.Name = name |
| + payload, _ := json.Marshal(v.Preview) |
| + h := sha256.New() |
| + h.Write([]byte("carlos-sponsored-enrollment-preview-v1\x00")) |
| + var size [8]byte |
| + binary.BigEndian.PutUint64(size[:], uint64(len(payload))) |
| + h.Write(size[:]) |
| + h.Write(payload) |
| + v.PreviewDigest = fmt.Sprintf("%x", h.Sum(nil)) |
| + raw, _ = json.Marshal(v) |
| + } else { |
| + var v sponsoredWireResult |
| + json.Unmarshal(f["enrollment"], &v) |
| + v.Historical.Rows[0].Facts.Name = name |
| + v.Replayed = nil |
| + raw, _ = json.Marshal(v) |
| + } |
| + sess := sponsoredClientSession(t, func(w http.ResponseWriter, r *http.Request) { w.Write(raw) }) |
| + var req sponsoredWireRequest |
| + json.Unmarshal(f["request"], &req) |
| + for _, asJSON := range []bool{false, true} { |
| + var out bytes.Buffer |
| + err := runSponsored(context.Background(), sess, sponsoredInvocation{verb: verb, operation: req.OperationID, json: asJSON}, nil, req, &out) |
| + if err != nil { |
| + t.Fatal(verb, err) |
| + } |
| + if asJSON { |
| + if !bytes.Contains(out.Bytes(), []byte(name)) { |
| + t.Fatal("json changed") |
| + } |
| + continue |
| + } |
| + for _, b := range out.Bytes() { |
| + if b >= 127 { |
| + t.Fatalf("raw terminal byte %x in %s", b, verb) |
| + } |
| + } |
| + for _, escaped := range []string{`\u007f`, `\u0085`, `\u009f`, `\u202e`, `\u2066`, `\u00e9`, `\ud83d\ude00`} { |
| + if !strings.Contains(out.String(), escaped) { |
| + t.Fatal("missing escape", escaped) |
| + } |
| + } |
| + label := sponsoredCopyPreviewScopeFieldAccount |
| + if verb == "status" { |
| + label = sponsoredCopyOutcomeSummaryFieldOutcome |
| + } |
| + for _, line := range strings.Split(out.String(), "\n") { |
| + if strings.HasPrefix(line, label+": ") { |
| + var row map[string]json.RawMessage |
| + if json.Unmarshal([]byte(strings.TrimPrefix(line, label+": ")), &row) != nil { |
| + t.Fatal("invalid escaped JSON") |
| + } |
| + var facts struct { |
| + Name string `json:"name"` |
| + } |
| + json.Unmarshal(row["facts"], &facts) |
| + if facts.Name != name { |
| + t.Fatal("surrogate roundtrip", facts.Name) |
| + } |
| + } |
| + } |
| + } |
| + } |
| +} |
diff --git a/docs/site/cli.md b/docs/site/cli.md| index d59e6df6a..67013c679 100644 |
| --- a/docs/site/cli.md |
| +++ b/docs/site/cli.md |
| @@ -1294,6 +1294,48 @@ Operator only. Any other signed-in address gets the same bare 404 the console |
| gives an unknown route; with no credential, or an expired one, you are told |
| to log in. |
| +#### `carlos billing sponsored` |
| + |
| +Use `preview` to read an account snapshot, `enroll` to submit a saved review, and `status` to read the historical result. Enrollment needs an explicit console from `--console` or `CARLOS_CONSOLE`. The CLI does not create operation IDs, fill choices, rewrite the roster, or choose a target. |
| + |
| +`carlos billing sponsored preview [--console URL] [--json]` |
| + |
| +The preview is a complete bounded snapshot of the accounts known to the selected console at one observation time. It includes current accounts and tombstones for accounts that are no longer active. It is not a historical census and does not prove which accounts existed on the instruction date. |
| + |
| +The preview can contain more accounts than you intend to enroll. Review every item and record one choice for each submitted item. |
| + |
| +Account 1, `bab`, is marked as a system account. To enroll it, use `choice: enroll` and `confirm_system_account: true`. For every other choice and account, `confirm_system_account` must be `false`. The preview never chooses `enroll`, `exclude`, or `needs_review` for you. |
| + |
| +Preview output contains owner email addresses and account facts. You control where command output and manually saved files are shared. The CLI does not automatically save, cache, or upload this data. Diagnostics do not include raw rosters, owner lists, notes, tokens, database details, or corrupt receipt bodies. |
| + |
| +`carlos billing sponsored enroll --roster FILE [--console URL] [--json]` |
| + |
| +##### Roster file |
| + |
| +The roster file contains `console_url` and `request`. Its `console_url` must be the normalized console URL exactly as selected: no user information, path, query, fragment, trailing slash, or surrounding whitespace. The `request` uses version `1` and requires a 64-character lowercase hexadecimal `operation_id`, a 64-character lowercase hexadecimal `preview_digest`, the exact `decision_reference` `carloku-existing-2026-10-07`, `coverage_acknowledged: true`, a `note`, and an ordered `items` array with 1 to 1000 entries. |
| + |
| +Items must be ordered by strictly increasing `account_id`. Each item requires a matching `sqid`, one `choice` from `enroll`, `exclude`, or `needs_review`, a `reason`, and `confirm_system_account`. Account 1 requires explicit confirmation only when its choice is `enroll`. |
| + |
| +Choose a unique operation ID for the file. It is an idempotency key, which lets the same request be recognized again. It is not a secret or cryptographic proof. |
| + |
| +The CLI reads the supplied file once and sends the accepted request bytes. It does not sort, deduplicate, trim, repair, rewrite, or fill missing values. |
| + |
| +`coverage_acknowledged: true` records that the operator understands the preview's evidence limits. It is not proof that every historical account was found. It does not select all accounts automatically. |
| + |
| +Enrollment is addition-only. `exclude` and `needs_review` record the reviewed outcome; they do not revoke an existing enrollment. |
| + |
| +Enrollment does not apply grants or change billing. It does not clear holds, set an expiry, convert accounts to paid use, or establish current sponsorship authority. |
| + |
| +`carlos billing sponsored status --operation 64hex [--console URL] [--json]` |
| + |
| +Enrollment and status output is historical evidence. It identifies the selected console, original operation, original operator, time, request digest, receipt digest, and recorded outcomes. A digest is a calculated reference to submitted or returned data. |
| + |
| +The output does not claim that an account has a current grant or valid current sponsorship. Status is read-only, and another current operator may read an earlier operator's historical receipt. |
| + |
| +The result lists every submitted item. Outcomes distinguish newly added and already recorded accounts from operator exclusions, items marked for review, missing accounts, and retired accounts. An operation can add zero memberships. |
| + |
| +The enrollment may have been recorded. Check `carlos billing sponsored status --operation 64hex` on the same console, or retry exactly with the same saved file, operation ID, selected console, and authenticated operator. Do not repost automatically, create a new operation, rewrite the file, or change consoles. A 404 from another, moved, or restored console says nothing about the original attempt. |
| + |
| ### carlos status |
| `--live` prints what every box in the fleet last reported about itself: |
diff --git a/internal/console/server.go b/internal/console/server.go| index 9c5d19e0b..659c31886 100644 |
| --- a/internal/console/server.go |
| +++ b/internal/console/server.go |
| @@ -1156,6 +1156,9 @@ func NewServer(cfg Config, st *Store) (*Server, error) { |
| s.mux.HandleFunc("GET /api/billing/rejected", s.handleAPIBillingRejected) |
| s.mux.HandleFunc("GET /api/billing/house", s.handleAPIBillingHouseList) |
| s.mux.HandleFunc("POST /api/billing/house", s.handleAPIBillingHouseSet) |
| + s.mux.HandleFunc("GET /api/billing/sponsored/preview", s.handleAPIBillingSponsoredPreview) |
| + s.mux.HandleFunc("POST /api/billing/sponsored/enrollments", s.handleAPIBillingSponsoredEnroll) |
| + s.mux.HandleFunc("GET /api/billing/sponsored/enrollments/{operation}", s.handleAPIBillingSponsoredStatus) |
| s.mux.HandleFunc("POST /a/{sqid}/errors/level", s.handleAccountErrorLevel) |
| s.mux.HandleFunc("POST /a/{sqid}/analytics/setting", s.handleAccountAnalyticsSetting) |
| // Go 1.22's mux prefers this literal over apps/{app} below — exactly why |
diff --git a/internal/console/sponsoredoperator.go b/internal/console/sponsoredoperator.go| new file mode 100644 |
| index 000000000..cfbe0efa2 |
| --- /dev/null |
| +++ b/internal/console/sponsoredoperator.go |
| @@ -0,0 +1,749 @@ |
| +package console |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "database/sql" |
| + "encoding/json" |
| + "errors" |
| + "github.com/carlosframework/platform/internal/domains" |
| + "io" |
| + "log" |
| + "net/http" |
| + "strings" |
| + "time" |
| + "unicode/utf8" |
| +) |
| + |
| +const ( |
| + sponsoredCopyUsageHelpHeading = "Sponsored account enrollment" |
| + sponsoredCopyUsageDocsHeading = "`carlos billing sponsored`" |
| + sponsoredCopyUsageHelpSummary = "Use `preview` to read an account snapshot, `enroll` to submit a saved review, and `status` to read the historical result. Enrollment needs an explicit console from `--console` or `CARLOS_CONSOLE`. The CLI does not create operation IDs, fill choices, rewrite the roster, or choose a target." |
| + sponsoredCopyUsageUsagePreview = "`carlos billing sponsored preview [--console URL] [--json]`" |
| + sponsoredCopyUsageUsageEnroll = "`carlos billing sponsored enroll --roster FILE [--console URL] [--json]`" |
| + sponsoredCopyUsageUsageStatus = "`carlos billing sponsored status --operation 64hex [--console URL] [--json]`" |
| + sponsoredCopyUsageArgumentError = "Invalid sponsored billing arguments." |
| + sponsoredCopyUsageWriteConsoleError = "Enrollment requires an explicit console from `--console` or `CARLOS_CONSOLE`." |
| + sponsoredCopyPreviewScopeOutputHeader = "Complete snapshot for the selected console" |
| + sponsoredCopyPreviewScopeFieldConsole = "Selected console" |
| + sponsoredCopyPreviewScopeFieldObservedAt = "Observed" |
| + sponsoredCopyPreviewScopeFieldCount = "Accounts" |
| + sponsoredCopyPreviewScopeFieldAccount = "Account" |
| + sponsoredCopyPreviewScopeDocs = "The preview is a complete bounded snapshot of the accounts known to the selected console at one observation time. It includes current accounts and tombstones for accounts that are no longer active. It is not a historical census and does not prove which accounts existed on the instruction date.\n\nThe preview can contain more accounts than you intend to enroll. Review every item and record one choice for each submitted item." |
| + sponsoredCopyPreviewSystemOutputLabel = "System account" |
| + sponsoredCopyPreviewSystemDocs = "Account 1, `bab`, is marked as a system account. To enroll it, use `choice: enroll` and `confirm_system_account: true`. For every other choice and account, `confirm_system_account` must be `false`. The preview never chooses `enroll`, `exclude`, or `needs_review` for you." |
| + sponsoredCopyPreviewLimitError = "The complete preview is too large to return. No partial roster was produced." |
| + sponsoredCopyRosterFormatDocsHeading = "Roster file" |
| + sponsoredCopyRosterFormatDocsFile = "The roster file contains `console_url` and `request`. Its `console_url` must be the normalized console URL exactly as selected: no user information, path, query, fragment, trailing slash, or surrounding whitespace. The `request` uses version `1` and requires a 64-character lowercase hexadecimal `operation_id`, a 64-character lowercase hexadecimal `preview_digest`, the exact `decision_reference` `carloku-existing-2026-10-07`, `coverage_acknowledged: true`, a `note`, and an ordered `items` array with 1 to 1000 entries." |
| + sponsoredCopyRosterFormatDocsItems = "Items must be ordered by strictly increasing `account_id`. Each item requires a matching `sqid`, one `choice` from `enroll`, `exclude`, or `needs_review`, a `reason`, and `confirm_system_account`. Account 1 requires explicit confirmation only when its choice is `enroll`." |
| + sponsoredCopyRosterFormatDocsOperation = "Choose a unique operation ID for the file. It is an idempotency key, which lets the same request be recognized again. It is not a secret or cryptographic proof." |
| + sponsoredCopyRosterFormatDocsSend = "The CLI reads the supplied file once and sends the accepted request bytes. It does not sort, deduplicate, trim, repair, rewrite, or fill missing values." |
| + sponsoredCopyCoverageAckDocs = "`coverage_acknowledged: true` records that the operator understands the preview's evidence limits. It is not proof that every historical account was found. It does not select all accounts automatically." |
| + sponsoredCopyConsoleMismatchError = "The roster `console_url` must exactly match the selected console." |
| + sponsoredCopyInvalidRequestError = "The request is invalid." |
| + sponsoredCopyBodyLimitError = "The roster file is too large." |
| + sponsoredCopyResultLimitError = "The enrollment result is too large. No enrollment was recorded." |
| + sponsoredCopyConflictError = "This operation ID is already bound to another request or operator." |
| + sponsoredCopyNotFoundError = "No sponsored enrollment operation was found on this console." |
| + sponsoredCopyReadWriteFailureReadError = "The sponsored billing result could not be read." |
| + sponsoredCopyReadWriteFailureWriteError = "The enrollment may have been recorded. Check historical status, or retry with the same saved file, operation ID, selected console, and authenticated operator." |
| + sponsoredCopyHistoricalResultOutputHeading = "Historical enrollment result" |
| + sponsoredCopyHistoricalResultFieldOperation = "Operation" |
| + sponsoredCopyHistoricalResultFieldActor = "Operator" |
| + sponsoredCopyHistoricalResultFieldRecordedAt = "Recorded" |
| + sponsoredCopyHistoricalResultFieldRequestDigest = "Request digest" |
| + sponsoredCopyHistoricalResultFieldReceiptDigest = "Receipt digest" |
| + sponsoredCopyHistoricalResultFieldReplayed = "Replayed" |
| + sponsoredCopyHistoricalResultDocs = "Enrollment and status output is historical evidence. It identifies the selected console, original operation, original operator, time, request digest, receipt digest, and recorded outcomes. A digest is a calculated reference to submitted or returned data.\n\nThe output does not claim that an account has a current grant or valid current sponsorship. Status is read-only, and another current operator may read an earlier operator's historical receipt." |
| + sponsoredCopyOutcomeSummaryOutputHeading = "Submitted item outcomes" |
| + sponsoredCopyOutcomeSummaryFieldOutcome = "Outcome" |
| + sponsoredCopyOutcomeSummaryDocs = "The result lists every submitted item. Outcomes distinguish newly added and already recorded accounts from operator exclusions, items marked for review, missing accounts, and retired accounts. An operation can add zero memberships." |
| + sponsoredCopyRetryWarning = "The enrollment may have been recorded. Check `carlos billing sponsored status --operation 64hex` on the same console, or retry exactly with the same saved file, operation ID, selected console, and authenticated operator. Do not repost automatically, create a new operation, rewrite the file, or change consoles. A 404 from another, moved, or restored console says nothing about the original attempt." |
| + sponsoredCopyOutputFailureError = "The enrollment result was received, but the complete output could not be written. Do not retry the enrollment. Use `status` to recover the historical evidence." |
| + sponsoredCopyOutputFailureReadError = "The sponsored billing result was received, but the complete output could not be written. Run the read command again." |
| + sponsoredCopyProtocolErrorEnrollError = "The enrollment response was invalid or incomplete. The enrollment may have been recorded. Check historical status or retry exactly with the same saved file, operation ID, selected console, and authenticated operator." |
| + sponsoredCopyProtocolErrorReadError = "The sponsored billing response was invalid or incomplete." |
| + sponsoredCopyAdditionOnlyDocs = "Enrollment is addition-only. `exclude` and `needs_review` record the reviewed outcome; they do not revoke an existing enrollment.\n\nEnrollment does not apply grants or change billing. It does not clear holds, set an expiry, convert accounts to paid use, or establish current sponsorship authority." |
| + sponsoredCopyPersonalDataWarning = "Preview output contains owner email addresses and account facts. You control where command output and manually saved files are shared. The CLI does not automatically save, cache, or upload this data. Diagnostics do not include raw rosters, owner lists, notes, tokens, database details, or corrupt receipt bodies." |
| +) |
| + |
| +const sponsoredWireRequestLimit = 4 << 20 |
| +const sponsoredWireResponseLimit = 8 << 20 |
| + |
| +var errSponsoredWire = errors.New(sponsoredCopyInvalidRequestError) |
| + |
| +type sponsoredWireRequest struct { |
| + Version int `json:"version"` |
| + OperationID string `json:"operation_id"` |
| + PreviewDigest string `json:"preview_digest"` |
| + DecisionReference string `json:"decision_reference"` |
| + CoverageAcknowledged bool `json:"coverage_acknowledged"` |
| + Note string `json:"note"` |
| + Items []sponsoredWireItem `json:"items"` |
| +} |
| + |
| +func (out *sponsoredWireRequest) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireRequest |
| + if err := sponsoredWireObject(raw, true, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "operation_id": {target: &v.OperationID, optional: false, nullable: false}, |
| + "preview_digest": {target: &v.PreviewDigest, optional: false, nullable: false}, |
| + "decision_reference": {target: &v.DecisionReference, optional: false, nullable: false}, |
| + "coverage_acknowledged": {target: &v.CoverageAcknowledged, optional: false, nullable: false}, |
| + "note": {target: &v.Note, optional: false, nullable: false}, |
| + "items": {target: &v.Items, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireItem struct { |
| + AccountID int64 `json:"account_id"` |
| + Sqid string `json:"sqid"` |
| + Choice string `json:"choice"` |
| + Reason string `json:"reason"` |
| + ConfirmSystemAccount bool `json:"confirm_system_account"` |
| +} |
| + |
| +func (out *sponsoredWireItem) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireItem |
| + if err := sponsoredWireObject(raw, true, map[string]sponsoredWireField{ |
| + "account_id": {target: &v.AccountID, optional: false, nullable: false}, |
| + "sqid": {target: &v.Sqid, optional: false, nullable: false}, |
| + "choice": {target: &v.Choice, optional: false, nullable: false}, |
| + "reason": {target: &v.Reason, optional: false, nullable: false}, |
| + "confirm_system_account": {target: &v.ConfirmSystemAccount, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireBilling struct { |
| + Present bool `json:"present"` |
| + House bool `json:"house"` |
| + LiveMode bool `json:"live_mode"` |
| + CustomerPresent bool `json:"customer_present"` |
| + Subscription string `json:"subscription"` |
| + Status string `json:"status"` |
| +} |
| + |
| +func (out *sponsoredWireBilling) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireBilling |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "present": {target: &v.Present, optional: false, nullable: false}, |
| + "house": {target: &v.House, optional: false, nullable: false}, |
| + "live_mode": {target: &v.LiveMode, optional: false, nullable: false}, |
| + "customer_present": {target: &v.CustomerPresent, optional: false, nullable: false}, |
| + "subscription": {target: &v.Subscription, optional: false, nullable: false}, |
| + "status": {target: &v.Status, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireOutbox struct { |
| + Present bool `json:"present"` |
| + LiveMode bool `json:"live_mode"` |
| + SerialOwed bool `json:"serial_owed"` |
| + Revision int64 `json:"revision"` |
| + PublishedRevision int64 `json:"published_revision"` |
| + Digest string `json:"digest"` |
| + State string `json:"state"` |
| +} |
| + |
| +func (out *sponsoredWireOutbox) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireOutbox |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "present": {target: &v.Present, optional: false, nullable: false}, |
| + "live_mode": {target: &v.LiveMode, optional: false, nullable: false}, |
| + "serial_owed": {target: &v.SerialOwed, optional: false, nullable: false}, |
| + "revision": {target: &v.Revision, optional: false, nullable: false}, |
| + "published_revision": {target: &v.PublishedRevision, optional: false, nullable: false}, |
| + "digest": {target: &v.Digest, optional: false, nullable: false}, |
| + "state": {target: &v.State, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireFacts struct { |
| + AccountID int64 `json:"account_id"` |
| + Sqid string `json:"sqid"` |
| + Name string `json:"name"` |
| + StoredCreatedAt int64 `json:"stored_created_at"` |
| + DeletedAt int64 `json:"deleted_at"` |
| + Owners []string `json:"owners"` |
| + Billing sponsoredWireBilling `json:"billing"` |
| + CheckoutPresent bool `json:"checkout_present"` |
| + BillingRetirement bool `json:"billing_retirement"` |
| + Outbox sponsoredWireOutbox `json:"outbox"` |
| + OpenBillingReviews int64 `json:"open_billing_reviews"` |
| + EntitlementReviews int64 `json:"entitlement_reviews"` |
| +} |
| + |
| +func (out *sponsoredWireFacts) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireFacts |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "account_id": {target: &v.AccountID, optional: false, nullable: false}, |
| + "sqid": {target: &v.Sqid, optional: false, nullable: false}, |
| + "name": {target: &v.Name, optional: false, nullable: false}, |
| + "stored_created_at": {target: &v.StoredCreatedAt, optional: false, nullable: false}, |
| + "deleted_at": {target: &v.DeletedAt, optional: false, nullable: false}, |
| + "owners": {target: &v.Owners, optional: false, nullable: false}, |
| + "billing": {target: &v.Billing, optional: false, nullable: false}, |
| + "checkout_present": {target: &v.CheckoutPresent, optional: false, nullable: false}, |
| + "billing_retirement": {target: &v.BillingRetirement, optional: false, nullable: false}, |
| + "outbox": {target: &v.Outbox, optional: false, nullable: false}, |
| + "open_billing_reviews": {target: &v.OpenBillingReviews, optional: false, nullable: false}, |
| + "entitlement_reviews": {target: &v.EntitlementReviews, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWirePreviewRow struct { |
| + Facts sponsoredWireFacts `json:"facts"` |
| + SystemAccount bool `json:"system_account"` |
| + Retired bool `json:"retired"` |
| +} |
| + |
| +func (out *sponsoredWirePreviewRow) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWirePreviewRow |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "facts": {target: &v.Facts, optional: false, nullable: false}, |
| + "system_account": {target: &v.SystemAccount, optional: false, nullable: false}, |
| + "retired": {target: &v.Retired, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWirePreviewPayload struct { |
| + Version int `json:"version"` |
| + Coverage string `json:"coverage"` |
| + Count int `json:"count"` |
| + Rows []sponsoredWirePreviewRow `json:"rows"` |
| +} |
| + |
| +func (out *sponsoredWirePreviewPayload) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWirePreviewPayload |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "coverage": {target: &v.Coverage, optional: false, nullable: false}, |
| + "count": {target: &v.Count, optional: false, nullable: false}, |
| + "rows": {target: &v.Rows, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWirePreview struct { |
| + Version int `json:"version"` |
| + Preview sponsoredWirePreviewPayload `json:"preview"` |
| + PreviewDigest string `json:"preview_digest"` |
| + ObservedAt int64 `json:"observed_at"` |
| +} |
| + |
| +func (out *sponsoredWirePreview) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWirePreview |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "preview": {target: &v.Preview, optional: false, nullable: false}, |
| + "preview_digest": {target: &v.PreviewDigest, optional: false, nullable: false}, |
| + "observed_at": {target: &v.ObservedAt, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireObservation struct { |
| + AccountID int64 `json:"account_id"` |
| + Sqid string `json:"sqid"` |
| + Outcome string `json:"outcome"` |
| + Facts *sponsoredWireFacts `json:"facts"` |
| + FirstEnrollmentOperationID string `json:"first_enrollment_operation_id"` |
| +} |
| + |
| +func (out *sponsoredWireObservation) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireObservation |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "account_id": {target: &v.AccountID, optional: false, nullable: false}, |
| + "sqid": {target: &v.Sqid, optional: false, nullable: false}, |
| + "outcome": {target: &v.Outcome, optional: false, nullable: false}, |
| + "facts": {target: &v.Facts, optional: false, nullable: true}, |
| + "first_enrollment_operation_id": {target: &v.FirstEnrollmentOperationID, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireHistorical struct { |
| + Version int `json:"version"` |
| + Actor string `json:"actor"` |
| + Request sponsoredWireRequest `json:"request"` |
| + RecordedAt int64 `json:"recorded_at"` |
| + Rows []sponsoredWireObservation `json:"rows"` |
| +} |
| + |
| +func (out *sponsoredWireHistorical) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireHistorical |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "actor": {target: &v.Actor, optional: false, nullable: false}, |
| + "request": {target: &v.Request, optional: false, nullable: false}, |
| + "recorded_at": {target: &v.RecordedAt, optional: false, nullable: false}, |
| + "rows": {target: &v.Rows, optional: false, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireResult struct { |
| + Version int `json:"version"` |
| + HistoricalOnly bool `json:"historical_only"` |
| + RequestDigest string `json:"request_digest"` |
| + ReceiptDigest string `json:"receipt_digest"` |
| + Historical sponsoredWireHistorical `json:"historical"` |
| + Replayed *bool `json:"replayed,omitempty"` |
| +} |
| + |
| +func (out *sponsoredWireResult) UnmarshalJSON(raw []byte) error { |
| + var v sponsoredWireResult |
| + if err := sponsoredWireObject(raw, false, map[string]sponsoredWireField{ |
| + "version": {target: &v.Version, optional: false, nullable: false}, |
| + "historical_only": {target: &v.HistoricalOnly, optional: false, nullable: false}, |
| + "request_digest": {target: &v.RequestDigest, optional: false, nullable: false}, |
| + "receipt_digest": {target: &v.ReceiptDigest, optional: false, nullable: false}, |
| + "historical": {target: &v.Historical, optional: false, nullable: false}, |
| + "replayed": {target: &v.Replayed, optional: true, nullable: false}, |
| + }); err != nil { |
| + return err |
| + } |
| + *out = v |
| + return nil |
| +} |
| + |
| +type sponsoredWireField struct { |
| + target any |
| + optional, nullable bool |
| +} |
| + |
| +func sponsoredWireObject(raw []byte, strict bool, fields map[string]sponsoredWireField) error { |
| + if !utf8.Valid(raw) { |
| + return errSponsoredWire |
| + } |
| + dec := json.NewDecoder(bytes.NewReader(raw)) |
| + token, err := dec.Token() |
| + if err != nil || token != json.Delim('{') { |
| + return errSponsoredWire |
| + } |
| + seen := map[string]bool{} |
| + for dec.More() { |
| + k, err := dec.Token() |
| + if err != nil { |
| + return errSponsoredWire |
| + } |
| + key, ok := k.(string) |
| + if !ok || seen[key] { |
| + return errSponsoredWire |
| + } |
| + seen[key] = true |
| + var value json.RawMessage |
| + if dec.Decode(&value) != nil { |
| + return errSponsoredWire |
| + } |
| + field, known := fields[key] |
| + if !known { |
| + if strict { |
| + return errSponsoredWire |
| + } |
| + for name := range fields { |
| + if strings.EqualFold(key, name) { |
| + return errSponsoredWire |
| + } |
| + } |
| + continue |
| + } |
| + if bytes.Equal(bytes.TrimSpace(value), []byte("null")) { |
| + if !field.nullable { |
| + return errSponsoredWire |
| + } |
| + continue |
| + } |
| + if _, ok := field.target.(*[]string); ok { |
| + var values []json.RawMessage |
| + if json.Unmarshal(value, &values) != nil { |
| + return errSponsoredWire |
| + } |
| + for _, v := range values { |
| + if len(bytes.TrimSpace(v)) == 0 || bytes.TrimSpace(v)[0] != '"' { |
| + return errSponsoredWire |
| + } |
| + } |
| + } |
| + if json.Unmarshal(value, field.target) != nil { |
| + return errSponsoredWire |
| + } |
| + } |
| + if tok, err := dec.Token(); err != nil || tok != json.Delim('}') { |
| + return errSponsoredWire |
| + } |
| + if dec.Decode(new(json.RawMessage)) != io.EOF { |
| + return errSponsoredWire |
| + } |
| + for key, f := range fields { |
| + if !seen[key] && !f.optional { |
| + return errSponsoredWire |
| + } |
| + } |
| + return nil |
| +} |
| +func sponsoredWireHex(v string) bool { |
| + if len(v) != 64 { |
| + return false |
| + } |
| + for _, c := range v { |
| + if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| +func sponsoredWireText(v string) bool { |
| + if v == "" || len(v) > 256 || !utf8.ValidString(v) || strings.ContainsRune(v, '\uFFFD') { |
| + return false |
| + } |
| + for _, c := range v { |
| + if c < 32 || c == 127 { |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| +func (v sponsoredWireRequest) validate() error { |
| + if v.Version != 1 || !sponsoredWireHex(v.OperationID) || !sponsoredWireHex(v.PreviewDigest) || v.DecisionReference != "carloku-existing-2026-10-07" || !v.CoverageAcknowledged || !sponsoredWireText(v.Note) || len(v.Items) < 1 || len(v.Items) > 1000 { |
| + return errSponsoredWire |
| + } |
| + var last int64 |
| + for _, item := range v.Items { |
| + if item.AccountID <= last || item.Sqid != domains.Sqid(item.AccountID) || !sponsoredWireText(item.Reason) || (item.Choice != "enroll" && item.Choice != "exclude" && item.Choice != "needs_review") || item.ConfirmSystemAccount != (item.AccountID == 1 && item.Choice == "enroll") { |
| + return errSponsoredWire |
| + } |
| + last = item.AccountID |
| + } |
| + return nil |
| +} |
| + |
| +func (v sponsoredWireRequest) storeRequest() sponsoredEnrollmentRequest { |
| + out := sponsoredEnrollmentRequest{OperationID: v.OperationID, PreviewDigest: v.PreviewDigest, DecisionReference: v.DecisionReference, Note: v.Note, CoverageAcknowledged: v.CoverageAcknowledged, Items: make([]sponsoredEnrollmentChoice, 0, len(v.Items))} |
| + for _, i := range v.Items { |
| + choice := sponsoredNeedsReview |
| + switch i.Choice { |
| + case "enroll": |
| + choice = sponsoredEnroll |
| + case "exclude": |
| + choice = sponsoredExclude |
| + } |
| + out.Items = append(out.Items, sponsoredEnrollmentChoice{AccountID: i.AccountID, Sqid: i.Sqid, Choice: choice, Reason: i.Reason, ConfirmSystemAccount: i.ConfirmSystemAccount}) |
| + } |
| + return out |
| +} |
| +func sponsoredWireFromRequest(v sponsoredEnrollmentRequest) sponsoredWireRequest { |
| + out := sponsoredWireRequest{Version: 1, OperationID: v.OperationID, PreviewDigest: v.PreviewDigest, DecisionReference: v.DecisionReference, Note: v.Note, CoverageAcknowledged: v.CoverageAcknowledged, Items: make([]sponsoredWireItem, 0, len(v.Items))} |
| + for _, i := range v.Items { |
| + choice := "" |
| + switch i.Choice { |
| + case sponsoredEnroll: |
| + choice = "enroll" |
| + case sponsoredExclude: |
| + choice = "exclude" |
| + case sponsoredNeedsReview: |
| + choice = "needs_review" |
| + } |
| + out.Items = append(out.Items, sponsoredWireItem{i.AccountID, i.Sqid, choice, i.Reason, i.ConfirmSystemAccount}) |
| + } |
| + return out |
| +} |
| +func sponsoredWireFromFacts(v *sponsoredEnrollmentFactsV1) *sponsoredWireFacts { |
| + if v == nil { |
| + return nil |
| + } |
| + return &sponsoredWireFacts{AccountID: v.ID, Sqid: v.Sqid, Name: v.Name, StoredCreatedAt: v.StoredCreatedAt, DeletedAt: v.DeletedAt, Owners: append([]string{}, v.Owners...), Billing: sponsoredWireBilling{v.Billing.Present, v.Billing.House, v.Billing.LiveMode, v.Billing.CustomerPresent, v.Billing.Subscription, v.Billing.Status}, CheckoutPresent: v.CheckoutPresent, BillingRetirement: v.BillingRetirement, Outbox: sponsoredWireOutbox{v.Outbox.Present, v.Outbox.LiveMode, v.Outbox.SerialOwed, v.Outbox.Revision, v.Outbox.PublishedRevision, v.Outbox.Digest, v.Outbox.State}, OpenBillingReviews: v.OpenBillingReviews, EntitlementReviews: v.EntitlementReviews} |
| +} |
| +func sponsoredWireFromReceipt(v sponsoredEnrollmentReceipt) (sponsoredWireResult, error) { |
| + var zero sponsoredWireResult |
| + req := sponsoredWireFromRequest(v.Request) |
| + if err := req.validate(); err != nil { |
| + return zero, err |
| + } |
| + h := sponsoredWireHistorical{Version: v.Version, Actor: v.Actor, Request: req, RecordedAt: v.RecordedAt, Rows: make([]sponsoredWireObservation, 0, len(v.Rows))} |
| + for _, row := range v.Rows { |
| + outcome := "" |
| + switch row.Outcome { |
| + case sponsoredAdded: |
| + outcome = "added" |
| + case sponsoredAlreadyRecorded: |
| + outcome = "already_recorded" |
| + case sponsoredExcludedByOperator: |
| + outcome = "excluded_by_operator" |
| + case sponsoredAwaitingReview: |
| + outcome = "needs_review" |
| + case sponsoredExcludedMissing: |
| + outcome = "excluded_missing" |
| + case sponsoredExcludedRetired: |
| + outcome = "excluded_retired" |
| + default: |
| + return zero, errSponsoredWire |
| + } |
| + h.Rows = append(h.Rows, sponsoredWireObservation{row.AccountID, row.Sqid, outcome, sponsoredWireFromFacts(row.Facts), row.FirstEnrollmentOperationID}) |
| + } |
| + body, err := json.Marshal(v) |
| + if err != nil { |
| + return zero, err |
| + } |
| + digest, err := sponsoredEnrollmentRequestDigest(v.Actor, v.Request) |
| + if err != nil { |
| + return zero, err |
| + } |
| + return sponsoredWireResult{Version: 1, HistoricalOnly: true, RequestDigest: digest, ReceiptDigest: sponsoredEnrollmentDigest("receipt", body), Historical: h}, nil |
| +} |
| +func (s *Store) readSponsoredPreview(ctx context.Context, actor string) (sponsoredCandidateInventory, error) { |
| + var zero sponsoredCandidateInventory |
| + if s == nil || s.db == nil || ctx == nil || !sponsoredEnrollmentActor(actor) { |
| + return zero, errSponsoredEnrollmentInvalid |
| + } |
| + tx, err := s.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) |
| + if err != nil { |
| + return zero, err |
| + } |
| + defer tx.Rollback() |
| + out, err := readSponsoredPreviewTx(ctx, tx, actor, nil) |
| + if err != nil { |
| + return zero, err |
| + } |
| + if err := tx.Commit(); err != nil { |
| + return zero, err |
| + } |
| + return out, nil |
| +} |
| +func readSponsoredPreviewTx(ctx context.Context, tx *sql.Tx, actor string, afterAuthorize func() error) (sponsoredCandidateInventory, error) { |
| + var zero sponsoredCandidateInventory |
| + if !sponsoredEnrollmentActor(actor) { |
| + return zero, errSponsoredEnrollmentInvalid |
| + } |
| + if err := authorizeSponsoredEnrollment(ctx, tx, actor); err != nil { |
| + return zero, err |
| + } |
| + if afterAuthorize != nil { |
| + if err := afterAuthorize(); err != nil { |
| + return zero, err |
| + } |
| + } |
| + return readSponsoredCandidatesTx(ctx, tx, nil) |
| +} |
| +func sponsoredWireFromPreview(v sponsoredCandidateInventory, now time.Time) (sponsoredWirePreview, error) { |
| + var zero sponsoredWirePreview |
| + payload := sponsoredWirePreviewPayload{Version: 1, Coverage: "console_known_accounts", Count: len(v.Rows), Rows: make([]sponsoredWirePreviewRow, 0, len(v.Rows))} |
| + for _, row := range v.Rows { |
| + facts := sponsoredEnrollmentCopyFacts(row) |
| + if !validSponsoredEnrollmentFacts(facts) { |
| + return zero, errSponsoredEnrollmentCorrupt |
| + } |
| + payload.Rows = append(payload.Rows, sponsoredWirePreviewRow{*sponsoredWireFromFacts(facts), row.ID == 1, row.DeletedAt != 0 || row.BillingRetirement}) |
| + } |
| + raw, err := json.Marshal(payload) |
| + if err != nil { |
| + return zero, err |
| + } |
| + return sponsoredWirePreview{1, payload, sponsoredEnrollmentDigest("preview", raw), now.Unix()}, nil |
| +} |
| +func sponsoredOperatorEncode(v any) ([]byte, error) { |
| + raw, err := json.Marshal(v) |
| + if err != nil { |
| + return nil, err |
| + } |
| + if len(raw) > sponsoredWireResponseLimit { |
| + return nil, errSponsoredEnrollmentTooLarge |
| + } |
| + return raw, nil |
| +} |
| +func sponsoredOperatorSuccess(w http.ResponseWriter, v any) (started bool, err error) { |
| + raw, err := sponsoredOperatorEncode(v) |
| + if err != nil { |
| + return false, err |
| + } |
| + w.Header().Set("Content-Type", "application/json") |
| + w.Header().Set("Cache-Control", "no-store") |
| + w.WriteHeader(http.StatusOK) |
| + n, err := w.Write(raw) |
| + if err == nil && n != len(raw) { |
| + err = io.ErrShortWrite |
| + } |
| + if err != nil { |
| + safe := io.ErrClosedPipe |
| + if errors.Is(err, io.ErrShortWrite) { |
| + safe = io.ErrShortWrite |
| + } |
| + log.Printf("console: api billing operator: %v", safe) |
| + } |
| + return true, err |
| +} |
| +func sponsoredOperatorQuery(w http.ResponseWriter, r *http.Request) bool { |
| + if r.URL.RawQuery != "" { |
| + jsonError(w, http.StatusBadRequest, sponsoredCopyInvalidRequestError) |
| + return false |
| + } |
| + if r.Method == http.MethodGet && r.Body != nil { |
| + b, err := io.ReadAll(io.LimitReader(r.Body, 1)) |
| + if len(b) != 0 || err != nil { |
| + jsonError(w, http.StatusBadRequest, sponsoredCopyInvalidRequestError) |
| + return false |
| + } |
| + } |
| + return true |
| +} |
| +func sponsoredOperatorError(w http.ResponseWriter, r *http.Request, err error, writing bool) { |
| + switch { |
| + case errors.Is(err, errSponsoredEnrollmentDenied): |
| + http.NotFound(w, r) |
| + case errors.Is(err, errSponsoredEnrollmentConflict): |
| + jsonError(w, http.StatusConflict, sponsoredCopyConflictError) |
| + case errors.Is(err, errSponsoredEnrollmentNotFound): |
| + jsonError(w, http.StatusNotFound, sponsoredCopyNotFoundError) |
| + case errors.Is(err, errSponsoredEnrollmentTooLarge): |
| + jsonError(w, http.StatusUnprocessableEntity, sponsoredCopyResultLimitError) |
| + default: |
| + msg := sponsoredCopyReadWriteFailureReadError |
| + if writing { |
| + msg = sponsoredCopyReadWriteFailureWriteError |
| + } |
| + jsonError(w, http.StatusInternalServerError, msg) |
| + } |
| +} |
| +func (s *Server) handleAPIBillingSponsoredPreview(w http.ResponseWriter, r *http.Request) { |
| + actor, ok := s.billingOperator(w, r) |
| + if !ok { |
| + return |
| + } |
| + if !sponsoredOperatorQuery(w, r) { |
| + return |
| + } |
| + inventory, err := s.store.readSponsoredPreview(r.Context(), actor) |
| + if err != nil { |
| + sponsoredOperatorError(w, r, err, false) |
| + return |
| + } |
| + result, err := sponsoredWireFromPreview(inventory, time.Now()) |
| + if err != nil { |
| + sponsoredOperatorError(w, r, err, false) |
| + return |
| + } |
| + started, err := sponsoredOperatorSuccess(w, result) |
| + if !started && err != nil { |
| + if errors.Is(err, errSponsoredEnrollmentTooLarge) { |
| + jsonError(w, http.StatusUnprocessableEntity, sponsoredCopyPreviewLimitError) |
| + } else { |
| + sponsoredOperatorError(w, r, err, false) |
| + } |
| + } |
| +} |
| +func (s *Server) handleAPIBillingSponsoredEnroll(w http.ResponseWriter, r *http.Request) { |
| + actor, ok := s.billingOperator(w, r) |
| + if !ok { |
| + return |
| + } |
| + if !sponsoredOperatorQuery(w, r) { |
| + return |
| + } |
| + ct, _, _ := strings.Cut(r.Header.Get("Content-Type"), ";") |
| + if strings.TrimSpace(strings.ToLower(ct)) != "application/json" { |
| + jsonError(w, http.StatusUnsupportedMediaType, "send a JSON body with Content-Type: application/json") |
| + return |
| + } |
| + raw, err := io.ReadAll(http.MaxBytesReader(w, r.Body, sponsoredWireRequestLimit)) |
| + if err != nil { |
| + var size *http.MaxBytesError |
| + if errors.As(err, &size) { |
| + jsonError(w, http.StatusRequestEntityTooLarge, "request body too large") |
| + } else { |
| + jsonError(w, http.StatusBadRequest, sponsoredCopyInvalidRequestError) |
| + } |
| + return |
| + } |
| + var input sponsoredWireRequest |
| + if json.Unmarshal(raw, &input) != nil || input.validate() != nil { |
| + jsonError(w, http.StatusBadRequest, sponsoredCopyInvalidRequestError) |
| + return |
| + } |
| + req := input.storeRequest() |
| + if validateSponsoredEnrollmentRequest(req) != nil { |
| + jsonError(w, http.StatusBadRequest, sponsoredCopyInvalidRequestError) |
| + return |
| + } |
| + result, err := s.store.enrollSponsoredCandidates(r.Context(), actor, req, time.Now()) |
| + if err != nil { |
| + sponsoredOperatorError(w, r, err, true) |
| + return |
| + } |
| + sponsoredOperatorEnrollResponse(w, result, sponsoredWireFromReceipt) |
| +} |
| +func sponsoredOperatorEnrollResponse(w http.ResponseWriter, result sponsoredEnrollmentResult, convert func(sponsoredEnrollmentReceipt) (sponsoredWireResult, error)) { |
| + out, err := convert(result.Historical) |
| + if err != nil { |
| + jsonError(w, http.StatusInternalServerError, sponsoredCopyReadWriteFailureWriteError) |
| + return |
| + } |
| + out.Replayed = &result.Replayed |
| + started, err := sponsoredOperatorSuccess(w, out) |
| + if !started && err != nil { |
| + jsonError(w, http.StatusInternalServerError, sponsoredCopyReadWriteFailureWriteError) |
| + } |
| +} |
| +func (s *Server) handleAPIBillingSponsoredStatus(w http.ResponseWriter, r *http.Request) { |
| + actor, ok := s.billingOperator(w, r) |
| + if !ok { |
| + return |
| + } |
| + if !sponsoredOperatorQuery(w, r) { |
| + return |
| + } |
| + operation := r.PathValue("operation") |
| + if !sponsoredWireHex(operation) { |
| + jsonError(w, http.StatusBadRequest, sponsoredCopyInvalidRequestError) |
| + return |
| + } |
| + receipt, err := s.store.readSponsoredEnrollment(r.Context(), actor, operation) |
| + if err != nil { |
| + sponsoredOperatorError(w, r, err, false) |
| + return |
| + } |
| + out, err := sponsoredWireFromReceipt(receipt) |
| + if err != nil { |
| + jsonError(w, http.StatusInternalServerError, sponsoredCopyReadWriteFailureReadError) |
| + return |
| + } |
| + started, err := sponsoredOperatorSuccess(w, out) |
| + if !started && err != nil { |
| + jsonError(w, http.StatusInternalServerError, sponsoredCopyReadWriteFailureReadError) |
| + } |
| +} |
diff --git a/internal/console/sponsoredoperator_test.go b/internal/console/sponsoredoperator_test.go| new file mode 100644 |
| index 000000000..1215ce575 |
| --- /dev/null |
| +++ b/internal/console/sponsoredoperator_test.go |
| @@ -0,0 +1,611 @@ |
| +package console |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "database/sql" |
| + "encoding/json" |
| + "errors" |
| + "github.com/carlosframework/platform/internal/domains" |
| + "io" |
| + "log" |
| + "net/http" |
| + "net/http/httptest" |
| + "os" |
| + "path/filepath" |
| + "reflect" |
| + "sort" |
| + "strings" |
| + "testing" |
| + "time" |
| +) |
| + |
| +func operatorWire(t *testing.T) map[string]json.RawMessage { |
| + t.Helper() |
| + b, e := os.ReadFile("testdata/sponsored-operator-wire.json") |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + var v map[string]json.RawMessage |
| + if json.Unmarshal(b, &v) != nil { |
| + t.Fatal("fixture") |
| + } |
| + return v |
| +} |
| +func operatorRequest(t *testing.T) sponsoredWireRequest { |
| + t.Helper() |
| + var r sponsoredWireRequest |
| + if json.Unmarshal(operatorWire(t)["request"], &r) != nil { |
| + t.Fatal("request fixture") |
| + } |
| + return r |
| +} |
| +func operatorHTTP(t *testing.T, fx *consoleFixture, method, path, token string, body []byte) (int, http.Header, []byte) { |
| + t.Helper() |
| + r := httptest.NewRequest(method, path, bytes.NewReader(body)) |
| + r.Header.Set("Content-Type", "application/json") |
| + if token != "" { |
| + r.Header.Set("Authorization", "Bearer "+token) |
| + } |
| + w := httptest.NewRecorder() |
| + fx.srv.ServeHTTP(w, r) |
| + return w.Code, w.Header(), w.Body.Bytes() |
| +} |
| +func operatorJSON(t *testing.T, v any) []byte { |
| + t.Helper() |
| + b, e := json.Marshal(v) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + return b |
| +} |
| +func operatorSnapshot(t *testing.T, s *Store) []sponsoredTableSnapshot { |
| + t.Helper() |
| + out := sponsoredLogicalSnapshot(t, s.db) |
| + for i := range out { |
| + if out[i].Name != "cli_tokens" { |
| + continue |
| + } |
| + rows, e := s.db.Query(`SELECT * FROM cli_tokens`) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + cols, e := rows.Columns() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + out[i].Rows = nil |
| + for rows.Next() { |
| + v := make([]any, len(cols)) |
| + p := make([]any, len(cols)) |
| + for j := range v { |
| + p[j] = &v[j] |
| + } |
| + if rows.Scan(p...) != nil { |
| + t.Fatal("scan") |
| + } |
| + parts := []string{} |
| + for j, k := range cols { |
| + if k != "last_used" { |
| + parts = append(parts, sponsoredSnapshotValue(v[j])) |
| + } |
| + } |
| + out[i].Rows = append(out[i].Rows, strings.Join(parts, "|")) |
| + } |
| + if e := rows.Err(); e != nil { |
| + t.Fatal(e) |
| + } |
| + if e := rows.Close(); e != nil { |
| + t.Fatal(e) |
| + } |
| + sort.Strings(out[i].Rows) |
| + } |
| + return out |
| +} |
| +func TestSponsoredOperatorWireAndCodec(t *testing.T) { |
| + f := operatorWire(t) |
| + var req sponsoredWireRequest |
| + if e := json.Unmarshal(f["request"], &req); e != nil || req.validate() != nil { |
| + t.Fatal(e) |
| + } |
| + if !reflect.DeepEqual(req, sponsoredWireFromRequest(req.storeRequest())) { |
| + t.Fatal("request projection") |
| + } |
| + var preview sponsoredWirePreview |
| + if e := json.Unmarshal(f["preview"], &preview); e != nil { |
| + t.Fatal(e) |
| + } |
| + p := preview.Preview |
| + b := operatorJSON(t, p) |
| + if sponsoredEnrollmentDigest("preview", b) != preview.PreviewDigest { |
| + t.Fatal("golden digest") |
| + } |
| + if sponsoredEnrollmentDigest("receipt", b) == preview.PreviewDigest { |
| + t.Fatal("domain collision") |
| + } |
| + raw := string(f["request"]) |
| + bad := []string{"null", "[]", raw + " {}", strings.Replace(raw, `"version": 1`, `"version": 1, "version": 1`, 1), strings.Replace(raw, `"version"`, `"Version"`, 1), strings.Replace(raw, `"note": "reviewed fixture"`, `"note": null`, 1), strings.Replace(raw, `"note": "reviewed fixture"`, `"note": "\ud800"`, 1), strings.Replace(raw, `"reason": "existing fixture"`, `"reason": "�"`, 1), strings.Replace(raw, `"choice": "enroll"`, `"choice": "enroll", "choice": "exclude"`, 1), strings.Replace(raw, `"confirm_system_account": false`, `"confirm_system_account": null`, 1), strings.Replace(raw, `"coverage_acknowledged": true`, `"coverage_acknowledged": null`, 1), strings.Replace(raw, `"note": "reviewed fixture"`, "\"note\": \"\xff\"", 1)} |
| + for i, b := range bad { |
| + var v sponsoredWireRequest |
| + e := json.Unmarshal([]byte(b), &v) |
| + if e == nil { |
| + e = v.validate() |
| + } |
| + if e == nil { |
| + t.Errorf("bad case%d accepted", i) |
| + } |
| + } |
| +} |
| +func TestSponsoredOperatorAuthAndHTTPHistory(t *testing.T) { |
| + fx := newConsoleFixture(t) |
| + member := cliToken(t, fx, "member@example.test", "op-member") |
| + token := billingOperatorToken(t, fx, "operator@example.test", "op-admin") |
| + sponsoredInsertAccount(t, fx.st.db, 100, "fixture", 10, 0) |
| + req := operatorRequest(t) |
| + req.Items[0].AccountID = 100 |
| + req.Items[0].Sqid = domains.Sqid(100) |
| + wantInventory := readSponsoredTestInventory(t, fx.st) |
| + body := operatorJSON(t, req) |
| + paths := []struct{ method, path string }{{"GET", "/api/billing/sponsored/preview"}, {"POST", "/api/billing/sponsored/enrollments"}, {"GET", "/api/billing/sponsored/enrollments/" + req.OperationID}} |
| + _, nh, nb := operatorHTTP(t, fx, "GET", "/unknown", member, nil) |
| + before := operatorSnapshot(t, fx.st) |
| + for _, p := range paths { |
| + status, h, b := operatorHTTP(t, fx, p.method, p.path, member, body) |
| + if status != 404 || !bytes.Equal(b, nb) || !reflect.DeepEqual(h, nh) { |
| + t.Fatalf("hidden404 %s %d %v %v", p.path, status, h, nh) |
| + } |
| + s1, h1, b1 := operatorHTTP(t, fx, p.method, p.path, "", body) |
| + s2, h2, b2 := operatorHTTP(t, fx, p.method, p.path, "fabricated", body) |
| + if s1 != 401 || s2 != 401 || !reflect.DeepEqual(h1, h2) || !bytes.Equal(b1, b2) { |
| + t.Fatal("401") |
| + } |
| + } |
| + status, headers, raw := operatorHTTP(t, fx, "GET", paths[0].path, token, nil) |
| + if status != 200 || headers.Get("Cache-Control") != "no-store" { |
| + t.Fatal(status, string(raw)) |
| + } |
| + var preview sponsoredWirePreview |
| + if json.Unmarshal(raw, &preview) != nil || preview.Preview.Count != len(wantInventory.Rows) || !preview.Preview.Rows[0].SystemAccount { |
| + t.Fatal("preview") |
| + } |
| + if !reflect.DeepEqual(before, operatorSnapshot(t, fx.st)) { |
| + t.Fatal("read mutated beyond last_used") |
| + } |
| + for _, path := range []string{paths[0].path + "?x=", paths[1].path + "?x", paths[2].path + "?x"} { |
| + method := "GET" |
| + if strings.Contains(path, "enrollments?") { |
| + method = "POST" |
| + } |
| + status, _, _ := operatorHTTP(t, fx, method, path, token, body) |
| + if status != 400 { |
| + t.Fatal("query", status) |
| + } |
| + } |
| + status, _, raw = operatorHTTP(t, fx, "POST", paths[1].path, token, body) |
| + if status != 200 { |
| + t.Fatal(status, string(raw)) |
| + } |
| + var first sponsoredWireResult |
| + if json.Unmarshal(raw, &first) != nil || first.Replayed == nil || *first.Replayed || first.Historical.Rows[0].Outcome != "added" { |
| + t.Fatal("new receipt") |
| + } |
| + sponsoredExec(t, fx.st.db, `UPDATE accounts SET deleted_at=99 WHERE id=100`) |
| + status, _, raw = operatorHTTP(t, fx, "POST", paths[1].path, token, body) |
| + var replay sponsoredWireResult |
| + if status != 200 || json.Unmarshal(raw, &replay) != nil || replay.Replayed == nil || !*replay.Replayed || !reflect.DeepEqual(first.Historical, replay.Historical) { |
| + t.Fatal("historical replay") |
| + } |
| + other := billingOperatorToken(t, fx, "other@example.test", "op-other") |
| + before = operatorSnapshot(t, fx.st) |
| + status, _, raw = operatorHTTP(t, fx, "GET", paths[2].path, other, nil) |
| + var history sponsoredWireResult |
| + if status != 200 || json.Unmarshal(raw, &history) != nil || history.Replayed != nil || history.Historical.Actor != "operator@example.test" { |
| + t.Fatal("cross actor history") |
| + } |
| + if !reflect.DeepEqual(before, operatorSnapshot(t, fx.st)) { |
| + t.Fatal("status wrote") |
| + } |
| + status, _, _ = operatorHTTP(t, fx, "POST", paths[1].path, other, body) |
| + if status != 409 { |
| + t.Fatal("actor conflict", status) |
| + } |
| + if e := fx.st.SetOperator("operator@example.test", false); e != nil { |
| + t.Fatal(e) |
| + } |
| + status, _, _ = operatorHTTP(t, fx, "POST", paths[1].path, token, body) |
| + if status != 404 { |
| + t.Fatal("revoked", status) |
| + } |
| +} |
| +func TestSponsoredOperatorPreviewAuthSnapshot(t *testing.T) { |
| + path := filepath.Join(t.TempDir(), "auth.db") |
| + s := openSponsoredStoreAt(t, path) |
| + other := openSponsoredStoreAt(t, path) |
| + if e := s.SetOperator(enrollmentActor, true); e != nil { |
| + t.Fatal(e) |
| + } |
| + sponsoredInsertAccount(t, s.db, 2, "before", 1, 0) |
| + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| + defer cancel() |
| + before := sponsoredLogicalSnapshot(t, s.db) |
| + if _, e := s.readSponsoredPreview(ctx, enrollmentActor); e != nil { |
| + t.Fatal(e) |
| + } |
| + if !reflect.DeepEqual(before, sponsoredLogicalSnapshot(t, s.db)) { |
| + t.Fatal("store read wrote") |
| + } |
| + tx, e := s.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer tx.Rollback() |
| + got, e := readSponsoredPreviewTx(ctx, tx, enrollmentActor, func() error { |
| + if e := other.SetOperator(enrollmentActor, false); e != nil { |
| + return e |
| + } |
| + _, e := other.db.ExecContext(ctx, `UPDATE accounts SET name='after' WHERE id=2`) |
| + return e |
| + }) |
| + if e != nil || got.Rows[0].Name != "before" { |
| + t.Fatal("authorization snapshot", e, got) |
| + } |
| + if e := tx.Commit(); e != nil { |
| + t.Fatal(e) |
| + } |
| + got, e = s.readSponsoredPreview(ctx, enrollmentActor) |
| + if !errors.Is(e, errSponsoredEnrollmentDenied) || !reflect.DeepEqual(got, sponsoredCandidateInventory{}) { |
| + t.Fatal("revoked read", e, got) |
| + } |
| +} |
| +func TestSponsoredOperatorHTTPValidationAtomicity(t *testing.T) { |
| + fx := newConsoleFixture(t) |
| + token := billingOperatorToken(t, fx, "operator@example.test", "op-validation") |
| + req := operatorRequest(t) |
| + raw := operatorJSON(t, req) |
| + before := operatorSnapshot(t, fx.st) |
| + bad := [][]byte{append(append([]byte{}, raw...), []byte(" {}")...), []byte("null"), bytes.Replace(raw, []byte(`"version":1`), []byte(`"version":null`), 1), bytes.Replace(raw, []byte(`"choice":"enroll"`), []byte(`"choice":"unknown"`), 1)} |
| + for _, b := range bad { |
| + status, _, _ := operatorHTTP(t, fx, "POST", "/api/billing/sponsored/enrollments", token, b) |
| + if status != 400 { |
| + t.Fatal("invalid", status) |
| + } |
| + } |
| + if !reflect.DeepEqual(before, operatorSnapshot(t, fx.st)) { |
| + t.Fatal("invalid request wrote") |
| + } |
| + for _, n := range []int{sponsoredWireRequestLimit - 1, sponsoredWireRequestLimit, sponsoredWireRequestLimit + 1} { |
| + body := append(append([]byte{}, raw...), bytes.Repeat([]byte(" "), n-len(raw))...) |
| + status, _, b := operatorHTTP(t, fx, "POST", "/api/billing/sponsored/enrollments", token, body) |
| + want := 200 |
| + if n > sponsoredWireRequestLimit { |
| + want = 413 |
| + } |
| + if status != want { |
| + t.Fatalf("size%d status%d want%d %s", n, status, want, b) |
| + } |
| + } |
| +} |
| +func TestSponsoredOperatorBufferedResponseAndPostCommit(t *testing.T) { |
| + for _, delta := range []int{-1, 0, 1} { |
| + v := strings.Repeat("x", sponsoredWireResponseLimit-2+delta) |
| + w := httptest.NewRecorder() |
| + started, e := sponsoredOperatorSuccess(w, v) |
| + if delta > 0 { |
| + if started || !errors.Is(e, errSponsoredEnrollmentTooLarge) || w.Body.Len() != 0 { |
| + t.Fatal("oversize started") |
| + } |
| + } else if !started || e != nil || w.Body.Len() != sponsoredWireResponseLimit+delta || w.Header().Get("Cache-Control") != "no-store" { |
| + t.Fatal("boundary", delta, e) |
| + } |
| + } |
| + w := httptest.NewRecorder() |
| + started, e := sponsoredOperatorSuccess(w, make(chan int)) |
| + if started || e == nil || w.Body.Len() != 0 { |
| + t.Fatal("encode failure") |
| + } |
| + s := enrollmentStore(t) |
| + sponsoredInsertAccount(t, s.db, 2, "account", 0, 0) |
| + result, e := s.enrollSponsoredCandidates(enrollmentContext(t), enrollmentActor, operatorRequest(t).storeRequest(), enrollmentNow) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + for _, replay := range []bool{false, true} { |
| + result.Replayed = replay |
| + for _, mode := range []int{0, 1} { |
| + w := httptest.NewRecorder() |
| + sponsoredOperatorEnrollResponse(w, result, func(r sponsoredEnrollmentReceipt) (sponsoredWireResult, error) { |
| + if mode == 0 { |
| + return sponsoredWireResult{}, errSponsoredEnrollmentTooLarge |
| + } |
| + out, e := sponsoredWireFromReceipt(r) |
| + out.Historical.Actor = strings.Repeat("x", sponsoredWireResponseLimit) |
| + return out, e |
| + }) |
| + if w.Code != 500 || !strings.Contains(w.Body.String(), sponsoredCopyReadWriteFailureWriteError) { |
| + t.Fatal("postcommit status", w.Code) |
| + } |
| + } |
| + } |
| + if _, e := s.readSponsoredEnrollment(enrollmentContext(t), enrollmentActor, result.Historical.Request.OperationID); e != nil { |
| + t.Fatal("committed receipt lost") |
| + } |
| + w = httptest.NewRecorder() |
| + sponsoredOperatorError(w, httptest.NewRequest("POST", "/", nil), errSponsoredEnrollmentInvalid, true) |
| + if w.Code != 500 { |
| + t.Fatal("StoreInvalid must500") |
| + } |
| +} |
| +func TestSponsoredOperatorCompletePreviewBound(t *testing.T) { |
| + inv := sponsoredCandidateInventory{Rows: []sponsoredCandidateRow{}} |
| + for id := int64(1); id <= 1001; id++ { |
| + inv.Rows = append(inv.Rows, sponsoredCandidateRow{ID: id, Sqid: domains.Sqid(id), Owners: []string{}}) |
| + } |
| + out, e := sponsoredWireFromPreview(inv, enrollmentNow) |
| + if e != nil || out.Preview.Count != 1001 { |
| + t.Fatal("truncated", e) |
| + } |
| + if _, e := sponsoredOperatorEncode(out); e != nil { |
| + t.Fatal(e) |
| + } |
| + inv.Rows[0].Name = strings.Repeat("x", sponsoredWireResponseLimit) |
| + out, e = sponsoredWireFromPreview(inv, enrollmentNow) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + if _, e := sponsoredOperatorEncode(out); !errors.Is(e, errSponsoredEnrollmentTooLarge) { |
| + t.Fatal("preview exceeded", e) |
| + } |
| + inv.Rows[0].Name = string([]byte{255}) |
| + if _, e := sponsoredWireFromPreview(inv, enrollmentNow); e == nil { |
| + t.Fatal("invalid stored UTF8") |
| + } |
| +} |
| +func TestSponsoredOperatorDerivedWireBounds(t *testing.T) { |
| + keyGrowth := func(old, new reflect.Type) int { |
| + n := 0 |
| + for i := 0; i < old.NumField(); i++ { |
| + a := old.Field(i) |
| + b := new.Field(i) |
| + if a.Name == "ID" && b.Name != "AccountID" { |
| + t.Fatal("ID mapping") |
| + } |
| + key := strings.Split(b.Tag.Get("json"), ",")[0] |
| + delta := len(key) - len(a.Name) |
| + if delta > 0 { |
| + n += delta |
| + } |
| + } |
| + return n |
| + } |
| + // One fixed object, at most1000 request items and1000 observed facts; negative deltas are discarded. |
| + itemKeys := keyGrowth(reflect.TypeOf(sponsoredEnrollmentChoice{}), reflect.TypeOf(sponsoredWireItem{})) |
| + obsKeys := keyGrowth(reflect.TypeOf(sponsoredEnrollmentObservation{}), reflect.TypeOf(sponsoredWireObservation{})) |
| + factsKeys := keyGrowth(reflect.TypeOf(sponsoredEnrollmentFactsV1{}), reflect.TypeOf(sponsoredWireFacts{})) |
| + billKeys := keyGrowth(reflect.TypeOf(sponsoredEnrollmentBillingV1{}), reflect.TypeOf(sponsoredWireBilling{})) |
| + outboxKeys := keyGrowth(reflect.TypeOf(sponsoredEnrollmentOutboxV1{}), reflect.TypeOf(sponsoredWireOutbox{})) |
| + // Struct order differs in request; its fixed key growth, wire version and envelope are bounded independently below. |
| + enumGrowth := len(`"needs_review"`) - 1 + len(`"excluded_by_operator"`) - 1 |
| + baseRequest := operatorRequest(t).storeRequest() |
| + baseReceipt := sponsoredEnrollmentReceipt{Version: 1, Actor: enrollmentActor, Request: baseRequest, RecordedAt: enrollmentNow.Unix(), Rows: []sponsoredEnrollmentObservation{{AccountID: 2, Sqid: domains.Sqid(2), Outcome: sponsoredAdded, Facts: &sponsoredEnrollmentFactsV1{ID: 2, Sqid: domains.Sqid(2), Owners: []string{}}, FirstEnrollmentOperationID: baseRequest.OperationID}}} |
| + wire, err := sponsoredWireFromReceipt(baseReceipt) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + noReplay := false |
| + wire.Replayed = &noReplay |
| + baseReceipt.Request.Items = []sponsoredEnrollmentChoice{} |
| + baseReceipt.Rows = []sponsoredEnrollmentObservation{} |
| + wire.Historical.Request.Items = []sponsoredWireItem{} |
| + wire.Historical.Rows = []sponsoredWireObservation{} |
| + fixed := len(operatorJSON(t, wire)) - len(operatorJSON(t, baseReceipt)) |
| + if fixed <= 0 { |
| + t.Fatal("fixed envelope bound") |
| + } |
| + baseReceipt.Request = baseRequest |
| + baseReceipt.Rows = []sponsoredEnrollmentObservation{{AccountID: 2, Sqid: domains.Sqid(2), Outcome: sponsoredAdded, Facts: &sponsoredEnrollmentFactsV1{ID: 2, Sqid: domains.Sqid(2), Owners: []string{}}, FirstEnrollmentOperationID: baseRequest.OperationID}} |
| + baseReceipt.Rows[0].Facts.Name = strings.Repeat("x", sponsoredEnrollmentMaxBody-len(operatorJSON(t, baseReceipt))) |
| + if len(operatorJSON(t, baseReceipt)) != sponsoredEnrollmentMaxBody { |
| + t.Fatal("receipt boundary") |
| + } |
| + wire, err = sponsoredWireFromReceipt(baseReceipt) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + wire.Replayed = &noReplay |
| + if _, err := sponsoredOperatorEncode(wire); err != nil { |
| + t.Fatal("exact receipt boundary transport", err) |
| + } |
| + growth := fixed + 1000*(itemKeys+obsKeys+factsKeys+billKeys+outboxKeys+enumGrowth) |
| + if sponsoredEnrollmentMaxBody+growth > sponsoredWireResponseLimit { |
| + t.Fatal("derived bound", growth) |
| + } |
| + req := operatorRequest(t) |
| + req.Items = nil |
| + for id := int64(2); id <= 1001; id++ { |
| + req.Items = append(req.Items, sponsoredWireItem{id, domains.Sqid(id), "exclude", strings.Repeat("<", 256), false}) |
| + } |
| + req.Note = strings.Repeat("&", 256) |
| + raw := operatorJSON(t, req) |
| + if req.validate() != nil || len(raw) >= sponsoredWireRequestLimit { |
| + t.Fatal("escaped request bound", len(raw)) |
| + } |
| + t.Logf("maximum nonnegative per-row key/enum growth=%d; fixed=%d; total upper bound=%d", growth-fixed, fixed, sponsoredEnrollmentMaxBody+growth) |
| +} |
| + |
| +func TestSponsoredOperatorPersonBearerOnly(t *testing.T) { |
| + fx := newConsoleFixture(t) |
| + tokens := []string{} |
| + for i, kind := range []string{"service", "expired", "revoked"} { |
| + token := cliToken(t, fx, "blocked@example.test", "blocked-"+kind) |
| + tokens = append(tokens, token) |
| + switch i { |
| + case 0: |
| + sponsoredExec(t, fx.st.db, `UPDATE cli_tokens SET kind='service' WHERE token_hash=?`, hashToken(token)) |
| + case 1: |
| + sponsoredExec(t, fx.st.db, `UPDATE cli_tokens SET expires_at=1 WHERE token_hash=?`, hashToken(token)) |
| + case 2: |
| + sponsoredExec(t, fx.st.db, `UPDATE cli_tokens SET revoked_at=1 WHERE token_hash=?`, hashToken(token)) |
| + } |
| + } |
| + cookie, e := fx.st.CreateSession("operator@example.test", "fixture", time.Now()) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + if e := fx.st.SetOperator("operator@example.test", true); e != nil { |
| + t.Fatal(e) |
| + } |
| + for _, p := range []struct{ method, path string }{{"GET", "/api/billing/sponsored/preview"}, {"POST", "/api/billing/sponsored/enrollments"}, {"GET", "/api/billing/sponsored/enrollments/" + strings.Repeat("1", 64)}} { |
| + _, headers, body := operatorHTTP(t, fx, p.method, p.path, "", nil) |
| + for _, token := range tokens { |
| + status, h, b := operatorHTTP(t, fx, p.method, p.path, token, []byte("invalid")) |
| + if status != 401 || !bytes.Equal(body, b) || !reflect.DeepEqual(headers, h) { |
| + t.Fatal("person gate", status) |
| + } |
| + } |
| + r := httptest.NewRequest(p.method, p.path, nil) |
| + r.AddCookie(&http.Cookie{Name: sessionCookie, Value: cookie}) |
| + out := httptest.NewRecorder() |
| + fx.srv.ServeHTTP(out, r) |
| + if out.Code != 401 || !bytes.Equal(out.Body.Bytes(), body) || !reflect.DeepEqual(out.Header(), headers) { |
| + t.Fatal("cookie accepted") |
| + } |
| + } |
| +} |
| +func TestSponsoredOperatorMixedFiniteHTTPAndRollback(t *testing.T) { |
| + fx := newConsoleFixture(t) |
| + token := billingOperatorToken(t, fx, "operator@example.test", "mixed-op") |
| + for id := int64(100); id <= 106; id++ { |
| + if id != 104 { |
| + sponsoredInsertAccount(t, fx.st.db, id, "fixture", 10, 0) |
| + } |
| + } |
| + sponsoredExec(t, fx.st.db, `UPDATE accounts SET deleted_at=1 WHERE id=105`) |
| + sponsoredExec(t, fx.st.db, `INSERT INTO billing_retirements(account_id,started_at) VALUES(106,1)`) |
| + sponsoredExec(t, fx.st.db, `INSERT INTO billing_accounts(account_id,subscription,status,live_mode) VALUES(101,'sub_fixture','active',1)`) |
| + sponsoredExec(t, fx.st.db, `INSERT INTO billing_checkout_intents(account_id,id,plan,terms,band,price,quantity,created_at) VALUES(101,'ci_fixture','personal','terms','band','price',1,1)`) |
| + req := operatorRequest(t) |
| + req.Items = []sponsoredWireItem{{100, domains.Sqid(100), "enroll", "reviewed", false}} |
| + status, _, raw := operatorHTTP(t, fx, "POST", "/api/billing/sponsored/enrollments", token, operatorJSON(t, req)) |
| + if status != 200 { |
| + t.Fatal(status, string(raw)) |
| + } |
| + req.OperationID = strings.Repeat("5", 64) |
| + req.Items = []sponsoredWireItem{{1, domains.Sqid(1), "enroll", "system reviewed", true}} |
| + for id := int64(100); id <= 106; id++ { |
| + choice := "enroll" |
| + if id == 102 { |
| + choice = "exclude" |
| + } |
| + if id == 103 { |
| + choice = "needs_review" |
| + } |
| + req.Items = append(req.Items, sponsoredWireItem{id, domains.Sqid(id), choice, "reviewed", false}) |
| + } |
| + before := operatorSnapshot(t, fx.st) |
| + status, _, raw = operatorHTTP(t, fx, "POST", "/api/billing/sponsored/enrollments", token, operatorJSON(t, req)) |
| + if status != 200 { |
| + t.Fatal(status, string(raw)) |
| + } |
| + var result sponsoredWireResult |
| + if json.Unmarshal(raw, &result) != nil { |
| + t.Fatal("decode") |
| + } |
| + want := []string{"added", "already_recorded", "added", "excluded_by_operator", "needs_review", "excluded_missing", "excluded_retired", "excluded_retired"} |
| + for i, row := range result.Historical.Rows { |
| + if row.Outcome != want[i] { |
| + t.Fatal("outcome", i, row.Outcome) |
| + } |
| + } |
| + if !result.Historical.Rows[2].Facts.CheckoutPresent || result.Historical.Rows[2].Facts.Billing.Subscription != "sub_fixture" { |
| + t.Fatal("conflict lost") |
| + } |
| + after := operatorSnapshot(t, fx.st) |
| + for i, row := range before { |
| + if row.Name == "sponsored_enrollment_receipts" || row.Name == "sponsored_cohort_memberships" { |
| + continue |
| + } |
| + if !reflect.DeepEqual(row, after[i]) { |
| + t.Fatal("unrelated mutation", row.Name) |
| + } |
| + } |
| + req.OperationID = strings.Repeat("6", 64) |
| + for i := range req.Items { |
| + req.Items[i].Choice = "exclude" |
| + req.Items[i].ConfirmSystemAccount = false |
| + } |
| + status, _, raw = operatorHTTP(t, fx, "POST", "/api/billing/sponsored/enrollments", token, operatorJSON(t, req)) |
| + if status != 200 || json.Unmarshal(raw, &result) != nil { |
| + t.Fatal("zero addition") |
| + } |
| + for _, row := range result.Historical.Rows { |
| + if row.Outcome == "added" { |
| + t.Fatal("exclusion added") |
| + } |
| + } |
| + req.OperationID = strings.Repeat("7", 64) |
| + sponsoredExec(t, fx.st.db, `CREATE TRIGGER operator_fixture_failure BEFORE INSERT ON sponsored_enrollment_receipts BEGIN SELECT RAISE(ABORT,'fixture'); END`) |
| + before = operatorSnapshot(t, fx.st) |
| + status, _, _ = operatorHTTP(t, fx, "POST", "/api/billing/sponsored/enrollments", token, operatorJSON(t, req)) |
| + if status != 500 || !reflect.DeepEqual(before, operatorSnapshot(t, fx.st)) { |
| + t.Fatal("failure partial write", status) |
| + } |
| +} |
| + |
| +type sponsoredFailingResponse struct { |
| + header http.Header |
| + codes []int |
| + writes int |
| + short bool |
| +} |
| + |
| +func (w *sponsoredFailingResponse) Header() http.Header { return w.header } |
| +func (w *sponsoredFailingResponse) WriteHeader(n int) { w.codes = append(w.codes, n) } |
| +func (w *sponsoredFailingResponse) Write(p []byte) (int, error) { |
| + w.writes++ |
| + if w.short { |
| + return len(p) - 1, nil |
| + } |
| + return 0, errors.New("private-owner@example.test roster-note body writer-text") |
| +} |
| +func TestSponsoredOperatorPostHeaderSafeLog(t *testing.T) { |
| + fx := newConsoleFixture(t) |
| + token := billingOperatorToken(t, fx, "operator@example.test", "log-failure") |
| + req := operatorRequest(t) |
| + req.Items[0].AccountID = 1 |
| + req.Items[0].Sqid = "bab" |
| + req.Items[0].ConfirmSystemAccount = true |
| + oldWriter, oldFlags, oldPrefix := log.Writer(), log.Flags(), log.Prefix() |
| + t.Cleanup(func() { log.SetOutput(oldWriter); log.SetFlags(oldFlags); log.SetPrefix(oldPrefix) }) |
| + log.SetFlags(0) |
| + log.SetPrefix("") |
| + for _, verb := range []string{"preview", "enroll", "status"} { |
| + for _, short := range []bool{false, true} { |
| + var captured bytes.Buffer |
| + log.SetOutput(&captured) |
| + method, path := "GET", "/api/billing/sponsored/"+verb |
| + if verb == "enroll" { |
| + method = "POST" |
| + path = "/api/billing/sponsored/enrollments" |
| + } else if verb == "status" { |
| + path = "/api/billing/sponsored/enrollments/" + req.OperationID |
| + } |
| + var body []byte |
| + if verb == "enroll" { |
| + body = operatorJSON(t, req) |
| + } |
| + r := httptest.NewRequest(method, path, bytes.NewReader(body)) |
| + r.Header.Set("Authorization", "Bearer "+token) |
| + r.Header.Set("Content-Type", "application/json") |
| + w := &sponsoredFailingResponse{header: make(http.Header), short: short} |
| + fx.srv.ServeHTTP(w, r) |
| + canonical := io.ErrClosedPipe |
| + if short { |
| + canonical = io.ErrShortWrite |
| + } |
| + want := "console: api billing operator: " + canonical.Error() + "\n" |
| + if captured.String() != want || !reflect.DeepEqual(w.codes, []int{200}) || w.writes != 1 { |
| + t.Errorf("%s short=%v log=%q codes=%v writes=%d", verb, short, captured.String(), w.codes, w.writes) |
| + } |
| + } |
| + } |
| +} |
diff --git a/internal/console/testdata/sponsored-operator-wire.json b/internal/console/testdata/sponsored-operator-wire.json| new file mode 100644 |
| index 000000000..d551c85ce |
| --- /dev/null |
| +++ b/internal/console/testdata/sponsored-operator-wire.json |
| @@ -0,0 +1,135 @@ |
| +{ |
| + "request": { |
| + "version": 1, |
| + "operation_id": "1111111111111111111111111111111111111111111111111111111111111111", |
| + "preview_digest": "2222222222222222222222222222222222222222222222222222222222222222", |
| + "decision_reference": "carloku-existing-2026-10-07", |
| + "coverage_acknowledged": true, |
| + "note": "reviewed fixture", |
| + "items": [ |
| + { |
| + "account_id": 2, |
| + "sqid": "bac", |
| + "choice": "enroll", |
| + "reason": "existing fixture", |
| + "confirm_system_account": false |
| + } |
| + ] |
| + }, |
| + "preview": { |
| + "version": 1, |
| + "preview": { |
| + "version": 1, |
| + "coverage": "console_known_accounts", |
| + "count": 1, |
| + "rows": [ |
| + { |
| + "facts": { |
| + "account_id": 2, |
| + "sqid": "bac", |
| + "name": "fixture", |
| + "stored_created_at": 10, |
| + "deleted_at": 0, |
| + "owners": [], |
| + "billing": { |
| + "present": false, |
| + "house": false, |
| + "live_mode": false, |
| + "customer_present": false, |
| + "subscription": "", |
| + "status": "" |
| + }, |
| + "checkout_present": false, |
| + "billing_retirement": false, |
| + "outbox": { |
| + "present": false, |
| + "live_mode": false, |
| + "serial_owed": false, |
| + "revision": 0, |
| + "published_revision": 0, |
| + "digest": "", |
| + "state": "" |
| + }, |
| + "open_billing_reviews": 0, |
| + "entitlement_reviews": 0 |
| + }, |
| + "system_account": false, |
| + "retired": false |
| + } |
| + ] |
| + }, |
| + "preview_digest": "4a6b00a1dba16e1bd0e686c17b4fb6cdf37d85a1b6a1b81a511e95bafb1dbfa0", |
| + "observed_at": 1791921600 |
| + }, |
| + "enrollment": { |
| + "version": 1, |
| + "historical_only": true, |
| + "request_digest": "3333333333333333333333333333333333333333333333333333333333333333", |
| + "receipt_digest": "4444444444444444444444444444444444444444444444444444444444444444", |
| + "historical": { |
| + "version": 1, |
| + "actor": "operator@example.test", |
| + "request": { |
| + "version": 1, |
| + "operation_id": "1111111111111111111111111111111111111111111111111111111111111111", |
| + "preview_digest": "2222222222222222222222222222222222222222222222222222222222222222", |
| + "decision_reference": "carloku-existing-2026-10-07", |
| + "coverage_acknowledged": true, |
| + "note": "reviewed fixture", |
| + "items": [ |
| + { |
| + "account_id": 2, |
| + "sqid": "bac", |
| + "choice": "enroll", |
| + "reason": "existing fixture", |
| + "confirm_system_account": false |
| + } |
| + ] |
| + }, |
| + "recorded_at": 1791921600, |
| + "rows": [ |
| + { |
| + "account_id": 2, |
| + "sqid": "bac", |
| + "outcome": "added", |
| + "facts": { |
| + "account_id": 2, |
| + "sqid": "bac", |
| + "name": "fixture", |
| + "stored_created_at": 10, |
| + "deleted_at": 0, |
| + "owners": [], |
| + "billing": { |
| + "present": false, |
| + "house": false, |
| + "live_mode": false, |
| + "customer_present": false, |
| + "subscription": "", |
| + "status": "" |
| + }, |
| + "checkout_present": false, |
| + "billing_retirement": false, |
| + "outbox": { |
| + "present": false, |
| + "live_mode": false, |
| + "serial_owed": false, |
| + "revision": 0, |
| + "published_revision": 0, |
| + "digest": "", |
| + "state": "" |
| + }, |
| + "open_billing_reviews": 0, |
| + "entitlement_reviews": 0 |
| + }, |
| + "first_enrollment_operation_id": "1111111111111111111111111111111111111111111111111111111111111111" |
| + } |
| + ] |
| + }, |
| + "replayed": false |
| + }, |
| + "paths": { |
| + "preview": "/api/billing/sponsored/preview", |
| + "enroll": "/api/billing/sponsored/enrollments", |
| + "status": "/api/billing/sponsored/enrollments/1111111111111111111111111111111111111111111111111111111111111111" |
| + } |
| +} |