# Design: cp9 Billing Persona

Issue: <https://github.com/tidbcloud/controlplane9/issues/254>

Dogfood design:
[`20260527-design-cp9-billing-persona-dogfood.md`](./20260527-design-cp9-billing-persona-dogfood.md)

Sibling Account design:
[`20260527-design-cp9-account-persona.md`](./20260527-design-cp9-account-persona.md)

This document defines only the Billing persona. Account is intentionally split
into its own design and dogfood contract because Billing and Account have
different owner repos, data stores, gateway boundaries, and acceptance proofs.

## Current cp9 Baseline

The current active baseline is the Premium family:

- `premium-full`: real CP plus real Premium dataplane proof.

Older material may say Premium Reserved. That active persona has been removed;
Billing implementation should reuse current Premium-family mechanics and
`skills/get-started/SKILL.md`, not resurrect `premium-reserved`.

## Decisions

1. Billing and Account are separate runnable personas. There is no combined
   `billing-account` acceptance target.
2. Billing acceptance is product dogfood, not process health. A Billing
   developer must be able to produce and inspect a Billing-owned artifact.
3. Account/Auth may be represented by a contract-accurate local fake only for
   Billing workflows that do not require real identity mutation.
4. External payment, marketplace, Salesforce, SQS/eventbus, and object-storage
   dependencies are local deterministic fakes for this local gate. Real
   provider sandboxes are later certification lanes.
5. Staging may provide read-only seed/config reference data. It is not a
   runtime success path.
6. Persona inputs must stay data-driven: example config, app-delivery envs,
   seed SQL, service config, and upstream source fixes. Do not add hidden
   persona-specific Go overlays.

## Product Scope

The Billing persona focuses on `tidbcloud/billing` source services and the
Billing product flows documented in the Billing research input:

- public Billing OpenAPI paths such as `/v1beta1/bills`,
  `/v1beta1/billsDetails`, and `/v1beta1/billsCostExplorer`;
- partner/MSP Billing paths where selected by the first accepted workflow;
- inner Billing paths such as `/inner/billing/orgs/{org_id}/measures`,
  `/inner/billing/units`, `/inner/billing/sku_groups`, and
  `/inner/billing/ar_tables/trigger`;
- real Billing runtimes `billing2`, `billing2-api`, and `billing2-worker`.

## Runtime Shape

Phase 0 now has three authoritative input families:

- Billing repo deploy: `tidbcloud/billing/deploy/aws/index.ts` and the Pulumi
  stack files under `tidbcloud/billing/deploy/aws/`;
- app-delivery staging:
  `tidbcloud/app-delivery/staging/billing-svc/envs/staging` plus
  `tidbcloud/app-delivery/staging/billing-svc/base`;
- staging database seed input from the `staging-base-us-west-2` Kubernetes
  context using `cp9 db dump-init`.

These sources are evidence for local contract design. They are not permission
to run cp9 against staging RDS, staging queues, staging Salesforce, or provider
sandboxes.

Expected source services after current Billing repo and app-delivery review:

| Service | Source entrypoint | Staging command | Runtime discriminator | Role |
|---|---|---|---|---|
| `billing2` | `tidbcloud/billing/cmd/server` | `/usr/local/bin/server` | `CONTAINER_APP_NAME=billing2` | Metering/ingestion-heavy runtime; starts SQS measure consumption only for this container app name |
| `billing2-api` | `tidbcloud/billing/cmd/server` | `/usr/local/bin/server` | `CONTAINER_APP_NAME=billing2-api` | Query/export/control API runtime |
| `billing2-worker` | `tidbcloud/billing/cmd/worker` | `/usr/local/bin/worker` | `CONTAINER_APP_NAME=billing2-worker` | Periodic/offline convergence jobs and DB migration runner |

The staging app-delivery shape mounts config at `/etc/billing`, mounts the
`billing-secrets-v2` secret at `/etc/billing_secrets`, injects secret values
through `envFrom`, and exposes HTTP on port `18888`. The local persona must keep
that contract shape while replacing secret and external-provider values with
local deterministic inputs.

## Data And Dependency Inventory

Billing Phase 0 must produce a concrete inventory before persona graph code:

