Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions app_mappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,6 @@ func mapRateLimitRuleToCore(input RateLimitRule) wailsapp.RateLimitRule {
return wailsapp.RateLimitRule{
ID: input.ID,
AccountKey: input.AccountKey,
MatchKey: input.MatchKey,
Strategy: input.Strategy,
Window: input.Window,
LimitValue: input.LimitValue,
Expand All @@ -517,7 +516,6 @@ func mapRateLimitRule(item wailsapp.RateLimitRule) RateLimitRule {
return RateLimitRule{
ID: item.ID,
AccountKey: item.AccountKey,
MatchKey: item.MatchKey,
Strategy: item.Strategy,
Window: item.Window,
LimitValue: item.LimitValue,
Expand All @@ -535,7 +533,6 @@ func mapRateLimitState(input *wailsapp.RateLimitState) *RateLimitState {
}
return &RateLimitState{
AccountKey: input.AccountKey,
MatchKey: input.MatchKey,
Blocked: input.Blocked,
BlockReason: input.BlockReason,
Rules: mapRateLimitRuleStates(input.Rules),
Expand Down Expand Up @@ -581,7 +578,6 @@ func mapRateLimitEvents(items []wailsapp.RateLimitEvent) []RateLimitEvent {
out = append(out, RateLimitEvent{
ID: item.ID,
AccountKey: item.AccountKey,
MatchKey: item.MatchKey,
RuleID: item.RuleID,
Strategy: item.Strategy,
Window: item.Window,
Expand Down
3 changes: 0 additions & 3 deletions app_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,6 @@ type RateLimitStrategyMeta struct {
type RateLimitRule struct {
ID string `json:"id,omitempty"`
AccountKey string `json:"accountKey"`
MatchKey string `json:"matchKey,omitempty"`
Strategy string `json:"strategy"`
Window string `json:"window"`
LimitValue int64 `json:"limitValue"`
Expand All @@ -741,7 +740,6 @@ type RateLimitRuleState struct {

type RateLimitState struct {
AccountKey string `json:"accountKey"`
MatchKey string `json:"matchKey,omitempty"`
Blocked bool `json:"blocked"`
BlockReason string `json:"blockReason,omitempty"`
Rules []RateLimitRuleState `json:"rules"`
Expand All @@ -751,7 +749,6 @@ type RateLimitState struct {
type RateLimitEvent struct {
ID string `json:"id"`
AccountKey string `json:"accountKey"`
MatchKey string `json:"matchKey,omitempty"`
RuleID string `json:"ruleID"`
Strategy string `json:"strategy"`
Window string `json:"window"`
Expand Down
41 changes: 41 additions & 0 deletions docs-linhay/dev/account-card-identity-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 账号卡身份模型

## 结论

GetTokens 不引入用户实体。业务归属实体只有账号卡,账号卡 ID 即 `account_key` / `account_id`,用于 usage attribution、rate-limit、route guard、账号详情和配置编辑的稳定关联。

## 身份边界

- `account_key`:唯一业务身份,代表一张账号卡。
- `auth-id` / `auth-index` / `source_hash` / provider / OAuth subject / email / API key hash:运行态证据,只用于诊断、关联和迁移辅助。
- `attribution_key`:usage evidence,不允许作为 rate-limit 配置匹配键。

## 创建与更新语义

- 账号登录、新增 API key、导入、复制:创建新账号卡,分配新的 `account_key`。
- 重新登录、编辑当前卡凭证:更新当前账号卡的凭证和 runtime evidence,保留原 `account_key`。
- 两张账号卡即使凭证内容完全相同,也必须拥有不同 `account_key`。

## 当前实现映射

- Codex API key:GetTokens 本地 store 生成并持久化 `local-id`,sidecar runtime `Auth.AccountKey` 使用该值。
- Standalone sidecar Codex API key:缺失 `local-id` 时生成 `codex-api-key:legacy-*` 并写回 `config.yaml`。
- auth-file:`Auth.AccountKey = auth-file:<file-name>`。
- OpenAI-compatible provider:`Auth.AccountKey = openai-compatible:<provider-name>`。

## Rate-limit 规则

Rate-limit 是账号卡资产级策略:

- `rate_limit_rules` 只存 `account_key`。
- `rate_limit_events` 只存 `account_key`。
- evaluator 查询 usage 只允许 `account_key = ?`。
- `match_key` 已破坏性移除,不做旧版本兼容。

## UI 规则

账号详情中的限流规则默认显示单行摘要。点击编辑进入配置态,配置态以可换行表单行展示,不使用横向滚动宽表。保存成功后回到单行摘要。

## 前端运行边界

账号详情组件不直接绑定 Wails rate-limit CRUD。`RateLimitRulesAPI` 由页面 shell 注入:desktop 注入真实 Wails 方法,browser preview 注入 `undefined` 并使用状态快照只做展示和布局验收。这样 preview 不会因为缺少 `window.go.main.App` 而崩溃,也能防止组件绕过账号卡身份模型直接调用旧接口。
15 changes: 15 additions & 0 deletions docs-linhay/memory/2026-05-29.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 2026-05-29

## 账号卡身份模型迁移

- 决策:GetTokens 本地产品模型不引入用户实体,只有账号卡;`account_key/account_id` 是账号卡唯一业务身份。
- 决策:账号登录、新增、导入、复制创建新账号卡;重新登录和编辑当前凭证更新原账号卡并保留原 ID。
- 决策:`auth-id/auth-index/provider/attribution_key/email/API key hash` 都是 runtime evidence,不允许作为 rate-limit 策略匹配键。
- 进展:sidecar fork 已让 Codex API key、auth-file、OpenAI-compatible provider 的 runtime auth 携带 `AccountKey`。
- 进展:sidecar standalone Codex API key 缺失 `local-id` 时会生成 `codex-api-key:legacy-*` 并写回配置。
- 进展:sidecar rate-limit schema/API/evaluator 破坏性删除 `match_key`,usage 查询只按 `account_key`。
- 进展:GetTokens Wails / frontend rate-limit DTO 删除 `matchKey`,`RateLimitRulesSection` 改为单行摘要 + 配置态,无横向滚动表格。
- 进展:账号详情 rate-limit CRUD 改为由页面 shell 注入,browser preview 不再直接触发真实 Wails binding。
- 验证:Sidecar 聚焦 Go 测试、GetTokens Go 测试、frontend `npm run typecheck`、`npm run build`、`npm run test:unit` 均通过。
- 验证:已用 `playwright-cli` 验收账号详情限流区 summary / config 态,确认无 Wails binding 崩溃并归档截图。
- 验证:已用当前 GetTokens worktree 构建产物加载当前 sidecar worktree,dev sidecar `/healthz` 200;rate-limit management API 的 strategies/create/status/events/delete/list 链路均 200,临时规则已清理。
102 changes: 102 additions & 0 deletions docs-linhay/spaces/20260529-account-card-identity-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 账号卡身份模型迁移

## 背景

当前账号池存在两套身份语义:

1. GetTokens 前端账号卡使用 `AccountRecord.id`,例如 `codex-api-key:<local-id>`。
2. sidecar 请求热路径和 usage attribution 使用 `auth-id` / `auth-index` / `provider` 等 runtime evidence。

这种分层在展示上可以通过 Wails join 兜底,但在 rate-limit 这种需要稳定策略匹配的能力上会产生错配:规则绑定账号卡 ID,实际用量事件可能只写 runtime evidence,导致账号卡下的单日限量规则用量显示为 0。

本次迁移把产品模型收敛为:**没有用户实体,只有账号卡;账号卡 ID 是所有业务归属的唯一身份源**。

## 目标

1. `account_key` / `account_id` 唯一代表一张账号卡。
2. 登录/导入/复制创建新账号卡并分配新 `account_key`。
3. 重新登录从现有账号卡发起,只更新凭证和 runtime evidence,保留原 `account_key`。
4. sidecar runtime auth candidate 必须携带账号卡 `account_key`。
5. usage attribution 新事件直接写入 `account_key`;`attribution_key` 只作为诊断 evidence。
6. rate-limit 规则、状态、事件只按 `account_key` 匹配,破坏性移除 `match_key`。

## 范围

### GetTokens App

- Wails / management client rate-limit DTO 删除 `matchKey`。
- 前端 `RateLimitRulesSection` 只保存 `accountKey`,保留单行摘要 + 编辑配置态。
- 账号卡文档和测试补充“登录创建卡,重新登录更新卡”的身份语义。

### CLIProxyAPI Sidecar Fork

- `config.CodexKey` 增加并持久化 `local-id`。
- runtime `auth.Auth` 增加 `AccountKey`。
- watcher synthesizer 为 Codex API key、auth-file、OpenAI-compatible provider 填充账号卡 ID。
- usage attribution 新事件必须尽可能写入 `account_key`。
- rate-limit SQLite schema/API/DTO/evaluator 破坏性移除 `match_key`。

## 非目标

- 不兼容旧版本前端或旧 sidecar API。
- 不保留 rate-limit 表中的 `match_key` 数据。
- 不引入服务端多用户体系;OAuth subject / email / API key hash 都只是账号卡 evidence。
- 不把 SQLite `rowid` / 自增 ID 作为产品身份。

## 验收标准

1. sidecar runtime 中每个 GetTokens 管理的 auth candidate 都带稳定 `account_key`。
2. 两张账号卡即使凭证内容相同,也拥有不同 `account_key`,usage 和 rate-limit 不串账。
3. 重新登录账号卡后 `account_key` 不变,旧/new runtime evidence 都归属同一账号卡。
4. 新 `usage_attribution_events` 对 GetTokens 管理账号必须写入非空 `account_key`。
5. `rate_limit_rules` / `rate_limit_events` schema 不再包含 `match_key`。
6. sidecar rate-limit evaluator 查询 usage 时只使用 `account_key = ?`。
7. GetTokens frontend / Wails / API 类型不再出现 rate-limit `matchKey`。
8. 文档和 memory 写入本次身份模型决策,并完成 `qmd update && qmd embed`。

## 设计稿入口

- 本期设计稿:`(未产出)`
- 约束:单期只保留一个 HTML 文件;若存在多稿对比,也必须收敛在同一个 HTML 文件内。

## Worktree 映射

- branch:`feat/20260529-account-card-identity-migration`
- worktree:`../GetTokens-worktrees/20260529-account-card-identity-migration/`
- sidecar branch:`gettokens/account-card-identity-migration`
- sidecar worktree:`../CLIProxyAPI-worktrees/20260529-account-card-identity-migration/`

## 相关链接

- 既有限流 space:`docs-linhay/spaces/20260515-rate-limit-middleware/`
- sidecar usage attribution 架构:`docs-linhay/dev/20260514-sidecar-usage-account-attribution-architecture.md`
- 账号详情 runtime 观测边界:`docs-linhay/dev/20260519-account-detail-runtime-observability-boundary.md`

## 当前状态
- 状态:implemented-in-worktrees
- 最近更新:2026-05-29

## 2026-05-29 执行结果

- Sidecar fork 已接入 `Auth.AccountKey`,Codex API key 使用 `local-id`,auth-file 使用 `auth-file:<file-name>`,OpenAI-compatible 使用 `openai-compatible:<provider-name>`。
- Sidecar fork 对缺失 `local-id` 的 standalone Codex API key 会生成 `codex-api-key:legacy-*` 并写回配置。
- Sidecar usage attribution 写入 `account_key`,rate-limit schema / evaluator / API 已破坏性移除 `match_key`。
- GetTokens Wails / cliproxyapi / frontend rate-limit DTO 已移除 `matchKey`。
- `RateLimitRulesSection` 默认展示单行摘要,点击编辑进入配置态;配置态不再使用横向滚动表格。
- 账号详情弹窗的 rate-limit CRUD 改为由页面 shell 注入;browser preview 不再直接触发真实 Wails binding。
- 登录语义保持为:新登录产生新账号卡;从账号详情发起重新登录时回填到原 auth-file 名称,因此保留原账号卡 ID。

## 当前验证

- Sidecar:`go test ./internal/config ./internal/watcher/synthesizer ./internal/gettokenshooks ./internal/runtime/executor/helps ./sdk/cliproxy/usage ./sdk/cliproxy/auth ./internal/api/handlers/management`
- GetTokens:`go test ./internal/sidecar ./internal/cliproxyapi ./internal/wailsapp`
- GetTokens frontend:`npm run typecheck`
- GetTokens frontend:`npm run build`
- GetTokens frontend:`npm run test:unit`
- Build smoke:`CLI_PROXY_SOURCE_DIR=../CLIProxyAPI-worktrees/20260529-account-card-identity-migration ./scripts/wails-cli.sh build`,确认打包产物使用 sidecar 提交 `3837f0a3`。
- Dev runtime smoke:以 `GETTOKENS_APP_PROFILE=dev` 启动构建产物,sidecar 监听 `18317`,`/healthz` 返回 200。
- Rate-limit management smoke:对 dev sidecar 执行 `strategies -> create rule -> status -> events -> delete rule -> list`,状态码均为 200,临时规则 `runtime-smoke-delete-me` 已删除。
- `playwright-cli` preview:打开 `?preview=accounts#frame=accounts&detail=codex-api-key%3Astable-001`,确认无 `Cannot read properties of undefined`,summary 为单行,点击编辑进入配置表单态。
- 截图:
- `docs-linhay/spaces/20260529-account-card-identity-migration/screenshots/20260529/accounts/20260529-accounts-rate-limit-summary-after-v01.png`
- `docs-linhay/spaces/20260529-account-card-identity-migration/screenshots/20260529/accounts/20260529-accounts-rate-limit-config-after-v01.png`
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# 账号卡身份模型迁移执行计划 v01

## 核心决策

1. 本地产品实体只有账号卡,没有用户实体。
2. `account_key` / `account_id` 是账号卡的唯一业务身份。
3. `auth-id` / `auth-index` / `source_hash` / `provider` / OAuth subject / email / API key hash 只作为 runtime evidence。
4. rate-limit 是账号卡资产级策略,只能以 `account_key` 为匹配键。
5. 本次不兼容旧版本:迁移后移除 rate-limit `match_key` schema、DTO、API 和前端字段。

## 数据模型

### Sidecar Config

`config.CodexKey` 增加:

```go
LocalID string `yaml:"local-id,omitempty" json:"local-id,omitempty"`
```

规则:

- 账号登录 / 新增 key:创建新账号卡,生成新 `local-id`。
- 重新登录 / 编辑当前卡凭证:保留原 `local-id`。
- standalone sidecar 发现缺失 `local-id` 的 codex key:启动或保存时生成并持久化。

### Runtime Auth

`sdk/cliproxy/auth.Auth` 增加:

```go
AccountKey string `json:"account_key,omitempty"`
```

runtime candidate 生成规则:

- Codex API key:`config.CodexKey.LocalID`
- auth-file:`auth-file:<file-name>`
- OpenAI-compatible:`openai-compatible:<provider-name>`

### SQLite

`usage_attribution_events` 保留:

- `account_key`:业务归属,必须优先写入。
- `attribution_key`:运行态证据,仅用于诊断和迁移回填。

`rate_limit_rules` 删除:

- `match_key`
- `idx_rate_limit_rules_match`

`rate_limit_events` 删除:

- `match_key`

## 执行阶段

### 阶段 1:红灯测试(已完成)

Sidecar tests:

1. `CodexKey` 缺失 `local-id` 时,synthesizer/runtime auth 必须失败当前新测试。
2. `Auth` 缺失 `AccountKey` 时,runtime identity 测试失败。
3. rate-limit schema 仍含 `match_key` 的结构测试失败。
4. evaluator 仍使用 `attribution_key` fallback 的源码结构测试失败。

GetTokens tests:

1. 前端 rate-limit 源码不允许出现 `matchKey` 的测试先失败。
2. Wails / cliproxyapi DTO 不允许出现 `MatchKey` 的测试先失败。

### 阶段 2:sidecar identity foundation(已完成)

修改:

- `internal/config/config.go`
- `internal/api/handlers/management/config_lists.go`
- `internal/watcher/synthesizer/config.go`
- `sdk/cliproxy/auth/types.go`
- 必要时补充 auth-file / OpenAI-compatible synthesizer 的 `AccountKey`

验收:

- `go test ./internal/config ./internal/api/handlers/management ./internal/watcher/synthesizer ./sdk/cliproxy/auth`

### 阶段 3:usage attribution account_key 写入(已完成)

修改:

- `internal/gettokenshooks/usage_attribution.go`
- `internal/gettokenshooks/usage_attribution_test.go`

验收:

- GetTokens 管理账号的新 usage event 必须有 `account_key`。
- unresolved event 保留 `attribution_key`,但不参与账号卡策略。

### 阶段 4:rate-limit 破坏性清理(已完成)

修改:

- `internal/gettokenshooks/rate_limit.go`
- `internal/gettokenshooks/rate_limit_test.go`

验收:

- `PRAGMA table_info(rate_limit_rules)` 不含 `match_key`。
- `PRAGMA table_info(rate_limit_events)` 不含 `match_key`。
- usage 查询只按 `account_key = ?`。
- 两个相同凭证不同账号卡的用量和规则互不影响。

### 阶段 5:GetTokens bridge/frontend 清理(已完成)

修改:

- `internal/cliproxyapi/types.go`
- `internal/cliproxyapi/client_test.go`
- `internal/wailsapp/rate_limit_test.go`
- `app_types.go`
- `app_mappers.go`
- `frontend/src/features/accounts/model/rateLimit.ts`
- `frontend/src/features/accounts/components/RateLimitRulesSection.tsx`
- `frontend/src/features/accounts/tests/rateLimit.test.mjs`

验收:

- 前端和 Wails rate-limit 类型不再出现 `matchKey` / `MatchKey`。
- UI 保持单行摘要 + 编辑配置态,不再横向滚动。
- 账号详情弹窗不直接 import Wails rate-limit CRUD;由 Accounts/Codex page shell 根据 desktop/preview 注入 `RateLimitRulesAPI`。

### 阶段 6:文档、memory、索引(已完成)

修改:

- 本 space README / plan
- `docs-linhay/dev/account-card-identity-model.md`
- `docs-linhay/memory/2026-05-29.md`

命令:

```bash
docs-linhay/scripts/check-docs.sh
qmd update
qmd embed
```

## 验证命令

Sidecar:

```bash
go test ./internal/gettokenshooks
go test ./internal/config ./internal/api/handlers/management ./internal/watcher/synthesizer ./sdk/cliproxy/auth
```

GetTokens:

```bash
go test ./internal/cliproxyapi ./internal/wailsapp
cd frontend && node --test src/features/accounts/tests/rateLimit.test.mjs
cd frontend && npm run typecheck
docs-linhay/scripts/check-docs.sh
qmd update && qmd embed
```

## 风险

1. WebSocket pinned auth 可能存在独立路径,必须确认同样携带 `AccountKey`。
2. 破坏性迁移会丢弃无法归属账号卡的旧 rate-limit 规则;这是本次明确接受的行为。

## 执行记录

- 2026-05-29:sidecar runtime auth 已覆盖 Codex API key、auth-file、OpenAI-compatible provider 的 `AccountKey`。
- 2026-05-29:sidecar standalone Codex API key 缺失 `local-id` 时会生成 `codex-api-key:legacy-*` 并写回配置。
- 2026-05-29:sidecar rate-limit schema/API/evaluator 删除 `match_key`,只按 `account_key` 查询 usage。
- 2026-05-29:GetTokens Wails / frontend rate-limit DTO 删除 `matchKey`,规则区改为单行摘要 + 配置态。
- 2026-05-29:修复 browser preview 根因,`UnifiedAccountDetailModal` / `CodexAccountDetailModal` 不再直连真实 Wails rate-limit CRUD。
- 2026-05-29:已运行 Sidecar 聚焦 Go 测试、GetTokens Go 测试、frontend `typecheck`、`build` 和 `test:unit`。
- 2026-05-29:已用 `playwright-cli` 验收账号详情限流区 summary / config 态,并归档截图。
Loading
Loading