// GET /relay/balance — peek at the caller's credit balance + tier WITHOUT // charging anything. Recap clients call this to populate the // "N credits remaining · Tier: X" banner. // // Two auth surfaces (see identity.js): // - cloud: X-Recap-User-Id + X-Recap-Operator-Key → user: pool, // tier is the relay's stored (operator-set) value. // - license: X-Recap-Install-Id (+ optional Bearer license) → legacy // license/install pool, tier from the license. // // Returns the standard envelope (result=null, credit_charged=0). No row // mutation beyond keeping tier_snapshot synced on the license path. import express from "express"; import { resolveIdentity, identityTier } from "../identity.js"; import { getOrCreateRow, applyTierPromotion } from "../credits.js"; import { envelope, errorEnvelope } from "./envelope.js"; export function balanceRouter() { const router = express.Router(); router.get("/balance", async (req, res) => { let identity; try { identity = await resolveIdentity(req); } catch (err) { const e = await errorEnvelope({ error: err?.message || "auth_error", statusHint: err?.status || 401, }); return res.status(e.statusHint || 401).json(e.body); } if (identity.kind === "license" && !identity.installId) { const e = await errorEnvelope({ error: "missing X-Recap-Install-Id header", statusHint: 400, }); return res.status(400).json(e.body); } const row = await getOrCreateRow({ creditKey: identity.creditKey, installId: identity.installId, license: identity.license, }); // License path: fire the Core→paid promotion bookkeeping (this is // typically the FIRST relay call after a license activation) and keep // tier_snapshot synced to the license. Cloud path: the tier is the // relay's stored, operator-set value — leave it untouched. if (identity.kind === "license") { const tier = identity.license.tier; const promoted = await applyTierPromotion(row, tier); if (!promoted) row.tier_snapshot = tier; } const tier = identityTier(identity, row); const body = await envelope({ result: null, creditKey: identity.creditKey, installId: identity.installId, license: identity.license, tier, creditCharged: 0, }); res.json(body); }); return router; }