Skip to Content
InternalDocsOperationsP9 A Invoice Activation Runbook

P9 A Invoice Activation Runbook

Source: docs/operations/p9-a-invoice-activation-runbook.md

# P9-A Invoice Activation Runbook Operational runbook for first-customer gain-share invoice activation in staging. ## Scope - Validate billing readiness for one tenant. - Generate/cancel draft invoices safely for validation. - Optionally seed temporary `RECOVERED` value events when staging has no billable data. Core script: - `apps/api/scripts/invoice-dry-run.ts` - wrapper: `scripts/run-invoice-dry-run.sh` ## Prerequisites - `DATABASE_URL` for staging DB. - `pnpm` installed locally. - Tenant UUID for the target customer. Find tenant UUID: ```bash DATABASE_URL='<staging-db-url>' \ psql "$DATABASE_URL" -c "SELECT id, name, slug, status FROM app.tenants ORDER BY name;" ``` ## Step 1: Read-only Dry Run Run this first. It does not create invoices. ```bash DATABASE_URL='<staging-db-url>' \ scripts/run-invoice-dry-run.sh \ --tenant-id='<tenant-uuid>' \ --period-start=2026-02 \ --period-end=2026-03 \ --verbose ``` Expected pass criteria: - tenant exists and is active - billable `RECOVERED` events exist in period - no overlapping draft/issued invoices - gain-share total is computed ## Step 2: Execute + Cleanup Validation This validates draft generation and amount reconciliation, then removes the draft. ```bash DATABASE_URL='<staging-db-url>' \ scripts/run-invoice-dry-run.sh \ --tenant-id='<tenant-uuid>' \ --period-start=2026-02 \ --period-end=2026-03 \ --execute \ --cleanup \ --verbose ``` Expected pass criteria: - draft invoice generated - draft amount matches expected gain-share - draft cleanup succeeds ## Step 3: If No Billable Events Exist If dry-run reports no unbilled billable `RECOVERED` events, seed temporary rows. > **Note:** Validate the UUID format before substitution — the wrapper script > (`scripts/run-invoice-dry-run.sh`) does this automatically, but the raw `psql` > path below relies on the `::uuid` cast as a server-side guard. ```bash DATABASE_URL='<staging-db-url>' TENANT_ID='<tenant-uuid>' psql "$DATABASE_URL" <<SQL INSERT INTO app.value_events ( id, tenant_id, category, amount, currency, source_entity_type, source_entity_id, finding_type, reason_code, idempotency_key, billable, billed_at, occurred_at, created_at ) VALUES (gen_random_uuid(), '$TENANT_ID'::uuid, 'RECOVERED', 1200.50, 'USD', 'SHIP_FINDING', gen_random_uuid(), 'DUPLICATE_CHARGE', 'DRY_RUN', 'dryrun-2026-02-001', true, NULL, '2026-02-10T12:00:00Z'::timestamptz, now()), (gen_random_uuid(), '$TENANT_ID'::uuid, 'RECOVERED', 875.25, 'USD', 'SHIP_FINDING', gen_random_uuid(), 'ADDRESS_CORRECTION', 'DRY_RUN', 'dryrun-2026-02-002', true, NULL, '2026-02-14T12:00:00Z'::timestamptz, now()), (gen_random_uuid(), '$TENANT_ID'::uuid, 'RECOVERED', 390.00, 'USD', 'SIMA_RESULT', gen_random_uuid(), 'CLASSIFICATION_ERROR', 'DRY_RUN', 'dryrun-2026-02-003', true, NULL, '2026-02-18T12:00:00Z'::timestamptz, now()); SQL ``` Re-run Steps 1 and 2. Then clean up seed rows: ```bash DATABASE_URL='<staging-db-url>' \ psql "$DATABASE_URL" -c "DELETE FROM app.value_events WHERE idempotency_key LIKE 'dryrun-2026-02-%';" ``` ## Step 4: Manual Invoice Lifecycle (optional) Use this when validating issue/paid transitions beyond draft checks. 1. Generate draft through admin billing endpoint. 2. Issue invoice (`POST /api/admin/billing/invoices/{id}/issue`) with body `{ "tenantId": "..." }`. 3. Mark paid (`POST /api/admin/billing/invoices/{id}/mark-paid`) with body `{ "tenantId": "..." }`. 4. Verify status transitions in `app.billing_invoices`. ## Evidence to Capture Store these in session notes or PR comments: - tenant ID + period used - dry-run output summary (checks passed/failed) - generated invoice number (if execute mode used) - any seeded value-event keys and cleanup confirmation ## Exit Criteria Mapping (P9-A) P9-A is operationally ready when all are true: - read-only dry-run passes on staging for target tenant - execute+cleanup dry-run passes - gain-share math is validated against expected totals - no residual dry-run seed data remains