Skip to content

租户开通 Runbook(GL-05 · 二开方接入的 ops 步骤)

定位:把一个二开方(second-party)从零开通到能用真实 OIDC token 调 AgentHub API。 底稿:GL-01~03 在 ECS-B 真实栈上实测跑通的命令(2026-07-07);不写"应该可以",只写"验证过"。 配套教程:开通完成后,把 hello-agent-tutorial.md 交给二开方自助跑通首个任务。 相关:ADR-0042 二开接入模型 · 免计费口径 · testbed-runbook


Mode B(主路径 · IE-4)— 自助注册 → 个人租户 → 升级正式租户

优先使用本路径开通 SaaS / Builder 终端用户。Mode A(下文 §1 起)保留给二开方 M2M / ops 手工开户。清单见 saas-mode-b-checklist.md

  1. 注册:用户打开 Builder/Ops/Starter 登录页 →「注册」→ Keycloak /realms/agenthub/protocol/openid-connect/registrations(需 registrationAllowed; 邮箱验证依赖 SMTP,见 keycloak-registration-secrets.md)。
  2. 首登 JIT:回调后调用 GET /api/v1/me → 创建 personal 租户 + 本地 users 行; 若 token_refresh_required,引导重新登录。
  3. 升级正式租户:Builder /onboarding/tenantPOST /api/v1/tenants(name + slug)→ 角色升为 tenant_admin → 再次登录刷新 JWT scopes。
  4. 成员:租户控制台 /settings/tenant → 邀请码 POST /api/v1/tenants/current/invites; 受邀方(仍为 personal)POST /api/v1/tenants/invites/redeem
  5. 积分 / 项目:升级后按既有 ops 积分发放与项目建模流程继续。

Mode A(手工 / 二开方 M2M)— 原 GL-05 路径

0. 前置

  • ECS-B 栈健康:bash scripts/verify-testbed.sh 全绿;
  • Keycloak realm agenthub 已导入(infra/keycloak/realm.json,含 agenthub-m2m client 模板);
  • OpenBao 已初始化且平台 fallback 模型 key 已写入(见 testbed-runbook §5 + §3 本文)。

1. 发放租户与用户(PostgreSQL)

为租户插入 tenants 行 + 首个 users 行(以及可选的 projects 行)。testbed 上的参考实现是 scripts/seed-testbed.sh --data(幂等 ON CONFLICT DO NOTHING,FK 拓扑序:tenants → users → projects → …)。生产开通按同一拓扑执行,UUID 换成真实随机值:

sql
INSERT INTO tenants (id, name, slug, keycloak_realm_id, status, created_at, updated_at)
VALUES ('<tenant-uuid>', '<租户名>', '<slug>', 'agenthub', 'active', NOW(), NOW());

INSERT INTO users (id, tenant_id, keycloak_user_id, name, primary_role, status, created_at, updated_at)
VALUES ('<user-uuid>', '<tenant-uuid>', '<kc-user-or-service-account-id>', '<名>', 'builder', 'active', NOW(), NOW());

记住这两个 UUID —— 它们必须与 Keycloak token 中的 tenant_id / user_id claim 一致(JwtValidator 硬契约,均为 UUID 字符串)。

2. Keycloak:M2M client(client credentials,二开方标准通道)

