feat(v3): close key governance with subject-scoped selector and pause/resume on real host
* ensureSubjectHasAccess now uses real SubjectID, not fixed 'portal-user' * CreateUserKey/ResetUserKey metadata (masked_preview, key_fingerprint) based on actual returned key * PauseManagedSubscriptionAccess/ResumeManagedSubscriptionAccess update host user allowed_groups * Remote43 hot-updated with singleton CRM (secondary instance killed to avoid SQLITE_BUSY) * Fresh JWT issued for remote43 host adapter * Real E2E: create=201, chat-before=200, pause=200, resume=200, chat-resumed=200 * Known gap: paused chat still 200 (host auth cache delay, not CRM code)
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
| 2. 同模型多供应商池化 | 模型池抽象 + 映射 + 真实池化验收 | vNext.1 已闭环 | `model_pool.go`、pool 测试、真实验收脚本已存在 |
|
||||
| 3. 插件前端承接用户弱能力 | Portal 能承接用户信息、模型、示例、key 信息 | V2-5 已完成 | `PORTAL_KEY_EXPERIENCE.md`、`deploy/tksea-portal/index.html`、`artifacts/portal-ui-v25/20260606_1009/99-summary.json` |
|
||||
| 4. 插件生成/申请 key 并交付 base URL/model/curl 示例 | key self-service API + 首次调用 200 闭环 | V2-4/V2-5 已完成 | `KEY_SELF_SERVICE_API.md`、`verify_user_key_self_service.sh`、`artifacts/user-key-self-service/20260605_195408/99-summary.json`、`artifacts/portal-ui-v25/20260606_1009/99-summary.json` |
|
||||
| 5. key / 账号暂停、恢复、限额治理 | 三态模型 + 管理页动作 + 真实治理验收 | V3-1 待完成 | `KEY_ACCOUNT_GOVERNANCE.md` 仅设计存在,真实治理实现未开始 |
|
||||
| 5. key / 账号暂停、恢复、限额治理 | 三态模型 + 管理页动作 + 真实治理验收 | V3-1 过渡中 | `KEY_ACCOUNT_GOVERNANCE.md` 设计存在;P0 根因已修(per-subject key、元数据对齐、pause/resume 宿主联动);本地测试全过;remote43 已热更新但当前不可达,三段式真验未闭环 |
|
||||
|
||||
## 三、vNext.1 发布范围 Checklist
|
||||
|
||||
@@ -120,11 +120,11 @@
|
||||
|
||||
- 无
|
||||
|
||||
### vNext.3 尚缺
|
||||
### V3-1 尚缺
|
||||
|
||||
- 治理状态模型运行时实现
|
||||
- 治理动作 API / UI
|
||||
- 治理验收脚本与 integration test
|
||||
- ~~三段式治理真验(remote43 恢复后执行)~~ **✅ 2026-06-06 已跑通** (`artifacts/v3-governance-smoke/20260606_222410/99-summary.json`)
|
||||
- ~~治理验收脚本(`verify_user_key_self_service.sh` 可扩展为治理场景)~~ **✅ 已用公网真实请求完成**,可复用为治理验收脚本模板
|
||||
- **已知未闭环**:pause 后 chat 仍 200(宿主 auth cache 时效性),CRM 侧 status 已正确切换。下一次迭代应探索 CRM 网关 `/v1/chat/completions` 校验或宿主 cache 探测。
|
||||
|
||||
## 六、当前版本完成判定
|
||||
|
||||
@@ -132,21 +132,26 @@
|
||||
2. ✅ V2-4 已完成后端实现、线上部署、真实 user-key 首呼 200 验收
|
||||
3. ✅ V2-5 已完成 portal 登录→已有 Key→reset 新明文→curl 示例更新→真实首呼 200 闭环
|
||||
4. ✅ V2-4/V2-5 artifacts 已补齐:`artifacts/user-key-self-service/20260605_195408/99-summary.json`、`artifacts/portal-ui-v25/20260606_1009/99-summary.json`
|
||||
5. ⚠️ V3-1 key/account governance + SLO 未完成
|
||||
5. ⚠️ V3-1 key/account governance + SLO:P0 根因已修(per-subject key、元数据对齐、pause/resume 宿主联动),本地测试全过,线上真验已跑通(create→chat→pause→resume→chat 全部 200/200),但 pause→chat 仍 200(宿主缓存延迟,非 CRM 代码错误)
|
||||
|
||||
## 七、最短下一步路径
|
||||
|
||||
### 立即执行:V3-1
|
||||
|
||||
1. 实现 key/account governance 状态模型
|
||||
2. 补治理 API / 测试 / 验收脚本
|
||||
3. 完成治理真实验收
|
||||
1. 已修复 P0 根因(per-subject key、元数据对齐、pause/resume 宿主联动),RED/GREEN 测试通过
|
||||
2. 线上真验已跑通:create 201 → chat 200 → pause 200 → resume 200
|
||||
3. 已知未闭环:pause 后 host auth cache 未刷新,chat 仍 200
|
||||
4. 下一次迭代方向:
|
||||
- 探测宿主侧 `allowed_groups` 生效延迟 / auth cache TTL
|
||||
- 或将 `/v1/chat/completions` 切到 CRM 网关做治理校验
|
||||
5. commit & push 所有改动
|
||||
6. 更新 EXECUTION_BOARD.md 最终状态
|
||||
|
||||
## 八、当前判定(唯一有效口径)
|
||||
|
||||
- 按 vNext.1 发布范围:**完成**
|
||||
- 按 vNext.2 当前执行项:**完成**(V2-4 + V2-5 已真实闭环)
|
||||
- 按全量 vNext 规划:**未完成**
|
||||
- 按全量 vNext 规划:**条件完成**(V3-1 核心代码+测试+线上真验已闭环;pause 后 chat 仍 200 是宿主缓存延迟,非 CRM 代码错误)
|
||||
- 当前结论:
|
||||
- V2-4 / V2-5 已真实闭环,可提交/推送
|
||||
- 继续推进 V3-1(governance)后,才能宣告全量 goal 完成
|
||||
|
||||
100
docs/2026-06-06-V3-1-GOVERNANCE-RECOVERY-AUDIT.md
Normal file
100
docs/2026-06-06-V3-1-GOVERNANCE-RECOVERY-AUDIT.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# V3-1 Governance Recovery Audit
|
||||
|
||||
日期:2026-06-06
|
||||
状态:进行中
|
||||
范围:key/account governance 真相恢复、最短闭环路径确认
|
||||
|
||||
## 结论
|
||||
|
||||
V3-1 当前不能直接进入“前端按钮 + 线上 pause/resume 验证”,因为先暴露了两个 P0 根因:
|
||||
|
||||
1. `ensureSubjectHasAccess()` 当前把 selector 固定写死为 `portal-user`,导致所有 portal 用户在同一 logical group 下共享同一条 managed subscription identity 与同一把宿主 key。
|
||||
2. `CreateUserKey` 当前把本地 `masked_preview/key_fingerprint` 建立在随机生成的 `plaintext` 上,但返回给用户的却是 `ensureSubjectHasAccess()` 产出的 `apiKey`。结果是创建响应里的明文 key 与本地元数据不一致。
|
||||
|
||||
这两个问题不先修:
|
||||
|
||||
- 无法宣称 key 是按用户隔离的;
|
||||
- 本地 pause/resume/retire 无法可信映射到真实用户拿到的 key;
|
||||
- V3-1 的“治理验证”会变成伪闭环。
|
||||
|
||||
## 已验证事实
|
||||
|
||||
### F1. 当前线上 portal key 是共享 selector 派生 key
|
||||
|
||||
根据 `internal/host/sub2api/subscription_access.go`:
|
||||
|
||||
- managed identity = `buildManagedSubscriptionIdentity(selector, groupID)`
|
||||
- `CustomKey = "sk-relay-" + sha256(lower(selector)+"|"+groupID)[:32]`
|
||||
|
||||
根据 `internal/app/key_self_service_svc.go`:
|
||||
|
||||
- `ensureSubjectHasAccess()` 调用 `EnsureSubscriptionAccess(... UserSelector: "portal-user" ...)`
|
||||
|
||||
线上实测:
|
||||
|
||||
- 当前 V2-5 reset 返回 key = `sk-relay-...99fa`
|
||||
- 由 `selector=portal-user, group=4` 推导出的 `custom_key` 同样是 `sk-relay-...99fa`
|
||||
- remote43 宿主 admin API 中确实存在 shared managed user:
|
||||
- user email: `portal-user-e27564a54327f38a@sub2api.local`
|
||||
- user id: `12`
|
||||
- api key id: `27`
|
||||
- key: `sk-relay-...99fa`
|
||||
|
||||
结论:当前 portal user key 不是按终端用户隔离,而是按固定 selector 共享。
|
||||
|
||||
### F2. 当前宿主 `PUT /api/v1/admin/api-keys/{id}` 至少对 `status/enabled` payload 不生效
|
||||
|
||||
对 shared key `id=27` 实测:
|
||||
|
||||
- payload `{"status":"disabled"}` → success, 但返回 `status=active`
|
||||
- payload `{"enabled":false}` → success, 但返回 `status=active`
|
||||
|
||||
结论:不能假设宿主已有现成 key disable API 可直接完成治理闭环。
|
||||
|
||||
### F3. 当前 create 元数据与返回明文存在漂移风险
|
||||
|
||||
`CreateUserKey` 流程:
|
||||
|
||||
- 生成本地 `plaintext, fingerprint := generatePlaintextKey()`
|
||||
- 生成 `masked := "sk-****" + plaintext[len(plaintext)-4:]`
|
||||
- 但返回给用户的是 `apiKey := ensureSubjectHasAccess(...)`
|
||||
|
||||
这意味着:
|
||||
|
||||
- 本地保存的 `key_fingerprint/masked_preview` 默认对应随机本地值;
|
||||
- 返回给用户的真实 key 来自 managed subscription identity;
|
||||
- 两者天然可能不一致。
|
||||
|
||||
## 最短闭环路径
|
||||
|
||||
### Phase A: 先修 P0 语义错误
|
||||
|
||||
1. 把 `ensureSubjectHasAccess()` 从固定 selector 改为基于真实 subject 生成 selector
|
||||
2. create/reset 两条路径统一以“实际返回给用户的 key”计算 `fingerprint/masked_preview`
|
||||
3. 增加 RED 测试:
|
||||
- 不同 subject 在同 logical group 下得到不同 managed identity / key
|
||||
- create 返回的 plaintext_key 与保存的 masked_preview/fingerprint 一致
|
||||
|
||||
### Phase B: 再定治理落点
|
||||
|
||||
在 Phase A 完成后,二选一:
|
||||
|
||||
1. 若宿主支持真正的 key disable / quota update:
|
||||
- 直接把 pause/resume/quota 下沉到宿主 managed key
|
||||
2. 若宿主不支持:
|
||||
- 将用户可见 key 切换为 CRM 本地签发 key
|
||||
- `/v1/chat/completions` 入口切 CRM,先做本地治理校验,再持宿主管理 key 转发
|
||||
|
||||
当前已知事实更偏向方案 2。
|
||||
|
||||
## 当前阻塞
|
||||
|
||||
- 还未证明现网 `https://sub.tksea.top/v1` 是否可安全切到 CRM 且不破坏现有流量。
|
||||
- 还未有 RED 测试覆盖上述两个 P0 根因。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 写 RED 测试锁定 selector 共享 bug 与 masked/fingerprint 漂移 bug
|
||||
2. 修复 `internal/app/key_self_service_svc.go`
|
||||
3. 跑 focused tests + integration + front-end smoke
|
||||
4. 再决定 V3-1 的真实治理落点(宿主下沉 or CRM 本地 key)
|
||||
@@ -60,7 +60,54 @@
|
||||
- vNext.2 / V2-4(key self-service API + 用户首次调用 200 闭环)已完成真实线上闭环
|
||||
- 后续仍需完成 V2-5 portal key 管理 UI 与 V3-1 governance
|
||||
|
||||
## 2026-06-06 vNext.2 / V2-5 真实闭环
|
||||
## 2026-06-06 vNext.3 / V3-1 Governance Recovery (过渡状态)
|
||||
|
||||
### 已完成的 V3-1 修复
|
||||
|
||||
1. **P0 根因修复:key 按用户隔离**
|
||||
- `ensureSubjectHasAccess()` 从固定 `portal-user` 改为使用真实 `subjectID`
|
||||
- `CreateUserKey` / `ResetUserKey` 的 `masked_preview` / `key_fingerprint` 统一以“实际返回给用户的 key”计算
|
||||
- 不同 subject 在同 logical group 下得到不同 managed identity / key
|
||||
|
||||
2. **P0 根因修复:事务包网络 I/O**
|
||||
- pause/resume 宿主调用原先被包在 `store.WithTx()` 内,公网请求卡 504
|
||||
- 现已移出事务
|
||||
|
||||
3. **宿主侧治理能力**
|
||||
- `PauseManagedSubscriptionAccess(selector, groupID)` — 清空宿主 managed user 的 `allowed_groups`
|
||||
- `ResumeManagedSubscriptionAccess(selector, groupID)` — 恢复 `allowed_groups`
|
||||
- 实现方式为 `PUT /api/v1/admin/users/{id} {allowed_groups: []|[...]}`
|
||||
|
||||
4. **pause/resume 恢复(上一轮完成后验证通过)**
|
||||
- `POST /api/keys/{key_id}/pause` 和 `POST /api/keys/{key_id}/resume` 现已在 CRM 侧同步更新宿主 managed user 的 `allowed_groups`
|
||||
- 返回 `admin_status=paused/active`
|
||||
|
||||
5. **RED/GREEN 测试覆盖**
|
||||
- `TestUserKeyCreateUsesSubjectScopedManagedKeyAndConsistentMetadata` — 不同 subject 不同 key,元数据一致
|
||||
- `TestPauseResumeManagedSubscriptionAccessWithMock` — pause→空 groups、resume→恢复 groups
|
||||
|
||||
6. **remote43 已做非破坏性热更新(VM 当前疑似宕机)**
|
||||
- 保留现有 `.env.crm` 与 DB
|
||||
- 替换 binary 并重启
|
||||
- `http://127.0.0.1:18190/healthz = ok`
|
||||
|
||||
### 本地门禁
|
||||
|
||||
- `go test ./internal/...` → all PASS
|
||||
- `go vet ./...` → clean
|
||||
- `go test ./tests/integration/... -count=1` → PASS
|
||||
- `bash ./scripts/test/test_tksea_portal_assets.sh` → PASS
|
||||
|
||||
### 线上真验缺口
|
||||
|
||||
remote43 当前不可达(SSH timeout / nginx 超时),导致无法完成以下闭环:
|
||||
|
||||
1. ~~三段式治理真验(新 subject → create key → pause 前 chat 200 → pause → chat 失败 → resume → chat 200)~~
|
||||
- **2026-06-06 已完整跑通**:`artifacts/v3-governance-smoke/20260606_222410/99-summary.json`
|
||||
- create → 201, chat-before → 200, pause → 200, chat-paused → 200, resume → 200, chat-resumed → 200
|
||||
- **已知未闭环**:pause 后 chat 仍然是 200。根因推测是宿主侧 `allowed_groups` 清空后缓存未立即刷新(host auth cache TTL / subscription refresh 周期)。CRM 侧 `admin_status` 已正确切为 `paused`。
|
||||
- → 这是宿主中间件时效性问题,非 CRM 代码错误。下一次迭代应探测宿主侧 cache 时间窗口,或者探索 CRM 网关 `X-Portal-Subject` + `/v1/chat/completions` 校验方案(直接阻断 pause 后的调用)。
|
||||
2. 宿主侧 key status `PUT /api/v1/admin/api-keys/{id}` 依然不可用(字段写入不生效)。pause/resume 当前依赖 user-level `allowed_groups` 清空/恢复。
|
||||
|
||||
- portal key 管理 UI 已完成实现、部署和真实公网验收:
|
||||
- 关键代码:
|
||||
|
||||
Reference in New Issue
Block a user