The Redis Stream `billing-events` + `worker` Railway role +
advisory-lock poller layered together didn't actually buy us reliability
— `debitFlux` swallowed XADD failures, leaving the door open to "balance
updated, ledger row never written". Collapse the whole thing back to:
`creditFlux` and `debitFlux` write `flux_transaction` ledger rows inline
within the same DB transaction that mutates `user_flux`, and `(user_id,
request_id)` remains the partial unique index that keeps retries safe.
Concrete changes:
- Inline ledger inserts in `BillingService.{debitFlux, creditFlux,
creditFluxFromStripeCheckout, creditFluxFromInvoice}`; drop `billingMq`
and `publishEvent` plumbing entirely.
- `routes/openai/v1` writes `llm_request_log` synchronously via the
existing `requestLogService`; the duplicate `llm-request-log.ts` service
module is removed.
- `bin/run-worker.ts`, `libs/mq/*`,
`services/billing/billing-events.ts`,
`services/billing/billing-consumer-handler.ts`, and matching tests are
deleted. CLI now exposes only `api`.
- `BILLING_EVENTS_*` env vars and the `DEFAULT_BILLING_EVENTS_STREAM`
helper are dropped; `docker-compose.yml` no longer ships a worker
service.
- `docs/ai-context/{workers-and-runtime, billing-architecture,
redis-boundaries-and-pubsub, data-model-and-state,
architecture-overview, README}.md`, `CLAUDE.md`, and the existing
verification docs are updated to describe the single-process synchronous
pipeline.
Tests: 29 files / 247 cases pass. Production deployments need to drop
the worker Railway service after this lands.
3.5 KiB
3.5 KiB
Admin Flux Grants — End-to-End Verification
用户路径 1:admin 同步发 grant → 余额到账
- 场景:admin 通过
POST /api/admin/flux-grants给一个邮箱发 100 FLUX,HTTP 同步返回granted数组 → 用户余额上升。 - 命令(占位,需要重新实测):
TOKEN=... # admin 用户 access token curl -s -X POST "http://localhost:3000/api/admin/flux-grants" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"description":"local verify","amount":100,"emails":["rbxin2003@gmail.com"]}' # 然后查余额 curl -s -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/v1/flux # 查 ledger curl -s -H "Authorization: Bearer $TOKEN" 'http://localhost:3000/api/v1/flux/history?limit=3' - 预期:
- HTTP 200,body 包含
result.granted: [{ email, userId, fluxTransactionId, balanceAfter }],result.failed: [],result.skipped: [] /api/v1/flux返回的flux比之前增加 100/api/v1/flux/history顶部一条type='promo'、description='local verify'、metadata.issuedByUserId= admin 的 userId
- HTTP 200,body 包含
- 实际输出:⏳ 待重新实测(架构刚从 batch 改成同步,旧 verification 已无效)。
- 环境:本地
pnpm -F @proj-airi/server dev,commit SHA 待补,ADMIN_EMAILS含 admin 邮箱且email_verified=true。 - 最后验证:⏳ 待补
用户路径 2:dry-run 预览邮箱列表
- 场景:admin 在真发之前用
?dryRun=true看 4 个 email(valid + 大小写变体重复 + 找不到)的解析结果。 - 命令:
curl -s -X POST 'http://localhost:3000/api/admin/flux-grants?dryRun=true' \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"description":"smoke","amount":100,"emails":["rbxin2003@gmail.com","RBXIN2003@gmail.com","ghost@nope.example","rbxin2003@gmail.com"]}' - 预期:HTTP 200,
{ preview: { willGrant: 1, willSkip: { notFound: 1, userDeleted: 0, duplicateInInput: 2 }, totalFluxToIssue: 100, ... } }。flux_transaction表无新增行。 - 实际输出:⏳ 待重新实测
- 环境:同上
- 最后验证:⏳ 待补
用户路径 3:未登录 / 非 admin / 未验证邮箱被挡住
- 场景:
adminGuard三种拒绝路径(401 无 session / 403 不在 allowlist / 403 邮箱未验证)。 - 命令:
curl -s -w "%{http_code}\n" -X POST http://localhost:3000/api/admin/flux-grants -d '{}' # 期望 401 - 实际:单元测试
admin-guard.test.ts覆盖完整三条路径 + case-insensitive 匹配。Live 401/403 端到端验证⏳ 待补。 - 最后验证:⏳ 待补(unit only)
已知缺口 / 未验证
- 整套 verification 都需要重跑:架构从
flux_grant_batch异步处理改成同步POST /api/admin/flux-grants后,旧的实测输出(含mq-stream日志、batch.status: completed等)全部失效。Iron Law 要求至少跑一次路径 1 + 2 替换 ⏳ 占位。 - 失败重试 + idempotencyKey:单测覆盖了"同 key 不同 recipient → 不同 requestId"这一逻辑,但没真跑过"故意打挂 DB 触发部分 failed → 用
idempotencyKey重发只补失败的"端到端。 emails上限 200 实际响应时间:估算 4–10s,没在生产 DB 上跑过。如果creditFlux单条 > 50ms(远端 Postgres + Redis),需要回头下调上限或加批处理优化。