realm 模板已含 agenthub-m2m client(confidential + service accounts + aud=agenthub-console audience mapper + tenant_id/user_id/permissions user-attribute mappers)。为每个二开方 克隆一个专属 client(或首个二开方直接用 agenthub-m2m):

  1. 确保 client 存在 + 配置 service account 属性(幂等,实测入口):

    bash
    bash scripts/gl-acceptance.sh --m2m-provision

    内部动作(Admin API,凭据取自 KC 容器 env / .env.testbed,全程不落盘):

    • 若 client 不存在则创建(serviceAccountsEnabled + 4 个 protocol mappers);
    • service-account-agenthub-m2m 用户写属性: tenant_id=<tenant-uuid>user_id=<user-uuid>permissions=[...](JSON 数组或字符串数组; scope 清单以各端点 require_scope( 为准,如 task:submit / task:read / builder:*); 以及 KC26 User Profile 必填 firstName/lastName/email。
  2. 取 client secret(KC 生成,严禁入库):Admin Console → Clients → agenthub-m2m → Credentials, 或 Admin API GET /admin/realms/agenthub/clients/{uuid}/client-secret。走安全通道交付二开方。

  3. 验证(实测全绿的验收探针):

    bash
    bash scripts/gl-acceptance.sh --m2m-auth       # client_credentials → GET 200 / POST 缺 scope 403
    bash scripts/gl-acceptance.sh --negative-auth  # 无凭据/伪造 HS256/错误 issuer → 全部 401

    期望 token claims(实测样例):

    json
    {
      "iss": "https://<EIP>/auth/realms/agenthub",
      "aud": ["agenthub-console", "account"],
      "azp": "agenthub-m2m",
      "tenant_id": "00000000-0000-0000-0000-000000000001",
      "user_id": "00000000-0000-0000-0000-000000000002",
      "permissions": ["task:read"]
    }

issuer 提醒:容器内 KEYCLOAK_ISSUER 钉在外部网关 origin(https://<EIP>/auth/realms/agenthub)。 二开方必须经网关取 token(https://<EIP>/auth/...),直连 :8080 取到的 token iss 不匹配 → 401。

2b. 自助通道(IE-5,正式租户首选)

正式租户的 tenant_admin 可在 web-builder「设置 → 租户控制台」→「M2M 接入凭据」 自助完成上表全部动作(每租户上限 3 个 active client):

  • POST /api/v1/tenants/current/m2m-clients(scope identity:m2m:write)自动完成: Keycloak confidential client 创建(service-account + 4 mappers,与 §2 人工通道同规格)+ tenant_id/user_id(专用影子用户行)属性回写 + 固定 scope 白名单builder:* 运营面 + agent:* + sop:* + delivery:api_key:* + billing:points:tenant_grant 等; 刻意不含 builder:model:write——模型/路由归平台方,与 §3 分工一致; 也不含 ops 级 billing:points:grant);
  • client_secret 仅创建/轮转响应返回一次(丢失只能轮转);rotate 即时失效旧 secret;revoke 落 DB 留痕;
  • 验证:生成后直接跑 §2.3 的 --m2m-auth 思路——用新凭据经网关取 token 调 GET /api/v1/builder/agents

人工通道(上文 §2)保留给:personal 租户、超过 3 个 client 的批量需求、或需要非白名单 scope 的特殊接入。

3. 模型接入(OpenBao + model_routes)

平台 fallback(开机可用兜底)——ops 一次性:

bash
# 1) OpenBao AppRole(幂等)
bash scripts/openbao-init.sh          # 产出 OPENBAO_ROLE_ID / OPENBAO_SECRET_ID → 写 .env.testbed(chmod 600)
# 2) 写平台 chat key(KV v2;路径与 MODEL_FALLBACK_CHAT_VAULT_PATH 对齐,默认 agenthub/model_fallback/chat)
bao kv put secret/agenthub/model_fallback/chat api_key="$REAL_KEY"
# 3) api/worker 注入 MODEL_FALLBACK_CHAT_{VENDOR,MODEL,API_BASE}(deploy/compose/.env.testbed)

租户级路由(primary/fallback,可选)——二开方或 ops 经 Builder API:

  1. POST /api/v1/builder/model-groups(scope builder:model:write);
  2. POST /api/v1/builder/models——注册模型时提交 api_key,服务端经 CredentialsStore.write_ref 写入 OpenBao,DB 只存 vault_secret_ref_id(明文 key 永不落库);
  3. POST /api/v1/builder/routes——配置 primary + 可选 fallback(同组校验)。

凭据唯一入口 = OpenBao(ADR-0042);禁止为绕过 ModelCredentialsResolver 新增环境变量直连 key。

fail-closed 核验(实测):删 key → 任务 retrying/failedfailure_reason=agent_unavailable:model_credential; 恢复 key → completed。一键复验:bash scripts/gl-acceptance.sh --fail-closed(KV v2 soft-delete + undelete,无凭据外泄)。

3.5 API-key 发放与初始积分(T2 · ADR-0044)

开通流程在 M2M client 之外增加两步(均可走 web-ops 页面或 curl):

bash
# 1) API-key 发放(scope delivery:api_key:write;明文仅创建响应返回一次,交付渠道走一次性密文通道)
curl -sk -X POST "$BASE/api/v1/delivery/api-keys" \
  -H "Authorization: Bearer $OPS_TOKEN" -H 'Content-Type: application/json' \
  -d '{"name": "<tenant-slug>-prod"}'
# 轮转 POST /delivery/api-keys/{id}:rotate · 吊销 POST /delivery/api-keys/{id}:revoke(即时生效)

# 2) 初始积分授予(scope billing:points:grant;平台→租户商务结算入账,跨租户,WORM 审计)
curl -sk -X POST "$BASE/api/v1/billing/points:grant" \
  -H "Authorization: Bearer $OPS_TOKEN" -H 'Content-Type: application/json' -d '{
    "tenant_id": "<tenant-uuid>",
    "user_id": "<tenant-shadow-user-uuid>",
    "project_id": "<project-uuid>",
    "amount": 100000,
    "operation_id": "grant:contract-<合同号>",
    "expires_at": "<ISO 到期时间>",
    "reason": "开通合同 <合同号> 首期结算"
  }'
  • 高额(≥10000)在 web-ops 积分发放页强制二次确认;operation_id = 商务结算单据号(幂等 + 两侧对账键);
  • 目标影子用户无长期账户时自动开户;流水核对 GET /billing/points/ledger?tenant_id=<tenant-uuid>(ops 持 grant scope 可跨租户)。

