Skip to content

Commit 99f1974

Browse files
committed
feat(planning): enable auto chain in config and update migration plan for persistent volume name
1 parent a202563 commit 99f1974

6 files changed

Lines changed: 464 additions & 315 deletions

File tree

.planning/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"research_before_questions": false,
2727
"discuss_mode": "discuss",
2828
"skip_discuss": false,
29-
"_auto_chain_active": false
29+
"_auto_chain_active": true
3030
},
3131
"hooks": {
3232
"context_warnings": true

.planning/phases/30-entry-api/plans/01-migration-entry-store/PLAN.md

Lines changed: 78 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,124 +14,134 @@ requirements:
1414
- REQ-F7-A
1515
must_haves:
1616
truths:
17-
- "存在 `0014_claude_account_persistent_volume.sql`,对 `claude_accounts` 增加可空 `persistent_volume_name`(语义见 D-02)"
18-
- "`ClaudeAccount` 模型可表达该列的 NULL/非 NULL;`GetHostByShortID` 返回 `hosts.template_image_ref` 供 Entry 推导镜像 tag(D-06 前置)"
19-
- "`Repository.ResolveClaudeAccountIDForEntry`(或等价命名)严格按 D-05 两条 SQL 顺序解析;无匹配时 `ok=false` 且非错误"
17+
- "数据层具备 `persistent_volume_name` 列,且 `NULL` 语义可表达“未分配”状态(D-01, D-02, D-10"
18+
- "仓储层可按 D-05 规则稳定解析 `claude_account_id`,并返回 Entry API 所需 `template_image_ref` 输入"
19+
- "本计划不触碰 HTTP/客户端协议,避免与 Wave 2 计划重叠"
2020
artifacts:
2121
- path: internal/store/migrations/0014_claude_account_persistent_volume.sql
22-
provides: "ADD COLUMN IF NOT EXISTS persistent_volume_name;含可手工执行的 down 片段注释(DROP IF EXISTS)"
22+
provides: "claude_accounts.persistent_volume_name schema 变更与安全回滚注释"
2323
contains: persistent_volume_name
2424
- path: internal/store/repository/models.go
25-
provides: "ClaudeAccount.PersistentVolumeName 可空字段"
25+
provides: "ClaudeAccount 可空字段映射"
2626
contains: PersistentVolumeName
2727
- path: internal/store/repository/queries.go
28-
provides: "HostSSHAuth 扩展 TemplateImageRef;Resolve 方法"
28+
provides: "ResolveClaudeAccountIDForEntry + template_image_ref 查询输出"
2929
contains: ResolveClaudeAccountIDForEntry
3030
key_links:
31+
- from: ResolveClaudeAccountIDForEntry
32+
to: claude_accounts
33+
via: "host_id 优先,fallback 到 user_id + host_id IS NULL(D-05)"
34+
pattern: ORDER BY created_at ASC LIMIT 1
3135
- from: GetHostByShortID
3236
to: hosts.template_image_ref
33-
via: "SELECT 列扩展"
37+
via: "SELECT 列扩展供 Wave 2 推导能力字段"
3438
pattern: template_image_ref
35-
- from: ResolveClaudeAccountIDForEntry
36-
to: claude_accounts
37-
via: "host_id 优先、再 user_id+host_id IS NULL(D-05)"
3839
---
3940

40-
## 目标
41-
42-
落地 Phase 30 数据面:`claude_accounts.persistent_volume_name` 列(REQ-F7-A / Q4 → D-01、D-02、D-10),并为 Entry 握手提供 **`template_image_ref` 与确定性 `claude_account_id` 解析** 的仓储能力(D-05、D-11),****在本 plan 修改 HTTP handler 或 `HostActionRequest`(属 plan 02)。
43-
44-
## 执行上下文
45-
46-
- 讨论锁定:`.planning/phases/30-entry-api/30-CONTEXT.md`
47-
- 研究备忘:`.planning/phases/30-entry-api/30-RESEARCH.md`
41+
<objective>
42+
完成 Phase 30 的数据层基础:数据库迁移、仓储模型与账号解析查询,给 Phase 31/33 提供握手和 volume 命名所需的统一数据来源。
4843

49-
## 范围说明
44+
Purpose: 先锁定数据真值,确保 API 层只做协议拼装而不再重复定义规则。
45+
Output: `0014` migration + repository 查询/测试,形成 Wave 2 的稳定依赖。
46+
</objective>
5047

51-
### 纳入
48+
<execution_context>
49+
@.planning/phases/30-entry-api/30-CONTEXT.md
50+
@.planning/phases/30-entry-api/30-RESEARCH.md
51+
</execution_context>
5252

53-
- 新迁移文件名必须为 **`0014_claude_account_persistent_volume.sql`**(D-10)。`ALTER TABLE ... ADD COLUMN IF NOT EXISTS persistent_volume_name TEXT`**禁止**用空字符串表示未分配(D-02)。列可为 NULL;不设 `NOT NULL DEFAULT ''`
54-
- 文件末尾用 **SQL 注释** 给出可手工执行的 down 片段:`ALTER TABLE claude_accounts DROP COLUMN IF EXISTS persistent_volume_name;`,以满足 ROADMAP「可回滚」叙述且不与当前仅正向的 `migrator` 冲突。
55-
- `ClaudeAccount` 增加 `PersistentVolumeName *string`(或 `sql.NullString`,与仓库现有可空风格一致)。
56-
- `HostSSHAuth` 增加 `TemplateImageRef string`(或从 DB COALESCE 为空串),`GetHostByShortID``SELECT` 增加 `h.template_image_ref`(及 `COALESCE` 若需)。
57-
- 新增 `func (r *Repository) ResolveClaudeAccountIDForEntry(ctx context.Context, hostID, userID string) (id string, ok bool, err error)`
58-
1. 先查 `host_id = $hostID ORDER BY created_at ASC LIMIT 1`
59-
2. 若无行,再查 `user_id = $userID AND host_id IS NULL ORDER BY created_at ASC LIMIT 1`
60-
3. 仍无行则 `ok=false``id==""``err==nil`(D-05 步骤 3)。
61-
- 若未来已有 `ListClaudeAccounts` 等扫描 `claude_accounts` 的查询,本 plan 一并补扫 `persistent_volume_name`;当前仓库若无此类 SQL,仅保证模型与迁移一致即可。
62-
63-
### 排除(D-12 / 其它阶段)
64-
65-
- admin / GraphQL 新 surface;`docker volume create`;Entry JSON 响应体(plan 02)。
53+
<context>
54+
@.planning/ROADMAP.md
55+
@.planning/REQUIREMENTS.md
56+
@internal/store/migrations/0007_auth_unification.sql
57+
@internal/store/repository/models.go
58+
@internal/store/repository/queries.go
59+
</context>
6660

6761
<tasks>
6862

6963
<task type="auto" tdd="true">
70-
<name>任务 1:迁移 0014 + ClaudeAccount 模型字段</name>
71-
<files>internal/store/migrations/0014_claude_account_persistent_volume.sql,internal/store/repository/models.go,internal/store/repository/migration_0014_test.go</files>
64+
<name>Task 1: 落地 0014 migration 与可空字段语义</name>
65+
<files>internal/store/migrations/0014_claude_account_persistent_volume.sql, internal/store/repository/models.go, internal/store/repository/migration_0014_test.go</files>
66+
<read_first>.planning/phases/30-entry-api/30-CONTEXT.md (D-01,D-02,D-10), internal/store/migrations/0007_auth_unification.sql</read_first>
7267
<behavior>
73-
- 测试:迁移文件存在且包含 `ADD COLUMN IF NOT EXISTS persistent_volume_name`;注释块中含 `DROP COLUMN IF EXISTS persistent_volume_name`
74-
- 测试:`ClaudeAccount` 结构体含 `PersistentVolumeName` 字段且 `json` tag 使用 `omitempty`(若序列化用得到)
68+
- migration 使用 `ADD COLUMN IF NOT EXISTS persistent_volume_name TEXT`,不设置空字符串默认值
69+
- down 注释包含 `DROP COLUMN IF EXISTS persistent_volume_name`,确保回滚路径明确
7570
</behavior>
76-
<action>按 D-01/D-02/D-10 编写迁移与模型字段;迁移内不写绝对路径;索引仅在为后续 phase 确有查询模式时再加,本 plan 默认不加(RESEARCH 可选索引)。对应 D-01(列就绪)、D-02、D-10。</action>
71+
<action>按 D-01/D-02/D-10 新增 migration,并在 `ClaudeAccount` 增加可空字段映射;保持向后兼容,不修改任何 HTTP/agent 契约。</action>
7772
<verify>
7873
<automated>go test ./internal/store/repository/... -count=1 -short</automated>
7974
</verify>
80-
<done>迁移与模型字段合并;`migration_0014_test.go` 绿灯。</done>
75+
<acceptance_criteria>空库与升级库均可执行 migration;模型层可区分 `NULL` 与非空 volume 名。</acceptance_criteria>
76+
<done>schema 与模型完成并有自动化测试覆盖。</done>
8177
</task>
8278

8379
<task type="auto" tdd="true">
84-
<name>任务 2:GetHostByShortID 扩展 + ResolveClaudeAccountIDForEntry</name>
85-
<files>internal/store/repository/queries.go,internal/store/repository/migration_0014_test.go</files>
80+
<name>Task 2: 实现 D-05 账号选择查询与 template_image_ref 输出</name>
81+
<files>internal/store/repository/queries.go, internal/store/repository/migration_0014_test.go</files>
82+
<read_first>.planning/phases/30-entry-api/30-CONTEXT.md (D-05,D-06,D-11), internal/store/repository/queries.go</read_first>
8683
<behavior>
87-
- 测试(表驱动):对 `ResolveClaudeAccountIDForEntry` 使用 **fake DB** 或 **仅测 SQL 常量化片段** 不可行时,改为在 `migration_0014_test.go` 或新建 `resolve_claude_account_test.go` 中:用 `pgxmock` **或** 最小接口 `type rowQuerier interface{ QueryRow(...)}` + fake stub,断言两次查询的先后顺序与 `ok` 语义(无第二命中时 `ok=false`)。若引入 `pgxmock` 需改 `go.mod`,优先用 **手写 stub 实现 `Querier` 子集**(仅 `QueryRow`)避免新依赖(D-11)。
88-
- 至少覆盖:仅命中 host 绑定;仅命中 host_id IS NULL;全无。
84+
- host 绑定账号优先:`host_id = ? ORDER BY created_at ASC LIMIT 1`
85+
- fallback 账号:`user_id = ? AND host_id IS NULL ORDER BY created_at ASC LIMIT 1`
86+
- 无命中返回 `ok=false`,不是错误。
8987
</behavior>
90-
<action>扩展 `HostSSHAuth``GetHostByShortID`(D-06 数据输入);实现 D-05 解析逻辑;错误仅来自 DB 故障,无行不算错误。sshproxy 等调用方忽略新字段不受影响。</action>
88+
<action>新增 `ResolveClaudeAccountIDForEntry`(或等价语义命名),并扩展 `GetHostByShortID` 查询输出 `template_image_ref`,为 Wave 2 的能力字段推导提供输入。</action>
9189
<verify>
9290
<automated>go test ./internal/store/repository/... -count=1 -short</automated>
9391
</verify>
94-
<done>仓库编译通过;`ResolveClaudeAccountIDForEntry` 行为与 D-05 一致且有自动化断言。</done>
92+
<acceptance_criteria>解析顺序与 D-05 完全一致;`template_image_ref` 可被上层读取且不破坏现有调用。</acceptance_criteria>
93+
<done>仓储层提供稳定 API,Wave 2 不再需要改 SQL。</done>
94+
</task>
95+
96+
<task type="auto">
97+
<name>Task 3: 数据层完整性回归(防止与 Wave 2 重叠)</name>
98+
<files>internal/store/repository/migration_0014_test.go</files>
99+
<read_first>.planning/ROADMAP.md (Phase 30 Scope/Success Criteria)</read_first>
100+
<action>补充回归断言:本计划仅覆盖 migration/repository,不引入 `internal/controlplane/http``internal/cloudclaude` 变更,确保数据层与 API 层职责边界清晰。</action>
101+
<verify>
102+
<automated>go test ./internal/store/repository/... -count=1</automated>
103+
</verify>
104+
<acceptance_criteria>测试通过且无 HTTP/客户端文件变更,满足“数据层先于 API 层”的波次隔离。</acceptance_criteria>
105+
<done>Wave 1 可独立执行并产出可被 Wave 2 直接消费的数据接口。</done>
95106
</task>
96107

97108
</tasks>
98109

99110
<threat_model>
100-
## 信任边界
111+
## Trust Boundaries
101112

102-
| 边界 | 说明 |
103-
|------|------|
104-
| SQL 迁移 | 以文件形式进入部署管线;错误 DDL 可导致启动失败或数据不可用 |
105-
| 仓储查询 | 从控制面进程访问 DB;参数化查询边界防止注入 |
113+
| Boundary | Description |
114+
|----------|-------------|
115+
| controlplane → postgres | schema 与查询变更直接影响认证链路数据正确性 |
106116

107-
## STRIDE 登记
117+
## STRIDE Threat Register
108118

109-
| 威胁 ID | 类别 | 组件 | 处置 | 缓解措施 |
110-
|---------|------|------|------|----------|
111-
| T-30-01 | T | `queries.go` / Resolve | mitigate | 仅使用参数化查询(`$1`/`$2`),禁止字符串拼接用户输入进 SQL |
112-
| T-30-02 | I | `0014_*.sql` | mitigate | 迁移仅 DDL;部署前在 CI 跑 `go test` + 代码审阅核对 `IF NOT EXISTS` / `DROP IF EXISTS` |
113-
| T-30-03 | E | `claude_accounts` 行泄露 | accept | 与既有 Entry 认证同属控制面信任域;本方法不扩大 HTTP 暴露面 |
119+
| Threat ID | Category | Component | Disposition | Mitigation Plan |
120+
|-----------|----------|-----------|-------------|-----------------|
121+
| T-30-01 | T | repository SQL | mitigate | 全部使用参数化查询,不拼接输入字符串 |
122+
| T-30-02 | D | migration 0014 | mitigate | 使用 IF NOT EXISTS / IF EXISTS 与自动化回归测试 |
123+
| T-30-03 | I | claude_account 解析 | accept | 仅在控制面内部使用,不扩大外部暴露面 |
114124
</threat_model>
115125

116126
<verification>
117-
- `go test ./internal/store/repository/... -count=1 -short` 通过。
118-
- `go build ./...` 通过(可由 CI 覆盖)。
127+
- `go test ./internal/store/repository/... -count=1 -short`
128+
- `go test ./internal/store/repository/... -count=1`
119129
</verification>
120130

121131
<success_criteria>
122-
- 迁移文件命名与 D-10 一致;列语义满足 D-02
123-
- D-05 在仓储层可测且行为固定
132+
- D-01/D-02/D-10 D-05 在数据层全部落地且有自动化断言
133+
- Wave 1 输出可直接被 Wave 2 的 Entry API 计划消费,不需要重复迁移或重复查询实现
124134
</success_criteria>
125135

126136
<output>
127-
完成后撰写 `.planning/phases/30-entry-api/plans/01-migration-entry-store/SUMMARY.md`(由执行器生成)。
137+
After completion, create `.planning/phases/30-entry-api/plans/01-migration-entry-store/SUMMARY.md`
128138
</output>
129139

130-
## 来源审计(规划闭环)
140+
## Source Audit
131141

132-
| 来源 | 条目 | Plan | 状态 |
133-
|------|------|------|------|
134-
| GOAL | 控制面通道 + 数据模型 | 01 | COVERED |
135-
| REQ | REQ-F7-A(命名粒度数据模型) | 01 | COVERED |
136-
| RESEARCH | migration / repository / D-05 SQL | 01 | COVERED |
137-
| CONTEXT | D-01,D-02,D-05,D-10,D-11,D-12(排除项) | 01 | COVERED |
142+
| Source | Item | Coverage |
143+
|--------|------|----------|
144+
| GOAL | Phase 30 数据模型与握手前置 | COVERED |
145+
| REQ | REQ-F7-A(命名约定的数据模型落地) | COVERED |
146+
| RESEARCH | migration + repository + D-05 查询策略 | COVERED |
147+
| CONTEXT | D-01,D-02,D-05,D-06,D-10,D-11,D-12 | COVERED |

.planning/phases/30-entry-api/plans/01-migration-repository/PLAN.md

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)