summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorChia <Chia@93.nz>2026-08-06 15:58:57 +1200
committerChia <Chia@93.nz>2026-08-06 15:58:57 +1200
commit3f702084d20b3c3a3ea916f3110e99b22bda60b3 (patch)
tree517f76c51025ce1ee085ea4898c60f799e5c37ea /README.md
parent41e322c53d7b4b796eb377d0df9c29ecd10ba431 (diff)
feat: complete commercial developer workflowspublish-commercial-control-plane
Add tenant-safe usage observability, prepaid billing controls, API key lifecycle management, Embeddings metering, configurable billing alerts, and resilient provider health propagation. Harden Stripe failure handling, migrations, readiness, and the authenticated control-plane UI with end-to-end verification evidence.
Diffstat (limited to '')
-rw-r--r--README.md67
1 files changed, 48 insertions, 19 deletions
diff --git a/README.md b/README.md
index 45bf077..13ed4c0 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,19 @@
# AIGW
-AIGW 是一个轻量、无状态的 AI API 中转后端。当前阶段聚焦上游接入与 API 分发:提供 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 兼容入口,支持公开模型名映射、多上游路由、加权分流、故障转移、SSE 直通、客户密钥鉴权以及异步用量事件。
+AIGW 是一个轻量、无状态的 AI API 中转后端。当前阶段聚焦上游接入与 API 分发:提供 OpenAI Chat Completions、Responses、Embeddings 和 Anthropic Messages 兼容入口,支持公开模型名映射、多上游路由、加权分流、故障转移、SSE 直通、客户密钥鉴权以及持久化用量结算。
它借鉴了 ZenMux 的双协议、`provider/model` 模型命名、统一错误、请求 ID、路由和可观测性边界,但没有复制其业务实现。
## 当前能力
-- OpenAI:`POST /v1/chat/completions`、`POST /v1/responses`、`GET /v1/models`
+- OpenAI:`POST /v1/chat/completions`、`POST /v1/responses`、`POST /v1/embeddings`、`GET /v1/models`
- Anthropic:`POST /anthropic/v1/messages`、`GET /anthropic/v1/models`
- ZenMux 风格别名:所有入口同时提供 `/api/...` 路径
- 一个公开模型可配置多个同协议上游,低 `priority` 优先,同级按 `weight` 分流
- 请求可用 `model-id:provider-slug` 固定到模型目录公开的某个供应商;固定后不会回退到其他供应商
- 在 429、502、503、504 或连接失败时,于响应开始前自动尝试下一条路由
- 每条模型/供应商 route 记录真实请求的近期可用率和响应头延迟;连续 3 次可重试失败后熔断 30 秒,冷却期间路由自动绕行
+- 可选的认证 `/models` 主动探测在无客户流量时更新同一熔断器;默认关闭,不发推理请求、不产生 token 费用
- SSE 增量直通、主动断连传播、共享 HTTP/2 连接池
- `Authorization: Bearer` 和 `x-api-key` 客户鉴权
- 统一 JSON 错误、`X-AIGW-Request-ID`、Prometheus 文本指标
@@ -20,13 +21,13 @@ AIGW 是一个轻量、无状态的 AI API 中转后端。当前阶段聚焦上æ
- PostgreSQL 预付余额、请求额度冻结、实际 token 结算和不可变账本
- Stripe 托管 Checkout 充值、签名 Webhook 与事件/订单双重幂等
- Stripe 托管支付方式保存与低余额自动充值,off-session PaymentIntent 失败会暂停自动充值并提示客户处理
-- PostgreSQL Usage Ledger、月度项目汇总和单请求成本追溯
+- PostgreSQL Usage Ledger、稳定游标分页、TTFT 与 P50/P95 聚合、按 API Key 成本追溯
- 邮箱验证、邀请注册、密码重置、登录限流、可撤销设备会话和管理 API 审计日志
- TOTP(含一次性恢复码)与 WebAuthn Passkey 注册、二次验证和无密码登录
- 六种 RBAC 角色、租户数据隔离和 CSRF 防护
- 项目级 RPM、估算 TPM、并发限制和月度消费配额
-- API Key 级模型白名单、月度消费上限、过期时间、标签和最后使用时间
-- 无需登录的 `/admin/models` 模型与价格目录,支持搜索、协议/输入/开发者筛选、详情和 token 成本估算
+- API Key 级模型白名单、日/月消费上限、RPM/TPM、过期时间、标签、停用和原子轮换
+- 无需登录的 `/admin/models` 模型与价格目录,支持搜索、协议/输入/开发者筛选;每个模型有服务端渲染的 canonical 详情 URL、价格和协议代码示例
## 快速运行
@@ -90,6 +91,15 @@ curl http://127.0.0.1:18081/v1/responses \
-d '{"model":"openai/gpt-5.5","input":"Reply with OK.","max_output_tokens":32}'
```
+OpenAI Embeddings 同样使用独立 `wire_api: "embeddings"` 路由,并按上游返回的输入 token 从预付余额扣费:
+
+```bash
+curl http://127.0.0.1:8080/v1/embeddings \
+ -H 'Authorization: Bearer sk-local-change-me' \
+ -H 'Content-Type: application/json' \
+ -d '{"model":"openai/text-embedding-3-small","input":["text to embed"]}'
+```
+
Anthropic 调用示例:
```bash
@@ -102,7 +112,7 @@ curl http://127.0.0.1:8080/anthropic/v1/messages \
## 配置路由
-每条 route 把一个对外模型映射到一个上游模型。供应商的 `protocol` 表示 OpenAI/Anthropic 协议族,`wire_api` 表示实际调用 `chat_completions`、`responses` 或 `messages`。静态配置可为供应商设置唯一的公开 `slug`;省略时使用符合相同格式的 `id`:
+每条 route 把一个对外模型映射到一个上游模型。供应商的 `protocol` 表示 OpenAI/Anthropic 协议族,`wire_api` 表示实际调用 `chat_completions`、`responses`、`embeddings` 或 `messages`。静态配置可为供应商设置唯一的公开 `slug`;省略时使用符合相同格式的 `id`:
```json
{
@@ -118,7 +128,7 @@ curl http://127.0.0.1:8080/anthropic/v1/messages \
这里 A/B 承担约 80/20 的首选流量,C 只作为更低优先级的后备。所有上游密钥仅通过 `api_key_env` 指向的环境变量读取。客户密钥从 `AIGW_API_KEYS` JSON 数组读取,进程内只保存 SHA-256 摘要。
-默认请求只传基础模型 ID,由网关自动进行权重分流、熔断绕行和故障转移。需要复现特定供应商行为时,可以先从 `GET /v1/models` 的 `providers` 字段读取公开 slug,再把它附加到模型名:
+默认请求只传基础模型 ID,由网关自动进行权重分流、健康择优、熔断绕行和故障转移。同优先级 route 在积累至少 5 个可用性样本或 3 个 TTFT 样本后,会优先选择近期成功率和 TTFT 更好的供应商;每 20 次保留一次原权重探索,未测量的新 route 也不会被饿死。需要复现特定供应商行为时,可以先从 `GET /v1/models` 的 `providers` 字段读取公开 slug,再把它附加到模型名:
```bash
curl http://127.0.0.1:8080/v1/chat/completions \
@@ -129,16 +139,18 @@ curl http://127.0.0.1:8080/v1/chat/completions \
指定供应商时,路由器只会尝试该 slug 下与当前 API 协议兼容的 route,不会静默切换到其他供应商。该供应商不存在时返回 `404 provider_not_found`,正在熔断冷却时返回 `503 provider_unavailable`。API Key 模型白名单仍按基础模型 ID 校验,用量与扣费也归集到基础模型,避免供应商后缀拆分账单。
+主动探测通过 `AIGW_PROVIDER_ACTIVE_PROBES_ENABLED=true` 显式开启;间隔和超时由 `provider_health.probe_interval_seconds` 与 `probe_timeout_seconds` 配置。探测只调用每个供应商一次认证 `GET {base_url}/models`,要求 2xx、`application/json` 和标准 `data` 数组,再把结果投影到该供应商的全部 route。没有兼容 Models API 的供应商应保持关闭,避免被误熔断。指标为 `aigw_provider_probes_total`、`aigw_provider_probe_failures_total`、`aigw_upstream_ttft_ms_count` 和 `aigw_upstream_ttft_ms_sum`。
+
## 生产边界
当前版本可以作为带预付计费的数据面,并已把控制面、账务和运营入口拆开:
- 控制面计费模式会把 UsageEvent、冻结记录和扣费流水同步、幂等写入 PostgreSQL;响应结束只负责把结算事件投递到持久化队列,worker 负责重试、过期冻结恢复和本地 JSONL spool 补偿。
- 客户密钥和模型目录在控制面模式下从 PostgreSQL 载入到原子内存快照;Redis 只是可选的变更广播加速层,故障时通过 PostgreSQL generation 轮询收敛。
-- 当前只把 Chat Completions、Responses、Anthropic Messages 入口发给相同 wire API 的兼容上游,不做隐式跨协议转换。
+- 当前只把 Chat Completions、Responses、Embeddings、Anthropic Messages 入口发给相同 wire API 的兼容上游,不做隐式跨协议转换。
- 自动故障转移可能在极少数网络错误下造成上游重复执行。正式计费时需要上游幂等能力、请求去重策略和重复成本对账。
-- 供应商健康状态是每个网关实例基于真实流量维护的 100 次滑动窗口,不是主动探测、全局 SLA 或首 token 延迟;新 route 在首个请求前显示为未采样。熔断状态不写入 PG/Redis,实例重启后重新学习。
-- 计费上游必须返回 usage。OpenAI 流请求会强制请求 `stream_options.include_usage=true`;成功响应缺少 usage 时不会按零费用放行,也不会猜测 token,而是把授权保持为 `metering_failed`、触发 readiness/Prometheus 告警,直到运营切断或修复该上游。显式的零 token usage 仍可正常结算。
+- 供应商健康状态使用 100 次滑动窗口,默认只使用本机真实流量;显式开启后还可包含认证 Models API 主动探测。控制面部署默认通过有界、带 TTL 的 Redis Stream 共享近期 route 结果和 TTFT,新实例会回放仍在 TTL 内的样本,再继续实时消费。推理线程只做非阻塞入队,Redis 故障时立即保留本机窗口并自动重连,不影响 readiness、计费或请求处理。该窗口用于选路而非持久 SLA,长期统计仍以 PostgreSQL Usage Ledger 为准。
+- 计费上游必须返回 usage。OpenAI 流请求会强制请求 `stream_options.include_usage=true`;成功响应缺少 usage 时不会按零费用放行,也不会猜测 token,而是把授权保持为 `metering_failed`、触发 readiness/Prometheus 告警,直到运营修复或通过带原因和 RBAC/审计证据的 reservation release API 处置。释放后事件保留 `usage_reported=false` 并标记 `released_unmetered`,钱包余额不变,只有冻结额返回可用余额。显式的零 token usage 仍可正常结算。
- 默认生产配置使用独立的推理、管理、支付/邮件 Webhook 和 operations listener。`/healthz` 只表示进程存活,`/readyz` 会检查 PG、快照、Stripe 对账、Webhook、退款、未收款、缺失用量、结算队列和邮件积压;Redis 是可降级传播层。`/metrics` 应仅在内网暴露;公网 TLS、WAF 和连接层限速应放在负载均衡器或边缘代理。
## PostgreSQL + Redis 控制面
@@ -165,17 +177,17 @@ curl http://127.0.0.1:8080/v1/chat/completions \
然后打开 `http://127.0.0.1:8081/admin/`,本地邮件在 `http://127.0.0.1:8025/` 查看;健康检查在 `http://127.0.0.1:9090/readyz`。开发者控制台的调用示例使用 `AIGW_INFERENCE_PUBLIC_URL` 作为网关地址,分离 listener 时不要把它误配成管理地址。Stripe CLI Webhook 转发到 `http://127.0.0.1:8082/billing/stripe/webhook`。启用注册时,新账号必须通过一次性邮件链接验证;团队成员由管理员邀请并自行设置密码。平台管理员也可以从权限为 `0600` 的环境文件读取 `AIGW_ADMIN_TOKEN`,将其作为 bootstrap/break-glass 凭证。日常操作使用邮箱/密码、TOTP 或 Passkey,服务端创建可逐设备撤销的数据库会话,所有写请求需要 CSRF token。第一套资源的创建顺序是:Tenant → Project → API key → Provider → Model route。客户 API Key 明文只在创建成功时返回一次;团队成员使用自己的账号,不共享管理员令牌。
-账号邮件先在 PostgreSQL outbox 中加密持久化,再由后台 worker 发送;SMTP 临时不可用不会回滚注册、邀请或重置请求。普通失败指数退避,10 次后进入 dead-letter。生产环境把 `AIGW_PUBLIC_URL` 设置为 HTTPS 控制台 URL,把 `AIGW_SMTP_ADDRESS`、`AIGW_SMTP_FROM_ADDRESS`、`AIGW_SMTP_USERNAME`、`AIGW_SMTP_PASSWORD` 和 `AIGW_MAIL_FEEDBACK_SECRET` 通过密钥管理服务注入,并将 `admin.mail.tls_mode` 改为 `starttls` 或 `tls`。邮件供应商的 bounce/complaint 事件应由边缘适配器规范化后签名发送到 `/mail/feedback`;永久退信和投诉地址会进入抑制表。worker 会按租户保存的阈值、按日幂等发送低余额通知,并发送异常消费通知;未配置租户继续使用 `admin.mail.low_balance_micros` 的全局默认值。域名 DNS 仍必须在邮件供应商处配置 SPF、DKIM 和 DMARC,这不是应用代码可以代替的步骤。WebAuthn 的 `AIGW_WEBAUTHN_RP_ID` 必须是控制台有效域名,`AIGW_WEBAUTHN_ORIGINS` 是逗号分隔的 HTTPS origin。
+账号邮件先在 PostgreSQL outbox 中加密持久化,再由后台 worker 发送;SMTP 临时不可用不会回滚注册、邀请或重置请求。普通失败指数退避,10 次后进入 dead-letter。生产环境把 `AIGW_PUBLIC_URL` 设置为 HTTPS 控制台 URL,把 `AIGW_SMTP_ADDRESS`、`AIGW_SMTP_FROM_ADDRESS`、`AIGW_SMTP_USERNAME`、`AIGW_SMTP_PASSWORD` 和 `AIGW_MAIL_FEEDBACK_SECRET` 通过密钥管理服务注入,并将 `admin.mail.tls_mode` 改为 `starttls` 或 `tls`。邮件供应商的 bounce/complaint 事件应由边缘适配器规范化后签名发送到 `/mail/feedback`;永久退信和投诉地址会进入抑制表。Billing 角色可以独立启停低余额和异常消费提醒,并设置低余额金额、相对前七日平均消费的倍数和最低异常金额;设置按租户保存在 PostgreSQL,并按日幂等发送给已验证的 tenant admin/billing 成员。未配置租户继承 `admin.mail.low_balance_micros`、`admin.mail.spend_anomaly_multiplier` 和 `admin.mail.spend_anomaly_min_micros` 的部署默认值。域名 DNS 仍必须在邮件供应商处配置 SPF、DKIM 和 DMARC,这不是应用代码可以代替的步骤。WebAuthn 的 `AIGW_WEBAUTHN_RP_ID` 必须是控制台有效域名,`AIGW_WEBAUTHN_ORIGINS` 是逗号分隔的 HTTPS origin。
-租户登录后的 Quickstart 首屏会显示充值、密钥、可用模型和首次成功请求四步状态;模型目录按协议、输入模态和开发者筛选,并可按发布时间、名称、输入/输出价格和上下文长度排序,再生成使用 `AIGW_INFERENCE_PUBLIC_URL` 的 cURL、Python 和 Node.js 示例。新工作区可以在 Quickstart 直接为默认项目和当前模型创建 starter key,明文仍只显示一次,同时自动放入当前页面内存中的 Playground。连接面板集中列出 OpenAI/Anthropic SDK Base URL、Chat Completions、Responses、Messages、Models 端点,并可复制不含真实密钥的环境变量模板。每个模型都有客户安全的详情视图,展示协议、模态、上下文、最大输出、能力、生命周期和别名,并按当前价格版本实时估算输入、输出、缓存读取和缓存写入成本;逐供应商运行状态只暴露名称、协议、近期可用率、响应头延迟、样本量和熔断恢复时间,不暴露地址、凭证或上游模型。完全熔断的模型不能从快捷入口发起请求。API Playground 会直接从浏览器调用该推理地址,使用当前客户 API Key 经过完整鉴权、路由、余额冻结/结算和 Usage 链路;它只把 Key 保留在当前页面内存,刷新或退出立即清除。失败时页面保留结构化响应和请求 ID,并把权限、余额、模型、限流或上游错误引导到对应控制台页面。分离 listener 时推理服务只允许 `AIGW_PUBLIC_URL` 的精确 Origin,并且不带管理 Cookie。租户可把目录内可用模型保存为默认模型和不同的 fallback 模型,Quickstart 会立即采用该默认值;billing 成员可以单独启停低余额邮件并设置阈值。
+租户登录后的 Quickstart 首屏会显示充值、密钥、可用模型和首次成功请求四步状态;模型目录按协议、输入模态和开发者筛选,并可按发布时间、名称、输入/输出价格和上下文长度排序,再生成使用 `AIGW_INFERENCE_PUBLIC_URL` 的 cURL、Python 和 Node.js 示例。新工作区可以在 Quickstart 直接为默认项目和当前模型创建 starter key,明文仍只显示一次,同时自动放入当前页面内存中的 Playground。连接面板集中列出 OpenAI/Anthropic SDK Base URL、Chat Completions、Responses、Messages、Models 端点,并可复制不含真实密钥的环境变量模板。每个模型都有客户安全的详情视图,展示协议、模态、上下文、最大输出、能力、生命周期和别名,并按当前价格版本实时估算输入、输出、缓存读取和缓存写入成本;逐供应商运行状态只暴露名称、协议、近期可用率、响应头延迟、样本量和熔断恢复时间,不暴露地址、凭证或上游模型。完全熔断的模型不能从快捷入口发起请求。API Playground 会直接从浏览器调用该推理地址,使用当前客户 API Key 经过完整鉴权、路由、余额冻结/结算和 Usage 链路;它只把 Key 保留在当前页面内存,刷新或退出立即清除。失败时页面保留结构化响应和请求 ID,并把权限、余额、模型、限流或上游错误引导到对应控制台页面。分离 listener 时推理服务只允许 `AIGW_PUBLIC_URL` 的精确 Origin,并且不带管理 Cookie。租户可把目录内可用模型保存为默认模型和不同的 fallback 模型,Quickstart 会立即采用该默认值;billing 成员可以单独配置低余额与异常消费邮件策略。
-API keys 页面会展示每个 Key 当月已结算费用、待结算冻结、请求数、月度上限、剩余额度、过期时间和最后使用时间;月度统计直接读取 Usage Ledger 与 billing reservation,不在浏览器侧计算。
+API keys 页面会展示每个 Key 今日与当月已结算费用、待结算冻结、请求数、日/月上限、剩余额度、RPM/TPM、过期时间和最后使用时间;统计直接读取 Usage Ledger 与 billing reservation,不在浏览器侧计算。Key 可以即时停用/启用、撤销或原子轮换;轮换会在一个 PostgreSQL 事务内创建继承原策略的新 Key 并撤销旧 Key,新明文仍只返回一次。即使 Redis 广播暂时失败,本机快照也会先应用并由 PG 轮询让其他实例最终收敛。
控制台角色分为:`platform_admin`、`platform_viewer`、`tenant_admin`、`tenant_billing`、`tenant_developer`、`tenant_viewer`。租户角色的查询条件在服务端下推到 PostgreSQL,不能读取其他租户的项目、密钥、余额、Usage 或审计事件;供应商凭证和路由管理只对平台角色开放。默认模型与财务告警使用独立写权限:developer 不能修改低余额阈值,billing 成员不能修改 API 默认模型。
## Usage、配额与限流
-每个完成上游尝试的请求都会按 `request_id` 幂等写入 `usage_events`,并更新 `usage_monthly_rollups`。Usage 持久化独立于预付费冻结记录,因此关闭计费也不会关闭用量账本。Admin WebUI 提供本月汇总、单请求状态、模型、token、成本、未收金额和延迟查询;同一时间、项目、API Key、模型、供应商 slug、协议、流式状态、错误类型与成功状态过滤会下推到模型成本排行和供应商性能聚合,展示本期/上期费用变化、成功率、缓存命中、P95 延迟和缺失 usage 请求。每条请求可以打开详情,查看完整 request ID、项目与 Key、路由上游、协议、流式状态、重试、吞吐量、缓存 token 和结算状态,并复制不含 prompt、响应正文或客户密钥的诊断 JSON。
+每个完成上游尝试的请求都会按 `request_id` 幂等写入 `usage_events`,并更新 `usage_monthly_rollups`。Usage 持久化独立于预付费冻结记录,因此关闭计费也不会关闭用量账本。Admin WebUI 提供稳定游标分页、本月汇总、单请求状态、模型、token、成本、未收金额、总延迟和首个有效输出延迟(TTFT)查询;同一时间、项目、API Key、模型、供应商 slug、协议、流式状态、错误类型与成功状态过滤会下推到模型、API Key 和供应商聚合,展示本期/上期费用变化、成功率、缓存命中、总延迟与 TTFT 的 P50/P95,以及缺失 usage 请求。租户视图在服务端移除供应商 ID、供应商名称和上游模型,只有平台角色能查看路由归因。每条请求可以打开详情并复制不含 prompt、响应正文或客户密钥的诊断 JSON。
Limits 页面按项目配置:
@@ -184,11 +196,13 @@ Limits 页面按项目配置:
- `concurrent_requests`:从进入上游前到响应复制完成的并发 lease,`0` 表示不限。
- `monthly_spend_micros`:PG 事务内检查当月已计成本和未结冻结,`0` 表示不限。
+API Key 还可以独立配置 `requests_per_minute`、`tokens_per_minute`、`daily_spend_micros` 和 `monthly_spend_micros`。项目与 Key 的 RPM/TPM 在同一次原子判断中取更严格的边界;任一限制拒绝时不会消耗另一层的计数。日/月金额限制在钱包行锁事务内连同待结冻结检查,不能通过并发请求超额。
+
Redis 可用时,RPM/TPM/并发通过 Lua 原子执行并在多实例间共享;Redis 故障时自动退回本机计数,服务继续可用,但降级期间限制是“每实例”而不是“全局”。Redis 恢复后新请求会自动重新使用分布式计数。启用预付计费时,月度消费配额和余额由 PostgreSQL 保证,不依赖 Redis。
## 余额与 Stripe 充值
-模型价格在后台按“币种单位 / 100 万 token”配置,数据库使用 `amount_micros` 固定精度整数保存金额。Stripe 不直接为推理请求结账,只向 PostgreSQL 预付钱包充值;推理请求先按请求体字节数和 `max_tokens`/`max_completion_tokens` 保守冻结余额,成功响应按可信 usage 扣款,失败请求释放冻结。计量层把 OpenAI 总 input 中的 `cached_tokens`/cache-write 子集归一化为互斥的非缓存输入、缓存读取和缓存写入桶,Anthropic 已独立报告的缓存字段则保持不变,避免按输入价和缓存价重复扣费。`request_id` 是用量与扣费幂等键,余额、冻结和不可变 ledger 都在同一个 PG 事务中更新。
+模型价格在后台按“币种单位 / 100 万 token”配置,数据库使用 `amount_micros` 固定精度整数保存金额。Stripe 不直接为推理请求结账,只向 PostgreSQL 预付钱包充值;推理请求先按请求体字节数和 `max_tokens`/`max_completion_tokens` 保守冻结余额,成功响应按可信 usage 扣款,失败请求释放冻结。计量层把 OpenAI 总 input 中的 `cached_tokens`/cache-write 子集归一化为互斥的非缓存输入、缓存读取和缓存写入桶,Anthropic 已独立报告的缓存字段则保持不变,避免按输入价和缓存价重复扣费。`request_id` 是用量与扣费幂等键,余额、冻结和不可变 ledger 都在同一个 PG 事务中更新。Billing 页面中的 request 扣费引用可以直接打开对应的 token、价格、状态与延迟详情;精确 ID 查询仍由服务端强制附加当前租户边界。
Stripe 使用托管 Checkout,服务端不会接触卡号,也没有硬编码支付方式;支付方式由 Stripe Dashboard 动态配置。租户可在 Billing 页面保存开票名称、邮箱和地址,资料用稳定幂等键创建或更新 Stripe Customer;Stripe 暂时不可用时本地资料标记为待修复,下一次充值、保存支付方式或打开客户门户会再次同步。本地不保存税号,启用且确认 Stripe Tax 注册后由 Checkout/Customer Portal 托管税号。手动充值只在签名校验通过的 Webhook 确认 `payment_status=paid` 后入账,成功跳转页不会直接修改余额。自动充值先通过 Checkout Setup Session 保存支付方式;余额低于客户阈值时,后台 worker 用订单 ID 作为幂等键创建并确认 off-session PaymentIntent。同步结果、Webhook 重放和周期对账都只能生成一笔钱包入账;需要客户认证或支付方式失效时会暂停自动充值,不会循环扣款。
@@ -203,6 +217,21 @@ stripe listen --api-key "$AIGW_STRIPE_CLI_API_KEY" --forward-to http://127.0.0.1
docker compose up --build
```
+启动网关前先用只读预检检查 test restricted key。命令固定发送当前
+`stripe-go` SDK 的 API 版本,不创建 Customer、Session、PaymentIntent 或退款,
+也不会输出密钥和 Stripe 原始错误正文;为防误操作,它拒绝 `rk_live_`/`sk_live_`
+密钥:
+
+```bash
+AIGW_STRIPE_API_KEY="$AIGW_STRIPE_API_KEY" go run ./cmd/stripe-preflight
+# 已构建镜像也可直接运行:
+docker compose run --rm --entrypoint stripe-preflight aigw
+```
+
+`ready=true` 表示 Customers、Checkout Sessions、Setup/Payment Intents、Refunds、
+Charges、Disputes、Invoices 和 Billing Portal 配置的读取权限齐全。写权限无法在
+不创建外部对象的前提下证明,仍必须通过下述真实测试模式流程验证。
+
Webhook 至少订阅:
- `checkout.session.completed`
@@ -226,11 +255,11 @@ Webhook 至少订阅:
- `invoice.paid`
- `invoice.payment_failed`
-网关 restricted key 的最小权限按实际启用功能配置:Checkout Sessions Write、Customer Portal Sessions Write、Setup Intents Read、Payment Intents Write、Refunds Write,以及 Customers、Charges、Disputes、Invoices 的 Read;如果 Dashboard 将 Checkout 自动创建 Customer 归入 Customers 写权限,再增加 Customers Write。不要给主网关 Debugging Tools 权限;Stripe CLI 使用独立的测试 key。Dashboard 保存权限时可能要求账户持有人完成二次验证。修改权限后必须在测试模式重新跑一次手动充值、保存支付方式、自动充值、客户门户、退款和对账。
+网关 restricted key 的最小权限按实际启用功能配置:Checkout Sessions Write、Customer Portal Sessions Write、Customers Write、Setup Intents Read、Payment Intents Write、Refunds Write,以及 Charges、Disputes、Invoices 的 Read。读取预检还需要 Checkout Sessions、Customers、Payment Intents、Refunds 和 Billing Portal Configurations 的 Read(Dashboard 中 Write 通常已包含同一资源的 Read)。不要给主网关 Debugging Tools 权限;Stripe CLI 使用独立的测试 key。Dashboard 保存权限时可能要求账户持有人完成二次验证。修改权限后必须先通过 `cmd/stripe-preflight`,再在测试模式重新跑一次手动充值、保存支付方式、自动充值、客户门户、退款和对账。
后台已覆盖 Checkout 重试、客户门户、退款队列、争议/发票/收据记录、失败重试、周期性 Stripe 对账和 CSV 财务导出。Stripe 已支付但本地 pending 的订单会自动补账且传播错误不会被忽略;Stripe 中不存在的未入账订单只能通过 RBAC/审计保护的 resolution 作废,已入账孤立充值只能用等额负向账本冲销,原记录不会删除或改写。退款与争议会先冻结/扣除本地余额,余额不足进入 `uncollected_micros`,不会静默丢账。当前没有默认启用 Stripe Tax,因为是否有有效税务注册不能由代码推断;确认注册和 canonical product tax code 后再显式打开。生产环境应把 Stripe restricted key 和 Webhook signing secret 放入云平台的密钥管理服务,并限制密钥权限和来源 IP,不要放进镜像或仓库。
-模型目录支持输入/输出模态、上下文窗口、最大输出、能力集合、生命周期、弃用替代模型、区域和租户/API key allowlist;价格在 `model_price_versions` 中按生效时间版本化。每个客户 API Key 还可以独立设置模型白名单、月度消费上限和过期时间:限制随 PostgreSQL generation 热加载到鉴权快照,模型检查发生在路由前,金额上限在钱包行锁事务内和待结算冻结一起检查。旧 alias 只映射到 canonical model ID。
+模型目录支持输入/输出模态、上下文窗口、最大输出、能力集合、生命周期、弃用替代模型、区域和租户/API key allowlist;价格在 `model_price_versions` 中按生效时间版本化。每个客户 API Key 还可以独立设置模型白名单、日/月消费上限、RPM/TPM 和过期时间:限制随 PostgreSQL generation 热加载到鉴权快照,模型检查发生在路由前,金额上限在钱包行锁事务内和待结算冻结一起检查。旧 alias 只映射到 canonical model ID。
静态模式的上游 Base URL 与 API Key 分别使用 `base_url_env` 和 `api_key_env`;控制面、Redis、Stripe、SMTP、WebAuthn 和监听地址同样只通过环境变量或密钥管理服务注入。严格 JSON 解析会拒绝旧的 `address`、`base_url`、`success_url` 和 `cancel_url` 字面量字段,避免环境隔离被配置文件绕过。版本化配置只保存 `*_env` 名称,不保存外部服务密钥或部署域名。
@@ -242,7 +271,7 @@ AIGW_DATABASE_URL="postgres://..." go run ./cmd/migrate
AIGW_DATABASE_URL="postgres://..." go run ./cmd/migrate -status
```
-管理 API 支持账号密码、TOTP、Passkey 会话和仅用于初始化/故障恢复的 bootstrap token。即使已经启用 RBAC,生产环境仍应设置 HTTPS、把推理端口和管理端口分离、按需关闭公开注册,并把 bootstrap token 存入密钥管理服务;企业部署可再接 OIDC/SAML 与强制 MFA 策略。
+管理 API 支持账号密码、TOTP、Passkey 会话和仅用于初始化/故障恢复的 bootstrap token。客户 API Key 明文只在创建或轮换响应中返回一次;后续列表仅返回已持久化的前缀和六字符后缀,历史上未保存后缀的 Key 保持前缀显示,不能从 SHA-256 摘要反推。即使已经启用 RBAC,生产环境仍应设置 HTTPS、把推理端口和管理端口分离、按需关闭公开注册,并把 bootstrap token 存入密钥管理服务;企业部署可再接 OIDC/SAML 与强制 MFA 策略。
完整的扩展边界见 [架构说明](docs/architecture.md),与 ZenMux 模型目录对照后的上线缺口见 [商业化就绪清单](docs/commercial-readiness.md)。
@@ -258,4 +287,4 @@ CGO_ENABLED=0 go build -buildvcs=false ./cmd/...
docker compose --env-file .env.debug --profile test run --rm integration-test
```
-设置 `AIGW_TEST_DATABASE_URL` 后,测试会强制执行 PostgreSQL Webhook/结算/迁移升级用例。CI 还构建容器镜像、生成 SPDX SBOM、以 Trivy 阻断 HIGH/CRITICAL 漏洞,并在 `v*` 标签发布时使用 OIDC keyless Cosign 签名。负载与 Redis 降级演练分别使用 `scripts/load-smoke.sh` 和 `scripts/redis-fault-drill.sh`;备份恢复演练使用 `scripts/backup-postgres.sh` 与 `scripts/restore-drill.sh`。
+设置 `AIGW_TEST_DATABASE_URL` 和 `AIGW_TEST_REDIS_URL` 后,测试会强制执行 PostgreSQL Webhook/结算/迁移升级,以及跨实例供应商健康传播/重启回放用例。CI 还构建容器镜像、生成 SPDX SBOM、以 Trivy 阻断 HIGH/CRITICAL 漏洞,并在 `v*` 标签发布时使用 OIDC keyless Cosign 签名。负载与 Redis 降级演练分别使用 `scripts/load-smoke.sh` 和 `scripts/redis-fault-drill.sh`;后者会验证 Redis 停止期间 readiness 不受影响以及恢复后的自动重连,传入 `AIGW_METRICS_URL` 时还会严格检查共享健康连接 gauge。备份恢复演练使用 `scripts/backup-postgres.sh` 与 `scripts/restore-drill.sh`。