3.6 仅调用方试用积分口径(call-only trial)

适用对象:不自建 Agent、不做建模自助,只调用运行面(会话 / 语音 / 文生图)的接入方——交付物为「地址 + API-Key + 已发布 Agent ID + 协议一页」。

  • 建议首发额度 500–1000 积分points_per_turn=1 基准 ≈ 500–1000 轮对话;转写 / 合成各 1/次,TTS 缓存命中零扣点);
  • 发放通道复用 §3.5(ops points:grant 或租户自助 points:tenant-grant),operation_id 建议前缀 trial:(如 trial:<tenant-slug>-20260827),与正式商务入账在流水中天然可分;
  • 试错成本特性(对账时放心):失败轮次 unfreeze 全额退、402 POINTS_INSUFFICIENT 零副作用——试用消耗 ≈ 仅成功轮次;
  • 用尽与续发GET /billing/points/ledger?user_id=<影子用户> 核对余量后,用新 operation_id 再授一笔即可(幂等键不复用);
  • 试用通道验收:接入方跑通「开会话 → 一轮流式 → 断连重拉」三步即视为通道 OK——对照 sdk/python/examples/(mock 自验 + 换真实 AGENTHUB_BASE_URL 复跑)。

4. 计费口径

billing-free-run-mode.md:默认 estimated_points=0 免计费; T2 起计费闭环走 §3.5 入账 + binding billing_rules.estimated_points>0 + X-AgentHub-On-Behalf-Of 归属(教程 hello-agent-tutorial.md §5.5)。

5. 已知 fail-closed 项(预期行为,勿当故障)

现象原因处置
reconcile live-fix(dry_run=false)全拒RECONCILE_MFA_TOTP_SECRET 未配置预期 fail-closed;需要时配置 TOTP secret
无模型 key 时 Agent 任务 retrying→failedOpenBao 无 KV / AppRole 凭据缺失按 §3 provisioning
headless 形态下 https://<EIP>/ 502ADR-0041:web-* 不部署(gateway 运行时解析降级)预期;deploy-from-cloud-agent.sh --with-frontend 可恢复 SPA

6. 开通验收清单(每租户)

  • [ ] tenants/users 行已插入且 UUID 与 KC claims 一致;
  • [ ] M2M client + service-account 属性已配置(--m2m-provision);
  • [ ] --m2m-auth 探针 200/403 全对;
  • [ ] API-key 已发放并一次性交付(§3.5-1);
  • [ ] 初始积分已授予且流水可见(§3.5-2,manual_adjust + reason 含合同号);
  • [ ] 二开方按教程跑通 Hello Agent(completed + SSE 事件 + cost_records 记录)。
  • [ ] (可选)Starter 验证台:GET /api/v1/app/manifests 可见本租户已发布 App;浏览器选 App 提交一笔 estimated_points>0 任务后 /points 流水可见;不足时 402(见 hello-agent-tutorial.md §9 / scripts/t6-starter-verification.sh)。