docs(adr): ADR-0012 业务系统账号绑定与凭据获取;A5.4 收敛为实例路由

This commit is contained in:
zh 2026-09-29 14:39:20 +08:00
parent ee42650f9c
commit 143216e9b8
4 changed files with 77 additions and 4 deletions

View File

@ -0,0 +1,49 @@
# ADR-0012:业务系统账号绑定与凭据获取
- **状态**:已接受
- **日期**:2026-09-29
- **决策人**:技术负责人
- **相关**:任务表 A5.4 / A5.7;架构设计 4.1、4.3、6;实施设计 2.4、5;ADR-0004(桌面端授权)、ADR-0008(库归属)
## 背景
A4.1 已经落地「平台用户 → 各系统账号」的绑定表和策略(未绑定默认拒绝,一个系统停用不影响另一个),A4.3 也把凭据做成密文存储加一次性执行句柄。但有一件事当时刻意留空:**这条绑定里的登录态是怎么拿到的**。
A5 要接真实 APS,这个问题必须先答:如果答案错了,后面每个业务系统都要各写一套登录和续期逻辑,凭据会散落各处,「不同系统登录态互相独立」也守不住。
## 选项
| 选项 | 做法 | 代价 | 不选的理由 |
| --- | --- | --- | --- |
| A 业务系统提供标准授权登录(OAuth2 / OIDC 授权码) | 平台发起跳转 → 用户在业务系统登录 → 回调带 code → 平台换 token | 需要业务系统支持标准协议;要处理回调与续期 | 选定为主 |
| B 平台收账号密码,一次性换 token | 老系统没有标准登录时,平台弹窗收账号密码,调它的登录接口换 token,密码用完即弃 | 部分系统只能这么接;要明确「密码不落盘」的实现约束 | 作为过渡方案保留 |
| C 长期 API Key / 服务账号 | 管理员在平台配置 key,挂在租户或实例上 | 没有按人的登录态,出问题查不到具体人 | 仅用于无人值守的系统级集成 |
| D 平台存业务系统密码,每次调用现登 | 密码入库,调用前先登录 | 实现最直白 | 密码落盘等于把业务系统的身份长期托管给平台;失效判断、改密、审计全部变复杂 |
## 结论
选 A 为主、B 为过渡、C 限系统级。共同规则如下:
| 项 | 结论 |
| --- | --- |
| 声明位置 | Provider manifest 增加 `auth` 声明(`oauth` / `password-exchange` / `apikey`),平台按声明走对应流程,平台侧接口不变 |
| 平台职责 | 发起绑定、接收回调、兑换凭据、加密存储、续期、失效判定、日志脱敏;**不实现业务系统的登录页面** |
| 存什么 | 只存 token / refresh token(或 API Key)的密文,**绝不存密码**;密码只在一次兑换调用里经过内存 |
| 何时取用 | 只在一次 execution 内,经 A4.3 的一次性句柄;工具参数、事件、日志、模型上下文里都没有 |
| 失效行为 | 该系统的调用立即被拒(`policy.identity_disabled` 或凭据不可用),界面提示重新绑定;其他系统不受影响 |
| 解绑 | 删除绑定与密文;审计与执行历史保留(ADR-0008 的「执行历史不删」) |
| 桌面端 | 走系统浏览器 + 本地 loopback 回调,不在内嵌 webview 里收集密码(与 ADR-0004 的授权码 + 设备绑定同一思路) |
| 浏览器端 | 复用平台登录态发起绑定;业务系统 token 不下发给客户端(协议第 3 节) |
## 影响
- **对代码**:`server/provider-sdk` 增加 `auth` 声明与 `exchange()`;`server/core/identity` 增加绑定流程(发起 / 完成 / 解绑);`server/entry/http` 增加绑定端点;客户端设置页增加「账号绑定」区块
- **对任务**:A5.4 收敛为**实例路由**(哪个租户或工厂连哪个 APS 实例);新增 **A5.7 账号绑定与登录态** 承担本 ADR 的落地
- **对文档**:架构设计第 4.1、6 节补绑定机制;实施设计 2.4 与第 5 节补绑定步骤;协议第 4 节在实现时加端点,并遵循「四处同步」规则
- **不这么做的后果**:每个业务系统各自实现登录与续期,凭据散落在 Provider 内部,「凭据不出域」与「登录态互不影响」两条都会被绕过
## 遗留
- APS 最终用哪种方式(它能不能提供标准授权登录):由 B5 的契约化接口层定,这一条不阻塞平台侧实现
- 一个用户在同一个系统里绑多个账号(多工厂账号):首版一人一系统一条绑定,等有真实需求再放开
- token 续期的失败重试与提醒策略:A5.7 落地时定

