Skip to content

Commit efd6524

Browse files
committed
merge: account card identity migration
2 parents 89a4dbc + 4fd4d01 commit efd6524

23 files changed

Lines changed: 560 additions & 114 deletions

app_mappers.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,6 @@ func mapRateLimitRuleToCore(input RateLimitRule) wailsapp.RateLimitRule {
490490
return wailsapp.RateLimitRule{
491491
ID: input.ID,
492492
AccountKey: input.AccountKey,
493-
MatchKey: input.MatchKey,
494493
Strategy: input.Strategy,
495494
Window: input.Window,
496495
LimitValue: input.LimitValue,
@@ -517,7 +516,6 @@ func mapRateLimitRule(item wailsapp.RateLimitRule) RateLimitRule {
517516
return RateLimitRule{
518517
ID: item.ID,
519518
AccountKey: item.AccountKey,
520-
MatchKey: item.MatchKey,
521519
Strategy: item.Strategy,
522520
Window: item.Window,
523521
LimitValue: item.LimitValue,
@@ -535,7 +533,6 @@ func mapRateLimitState(input *wailsapp.RateLimitState) *RateLimitState {
535533
}
536534
return &RateLimitState{
537535
AccountKey: input.AccountKey,
538-
MatchKey: input.MatchKey,
539536
Blocked: input.Blocked,
540537
BlockReason: input.BlockReason,
541538
Rules: mapRateLimitRuleStates(input.Rules),
@@ -581,7 +578,6 @@ func mapRateLimitEvents(items []wailsapp.RateLimitEvent) []RateLimitEvent {
581578
out = append(out, RateLimitEvent{
582579
ID: item.ID,
583580
AccountKey: item.AccountKey,
584-
MatchKey: item.MatchKey,
585581
RuleID: item.RuleID,
586582
Strategy: item.Strategy,
587583
Window: item.Window,

app_types.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,6 @@ type RateLimitStrategyMeta struct {
720720
type RateLimitRule struct {
721721
ID string `json:"id,omitempty"`
722722
AccountKey string `json:"accountKey"`
723-
MatchKey string `json:"matchKey,omitempty"`
724723
Strategy string `json:"strategy"`
725724
Window string `json:"window"`
726725
LimitValue int64 `json:"limitValue"`
@@ -741,7 +740,6 @@ type RateLimitRuleState struct {
741740

742741
type RateLimitState struct {
743742
AccountKey string `json:"accountKey"`
744-
MatchKey string `json:"matchKey,omitempty"`
745743
Blocked bool `json:"blocked"`
746744
BlockReason string `json:"blockReason,omitempty"`
747745
Rules []RateLimitRuleState `json:"rules"`
@@ -751,7 +749,6 @@ type RateLimitState struct {
751749
type RateLimitEvent struct {
752750
ID string `json:"id"`
753751
AccountKey string `json:"accountKey"`
754-
MatchKey string `json:"matchKey,omitempty"`
755752
RuleID string `json:"ruleID"`
756753
Strategy string `json:"strategy"`
757754
Window string `json:"window"`
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# 账号卡身份模型
2+
3+
## 结论
4+
5+
GetTokens 不引入用户实体。业务归属实体只有账号卡,账号卡 ID 即 `account_key` / `account_id`,用于 usage attribution、rate-limit、route guard、账号详情和配置编辑的稳定关联。
6+
7+
## 身份边界
8+
9+
- `account_key`:唯一业务身份,代表一张账号卡。
10+
- `auth-id` / `auth-index` / `source_hash` / provider / OAuth subject / email / API key hash:运行态证据,只用于诊断、关联和迁移辅助。
11+
- `attribution_key`:usage evidence,不允许作为 rate-limit 配置匹配键。
12+
13+
## 创建与更新语义
14+
15+
- 账号登录、新增 API key、导入、复制:创建新账号卡,分配新的 `account_key`
16+
- 重新登录、编辑当前卡凭证:更新当前账号卡的凭证和 runtime evidence,保留原 `account_key`
17+
- 两张账号卡即使凭证内容完全相同,也必须拥有不同 `account_key`
18+
19+
## 当前实现映射
20+
21+
- Codex API key:GetTokens 本地 store 生成并持久化 `local-id`,sidecar runtime `Auth.AccountKey` 使用该值。
22+
- Standalone sidecar Codex API key:缺失 `local-id` 时生成 `codex-api-key:legacy-*` 并写回 `config.yaml`
23+
- auth-file:`Auth.AccountKey = auth-file:<file-name>`
24+
- OpenAI-compatible provider:`Auth.AccountKey = openai-compatible:<provider-name>`
25+
26+
## Rate-limit 规则
27+
28+
Rate-limit 是账号卡资产级策略:
29+
30+
- `rate_limit_rules` 只存 `account_key`
31+
- `rate_limit_events` 只存 `account_key`
32+
- evaluator 查询 usage 只允许 `account_key = ?`
33+
- `match_key` 已破坏性移除,不做旧版本兼容。
34+
35+
## UI 规则
36+
37+
账号详情中的限流规则默认显示单行摘要。点击编辑进入配置态,配置态以可换行表单行展示,不使用横向滚动宽表。保存成功后回到单行摘要。
38+
39+
## 前端运行边界
40+
41+
账号详情组件不直接绑定 Wails rate-limit CRUD。`RateLimitRulesAPI` 由页面 shell 注入:desktop 注入真实 Wails 方法,browser preview 注入 `undefined` 并使用状态快照只做展示和布局验收。这样 preview 不会因为缺少 `window.go.main.App` 而崩溃,也能防止组件绕过账号卡身份模型直接调用旧接口。

docs-linhay/memory/2026-05-29.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# 2026-05-29
2+
3+
## 账号卡身份模型迁移
4+
5+
- 决策:GetTokens 本地产品模型不引入用户实体,只有账号卡;`account_key/account_id` 是账号卡唯一业务身份。
6+
- 决策:账号登录、新增、导入、复制创建新账号卡;重新登录和编辑当前凭证更新原账号卡并保留原 ID。
7+
- 决策:`auth-id/auth-index/provider/attribution_key/email/API key hash` 都是 runtime evidence,不允许作为 rate-limit 策略匹配键。
8+
- 进展:sidecar fork 已让 Codex API key、auth-file、OpenAI-compatible provider 的 runtime auth 携带 `AccountKey`
9+
- 进展:sidecar standalone Codex API key 缺失 `local-id` 时会生成 `codex-api-key:legacy-*` 并写回配置。
10+
- 进展:sidecar rate-limit schema/API/evaluator 破坏性删除 `match_key`,usage 查询只按 `account_key`
11+
- 进展:GetTokens Wails / frontend rate-limit DTO 删除 `matchKey``RateLimitRulesSection` 改为单行摘要 + 配置态,无横向滚动表格。
12+
- 进展:账号详情 rate-limit CRUD 改为由页面 shell 注入,browser preview 不再直接触发真实 Wails binding。
13+
- 验证:Sidecar 聚焦 Go 测试、GetTokens Go 测试、frontend `npm run typecheck``npm run build``npm run test:unit` 均通过。
14+
- 验证:已用 `playwright-cli` 验收账号详情限流区 summary / config 态,确认无 Wails binding 崩溃并归档截图。
15+
- 验证:已用当前 GetTokens worktree 构建产物加载当前 sidecar worktree,dev sidecar `/healthz` 200;rate-limit management API 的 strategies/create/status/events/delete/list 链路均 200,临时规则已清理。
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# 账号卡身份模型迁移
2+
3+
## 背景
4+
5+
当前账号池存在两套身份语义:
6+
7+
1. GetTokens 前端账号卡使用 `AccountRecord.id`,例如 `codex-api-key:<local-id>`
8+
2. sidecar 请求热路径和 usage attribution 使用 `auth-id` / `auth-index` / `provider` 等 runtime evidence。
9+
10+
这种分层在展示上可以通过 Wails join 兜底,但在 rate-limit 这种需要稳定策略匹配的能力上会产生错配:规则绑定账号卡 ID,实际用量事件可能只写 runtime evidence,导致账号卡下的单日限量规则用量显示为 0。
11+
12+
本次迁移把产品模型收敛为:**没有用户实体,只有账号卡;账号卡 ID 是所有业务归属的唯一身份源**
13+
14+
## 目标
15+
16+
1. `account_key` / `account_id` 唯一代表一张账号卡。
17+
2. 登录/导入/复制创建新账号卡并分配新 `account_key`
18+
3. 重新登录从现有账号卡发起,只更新凭证和 runtime evidence,保留原 `account_key`
19+
4. sidecar runtime auth candidate 必须携带账号卡 `account_key`
20+
5. usage attribution 新事件直接写入 `account_key``attribution_key` 只作为诊断 evidence。
21+
6. rate-limit 规则、状态、事件只按 `account_key` 匹配,破坏性移除 `match_key`
22+
23+
## 范围
24+
25+
### GetTokens App
26+
27+
- Wails / management client rate-limit DTO 删除 `matchKey`
28+
- 前端 `RateLimitRulesSection` 只保存 `accountKey`,保留单行摘要 + 编辑配置态。
29+
- 账号卡文档和测试补充“登录创建卡,重新登录更新卡”的身份语义。
30+
31+
### CLIProxyAPI Sidecar Fork
32+
33+
- `config.CodexKey` 增加并持久化 `local-id`
34+
- runtime `auth.Auth` 增加 `AccountKey`
35+
- watcher synthesizer 为 Codex API key、auth-file、OpenAI-compatible provider 填充账号卡 ID。
36+
- usage attribution 新事件必须尽可能写入 `account_key`
37+
- rate-limit SQLite schema/API/DTO/evaluator 破坏性移除 `match_key`
38+
39+
## 非目标
40+
41+
- 不兼容旧版本前端或旧 sidecar API。
42+
- 不保留 rate-limit 表中的 `match_key` 数据。
43+
- 不引入服务端多用户体系;OAuth subject / email / API key hash 都只是账号卡 evidence。
44+
- 不把 SQLite `rowid` / 自增 ID 作为产品身份。
45+
46+
## 验收标准
47+
48+
1. sidecar runtime 中每个 GetTokens 管理的 auth candidate 都带稳定 `account_key`
49+
2. 两张账号卡即使凭证内容相同,也拥有不同 `account_key`,usage 和 rate-limit 不串账。
50+
3. 重新登录账号卡后 `account_key` 不变,旧/new runtime evidence 都归属同一账号卡。
51+
4.`usage_attribution_events` 对 GetTokens 管理账号必须写入非空 `account_key`
52+
5. `rate_limit_rules` / `rate_limit_events` schema 不再包含 `match_key`
53+
6. sidecar rate-limit evaluator 查询 usage 时只使用 `account_key = ?`
54+
7. GetTokens frontend / Wails / API 类型不再出现 rate-limit `matchKey`
55+
8. 文档和 memory 写入本次身份模型决策,并完成 `qmd update && qmd embed`
56+
57+
## 设计稿入口
58+
59+
- 本期设计稿:`(未产出)`
60+
- 约束:单期只保留一个 HTML 文件;若存在多稿对比,也必须收敛在同一个 HTML 文件内。
61+
62+
## Worktree 映射
63+
64+
- branch:`feat/20260529-account-card-identity-migration`
65+
- worktree:`../GetTokens-worktrees/20260529-account-card-identity-migration/`
66+
- sidecar branch:`gettokens/account-card-identity-migration`
67+
- sidecar worktree:`../CLIProxyAPI-worktrees/20260529-account-card-identity-migration/`
68+
69+
## 相关链接
70+
71+
- 既有限流 space:`docs-linhay/spaces/20260515-rate-limit-middleware/`
72+
- sidecar usage attribution 架构:`docs-linhay/dev/20260514-sidecar-usage-account-attribution-architecture.md`
73+
- 账号详情 runtime 观测边界:`docs-linhay/dev/20260519-account-detail-runtime-observability-boundary.md`
74+
75+
## 当前状态
76+
- 状态:implemented-in-worktrees
77+
- 最近更新:2026-05-29
78+
79+
## 2026-05-29 执行结果
80+
81+
- Sidecar fork 已接入 `Auth.AccountKey`,Codex API key 使用 `local-id`,auth-file 使用 `auth-file:<file-name>`,OpenAI-compatible 使用 `openai-compatible:<provider-name>`
82+
- Sidecar fork 对缺失 `local-id` 的 standalone Codex API key 会生成 `codex-api-key:legacy-*` 并写回配置。
83+
- Sidecar usage attribution 写入 `account_key`,rate-limit schema / evaluator / API 已破坏性移除 `match_key`
84+
- GetTokens Wails / cliproxyapi / frontend rate-limit DTO 已移除 `matchKey`
85+
- `RateLimitRulesSection` 默认展示单行摘要,点击编辑进入配置态;配置态不再使用横向滚动表格。
86+
- 账号详情弹窗的 rate-limit CRUD 改为由页面 shell 注入;browser preview 不再直接触发真实 Wails binding。
87+
- 登录语义保持为:新登录产生新账号卡;从账号详情发起重新登录时回填到原 auth-file 名称,因此保留原账号卡 ID。
88+
89+
## 当前验证
90+
91+
- Sidecar:`go test ./internal/config ./internal/watcher/synthesizer ./internal/gettokenshooks ./internal/runtime/executor/helps ./sdk/cliproxy/usage ./sdk/cliproxy/auth ./internal/api/handlers/management`
92+
- GetTokens:`go test ./internal/sidecar ./internal/cliproxyapi ./internal/wailsapp`
93+
- GetTokens frontend:`npm run typecheck`
94+
- GetTokens frontend:`npm run build`
95+
- GetTokens frontend:`npm run test:unit`
96+
- Build smoke:`CLI_PROXY_SOURCE_DIR=../CLIProxyAPI-worktrees/20260529-account-card-identity-migration ./scripts/wails-cli.sh build`,确认打包产物使用 sidecar 提交 `3837f0a3`
97+
- Dev runtime smoke:以 `GETTOKENS_APP_PROFILE=dev` 启动构建产物,sidecar 监听 `18317``/healthz` 返回 200。
98+
- Rate-limit management smoke:对 dev sidecar 执行 `strategies -> create rule -> status -> events -> delete rule -> list`,状态码均为 200,临时规则 `runtime-smoke-delete-me` 已删除。
99+
- `playwright-cli` preview:打开 `?preview=accounts#frame=accounts&detail=codex-api-key%3Astable-001`,确认无 `Cannot read properties of undefined`,summary 为单行,点击编辑进入配置表单态。
100+
- 截图:
101+
- `docs-linhay/spaces/20260529-account-card-identity-migration/screenshots/20260529/accounts/20260529-accounts-rate-limit-summary-after-v01.png`
102+
- `docs-linhay/spaces/20260529-account-card-identity-migration/screenshots/20260529/accounts/20260529-accounts-rate-limit-config-after-v01.png`
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# 账号卡身份模型迁移执行计划 v01
2+
3+
## 核心决策
4+
5+
1. 本地产品实体只有账号卡,没有用户实体。
6+
2. `account_key` / `account_id` 是账号卡的唯一业务身份。
7+
3. `auth-id` / `auth-index` / `source_hash` / `provider` / OAuth subject / email / API key hash 只作为 runtime evidence。
8+
4. rate-limit 是账号卡资产级策略,只能以 `account_key` 为匹配键。
9+
5. 本次不兼容旧版本:迁移后移除 rate-limit `match_key` schema、DTO、API 和前端字段。
10+
11+
## 数据模型
12+
13+
### Sidecar Config
14+
15+
`config.CodexKey` 增加:
16+
17+
```go
18+
LocalID string `yaml:"local-id,omitempty" json:"local-id,omitempty"`
19+
```
20+
21+
规则:
22+
23+
- 账号登录 / 新增 key:创建新账号卡,生成新 `local-id`
24+
- 重新登录 / 编辑当前卡凭证:保留原 `local-id`
25+
- standalone sidecar 发现缺失 `local-id` 的 codex key:启动或保存时生成并持久化。
26+
27+
### Runtime Auth
28+
29+
`sdk/cliproxy/auth.Auth` 增加:
30+
31+
```go
32+
AccountKey string `json:"account_key,omitempty"`
33+
```
34+
35+
runtime candidate 生成规则:
36+
37+
- Codex API key:`config.CodexKey.LocalID`
38+
- auth-file:`auth-file:<file-name>`
39+
- OpenAI-compatible:`openai-compatible:<provider-name>`
40+
41+
### SQLite
42+
43+
`usage_attribution_events` 保留:
44+
45+
- `account_key`:业务归属,必须优先写入。
46+
- `attribution_key`:运行态证据,仅用于诊断和迁移回填。
47+
48+
`rate_limit_rules` 删除:
49+
50+
- `match_key`
51+
- `idx_rate_limit_rules_match`
52+
53+
`rate_limit_events` 删除:
54+
55+
- `match_key`
56+
57+
## 执行阶段
58+
59+
### 阶段 1:红灯测试(已完成)
60+
61+
Sidecar tests:
62+
63+
1. `CodexKey` 缺失 `local-id` 时,synthesizer/runtime auth 必须失败当前新测试。
64+
2. `Auth` 缺失 `AccountKey` 时,runtime identity 测试失败。
65+
3. rate-limit schema 仍含 `match_key` 的结构测试失败。
66+
4. evaluator 仍使用 `attribution_key` fallback 的源码结构测试失败。
67+
68+
GetTokens tests:
69+
70+
1. 前端 rate-limit 源码不允许出现 `matchKey` 的测试先失败。
71+
2. Wails / cliproxyapi DTO 不允许出现 `MatchKey` 的测试先失败。
72+
73+
### 阶段 2:sidecar identity foundation(已完成)
74+
75+
修改:
76+
77+
- `internal/config/config.go`
78+
- `internal/api/handlers/management/config_lists.go`
79+
- `internal/watcher/synthesizer/config.go`
80+
- `sdk/cliproxy/auth/types.go`
81+
- 必要时补充 auth-file / OpenAI-compatible synthesizer 的 `AccountKey`
82+
83+
验收:
84+
85+
- `go test ./internal/config ./internal/api/handlers/management ./internal/watcher/synthesizer ./sdk/cliproxy/auth`
86+
87+
### 阶段 3:usage attribution account_key 写入(已完成)
88+
89+
修改:
90+
91+
- `internal/gettokenshooks/usage_attribution.go`
92+
- `internal/gettokenshooks/usage_attribution_test.go`
93+
94+
验收:
95+
96+
- GetTokens 管理账号的新 usage event 必须有 `account_key`
97+
- unresolved event 保留 `attribution_key`,但不参与账号卡策略。
98+
99+
### 阶段 4:rate-limit 破坏性清理(已完成)
100+
101+
修改:
102+
103+
- `internal/gettokenshooks/rate_limit.go`
104+
- `internal/gettokenshooks/rate_limit_test.go`
105+
106+
验收:
107+
108+
- `PRAGMA table_info(rate_limit_rules)` 不含 `match_key`
109+
- `PRAGMA table_info(rate_limit_events)` 不含 `match_key`
110+
- usage 查询只按 `account_key = ?`
111+
- 两个相同凭证不同账号卡的用量和规则互不影响。
112+
113+
### 阶段 5:GetTokens bridge/frontend 清理(已完成)
114+
115+
修改:
116+
117+
- `internal/cliproxyapi/types.go`
118+
- `internal/cliproxyapi/client_test.go`
119+
- `internal/wailsapp/rate_limit_test.go`
120+
- `app_types.go`
121+
- `app_mappers.go`
122+
- `frontend/src/features/accounts/model/rateLimit.ts`
123+
- `frontend/src/features/accounts/components/RateLimitRulesSection.tsx`
124+
- `frontend/src/features/accounts/tests/rateLimit.test.mjs`
125+
126+
验收:
127+
128+
- 前端和 Wails rate-limit 类型不再出现 `matchKey` / `MatchKey`
129+
- UI 保持单行摘要 + 编辑配置态,不再横向滚动。
130+
- 账号详情弹窗不直接 import Wails rate-limit CRUD;由 Accounts/Codex page shell 根据 desktop/preview 注入 `RateLimitRulesAPI`
131+
132+
### 阶段 6:文档、memory、索引(已完成)
133+
134+
修改:
135+
136+
- 本 space README / plan
137+
- `docs-linhay/dev/account-card-identity-model.md`
138+
- `docs-linhay/memory/2026-05-29.md`
139+
140+
命令:
141+
142+
```bash
143+
docs-linhay/scripts/check-docs.sh
144+
qmd update
145+
qmd embed
146+
```
147+
148+
## 验证命令
149+
150+
Sidecar:
151+
152+
```bash
153+
go test ./internal/gettokenshooks
154+
go test ./internal/config ./internal/api/handlers/management ./internal/watcher/synthesizer ./sdk/cliproxy/auth
155+
```
156+
157+
GetTokens:
158+
159+
```bash
160+
go test ./internal/cliproxyapi ./internal/wailsapp
161+
cd frontend && node --test src/features/accounts/tests/rateLimit.test.mjs
162+
cd frontend && npm run typecheck
163+
docs-linhay/scripts/check-docs.sh
164+
qmd update && qmd embed
165+
```
166+
167+
## 风险
168+
169+
1. WebSocket pinned auth 可能存在独立路径,必须确认同样携带 `AccountKey`
170+
2. 破坏性迁移会丢弃无法归属账号卡的旧 rate-limit 规则;这是本次明确接受的行为。
171+
172+
## 执行记录
173+
174+
- 2026-05-29:sidecar runtime auth 已覆盖 Codex API key、auth-file、OpenAI-compatible provider 的 `AccountKey`
175+
- 2026-05-29:sidecar standalone Codex API key 缺失 `local-id` 时会生成 `codex-api-key:legacy-*` 并写回配置。
176+
- 2026-05-29:sidecar rate-limit schema/API/evaluator 删除 `match_key`,只按 `account_key` 查询 usage。
177+
- 2026-05-29:GetTokens Wails / frontend rate-limit DTO 删除 `matchKey`,规则区改为单行摘要 + 配置态。
178+
- 2026-05-29:修复 browser preview 根因,`UnifiedAccountDetailModal` / `CodexAccountDetailModal` 不再直连真实 Wails rate-limit CRUD。
179+
- 2026-05-29:已运行 Sidecar 聚焦 Go 测试、GetTokens Go 测试、frontend `typecheck``build``test:unit`
180+
- 2026-05-29:已用 `playwright-cli` 验收账号详情限流区 summary / config 态,并归档截图。

0 commit comments

Comments
 (0)