Appearance
租户开通 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。
- 注册:用户打开 Builder/Ops/Starter 登录页 →「注册」→ Keycloak
/realms/agenthub/protocol/openid-connect/registrations(需registrationAllowed; 邮箱验证依赖 SMTP,见keycloak-registration-secrets.md)。 - 首登 JIT:回调后调用
GET /api/v1/me→ 创建 personal 租户 + 本地users行; 若token_refresh_required,引导重新登录。 - 升级正式租户:Builder
/onboarding/tenant→POST /api/v1/tenants(name + slug)→ 角色升为tenant_admin→ 再次登录刷新 JWT scopes。 - 成员:租户控制台
/settings/tenant→ 邀请码POST /api/v1/tenants/current/invites; 受邀方(仍为 personal)POST /api/v1/tenants/invites/redeem。 - 积分 / 项目:升级后按既有 ops 积分发放与项目建模流程继续。
Mode A(手工 / 二开方 M2M)— 原 GL-05 路径
0. 前置
- ECS-B 栈健康:
bash scripts/verify-testbed.sh全绿; - Keycloak realm
agenthub已导入(infra/keycloak/realm.json,含agenthub-m2mclient 模板); - 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_idclaim 一致(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):
确保 client 存在 + 配置 service account 属性(幂等,实测入口):
bashbash 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。
取 client secret(KC 生成,严禁入库):Admin Console → Clients →
agenthub-m2m→ Credentials, 或 Admin APIGET /admin/realms/agenthub/clients/{uuid}/client-secret。走安全通道交付二开方。验证(实测全绿的验收探针):
bashbash 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取到的 tokeniss不匹配 → 401。
2b. 自助通道(IE-5,正式租户首选)
正式租户的 tenant_admin 可在 web-builder「设置 → 租户控制台」→「M2M 接入凭据」 自助完成上表全部动作(每租户上限 3 个 active client):
POST /api/v1/tenants/current/m2m-clients(scopeidentity: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:
POST /api/v1/builder/model-groups(scopebuilder:model:write);POST /api/v1/builder/models——注册模型时提交 api_key,服务端经CredentialsStore.write_ref写入 OpenBao,DB 只存vault_secret_ref_id(明文 key 永不落库);POST /api/v1/builder/routes——配置 primary + 可选 fallback(同组校验)。
凭据唯一入口 = OpenBao(ADR-0042);禁止为绕过
ModelCredentialsResolver新增环境变量直连 key。
fail-closed 核验(实测):删 key → 任务 retrying/failed 且 failure_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全额退、402POINTS_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→failed | OpenBao 无 KV / AppRole 凭据缺失 | 按 §3 provisioning |
headless 形态下 https://<EIP>/ 502 | ADR-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)。