View File

@ -49,5 +49,6 @@ ADR 写完不再修改。要改结论就**新写一份**,并在两份里互相
| [0009](./0009-delivery-shape.md) | 交付形态:纯平台包 + APS 单独部署 | 已接受 | 任务表 A0(决策 D14) |
| [0010](./0010-aps-dev-shape-in-repo.md) | APS 开发期与本仓库同仓不同目录 | 已接受 | 任务表 B0 |
| [0011](./0011-model-endpoint.md) | 模型与端点:DeepSeek + 本机配置文件 | 已接受 | 任务表 A0(决策 D15) |
| [0012](./0012-identity-binding-and-credentials.md) | 业务系统账号绑定与凭据获取 | 已接受 | 任务表 A5.4 / A5.7 |
编号在写之前先在本表登记,避免多人并行时撞号。

View File

@ -221,6 +221,8 @@ Core 不是所有请求的中转站,而是一组被多方调用的确定性后
**一句话:**权限归业务系统,映射与确认归平台。动作在业务系统里已有审批流的,交给它审;只有跨系统编排这类没人能替你决定意图的动作,才由平台出确认卡。
**登录态从哪来(ADR-0012):**用户在每个系统上各自绑定一次——首选跳转该系统的授权登录(OAuth2 / OIDC),拿回来的 token 只以密文存在平台的凭据代理里;老系统没有标准登录时退化为「账号密码一次性换 token」,密码用完即弃、绝不落盘。平台不实现业务系统的登录页,也不把业务 token 下发给客户端。
## 4.2 确认与执行记录只存一张表
现有实现用三张表(审批请求、一次性凭据、审批事件)承载一件事。单用户场景下可以合并成一张**待确认动作表**:请求参数摘要、状态(待确认 / 已确认 / 已执行 / 已拒绝)、执行回执、幂等键、有效期。
@ -341,6 +343,15 @@ interface Capability {
}
```
Provider 还要声明**登录方式**(ADR-0012),平台按声明走同一条绑定流程,不自己实现业务系统的登录页:
| `auth.kind` | 用于 | 平台做什么 |
| --- | --- | --- |
| `oauth` | 有标准授权登录的系统(首选) | 发起跳转 → 收回调 code → 用 `exchange()` 换 token |
| `password-exchange` | 老系统,没有标准登录 | 收一次账号密码 → `exchange()` 换 token,密码用完即弃 |
| `apikey` | 系统级集成、无人值守 | 管理员配置,密钥挂在租户或实例上 |
| 不声明 | 没有账号概念的自建系统(联调桩) | 视为 `requiresIdentity: false` |
Provider 的开发步骤、运行机制与契约测试见实施设计。
**能力声明的取舍:**read 不改变状态;draft 只写隔离草稿,不走审批但必须声明清理方式;write 必须绑定唯一目标、幂等键与授权引用。sync 直接返回结果,async 先返回 jobId。

View File

@ -342,14 +342,26 @@ Provider 先写适配器,再以包的形式安装和管理。开发步骤与
```text
1. 定义能力:id、effect、mode、输入输出 Schema
2. 写 manifest:providerType、version、capabilities
3. 实现适配器:describe / invoke / health
2. 写 manifest:providerType、version、capabilities、auth 声明
3. 实现适配器:describe / invoke / health(+ preview、exchange)
4. 跑契约测试
5. 注册实例:地址、租户或工厂绑定、适配器版本
6. 绑定账号与路由
5. 注册实例:地址、租户或工厂绑定(实例路由)、适配器版本
6. 账号绑定:用户在每个系统各自绑定一次登录态(ADR-0012)
7. 灰度开放给引擎
```
**账号绑定怎么走(ADR-0012):**平台负责发起、回调、兑换、加密存储、续期与失效判定,登录形态由 Provider 的 `auth` 声明决定(`oauth` / `password-exchange` / `apikey`)。
```text
用户点「绑定 APS」
→ 平台按 auth.kind 发起(跳转授权登录 / 收一次账号密码 / 用管理员配置的 key)
→ Provider.exchange() 把 code 或账号密码换成 token
→ 平台把 token 密文写进 identity_bindings,明文只在这一步的调用里经过
→ 设置页显示该系统的登录态:已绑定 / 已失效 / 未绑定
失效:token 过期或被业务系统踢下线 → 该系统的调用立即被拒,提示重新绑定;其他系统不受影响
解绑:删除绑定与密文;审计与执行历史保留
```
| 运行机制 | 约定 |
|-|-|
| 凭据边界 | 首版 Provider 运行在平台信任域内,Core 按 execution 注入短期凭据,Provider 不落库、不缓存。外部团队接入时走跨进程模式,双向认证加一次性凭据。 |