Story-2544 - pramaan-admin - Billing surfaces

Rate cards and the firm page ledger replace flat admin pricing

Admin pricing moves from flat, code-adjacent config knobs to a versioned, append-only commercial model: staff publish immutable India/US Individual and Firm rate-card versions, record and reverse offline page grants against a firm's pool, and read staff-only unit economics — all backed by the new admin billing API from pramaan-functions PR#699.

Before

/pricing edited a flat, mutable PricingConfigView (AI markup, FX peg, GST, min top-up) and per-plan credit/storage fields directly. A separate /billing page let any staff member post a signed credit adjustment to a firm's wallet with no ledger read-back beyond the current session. Neither surface distinguished historical receipts from live config, and there was no unit economics view.

After

/pricing is now a rate-card editor: staff publish the next immutable version per (region, account type) with validated terms; nothing is ever edited in place. The Firm Billing tab shows the firm's page pool, its full immutable grant/reversal ledger, an offline-grant wizard (form → confirm → success) with a live projected pool increase, reversal-with-reason, and a staff-only monthly unit economics panel (revenue, Textract, inference, storage, contribution) with a matter/document/receipt-id drill-down — no legal content.

Deletion census

Old AI-markup, credit, top-up, and customer storage-price admin controls are removed per the Story-2544 acceptance criteria. The functions endpoints they called (/admin/billing/pricing-config, /admin/billing/plans, /admin/firms/{firmId}/billing/credits/adjustment) are untouched here — this PR is the admin UI only; retiring the backend routes is out of scope and tracked separately.

Removed Replaced by
src/components/pricing/pricing-view.tsx src/components/pricing/rate-card-editor.tsx
src/components/pricing/config-form.tsx react-hook-form + zod fields inlined in the rate-card editor
src/lib/actions/pricing.ts createRateCardVersion in src/lib/actions/billing.ts
src/app/(admin)/billing/page.tsx (global credit-adjustment page) Firm Billing tab: FirmPagePoolPanel + FirmUnitEconomicsPanel
src/components/billing/credit-adjustment-form.tsx (+ stories) PageGrantModal (offline grant wizard)
src/lib/actions/credit-adjustment.ts recordPageGrant / reversePageGrant in src/lib/actions/billing.ts
formatCredits() in src/lib/formatters.ts (orphaned) formatMicros / formatUsdMicros in src/lib/billing/micros.ts
Rail nav "Billing" top-level item Removed — page grants and unit economics live on Firms > Billing, not a duplicate top-level page. "Pricing" relabeled "Rate cards".

New surfaces

Wiring notes

  • Reads are fetched once, server-side, alongside the rest of the firm detail RSC (fetchFirmBillingExtras in src/lib/billing/firm-billing-extras.ts) and threaded down as props — no new client-side data fetch or /api/internal proxy route was needed.
  • Writes reuse the prior agent's server actions (recordPageGrant, reversePageGrant, createRateCardVersion in src/lib/actions/billing.ts), which already carry the ADR-124 business-action audit logging (success/denied/ failed outcomes). Per HR-6/component doctrine, admin writes are server-confirmed only: on success the client calls router.refresh(); there is no optimistic cache flip.
  • Client components never import a "use server" action file directly — Storybook's Vite bundler does not understand the "use server" transform and pulls the action's server-only dependencies (Clerk, the gateway) into the browser bundle, which broke every story that rendered the tree with a bare ReferenceError: __dirname is not defined. The fix mirrors the existing onResendInvite pattern: the RSC page imports the action and passes it down as a prop (onRecordGrant, onReverseGrant, onPublishRateCard); leaf components only import the action's types.
  • Non-staff denial: both the rate-card list fetch and the firm billing ledger/unit-economics fetch catch a 403 GatewayError and render the shared AccessRestricted primitive instead of the data view; every write already returns an audited "denied" outcome on 403 via the existing action logging.
  • pramaan-docs ADR-124: no new manual span. Every write already routes through the audited server actions built for Story-2544; this PR adds no new route, handler, queue, or job boundary.

Storybook coverage

Per the acceptance criteria (grant form, confirmation, success, reversal, validation, denied):

Verification