Story 2174 - pramaan-admin

Staff can grant or correct a firm’s AI credits from the admin billing page

This story adds a staff-only /billing page that grants or claws back a firm’s credits (RFD-BILL-001 §5.8, goal G7). The money math, cap enforcement, append-only ledger write, and exactly-once idempotency all live behind the merged S3 route in pramaan-functions (pramaan-docs ADR-158). The admin only proxies it through src/lib/gateway.ts.

Flow

The operator picks a firm, enters a signed credit amount (positive grants, negative claws back), chooses the bucket (top-up/bonus or allowance), and writes a mandatory audit reason. Submitting calls the recordCreditAdjustment server action, which posts to POST /admin/firms/{firmId}/billing/credits/adjustment via the gateway and parses the confirmed response with the generated contract in src/gen/.

Amounts are INR (1 credit = ₹1). On success the resulting balance card shows the firm’s server-confirmed total, allowance, and top-up buckets, and the adjustment is added to this session’s list. The write is server-confirmed only — no optimistic cache flip.

Idempotent submit

Each pending grant carries one caller-supplied adjustmentId (a UUID held in component state). A double-click reuses the same key, and the S3 writer’s partial unique index makes the second write a no-op (applied: false), so credits are never doubled. A fresh key is minted only after a write lands.

Acceptance evidence

Criterion Evidence State
Staff grant/correct (firm, delta, bucket, reason) via S3 src/lib/actions/credit-adjustment.ts + src/lib/billing/gateway.ts proxy the merged credits/adjustment route; contract parsed from src/gen/. done
Balance shown Resulting-balance card renders the server-confirmed totalCredits / allowanceCredits / topupCredits from the write response, in INR. done
Recent adjustments shown This session’s applied adjustments are listed. Full ledger history has no admin read route yet (see scope note). done
Idempotent submit Stable per-grant adjustmentId; backend enforces exactly-once. Submit disabled while in flight. done
Staff-only Page sits under the (admin) group gated by checkAdminMe; the S3 route is StaffDbDep. done
Local verify gate pnpm exec tsc --noEmit, pnpm run lint, and pnpm run test:unit pass locally. done

Scope note: pramaan-functions serves no admin read route for a firm’s credit balance or ledger history, so pre-submit balance and full history are not shown — the page shows the server-confirmed balance and adjustments from this session. Wiring a live balance/history read needs a functions read endpoint (follow-up), per ADR-031 single-repo executable scope.