| Dependency | First cp9 behavior | Evidence to collect |
|---|---|---|
| MySQL/Aurora | Billing-specific local DB seeded from reviewed SQL; both writable and readonly config point local | table list, migration behavior from `cmd/worker`, and sanitized init SQL from `cp9 db dump-init` in `staging-base-us-west-2` |
| Redis | local Redis or local cache adapter only if the selected flow uses it | keys, DB index, TTL/eviction expectations |
| SQS/eventbus | deterministic local queue or recorder; never staging queue URLs | measure message schema, queue retry/dedupe behavior, eventbus startup behavior |
| S3/object storage | disabled for the first workflow unless bills-detail export is selected; otherwise local file-backed artifact sink | bucket/key shape and generated artifact proof |
| Payment provider | local fake adapter with visible event log; defaults disabled unless selected workflow needs payment | request/response payloads |
| Marketplace provider | local fake adapter with deterministic SKU/package mapping; defaults disabled unless selected workflow needs marketplace | provider payload samples |
| Salesforce/CRM | disabled sink or local fake with visible log; never sandbox as success path | emitted event shape |
| Account/Auth | contract-accurate fake for Billing calls unless real Account is part of the Billing proof | RPC/HTTP contract and positive/negative behavior |
| API-gateway | include only if the first Billing workflow proves a public Billing route through Kong | route and plugin list from `api-gateway/deploy/configs/formal.yaml` |

Billing must not silently call staging RDS, staging Redis, staging SQS, staging
Salesforce, or staging provider sandboxes during the cp9 success path.

Do not paste or commit database passwords, ExternalSecret payloads, Stripe
keys, marketplace credentials, Salesforce credentials, or the full staging
database URI. The only committed seed artifact may be sanitized SQL or a
documented generator that can rebuild sanitized local seed data.

## First Workflow Candidate

The first Billing acceptance workflow should be selected in Phase 0, before
persona graph implementation. Current evidence favors this tracer:

1. seed local Billing DB with sanitized billing config, unit, package/payment
   mock, and cost-explorer seed rows derived from Billing tests and the staging
   dump;
2. drive a usage fact through the inner measure route
   `POST /inner/billing/tenants/{tenant_id}/measures` or
   `POST /inner/billing/orgs/{org_id}/measures`;
3. prove real Billing code materializes a cost artifact;
4. query `POST /v1beta1/tenants/:tenant_id/billsCostExplorer` or
   `GET /v1beta1/tenants/:tenant_id/billsDetails/:billedMonth`;
5. prove one negative case such as invalid unit/SKU or missing Billing config.

If Phase 0 shows the measure route requires a broader external queue path, the
fallback is a seeded cost-explorer read proof. That fallback is only a preflight
slice, not enough to close the Billing persona.

## Acceptance Matrix

The Billing persona is accepted when a developer can:

1. start cp9 with `cp9.billing.example.yaml` from clean repo heads;
2. see real Billing source services healthy through Tilt;
3. drive a local TiDB Cloud usage/business fact into Billing;
4. observe real Billing code consuming that fact;
5. query a concrete Billing artifact, such as usage, invoice, credit, package,
   cost-explorer, AR, or charge-calculation output;
6. prove a negative/edge case for the selected workflow, such as missing
   Billing config, invalid SKU, exhausted credit, or permission denial;
7. hot reload a Billing source edit without rebuilding the whole cp9 session;
8. run `cp9 destroy` and direct zero-survivor checks when the run creates AWS
   resources.

Health checks, render success, or a fake Billing service are not accepted.

## Phases

### Phase 0: Billing Contract Inventory

Deliver:

- current Billing entrypoints and process flags;
- app-delivery service/env inputs for `billing2`, `billing2-api`, and
  `billing2-worker`, including `CONTAINER_APP_NAME`, config mount, secret mount,
  and port `18888`;
- Billing repo deploy config requirements from `deploy/aws/index.ts`;
- required Billing config files and environment variables;
- database schema/migration source and seed SQL plan from
  `staging-base-us-west-2` plus `cp9 db dump-init`;
- external-provider call list and local fake/recorder shape;
- one selected first acceptance workflow with concrete commands and expected
  output fields;
- decision on whether API-gateway is required for that first workflow.

### Phase 1: Billing Persona Graph

Deliver:

- `cp9.billing.example.yaml`;
- Billing app-delivery env/base inputs in cp9 or a reviewed source location;
- local DB/cache/queue/object-store/fake adapter inputs;
- render/plan tests that prove the service graph;
- negative tests that forbid hidden staging endpoints and permissive fakes;
- get-started doc updates with concrete commands.

### Phase 2: Billing Runtime Proof

Deliver at least two cold proofs with:

- cp9 SHA and Billing repo SHA;
- exact `cp9.yaml`;
- Tilt health;
- accepted Billing workflow command transcript;
- produced Billing artifact and expected fields;
- fake adapter logs for external dependencies;
- hot reload evidence;
- cleanup evidence when cloud resources were created.

### Phase 3: External Certification Follow-Up

After local Billing passes, file separate follow-ups for real payment,
marketplace, Salesforce, and queue/object-store sandbox certification. These are
not prerequisites for closing the local Billing persona.

## Done Definition

Billing is done when the local Billing product workflow passes repeatedly from
clean heads, the dogfood contract has concrete commands, source changes are
data/config driven, external dependencies are explicit local fakes, and cleanup
evidence is attached to the implementation PRs.
