Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
accd3943ea | ||
|
|
24aa13d9ac | ||
|
|
8207d780d1 | ||
|
|
fcd7282e17 | ||
|
|
4d066efe91 | ||
|
|
6ced938a12 | ||
|
|
b9560c7e21 | ||
|
|
9a6ae6c571 | ||
|
|
c3fc389b9f | ||
|
|
9eeba5afa2 | ||
|
|
66e2c98060 | ||
|
|
b58caf2d99 | ||
|
|
ade7083c57 | ||
|
|
f673c2a15a | ||
|
|
0e70a52aea | ||
|
|
1383cf1d2b | ||
|
|
344776cc18 | ||
|
|
8e4cdf09af | ||
|
|
fa96910d17 | ||
|
|
b0d481e91e | ||
|
|
c4bd492230 | ||
|
|
2739dcbfcc | ||
|
|
6cb6a276ca | ||
|
|
03ed068408 | ||
|
|
d0b2bde9b0 | ||
|
|
cb36b59acc | ||
|
|
826398c969 | ||
|
|
6bcd496636 | ||
|
|
6e0dd42190 | ||
|
|
aa1e8c2b2b | ||
|
|
9d5ed689ce | ||
|
|
c22b9fb34a | ||
|
|
7f23ed9ed1 | ||
|
|
ab5f51174a | ||
|
|
11e61d6f8a | ||
|
|
b7acfeb3ee | ||
|
|
a69e8c82bd | ||
|
|
abf4c27736 | ||
|
|
a14752bf1e | ||
|
|
f84d7c724e | ||
|
|
6bec85e1f8 | ||
|
|
c92443280e | ||
|
|
8441284c7c | ||
|
|
05e4034089 | ||
|
|
ce7b0d9c44 | ||
|
|
bd2fe3de22 | ||
|
|
1651efa97b | ||
|
|
0fd28ea94d | ||
|
|
39bb5bc71f | ||
|
|
88581f3440 | ||
|
|
8cb2d2297b | ||
|
|
3d3474a731 | ||
|
|
9126b09b5d | ||
|
|
41391776e1 | ||
|
|
74f3278514 | ||
|
|
d138dd8b45 | ||
|
|
c88cb95654 | ||
|
|
a29562ef83 | ||
|
|
b63685d7b5 | ||
|
|
98ee59c477 | ||
|
|
60bd1bedb4 | ||
|
|
10b751f159 | ||
|
|
ceb6bb9f5a | ||
|
|
940409a85e |
@@ -35,7 +35,7 @@ jobs:
|
||||
parsed_server = urlsplit(SERVER)
|
||||
if parsed_server.scheme not in {"http", "https"} or not parsed_server.netloc:
|
||||
sys.exit("Checkout server must be the platform-injected http(s) URL")
|
||||
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
SSH = "ssh://git@gitea.naitangbaobao.cn:52222/weichen/"
|
||||
# Reviewed allowlist, independent of candidate .gitmodules. Add repositories
|
||||
# here only with an explicit review of the checkout credential's scope.
|
||||
REPOSITORIES = {
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
parsed_server = urlsplit(SERVER)
|
||||
if parsed_server.scheme not in {"http", "https"} or not parsed_server.netloc:
|
||||
sys.exit("Checkout server must be the platform-injected http(s) URL")
|
||||
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
SSH = "ssh://git@gitea.naitangbaobao.cn:52222/weichen/"
|
||||
# Reviewed allowlist, independent of candidate .gitmodules. Add repositories
|
||||
# here only with an explicit review of the checkout credential's scope.
|
||||
REPOSITORIES = {
|
||||
@@ -410,7 +410,7 @@ jobs:
|
||||
export GIT_CONFIG_KEY_1="http.$SERVER/.extraheader"
|
||||
export GIT_CONFIG_VALUE_1="AUTHORIZATION: token $SUBMODULE_TOKEN"
|
||||
export GIT_CONFIG_KEY_2="url.$SERVER/weichen/.insteadOf"
|
||||
export GIT_CONFIG_VALUE_2="ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
export GIT_CONFIG_VALUE_2="ssh://git@gitea.naitangbaobao.cn:52222/weichen/"
|
||||
export GIT_CONFIG_KEY_3="core.hooksPath"
|
||||
export GIT_CONFIG_VALUE_3="/dev/null"
|
||||
git clone --depth=1 "$SERVER/weichen/Agent-System-RDM.git" rdm
|
||||
@@ -544,7 +544,7 @@ jobs:
|
||||
parsed_server = urlsplit(SERVER)
|
||||
if parsed_server.scheme not in {"http", "https"} or not parsed_server.netloc:
|
||||
sys.exit("Checkout server must be the platform-injected http(s) URL")
|
||||
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
SSH = "ssh://git@gitea.naitangbaobao.cn:52222/weichen/"
|
||||
# Reviewed allowlist, independent of candidate .gitmodules. Add repositories
|
||||
# here only with an explicit review of the checkout credential's scope.
|
||||
REPOSITORIES = {
|
||||
@@ -698,7 +698,7 @@ jobs:
|
||||
export GIT_CONFIG_KEY_1="http.$SERVER/.extraheader"
|
||||
export GIT_CONFIG_VALUE_1="AUTHORIZATION: token $SUBMODULE_TOKEN"
|
||||
export GIT_CONFIG_KEY_2="url.$SERVER/weichen/.insteadOf"
|
||||
export GIT_CONFIG_VALUE_2="ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
export GIT_CONFIG_VALUE_2="ssh://git@gitea.naitangbaobao.cn:52222/weichen/"
|
||||
export GIT_CONFIG_KEY_3="core.hooksPath"
|
||||
export GIT_CONFIG_VALUE_3="/dev/null"
|
||||
git clone --depth=1 "$SERVER/weichen/Agent-System-RDM.git" rdm
|
||||
|
||||
+14
-14
@@ -1,42 +1,42 @@
|
||||
[submodule "modules/core"]
|
||||
path = modules/core
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Core.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-Core.git
|
||||
[submodule "modules/backend"]
|
||||
path = modules/backend
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Backend.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-Backend.git
|
||||
[submodule "modules/frontend"]
|
||||
path = modules/frontend
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Frontend.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-Frontend.git
|
||||
[submodule "modules/documents"]
|
||||
path = modules/documents
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Documents.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-Documents.git
|
||||
[submodule "deps/development-guidelines"]
|
||||
path = deps/development-guidelines
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Guidelines.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Development-Guidelines.git
|
||||
[submodule "deps/agent-native"]
|
||||
path = deps/agent-native
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Native.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-Native.git
|
||||
[submodule "deps/agent-squad"]
|
||||
path = deps/agent-squad
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Squad.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-Squad.git
|
||||
[submodule "deps/agent-intent"]
|
||||
path = deps/agent-intent
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Intent.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-Intent.git
|
||||
[submodule "deps/error-code-system"]
|
||||
path = deps/error-code-system
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Error-Code-System.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Error-Code-System.git
|
||||
[submodule "deps/logging-system"]
|
||||
path = deps/logging-system
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Logging-System.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Logging-System.git
|
||||
[submodule "modules/ui"]
|
||||
path = modules/ui
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-UI.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-UI.git
|
||||
[submodule "deps/agent-command"]
|
||||
path = deps/agent-command
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Command.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-Command.git
|
||||
[submodule "deps/development-manager"]
|
||||
path = deps/development-manager
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Manager.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Development-Manager.git
|
||||
[submodule "deps/agent-document-renderer"]
|
||||
path = deps/agent-document-renderer
|
||||
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Document-Renderer.git
|
||||
url = ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-Document-Renderer.git
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
# feature/exec-recovery-diagnostics-a2 任务卡
|
||||
|
||||
任务主记录:PR #704 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/704;状态:已完成(合入前闭合);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= c92443280ef8e9ed29bf5bf5250df650c01b4478
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-exec-recovery-diagnostics-a2(worktree);分支:feature/exec-recovery-diagnostics-a2
|
||||
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
|
||||
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=34e72676959d0e970f1a3cb0e90ac2067dfb31f3c2e7a6650b5de9f3f77923c7;triage_receipt=/tmp/triage-a2-1792672248.json;receipt_sha256=7924fdbaeba41d98994c2e11bc96026d4db3cdbd0b28a9d1e381400eae9a6de4
|
||||
|
||||
- 授权与交付终点:用户已批准启动 A2 执行与恢复诊断(方案 Functional Follow-Ups 第二项)。范围内连续推进:设计、agent-per-unit 实施、验证、组合、Review、PR/CI/合入。未授权:控制台/前端界面、A3/A4、部署。
|
||||
- 设计决定(2026-10-22,host):
|
||||
1. 只读诊断 API:GET /api/runs/{id}/diagnostics(owner/project 授权,复用既有 run 鉴权),响应聚合:状态、当前责任方与等待原因(running / waiting_hitl / confirmation_pending / recovery_hold(原因+created_at) / reconciling / failed+reason / completed)、最后稳定 checkpoint(如可得)、已知副作用与关联 operation 记录(复用 cmdbus 审计信封查询)、下一步动作建议(确认卡片 / 解除 hold / 可安全重试 / 需人工核对 / 无)。
|
||||
2. 纯读聚合,不改任何执行/恢复行为;复用既有 store 方法(GetRun/RecoveryHold/ListInterruptedSubagentRuns/审计查询)与 U7b 的 operation 查询。
|
||||
3. 不建控制台、不加写接口;批量列表走既有会话 run 列表。
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
|
||||
- 当前阶段:需求拆解;当前增量:I1(待定义)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
**目标**:单只读端点聚合"卡在哪里/谁在等/为何等/最后 checkpoint/已知副作用/下一步",覆盖 HITL 等待、确认等待、恢复 hold(批二 needs-reconciliation)、失败原因等全部可诊断状态。
|
||||
|
||||
**非目标**:控制台/UI;任何写操作或状态变更端点;A3/A4; squad sqlite 协作层的跨库聚合(只读后端 store 与审计)。
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 关联方向与规划
|
||||
|
||||
- 适用状态:待核对(无适用规划时说明原因)
|
||||
- 主记录路径/版本:待核对(路径按仓库根目录定位,归档后不变;只引用唯一正文)
|
||||
- 对应里程碑:待核对
|
||||
|
||||
## 需求分析
|
||||
|
||||
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| R1 | 待填写 | 待评估 | 待定 | 待定义 | 待确认 | 待评审 |
|
||||
|
||||
## 方案与计划
|
||||
|
||||
- 方案引用:待填写(含对象版本)
|
||||
- 实施计划引用:待填写(含增量与需求 ID)
|
||||
- 测试计划引用:待填写(含需求 ID 与验收条件)
|
||||
|
||||
## 阶段记录
|
||||
|
||||
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|
||||
|---|---|---|---|---|---|
|
||||
| 需求拆解 | 待定 | 待执行 | — | — | — |
|
||||
| 需求评估 | 待定 | 待执行 | — | — | — |
|
||||
| 需求表确认 | 待定 | 待执行 | — | — | — |
|
||||
| 方案设计 | 待定 | 待执行 | — | — | — |
|
||||
| 实施计划 | 待定 | 待执行 | — | — | — |
|
||||
| 实施与自检 | 待定 | 待执行 | — | — | — |
|
||||
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
|
||||
| 代码 Review | 待定 | 待执行 | — | — | — |
|
||||
| 合入 | 待定 | 待执行 | — | — | — |
|
||||
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
|
||||
|
||||
## 探索试验与迭代
|
||||
|
||||
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | c92443280ef8e9ed29bf5bf5250df650c01b4478 | c92443280ef8e9ed29bf5bf5250df650c01b4478 | 2026-10-22T12:37:28Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 交付调度
|
||||
|
||||
- 调度状态:未入队;调度记录:无
|
||||
- entry_id:无;attempt_id:无;窗口执行者:无
|
||||
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
|
||||
- 调度依据:policy/delivery.md#合入调度
|
||||
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
- 影响与剩余项:
|
||||
|
||||
## 规划进展与下一步
|
||||
|
||||
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
|
||||
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
|
||||
- 推荐依据:待填写
|
||||
- 前置条件/待确认:待核对
|
||||
- 预期产物/验收条件:待定义
|
||||
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
|
||||
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- 实施收据:a2-diagnostics-api(pi attempt1,rundiag 聚合 + GET /api/runs/{id}/diagnostics,backend 06b0c9f,verify-a2b exit=0);架构基线边登记(repair,a2arch-run.json);backend pin 集成(a2git2-run.json,cursor 配额间歇故障重试一次后成功)。
|
||||
- 组合验证:全量 make test PASS(verify-a2-combo2.json exit=0,本轮 svc 既有 flake 未复现)。
|
||||
- Review 记录:独立 CLI 评审单元 a2-independent-review(第 2 次派发 success;首轮 pi-glm 终态记录不可解码,现场保留);结论 APPROVE(0 blocker / 0 major);报告 .task/reviews/2026-10-22-a2-diagnostics-independent-review.md;被审对象合并基线 vs main
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T12:30:58.698213+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "c92443280ef8e9ed29bf5bf5250df650c01b4478",
|
||||
"after_head": "c92443280ef8e9ed29bf5bf5250df650c01b4478",
|
||||
"branch": "fix/gitea-address-migration",
|
||||
"target_branch": "main",
|
||||
"target_head": "c92443280ef8e9ed29bf5bf5250df650c01b4478",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=fix/gitea-address-migration head=c92443280e",
|
||||
"[remote] origin/main=c92443280e ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=7",
|
||||
"[recycle] merged un-recycled branches: fix/gitea-endpoint-facts",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] c92443280e(完整:c92443280ef8e9ed29bf5bf5250df650c01b4478)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "d670ed210b367c72f877fb63bbbeb5b28411e95cb56fab782f27172fa481ed89",
|
||||
"receipt_sha256": "2829e17065c329449fac8ef2690d50babf82015a1f546dcbfd3f27f67789dd0c",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/2829e17065c329449fac8ef2690d50babf82015a1f546dcbfd3f27f67789dd0c.json"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(合入前折叠)
|
||||
|
||||
- 本卡在 PR #704(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/704)合入前闭合并归档,随该 PR 一同合入。
|
||||
- 该 PR 的合并提交即本任务的落地记录。
|
||||
- 合入后无需回填,也无需单独的闭合 PR。
|
||||
@@ -0,0 +1,168 @@
|
||||
# feature/intent-multi-turn-a1 任务卡
|
||||
|
||||
任务主记录:PR #700 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/700;状态:已完成(合入前闭合);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 955a32de2bb49171348bad5f566cce935df19212
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-intent-multi-turn-a1(worktree);分支:feature/intent-multi-turn-a1
|
||||
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
|
||||
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=5dd60900f5e7d69e382c367bfde373c087528dd9dcae7e16199c956ef4f3d9bf;triage_receipt=/tmp/triage-a1-1792658810.json;receipt_sha256=35b32668d143e39f386f069ae4c0004cae9761125d95241bb66e6aff1530d24b
|
||||
|
||||
- 授权与交付终点:用户已批准启动 A1 多轮意图闭环(方案 Functional Follow-Ups 第一项)。范围内连续推进:设计、agent-per-unit 实施、验证、组合、Review、PR/CI/合入。未授权:A2-A4、A2A、部署/发布、其余业务动作族扩展。
|
||||
- 设计决定(2026-10-22,host):
|
||||
1. 第一类真实业务动作 = **运行取消**("取消刚才的任务"):cancel 命令已注册且为高后果动作,走既有 cmdbus 确认卡;后续动作族另行增量。
|
||||
2. 会话状态存 Backend 既有 store(新表 intent_session,键 owner+project+conversation),保存挂起预期(action ref/revision/缺失槽位/已累积槽位)与过期时间(30 分钟 TTL);revision 递增防过期回答污染新任务。
|
||||
3. 库层不动 Stage 1 语义:Backend 实现 ExpectationProvider 数据源并在 intentgate/route 消费 set_slot/change_slot/cancel/expectation 命令(route.go 现按"宿主不消费"转 turn——本任务改变这一点)。
|
||||
4. 高危动作仍必须经 cmdbus 确认;会话只是槽位与预期的载体,不授予执行权。
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
|
||||
- 当前阶段:需求拆解;当前增量:I1(待定义)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
**目标**:多轮对话中:缺槽动作 → 澄清 → 用户回答补槽 → 动作完成(第一类=运行取消);"取消刚才的任务"作用于明确对象;歧义先澄清;过期回答(TTL 外)不影响新任务;双用户隔离;revision 防串轮。拆两个单元:U-A1b 会话状态与命令消费(intentgate/route/server + store 表);U-A1c 取消意图路由到 cmdbus(确认卡 + 明确对象解析)。
|
||||
|
||||
**非目标**:A2-A4;A2A;更多业务动作族;前端界面改动;部署。
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 关联方向与规划
|
||||
|
||||
- 适用状态:待核对(无适用规划时说明原因)
|
||||
- 主记录路径/版本:待核对(路径按仓库根目录定位,归档后不变;只引用唯一正文)
|
||||
- 对应里程碑:待核对
|
||||
|
||||
## 需求分析
|
||||
|
||||
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| R1 | 待填写 | 待评估 | 待定 | 待定义 | 待确认 | 待评审 |
|
||||
|
||||
## 方案与计划
|
||||
|
||||
- 方案引用:待填写(含对象版本)
|
||||
- 实施计划引用:待填写(含增量与需求 ID)
|
||||
- 测试计划引用:待填写(含需求 ID 与验收条件)
|
||||
|
||||
## 阶段记录
|
||||
|
||||
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|
||||
|---|---|---|---|---|---|
|
||||
| 需求拆解 | 待定 | 待执行 | — | — | — |
|
||||
| 需求评估 | 待定 | 待执行 | — | — | — |
|
||||
| 需求表确认 | 待定 | 待执行 | — | — | — |
|
||||
| 方案设计 | 待定 | 待执行 | — | — | — |
|
||||
| 实施计划 | 待定 | 待执行 | — | — | — |
|
||||
| 实施与自检 | 待定 | 待执行 | — | — | — |
|
||||
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
|
||||
| 代码 Review | 待定 | 待执行 | — | — | — |
|
||||
| 合入 | 待定 | 待执行 | — | — | — |
|
||||
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
|
||||
|
||||
## 探索试验与迭代
|
||||
|
||||
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 955a32de2bb49171348bad5f566cce935df19212 | 955a32de2bb49171348bad5f566cce935df19212 | 2026-10-22T08:48:01Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 交付调度
|
||||
|
||||
- 调度状态:未入队;调度记录:无
|
||||
- entry_id:无;attempt_id:无;窗口执行者:无
|
||||
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
|
||||
- 调度依据:policy/delivery.md#合入调度
|
||||
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
- 影响与剩余项:
|
||||
|
||||
## 规划进展与下一步
|
||||
|
||||
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
|
||||
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
|
||||
- 推荐依据:待填写
|
||||
- 前置条件/待确认:待核对
|
||||
- 预期产物/验收条件:待定义
|
||||
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
|
||||
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- 实施收据:U-A1b(pi attempt1,intent-session 会话/命令消费,backend 0fb64c4,verify-a1b exit=0);U-A1c(pi attempt1,cancel 经 cmdbus 路由,backend c2e3104→verify-a1c exit=0);架构基线边登记(repair,a1arch4-run.json)与 storage-migrate manifest 补齐(repair,backend 9ee752c,verify-a1sr exit=0)。
|
||||
- 组合验证:test-governance 存在 svc 环境不稳定失败(两次运行分别挂在 test_addr_change / test_U2_health;addr_change 已在干净 main 复现,见批二后验证),其余全部目标 PASS(verify-a1-rest.json exit=0);CI 环境为最终裁决(批二同代码 CI 全绿)。
|
||||
- Review 记录:待执行(独立 CLI 评审,随 PR 流程)
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T08:46:59.325275+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"after_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"branch": "main",
|
||||
"target_branch": "main",
|
||||
"target_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=main head=955a32de2b",
|
||||
"[remote] origin/main=955a32de2b ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=4",
|
||||
"[recycle] merged un-recycled branches: feature/container-storage-plan",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] 955a32de2b(完整:955a32de2bb49171348bad5f566cce935df19212)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "f06e6cc8ddb0c414c2ac92e5100dc58b0b926a2bf7196273056b01be80253217",
|
||||
"receipt_sha256": "32899ef2b4ebee5bf1725fb79e1bba0382177731155cebab04178821e6da3f1b",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/32899ef2b4ebee5bf1725fb79e1bba0382177731155cebab04178821e6da3f1b.json"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(合入前折叠)
|
||||
|
||||
- 本卡在 PR #700(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/700)合入前闭合并归档,随该 PR 一同合入。
|
||||
- 该 PR 的合并提交即本任务的落地记录。
|
||||
- 合入后无需回填,也无需单独的闭合 PR。
|
||||
@@ -0,0 +1,98 @@
|
||||
# fix/svc-test-port-governance-batch1 任务卡
|
||||
|
||||
任务主记录:PR #702 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/702(merge cb36b59acc);状态:已完成(回填);owner:weichen
|
||||
|
||||
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 3d3474a731f9ef3ee1f41935e63bca3afb70db56
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-test-port-governance-batch1(worktree);分支:fix/svc-test-port-governance-batch1
|
||||
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
|
||||
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=1723e9e3e6bd50bedc041afba3533c35e8b663edb12472f29c067df860df799b;triage_receipt=/tmp/pa1-triage.json;receipt_sha256=71d236166f2a1de8cecd6eb93042ef5199a09e2c49318dbf8eb9d5b5582a99e5
|
||||
|
||||
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
<预期结果、受影响入口/模块/契约;明确不做什么。>
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 3d3474a731f9ef3ee1f41935e63bca3afb70db56 | 3d3474a731f9ef3ee1f41935e63bca3afb70db56 | 2026-10-22T11:15:53Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
- 影响与剩余项:
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T11:15:04.613824+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56",
|
||||
"after_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56",
|
||||
"branch": "main",
|
||||
"target_branch": "main",
|
||||
"target_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=main head=3d3474a731",
|
||||
"[remote] origin/main=3d3474a731 ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=6",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] 3d3474a731(完整:3d3474a731f9ef3ee1f41935e63bca3afb70db56)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "87e58b98732700984715adb3f7a46aeacdc3749aacf51643f68a2ad9ede2451d",
|
||||
"receipt_sha256": "72f14e2080b227c1fc4094a0a4f573b0dc12fb08ed30c86400265a610fb09b0d",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/72f14e2080b227c1fc4094a0a4f573b0dc12fb08ed30c86400265a610fb09b0d.json"
|
||||
}
|
||||
```
|
||||
| 同步(记录前对象) | c92443280ef8e9ed29bf5bf5250df650c01b4478 | c22b9fb34a3702191a9e5f6007f3538520db694b | 2026-10-22T13:10:38Z |
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge cb36b59accd0418c5ca952912d7c181b83038a4c(PR #702)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Use dynamic ports across the five test files that shared hardcoded bindings' (#702) from fix/svc-test-port-governance-batch1 into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
@@ -0,0 +1,99 @@
|
||||
# fix/svc-test-port-governance-deploy-txn 任务卡
|
||||
|
||||
任务主记录:PR #705 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/705(merge f673c2a15a);状态:已完成(回填);owner:weichen
|
||||
|
||||
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= cb36b59accd0418c5ca952912d7c181b83038a4c
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-test-port-governance-deploy-txn(worktree);分支:fix/svc-test-port-governance-deploy-txn
|
||||
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
|
||||
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=37c57479990e37eb048ba81763fe402e1e679a822679f315a4be18e4b965c333;triage_receipt=/tmp/plast-triage.json;receipt_sha256=d403df8b2564bf4e1fe16fb12ecc48cf31c25be4f7472e6b27bce6da3ecd93e0
|
||||
|
||||
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
<预期结果、受影响入口/模块/契约;明确不做什么。>
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | cb36b59accd0418c5ca952912d7c181b83038a4c | cb36b59accd0418c5ca952912d7c181b83038a4c | 2026-10-22T13:39:45Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
- 影响与剩余项:
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T13:28:41.022157+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "cb36b59accd0418c5ca952912d7c181b83038a4c",
|
||||
"after_head": "cb36b59accd0418c5ca952912d7c181b83038a4c",
|
||||
"branch": "main",
|
||||
"target_branch": "main",
|
||||
"target_head": "cb36b59accd0418c5ca952912d7c181b83038a4c",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=main head=cb36b59acc",
|
||||
"[remote] origin/main=cb36b59acc ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=9",
|
||||
"[recycle] merged un-recycled branches: fix/gitea-address-migration fix/svc-test-port-governance-batch1",
|
||||
"[cards] 已合入未闭合任务卡:fix-svc-test-port-governance-batch1.md——make task-close 回填归档(主检出运行)",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] cb36b59acc(完整:cb36b59accd0418c5ca952912d7c181b83038a4c)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "9f6260af81b0761dbb20afc5d145a9cb310303d426f655840a50f0879d81a49e",
|
||||
"receipt_sha256": "eeb4f9ac9bdc0e30a0a7bbaa49176626bb8aca54a20f152b1771e1df8fd855d3",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/eeb4f9ac9bdc0e30a0a7bbaa49176626bb8aca54a20f152b1771e1df8fd855d3.json"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge f673c2a15a50bef2c55f2c79a17d7601f82c8a07(PR #705)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Use a dynamic port in the last hardcoded test binding' (#705) from fix/svc-test-port-governance-deploy-txn into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
@@ -0,0 +1,99 @@
|
||||
# fix/svc-test-port-governance-final 任务卡
|
||||
|
||||
任务主记录:PR #698 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/698;状态:已完成(合入前闭合);owner:weichen
|
||||
|
||||
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 955a32de2bb49171348bad5f566cce935df19212
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-test-port-governance-final(worktree);分支:fix/svc-test-port-governance-final
|
||||
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
|
||||
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=04cbe9df900a03163aeadb42fd22e5a86ddeced179901b50ac0f4642ce7f6881;triage_receipt=/tmp/pg3-triage.json;receipt_sha256=aca390790027841efaefe2082123780b1ec8d775f9ca0be80262a80f7121bde9
|
||||
|
||||
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
<预期结果、受影响入口/模块/契约;明确不做什么。>
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 955a32de2bb49171348bad5f566cce935df19212 | 955a32de2bb49171348bad5f566cce935df19212 | 2026-10-22T08:55:33Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
- 影响与剩余项:
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T08:54:47.013779+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"after_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"branch": "main",
|
||||
"target_branch": "main",
|
||||
"target_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=main head=955a32de2b",
|
||||
"[remote] origin/main=955a32de2b ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=5",
|
||||
"[recycle] merged un-recycled branches: feature/container-storage-plan",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] 955a32de2b(完整:955a32de2bb49171348bad5f566cce935df19212)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "b63c0d5c11be0292024cc972eee78fbcef7349381ee3bdecb432815b903373e6",
|
||||
"receipt_sha256": "3ee914c56ff0e9cf2a4d11db1ec0a2e6c74cf745cceb7eff1cefd2f9d938ee89",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/3ee914c56ff0e9cf2a4d11db1ec0a2e6c74cf745cceb7eff1cefd2f9d938ee89.json"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(合入前折叠)
|
||||
|
||||
- 本卡在 PR #698(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/698)合入前闭合并归档,随该 PR 一同合入。
|
||||
- 该 PR 的合并提交即本任务的落地记录。
|
||||
- 合入后无需回填,也无需单独的闭合 PR。
|
||||
| 同步(记录前对象) | 98ee59c477785b2d5094fcbea3e67a4dc864c388 | a29562ef8315c6cbe71bf531720ce5ffa5ca2717 | 2026-10-22T09:35:11Z |
|
||||
@@ -1,178 +0,0 @@
|
||||
# feature/container-storage-plan-u1 任务卡
|
||||
|
||||
任务主记录:<PR 链接,交付前填写>;状态:进行中;owner:weichen
|
||||
|
||||
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 955a32de2bb49171348bad5f566cce935df19212
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1(worktree);分支:feature/container-storage-plan-u1
|
||||
- 分支类型:child;initiative_id:feature-container-storage-plan;root_target:main;immediate_target:feature/container-storage-plan;accepting_requirements:false;parent_branch:feature/container-storage-plan;parent_head_sha:b360bcb9745e20d9d4f9f06c44a2936f47217ae4;parent_pr:无;parent_source:origin
|
||||
- 需求分流:免(父 initiative 子任务登记已列出 feature/container-storage-plan-u1;parent_head_sha=b360bcb9745e20d9d4f9f06c44a2936f47217ae4)
|
||||
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
- 授权与交付终点:在父 initiative 授权范围内实施方案 U1(环境与数据归属合同),交付终点为合入父分支 feature/container-storage-plan。不含:生产数据盘点/访问、部署、生产迁移、容器改造实施(U2 起)。
|
||||
- 当前阶段:实施与自检
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
**目标(方案 U1)**:扩展现有实例配置与 manifest,绑定基座库、RDM 领域库、知识库三个逻辑数据库、持久文件根和环境身份(prod/test/verify);产出全部必需持久数据所有者的归属清单(类型、源版本、目录、引用形式、唯一写者;不含秘密与业务内容)。
|
||||
|
||||
**主要落点**:`scripts/svc_manifest.py`、`scripts/svc.sh`、`docs/svc.md`、`PROJECT.md`;新增环境 schema/示例建议置 `deploy/`。测试扩展 `tests/test_svc_identity.py`、`tests/test_svc_lifecycle_isolation.py`、`tests/test_svc_precheck.py`。
|
||||
|
||||
**完成条件**:配置、归属清单和迁移范围有唯一解释;明确需迁移的每个数据所有者;旧配置升级保留身份;错库/错根/foreign marker/缺失必需绑定拒绝;同名项目或冲突端口在修改前失败;在另一 checkout 调用仍定位同一资源。
|
||||
|
||||
**非目标**:不实现容器启停(U2)、迁移执行(U3)、备份恢复包(U4)、克隆隔离(U5);不改业务权限模型;不引入第二份 DSN/卷权威(KTD3:manifest 唯一权威)。
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| container-storage-u1-env-data-ownership | manifest/svc 绑定三库+文件根+环境身份;数据归属清单 | ccceb3bf… | implementation | cursor,antigravity,pi-glm,pi,codex | 1 | cursor(auto) | failed-unknown | 无(失败保留) | 否 | /tmp/container-storage-u1-run-receipt.json |
|
||||
| container-storage-u1-env-data-ownership | 同上(采用 attempt1 补丁并验证) | ccceb3bf… | implementation | cursor,… | 2 | cursor(auto) | failed-unknown | 无(JSON 后带尾文解码失败,现场保留) | 否 | /tmp/container-storage-u1-run2-receipt.json |
|
||||
| container-storage-u1-env-data-ownership | 同上 | ccceb3bf… | implementation | cursor,… | 3 | cursor(auto) | failed-unknown | 无(CLI 启动即报 Cannot use this model: auto,模型列表为空;单测 CLI 正常,判瞬时故障) | 否 | /tmp/container-storage-u1-run3-receipt.json |
|
||||
| container-storage-u1-env-data-ownership | 同上 | ccceb3bf… | implementation | cursor,antigravity,… | 4 | cursor(auto) | failed-unknown | 无(工作完成且 80 测试通过,但终态 JSON 后再次附带尾文解码失败;cursor 累计 4 次尝试耗尽,转下一候选) | 否 | /tmp/container-storage-u1-run4-receipt.json |
|
||||
| container-storage-u1-env-data-ownership | — | ccceb3bf… | implementation | (run5 于尝试启动前被 host 终止并删除其私有运行目录,无工作产物;目的:避免 cursor 超上限重试) | 5a | — | cancelled | 无 | 否 | 无(无收据;host 披露于本行) |
|
||||
| container-storage-routing-promote-antigravity | AGENT-EXECUTION.md implementation 顺序临时调整 | ccceb3bf… | implementation | cursor,… | 1(第 2 次运行;第 1 次遇 cursor 模型列表瞬时为空 failed-unknown) | cursor(auto) | completed | apply(0c8bea37) | 否 | /tmp/container-storage-routing-receipt2.json |
|
||||
| container-storage-u1-env-data-ownership | 同上(采用 attempt1 补丁内容,经 antigravity 全新会话独立重做并验证) | ccceb3bf… | implementation | antigravity,pi-glm,pi,cursor,codex | 1 | agy(gemini-3.8-flash-high) | completed | apply(2d75d101) | 否 | /tmp/container-storage-u1-run7-receipt.json |
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 955a32de2bb49171348bad5f566cce935df19212 | b360bcb9745e20d9d4f9f06c44a2936f47217ae4 | 2026-10-22T09:12:44Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:U1 完成。manifest/svc 扩展三库+文件根+环境身份绑定与错误拒绝;deploy/data-ownership.md 归属清单与 env-bindings.example.env 示例;文档同步。经 agent-exec transport ed39db43 进入分支(2d75d101)。
|
||||
- 验证:三套件 80 项 PASS(1 skip,SKIP 为环境缺 DSN 的既有用例);make test-affected 574 项 PASS(18 skip)。证据见上方两个导入收据(SELF)。容器真实运行验证属 U2,NOT_RUN。
|
||||
- 影响与剩余项:无阻塞。生产数据盘点需授权(方案 U1 生产部分推迟到 O1 前核实)。
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- Review 记录:REVIEW PASS(无阻塞项;N1—N8 非阻塞,报告 docs/superpowers/reviews/2026-10-22-container-storage-u1-review.md,transport 7f0f... 见 /tmp/container-storage-u1-review-receipt2.json)。审阅者:pi-glm 全新会话(fresh_session,非实施者);base 955a32de、head e1f08650;重放 80 项单测 OK
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T09:11:55.612223+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan",
|
||||
"before_head": "b360bcb9745e20d9d4f9f06c44a2936f47217ae4",
|
||||
"after_head": "b360bcb9745e20d9d4f9f06c44a2936f47217ae4",
|
||||
"branch": "feature/container-storage-plan",
|
||||
"target_branch": "main",
|
||||
"target_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan'",
|
||||
"[repo] branch=feature/container-storage-plan head=b360bcb974",
|
||||
"[remote] origin/main=955a32de2b ahead=2 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=6",
|
||||
"[initiatives] local cards (not authoritative): feature/container-storage-plan",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] 955a32de2b(完整:955a32de2bb49171348bad5f566cce935df19212)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan'"
|
||||
],
|
||||
"output_sha256": "58ef898d944057a44fb02e1fabb6acc2fd97943beed355977229d13711b2e6b2",
|
||||
"receipt_sha256": "59e8b2e7b7032c19207a19f9427486439ca15250333e83ddd162db739c5dc395",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/59e8b2e7b7032c19207a19f9427486439ca15250333e83ddd162db739c5dc395.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T10:41:41.286181+00:00",
|
||||
"command": "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_svc_identity tests.test_svc_lifecycle_isolation tests.test_svc_precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1",
|
||||
"before_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
|
||||
"after_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
|
||||
"branch": "feature/container-storage-plan-u1",
|
||||
"target_branch": "main",
|
||||
"target_head": "98ee59c477785b2d5094fcbea3e67a4dc864c388",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/container-storage-plan",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"........................s.......................................................",
|
||||
"----------------------------------------------------------------------",
|
||||
"Ran 80 tests in 66.437s",
|
||||
"",
|
||||
"OK (skipped=1)"
|
||||
],
|
||||
"output_sha256": "1bc6f83d7e4c64a628d5f29bad07fc4e8cc2468937c66bda4bd9a5e00fc22587",
|
||||
"receipt_sha256": "bfe74991277ebe308f667532e9b21af8215f453db124afeab1fc3d2115adf67d",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/bfe74991277ebe308f667532e9b21af8215f453db124afeab1fc3d2115adf67d.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T10:42:53.576009+00:00",
|
||||
"command": "make test-affected",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1",
|
||||
"before_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
|
||||
"after_head": "2d75d101a04ce1d1c9fa1d66d9567894222399d5",
|
||||
"branch": "feature/container-storage-plan-u1",
|
||||
"target_branch": "main",
|
||||
"target_head": "98ee59c477785b2d5094fcbea3e67a4dc864c388",
|
||||
"root_target": "main",
|
||||
"immediate_target": "feature/container-storage-plan",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"test_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok",
|
||||
"test_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok",
|
||||
"test_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok",
|
||||
"test_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok",
|
||||
"test_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok",
|
||||
"test_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok",
|
||||
"test_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok",
|
||||
"test_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok",
|
||||
"",
|
||||
"----------------------------------------------------------------------",
|
||||
"Ran 574 tests in 400.853s",
|
||||
"",
|
||||
"OK (skipped=18)",
|
||||
"make[2]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1'",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan-u1'"
|
||||
],
|
||||
"output_sha256": "d738cef7d9ea78bcc4624e0fe8eb87b651e88712fcd283bb26c21755168604e9",
|
||||
"receipt_sha256": "0565d5b327cb8d01e48deda867bd7369dd5728b5ad9024b51bbd6cb1a2fdf246",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/0565d5b327cb8d01e48deda867bd7369dd5728b5ad9024b51bbd6cb1a2fdf246.json"
|
||||
}
|
||||
```
|
||||
@@ -1,177 +0,0 @@
|
||||
# feature/container-storage-plan 任务卡
|
||||
|
||||
任务主记录:<PR 链接,交付前填写>;状态:进行中;owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 955a32de2bb49171348bad5f566cce935df19212
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-container-storage-plan(worktree);分支:feature/container-storage-plan
|
||||
- 分支类型:initiative;initiative_id:feature-container-storage-plan;root_target:main;immediate_target:main;accepting_requirements:true
|
||||
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=53a3aac8b5638347861577e2ab6f487c182b364e44b1fb0b563df0fdb5834870;triage_receipt=/tmp/container-storage-plan-triage-20261022-b.json;receipt_sha256=31e6accb098e8572e3698e04e3dbafc340fd9ef300df7a09110f1cb249aabb44
|
||||
|
||||
- 授权与交付终点:按已审阅方案实施 U1—U6(M1—M3)并交付到 main;含各仓代码/测试/文档变更、PR/CI/合入。不含:生产迁移 O1、生产数据读取/写入、部署到生产、数据库大版本升级。
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收
|
||||
- 当前阶段:实施与自检(M1/U1 起);当前增量:I1=U1
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 章程(charter)
|
||||
|
||||
**用途与目标**:按已审阅方案(`docs/superpowers/plans/2026-10-22-container-storage-plan.md`)把部署形态改造为:一个完整应用服务二进制、两个 PostgreSQL 容器(业务基座库 + RDM 领域库)、知识库 PostgreSQL/pgvector 容器(第三个逻辑库)、外置持久文件根;生产/测试/验证三套环境独立配置、数据、身份与凭据;形成可验证的完整备份恢复与环境克隆隔离流程(需求 R1—R8、决策 KTD1—KTD7 见方案)。
|
||||
|
||||
**接纳范围**:方案 Implementation Units U1—U6 及其测试;各仓仅做阻塞目标的最小接线。U1 环境与数据归属合同;U2 容器数据库与 svc 接线;U3 迁移与文件引用兼容;U4 组合备份与恢复新目标;U5 测试/验证克隆与环境选择;U6 单二进制与组合验收、运维交付。
|
||||
|
||||
**非目标**:生产迁移执行(方案 O1,另获窗口授权后单独安排);高可用平台、Kubernetes、托管云数据库、全量对象存储、应用微服务化;零停机/实时复制/PITR;删除既有 SQLite/原生 PG 路径;PG 大版本升级。
|
||||
|
||||
**分级测试策略**:L2→L1 用 `make test-affected` 最小直接检查;L1→main 最终窗口跑完整组合(`make test`,含 `test-pg`、`svc-realcombo`、`test-regulations-kb`);容器与文件恢复必须有真实临时环境验证,缺 DSN 的跳过不计通过。
|
||||
|
||||
**验收条件**:方案 Definition of Done——单二进制在目标环境启动并符合两容器三库边界;三环境独立可启停重建且隔离实测;完整备份恢复到新目录且原文/引用不变;真实迁移 fixture 对账通过;精确组件组合、测试证据、独立 Review、必需 CI 与规范文档齐备。
|
||||
|
||||
**追加领域规则**:manifest 是身份与期望态唯一权威,Compose 只作版本化模板(KTD3);普通启停/重建不隐式删卷、不隐式恢复旧备份、不在数据库失败时回退空 SQLite(R7);镜像按选定 digest/扩展版本逐项核对(KTD7);秘密不入仓不入日志(baseline 硬边界 2)。
|
||||
|
||||
**生命周期**:M1(U1→U2)→ M2(U3→U4)→ M3(U5→U6)→ M4 生产切换(O1 另行授权,不在本 initiative 内)。U1 完成后按实测数据规模细化后续工期;全部必需子任务合入后置 `accepting_requirements=false` 收口合入 main。
|
||||
|
||||
## 子任务登记
|
||||
|
||||
| 子任务分支 | 必需 | 状态 | PR |
|
||||
|---|---|---|---|
|
||||
| feature/container-storage-plan-u1 | 是 | 未开始 | — |
|
||||
| feature/container-storage-plan-u2 | 是 | 未开始 | — |
|
||||
| feature/container-storage-plan-u3 | 是 | 未开始 | — |
|
||||
| feature/container-storage-plan-u4 | 是 | 未开始 | — |
|
||||
| feature/container-storage-plan-u5 | 是 | 未开始 | — |
|
||||
| feature/container-storage-plan-u6 | 是 | 未开始 | — |
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 关联方向与规划
|
||||
|
||||
- 适用状态:适用(本 initiative 的方案与实施计划)
|
||||
- 主记录路径/版本:`docs/superpowers/plans/2026-10-22-container-storage-plan.md`(2026-10-22 审阅快照,含落库状态注记)
|
||||
- 对应里程碑:M1(U1→U2)→ M2(U3→U4)→ M3(U5→U6);M4/O1 生产切换不在本 initiative
|
||||
|
||||
## 需求分析
|
||||
|
||||
| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| R1—R8 | 见方案 Requirements 表(R1 单二进制、R2 容器化、R3 环境绑定、R4 恢复可核对、R5 迁移覆盖、R6 克隆隔离、R7 无隐式破坏、R8 操作记录) | 符合(方案已核对现有 svc/manifest/迁移器/KB Compose 能力) | 高 | 见方案各单元完成条件与 Definition of Done | U1←无;U2←U1;U3←U1/U2;U4←U2/U3;U5←U4;U6←U1—U5 | 纳入本增量(U1—U6) |
|
||||
|
||||
## 方案与计划
|
||||
|
||||
- 方案引用:`docs/superpowers/plans/2026-10-22-container-storage-plan.md`(2026-10-22 审阅快照;Product Contract、KTD1—KTD7、High-Level Technical Design)
|
||||
- 实施计划引用:同上 Implementation Units U1—U6 与里程碑 M1—M3(M4/O1 不在本 initiative)
|
||||
- 测试计划引用:同上 Verification Contract 与各单元测试文件/场景清单
|
||||
|
||||
## 阶段记录
|
||||
|
||||
| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 |
|
||||
|---|---|---|---|---|---|
|
||||
| 需求拆解 | 方案 Requirements R1—R8 | 已完成 | 主模型(方案会话)+ triage NEW | 2026-10-22 | /tmp/container-storage-plan-triage-20261022-b.json |
|
||||
| 需求评估 | 方案 Problem Frame/边界假设 | 已完成 | 三视角文档审阅 + 主模型复核 | 2026-10-22 | /tmp/container-storage-plan-20261022/review.md |
|
||||
| 需求表确认 | R1—R8 纳入 U1—U6 | 已完成 | 主模型 | 2026-10-22 | 方案 Requirements 表 |
|
||||
| 方案设计 | KTD1—KTD7 | 已完成 | 方案会话(已审阅) | 2026-10-22 | plan.md |
|
||||
| 实施计划 | U1—U6 + M1—M4 | 已完成 | 方案会话(已审阅) | 2026-10-22 | plan.md Implementation Units |
|
||||
| 实施与自检 | 待定 | 待执行 | — | — | — |
|
||||
| 正式测试与验收 | 待定 | 待执行 | — | — | — |
|
||||
| 代码 Review | 待定 | 待执行 | — | — | — |
|
||||
| 合入 | 待定 | 待执行 | — | — | — |
|
||||
| 合入核验与收口 | 待定 | 待执行 | — | — | — |
|
||||
|
||||
## 探索试验与迭代
|
||||
|
||||
| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 955a32de2bb49171348bad5f566cce935df19212 | 955a32de2bb49171348bad5f566cce935df19212 | 2026-10-22T09:09:21Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 交付调度
|
||||
|
||||
- 调度状态:未入队;调度记录:无
|
||||
- entry_id:无;attempt_id:无;窗口执行者:无
|
||||
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
|
||||
- 调度依据:policy/delivery.md#合入调度
|
||||
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
- 影响与剩余项:
|
||||
|
||||
## 规划进展与下一步
|
||||
|
||||
- 本次推进/剩余:待核对(依据实际成果更新适用方案/规划)
|
||||
- 首选下一任务:待核对(无适用规划、已完成或受阻时如实说明,不虚构任务)
|
||||
- 推荐依据:待填写
|
||||
- 前置条件/待确认:待核对
|
||||
- 预期产物/验收条件:待定义
|
||||
- 重复任务核对:待核对(已有/在途任务、PR 与规划记录)
|
||||
- 启动授权:未授权(建议不自动启动新任务,仍须明确授权)
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据)
|
||||
- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T09:08:28.920468+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"after_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"branch": "main",
|
||||
"target_branch": "main",
|
||||
"target_head": "955a32de2bb49171348bad5f566cce935df19212",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=main head=955a32de2b",
|
||||
"[remote] origin/main=955a32de2b ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=5",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] 955a32de2b(完整:955a32de2bb49171348bad5f566cce935df19212)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "ed0263b348ce265d081aab1faa0d1370f2a9478410fadfc3b9cec8a810c1295f",
|
||||
"receipt_sha256": "124a47e6bfbb43cbbbc398ad10bf42e3d448373a80affd297ed25c41d8b2b07a",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/124a47e6bfbb43cbbbc398ad10bf42e3d448373a80affd297ed25c41d8b2b07a.json"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,178 @@
|
||||
# fix/gitea-endpoint-migration 任务卡
|
||||
|
||||
任务主记录:<PR 链接,创建后回填>;状态:进行中;owner:weichen;状态:进行中;owner:weichen
|
||||
|
||||
- 权威仓:ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 3d3474a731f9ef3ee1f41935e63bca3afb70db56
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-gitea-endpoint-migration(worktree);分支:fix/gitea-endpoint-migration
|
||||
- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false
|
||||
- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=f4118023ba80fc3d75db8755e2dd21e4697b7a15b554f0df85717ac6ac814cf8;triage_receipt=/tmp/gitea-migration-triage.json;receipt_sha256=e0bca239b2c0ebaea8fb76d5b743de43d06a3ee911ea0e2587bc07226367110d
|
||||
|
||||
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
|
||||
- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进)
|
||||
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)
|
||||
- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入)
|
||||
- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
**目标**:Gitea 服务器迁移后(web https://gitea.naitangbaobao.cn,SSH gitea.naitangbaobao.cn:52222,凭据不变),更新本仓全部功能性端点引用:`.gitmodules`、`scripts/task.sh`、`scripts/task_triage.py`、`scripts/facts-verify.sh`、`scripts/svc.sh`(RDM_ORIGIN_DEFAULT)、`.gitea/workflows/ci.yaml`、`tests/test_facts_verify.py`、`tests/test_ci_credentials.py`、`tests/test_svc_realcombo.py`、`tests/test_task_triage.py`、`tests/test_ci_wait.py`、`policy/facts.md`(F1/F2/F3 端点与 last_verified)、`policy/delivery.md` 合入队列链接。旧值:git-home.u1680832.nyat.app:30319(web/API)、git-ssh.u1680832.nyat.app:31411、ssh://git@192.168.3.2:2222(SSH)。
|
||||
|
||||
**完成条件**:仓内除历史记录(.task/**、docs/superpowers/ 归档规格、runtime/)外无旧端点引用;`make facts-verify` 中 F1/F2/F3(本次迁移端点)PASS;`make test-governance` 与相关单测通过。已知漂移(非阻塞,转后续任务):F4(退役 git-panel 端点)判据过时,迁移后该端点 TLS unexpected-eof,F4 严格判据不再成立,需要宿主级子模块物化环境刷新;facts-verify 非法失败不计入本任务交付门禁(不在 make test 内)。
|
||||
|
||||
**非目标**:历史记录/归档文档中的旧 URL;其他仓(RDM/KB 等)仓内引用(各自任务处理);本地 ~/.ssh/config、~/.secrets/gitea、各仓 remote(已由 owner 更新);policy/facts.md F4(退役旧端点 git-panel 的历史事实,与本次迁移无关)。
|
||||
|
||||
## Agent 实施收据
|
||||
|
||||
路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。
|
||||
|
||||
| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| gitea-endpoint-migration | 端点迁移(13 文件) | — | implementation | cursor,… | 1 | cursor(auto) | failed-unknown | 无(终态 JSON 解码失败;现场保留,diff 导出 /tmp/gitea-migration-attempt1.patch) | 否 | /tmp/gitea-migration-run2-receipt.json |
|
||||
| gitea-endpoint-migration | 同上(采用 attempt1 补丁) | — | implementation | cursor,… | 2 | cursor(auto) | failed-unknown | 无(终态 JSON 解码再次失败;cursor 本单元 2 次尝试) | 否 | /tmp/gitea-migration-run3-receipt.json |
|
||||
| gitea-migration-routing-promote-antigravity | AGENT-EXECUTION.md implementation 顺序临时调整 | — | implementation | cursor,… | 1 | cursor(auto) | completed | apply | 否 | /tmp/gitea-migration-routing-receipt.json |
|
||||
| gitea-migration-f4-refresh | policy/facts.md F4 行更新 | — | implementation | antigravity,pi-glm,… | 1 | agy | failed-unknown | 无(agy OAuth 网络故障启动即败,0 轮对话) | 否 | /tmp/gitea-f4-receipt.json |
|
||||
| gitea-migration-f4-refresh | 同上 | — | implementation | antigravity,pi-glm,… | 2 | agy | failed-unknown | 无(agy OAuth 仍不可达) | 否 | /tmp/gitea-f4-receipt2.json |
|
||||
| gitea-migration-f4-refresh | 同上 | — | implementation | antigravity,pi-glm,… | 3(agy 耗尽) | agy | failed-unknown | 无(agy OAuth 持续不可达,3 次尝试耗尽) | 否 | /tmp/gitea-f4-receipt3.json |
|
||||
| gitea-migration-routing2 | AGENT-EXECUTION.md 再调整(pi-glm 优先) | — | implementation | — | 1 | — | failed-unknown(agy 启动即败,未达目标) | 无 | 否 | /tmp/gitea-migration-routing2-receipt.json |
|
||||
| gitea-migration-routing-revert | 回退 antigravity 优先为原顺序 | — | host(baseline-match revert) | — | — | — | committed | aa1e8c2b | — | 本行披露;admit 经 baseline 精确内容核对 |
|
||||
| gitea-migration-f4-refresh | F4 行刷新 | — | implementation | cursor(原序恢复后重试) | 4 | cursor(auto) | blocked | 诚实阻塞:F4 验证依赖宿主级子模块物化,worker 隔离区无法通过;现场保留。F4 刷新转后续任务 | 否 | /tmp/gitea-f4-receipt4.json |
|
||||
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 3d3474a731f9ef3ee1f41935e63bca3afb70db56 | 3d3474a731f9ef3ee1f41935e63bca3afb70db56 | 2026-10-22T11:37:21Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:端点迁移完成。11 文件 + plugins/registry.json 修复;F1/F2/F3 新端点实测 PASS;目标单测与 make test-governance 全部 OK。
|
||||
- 验证:F1/F2/F3 PASS(新端点实测);目标单测(facts_verify/ci_credentials/task_triage/ci_wait/svc_precheck)全部 OK(SELF 收据已导入);F4 FAIL 已知漂移(见完成条件注记)。make test-governance 与 svc_realcombo 待 host 最终验证。
|
||||
- 影响与剩余项:端点迁移本体完成(组件仓 14/14 已合入迁移 main;Dev 仓 13 组件 pin 已对齐)。阻塞项:modules/backend pin 对齐需要 Backend 代码先升级到当前 deps API 世代(vet 实测 API 不兼容),已作为独立集成任务移交;PR #706 保持开放、不请求合入,附阻塞说明评论。非阻塞遗留:policy/facts.md F4 判据刷新(需宿主级环境);Agent-Command/Intent/Native/Squad 的 fix/gitea-endpoint-migration-compat 分支与 Backend 仓 integration/gitea-endpoint-migration 分支为过渡产物,Backend 集成完成后可删。
|
||||
|
||||
## 合入与清理
|
||||
|
||||
- Review 记录:r1 REVIEW FAIL(阻塞项:plugins/registry.json 旧 SSH 端点,pi-glm 全新会话)→ repair transport 修复 → r2 REVIEW PASS(pi-glm 全新会话;49 处新端点书写一致、负例语义保留、facts_verify/ci_credentials/task_triage/ci_wait 全部离线实测、无秘密入仓;报告 docs/superpowers/reviews/2026-10-22-gitea-endpoint-migration-review.md)。base=3d3474a7,head 见最终推送。
|
||||
- 冲突记录:待合入前 make task-sync(main 前进时重放受影响验证)
|
||||
- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果)
|
||||
- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验)
|
||||
- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置)
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T11:36:22.453253+00:00",
|
||||
"command": "make precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev",
|
||||
"before_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56",
|
||||
"after_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56",
|
||||
"branch": "main",
|
||||
"target_branch": "main",
|
||||
"target_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'",
|
||||
"[repo] branch=main head=3d3474a731",
|
||||
"[remote] origin/main=3d3474a731 ahead=0 behind=0",
|
||||
"[tree] clean submodules_drift=0 uninit=0",
|
||||
"[worktrees] linked=7",
|
||||
"[initiatives] local cards (not authoritative): none",
|
||||
"[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)",
|
||||
"[facts] policy/facts.md 6 条事实均在有效期内",
|
||||
"[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md",
|
||||
"[R0] 3d3474a731(完整:3d3474a731f9ef3ee1f41935e63bca3afb70db56)",
|
||||
"[worktree] before-sync: recursive component checks passed",
|
||||
"[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'"
|
||||
],
|
||||
"output_sha256": "8fe96dfd5cc52716edb7b4361609ea244aa109e4028777d1cdaf8637be6b8a08",
|
||||
"receipt_sha256": "3b4027f3dabd3bb334b3ac7767771099e439b8e67c3677c997a5c78c8c7ddfb2",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/3b4027f3dabd3bb334b3ac7767771099e439b8e67c3677c997a5c78c8c7ddfb2.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T13:49:47.717844+00:00",
|
||||
"command": "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_facts_verify tests.test_ci_credentials tests.test_task_triage tests.test_ci_wait tests.test_svc_precheck",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-fix-gitea-endpoint-migration",
|
||||
"before_head": "6cb6a276ca3e10101cd76db1bd3d77834e54e551",
|
||||
"after_head": "6cb6a276ca3e10101cd76db1bd3d77834e54e551",
|
||||
"branch": "fix/gitea-endpoint-migration",
|
||||
"target_branch": "main",
|
||||
"target_head": "cb36b59accd0418c5ca952912d7c181b83038a4c",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
".............................................................................",
|
||||
"----------------------------------------------------------------------",
|
||||
"Ran 77 tests in 30.719s",
|
||||
"",
|
||||
"OK"
|
||||
],
|
||||
"output_sha256": "8e55dbb47a553d2c5a34ce16057ed2d742b908e45f6ab4f8119212911a554e74",
|
||||
"receipt_sha256": "2c2e2c19d6f0cc3734c9c6ae986bc3e3021a91caa320373d66158cab4f1ec124",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/2c2e2c19d6f0cc3734c9c6ae986bc3e3021a91caa320373d66158cab4f1ec124.json"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Local receipt: SELF. Digests detect corruption; they do not authenticate a reviewer or CI. -->
|
||||
```json
|
||||
{
|
||||
"summary_schema": 1,
|
||||
"source": "SELF",
|
||||
"timestamp": "2026-10-22T13:50:40.361266+00:00",
|
||||
"command": "make test-governance",
|
||||
"exit_code": 0,
|
||||
"cwd": "/home/weichen/projects/Agent-System-Dev-wt-fix-gitea-endpoint-migration",
|
||||
"before_head": "6cb6a276ca3e10101cd76db1bd3d77834e54e551",
|
||||
"after_head": "6cb6a276ca3e10101cd76db1bd3d77834e54e551",
|
||||
"branch": "fix/gitea-endpoint-migration",
|
||||
"target_branch": "main",
|
||||
"target_head": "cb36b59accd0418c5ca952912d7c181b83038a4c",
|
||||
"root_target": "main",
|
||||
"immediate_target": "main",
|
||||
"before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"output_tail": [
|
||||
"test_feature_missing_parent_is_allowed (test_write_guard.WriteGuardTests.test_feature_missing_parent_is_allowed) ... ok",
|
||||
"test_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok",
|
||||
"test_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok",
|
||||
"test_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok",
|
||||
"test_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok",
|
||||
"test_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok",
|
||||
"test_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok",
|
||||
"test_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok",
|
||||
"test_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok",
|
||||
"",
|
||||
"----------------------------------------------------------------------",
|
||||
"Ran 553 tests in 368.627s",
|
||||
"",
|
||||
"OK (skipped=18)",
|
||||
"make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-fix-gitea-endpoint-migration'"
|
||||
],
|
||||
"output_sha256": "e283e1b732170416dfd71d6ccb9118f756ac64d123a8c50b939592185c0096de",
|
||||
"receipt_sha256": "aa4fdc711f5d0839248ecefdc5e1242c6cb2d04ca3efde90099300e456f78f71",
|
||||
"receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/aa4fdc711f5d0839248ecefdc5e1242c6cb2d04ca3efde90099300e456f78f71.json"
|
||||
}
|
||||
```
|
||||
| 同步(记录前对象) | cb36b59accd0418c5ca952912d7c181b83038a4c | 1383cf1d2bc0dbaaecebed7d20c26d6ec8d2d783 | 2026-10-22T14:08:07Z |
|
||||
| 同步(记录前对象) | 6ced938a126981ee2a4fab671a2da94f49cb56c5 | 8207d780d12fa8c605b18fc7fa5f8cd3e8f6bc42 | 2026-10-22T16:37:40Z |
|
||||
@@ -0,0 +1,75 @@
|
||||
# 独立代码 Review:feature/intent-multi-turn-a1(A1 多轮意图闭环)
|
||||
|
||||
- Reviewer:独立 CLI 会话(pi,未参与本分支实施)
|
||||
- 日期:2026-10-22(UTC)
|
||||
- 对象:`git diff 955a32de...9126b09b`(Dev 超级仓);组件焦点 `modules/backend a75783fd..9ee752c3`
|
||||
- 任务卡:`.task/feature-intent-multi-turn-a1.md`(设计决定:run-cancel 为第一业务动作、30min TTL、revision、cmdbus 确认不变)
|
||||
- 性质:findings-only,未改任何代码;只新增本报告文件。
|
||||
|
||||
## 1. 范围与方法
|
||||
|
||||
- 超级仓 diff:`policy/architecture-baseline.json` +3 边、`modules/backend` gitlink a75783fd→9ee752c3、任务卡新增。
|
||||
- backend 组件 diff(3 commits,+3267/−21):新包 `internal/intentsession`、`internal/store/intent_session.go`(表 v52)、`internal/route`(Cancel 裁决)、`internal/server`(`intent_session.go`、`intent_cancel.go`、`server.go` chat 接线)、`cmd/storage-migrate`(copyOrder 补 intent_session)。
|
||||
- 方法:全量逐文件静态精读 + 调用链追踪(chat postMessage → fuseIntentSession → route.Decide → admitIntentCommand → Bus.Dispatch / handleIntentCancel → dispatchCancel;runmgr HITL 转轨判定的 route 契约),测试断言逐条对照审查镜头。
|
||||
|
||||
### 测试执行(诚实出口:NOT_RUN)
|
||||
|
||||
- 本会话**未能复跑**聚焦测试(`go test ./internal/intentsession/... ./internal/route/... ./internal/server/...`):当前工作区组件 checkout 只部分物化——`modules/backend/deps/agent-system-core、error-code-system、logging-system、agent-document-renderer` 为空目录,go.mod replace 闭包无法解析;无网络;本机模块缓存仅有 2026-10-13 伪版本(≠本次 pin 的提交),用它们编译出的结果不能作为本 PR 的证据,故弃用。
|
||||
- 尝试过程可复核:为诊断曾临时在 `modules/backend/deps/` 建指向超级仓 `deps/` 的符号链接并随即删除(超级仓 deps 亦缺上述四个组件,`go build` 报缺 go.mod 为证);现场已恢复,`git status --porcelain` 干净(见 §5)。
|
||||
- 任务卡记录的 verify-a1b/a1c exit=0、批二 CI 全绿:本会话无法重放,按 `[未经证实]` 处理;下述结论全部来自静态审查。按任务单要求未跑 `make test`。
|
||||
|
||||
## 2. 按镜头的结论(spec / security / regressions / tests)
|
||||
|
||||
**spec(多轮闭环)**:缺槽动作 → 澄清(`suspendIntentSession`,intent_session.go:273-310)→ 回答折叠(`fuseIntentSession` → `ApplyAnswer`,slot 齐则合成与完整识别同形的 MatchResult 走同一准入/派发路径,server.go:1252-1258 + intent_session.go:110-155)→ 完成;TTL 30min、写时续期、读侧惰性删除(intentsession.go:12-13、344-357);revision 单调、`ErrStale` 拒绝陈旧回答(intentsession.go:206-212、359-364);双用户/项目/会话键隔离(store 全查询带 owner 过滤)。主链路成立,测试覆盖到每一环。**例外见 B1/M1**。
|
||||
|
||||
**security**:会话不携带授权——补齐后仍走 `admitIntentCommand` + `Bus.Dispatch`,高危确认卡由 cmdbus 收口(TestIntentSessionStillRequiresHighRiskConfirmation 断言确认前处理器不执行);取消经 `run.cancel` 注册命令走同款 Dispatch,卡确认前不记「已取消」(intent_cancel.go:150-186,TestIntentCancelSurfacesHighDangerConfirmCard 覆盖确认→执行全链);跨用户运行 `GetRun(owner,…)` 不可见即澄清、不泄露存在性(intent_cancel.go:73-79,TestIntentCancelNeverResolvesAnotherOwnerRun);候选只列本人本会话未终态运行(intent_cancel.go:97-112)。**例外见 M2**。
|
||||
|
||||
**regressions**:无挂起预期时 fuse 段空转,`TestIntentSessionLegacyResponsesUnchanged` 做逐字节对照;route 契约——cancel 移出 unconsumedKinds 但 `StartFound` 刻意不置位(route.go:181-188),runmgr HITL 转轨判定(runmgr.go:2331-2334 只看 StartFound)行为与 main 一致;U6 准入——fused 结果同样过 `dispatchCommand`(res.Complete())与 `admitIntentCommand`(声明 schema 映射、必填不满足即 Blocked)两道闸,非法/缺失槽位不可能执行。**例外见 M1(累计槽位跨任务泄漏)。**
|
||||
|
||||
**tests**:覆盖面好——触发条件(有/无挂起、同意图/异意图、过期、隔离)、revision、高危确认、legacy 逐字节、显式/会话/歧义靶子、确认卡。缺口:无「同会话内换任务(异意图)再挂起」用例(正是 B1 场景);无真实并发写测试(revision 逻辑覆盖属逻辑等价,可接受);`answerFromRecognition` 显式落槽不看意图归属(intent_session.go:203)无专项用例。
|
||||
|
||||
## 3. Findings
|
||||
|
||||
### Blocker
|
||||
|
||||
**B1(spec/正确性)换任务再挂起时,上一个动作的累计槽位被继承,可把陈旧值派发给新动作。**
|
||||
`Service.Suspend` 无条件合并旧行槽位:`slots = mergeSlots(cur.Slots, slots)`(internal/intentsession/intentsession.go:193-194)。宿主侧 `suspendIntentSession` 对「route 层槽位未就绪」的新动作一律走 Suspend(internal/server/intent_session.go:273-310),不区分新旧预期是否同一动作。可达序列(同会话、TTL 内):
|
||||
1. T1 意图 A(如 `demo.run-cancel`)缺 reason → 挂起,累计 `{run_id: R-7}`;
|
||||
2. T2 用户改做意图 B(如 `demo.purge`,缺 scope)→ fuse 判「异意图、无落槽」不碰会话(intent_session.go:124-127)→ route 澄清 → `Suspend(B)` 继承 `{run_id: R-7}`;
|
||||
3. T3 用户补 scope → `ApplyAnswer` 合并全量槽位判齐(intentsession.go:227)→ `completedRecognition` 以累计槽位派发 B(intent_session.go:245-262);
|
||||
4. `mapSchemaParams` 只丢弃**未声明**键,B 声明过的 `run_id` 会带陈旧值 R-7 进 params(internal/server/intent_admission.go:69-84)——若 run_id 是 B 的必填,准入反而被陈旧值满足。
|
||||
这与包自己的不变量直接冲突(「绝不允许旧槽位漏进新任务」intentsession.go:25;「别的任务的输入绝不混进当前动作」intent_session.go:53、188)——该保护只做在了 fuse 的回答投影上(answerFromRecognition 异意图不并入,intent_session.go:180-208),Suspend 覆写预期时漏了。建议:Suspend 时若 `cur.Expectation` 存在且 `IntentID/Action` 与新预期不同,则丢弃旧累计槽位(或显式要求宿主先 Drop);并补「异意图换任务」回归用例。涉同一镜头的次生风险见 minor m4。
|
||||
|
||||
### Major
|
||||
|
||||
**M2(security/错误路径)`applyCancel` 吞掉存储错误后,可能把「放弃补槽」滑成「立即取消一个真实运行」。**
|
||||
同意图取消的设计语义是只清挂起预期、不派发(intent_session.go:20-24)。但 `applyCancel` 对非 ErrStale 的存储错误同样返回 nil 且错误被丢弃(internal/server/intent_session.go:165-170),`fuseIntentSession` 整体返回无会话语义 → postMessage 继续走 `route.KindCancel` 规划(server.go:1273-1279)。此时挂起行的 `run_id` 槽位经 `sessionRunTarget`→`Binding` 仍解出靶子(intent_session.go:313-327),`planIntentCancel` 对发起人可见即放行;若部署未注册 `run.cancel`,走 `dispatchCancel` 本地路径**立即 `CancelRun`、无任何确认**(intent_cancel.go:164-176)。即:一次瞬时存储故障把「算了不取消」变成「未确认取消 R-7」。建议:applyCancel 对 `err != nil`(非 ErrStale)向上传播错误(writeStoreErr),fail-closed。
|
||||
|
||||
### Minor
|
||||
|
||||
- **m1(文档失实)** `internal/store/intent_session.go:11-12` 声称该表「不进 storage-migrate 的 copyOrder(同 admission_leases)」,但 `cmd/storage-migrate/main.go:311-313` 已把 `intent_session` 加入 copyOrder(且是清单夹具测试 `main_test.go:218` 的强制要求,9ee752c 补得对)。代码正确、注释过期,需同步删改;旧源库缺表已有 `sqliteHasTable` 跳过(main.go:400-402),无行为问题。
|
||||
- **m2(回话如实性)** 总线路径派发被拒(StateRejected)时 `cancel.accepted=false` 但 `cancel.reason` 为空(internal/server/intent_cancel.go:142),与本地路径「失败带原因」不对称;receipt 虽随响应透出,建议把拒绝原因写入 `result.Reason`。
|
||||
- **m3(过期边界 fail-open)** `expires_at` 解析失败得零值时 `live()` 视为「永不过期」(intentsession.go:366-373 + store `parseTime` 容错返回零值)。当前写入方恒写 TTL,风险仅来自自身写坏的数据,但与全文件 fail-closed 纪律不一致:解析失败宜按已过期处理。
|
||||
- **m4(与 B1 联动)** 显式 `set_slot/change_slot` 命令「不看意图归属」直接写入当前挂起预期(intent_session.go:196-203,注释称有意)。B1 修复(换任务即弃旧槽位)落地后,此路径仍可把异动作的落槽命令并入当前预期;建议改为「命令名 ∈ 当前预期 Missing/声明键才采纳」,或补用例锁定现状语义。
|
||||
|
||||
### Notes
|
||||
|
||||
- **n1** 「放弃补槽」轮仍按 KindTurn 起普通回合(server.go:1273-1279 不改 routeKind;1340-1345 仅 KindCancel 拦截),响应同时带 `intent_session` note 与 run——与 main 上 cancel 落 turn 的旧行为一致,测试未固定该组合,建议在任务卡确认一次。
|
||||
- **n2** `completedRecognition` 不搬运 `Risk/RequiresConfirm`(intent_session.go:245-262):全仓 grep 确认 `Decision.RequiresConfirm` 目前无消费者(route.go:114-116 仅透传),确认权在 cmdbus danger,今日无行为影响;将来若有人消费该字段,fused 路径会静默不同——建议留注释或补字段。
|
||||
- **n3** `PutIntentSession` upsert 后读回非原子(store/intent_session.go:74-93),并发双写可能都读到同一新 revision;当前会话请求路径单写者,无实害,建议加注释固化「每会话单写者」前提。
|
||||
- **n4** 未注册 `run.cancel` 的本地回落取消无确认卡——与既有 `POST /api/runs/{id}/cancel` 逐字同款、属设计决定(任务卡决定 1/4 与 intent_cancel.go:15-17 注释一致),非本 PR 引入;注册了命令的部署确认链有测试护栏。记录在案以回应「确认是否被绕过」镜头:**会话路径在命令注册时不绕过确认**。
|
||||
- **n5** 架构基线 3 条新边与实际 import 逐一核对相符(intentsession→intentgate/store;server→intentsession);基线治理测试本会话未复跑(同 §1 NOT_RUN 原因)。
|
||||
- **n6** 任务卡记载的 svc governance 偶发失败为已知预存问题、按任务单不属本次范围,未复跑 `make test`。
|
||||
- **n7** 测试缺口清单:异意图换任务再挂起(B1 回归用例)、applyCancel 存储错误注入、显式落槽跨意图(m4)。
|
||||
|
||||
## 4. 结论
|
||||
|
||||
主链路(挂起→澄清→补齐→同路派发)、过期隔离、revision、双用户隔离、取消三态(显式/会话绑定/歧义→如实澄清)与 cmdbus 确认收口均实现且有测试护栏,route/runmgr 契约保持。但以下问题应先修再合:
|
||||
|
||||
- Blocker B1:跨动作挂起继承旧累计槽位,陈旧值可进新动作派发参数(intentsession.go:193-194;admission 只滤未声明键)。
|
||||
- Major M2:applyCancel 吞存储错误 fail-open,可滑向无确认的本地取消(intent_session.go:165-170 → server.go:1273 → intent_cancel.go:164-176)。
|
||||
- Minor m1–m4 建议随修复一并处理(m1 文档、m2 回话、m3 过期 fail-closed、m4 落槽归属)。
|
||||
|
||||
最终判定(理由如上三条):
|
||||
|
||||
REVIEW VERDICT: BLOCK
|
||||
@@ -0,0 +1,44 @@
|
||||
# Independent Code Review — feature/exec-recovery-diagnostics-a2 (A2 run diagnostics)
|
||||
|
||||
- Reviewer: independent CLI review session (`a2-independent-review` unit; fresh session, not the implementer)
|
||||
- Date: 2026-10-22
|
||||
- Review object: this checkout @ `d0b2bde9` (branch `feature/exec-recovery-diagnostics-a2`) versus authoritative main. No `origin/*` refs exist in this sandbox; task card R0 = `c9244328` (= `d0b2bde9~5`, the merged main tip `c92443280ef8e9ed29bf5bf5250df650c01b4478`), so `git diff c9244328...d0b2bde9` ≡ `git diff origin/main...HEAD`.
|
||||
- Dev-level scope: task card `.task/feature-exec-recovery-diagnostics-a2.md` (new), `modules/backend` gitlink `9ee752c3` → `06b0c9f1`, `policy/architecture-baseline.json` +3 edges. Verified: dev diff is exactly these 3 files; the three edges (`backend/rundiag backend/cmdbus`, `backend/rundiag backend/store`, `backend/server backend/rundiag`) sit in correct sorted positions and match the actual import graph (see N5).
|
||||
- Backend scope: `git -C modules/backend diff 9ee752c..06b0c9f` — 7 files, +1418/−0: `internal/rundiag/rundiag.go` (488), `internal/rundiag/rundiag_test.go` (440), `internal/server/run_diagnostics.go` (32), `internal/server/run_diagnostics_test.go` (226), `internal/server/server.go` (+2), `internal/store/run_diagnostics.go` (110), `internal/store/run_diagnostics_test.go` (120).
|
||||
- Method: read-only review of the full diff plus the neighboring run-read/cancel routes, the pre-existing store methods the aggregator reuses, and the cmdbus pending-confirm write path. No code changed; the only write is this report.
|
||||
|
||||
## Findings
|
||||
|
||||
### Blockers
|
||||
|
||||
None.
|
||||
|
||||
### Major
|
||||
|
||||
None.
|
||||
|
||||
### Minor
|
||||
|
||||
- **MI1 — Evidence-driven wait states outrank terminal run statuses; a completed/failed run can be reported as `waiting_hitl`.** In the responsibility switch the HITL arm `case ev.Run.Status == core.RunWaitingHITL || card != nil || decision != nil` (`modules/backend/internal/rundiag/rundiag.go:252`) is evaluated before `failed` (:281) and `completed` (:296). A run that ended while a decision card scoped only by owner+conversation (human_decisions has no run column; attributed by the time-window inference at :432-448) remained `pending`, or with a leftover unresolved run-scoped HITL card, yields `state=waiting_hitl`, `role=human`, `next_step.action=confirm_card` for a terminal run. This is exactly the fixed precedence the task card approved and the documented bounded attribution (`rundiag.go:15-19`), and `run.status` stays visible in the report — but a follow-up that suppresses evidence-driven wait mapping (or adds a note) when the run is terminal would remove the confusing "confirm card" advice on finished runs. Not blocking: matches the approved design; purely advisory output.
|
||||
- **MI2 — Silent truncation of side-effect evidence contradicts the package's own honesty contract.** `sideEffects` stops appending at `maxSideEffects = 50` with no note (`rundiag.go:342-366`, cap at :57), and `Gather` fetches only the 200 newest audit envelopes (`maxAuditScan`, `rundiag.go:175` → `ListAudits` orders `time DESC`, `internal/store/audit.go:147-152`), so a busy conversation can under-report a run's operations without any indication. Every other evidence gap in the package adds a note (even "absence of records is not proof of no effect", :313-315); a "side effects truncated" note would complete that contract. Diagnostic-only impact; recorded evidence is still correct as far as it goes.
|
||||
- **MI3 — Precedence is tested only at the top of the chain.** Of the ordered arms at `rundiag.go:224-310`, only "unreleased recovery hold wins over everything" (`rundiag_test.go:81-89`) exercises interleaved evidence; combinations such as released-hold+interrupted+open-HITL-card (→ `reconciling`) or decision+pending-confirm (→ `waiting_hitl`) are untested and rest on the switch order alone. Low risk because the order is literal and table-covered per state; suggesting coverage only because precedence is the stated spec center of this unit.
|
||||
|
||||
### Notes
|
||||
|
||||
- **N1 — Per-state derivation and precedence are complete and match the task-card contract.** All eight states are derivable: `unknown` fail-closed on unreadable hold (:226-233), `recovery_hold` on an open hold row (:234-240), `reconciling` for released-hold+`RunInterrupted` residue (:241-251, predicate at :402-403), `waiting_hitl` from status/run-scoped card/time-windowed decision (:252-273), `confirmation_pending` from an owner-scoped pending confirm parsed out of the dispatch payload (:274-280, :453-471), `failed` with retry-safety gated on unknown receipts (:281-295), `completed` (:296-298), `running` for running+pending (:299-303), `unknown` default for unmapped statuses (cancelled; interrupted without hold; :304-310). Precedence order is exactly `recovery_hold > reconciling > waiting_hitl > confirmation_pending > failed > completed > running > unknown`, with the fail-closed `HoldUnknown` degradation deliberately first.
|
||||
- **N2 — Security: scoping is byte-for-byte the neighboring run-read pattern; no cross-owner leakage path exists.** The handler resolves the run via `s.st.GetRun(s.owner(r), r.PathValue("id"))` (`internal/server/run_diagnostics.go:25`) — identical to `getRun` (`server.go:1592`) — where `owner` comes only from the auth context (`auth.go:220-225`), `GetRun` filters `WHERE id=? AND owner=?` with cross-tenant = `ErrNotFound` (`messages.go:307-316`), and `writeStoreErr` maps it to 404 indistinguishable from a missing run (`server.go:2617-2620`). Every follow-on query is keyed on the already-authorized run's own fields, never on request parameters: `GetRecoveryHold(run.ID)` (`recovery_holds.go:36`), `ActiveHITLCardsByRun(run.ID)` (`recovery.go:156`), `PendingHumanDecisionsByConversation(run.Owner, run.ConversationID)` with empty-owner defense (`store/run_diagnostics.go:51-53`), `ListOwnerPendingConfirms(run.Owner)` (:88-90), `LastRunCheckpoint(run.ConversationID, run.ID)` (:28-30), `ListAudits(run.Owner, run.ConversationID)` (`audit.go:134-140`). The `pending_confirms` payload is parsed only to extract command/owner/conversation identity; the raw payload is never echoed (`rundiag.go:453-471`).
|
||||
- **N3 — Read-only holds end to end.** All six store entry points the aggregator uses are SELECT-only (verified by reading each); `Build` is a pure function; the handler path contains no store write and the mux registers only the GET pattern (`server.go:396`). The author's `TestRunDiagnosticsIsReadOnly` additionally asserts the run row and hold are unchanged after a GET and that POST/PUT/DELETE/PATCH are not served (`run_diagnostics_test.go:153-193`).
|
||||
- **N4 — No route-registration conflicts.** `GET /api/runs/{id}/diagnostics` differs from `GET /api/runs/{id}` (:394) and `POST /api/runs/{id}/cancel` (:393) by a literal extra segment / method; ServeMux precedence is specificity-based, single registration, no duplicate pattern anywhere in the diff.
|
||||
- **N5 — Architecture-baseline edges are correct and validated.** `internal/rundiag` imports exactly `cmdbus` and `store` within the backend module (`rundiag.go:21-24`); the `agent-command` import is intentionally untracked — the baseline records only intra-repo package edges and has no deps-module edges anywhere (same precedent as `backend/cmdbus`, which also imports agent-command without an edge). `backend/server backend/rundiag` matches the handler import. Sorted positions are correct (`route < rundiag`; `ratelimit < rundiag < runmgr`), and `scripts/lint-architecture.go` compares this file against the collected import graph, so the edges are machine-checked in CI.
|
||||
- **N6 — Go tests NOT_RUN in this review workspace (environment, not code).** The backend's nested dependency checkouts (`modules/backend/deps/agent-command`, `deps/agent-system-core`, …) are empty gitlinks here and no `.git/modules` gitdirs exist; `git submodule init/update` is forbidden for this unit and network is off, so `go build ./internal/rundiag/ …` fails at module setup (`agent-command@v0.0.0 (replaced by ./deps/agent-command): reading deps/agent-command/go.mod: no such file or directory`), before any code compiles. Fallbacks applied: every changed file was fully line-read; all `core.Run*`/phase/event symbols referenced are used by pre-existing compiling backend code; `cmdbus.Request` carries `ID/Owner/ConversationID` (`cmdbus.go:163-177`) and the pending-confirm payload is the marshaled dispatch request (`cmdbus.go:522-523`), so `waitingConfirm`'s parsing is sound; the `events` table has `run_id`/`seq`/`ts`/`type` in both dialects (`migrate.go:47,71`) and the checkpoint query is served by `idx_events_conv(conversation_id, seq)`. Author-recorded receipts were reviewed and are internally consistent with the task card: `/tmp/agent-stack-b2/verify-a2b.json` (`go build ./... && go test -count=1 ./internal/server/... ./internal/rundiag/... ./internal/store/... && go vet …`, exit=0, `ok agent-system-backend/internal/{server,rundiag,store}`) and `/tmp/agent-stack-b2/verify-a2-combo2.json` (`make test`, exit=0) — author-claimed (SELF), not independently re-executed in this session.
|
||||
- **N7 — Attribution boundaries are documented and bounded; one sibling-run overlap remains.** Audit envelopes carry no runId today (host bus doesn't write it), so `belongsToRun` (`rundiag.go:371-383`) attributes by conversation + "not issued before run start". Concurrent sibling runs (subagents) in the same conversation have overlapping windows, so until the bus persists `runId`, an envelope from a sibling can appear in both runs' `side_effects`. The limitation is stated in the package doc (:17-19) and the report surfaces `operation_id`/`command_id` so a human can disambiguate; when the bus starts writing `env.RunID` the strict branch (:372-373) takes over automatically. Acceptable for a diagnostic surface.
|
||||
- **N8 — "No records ≠ no effect" honesty contract is consistently enforced** (unrecorded checkpoints → null + note at :311-312, tested at `rundiag_test.go:280-302`; empty side effects → note :313-315; unreadable hold → fail-closed unknown that can never yield `safe_retry` :226-233, tested :304-339). Only the truncation gap of MI2 falls short of it.
|
||||
|
||||
## Lens summaries
|
||||
|
||||
1. **Spec** — pass. State derivation and precedence are complete and per-state correct (N1); the JSON contract (snake_case, `last_checkpoint` explicit null, `side_effects` always an array, `next_step` never reason-less) is pinned by `TestReportJSONContract` (`rundiag_test.go:341-389`). Dev-level artifacts (task card, 3 baseline edges, gitlink `06b0c9f`) are exactly as claimed.
|
||||
2. **Security** — pass. Auth-context-only owner, tenant-filtered run fetch, 404 on cross-owner/missing (tested `run_diagnostics_test.go:62-97`), all downstream reads anchored on the authorized run, strictly read-only (N2/N3), no payload echo, empty-owner fail-closed store helpers.
|
||||
3. **Regressions** — pass. The diff is additive: 7 new files plus a 2-line route registration; no execution/recovery/runmgr path touched, no existing signature changed, no route conflicts (N4), no migration/schema/index change.
|
||||
4. **Tests** — pass with minor gaps. Per-state table of 12 cases covering all eight states plus degradation (`rundiag_test.go:78-225`), scoping at both server and store layers including cross-owner/cross-conversation/empty-owner (`run_diagnostics_test.go:62-97`, `store/run_diagnostics_test.go:13-120`), unknown-degradation at three levels (unreadable source, unmapped status, fail-closed hold; `run_diagnostics_test.go:195-226`), read-only and write-method rejection (`:153-193`), pre-run evidence exclusion (`rundiag_test.go:391-406`). Gaps: MI3 (chain precedence below the top arm), MI2 (truncation without note). Focused tests could not be re-run here (N6); author receipts for `go test ./internal/rundiag/... ./internal/server/... ./internal/store/...` and full `make test` are on record and uncontradicted.
|
||||
|
||||
REVIEW VERDICT: APPROVE
|
||||
+1
-3
@@ -41,9 +41,7 @@ execution:
|
||||
max_attempts_per_profile: 3
|
||||
|
||||
roles:
|
||||
# cursor 在 U1 单元已耗尽 4 次尝试:1 次传输中断、2 次终态 JSON 后附带尾文、1 次模型列表瞬时为空。
|
||||
# 临时调整,交付前回退。
|
||||
implementation: [antigravity, pi-glm, pi, cursor, codex]
|
||||
implementation: [cursor, antigravity, pi-glm, pi, codex]
|
||||
repair: [cursor, antigravity, pi-glm, pi, codex]
|
||||
test: [pi-glm, pi, antigravity, cursor, codex]
|
||||
review: [pi-glm, pi, antigravity, cursor, codex]
|
||||
|
||||
+1
-6
@@ -2,10 +2,7 @@
|
||||
|
||||
- 用途:开发集成父仓(gitlink 权威);非分发单元。
|
||||
- 组合:`modules/`(产品组件)+ `deps/`(共享依赖);完整组件清单以 [`.gitmodules`](.gitmodules) 为准。
|
||||
- 部署:非容器为现行默认(`storage_mode=legacy`);容器化存储改造见
|
||||
[容器化存储方案](docs/superpowers/plans/2026-10-22-container-storage-plan.md),
|
||||
环境与数据归属合同(U1)见 [deploy/data-ownership.md](deploy/data-ownership.md)。
|
||||
Releases 仓从本仓固定提交导出版本。
|
||||
- 部署:非容器;Releases 仓从本仓固定提交导出版本。
|
||||
|
||||
## 验证
|
||||
|
||||
@@ -59,8 +56,6 @@
|
||||
[计划](modules/documents/docs/proposals/2026-10-12-work-companion-ui-plan.md) 覆盖任务摘要、对话/输入、窄屏和真实状态投影。
|
||||
- [服务重启与全量数据恢复](modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md):svc 启停、异常退出后的依赖、配置/数据身份、任务/HITL/副作用、文件与组合备份恢复;
|
||||
U8 规格见 [SQLite 实例守护](docs/recovery/u8-sqlite-instance-guard.md)。
|
||||
- [单一应用二进制与容器化数据库、知识库存储](docs/superpowers/plans/2026-10-22-container-storage-plan.md):两容器三库、外置文件根、环境独立绑定;
|
||||
U1 归属清单见 [deploy/data-ownership.md](deploy/data-ownership.md)。
|
||||
- [svc 统一部署与一次性登录 Key](modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md):
|
||||
二进制/源码统一部署、环境准备、无初始密码开户与账户恢复;规格见 `docs/svc-deploy/`,
|
||||
账户与恢复续审见 [续审记录](docs/svc-deploy/2026-10-12-account-durability-plan-review.md)。
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
# 容器化存储:数据归属清单(U1)
|
||||
|
||||
权威设计:`docs/superpowers/plans/2026-10-22-container-storage-plan.md`(U1、KTD1—KTD4)。
|
||||
实例身份与期望态唯一权威:`runtime/<name>/manifest.json`(`scripts/svc_manifest.py`);
|
||||
本清单只登记**数据所有者、类型、引用形式与唯一写者**,不含秘密值与业务内容。
|
||||
|
||||
生产体量/源版本盘点需另行授权访问后补全;未核实项不得据此声明迁移完成。
|
||||
|
||||
## 归属总表
|
||||
|
||||
| 所有者 | 类型 | 引用形式(登记键) | 唯一写者 | 迁移范围 |
|
||||
|---|---|---|---|---|
|
||||
| 基座逻辑库 | PostgreSQL 逻辑库(账号/项目/消息/文档/设置等基座表族) | `base_dbref` 或 `base_dsn_ref`(绝对路径秘密文件)+ `base_role_ref` + `base_port`;身份锚 `identity.base_db_ref` | 本实例应用进程(经基座 DSN);引导/迁移角色与应用角色分离(U2) | 必需 |
|
||||
| RDM 领域库 | PostgreSQL 逻辑库(RDM 域表/项目文档树等) | `rdm_dbref` 或 `rdm_dsn_ref` + `rdm_role_ref` + `rdm_port`;锚 `identity.rdm_db_ref` | 本实例应用内 RDM 域(独立 DSN);非基座连接串 | 必需 |
|
||||
| 知识库 | PostgreSQL/pgvector 逻辑库(语料、索引投影) | `kb_dbref` 或 `kb_dsn_ref` + `kb_role_ref` + `kb_port`;锚 `identity.kb_db_ref` | 本实例知识库接入(应用内);索引可重建但语料库为权威 | 必需 |
|
||||
| 持久文件根 | 目录树(工件、RDM uploads、KB 原件及必要解析产物) | `files_root`(**绝对路径**,不相对 checkout/worktree 解析);锚 `identity.files_root` | 本实例应用进程;禁止他实例共用同一根 | 必需 |
|
||||
| 环境身份 | 枚举 | `env_class` ∈ `prod`\|`test`\|`verify`;锚 `identity.env_class` | 运维显式登记;克隆时换新(U5) | 必需(container) |
|
||||
| 实例身份 | 稳定 ID + 数据 marker | `instance_id`;`<data_root>/.agentx/data-marker.json` | `svc` manifest 治理;启动只校验不重建 | 必需 |
|
||||
| Compose 项目 | 容器项目名 | `compose_project`;同 runtime 内唯一 | 本环境容器驱动(U2);冲突在修改前拒绝 | container 必需 |
|
||||
|
||||
`storage_mode=legacy`(缺省):沿用既有 SQLite / 宿主 PG(`db`/`dbref`/`dsn`)路径,不要求三库与 `files_root`。
|
||||
`storage_mode=container`:上表 container 行全部必需;缺任一绑定、错库、错文件根、foreign marker、同名 Compose 或端口冲突 → **拒绝**,不静默建空库、不回退默认。
|
||||
|
||||
凭据只允许以受保护文件路径引用(`*_dsn_ref` / `*_role_ref`),明文不得入 manifest、日志或本清单。
|
||||
|
||||
## 配置形态
|
||||
|
||||
见同目录 [`env-bindings.example.env`](env-bindings.example.env)。写入实例:
|
||||
|
||||
```bash
|
||||
# 先写齐绑定键,最后打开 container 合同(缺一即拒)
|
||||
scripts/svc.sh <name> … # 经 svc_set_key / manifest set;Compose·端口冲突在修改前失败
|
||||
```
|
||||
|
||||
另一 checkout/worktree 只要指向同一绝对 `files_root` 与库引用,仍定位同一资源(路径不相对仓库根解析)。
|
||||
|
||||
## 与旧恢复清单关系
|
||||
|
||||
细粒度基座表/插件枚举仍见 [`docs/recovery/u1-data-ownership.md`](../docs/recovery/u1-data-ownership.md)。
|
||||
本文件是容器化三库+文件根的**合同级**归属;实施迁移(U3)时两者对照,必需所有者未覆盖则阻塞切换。
|
||||
@@ -1,31 +0,0 @@
|
||||
# 容器化存储实例绑定示例(U1)——写入 runtime/<name> 经 manifest 权威登记。
|
||||
# 秘密只引用受保护文件路径,不在此填写明文密码/DSN 值。
|
||||
# 完整合同见 deploy/data-ownership.md;schema 键由 scripts/svc_manifest.py 校验。
|
||||
|
||||
# legacy(缺省)| container(三库+文件根+环境身份全部必需)
|
||||
storage_mode=container
|
||||
|
||||
# 环境身份:prod | test | verify(三选一)
|
||||
env_class=prod
|
||||
|
||||
# 持久文件根:必须是绝对路径(不相对代码 checkout/worktree 解析)
|
||||
files_root=/var/lib/agent-system/state/prod/files
|
||||
|
||||
# Compose 项目名:同机各环境唯一;冲突在修改前拒绝
|
||||
compose_project=agentx-prod
|
||||
|
||||
# 基座逻辑库:dbref=<pg实例>/<库名> 或 dsn_ref=<绝对路径秘密文件>(二选一)
|
||||
# base_dbref=pg-business/agent_base
|
||||
base_dsn_ref=/etc/agent-system/prod/base.dsn
|
||||
base_role_ref=/etc/agent-system/prod/base.role
|
||||
base_port=15432
|
||||
|
||||
# RDM 领域库
|
||||
rdm_dsn_ref=/etc/agent-system/prod/rdm.dsn
|
||||
rdm_role_ref=/etc/agent-system/prod/rdm.role
|
||||
rdm_port=15432
|
||||
|
||||
# 知识库(pgvector 容器独立端口)
|
||||
kb_dsn_ref=/etc/agent-system/prod/kb.dsn
|
||||
kb_role_ref=/etc/agent-system/prod/kb.role
|
||||
kb_port=15433
|
||||
Vendored
+1
-1
Submodule deps/agent-command updated: fe7987dc03...f0fc65de2b
Vendored
+1
-1
Submodule deps/agent-document-renderer updated: 3f259990b5...40e5c671d1
Vendored
+1
-1
Submodule deps/agent-intent updated: 765a7e7177...8b54508964
Vendored
+1
-1
Submodule deps/agent-native updated: f2fb9ffa6d...2df0d10d96
Vendored
+1
-1
Submodule deps/agent-squad updated: b6d87638cc...b3da5284e8
Vendored
+1
-1
Submodule deps/development-guidelines updated: a15db50855...3ed982138c
Vendored
+1
-1
Submodule deps/development-manager updated: 2c2e372558...24e8a5c8ba
Vendored
+1
-1
Submodule deps/error-code-system updated: a3ee67bba2...00c03035b5
Vendored
+1
-1
Submodule deps/logging-system updated: 49b5d8be60...7005493077
@@ -1,266 +0,0 @@
|
||||
---
|
||||
artifact_contract: ce-unified-plan/v1
|
||||
product_contract_source: ce-plan-bootstrap
|
||||
execution: code
|
||||
date: 2026-10-22
|
||||
title: 单一应用二进制与容器化数据库、知识库存储改造方案
|
||||
---
|
||||
|
||||
> **落库状态(2026-10-22)**:方案已获审阅并授权实施。任务以 L1 initiative
|
||||
> `feature/container-storage-plan` 重新登记(R0=955a32de2bb49171348bad5f566cce935df19212)。
|
||||
> 原始规划工作区(基线 9a80897)因子模块物化失败已保留现场并对账后回收:
|
||||
> 全部子模块 HEAD 均在远端分支上、无独有提交、无未跟踪文件,任务卡副本存于
|
||||
> `/tmp/container-storage-plan-20261022/card-auto-generated.md`。本文正文为审阅时快照,
|
||||
> 其中基线 9a80897 与工作区失败描述属历史记录;实施以本卡与各子任务卡为准。
|
||||
|
||||
# 单一应用二进制与容器化数据库、知识库存储改造方案
|
||||
|
||||
本文是待用户审阅的方案与实施计划,不代表业务实施、部署或迁移授权。应用二进制及数据库容器的总体方向来自本次会话;具体分工、步骤与验收为本方案建议。
|
||||
|
||||
计划归属 Agent-System-Dev;本文因新工作区子模块物化失败暂存仓外,尚未提交或合入。路径以各标明仓库的根目录为基准。后续正规落点建议为 Dev `docs/superpowers/plans/2026-10-22-container-storage-plan.md`。
|
||||
|
||||
## Goal Capsule
|
||||
|
||||
- **目标**:运维人员能重建生产、测试、验证环境,把一个完整环境迁到新机器或新目录,并通过恢复验证确认账号、项目、文档、文件和知识库引用仍可用。
|
||||
- **手段**:一个应用服务二进制,两个 PostgreSQL 容器,三套逻辑数据库,外置持久文件;同一制品使用独立环境配置(KTD1—KTD4)。
|
||||
- **当前授权**:制定方案和计划。产品代码、容器部署、生产数据读取/复制、迁移切换均未在本轮执行;后续按用户明确授权范围实施。
|
||||
- **执行责任**:各仓遵循自身 AGENTS;Dev 范围的可独立实施单元按 AGENT-EXECUTION.md 分配全新外部 Agent 会话,host 管理范围、验证、独立 Review 和交付。任务进度放任务卡,不在本文伪装已完成。
|
||||
- **停止条件**:需要新增数据库类型、改变业务权限、无法完整迁移现有数据、需要丢弃新写入或扩大业务范围时停止相关动作,提交最小决策;不为容器化引入高可用平台、通用运维平台或应用微服务化。
|
||||
|
||||
## Product Contract
|
||||
|
||||
### Summary
|
||||
|
||||
保留前后端、Agent、RDM、知识库接入在一个应用进程内;数据库依赖使用 Compose。业务 PostgreSQL 容器分别承载基座库和 RDM 领域库,知识库 PostgreSQL/pgvector 容器承载语料与检索索引。生产、测试、验证环境分别建立,不通过临时修改生产连接串来“切环境”。
|
||||
|
||||
### Problem Frame
|
||||
|
||||
现有宿主数据库运维、应用快照、RDM 领域备份和知识库恢复各有入口,但尚未形成可核验的完整环境恢复闭环。容器化只改变数据库运行方式,必须同时处理身份绑定、数据库与文件的一致恢复点,以及复制环境后的副作用隔离。
|
||||
|
||||
### Requirements
|
||||
|
||||
| ID | 要求与可核验结果 |
|
||||
|---|---|
|
||||
| R1 | 完整应用服务只需一个可执行文件,包含前端资源、后端、Agent、RDM、知识库接入;运行机无需源码、Go 或 Node 构建环境。 |
|
||||
| R2 | 业务库和知识库存储容器化,明确持久卷、镜像版本、扩展版本及生命周期;容器重建后业务数据仍在。 |
|
||||
| R3 | 每环境独立绑定基座库、RDM 库、知识库、文件根、实例身份、端口和凭据;错误绑定拒绝启动。 |
|
||||
| R4 | 从备份恢复到新目录/主机后,账号、权限、业务记录、原文和历史引用可核对;成功备份必须是一个可解释的一致恢复点。 |
|
||||
| R5 | 从当前实际 SQLite/宿主 PG 迁移时覆盖所有必需持久数据所有者,保留业务标识、引用与原始内容,缺失或未知数据阻止切换。 |
|
||||
| R6 | 测试/验证克隆获得新实例身份及独立凭据,启动前隔离生产密钥、会话、自动任务和外部副作用;生产实例保持固定绑定。 |
|
||||
| R7 | 普通启停、重建、升级不隐式删卷、不执行数据库大版本升级、不恢复旧备份、不在数据库失败时回退到空 SQLite。 |
|
||||
| R8 | 形成可重复的组合验证和生产切换/回滚操作记录;实现完成、测试通过、生产迁移完成、业务验收通过分别记录。 |
|
||||
|
||||
### 边界与假设
|
||||
|
||||
- “单一二进制”按用户原话“最终的服务”解释为一个常驻应用服务。允许部署配置、脚本和离线迁移工具存在;不新增常驻 admin、KB HTTP 或前端服务。本期不为统一离线工具而重构全部 CLI。
|
||||
- 一期面向单机部署,每套环境独立;可在同机不同端口运行,也可迁到不同主机。宿主应用通过回环地址连接本机容器。
|
||||
- 知识库容器化指语料数据库及向量索引容器化;检索编排、权限与引用业务仍在应用二进制内。原始文档使用外置持久存储。
|
||||
- 允许维护窗口,先实现可验证的停写备份与迁移,不承诺零停机、实时复制、PITR 或自动故障切换。
|
||||
- 保留既有 SQLite/原生 PG 运行路径用于兼容与受控回退;本方案目标环境使用容器 PG,不顺带删除旧实现。
|
||||
- 公有知识库共享、跨环境共用只读卷、Kubernetes、托管云数据库、全量对象存储替换均不进入首期。
|
||||
- 生产停写窗口、源实例、机器容量、实际源版本与数据规模在 U1 核实;它们不妨碍方案审阅,但未核实前不能安排生产切换。规划不承诺未经测量的 RTO/RPO。
|
||||
|
||||
## Planning Contract
|
||||
|
||||
### 当前依据与可复用基础
|
||||
|
||||
本轮主仓基线 Dev `9a80897cb7e9108091d302e849f59662f21ce260`。以下是源码/文档核查,未探测实际生产实例,也未运行产品测试。
|
||||
|
||||
| 范围 | 已核对的依据 | 本次改造含义 |
|
||||
|---|---|---|
|
||||
| 单二进制 | Dev `Makefile`;Backend `web/embed.go`;RDM `main.go`、`scripts/build-product.sh` | 已有静态组合和前端嵌入,复用完整产品构建;不能用只有基座的二进制代替 RDM 产品。 |
|
||||
| 宿主 PG | Dev `scripts/svc.sh` 的 pg_bindir/db_create/pg_do_start/pg_do_stop/pg_run/pg_dump_run/db_backup/db_restore/db_clone | 容器支持需要完整后端分派,不能只替换启动命令;备份恢复继续使用已有安全边界。 |
|
||||
| 实例身份 | Dev `scripts/svc_manifest.py` 的 PROTECTED/db_anchor_of/reanchor;`tests/test_svc_identity.py` | manifest 已有权威身份;扩展三库和环境绑定,不另造第二份登记权威。 |
|
||||
| 基座迁移 | Backend `cmd/storage-migrate/main.go` | 已有 SQLite→PG 工具,但只迁 Backend;artifacts 参数只盘点不复制,不能据此声称迁了整个产品。 |
|
||||
| RDM 迁移 | RDM `main.go` 的 migrate-to-pg/migrate-check/backup-domain/restore-domain | 独立 RDM_DSN;RDM 领域库和 uploads 必须明确纳入。 |
|
||||
| KB Compose | KB-Regulations `deploy/compose.yaml`、`deploy/README.md` | 已有本地 pgvector/pgvector:pg16、回环端口、卷与扩展初始化模板;固定项目名 kbreg 不适合作为所有环境的全局名称,已有标签不等于生产版本批准。 |
|
||||
| 文件迁移 | Backend `internal/blobstore/blobstore.go`;KB-Regulations `migrations/0001_schema.sql`、`docs/operations/corpus-import.md` | 工件目录相关引用、source_assets.storage_uri 中 file 路径会影响异地恢复;不能只做 dump/restore。 |
|
||||
| 组合恢复 | Dev `scripts/svc.sh` 的 agent_backup_snapshot;`tests/integration/regulations-kb/README.md` | 应用快照不含全套数据库和原始 manifest;KB 测试可复用,但不证明全套外部文件已恢复。 |
|
||||
|
||||
RDM 研究对象为兄弟检出 `b14c84c6dcc945bd4d0a9ad446cf1ec980396491`,KB 为 `393555bec8d964f294f932616aa69ab833a06de1`;它们与 Dev `tests/integration/regulations-kb/components.pin` 的固定组合不同。本方案引用它们用于定位能力,正式实施前必须选定并验证精确组件组合,不能把研究检出当作已采纳版本。
|
||||
|
||||
已有恢复设计 `modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md` 是设计约束来源,本文沿用其停写一致恢复点、恢复新目标、旧写者隔离思路,不把历史执行状态当现状。
|
||||
|
||||
### Key Technical Decisions
|
||||
|
||||
**KTD1.应用保持单一服务二进制。** 复用现有产品组合构建,配置和数据外置;容器承载持久数据库依赖,知识库插件不拆进程。落实 R1/R2。(session-settled: user-directed — chosen over containerizing the whole application: user requires the final service to remain a single binary)
|
||||
|
||||
**KTD2.两个 PG 容器、三个逻辑库。** 业务容器中分别设基座库与 RDM 库,知识库另用带 vector/pg_trgm 的 PG 容器。数据库使用独立应用角色;引导/迁移角色与应用权限分开,先核对现有启动迁移要求,再安排显式迁移阶段,不能直接收紧权限导致启动失败。落实 R2/R3/R5。
|
||||
|
||||
**KTD3.复用 svc 管理入口。** 新增窄的容器驱动和版本化环境配置,保留现有命令语义。manifest 是身份与期望态权威;Compose 文件是版本化模板,环境参数由同一配置解析生成。不得并存两份可独立修改的 DSN/卷权威。只管理明确归属本环境的容器,不启停外部数据库。落实 R3/R7。
|
||||
|
||||
**KTD4.稳定绝对数据根与环境独立资源。** 建议形态为 `state/<env>/` 与其外部 `backups/<env>/`,实际根由配置给出绝对路径;不相对代码 checkout/worktree 解析。Compose project、两套卷、各库、角色与端口都显式区分环境。凭据放受保护文件/secret 引用,日志和清单脱敏。文件树示意仅表达归属,不规定最终名称。
|
||||
|
||||
**KTD5.先备份完整恢复点,再追求不停机。** 关闭新写入口,排空或隔离在途任务,停止应用、定时任务、KB 导入/索引写者及迁移器;确认其他客户端不再写入后,对三库分别逻辑导出并复制受控文件。三次 dump 各自的快照不自动构成跨库一致性,靠整个窗口无写者保持一致。完成校验后才发布恢复包完成标记。落实 R4/R8。
|
||||
|
||||
**KTD6.恢复和克隆是不同操作。** 原实例恢复保留业务与实例身份,但须先停旧写者,绝不双开同一生产身份。测试克隆保留业务 ID 及引用关系,创建新的部署身份和守护绑定、数据库凭据;使生产登录会话/长期 API key 在副本失效,外部 provider 密钥不带入。后台任务与外发能力默认关闭,授权后逐项启用;不通过批量改业务 ID 实现隔离。落实 R3/R6。
|
||||
|
||||
**KTD7.迁移与升级分离。** 源 PG 采用同大版本兼容目标与匹配工具链,SQLite 迁移用已有逻辑迁移器;本次不顺带 PG 大版本升级或强制统一业务/KB PG 大版本。实施时固定经过验证的镜像 digest、平台与扩展版本。镜像官方文档说明不同主版本的 PGDATA/卷位置存在差异,配置按选定镜像逐项核对,不能把 KB pg16 的挂载路径盲用到另一主版本。初始化脚本仅负责新卷引导,既有库变更走显式迁移。落实 R2/R5/R7。
|
||||
|
||||
### High-Level Technical Design
|
||||
|
||||
以下是关系和流程草图,新增配置/命令的最终命名由实现单元在既有接口中确定。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Client[浏览器] --> App[一个完整应用二进制]
|
||||
Profile[环境配置与实例 manifest] --> App
|
||||
Profile --> Compose[Compose 数据依赖]
|
||||
App --> Base[业务容器 / 基座库]
|
||||
App --> RDM[业务容器 / RDM 领域库]
|
||||
App --> KB[知识库容器 / 语料库与索引]
|
||||
App --> Files[持久文件根 / 工件 uploads 原件]
|
||||
Compose --> Base
|
||||
Compose --> RDM
|
||||
Compose --> KB
|
||||
```
|
||||
|
||||
```text
|
||||
state/<env>/
|
||||
instance/ manifest 与受保护配置、应用版本引用
|
||||
files/ 工件、RDM uploads、KB 原件及必要解析产物
|
||||
Docker 持久卷:<project>-business、<project>-knowledge(各环境独立)
|
||||
backups/<env>/<backup-id>/ 三库导出、文件、配置恢复材料、版本与校验清单
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[识别目标与锁定操作] --> B[预检配置/身份/镜像/资源]
|
||||
B --> C[启动两库容器]
|
||||
C --> D[认证/扩展/schema/三库身份检查]
|
||||
D --> E[按需执行显式迁移并核验]
|
||||
E --> F[启动应用]
|
||||
F --> G[业务就绪验证]
|
||||
D -->|失败| H[拒绝启动并保留现场]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Running
|
||||
Running --> Quiescing: 请求一致备份
|
||||
Quiescing --> Frozen: 写者已停且在途状态已记录
|
||||
Frozen --> Capturing: 三库与文件导出
|
||||
Capturing --> Verified: 完整性和清单校验
|
||||
Capturing --> Incomplete: 失败或中断
|
||||
Verified --> Running: 发布完成标记后恢复原实例
|
||||
Incomplete --> Running: 确认现场可安全恢复后解除停写
|
||||
```
|
||||
|
||||
恢复流程:读取完成清单 → 校验兼容版本与文件 hash → 选择空的新目标 → 建库/恢复角色权限及三库 → 还原文件与受控路径映射 → 核对实例模式(原实例/克隆)→ 禁止外部副作用下启动 → 登录、权限、文档、KB 引用验证 → 单独决定是否开放写入。任一步失败保持目标隔离,不覆盖旧系统。
|
||||
|
||||
### 完整恢复包合同
|
||||
|
||||
- 基座、RDM、KB 三库的逻辑导出及 schema/扩展版本;必需角色/权限用受控恢复定义记录,不能盲目导入生产全局超级用户。
|
||||
- 工件、uploads、KB 原件及必要解析产物;包含全部必需插件声明的数据。对对象存储记录不可变版本/校验值及可恢复获取方式;无法获取的必需对象阻止完整恢复声明。
|
||||
- 原始实例 manifest、部署配置、应用制品及插件/前端组合的 SHA、容器镜像 digest、平台信息;配置清单不含明文秘密,恢复秘密通过独立受保护介质或重新注入。仅含引用时必须验证引用在目标可解析,不能称恢复包独立可恢复。
|
||||
- 文件清单与 hash、数据所有者清单、停写时间和操作状态、恢复工具版本、校验结果及完成标记。备份与 runtime 卷分离,至少验证一份异机/独立存储副本可读取。
|
||||
- 原件路径变更仅调整定位信息,不改变内容 hash、语料修订、节点 ID、历史 excerpt 和审计封印。受保护审计内容若包含原路径,必须通过解析映射兼容,禁止重写封印记录。
|
||||
- 向量索引可重建,但 embedding 模型/维度和语料版本要记录;恢复测试不依赖付费重新嵌入,优先复用保存的向量重建索引。
|
||||
|
||||
## Implementation Units
|
||||
|
||||
### U1.环境与数据归属合同
|
||||
|
||||
- **需求**:R1/R3/R5/R6,KTD1—KTD4。
|
||||
- **目标**:一份可解析配置确定完整实例资源,明确现有数据来源和迁移边界。
|
||||
- **主要文件**:Dev `scripts/svc_manifest.py`、`scripts/svc.sh`、`docs/svc.md`、`PROJECT.md`;新增环境 schema/示例置于 Dev `deploy/`(建议路径)。
|
||||
- **做法**:扩展现有 manifest 绑定基座/RDM/KB,版本化旧配置升级;保护环境命令不受调用者环境变量污染。列出各数据所有者、类型、源版本、目录、体量、表/对象清单、引用形式和唯一写者。不输出秘密或业务内容。生产盘点需先获得对应访问授权。
|
||||
- **测试文件**:扩展 `tests/test_svc_identity.py`、`tests/test_svc_lifecycle_isolation.py`、`tests/test_svc_precheck.py`。
|
||||
- **场景**:旧配置升级保留身份;错库/错根/foreign marker 拒绝;同名项目或冲突端口在修改前失败;在另一 checkout 调用仍定位同一资源;三库中任一必需绑定缺失不能静默新建空库。
|
||||
- **完成条件**:配置、归属清单和迁移范围有唯一解释;明确需迁移的每个数据所有者。依赖:无。
|
||||
|
||||
### U2.容器数据库与 svc 接线
|
||||
|
||||
- **需求**:R2/R3/R7,KTD2—KTD4/KTD7。依赖 U1。
|
||||
- **主要文件**:Dev `scripts/svc.sh`(后端分派)、新增 `scripts/svc_containers.sh` 和 `deploy/compose.storage.yaml`(建议路径);KB-Regulations `deploy/compose.yaml`、`deploy/initdb/01-extensions.sql`、`deploy/README.md` 作为复用来源,避免复制成两套互相漂移的模板。
|
||||
- **做法**:start/stop/status/SQL/dump/restore 覆盖同一容器驱动;记录明确所有权;卷重建、普通停止和数据销毁分别处理。应用仅连 loopback 发布端口。引导创建基座/RDM 独立库与角色,KB 创建扩展;后续变更使用显式迁移。启动顺序验证真实认证、扩展和身份,不能只依赖 pg_isready。
|
||||
- **测试文件**:新增 Dev `tests/test_svc_containers.py`;扩展 `tests/test_svc_precheck.py`、`tests/test_svc_realcombo.py`。
|
||||
- **场景**:prod/test/verify 同时运行互不冲突;停止/重建一套不影响其他套且不丢数据;缺 Docker/镜像/磁盘/凭据时在破坏性动作前失败;密码变更不误以为改 env 就改变已初始化库;容器意外停止时应用不得写入 SQLite。
|
||||
- **完成条件**:用固定制品及临时环境完成两容器三库启动、持久化、重建与失败路径验证。运行冒烟优先,不用大量模拟测试替代真实容器验证。
|
||||
|
||||
### U3.迁移与文件引用兼容
|
||||
|
||||
- **需求**:R4/R5/R7,KTD5—KTD7。依赖 U1/U2。
|
||||
- **主要文件**:Backend `cmd/storage-migrate/`、`internal/blobstore/blobstore.go`;RDM `main.go` 与其现有领域迁移实现;KB-Regulations 的 source_assets 读取/原件定位实现及 `docs/operations/corpus-import.md`。函数名在实施启动时定点核对,不另建并行迁移框架。
|
||||
- **做法**:基座和 RDM 分别复用现有迁移器;覆盖实际 schema 的新增表、外键、序列/标识和身份记录;未知表不能悄悄遗漏。已有 PG 走兼容逻辑恢复。文件迁移校验内容和所有权,新增受控资产根映射或兼容相对引用,维持旧引用可读取。禁止迁移时跨出允许根或追随外部软链复制未知文件。
|
||||
- **测试文件**:Backend `cmd/storage-migrate/main_test.go`、`integration_test.go`、`seeded_singletons_test.go` 与 `internal/blobstore/` 对应测试;RDM 现有领域迁移测试(实施前定位);KB-Regulations `tests/integration/recovery_pg_test.go`。
|
||||
- **场景**:SQLite 基座+RDM 迁入不同逻辑库;原 PG 同版本恢复;空库与真实结构 fixture;失败/重跑/非空目标拒绝;未知表拒绝;不同根目录恢复后读取旧工件与 file URI 原件;越界路径拒绝;历史 citation/审计 hash 不变。
|
||||
- **完成条件**:逐所有者对账,不仅比较行数,还核对 ID、关键引用、内容 hash、权限和新写入;不能完成的必需所有者阻止继续生产准备。
|
||||
|
||||
### U4.组合备份与恢复新目标
|
||||
|
||||
- **需求**:R4/R5/R8,KTD5/KTD7。依赖 U2/U3。
|
||||
- **主要文件**:Dev `scripts/svc.sh` 的 backup/restore 入口;新增组合恢复辅助实现(建议 `scripts/svc_bundle.py`);`docs/svc.md`、恢复操作说明;各组件只提供必要导出/校验接口。
|
||||
- **做法**:按恢复包合同采集全部数据,锁定同环境操作,停写并确认所有写者;失败包无完成标记。完整备份默认只恢复到空的新目标;应用制品和 schema 兼容预检先于开放写入。秘密处理有明确备份/重新注入路径及权限校验。
|
||||
- **测试文件**:扩展 `tests/test_svc_backup.py`;新增 `tests/test_svc_bundle.py`;扩展 `tests/ops/run_backup_drill.sh` 的不同目标根场景。
|
||||
- **场景**:三库与文件可恢复;备份中断、空间不足、错包、hash 损坏、缺原件、缺必要 secret、镜像不兼容拒绝;目标非空拒绝;不得覆盖生产;重新启动源实例不把未完成包发布为成功;保留原生产身份恢复时,目标接管前必须隔离旧写者;独立克隆从已完成的一致备份恢复,源实例可在备份完成后继续运行,但目标首次启动前必须完成新身份、独立凭据及副作用隔离。
|
||||
- **完成条件**:干净环境恢复完整产品并生成可复核清单;实测备份大小、停写时长和恢复用时。
|
||||
|
||||
### U5.测试/验证克隆与环境选择
|
||||
|
||||
- **需求**:R3/R6/R8,KTD4/KTD6。依赖 U4。
|
||||
- **主要文件**:Dev `scripts/svc.sh` 的 clone/启动接线、`scripts/svc_manifest.py`;Backend/RDM 现有身份、会话和任务控制入口仅作必要接线;测试数据 fixture 与说明。
|
||||
- **做法**:以明确目标环境创建新实例。业务 ID 不改,部署身份、守护与凭据换新;登录会话/API key 不在副本继续有效,账号密码处理按克隆用途明确。需要复制生产敏感数据时先落实访问范围和脱敏策略;常规自动验收优先合成数据。外部密钥、自动调度与恢复任务副作用默认禁用,并在首次启动前生效。
|
||||
- **测试文件**:扩展 `tests/test_svc_identity.py`、`tests/test_svc_lifecycle_isolation.py`;新增 `tests/test_svc_environment_clone.py`。
|
||||
- **场景**:克隆后生产仍正常;新身份不触发原守护冲突;旧 token 无法访问副本;待执行任务不自动发信/调用真实 provider/写生产目录;选择 test 不修改 prod 配置;操作摘要明确目标环境。
|
||||
- **完成条件**:三套环境同机并存,隔离由实际读写与副作用拒绝验证,不靠名称或配置声明。
|
||||
|
||||
### U6.单二进制与组合验收、运维交付
|
||||
|
||||
- **需求**:R1—R8,KTD1—KTD7。依赖 U1—U5。
|
||||
- **主要文件**:Dev `Makefile`、`tests/test_svc_realcombo.py`、`tests/integration/regulations-kb/run.sh`、`components.pin`、`PROJECT.md`、`docs/svc.md`;RDM `scripts/build-product.sh`;各仓必要 runbook/组合指针。
|
||||
- **做法**:固定完整产品与镜像组合,在没有源码及 Go/Node 的机器/隔离环境用同一应用制品启动。把恢复、克隆、数据库容器重启加入组合验证。必要管理操作优先复用同一产品已有入口,离线工具不成为常驻依赖。受影响仓独立验证后再推进 Dev 组合,不重做发布体系。
|
||||
- **场景**:UI/API、正常账号登录、角色边界、项目/会话、RDM 上传/文档原件读取、知识库检索、历史引用解析;两库容器依次重启;新目录恢复;test/verify 重建不影响 prod;客户端制品无 secret。
|
||||
- **完成条件**:满足 Definition of Done;输出生产切换操作单与回滚分界,实测停写时间供用户安排窗口。生产执行属于下面 O1,不由代码测试替代。
|
||||
|
||||
## Verification Contract
|
||||
|
||||
- 本轮是规划:产品测试、真实容器试验、迁移演练、生产盘点/部署均为 **NOT_RUN**;源码核查属于 SELF,不能提升为 GATE 或 REPLAY。
|
||||
- 实施每单元先运行其最小直接检查,容器与文件恢复必须有真实临时环境验证。禁止把缺 DSN 导致的跳过计作通过;真实测试仅指向显式临时环境。
|
||||
- Backend 真 PG 迁移测试使用 `AGENT_PG_TEST_DSN`;法规组合门禁使用 `KBREG_TEST_DATABASE_URL`、精确 RDM/KB pin 及既有 `make test-regulations-kb`。
|
||||
- Dev 治理脚本确有修改时运行 `make test-governance`;最终组合按项目要求执行 `make test`,并以当前 CI 要求核对 `test-pg`、`svc-realcombo` 等检查,不能凭旧清单声称全部通过。
|
||||
- 推进 Dev gitlink 前完成所需组合验证;命令原始输出经 `make evidence` 写任务卡。正式 Review 前重放最小集合、同步权威目标;合入信任来自精确 HEAD 的必需 CI 或独立 REPLAY。
|
||||
- 全量检查只在最终候选或有必要时运行;发现无关非阻塞问题不扩大本次任务。
|
||||
|
||||
## O1.生产迁移与回滚计划(另行授权执行)
|
||||
|
||||
1. **确认源与窗口**:记录实际源实例、三库/文件清单、schema、版本、容量、现有身份、备份位置及授权范围;确认恢复演练达标和可接受停写时间。
|
||||
2. **提前准备目标**:拉取固定镜像、准备空库/角色/持久目录、验证磁盘与网络;用演练副本验证完整应用制品。此阶段不改生产连接。
|
||||
3. **冻结生产写入**:暂停入口及所有后台/外部写者,排空或隔离在途工作,确认旧实例不会被 supervisor 自动拉起;记录已确认写入的对账点。
|
||||
4. **最终备份与迁移**:按 U3/U4 生成最终完整包并恢复目标;核对所有必需数据与文件,运行只读业务验证;保持外部副作用关闭。
|
||||
5. **受控切换**:以迁移操作更新目标绑定、身份和文件路径;先验证登录/权限/文档/知识库,再开放写入及经确认的后台任务。避免两个生产写者同时存在。
|
||||
6. **观察与保留**:保留旧数据及最终备份,只读保护并记录新系统写入开始时间;到达业务验收及约定保留期后才单独安排旧资源回收。
|
||||
|
||||
回滚分界必须写入操作单:
|
||||
|
||||
- **新目标尚无业务写入**:停止新实例,确认无新数据后恢复旧配置/旧写者;不可让双方同时写。
|
||||
- **新目标已有业务写入**:停止写入并保存新数据,禁止简单切回旧库;评估向前修复或经过验证的数据对账/回迁。未经确认不丢弃新写入。
|
||||
- **schema 已改变**:旧应用制品不一定兼容。代码回滚、数据库恢复和业务数据回迁分别验证,不能用“换回旧二进制”代替恢复方案。
|
||||
|
||||
## Definition of Done
|
||||
|
||||
代码交付完成须同时满足:
|
||||
|
||||
- 一个完整应用服务二进制在目标运行环境启动,业务/RDM/KB 数据存储符合两容器三逻辑库边界。
|
||||
- prod/test/verify 可独立启动、停止、重建与选择,配置/数据/身份/权限隔离已验证。
|
||||
- 完整备份可以恢复到不同根目录;原文可读、业务引用和历史法规引用不变;缺失数据时明确失败。
|
||||
- 真实迁移 fixture 对账通过,重启与容器重建不丢数据,失败不会回退空系统或启动外部副作用。
|
||||
- 精确组件组合、测试证据、独立 Review、所需 CI 及规范文档齐全;没有遗留试验实现进入最终 diff。
|
||||
- O1 的执行另记:代码完成不等于生产迁移完成,恢复演练也不等于业务验收。
|
||||
|
||||
## 顺序与建议里程碑
|
||||
|
||||
| 里程碑 | 工作 | 出口 |
|
||||
|---|---|---|
|
||||
| M1 最小可运行 | U1 → U2 | 同一应用连接两容器三库,test/verify 并存,持久化与错误绑定验证通过 |
|
||||
| M2 可迁移可恢复 | U3 → U4 | 完整数据恢复到新目录,原文和历史引用可核对 |
|
||||
| M3 可安全切环境 | U5 → U6 | 克隆隔离、完整产品组合验证、操作说明齐备 |
|
||||
| M4 生产落地 | O1 | 获得窗口授权后迁移、业务验收,旧数据按约定保留 |
|
||||
|
||||
U1 完成后再按实际 schema 覆盖、文件规模及演练用时细化工期;当前不把未测量的数据量换算为承诺日期。每个里程碑只处理阻塞目标的问题。
|
||||
|
||||
## 外部依据与待核验项
|
||||
|
||||
本轮尝试读取 Docker Compose、PostgreSQL 备份与 pgvector 官方文档;网络读取不完整。成功获取 Docker Official Image 的 PostgreSQL 说明(docker-library/docs 的 postgres/README.md),其中确认 PGDATA/卷位置随主版本变化、初始化参数仅对新数据目录生效、密码支持文件注入。这些仅作为镜像适配约束,未确定最终镜像 digest,也未验证某个部署平台。
|
||||
|
||||
实施 U2 前需针对所选精确版本再次核对 Compose/镜像/pgvector 官方资料,并做真实冒烟;失败则阻止该镜像采用,不阻止本方案的源码设计工作。
|
||||
@@ -1,64 +0,0 @@
|
||||
# 独立 Review:容器化存储 U1(环境与数据归属合同)
|
||||
|
||||
- 日期:2026-10-22
|
||||
- 审阅对象:`feature/container-storage-plan-u1` 分支 HEAD
|
||||
- base:`955a32de2bb49171348bad5f566cce935df19212`
|
||||
- head:`e1f08650a12656611f27438f23fddbfab6a96299`(分支尖端;实施内容经 agent-exec transport 提交 `2d75d101a04ce1d1c9fa1d66d9567894222399d5`,另有路由调整 transport 提交 `0c8bea37` 与任务卡记录提交)
|
||||
- 送审 diff:`/tmp/container-storage-u1-review.diff`(已核对与 `git diff 955a32de..HEAD` 的代码部分逐字一致;仅 `.task/**` 任务卡不在 diff 内,属 host 直写豁免区)
|
||||
- 方案依据:`docs/superpowers/plans/2026-10-22-container-storage-plan.md` U1 小节、KTD1—KTD4
|
||||
- 审阅方式:全新会话独立审阅(非实施者);实际读代码核对绑定解析、拒绝路径与身份保留逻辑;未实施任何修复。
|
||||
|
||||
## 总体判定:REVIEW PASS
|
||||
|
||||
无阻塞项。U1 验收场景(旧配置升级保留身份、错库/错根/foreign marker 拒绝、同名项目或冲突端口修改前失败、另一 checkout 定位同一资源、缺必需绑定不静默新建空库)均有实现且被测试覆盖;KTD3 单一权威合同成立;无秘密入仓;测试 80/80 通过(1 项真实 PG 专项显式 NOT_RUN)。下列非阻塞项建议在 U2 及最终交付前处理。
|
||||
|
||||
## 阻塞项
|
||||
|
||||
无。
|
||||
|
||||
## 非阻塞项
|
||||
|
||||
| # | 级别 | 位置 | 发现 | 建议 |
|
||||
|---|---|---|---|---|
|
||||
| N1 | 建议 | `scripts/svc.sh` `svc_set_key`/`svc_del_key`(4674/4691 行起) | manifest `set` 因绑定校验或冲突被拒(exit 2/3/7)时,回退路径仍把该键写入派生视图 `svc.env`,整体操作"成功"返回。权威 manifest 未被污染(`resolve_storage_bindings` 只读 manifest),且两种漂移方向在启动时均 fail-closed(`svc.env=container`/`manifest=legacy` → `verify --require-storage` 触发 `完整存储合同要求 storage_mode=container` 拒启;反向漂移由 record_state 重建自愈),但"冲突在修改前失败"的语义被弱化为"权威拒绝+视图静默写入"。当前无任何调用方经 `svc_set_key` 写存储键(调用点仅 autostart/exec/dbref/dsn/conffile/addr/tags),风险处于潜伏状态。 | 存储键(STORAGE_KEYS 集合)在 manifest set 失败时硬失败,不走 legacy 单键回退。 |
|
||||
| N2 | 建议 | `scripts/svc.sh` `svc_manifest_verify`(1302 行) | container 分支门控用 `svc_get storage_mode`(读派生 `svc.env`)而非 manifest identity。manifest=container 而 svc.env 过期(manifest 与 svc.env 两次原子写之间的崩溃窗口,或绕过 svc 手改 manifest)时,该次启动跳过存储校验;下次 record_state 经 bulk-set 重新派生后自愈。窗口窄且可自愈,但与"manifest 为权威"的门控意图不一致。 | 门控改为读 manifest identity 的 `storage_mode`(或在检测到两处不一致时拒绝并提示重建派生视图)。 |
|
||||
| N3 | 建议 | `deploy/data-ownership.md`(持久文件根行) | 声明"禁止他实例共用同一根",但 `check_conflicts` 只扫描 compose 项目名与端口,不检测跨实例 files_root 重叠/嵌套;测试夹具(precheck `test_compose_project_conflict_refused`)中 b 的根位于 a 的根之下,同样未拦截。无 U1 验收场景要求此项,属文档强于实现。 | 收紧措辞为"登记层不拦截,容器挂载层(U2)负责隔离",或在 U2 增加跨实例根重叠预检。 |
|
||||
| N4 | 记录 | `scripts/svc_manifest.py` `check_conflicts` | 冲突扫描范围限定同一 runtime 目录(`RUNTIME="$ROOT/runtime"`,`--runtime` 显式传入)。同机跨 checkout 的多个 runtime 目录之间的 compose 项目名/端口冲突在 U1 登记层不可见,实际碰撞推迟到 U2 容器启动时由端口占用/compose 报错暴露。实现注释与文档均按"同一 runtime"表述,范围自洽;U2 场景"prod/test/verify 同时运行互不冲突"已承接此项。 | U2 落地容器驱动时复核跨 runtime 同机冲突的最终防线。 |
|
||||
| N5 | 建议 | `tests/` | 两个覆盖缺口:(a) 无 container 模式实例完整 start/stop 生命周期测试——`preserve_storage_keys`(record_state → bulk-set 写回时保住存储键)与 svc.sh 传 `--runtime` 的改动未被端到端 exercised,若该逻辑回归,绑定会在一次生命周期写回后被静默清掉;(b) `test_CS_U1_missing_binding_rejected` 只覆盖"全部缺失",未覆盖"三库中任一缺失"(如 base/rdm 齐备仅 kb 缺失)的逐槽拒绝。 | 各补一条测试:container 实例启停后锚点与 kv 绑定不变;逐槽缺失各断言一次。 |
|
||||
| N6 | 记录 | `AGENT-EXECUTION.md`(implementation 角色顺序) | 不在 U1 单元 allowed_paths 内,属越界文件变更;但它是独立登记的路由单元(任务卡行 `container-storage-routing-promote-antigravity`,transport `0c8bea37`,收据 `/tmp/container-storage-routing-receipt2.json`),披露完整,且变更注释自述"临时调整,交付前回退"。 | 按其自述在 U1 合入/交付前回退;不阻塞 U1 评审结论。 |
|
||||
| N7 | 记录 | `scripts/svc_manifest.py` | `slot_port_of` 只校验 `isdigit()`,接受 `0` 与超范围数值,未限 1—65535。 | 顺手项,U2 接真实容器端口发布前收紧即可。 |
|
||||
| N8 | 记录 | `scripts/svc_manifest.py` `cmd_verify` | container 启动路径把 `resolve_storage_bindings`(源自 manifest)的值回传给 `verify` 比对,本质是 identity↔kv 自一致性检查(可拦手改 manifest 的不一致),并非运行时"连到外宾库"检测;后者按方案属 U2(启动顺序真实认证/身份检查)与 U3(库内身份)范围。U1 的"错库拒绝"在绑定锚点层面成立(`base 库绑定漂移` 路径有测试),范围声明准确。 | 无需改动;U2 接线时保留本检查作为快速失败层。 |
|
||||
|
||||
## 核对点(实际读代码/跑测试核对)
|
||||
|
||||
1. **旧配置升级保留身份**:`build()` 对已带 storage 键的 kv 只做形态校验(`require_complete=(mode==container)`),不重建 instance_id;`test_CS_U1_legacy_upgrade_keeps_identity` 断言升级后 `instance_id`/`data_root` 不变、锚点落 identity。✅
|
||||
2. **身份保留/重锚**:`reanchor(write=False)` 预览仅用于冲突扫描、不落 marker;`mutate` 顺序为"校验 → 冲突扫描 → 重锚 → 落盘",冲突时不落盘;`slot_ref_of` 冲突(dbref 与 dsn_ref 同设)、相对路径 `*_dsn_ref`/`*_role_ref`/`files_root` 均拒绝;legacy→container 升级基座可回落既有 `dbref=` 专键,明文 `dsn=` 不进锚点(I5)。✅
|
||||
3. **拒绝路径**:container 缺任一必需绑定在 set(`validate_storage_kv` exit 3)与 verify(`--require-storage` + `require_complete=True`)双端拒绝;错文件根/错库绑定漂移 exit 6(测试断言"文件根"/"base 库绑定漂移");foreign marker 沿用 base 逻辑(T5b);compose 项目名/端口冲突 exit 7 且发生在落盘前(`check_conflicts` 在 `save` 之前)。✅
|
||||
4. **不静默新建空库**:U1 层无任何默认绑定生成;`resolve_storage_bindings` 缺绑定 exit 3 → svc.sh 拒绝;manifest 缺失时 verify exit 4、marker 缺失/损坏 exit 5,均阻塞启动。未发现静默回退空库路径。✅
|
||||
5. **另一 checkout 定位同一资源**:`files_root` 强制绝对路径并 normpath,锚点/解析不依赖 cwd 与仓库根;`test_CS_U1_other_checkout_keeps_absolute_files_root` 与 T6 从外部 cwd 调用验证。✅
|
||||
6. **调用方环境污染**:`env_config` 重置存储全局并 `unset AGENT_SYSTEM_FILES_ROOT/ENV_CLASS/COMPOSE_PROJECT/BASE_DB_REF/RDM_DB_REF/KB_DB_REF`;`test_caller_storage_env_does_not_reach_serve` 检查 serve 进程 environ 与 serve.env;三条棘轮测试守卫 env_config。✅
|
||||
7. **KTD3 单一权威**:`resolve_storage_bindings` 内嵌 python 直读 `manifest.json`(不读 svc.env、不读调用方 env);U1 未引入 compose 文件/第二份卷或 DSN 权威;凭据仅存受保护文件路径引用。`svc.env` 仍是派生只读视图(record_state 不产出存储键,`preserve_storage_keys` 自 manifest 旧 kv 保留)。✅
|
||||
8. **归属清单与契约文档**:`deploy/data-ownership.md` 覆盖基座/RDM/KB/文件根/环境身份/实例身份/Compose 项目七类所有者,含引用形式与唯一写者;体量/源版本/表清单明示"需另行授权访问后补全"(与方案"生产盘点需先获得对应访问授权"及诚实出口一致);无秘密值。`docs/svc.md` §10.1 键表与 `PROJECT.md` 指针已同步;链接目标 `docs/recovery/u1-data-ownership.md`、`docs/recovery/u1-identity-manifest.md` 均存在。✅
|
||||
9. **测试充分性**:三测试文件合计覆盖全部五条 U1 验收场景 + 调用方隔离 + 原子写崩溃窗口;缺口仅 N5 两项(均非验收场景直接要求)。`PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_svc_identity tests.test_svc_lifecycle_isolation tests.test_svc_precheck` → **80 tests, OK (skipped=1)**,唯一 skip 为真实 PG 专项显式 NOT_RUN(诚实标注)。✅
|
||||
10. **秘密与越界**:diff 无明文口令/DSN/token(示例 env 仅受保护路径,测试仅 fixture 值 `ref-only`/`x`);changed files 中除 `AGENT-EXECUTION.md`(见 N6,已另行登记)外全部落在 U1 主要文件/allowed_paths 内;`.task/**` 为 host 直写豁免。✅
|
||||
|
||||
## 复现命令
|
||||
|
||||
```bash
|
||||
# 1) 送审 diff 范围与实际 HEAD diff 一致(代码部分逐字相同)
|
||||
git diff 955a32de2bb49171348bad5f566cce935df19212 HEAD \
|
||||
-- AGENT-EXECUTION.md PROJECT.md deploy docs/superpowers/plans docs/svc.md scripts tests \
|
||||
| diff - /tmp/container-storage-u1-review.diff && echo IDENTICAL
|
||||
|
||||
# 2) 单元测试(本机实测:Ran 80 tests, OK (skipped=1),约 64s)
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest \
|
||||
tests.test_svc_identity tests.test_svc_lifecycle_isolation tests.test_svc_precheck
|
||||
|
||||
# 3) 对象 SHA
|
||||
git rev-parse HEAD # e1f08650a12656611f27438f23fddbfab6a96299
|
||||
git rev-parse 955a32de # 955a32de2bb49171348bad5f566cce935df19212
|
||||
```
|
||||
|
||||
## 结论
|
||||
|
||||
U1 单元按方案完成且质量合格:绑定/身份权威唯一、拒绝路径完整且 fail-closed、验收场景全部落地并有测试背书。上述 N1—N8 均不阻塞本单元交付;N1/N2/N5 建议随 U2 或合入前收口,N6 的路由临时调整须按其自述在交付前回退。
|
||||
@@ -0,0 +1,96 @@
|
||||
# 独立 Review:fix/gitea-endpoint-migration(第 2 轮)
|
||||
|
||||
- 日期:2026-10-22;Review 会话:全新独立会话(本会话未参与实施)
|
||||
- 对象:分支 `fix/gitea-endpoint-migration` HEAD `6cb6a276`,base `3d3474a731f9ef3ee1f41935e63bca3afb70db56`
|
||||
- 实施 diff:`/tmp/gitea-mig-review2.diff`(已核对与 `git diff 3d3474a7..HEAD` 逐字节一致,唯一差异为本报告新文件与任务卡 `.task/fix-gitea-endpoint-migration.md`,均不在功能范围)
|
||||
- 上轮(r1)唯一阻塞项:`plugins/registry.json` rdm 条目仍是旧 SSH 端点;本轮经 repair transport 修复,r1→r2 diff 增量恰为该文件一处 URL 替换,已核对。
|
||||
|
||||
## 总体判定
|
||||
|
||||
**REVIEW PASS(无阻塞项)。**
|
||||
|
||||
五项核对点全部通过:旧端点功能性引用已清零(历史记录与刻意保留的 F4 负例除外);新端点两形态(web 无端口 / SSH 端口 52222)全仓 49 处书写一致;测试负例语义完整保留并实测通过;`policy/facts.md` 变更严格限于 F1—F3 行与 last_verified;diff 无秘密入仓。
|
||||
|
||||
## 分级清单
|
||||
|
||||
### 阻塞项
|
||||
|
||||
无。
|
||||
|
||||
### 非阻塞建议(不阻塞合入,仅记录)
|
||||
|
||||
1. **历史文档中的旧 URL 仅为记录,防止误复用**:`docs/superpowers/plans/2026-10-16-development-governance-v2-master.md`(文末双钥匙命令段含 `API=https://git-home.u1680832.nyat.app:30319/api/v1`)、`docs/superpowers/plans/2026-10-17-guidelines-governance-adoption.md`、`docs/superpowers/specs/2026-10-20-fast-slow-o2-i{1,2}-review.md` 内含旧 git-home 链接。均为已 CLOSED/MERGED 的历史记录(PR/CI 当时事实),属本单元豁免类;且 AGENTS.md 已规定端点事实只以 `policy/facts.md` 为准。建议后续如有人重放这些命令段,以 facts.md 现行端点替换,勿照抄。
|
||||
2. **F4 存在已知漂移**:F4(旧 git-panel 端点 TLS 握手失败)last_verified 仍为 2026-10-14,任务卡已注记「F4 FAIL 已知漂移」。F4 刻意不动符合单元范围;漂移刷新属 facts 维护单元职责,与本迁移无耦合。
|
||||
3. **`.task/` 归档与 artifacts 内大量旧端点 URL**:均为任务历史回执/归档(豁免类),无需处理;列出仅为完整性确认。
|
||||
|
||||
## 核对点结果
|
||||
|
||||
### 1) 完整性——旧端点功能性引用清零 ✅
|
||||
|
||||
全仓扫描(排除 `.git/`、`.task/`)`git-ssh.u1680832` / `git-home.u1680832` 零残留;`git-panel.u1680832` 与裸 `u1680832` 仅剩以下刻意保留项:
|
||||
|
||||
| 位置 | 性质 | 判定 |
|
||||
|---|---|---|
|
||||
| `policy/facts.md:11` | 退役端点政策禁令(「一律不得使用」) | 刻意保留,正确 |
|
||||
| `policy/facts.md:21`(F4) | 旧端点 TLS 观察事实及探针命令 | 单元规定 F4 不动,正确 |
|
||||
| `tests/test_facts_verify.py:372,377` | F4 探针负例(针对退役端点) | 与 F4 配套,正确 |
|
||||
| `tests/test_ci_credentials.py:167` | 负例:无 scheme 的 metadata_url 必须被拒(实测 refused,0 请求) | 负例语义,正确 |
|
||||
| `tests/test_task_triage.py:394`、`tests/test_ci_wait.py:118` | 合成 fixture(`git.example`/`.invalid`) | 非 real 端点,正确 |
|
||||
| `docs/superpowers/{plans,specs}/*.md`(4 文件) | 历史 CLOSED/MERGED 记录 | 豁免类,见非阻塞 1 |
|
||||
|
||||
此前未覆盖的文件类型已补查:全仓 JSON/TOML/YAML/ini(`policy/architecture.yaml`、`policy/architecture-baseline.json`、`plugins/**.json`、`examples/**.json`、`.zcode/config.json`、`docs/structure/cockpit-e2e.yaml`、`docker/`、`.templates/`、`Makefile`、`AGENT-EXECUTION.md`、`PROJECT.md`、`AGENTS.md`)均无旧端点引用;唯一含端点的 JSON 为 `plugins/registry.json`,已迁移。
|
||||
|
||||
### 2) 正确性——新端点格式一致 ✅
|
||||
|
||||
全仓 49 处 `gitea.naitangbaobao.cn` 全部落在两形态(`sort|uniq` 核对):
|
||||
|
||||
- Web:`https://gitea.naitangbaobao.cn`(无端口),派生 `/api/v1`(task_triage DEFAULT_API、task.sh 两处 `${web_base}/api/v1`)、`/api/v1/user`(facts-verify ENDPOINT 及表头注释)、`/api/v1/version`(F1)、`/weichen/Agent-System-Dev/issues/518`(delivery 队列链接)。
|
||||
- SSH:`ssh://git@gitea.naitangbaobao.cn:52222/weichen/<Repo>.git`——`.gitmodules` 全部 14 条 URL、`plugins/registry.json`(rdm,上轮阻塞项)、`scripts/svc.sh` RDM_ORIGIN_DEFAULT、`ci.yaml` 4 处 allowlist 前缀 + 2 处 insteadOf 改写前缀、`tests/test_ci_credentials.py`/`test_svc_realcombo.py` 期望值、facts.md F3。
|
||||
|
||||
无混写(无新域名带旧端口、无旧域名、无 web 误带 52222、无 SSH 缺 52222)。`ci.yaml` 的 `SERVER` 仍强制平台注入 http(s) URL,`GIT_CONFIG_VALUE_2` 以新 SSH 前缀做 insteadOf → `$SERVER/weichen/`,链路自洽。
|
||||
|
||||
### 3) 测试负例语义保留 ✅
|
||||
|
||||
`tests/test_facts_verify.py` `test_f2_rejects_unregistered_destination_before_reading_credentials` 的 bad_urls 逐项映射:http 明文、`evil.invalid`、错端口 `:30320`、显式默认端口 `:443`(替代旧「无端口」负例——`facts-verify.sh` 对 ENDPOINT 做整串比较,`:443` 同样被拒,语义等价)、URL 内凭据、`@evil.invalid` 重定向、`#frag`、`?next=`、`/users`、`/user/../admin`、尾随空格——全部保留,且仍断言「未登记目的地不发请求、不读凭据」。重定向(301/302/303/307/308)不跟随、不转发 Authorization 的断言保留。本地实测(stub 断网,离线):
|
||||
|
||||
- `tests.test_facts_verify` 8/8 OK
|
||||
- `tests.test_ci_credentials` 9/9 OK(含 `git-panel` 无 scheme 负例仍 refused)
|
||||
- `tests.test_task_triage` 29/29 OK
|
||||
- `tests.test_ci_wait` 11/11 OK
|
||||
- `tests.test_svc_realcombo` 16 skipped(设计上需 `SVC_TEST_REAL=1`,离线 NOT_RUN,符合文件头条件)
|
||||
- `bash -n` task.sh / facts-verify.sh / svc.sh 通过;`task_triage.py` 编译通过;`registry.json` JSON 合法
|
||||
|
||||
### 4) policy/facts.md 变更范围 ✅
|
||||
|
||||
`git diff base..HEAD -- policy/facts.md` 仅 3 行:F1、F2、F3 行(端点 URL + last_verified 2026-10-14→2026-10-22)。F4/F5/F6 行逐字未动,表前政策禁令段未动。F2 验证命令仍不含 curl/Authorization/.secrets(`test_registry_separates_retired_endpoint_policy_from_tls_observation` 实测通过,该测试直接解析现行 facts.md)。
|
||||
|
||||
### 5) 无秘密入仓 ✅
|
||||
|
||||
对 HEAD 全量 diff 的 `+` 行做秘密模式扫描(长随机串、`ghp_`/`AKIA`/`xox`、PEM 头、password/secret 赋值等):命中行全部为 git URL、SHA256、任务卡元数据与本地路径,无凭据值。测试 token 均为 `synthetic-*-not-a-real-secret`;`facts-verify.sh` 仍在运行时进程内读取 `~/.secrets/gitea`,仓内仅存路径指针。
|
||||
|
||||
## 复现命令
|
||||
|
||||
```bash
|
||||
# diff 范围一致性(本报告对象与实际 HEAD diff 一致,除 .task 卡与本报告)
|
||||
git diff 3d3474a731f9ef3ee1f41935e63bca3afb70db56..HEAD -- . ':(exclude).task' > /tmp/actual-head-diff.diff
|
||||
diff /tmp/gitea-mig-review2.diff /tmp/actual-head-diff.diff && echo MATCH
|
||||
|
||||
# 旧端点残留(应仅剩 facts.md 禁令/F4 与测试负例/fixture/历史文档)
|
||||
grep -rn "u1680832" . | grep -v "^\./\.git/" | grep -v "^\./\.task/" | grep -v "docs/superpowers/"
|
||||
|
||||
# 新端点一致性(应只出现两形态)
|
||||
grep -rhn "gitea\.naitangbaobao\.cn" . | grep -v "^\./\.task/" \
|
||||
| grep -oE "[a-z]+://[^)\`'\" ,;;]*" | sort | uniq -c | sort -rn
|
||||
|
||||
# facts.md 变更范围(应仅 F1—F3 三行)
|
||||
git diff 3d3474a731f9ef3ee1f41935e63bca3afb70db56..HEAD -- policy/facts.md
|
||||
|
||||
# 负例与单测(离线,stub 断网)
|
||||
python3 -m unittest tests.test_facts_verify tests.test_ci_credentials \
|
||||
tests.test_task_triage tests.test_ci_wait tests.test_svc_realcombo
|
||||
```
|
||||
|
||||
## 证据与边界
|
||||
|
||||
- 本报告所列测试输出为本地离线实测(SELF 级);端点可达性(F1—F3 的 curl/ls-remote)属网络验证,本单元 `network=false`,**NOT_RUN**,以实施侧任务卡记录的 F1/F2/F3 PASS(新端点实测)为准,合入前由必需 CI 复核。
|
||||
- 审阅者为全新独立会话,未参与实施;Review 仅产出本报告,未实施任何修复。
|
||||
-12
@@ -866,21 +866,9 @@ scripts/svc.sh prod deploy --env .env.prod # 等价
|
||||
| `dbref` | agent 托管数据库绑定 `<pg名>/<库名>`(启动时由实例登记重建 DSN;见 §4.5) |
|
||||
| `dsn` | agent 外部直连 DSN(受保护;未命中本机实例登记的直连存放于此) |
|
||||
| `conffile` | agent 配置文件绝对路径(`<name> config` 命令写入,跨重启保留;见 §4.6) |
|
||||
| `storage_mode` | `legacy`(缺省,SQLite/宿主 PG)/ `container`(三库+文件根合同;见 [deploy/data-ownership.md](../deploy/data-ownership.md)) |
|
||||
| `env_class` | 环境身份:`prod` / `test` / `verify`(container 必需) |
|
||||
| `files_root` | 持久文件根**绝对路径**(不相对 checkout 解析;container 必需) |
|
||||
| `compose_project` | Compose 项目名(同 runtime 唯一;冲突在修改前拒绝) |
|
||||
| `base_dbref` / `base_dsn_ref` / `base_role_ref` / `base_port` | 基座逻辑库绑定(dsn/role 仅为受保护文件路径引用) |
|
||||
| `rdm_dbref` / `rdm_dsn_ref` / `rdm_role_ref` / `rdm_port` | RDM 领域库绑定 |
|
||||
| `kb_dbref` / `kb_dsn_ref` / `kb_role_ref` / `kb_port` | 知识库绑定 |
|
||||
| `user` / `password` / `dbname` / `authmode` / `pgdata` | postgres 专属 |
|
||||
| `htdocs` | static 专属 |
|
||||
|
||||
容器存储绑定由 `manifest.json` 冻结为身份锚点(与 `data_root`/`db_*` 同级);旧配置
|
||||
升级保留 `instance_id`。`storage_mode=container` 时缺任一必需绑定、错库、错文件根、
|
||||
foreign marker、同名 Compose 或端口冲突均拒绝启动/修改,不静默建空库、不回退默认。
|
||||
示例键见 [`deploy/env-bindings.example.env`](../deploy/env-bindings.example.env)。
|
||||
|
||||
### 10.2 history.jsonl 事件
|
||||
|
||||
一行一个 JSON:`ts / event / env / kind / source / result / pid / addr / detail`。
|
||||
|
||||
+1
-1
Submodule modules/backend updated: a75783fd2d...70598ae801
+1
-1
Submodule modules/core updated: 0cf03446c4...614dcbff4b
+1
-1
Submodule modules/documents updated: 2e9c3bc1d8...103d7c34fe
+1
-1
Submodule modules/frontend updated: 279b2d02a2...807c656886
+1
-1
Submodule modules/ui updated: df1c3ed8d6...ef0672e570
@@ -3,7 +3,7 @@
|
||||
{
|
||||
"id": "rdm",
|
||||
"name": "研发记忆 RDM",
|
||||
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-RDM.git",
|
||||
"repo": "ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-RDM.git",
|
||||
"repoEnv": "SVC_RDM_ORIGIN",
|
||||
"defaultRef": "origin/main",
|
||||
"goModule": "agent-system-rdm",
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
"backend/hostapi backend/store",
|
||||
"backend/ingest backend/vision",
|
||||
"backend/instanceguard backend/store",
|
||||
"backend/intentsession backend/intentgate",
|
||||
"backend/intentsession backend/store",
|
||||
"backend/pgbus backend/bus",
|
||||
"backend/pgbus backend/store",
|
||||
"backend/plugins backend/settings",
|
||||
@@ -49,6 +51,8 @@
|
||||
"backend/rag backend/settings",
|
||||
"backend/rag backend/store",
|
||||
"backend/route backend/intentgate",
|
||||
"backend/rundiag backend/cmdbus",
|
||||
"backend/rundiag backend/store",
|
||||
"backend/runmgr backend/bus",
|
||||
"backend/runmgr backend/cardsvc",
|
||||
"backend/runmgr backend/docgen",
|
||||
@@ -76,12 +80,14 @@
|
||||
"backend/server backend/execenv",
|
||||
"backend/server backend/ingest",
|
||||
"backend/server backend/intentgate",
|
||||
"backend/server backend/intentsession",
|
||||
"backend/server backend/plugins",
|
||||
"backend/server backend/profiles",
|
||||
"backend/server backend/projectscope",
|
||||
"backend/server backend/prompts",
|
||||
"backend/server backend/ratelimit",
|
||||
"backend/server backend/route",
|
||||
"backend/server backend/rundiag",
|
||||
"backend/server backend/runmgr",
|
||||
"backend/server backend/settings",
|
||||
"backend/server backend/store",
|
||||
|
||||
+1
-1
@@ -381,7 +381,7 @@ Review 只将阻塞项列为必修;非阻塞意见标明后续建议,不得
|
||||
解决冲突和重跑最终 CI。本节只安排 Dev `refs/heads/main` 的最终交付顺序;需求、授权、阶段状态和
|
||||
证据仍在原任务卡,受保护分支的最终准入仍由 Gitea 执行。
|
||||
|
||||
- **队列**:Dev main 只用一份[合入队列 #518](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/issues/518),
|
||||
- **队列**:Dev main 只用一份[合入队列 #518](https://gitea.naitangbaobao.cn/weichen/Agent-System-Dev/issues/518),
|
||||
只记录顺序和当前窗口:任务/PR、owner、依赖、就绪状态、当前 base/head、CI、合入结果。
|
||||
- **职责**:协调智能体维护顺序和窗口;任务 owner 负责分支内容和必要整改;窗口内的同步、冲突处置、
|
||||
Review、当前 HEAD CI 核验和所有 PR 合入由智能体执行。
|
||||
|
||||
+3
-3
@@ -15,9 +15,9 @@
|
||||
|
||||
| ID | 事实 | 验证命令 | last_verified | TTL(天) |
|
||||
|---|---|---|---|---|
|
||||
| F1 | Gitea Web/API 可达:https://git-home.u1680832.nyat.app:30319(1.27.3) | `curl -fsS -m 10 https://git-home.u1680832.nyat.app:30319/api/v1/version` | 2026-10-14 | 30 |
|
||||
| F2 | API token 存在且可用(值在 ~/.secrets/gitea,HOME/IMC 两段各一组 `- ACCESS_TOKEN=…`,本条取 HOME 段;不入仓不回显,不进子进程 argv) | `bash scripts/facts-verify.sh api-token https://git-home.u1680832.nyat.app:30319/api/v1/user` | 2026-10-14 | 30 |
|
||||
| F3 | Git over SSH 可达:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/<Repo>.git(key ~/.ssh/home_gitea) | `GIT_SSH_COMMAND="ssh -i ~/.ssh/home_gitea -o BatchMode=yes" git ls-remote ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Guidelines.git HEAD` | 2026-10-14 | 30 |
|
||||
| F1 | Gitea Web/API 可达:https://gitea.naitangbaobao.cn(1.27.3) | `curl -fsS -m 10 https://gitea.naitangbaobao.cn/api/v1/version` | 2026-10-22 | 30 |
|
||||
| F2 | API token 存在且可用(值在 ~/.secrets/gitea,HOME/IMC 两段各一组 `- ACCESS_TOKEN=…`,本条取 HOME 段;不入仓不回显,不进子进程 argv) | `bash scripts/facts-verify.sh api-token https://gitea.naitangbaobao.cn/api/v1/user` | 2026-10-22 | 30 |
|
||||
| F3 | Git over SSH 可达:ssh://git@gitea.naitangbaobao.cn:52222/weichen/<Repo>.git(key ~/.ssh/home_gitea) | `GIT_SSH_COMMAND="ssh -i ~/.ssh/home_gitea -o BatchMode=yes" git ls-remote ssh://git@gitea.naitangbaobao.cn:52222/weichen/Development-Guidelines.git HEAD` | 2026-10-22 | 30 |
|
||||
| F4 | 旧端点 git-panel.u1680832.nyat.app:30319 TCP 可连通但 TLS 握手失败(仅 TLS 层握手错误判 PASS) | `bash scripts/facts-verify.sh tls-handshake-fails git-panel.u1680832.nyat.app 30319` | 2026-10-14 | 180 |
|
||||
| F5 | 基座 users 表存有姓名(name 列,CreateAccount/ListUsersAdmin 读写;EXT-19 目录自 P3 起向授权管理读面下发) | `cd modules/backend && go test ./internal/app/ -run TestInprocHostSearchAccounts -count=1` | 2026-10-14 | 180 |
|
||||
| F6 | 基座账号无停用位(users 表无 disabled;EXT-19 Valid 恒为存在性)——停用机制未实现,不得当已具备 | `cd modules/backend && ! grep -rn -e disabled -e Disabled internal/store/auth.go pkg/extension/extension.go` | 2026-10-14 | 180 |
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# 用法:make facts-verify [ARGS=--update] (--update:把 PASS 项 last_verified 刷为当天并写回)
|
||||
# 表内验证命令约定不含竖线;含 "\|" 转义时按字面还原后执行。
|
||||
# 登记表引用的探针子命令(单独运行时在 stderr 说明判定,不输出凭据):
|
||||
# api-token URL URL 须逐字等于登记端点 https://git-home.u1680832.nyat.app:30319/api/v1/user,
|
||||
# api-token URL URL 须逐字等于登记端点 https://gitea.naitangbaobao.cn/api/v1/user,
|
||||
# 校验通过后才进程内读 ~/.secrets/gitea HOME 段 ACCESS_TOKEN 并 GET(不跟随重定向);
|
||||
# 0=接受 3=缺失 4=格式不符 5=请求失败/被拒/重定向 6=非登记端点
|
||||
# tls-handshake-fails HOST PORT 仅当 TCP 已连通且 TLS 层握手报错时 0;
|
||||
@@ -20,7 +20,7 @@ import sys, urllib.request
|
||||
sys.path.insert(0, sys.argv[1])
|
||||
import task_triage
|
||||
|
||||
ENDPOINT = "https://git-home.u1680832.nyat.app:30319/api/v1/user"
|
||||
ENDPOINT = "https://gitea.naitangbaobao.cn/api/v1/user"
|
||||
if sys.argv[2] != ENDPOINT:
|
||||
print("api-token: URL 不是 policy/facts.md F2 登记端点,拒绝发送凭据", file=sys.stderr)
|
||||
sys.exit(6)
|
||||
|
||||
+11
-104
@@ -144,7 +144,7 @@ SRC_DIR="${SVC_SRC_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/agent-system-svc/src}"
|
||||
# 独立 RDM 源仓(下游组合仓 Agent-System-RDM 的干净克隆;SVC_RDM_SRC_DIR 覆盖路径,
|
||||
# SVC_RDM_ORIGIN 覆盖克隆来源)。rdm 源的解析/列举/物化全部基于它,语义与 SRC_DIR 一致。
|
||||
RDM_SRC_DIR="${SVC_RDM_SRC_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/agent-system-svc/rdm-src}"
|
||||
RDM_ORIGIN_DEFAULT="ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-RDM.git"
|
||||
RDM_ORIGIN_DEFAULT="ssh://git@gitea.naitangbaobao.cn:52222/weichen/Agent-System-RDM.git"
|
||||
|
||||
# 提示信息走 stderr:stdout 只留给数据(提交表/服务表/DSN),便于 $( ) 捕获组合。
|
||||
c_info() { [ "${CLI_QUIET:-0}" = 1 ] && return 0; printf '\033[1;34m[svc]\033[0m %s\n' "$*" >&2; }
|
||||
@@ -1012,9 +1012,8 @@ record_state() {
|
||||
chmod 600 "$tmp"
|
||||
# U1 身份:manifest 治理下的整份提交(bulk-set 走 temp→fsync→rename 原子链,
|
||||
# revision 递增,svc.env 由 manifest 派生);失败保留旧配置,不产生半写态。
|
||||
# 存储绑定键(STORAGE_KEYS)由 manifest bulk-set 自旧 kv 保留,避免生命周期写回冲掉。
|
||||
if manifest_ok; then
|
||||
if python3 "$MANIFEST_PY" "$ENV_DIR" bulk-set --runtime "$RUNTIME" < "$tmp"; then
|
||||
if python3 "$MANIFEST_PY" "$ENV_DIR" bulk-set < "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
return 0
|
||||
fi
|
||||
@@ -1218,103 +1217,18 @@ svc_manifest_migrate() {
|
||||
c_err "[$ENV_NAME] manifest 迁移失败(保持旧治理):$(printf '%s' "$out" | tail -1)"
|
||||
return 0
|
||||
}
|
||||
# resolve_storage_bindings:从本实例 manifest 权威解析容器存储绑定(不读调用方环境)。
|
||||
# 结果全局:STORAGE_MODE ENV_CLASS FILES_ROOT COMPOSE_PROJECT
|
||||
# BASE_DB_REF RDM_DB_REF KB_DB_REF BASE_PORT RDM_PORT KB_PORT
|
||||
# storage_mode=container 且缺任一必需绑定时返回 1(拒绝静默默认/建空库);
|
||||
# legacy / 未配置返回 0 且 STORAGE_MODE=legacy。
|
||||
resolve_storage_bindings() {
|
||||
STORAGE_MODE=legacy; ENV_CLASS=""; FILES_ROOT=""; COMPOSE_PROJECT=""
|
||||
BASE_DB_REF=""; RDM_DB_REF=""; KB_DB_REF=""
|
||||
BASE_PORT=""; RDM_PORT=""; KB_PORT=""
|
||||
manifest_ok || return 0
|
||||
local out rc=0
|
||||
out="$(python3 - "$ENV_DIR" <<'PY' 2>&1
|
||||
import json, os, sys
|
||||
env_dir = sys.argv[1]
|
||||
path = os.path.join(env_dir, "manifest.json")
|
||||
with open(path, encoding="utf-8") as f:
|
||||
m = json.load(f)
|
||||
ident = m.get("identity") or {}
|
||||
kv = m.get("kv") or {}
|
||||
mode = (kv.get("storage_mode") or ident.get("storage_mode") or "legacy").strip() or "legacy"
|
||||
print("MODE=%s" % mode)
|
||||
print("ENV_CLASS=%s" % (kv.get("env_class") or ident.get("env_class") or ""))
|
||||
print("FILES_ROOT=%s" % (kv.get("files_root") or ident.get("files_root") or ""))
|
||||
print("COMPOSE_PROJECT=%s" % (kv.get("compose_project") or ident.get("compose_project") or ""))
|
||||
print("BASE_DB_REF=%s" % (ident.get("base_db_ref") or ""))
|
||||
print("RDM_DB_REF=%s" % (ident.get("rdm_db_ref") or ""))
|
||||
print("KB_DB_REF=%s" % (ident.get("kb_db_ref") or ""))
|
||||
print("BASE_PORT=%s" % (kv.get("base_port") or ident.get("base_port") or ""))
|
||||
print("RDM_PORT=%s" % (kv.get("rdm_port") or ident.get("rdm_port") or ""))
|
||||
print("KB_PORT=%s" % (kv.get("kb_port") or ident.get("kb_port") or ""))
|
||||
if mode == "container":
|
||||
missing = []
|
||||
if (kv.get("env_class") or ident.get("env_class") or "") not in ("prod", "test", "verify"):
|
||||
missing.append("env_class")
|
||||
fr = kv.get("files_root") or ident.get("files_root") or ""
|
||||
if not fr or not os.path.isabs(fr):
|
||||
missing.append("files_root")
|
||||
if not (kv.get("compose_project") or ident.get("compose_project") or ""):
|
||||
missing.append("compose_project")
|
||||
for slot, key in (("base", "base_db_ref"), ("rdm", "rdm_db_ref"), ("kb", "kb_db_ref")):
|
||||
if not (ident.get(key) or ""):
|
||||
missing.append(slot + "_db")
|
||||
if not (kv.get(slot + "_port") or ident.get(slot + "_port") or ""):
|
||||
missing.append(slot + "_port")
|
||||
if not (kv.get(slot + "_role_ref") or ""):
|
||||
missing.append(slot + "_role_ref")
|
||||
if missing:
|
||||
print("MISSING=%s" % ",".join(missing), file=sys.stderr)
|
||||
sys.exit(3)
|
||||
PY
|
||||
)" || rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
c_err "[$ENV_NAME] 存储绑定不完整或无效,拒绝(不静默新建空库/回退默认):$(printf '%s' "$out" | tail -1)"
|
||||
return 1
|
||||
fi
|
||||
local line
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
MODE=*) STORAGE_MODE="${line#MODE=}" ;;
|
||||
ENV_CLASS=*) ENV_CLASS="${line#ENV_CLASS=}" ;;
|
||||
FILES_ROOT=*) FILES_ROOT="${line#FILES_ROOT=}" ;;
|
||||
COMPOSE_PROJECT=*) COMPOSE_PROJECT="${line#COMPOSE_PROJECT=}" ;;
|
||||
BASE_DB_REF=*) BASE_DB_REF="${line#BASE_DB_REF=}" ;;
|
||||
RDM_DB_REF=*) RDM_DB_REF="${line#RDM_DB_REF=}" ;;
|
||||
KB_DB_REF=*) KB_DB_REF="${line#KB_DB_REF=}" ;;
|
||||
BASE_PORT=*) BASE_PORT="${line#BASE_PORT=}" ;;
|
||||
RDM_PORT=*) RDM_PORT="${line#RDM_PORT=}" ;;
|
||||
KB_PORT=*) KB_PORT="${line#KB_PORT=}" ;;
|
||||
esac
|
||||
done <<<"$out"
|
||||
return 0
|
||||
}
|
||||
|
||||
# svc_manifest_verify:启动前身份校验——数据 marker 存在且 instance_id 匹配;
|
||||
# 生效 --data / 数据库绑定与冻结锚点一致。失败即拒绝启动(KTD1/KTD2,I4)。
|
||||
# 顺序:文件 marker →(container)存储绑定完整/无漂移 → DB verify(缺/错即拦)。
|
||||
# ensure 只在领养(svc_manifest_migrate)与显式 init——普通启动不建身份,缺行即事件。
|
||||
# 顺序:文件 marker → DB verify(缺/错即拦)。ensure 只在领养(svc_manifest_migrate)
|
||||
# 与显式 init——普通启动不建身份,缺行即事件(修复路径:svc init,或换具备
|
||||
# identity 能力的产物更新时由候选门禁补齐)。
|
||||
svc_manifest_verify() {
|
||||
manifest_ok || return 0
|
||||
local out verify_args storage_mode
|
||||
verify_args=(--data "$ENV_DATA" --db-mode "${DB_MODE:-}" --db-ref "${DB_REF:-}")
|
||||
storage_mode="$(svc_get storage_mode)"
|
||||
if [ "$storage_mode" = container ]; then
|
||||
# 生效值只来自本实例登记(env_config 已剥离调用方污染);缺绑/错根/错库拒绝。
|
||||
resolve_storage_bindings || return 1
|
||||
verify_args+=(--require-storage
|
||||
--files-root "$FILES_ROOT" --env-class "$ENV_CLASS"
|
||||
--compose-project "$COMPOSE_PROJECT"
|
||||
--base-db-ref "$BASE_DB_REF" --rdm-db-ref "$RDM_DB_REF" --kb-db-ref "$KB_DB_REF")
|
||||
python3 "$MANIFEST_PY" "$ENV_DIR" check-conflicts --runtime "$RUNTIME" >/dev/null || {
|
||||
c_err "[$ENV_NAME] 存储绑定冲突,拒绝启动(Compose 项目名或端口与他实例冲突)"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
if ! out="$(python3 "$MANIFEST_PY" "$ENV_DIR" verify "${verify_args[@]}" 2>&1)"; then
|
||||
local out
|
||||
if ! out="$(python3 "$MANIFEST_PY" "$ENV_DIR" verify \
|
||||
--data "$ENV_DATA" --db-mode "${DB_MODE:-}" --db-ref "${DB_REF:-}" 2>&1)"; then
|
||||
c_err "[$ENV_NAME] 身份校验失败,拒绝启动:$(printf '%s' "$out" | tail -1)"
|
||||
c_err " 数据根/数据库/文件根绑定是冻结身份;换根/重绑须显式迁移或登记命令(docs/recovery/u1-identity-manifest.md §5;容器存储见 deploy/data-ownership.md)"
|
||||
c_err " 数据根/数据库绑定是冻结身份;换根/重绑须显式迁移或登记命令(docs/recovery/u1-identity-manifest.md §5)"
|
||||
return 1
|
||||
fi
|
||||
# 文件校验通过后再碰库。fresh 允许库尚未建立;established 只验证;
|
||||
@@ -1790,18 +1704,12 @@ env_config() {
|
||||
# update --all:dev 成功后 genui/rdm1/rdmcand 端口仍被孤儿占用)。
|
||||
SVC_DB_MODE=""; SVC_DB_SOURCE=""; SVC_DB_REF=""; SVC_DB_DSN_SAVE=""
|
||||
SVC_PIDCHECK=""; SVC_EXEC=""; SVC_VERSION=""; SVC_SOURCE=""
|
||||
STORAGE_MODE=""; ENV_CLASS=""; FILES_ROOT=""; COMPOSE_PROJECT=""
|
||||
BASE_DB_REF=""; RDM_DB_REF=""; KB_DB_REF=""
|
||||
BASE_PORT=""; RDM_PORT=""; KB_PORT=""
|
||||
# U1:身份环境同样按实例重置——上一服务 verify 成功后导出的
|
||||
# AGENT_SYSTEM_INSTANCE_ID/DBREF 不得串给下一服务(批量 update 单进程逐服务解析;
|
||||
# 2026-10-12 实况:dev 的身份漏给 rdm1/rdmcand,叠加 RDM 产物无 identity 子命令
|
||||
# 跳过校验,serve 带外宾身份即被基座门禁拒绝)。serve 只允许拿到本服务 verify
|
||||
# 写入的身份。
|
||||
# 容器存储 U1:调用方/上一服务的文件根、环境身份、Compose 与三库引用不得污染本实例。
|
||||
unset AGENT_SYSTEM_INSTANCE_ID AGENT_SYSTEM_DBREF
|
||||
unset AGENT_SYSTEM_FILES_ROOT AGENT_SYSTEM_ENV_CLASS AGENT_SYSTEM_COMPOSE_PROJECT
|
||||
unset AGENT_SYSTEM_BASE_DB_REF AGENT_SYSTEM_RDM_DB_REF AGENT_SYSTEM_KB_DB_REF
|
||||
local registered=""
|
||||
[ -f "$ENV_DIR/svc.env" ] && registered="$(svc_kind "$name")"
|
||||
SVC_KIND="${registered:-agent}"
|
||||
@@ -4670,11 +4578,10 @@ check_port_available() {
|
||||
|
||||
# svc_set_key KEY VALUE:改写当前服务 svc.env 的单键。manifest 治理下经 manifest
|
||||
# 整份提交(原子 + revision);legacy 路径保持先读后写 + 临时文件原子替换。
|
||||
# 存储绑定键变更经 --runtime 做 Compose/端口冲突预检(修改前失败)。
|
||||
svc_set_key() {
|
||||
local key="$1" val="$2" rest tmp
|
||||
if manifest_ok; then
|
||||
python3 "$MANIFEST_PY" "$ENV_DIR" set --runtime "$RUNTIME" "$key" "$val" && return 0
|
||||
python3 "$MANIFEST_PY" "$ENV_DIR" set "$key" "$val" && return 0
|
||||
c_warn "[$ENV_NAME] manifest set 失败,回退 legacy 单键写"
|
||||
fi
|
||||
[ -f "$ENV_DIR/svc.env" ] || { c_err "svc.env 不存在:$ENV_DIR"; return 1; }
|
||||
@@ -4691,7 +4598,7 @@ svc_set_key() {
|
||||
svc_del_key() {
|
||||
local key="$1"
|
||||
if manifest_ok; then
|
||||
python3 "$MANIFEST_PY" "$ENV_DIR" del --runtime "$RUNTIME" "$key" && return 0
|
||||
python3 "$MANIFEST_PY" "$ENV_DIR" del "$key" && return 0
|
||||
c_warn "[$ENV_NAME] manifest del 失败,回退 legacy 单键写"
|
||||
fi
|
||||
[ -f "$ENV_DIR/svc.env" ] || return 0
|
||||
|
||||
+36
-399
@@ -1,19 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""svc 实例身份 manifest(U1):manifest.json 为启动权威,svc.env 为派生只读视图。
|
||||
|
||||
设计合同:docs/recovery/u1-identity-manifest.md(已批准 2026-10-12);
|
||||
容器化存储扩展:docs/superpowers/plans/2026-10-22-container-storage-plan.md U1/KTD1—KTD4。
|
||||
|
||||
设计合同:docs/recovery/u1-identity-manifest.md(已批准 2026-10-12)。
|
||||
落地说明:v1.0 以「identity 锚点(数据根/数据库身份,冻结)+ kv 权威键值映射 +
|
||||
desired 期望态」承载不变量;本文件在同一权威上扩展三库/文件根/环境身份绑定,
|
||||
不另造第二份登记权威。完整 typed 分区与容器启停属后续单元。
|
||||
desired 期望态」承载 KTD1/KTD2 的全部不变量;完整 typed 分区随 U2/U6 演进。
|
||||
|
||||
用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|set-desired|verify|check-conflicts> [参数]
|
||||
用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|set-desired|verify> [参数]
|
||||
|
||||
不变量(对应设计 §1 + 容器存储 U1):
|
||||
不变量(对应设计 §1):
|
||||
I3 原子性 —— 整份 temp→fsync→rename→dirsync,任意时刻只有完整旧版或新版。
|
||||
I4 阻塞 —— manifest 存在时,数据 marker 缺失/身份不符即拒绝,不新建空系统。
|
||||
I5 机密 —— 凭据只存受保护文件路径引用(*_dsn_ref / *_role_ref),不存明文。
|
||||
I5 机密 —— 本文件不新增任何机密;kv 沿袭 svc.env 既有内容(DSN 迁出属 U6)。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -25,27 +22,7 @@ SCHEMA = 1
|
||||
MANIFEST = "manifest.json"
|
||||
DERIVED = "svc.env"
|
||||
MARKER_REL = os.path.join(".agentx", "data-marker.json")
|
||||
|
||||
# 显式持久化写入可重锚;启动期漂移由 verify 拦截。含容器存储绑定键。
|
||||
PROTECTED = (
|
||||
"db", "dbref", "dsn", "conffile",
|
||||
"storage_mode", "env_class", "files_root", "compose_project",
|
||||
"base_dbref", "base_dsn_ref", "base_role_ref", "base_port",
|
||||
"rdm_dbref", "rdm_dsn_ref", "rdm_role_ref", "rdm_port",
|
||||
"kb_dbref", "kb_dsn_ref", "kb_role_ref", "kb_port",
|
||||
)
|
||||
|
||||
# record_state/bulk-set 重建时须保留的存储绑定键(避免被生命周期写回冲掉)。
|
||||
STORAGE_KEYS = (
|
||||
"storage_mode", "env_class", "files_root", "compose_project",
|
||||
"base_dbref", "base_dsn_ref", "base_role_ref", "base_port",
|
||||
"rdm_dbref", "rdm_dsn_ref", "rdm_role_ref", "rdm_port",
|
||||
"kb_dbref", "kb_dsn_ref", "kb_role_ref", "kb_port",
|
||||
)
|
||||
|
||||
ENV_CLASSES = ("prod", "test", "verify")
|
||||
DB_SLOTS = ("base", "rdm", "kb")
|
||||
STORAGE_MODES = ("legacy", "container")
|
||||
PROTECTED = ("db", "dbref", "dsn", "conffile") # 显式持久化写入可重锚;启动期漂移由 verify 拦截
|
||||
|
||||
|
||||
def die(msg, code=1):
|
||||
@@ -139,144 +116,6 @@ def db_anchor_of(kv):
|
||||
return "", ""
|
||||
|
||||
|
||||
def storage_mode_of(kv):
|
||||
mode = (kv.get("storage_mode") or "").strip() or "legacy"
|
||||
if mode not in STORAGE_MODES:
|
||||
die("storage_mode 仅允许 legacy|container,收到:%s" % mode, 2)
|
||||
return mode
|
||||
|
||||
|
||||
def files_root_of(kv):
|
||||
"""持久文件根:必须已是绝对路径;绝不相对 checkout/worktree 解析。"""
|
||||
fr = (kv.get("files_root") or "").strip()
|
||||
if not fr:
|
||||
return ""
|
||||
if not os.path.isabs(fr):
|
||||
die("files_root 必须是绝对路径(不相对 checkout 解析):%s" % fr, 2)
|
||||
return os.path.normpath(fr)
|
||||
|
||||
|
||||
def slot_ref_of(kv, slot):
|
||||
"""逻辑库引用锚点:优先 dbref,否则 dsn_ref(受保护文件路径);明文 dsn 禁止。"""
|
||||
dbref = (kv.get("%s_dbref" % slot) or "").strip()
|
||||
dsn_ref = (kv.get("%s_dsn_ref" % slot) or "").strip()
|
||||
if dbref and dsn_ref:
|
||||
die("%s 绑定冲突:不可同时设置 %s_dbref 与 %s_dsn_ref" % (slot, slot, slot), 2)
|
||||
if dbref:
|
||||
return "dbref:%s" % dbref
|
||||
if dsn_ref:
|
||||
if not os.path.isabs(dsn_ref):
|
||||
die("%s_dsn_ref 必须是绝对路径(秘密文件引用):%s" % (slot, dsn_ref), 2)
|
||||
return "dsn_ref:%s" % os.path.normpath(dsn_ref)
|
||||
# 基座可回落既有 dbref/dsn 专键(legacy→container 升级兼容,dsn 明文不进锚点)
|
||||
if slot == "base":
|
||||
legacy = (kv.get("dbref") or "").strip()
|
||||
if legacy:
|
||||
return "dbref:%s" % legacy
|
||||
# 明文 dsn= 不是引用;container 模式不得用其充当绑定
|
||||
return ""
|
||||
|
||||
|
||||
def slot_port_of(kv, slot):
|
||||
return (kv.get("%s_port" % slot) or "").strip()
|
||||
|
||||
|
||||
def slot_role_ref_of(kv, slot):
|
||||
role = (kv.get("%s_role_ref" % slot) or "").strip()
|
||||
if not role:
|
||||
return ""
|
||||
if not os.path.isabs(role):
|
||||
die("%s_role_ref 必须是绝对路径(角色秘密文件引用):%s" % (slot, role), 2)
|
||||
return os.path.normpath(role)
|
||||
|
||||
|
||||
def validate_storage_kv(kv, require_complete=None):
|
||||
"""校验存储绑定形态。container 模式缺任一必需绑定即拒绝(不静默默认/建空库)。"""
|
||||
mode = storage_mode_of(kv)
|
||||
if require_complete is None:
|
||||
require_complete = mode == "container"
|
||||
env_class = (kv.get("env_class") or "").strip()
|
||||
if env_class and env_class not in ENV_CLASSES:
|
||||
die("env_class 仅允许 prod|test|verify,收到:%s" % env_class, 2)
|
||||
files_root = files_root_of(kv) # 相对路径在此失败
|
||||
compose = (kv.get("compose_project") or "").strip()
|
||||
if compose and (("/" in compose) or (".." in compose) or (" " in compose)):
|
||||
die("compose_project 非法(禁止路径分隔/空格):%s" % compose, 2)
|
||||
for slot in DB_SLOTS:
|
||||
port = slot_port_of(kv, slot)
|
||||
if port and not port.isdigit():
|
||||
die("%s_port 必须是正整端口号,收到:%s" % (slot, port), 2)
|
||||
slot_ref_of(kv, slot) # 冲突/相对路径检查
|
||||
slot_role_ref_of(kv, slot)
|
||||
if not require_complete:
|
||||
return mode
|
||||
# container 完整合同
|
||||
if mode != "container":
|
||||
die("完整存储合同要求 storage_mode=container", 2)
|
||||
if env_class not in ENV_CLASSES:
|
||||
die("container 模式缺少必需绑定 env_class(prod|test|verify)", 3)
|
||||
if not files_root:
|
||||
die("container 模式缺少必需绑定 files_root(绝对路径)", 3)
|
||||
if not compose:
|
||||
die("container 模式缺少必需绑定 compose_project", 3)
|
||||
for slot in DB_SLOTS:
|
||||
ref = slot_ref_of(kv, slot)
|
||||
port = slot_port_of(kv, slot)
|
||||
role = slot_role_ref_of(kv, slot)
|
||||
if not ref:
|
||||
die("container 模式缺少必需绑定 %s 库(%s_dbref 或 %s_dsn_ref)"
|
||||
% (slot, slot, slot), 3)
|
||||
if not port:
|
||||
die("container 模式缺少必需绑定 %s_port" % slot, 3)
|
||||
if not role:
|
||||
die("container 模式缺少必需绑定 %s_role_ref(角色秘密文件路径)" % slot, 3)
|
||||
return mode
|
||||
|
||||
|
||||
def storage_anchor_of(kv):
|
||||
"""从 kv 计算冻结的存储身份锚点(写入 identity;legacy 为空串)。"""
|
||||
mode = storage_mode_of(kv)
|
||||
if mode == "legacy" and not any(kv.get(k) for k in STORAGE_KEYS if k != "storage_mode"):
|
||||
return {
|
||||
"storage_mode": "legacy",
|
||||
"env_class": "",
|
||||
"files_root": "",
|
||||
"compose_project": "",
|
||||
"base_db_ref": "",
|
||||
"base_port": "",
|
||||
"rdm_db_ref": "",
|
||||
"rdm_port": "",
|
||||
"kb_db_ref": "",
|
||||
"kb_port": "",
|
||||
}
|
||||
# 非完整 container 时仍记录已显式写入的锚点(供漂移检测);完整校验另走 validate
|
||||
if mode == "container":
|
||||
validate_storage_kv(kv, require_complete=True)
|
||||
else:
|
||||
validate_storage_kv(kv, require_complete=False)
|
||||
return {
|
||||
"storage_mode": mode,
|
||||
"env_class": (kv.get("env_class") or "").strip(),
|
||||
"files_root": files_root_of(kv),
|
||||
"compose_project": (kv.get("compose_project") or "").strip(),
|
||||
"base_db_ref": slot_ref_of(kv, "base"),
|
||||
"base_port": slot_port_of(kv, "base"),
|
||||
"rdm_db_ref": slot_ref_of(kv, "rdm"),
|
||||
"rdm_port": slot_port_of(kv, "rdm"),
|
||||
"kb_db_ref": slot_ref_of(kv, "kb"),
|
||||
"kb_port": slot_port_of(kv, "kb"),
|
||||
}
|
||||
|
||||
|
||||
def preserve_storage_keys(new_kv, prev_kv):
|
||||
"""bulk-set/生命周期写回时保留未出现在新写集中的存储绑定键。"""
|
||||
out = dict(new_kv)
|
||||
for k in STORAGE_KEYS:
|
||||
if k not in out and k in prev_kv:
|
||||
out[k] = prev_kv[k]
|
||||
return out
|
||||
|
||||
|
||||
def marker_path(data_root):
|
||||
return os.path.join(data_root, MARKER_REL)
|
||||
|
||||
@@ -301,18 +140,13 @@ def read_marker(data_root):
|
||||
return {"schema": -1}
|
||||
|
||||
|
||||
def reanchor(env_dir, m, write=True):
|
||||
"""按显式持久化写入重算身份锚点;数据根变化时在新根补写 marker(目录存在才写)。
|
||||
|
||||
write=False 仅预览身份(冲突扫描用),不落 marker。
|
||||
"""
|
||||
def reanchor(env_dir, m):
|
||||
"""按显式持久化写入重算身份锚点;数据根变化时在新根补写 marker(目录存在才写)。"""
|
||||
ident = dict(m["identity"])
|
||||
ident["data_root"] = data_root_of(m["kv"], env_dir)
|
||||
ident["db_mode"], ident["db_ref"] = db_anchor_of(m["kv"])
|
||||
for k, v in storage_anchor_of(m["kv"]).items():
|
||||
ident[k] = v
|
||||
if ident != m["identity"]:
|
||||
if write and os.path.isdir(ident["data_root"]):
|
||||
if os.path.isdir(ident["data_root"]):
|
||||
write_marker(ident["data_root"], m["instance_id"])
|
||||
m["identity"] = ident
|
||||
return m
|
||||
@@ -326,9 +160,6 @@ def build(env_dir, kv, allow_create):
|
||||
if not allow_create:
|
||||
die("数据根不存在且非显式 init,拒绝建立身份(防新建空系统掩盖丢数据):%s" % data_root, 3)
|
||||
os.makedirs(data_root, exist_ok=True)
|
||||
# 旧配置升级:若已带 storage 键则校验;纯 legacy 不强制 container 合同
|
||||
if storage_mode_of(kv) == "container" or any(kv.get(k) for k in STORAGE_KEYS):
|
||||
validate_storage_kv(kv, require_complete=(storage_mode_of(kv) == "container"))
|
||||
existing = read_marker(data_root)
|
||||
if existing is not None:
|
||||
if existing.get("instance_id") != kv.get("instance_id"):
|
||||
@@ -339,14 +170,6 @@ def build(env_dir, kv, allow_create):
|
||||
instance_id = kv.get("instance_id") or "svc-%s-%s" % (name, secrets.token_hex(6))
|
||||
write_marker(data_root, instance_id)
|
||||
db_mode, db_ref = db_anchor_of(kv)
|
||||
storage = storage_anchor_of(kv)
|
||||
identity = {
|
||||
"data_root": data_root,
|
||||
"db_mode": db_mode,
|
||||
"db_ref": db_ref,
|
||||
"db_marker": "fresh",
|
||||
}
|
||||
identity.update(storage)
|
||||
m = {
|
||||
"schema": SCHEMA,
|
||||
"revision": 1,
|
||||
@@ -354,114 +177,18 @@ def build(env_dir, kv, allow_create):
|
||||
"service_group": name,
|
||||
"created_at": now_iso(),
|
||||
"desired": "running" if kv.get("autostart") == "1" else "stopped",
|
||||
"identity": identity,
|
||||
"identity": {
|
||||
"data_root": data_root,
|
||||
"db_mode": db_mode,
|
||||
"db_ref": db_ref,
|
||||
"db_marker": "fresh",
|
||||
},
|
||||
"kv": {k: v for k, v in kv.items() if k != "autostart"},
|
||||
}
|
||||
save(env_dir, m)
|
||||
return m
|
||||
|
||||
|
||||
def runtime_dir_of(env_dir):
|
||||
return os.path.dirname(os.path.abspath(env_dir))
|
||||
|
||||
|
||||
def iter_sibling_manifests(env_dir, runtime_dir=None):
|
||||
rt = os.path.abspath(runtime_dir) if runtime_dir else runtime_dir_of(env_dir)
|
||||
self_abs = os.path.abspath(env_dir)
|
||||
if not os.path.isdir(rt):
|
||||
return
|
||||
for name in sorted(os.listdir(rt)):
|
||||
d = os.path.join(rt, name)
|
||||
if not os.path.isdir(d) or os.path.abspath(d) == self_abs:
|
||||
continue
|
||||
path = os.path.join(d, MANIFEST)
|
||||
if not os.path.isfile(path):
|
||||
# 兼容尚未 migrate 的 sibling:读 svc.env 中的存储键
|
||||
env_path = os.path.join(d, DERIVED)
|
||||
if not os.path.isfile(env_path):
|
||||
continue
|
||||
kv = {}
|
||||
with open(env_path, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.rstrip("\n")
|
||||
if line and "=" in line and not line.startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
kv[k] = v
|
||||
yield name, {
|
||||
"instance_id": kv.get("instance_id", ""),
|
||||
"identity": storage_anchor_of(kv) if any(kv.get(k) for k in STORAGE_KEYS) else {},
|
||||
"kv": kv,
|
||||
}
|
||||
continue
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
yield name, json.load(f)
|
||||
except Exception: # noqa: BLE001 —— 损坏 sibling 不阻断本实例冲突扫描语义以外的操作
|
||||
continue
|
||||
|
||||
|
||||
def binding_ports_from(m_or_kv, is_manifest=True):
|
||||
if is_manifest:
|
||||
kv = m_or_kv.get("kv") or {}
|
||||
ident = m_or_kv.get("identity") or {}
|
||||
else:
|
||||
kv = m_or_kv
|
||||
ident = {}
|
||||
ports = set()
|
||||
for slot in DB_SLOTS:
|
||||
p = (kv.get("%s_port" % slot) or "").strip() or (ident.get("%s_port" % slot) or "").strip()
|
||||
if p:
|
||||
ports.add(p)
|
||||
return ports
|
||||
|
||||
|
||||
def compose_of(m_or_kv, is_manifest=True):
|
||||
if is_manifest:
|
||||
kv = m_or_kv.get("kv") or {}
|
||||
ident = m_or_kv.get("identity") or {}
|
||||
return (kv.get("compose_project") or ident.get("compose_project") or "").strip()
|
||||
return (m_or_kv.get("compose_project") or "").strip()
|
||||
|
||||
|
||||
def check_conflicts(env_dir, m, runtime_dir=None):
|
||||
"""同一 runtime 下 Compose 项目名或存储端口冲突 → 修改前失败。"""
|
||||
kv = m["kv"]
|
||||
ident = m.get("identity") or {}
|
||||
compose = (kv.get("compose_project") or ident.get("compose_project") or "").strip()
|
||||
ports = binding_ports_from(m, is_manifest=True)
|
||||
if not compose and not ports:
|
||||
return
|
||||
self_name = os.path.basename(os.path.abspath(env_dir))
|
||||
for name, other in iter_sibling_manifests(env_dir, runtime_dir):
|
||||
other_compose = compose_of(other, is_manifest=True)
|
||||
if compose and other_compose and compose == other_compose:
|
||||
die("Compose 项目名冲突:compose_project=%s 已被实例 %s 占用(修改前拒绝)"
|
||||
% (compose, name), 7)
|
||||
other_ports = binding_ports_from(other, is_manifest=True)
|
||||
clash = ports & other_ports
|
||||
if clash:
|
||||
die("存储端口冲突:port=%s 与实例 %s 冲突(修改前拒绝)"
|
||||
% (",".join(sorted(clash)), name), 7)
|
||||
# 亦与 sibling 的应用监听 addr 端口冲突(避免与已登记服务抢口)
|
||||
if ports:
|
||||
rt = os.path.abspath(runtime_dir) if runtime_dir else runtime_dir_of(env_dir)
|
||||
for name in sorted(os.listdir(rt)) if os.path.isdir(rt) else []:
|
||||
if name == self_name:
|
||||
continue
|
||||
env_path = os.path.join(rt, name, DERIVED)
|
||||
if not os.path.isfile(env_path):
|
||||
continue
|
||||
with open(env_path, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
if line.startswith("addr="):
|
||||
addr = line[5:].strip()
|
||||
ap = addr.rsplit(":", 1)[-1] if addr else ""
|
||||
if ap in ports:
|
||||
die("存储端口冲突:port=%s 与服务 %s 的 addr=%s 冲突(修改前拒绝)"
|
||||
% (ap, name, addr), 7)
|
||||
break
|
||||
|
||||
|
||||
def cmd_migrate(env_dir):
|
||||
if load(env_dir) is not None:
|
||||
print("manifest 已存在,幂等跳过")
|
||||
@@ -494,41 +221,32 @@ def cmd_status(env_dir):
|
||||
return 0
|
||||
|
||||
|
||||
def mutate(env_dir, fn, runtime_dir=None):
|
||||
def mutate(env_dir, fn):
|
||||
m = load(env_dir)
|
||||
if m is None:
|
||||
die("manifest 不存在:先 migrate/init", 3)
|
||||
before = json.dumps(m["identity"], sort_keys=True)
|
||||
fn(m)
|
||||
# 存储绑定变更:先形态校验,再冲突扫描,最后重锚——冲突时不落盘
|
||||
if any(k in m["kv"] for k in STORAGE_KEYS) or storage_mode_of(m["kv"]) == "container":
|
||||
validate_storage_kv(m["kv"], require_complete=(storage_mode_of(m["kv"]) == "container"))
|
||||
# 预览重锚后的 identity 以做冲突检测(不落 marker / 不写盘)
|
||||
preview = json.loads(json.dumps(m))
|
||||
preview = reanchor(env_dir, preview, write=False)
|
||||
check_conflicts(env_dir, preview, runtime_dir=runtime_dir)
|
||||
m["revision"] = int(m["revision"]) + 1
|
||||
m = reanchor(env_dir, m, write=True)
|
||||
m = reanchor(env_dir, m)
|
||||
save(env_dir, m)
|
||||
if before != json.dumps(m["identity"], sort_keys=True):
|
||||
print("[manifest] 身份锚点已随显式持久化写入更新(data=%s db=%s/%s storage=%s files=%s)"
|
||||
% (m["identity"]["data_root"], m["identity"]["db_mode"], m["identity"]["db_ref"],
|
||||
m["identity"].get("storage_mode", "legacy"),
|
||||
m["identity"].get("files_root") or "-"))
|
||||
print("[manifest] 身份锚点已随显式持久化写入更新(data=%s db=%s/%s)"
|
||||
% (m["identity"]["data_root"], m["identity"]["db_mode"], m["identity"]["db_ref"]))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_set(env_dir, key, value, runtime_dir=None):
|
||||
return mutate(env_dir, lambda m: m["kv"].__setitem__(key, value), runtime_dir=runtime_dir)
|
||||
def cmd_set(env_dir, key, value):
|
||||
return mutate(env_dir, lambda m: m["kv"].__setitem__(key, value))
|
||||
|
||||
|
||||
def cmd_del(env_dir, key, runtime_dir=None):
|
||||
def cmd_del(env_dir, key):
|
||||
def drop(m):
|
||||
m["kv"].pop(key, None)
|
||||
return mutate(env_dir, drop, runtime_dir=runtime_dir)
|
||||
return mutate(env_dir, drop)
|
||||
|
||||
|
||||
def cmd_bulk_set(env_dir, runtime_dir=None):
|
||||
def cmd_bulk_set(env_dir):
|
||||
kv = {}
|
||||
for line in sys.stdin.read().splitlines():
|
||||
if line and "=" in line and not line.startswith("#"):
|
||||
@@ -537,8 +255,8 @@ def cmd_bulk_set(env_dir, runtime_dir=None):
|
||||
kv.pop("autostart", None) # desired 独占期望态
|
||||
|
||||
def replace(m):
|
||||
m["kv"] = preserve_storage_keys(kv, m["kv"])
|
||||
return mutate(env_dir, replace, runtime_dir=runtime_dir)
|
||||
m["kv"] = kv
|
||||
return mutate(env_dir, replace)
|
||||
|
||||
|
||||
def cmd_set_desired(env_dir, state):
|
||||
@@ -547,20 +265,7 @@ def cmd_set_desired(env_dir, state):
|
||||
return mutate(env_dir, lambda m: m.__setitem__("desired", state))
|
||||
|
||||
|
||||
def cmd_check_conflicts(env_dir, runtime_dir=None):
|
||||
m = load(env_dir)
|
||||
if m is None:
|
||||
die("manifest 不存在:先 migrate/init", 3)
|
||||
check_conflicts(env_dir, m, runtime_dir=runtime_dir)
|
||||
print("绑定冲突检查通过 instance_id=%s compose=%s"
|
||||
% (m["instance_id"], m["identity"].get("compose_project") or "-"))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_verify(env_dir, data_root=None, db_mode=None, db_ref=None,
|
||||
files_root=None, env_class=None, compose_project=None,
|
||||
base_db_ref=None, rdm_db_ref=None, kb_db_ref=None,
|
||||
require_storage=False):
|
||||
def cmd_verify(env_dir, data_root=None, db_mode=None, db_ref=None):
|
||||
m = load(env_dir)
|
||||
if m is None:
|
||||
die("manifest 缺失:本实例已进入 manifest 治理,缺失即事件(先 init/人工处置,禁止静默重建)", 4)
|
||||
@@ -579,51 +284,14 @@ def cmd_verify(env_dir, data_root=None, db_mode=None, db_ref=None,
|
||||
die("数据库模式漂移:生效 %s ≠ 锚定 %s" % (db_mode, ident["db_mode"]), 6)
|
||||
if db_mode and ident["db_mode"] and db_ref is not None and (db_ref or "") != ident["db_ref"]:
|
||||
die("数据库绑定漂移:生效 %s ≠ 锚定 %s(重绑须经显式登记命令)" % (db_ref or "<空>", ident["db_ref"]), 6)
|
||||
|
||||
storage_mode = ident.get("storage_mode") or "legacy"
|
||||
if storage_mode == "container" or require_storage:
|
||||
# container:缺绑定/错根/错库一律拒绝,不回退默认、不建空库
|
||||
validate_storage_kv(m["kv"], require_complete=True)
|
||||
if files_root is not None:
|
||||
got = os.path.normpath(files_root) if os.path.isabs(files_root) else files_root
|
||||
if not os.path.isabs(files_root):
|
||||
die("文件根拒绝相对路径(不相对 checkout 解析):%s" % files_root, 6)
|
||||
if got != (ident.get("files_root") or ""):
|
||||
die("文件根漂移:生效 %s ≠ 锚定 %s" % (got, ident.get("files_root")), 6)
|
||||
if env_class is not None and env_class != (ident.get("env_class") or ""):
|
||||
die("环境身份漂移:生效 %s ≠ 锚定 %s" % (env_class, ident.get("env_class")), 6)
|
||||
if compose_project is not None and compose_project != (ident.get("compose_project") or ""):
|
||||
die("Compose 项目漂移:生效 %s ≠ 锚定 %s" % (compose_project, ident.get("compose_project")), 6)
|
||||
for slot, got in (("base", base_db_ref), ("rdm", rdm_db_ref), ("kb", kb_db_ref)):
|
||||
if got is None:
|
||||
continue
|
||||
want = ident.get("%s_db_ref" % slot) or ""
|
||||
if (got or "") != want:
|
||||
die("%s 库绑定漂移:生效 %s ≠ 锚定 %s(错库拒绝,不静默新建)"
|
||||
% (slot, got or "<空>", want or "<空>"), 6)
|
||||
|
||||
print("身份校验通过 instance_id=%s data=%s db=%s/%s storage=%s env=%s files=%s"
|
||||
% (m["instance_id"], ident["data_root"], ident["db_mode"] or "-", ident["db_ref"] or "-",
|
||||
storage_mode, ident.get("env_class") or "-", ident.get("files_root") or "-"))
|
||||
print("身份校验通过 instance_id=%s data=%s db=%s/%s"
|
||||
% (m["instance_id"], ident["data_root"], ident["db_mode"] or "-", ident["db_ref"] or "-"))
|
||||
return 0
|
||||
|
||||
|
||||
def _parse_runtime_flag(args):
|
||||
runtime_dir = None
|
||||
out = []
|
||||
it = iter(args)
|
||||
for a in it:
|
||||
if a == "--runtime":
|
||||
runtime_dir = next(it, None)
|
||||
else:
|
||||
out.append(a)
|
||||
return runtime_dir, out
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 3:
|
||||
die("用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|"
|
||||
"set-desired|verify|check-conflicts> [参数]", 2)
|
||||
die("用法: svc_manifest.py ENV_DIR <migrate|init|status|set|del|bulk-set|set-desired|verify> [参数]", 2)
|
||||
env_dir, cmd = argv[1], argv[2]
|
||||
args = argv[3:]
|
||||
if cmd == "migrate":
|
||||
@@ -632,27 +300,16 @@ def main(argv):
|
||||
return cmd_init(env_dir)
|
||||
if cmd == "status":
|
||||
return cmd_status(env_dir)
|
||||
if cmd == "set":
|
||||
runtime_dir, args = _parse_runtime_flag(args)
|
||||
if len(args) == 2:
|
||||
return cmd_set(env_dir, args[0], args[1], runtime_dir=runtime_dir)
|
||||
if cmd == "del":
|
||||
runtime_dir, args = _parse_runtime_flag(args)
|
||||
if len(args) == 1:
|
||||
return cmd_del(env_dir, args[0], runtime_dir=runtime_dir)
|
||||
if cmd == "set" and len(args) == 2:
|
||||
return cmd_set(env_dir, args[0], args[1])
|
||||
if cmd == "del" and len(args) == 1:
|
||||
return cmd_del(env_dir, args[0])
|
||||
if cmd == "bulk-set":
|
||||
runtime_dir, args = _parse_runtime_flag(args)
|
||||
return cmd_bulk_set(env_dir, runtime_dir=runtime_dir)
|
||||
return cmd_bulk_set(env_dir)
|
||||
if cmd == "set-desired" and len(args) == 1:
|
||||
return cmd_set_desired(env_dir, args[0])
|
||||
if cmd == "check-conflicts":
|
||||
runtime_dir, _ = _parse_runtime_flag(args)
|
||||
return cmd_check_conflicts(env_dir, runtime_dir=runtime_dir)
|
||||
if cmd == "verify":
|
||||
data_root = db_mode = db_ref = None
|
||||
files_root = env_class = compose_project = None
|
||||
base_db_ref = rdm_db_ref = kb_db_ref = None
|
||||
require_storage = False
|
||||
it = iter(args)
|
||||
for a in it:
|
||||
if a == "--data":
|
||||
@@ -661,27 +318,7 @@ def main(argv):
|
||||
db_mode = next(it, None)
|
||||
elif a == "--db-ref":
|
||||
db_ref = next(it, None)
|
||||
elif a == "--files-root":
|
||||
files_root = next(it, None)
|
||||
elif a == "--env-class":
|
||||
env_class = next(it, None)
|
||||
elif a == "--compose-project":
|
||||
compose_project = next(it, None)
|
||||
elif a == "--base-db-ref":
|
||||
base_db_ref = next(it, None)
|
||||
elif a == "--rdm-db-ref":
|
||||
rdm_db_ref = next(it, None)
|
||||
elif a == "--kb-db-ref":
|
||||
kb_db_ref = next(it, None)
|
||||
elif a == "--require-storage":
|
||||
require_storage = True
|
||||
return cmd_verify(
|
||||
env_dir, data_root, db_mode, db_ref,
|
||||
files_root=files_root, env_class=env_class,
|
||||
compose_project=compose_project,
|
||||
base_db_ref=base_db_ref, rdm_db_ref=rdm_db_ref, kb_db_ref=kb_db_ref,
|
||||
require_storage=require_storage,
|
||||
)
|
||||
return cmd_verify(env_dir, data_root, db_mode, db_ref)
|
||||
die("未知命令或参数不全:%s %s" % (cmd, " ".join(args)), 2)
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -813,7 +813,7 @@ card_merge_squash() { # $1=branch $2=ref → squash/rebase 落地没有 merge
|
||||
# 仅查最近 5 页(250 条):card_merge 的 merge 提交路径仍是主路径,此兜底面向新近任务卡。
|
||||
local token="${GITEA_TOKEN:-}" web_base url repo owner api payload pg found sha
|
||||
[ -n "$token" ] || return 1
|
||||
web_base="${TASK_CARD_WEB_BASE:-https://git-home.u1680832.nyat.app:30319}"
|
||||
web_base="${TASK_CARD_WEB_BASE:-https://gitea.naitangbaobao.cn}"
|
||||
url=$(git remote get-url origin 2>/dev/null) || return 1
|
||||
url="${url%.git}"
|
||||
repo=$(basename "$url"); owner=$(basename "$(dirname "$url")")
|
||||
@@ -951,7 +951,7 @@ cmd_close_self() {
|
||||
esac
|
||||
pr="$nums"
|
||||
fi
|
||||
web_base="${TASK_CARD_WEB_BASE:-https://git-home.u1680832.nyat.app:30319}"
|
||||
web_base="${TASK_CARD_WEB_BASE:-https://gitea.naitangbaobao.cn}"
|
||||
url=$(git remote get-url origin); url="${url%.git}"
|
||||
repo=$(basename "$url"); owner=$(basename "$(dirname "$url")")
|
||||
link="${web_base}/${owner}/${repo}/pulls/${pr}"
|
||||
@@ -981,7 +981,7 @@ cmd_close() {
|
||||
fetch_target
|
||||
# 默认值取远端通告的 Web 面(git push 输出的 PR 建议链接);可用 TASK_CARD_WEB_BASE 覆盖。
|
||||
# git-panel 前缀已失效(TLS 握手失败),默认值改为实测可用的 git-home(AGENTS.md 访问事实节)。
|
||||
local web_base="${TASK_CARD_WEB_BASE:-https://git-home.u1680832.nyat.app:30319}"
|
||||
local web_base="${TASK_CARD_WEB_BASE:-https://gitea.naitangbaobao.cn}"
|
||||
local url owner repo
|
||||
url=$(git remote get-url origin); url="${url%.git}"
|
||||
repo=$(basename "$url"); owner=$(basename "$(dirname "$url")")
|
||||
|
||||
@@ -29,7 +29,7 @@ import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
SCHEMA = "agent-system/task-triage/v1"
|
||||
DEFAULT_API = "https://git-home.u1680832.nyat.app:30319/api/v1"
|
||||
DEFAULT_API = "https://gitea.naitangbaobao.cn/api/v1"
|
||||
DEFAULT_TARGET = "main"
|
||||
DEFAULT_MAX_AGE = 14400
|
||||
PAGE_LIMIT = 50
|
||||
|
||||
@@ -11,8 +11,8 @@ from urllib.parse import urlsplit
|
||||
|
||||
|
||||
WORKFLOW = Path(__file__).resolve().parents[1] / ".gitea/workflows/ci.yaml"
|
||||
SERVER = "https://git-home.u1680832.nyat.app:30319"
|
||||
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
|
||||
SERVER = "https://gitea.naitangbaobao.cn"
|
||||
SSH = "ssh://git@gitea.naitangbaobao.cn:52222/weichen/"
|
||||
ROOT_TOKEN = "synthetic-root-token-not-a-real-secret"
|
||||
SUB_TOKEN = "synthetic-component-token-not-a-real-secret"
|
||||
|
||||
|
||||
+15
-15
@@ -256,7 +256,7 @@ class FactsVerifyTests(unittest.TestCase):
|
||||
self.assert_status(result.stdout, "F2", "PASS")
|
||||
requests = self.stub_events("http")
|
||||
self.assertEqual(len(requests), 1, requests)
|
||||
self.assertEqual(requests[0]["url"], "https://git-home.u1680832.nyat.app:30319/api/v1/user")
|
||||
self.assertEqual(requests[0]["url"], "https://gitea.naitangbaobao.cn/api/v1/user")
|
||||
self.assertEqual(requests[0]["authorization"], "token " + HOME_TOKEN)
|
||||
self.assertEqual(self.dates(), {"F2": self.today, "F8": self.today, "F9": OLD_DATE})
|
||||
self.assertTrue(receipt.exists(), result.stderr)
|
||||
@@ -285,7 +285,7 @@ class FactsVerifyTests(unittest.TestCase):
|
||||
self.assert_status(result.stdout, "F2", "FAIL")
|
||||
self.assertEqual(self.stub_events("http"), [], "no request without a valid HOME token")
|
||||
self.assertEqual(self.dates()["F2"], OLD_DATE)
|
||||
direct = self.run_script("api-token", "https://git-home.u1680832.nyat.app:30319/api/v1/user")
|
||||
direct = self.run_script("api-token", "https://gitea.naitangbaobao.cn/api/v1/user")
|
||||
self.assertNotEqual(direct.returncode, 0)
|
||||
self.assertIn("ACCESS_TOKEN", direct.stderr)
|
||||
self.assert_no_secrets(result.stdout, result.stderr, direct.stdout, direct.stderr,
|
||||
@@ -299,7 +299,7 @@ class FactsVerifyTests(unittest.TestCase):
|
||||
result = self.run_script("--update", http=http)
|
||||
self.assert_status(result.stdout, "F2", "FAIL")
|
||||
self.assertEqual(self.dates(), {"F2": OLD_DATE, "F8": self.today, "F9": OLD_DATE})
|
||||
direct = self.run_script("api-token", "https://git-home.u1680832.nyat.app:30319/api/v1/user",
|
||||
direct = self.run_script("api-token", "https://gitea.naitangbaobao.cn/api/v1/user",
|
||||
http=http)
|
||||
self.assertNotEqual(direct.returncode, 0)
|
||||
self.assertNotIn("Authorization", direct.stderr)
|
||||
@@ -307,17 +307,17 @@ class FactsVerifyTests(unittest.TestCase):
|
||||
|
||||
def test_f2_rejects_unregistered_destination_before_reading_credentials(self):
|
||||
bad_urls = [
|
||||
"http://git-home.u1680832.nyat.app:30319/api/v1/user",
|
||||
"http://gitea.naitangbaobao.cn/api/v1/user",
|
||||
"https://evil.invalid/api/v1/user",
|
||||
"https://git-home.u1680832.nyat.app:30320/api/v1/user",
|
||||
"https://git-home.u1680832.nyat.app/api/v1/user",
|
||||
"https://user:pw@git-home.u1680832.nyat.app:30319/api/v1/user",
|
||||
"https://git-home.u1680832.nyat.app:30319@evil.invalid/api/v1/user",
|
||||
"https://git-home.u1680832.nyat.app:30319/api/v1/user#frag",
|
||||
"https://git-home.u1680832.nyat.app:30319/api/v1/user?next=https://evil.invalid",
|
||||
"https://git-home.u1680832.nyat.app:30319/api/v1/users",
|
||||
"https://git-home.u1680832.nyat.app:30319/api/v1/user/../admin",
|
||||
"https://git-home.u1680832.nyat.app:30319/api/v1/user ",
|
||||
"https://gitea.naitangbaobao.cn:30320/api/v1/user",
|
||||
"https://gitea.naitangbaobao.cn:443/api/v1/user",
|
||||
"https://user:pw@gitea.naitangbaobao.cn/api/v1/user",
|
||||
"https://gitea.naitangbaobao.cn@evil.invalid/api/v1/user",
|
||||
"https://gitea.naitangbaobao.cn/api/v1/user#frag",
|
||||
"https://gitea.naitangbaobao.cn/api/v1/user?next=https://evil.invalid",
|
||||
"https://gitea.naitangbaobao.cn/api/v1/users",
|
||||
"https://gitea.naitangbaobao.cn/api/v1/user/../admin",
|
||||
"https://gitea.naitangbaobao.cn/api/v1/user ",
|
||||
]
|
||||
for secrets_present in (True, False):
|
||||
if not secrets_present:
|
||||
@@ -337,12 +337,12 @@ class FactsVerifyTests(unittest.TestCase):
|
||||
for code in ("301", "302", "303", "307", "308"):
|
||||
with self.subTest(code):
|
||||
self.stub_log.unlink(missing_ok=True)
|
||||
direct = self.run_script("api-token", "https://git-home.u1680832.nyat.app:30319/api/v1/user",
|
||||
direct = self.run_script("api-token", "https://gitea.naitangbaobao.cn/api/v1/user",
|
||||
http=code)
|
||||
self.assertEqual(direct.returncode, 5, direct.stderr)
|
||||
requests = self.stub_events("http")
|
||||
self.assertEqual([r["url"] for r in requests],
|
||||
["https://git-home.u1680832.nyat.app:30319/api/v1/user"], "no follow-up request")
|
||||
["https://gitea.naitangbaobao.cn/api/v1/user"], "no follow-up request")
|
||||
self.assertNotIn("redirect-target", direct.stdout + direct.stderr)
|
||||
self.assert_no_secrets(direct.stdout, direct.stderr)
|
||||
|
||||
|
||||
+38
-25
@@ -296,12 +296,14 @@ class SvcDbRecoveryTests(unittest.TestCase):
|
||||
# ---- 1. 存量 exec= DSN:连续启动两次均恢复,进程参数无秘密 ----
|
||||
|
||||
def test_legacy_exec_dsn_survives_consecutive_restarts(self):
|
||||
self.register_pg("pg1", 18890, password="legacy-pw-1")
|
||||
dsn = "postgresql://agent:legacy-pw-1@127.0.0.1:18890/agent"
|
||||
pg_port = free_test_port()
|
||||
self.register_pg("pg1", pg_port, password="legacy-pw-1")
|
||||
dsn = "postgresql://agent:legacy-pw-1@127.0.0.1:%d/agent" % pg_port
|
||||
d = self.runtime("dev")
|
||||
self.seed_agent("dev", 18791,
|
||||
"--addr 127.0.0.1:18791 --data %s --plugins %s --auth auto --dsn %s"
|
||||
% (d / "data", d / "plugins", dsn))
|
||||
port = free_test_port()
|
||||
self.seed_agent("dev", port,
|
||||
"--addr 127.0.0.1:%d --data %s --plugins %s --auth auto --dsn %s"
|
||||
% (port, d / "data", d / "plugins", dsn))
|
||||
for _ in range(2):
|
||||
self.clear_serve_record()
|
||||
self.svc("dev", "restart")
|
||||
@@ -322,12 +324,14 @@ class SvcDbRecoveryTests(unittest.TestCase):
|
||||
# ---- 2. 仅 dbref:裸 restart 与重新部署均保留绑定 ----
|
||||
|
||||
def test_dbref_binding_survives_bare_restart_and_redeploy(self):
|
||||
self.register_pg("pg1", 18891, password="bind-pw-2")
|
||||
dsn = "postgresql://agent:bind-pw-2@127.0.0.1:18891/agent"
|
||||
pg_port = free_test_port()
|
||||
self.register_pg("pg1", pg_port, password="bind-pw-2")
|
||||
dsn = "postgresql://agent:bind-pw-2@127.0.0.1:%d/agent" % pg_port
|
||||
d = self.runtime("dev")
|
||||
self.seed_agent("dev", 18792,
|
||||
"--addr 127.0.0.1:18792 --data %s --plugins %s --auth auto"
|
||||
% (d / "data", d / "plugins"),
|
||||
port = free_test_port()
|
||||
self.seed_agent("dev", port,
|
||||
"--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
|
||||
% (port, d / "data", d / "plugins"),
|
||||
extra_rows=[("dbref", "pg1/agent"), ("db", "pg")])
|
||||
self.clear_serve_record()
|
||||
self.svc("dev", "restart")
|
||||
@@ -413,9 +417,10 @@ class SvcDbRecoveryTests(unittest.TestCase):
|
||||
|
||||
def test_refusals_keep_registration_without_sqlite_fallback(self):
|
||||
d = self.runtime("a1")
|
||||
self.seed_agent("a1", 18796,
|
||||
"--addr 127.0.0.1:18796 --data %s --plugins %s --auth auto"
|
||||
% (d / "data", d / "plugins"),
|
||||
port = free_test_port()
|
||||
self.seed_agent("a1", port,
|
||||
"--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
|
||||
% (port, d / "data", d / "plugins"),
|
||||
extra_rows=[("dbref", "pgmissing/agent"), ("db", "pg")])
|
||||
self.clear_serve_record()
|
||||
result = self.svc("a1", "restart", check=False)
|
||||
@@ -425,33 +430,41 @@ class SvcDbRecoveryTests(unittest.TestCase):
|
||||
self.assertEqual(self.kv("a1", "dbref"), "pgmissing/agent") # 可恢复配置保留
|
||||
|
||||
# 实例登记不全(缺 password)→ 拒启。
|
||||
self.register_pg("pginc", 18895, complete=False)
|
||||
self.register_pg("pginc", free_test_port(), complete=False)
|
||||
d = self.runtime("a2")
|
||||
self.seed_agent("a2", 18797,
|
||||
"--addr 127.0.0.1:18797 --data %s --plugins %s --auth auto"
|
||||
% (d / "data", d / "plugins"),
|
||||
port = free_test_port()
|
||||
self.seed_agent("a2", port,
|
||||
"--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
|
||||
% (port, d / "data", d / "plugins"),
|
||||
extra_rows=[("dbref", "pginc/agent"), ("db", "pg")])
|
||||
result = self.svc("a2", "restart", check=False)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("拒绝", result.stdout)
|
||||
|
||||
# dbref 与 dsn= 指向不同库 → 配置冲突拒启。
|
||||
self.register_pg("pg1", 18896, password="clash-pw")
|
||||
pg_port = free_test_port()
|
||||
self.register_pg("pg1", pg_port, password="clash-pw")
|
||||
other_pg_port = free_test_port()
|
||||
while other_pg_port == pg_port:
|
||||
other_pg_port = free_test_port()
|
||||
d = self.runtime("a3")
|
||||
self.seed_agent("a3", 18798,
|
||||
"--addr 127.0.0.1:18798 --data %s --plugins %s --auth auto"
|
||||
% (d / "data", d / "plugins"),
|
||||
port = free_test_port()
|
||||
self.seed_agent("a3", port,
|
||||
"--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
|
||||
% (port, d / "data", d / "plugins"),
|
||||
extra_rows=[("dbref", "pg1/agent"), ("db", "pg"),
|
||||
("dsn", "postgresql://u:p@127.0.0.1:18999/otherdb")])
|
||||
("dsn", "postgresql://u:p@127.0.0.1:%d/otherdb"
|
||||
% other_pg_port)])
|
||||
result = self.svc("a3", "restart", check=False)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("拒绝", result.stdout)
|
||||
|
||||
# db=pg 但无可恢复 DSN → 拒启。
|
||||
d = self.runtime("a4")
|
||||
self.seed_agent("a4", 18799,
|
||||
"--addr 127.0.0.1:18799 --data %s --plugins %s --auth auto"
|
||||
% (d / "data", d / "plugins"),
|
||||
port = free_test_port()
|
||||
self.seed_agent("a4", port,
|
||||
"--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
|
||||
% (port, d / "data", d / "plugins"),
|
||||
extra_rows=[("db", "pg")])
|
||||
result = self.svc("a4", "restart", check=False)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _port_alloc import free_test_port # noqa: E402
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1]
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"tsi", SOURCE / "tests" / "test_svc_identity.py")
|
||||
@@ -46,7 +50,7 @@ class DeployTxnTests(unittest.TestCase):
|
||||
new.chmod(0o755)
|
||||
fx.stub.write_text(old.read_text())
|
||||
fx.stub.chmod(0o755)
|
||||
fx.seed_agent("txn", 9471)
|
||||
fx.seed_agent("txn", free_test_port())
|
||||
fx.start_ok("txn")
|
||||
pid = fx.pid_of("txn")
|
||||
script = fx.root / "scripts" / "svc.sh"
|
||||
@@ -68,7 +72,7 @@ class DeployTxnTests(unittest.TestCase):
|
||||
|
||||
def test_forward_only_requires_confirm_and_backup(self):
|
||||
fx = self.fx
|
||||
fx.seed_agent("fwd", 9472)
|
||||
fx.seed_agent("fwd", free_test_port())
|
||||
fx.start_ok("fwd")
|
||||
iid = json.loads((fx.runtime("fwd") / "manifest.json").read_text())["instance_id"]
|
||||
base = fx.stub.read_text()
|
||||
|
||||
+36
-209
@@ -12,10 +12,14 @@ import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _port_alloc import free_test_port # noqa: E402
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1]
|
||||
SVC_SCRIPT = SOURCE / "scripts" / "svc.sh"
|
||||
|
||||
@@ -56,7 +60,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T1 连续裸启停:同一身份与数据根 ----
|
||||
|
||||
def test_T1_bare_restart_stable_identity(self):
|
||||
self.seed_agent("id1", 9431)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id1", port)
|
||||
self.start_ok("id1")
|
||||
m1 = self.manifest("id1")
|
||||
self.assertIsNotNone(m1, "start 应完成 manifest 迁移")
|
||||
@@ -79,7 +84,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T2 只改端口:数据/DB 锚点不动 ----
|
||||
|
||||
def test_T2_port_change_keeps_anchors(self):
|
||||
self.seed_agent("id2", 9432)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id2", port)
|
||||
self.start_ok("id2")
|
||||
anchor = self.manifest("id2")["identity"]
|
||||
self.svc("id2", "stop")
|
||||
@@ -95,7 +101,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T3 数据根漂移:拒绝启动,不新建空系统 ----
|
||||
|
||||
def test_T3_data_drift_rejected(self):
|
||||
self.seed_agent("id3", 9434)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id3", port)
|
||||
self.start_ok("id3")
|
||||
self.svc("id3", "stop")
|
||||
other = self.root / "other-data"
|
||||
@@ -111,7 +118,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T4 迁移窗口与损坏:不猜测 ----
|
||||
|
||||
def test_T4a_leftover_new_file_ignored(self):
|
||||
self.seed_agent("id4", 9435)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id4", port)
|
||||
# 模拟 rename 前崩溃残留:.new 半成品必须被忽略/覆盖,不影响迁移结果
|
||||
(self.runtime("id4") / "manifest.json.new").write_text("{ truncated")
|
||||
self.start_ok("id4")
|
||||
@@ -121,7 +129,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
self.svc("id4", "stop")
|
||||
|
||||
def test_T4b_corrupt_manifest_blocks(self):
|
||||
self.seed_agent("id5", 9436)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id5", port)
|
||||
self.start_ok("id5")
|
||||
self.svc("id5", "stop")
|
||||
(self.runtime("id5") / "manifest.json").write_text("{ not json")
|
||||
@@ -133,7 +142,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T5 marker/manifest 事件:缺一即阻塞,不自动新建 ----
|
||||
|
||||
def test_T5a_missing_marker_blocks(self):
|
||||
self.seed_agent("id6", 9437)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id6", port)
|
||||
self.start_ok("id6")
|
||||
self.svc("id6", "stop")
|
||||
m = self.manifest("id6")
|
||||
@@ -145,7 +155,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
self.assertFalse(self.running("id6"))
|
||||
|
||||
def test_T5b_foreign_marker_blocks(self):
|
||||
self.seed_agent("id7", 9438)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id7", port)
|
||||
self.start_ok("id7")
|
||||
self.svc("id7", "stop")
|
||||
m = self.manifest("id7")
|
||||
@@ -158,7 +169,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
self.assertFalse(self.running("id7"))
|
||||
|
||||
def test_T5c_lost_manifest_blocks_not_rebuild(self):
|
||||
self.seed_agent("id8", 9439)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id8", port)
|
||||
self.start_ok("id8")
|
||||
m0 = self.manifest("id8")
|
||||
iid = m0["instance_id"]
|
||||
@@ -178,7 +190,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T6 cwd 无关:从别处调用身份不变 ----
|
||||
|
||||
def test_T6_cwd_independent_identity(self):
|
||||
self.seed_agent("id9", 9440)
|
||||
port = free_test_port()
|
||||
self.seed_agent("id9", port)
|
||||
self.start_ok("id9")
|
||||
iid = self.manifest("id9")["instance_id"]
|
||||
self.svc("id9", "stop")
|
||||
@@ -320,7 +333,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
旧服务继续运行、旧制品与登记不变(2026-10-12 rdm1/rdmcand 反例:停服→
|
||||
覆盖→启动即拒,只剩混合状态)。"""
|
||||
remote, old_sha, new_sha = self.make_rdm_repo(self.noid_stub())
|
||||
self.seed_agent("rdx", 9451)
|
||||
port = free_test_port()
|
||||
self.seed_agent("rdx", port)
|
||||
self.start_ok("rdx") # 已建立身份,有能力制品在运行
|
||||
self.set_kv("rdx", "source", "rdm@%s" % old_sha)
|
||||
pid = self.pid_of("rdx")
|
||||
@@ -344,7 +358,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
"""已建立身份后删行:update 在停服前拒绝,不自动补行,旧进程保持。
|
||||
补行只走 adopt --confirm。"""
|
||||
old_sha, new_sha = self.make_src_repo()
|
||||
self.seed_agent("rowx", 9452)
|
||||
port = free_test_port()
|
||||
self.seed_agent("rowx", port)
|
||||
self.start_ok("rowx")
|
||||
self.set_kv("rowx", "source", "dev@%s" % old_sha)
|
||||
db = Path(self.manifest("rowx")["identity"]["data_root"]) / "agent-system.db"
|
||||
@@ -364,7 +379,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
def test_U1_bare_start_missing_row_is_event_init_recovers(self):
|
||||
"""普通启动只验证不补行:DB 行缺失 → 拒绝且不建空行(缺行即事件);
|
||||
显式 init 补齐后恢复。"""
|
||||
self.seed_agent("rowm", 9453)
|
||||
port = free_test_port()
|
||||
self.seed_agent("rowm", port)
|
||||
self.start_ok("rowm")
|
||||
iid = self.manifest("rowm")["instance_id"]
|
||||
self.svc("rowm", "stop")
|
||||
@@ -416,7 +432,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
def test_U1_adopt_identityless_artifact_reports_pending(self):
|
||||
"""已装制品无 identity 能力:adopt 的 ensure 静默跳过,marker 留在 pending——
|
||||
输出必须如实报 pending,不得宣称 db_marker=established。"""
|
||||
self.seed_agent("adx", 9454)
|
||||
port = free_test_port()
|
||||
self.seed_agent("adx", port)
|
||||
self.start_ok("adx")
|
||||
iid = self.manifest("adx")["instance_id"]
|
||||
self.svc("adx", "stop")
|
||||
@@ -437,7 +454,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
cand.chmod(0o755)
|
||||
remote, old_sha, _ = self._make_track_repo(cand, "agent-system")
|
||||
self.git_ok(self.root, "clone", str(remote), str(self.root / "srccache"))
|
||||
self.seed_agent("scx", 9455)
|
||||
port = free_test_port()
|
||||
self.seed_agent("scx", port)
|
||||
self.start_ok("scx")
|
||||
self.set_kv("scx", "source", "dev@%s" % old_sha)
|
||||
mpath = self.runtime("scx") / "manifest.json"
|
||||
@@ -459,7 +477,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
self.svc("scx", "stop")
|
||||
|
||||
def test_I3_start_ensures_sqlite_identity(self):
|
||||
self.seed_agent("idb1", 9441)
|
||||
port = free_test_port()
|
||||
self.seed_agent("idb1", port)
|
||||
self.start_ok("idb1")
|
||||
iid = self.manifest("idb1")["instance_id"]
|
||||
rows = self.db_identity_row("idb1")
|
||||
@@ -470,7 +489,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
self.svc("idb1", "stop")
|
||||
|
||||
def test_I3_wrong_db_identity_blocks_start(self):
|
||||
self.seed_agent("idb2", 9442)
|
||||
port = free_test_port()
|
||||
self.seed_agent("idb2", port)
|
||||
self.start_ok("idb2")
|
||||
iid = self.manifest("idb2")["instance_id"]
|
||||
self.svc("idb2", "stop")
|
||||
@@ -500,199 +520,6 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
self.skipTest("真 PG 专项应由隔离套件执行(本桩文件不连真实 PG)")
|
||||
self.skipTest("真实 PG 身份专项 NOT_RUN:需真实实例(SVC_TEST_PG=1 + AGENT_PG_TEST_DSN)")
|
||||
|
||||
# ---- 容器存储 U1:环境与数据归属合同 ----
|
||||
|
||||
def _storage_secret_dir(self):
|
||||
d = self.root / "storage-secrets"
|
||||
d.mkdir(exist_ok=True)
|
||||
for slot in ("base", "rdm", "kb"):
|
||||
(d / ("%s.dsn" % slot)).write_text("ref-only\n", encoding="utf-8")
|
||||
(d / ("%s.role" % slot)).write_text("ref-only\n", encoding="utf-8")
|
||||
return d
|
||||
|
||||
def _apply_container_bindings(self, name, compose, ports, files_root, env_class="prod"):
|
||||
"""先写齐绑定键,最后打开 storage_mode=container(缺一即拒)。
|
||||
经 svc_manifest.py set(与 svc_set_key 同源权威)。"""
|
||||
sec = self._storage_secret_dir()
|
||||
items = [
|
||||
("env_class", env_class),
|
||||
("files_root", str(files_root)),
|
||||
("compose_project", compose),
|
||||
("base_dsn_ref", str(sec / "base.dsn")),
|
||||
("base_role_ref", str(sec / "base.role")),
|
||||
("base_port", ports[0]),
|
||||
("rdm_dsn_ref", str(sec / "rdm.dsn")),
|
||||
("rdm_role_ref", str(sec / "rdm.role")),
|
||||
("rdm_port", ports[1]),
|
||||
("kb_dsn_ref", str(sec / "kb.dsn")),
|
||||
("kb_role_ref", str(sec / "kb.role")),
|
||||
("kb_port", ports[2]),
|
||||
("storage_mode", "container"),
|
||||
]
|
||||
man = self.root / "scripts" / "svc_manifest.py"
|
||||
env_dir = self.runtime(name)
|
||||
runtime = env_dir.parent
|
||||
for k, v in items:
|
||||
proc = subprocess.run(
|
||||
["python3", str(man), str(env_dir), "set", "--runtime", str(runtime), k, str(v)],
|
||||
cwd=str(self.root), env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
self.assertEqual(proc.returncode, 0, "%s=%s → %s" % (k, v, proc.stdout))
|
||||
|
||||
def test_CS_U1_legacy_upgrade_keeps_identity(self):
|
||||
"""旧配置升级到 container 绑定:instance_id / data_root 保留。"""
|
||||
self.seed_agent("cs1", 9471)
|
||||
self.start_ok("cs1")
|
||||
m0 = self.manifest("cs1")
|
||||
iid, data = m0["instance_id"], m0["identity"]["data_root"]
|
||||
self.svc("cs1", "stop")
|
||||
files = self.root / "files-cs1"
|
||||
files.mkdir()
|
||||
self._apply_container_bindings("cs1", "svc-cs1", ("25432", "25433", "25434"), files)
|
||||
m1 = self.manifest("cs1")
|
||||
self.assertEqual(m1["instance_id"], iid)
|
||||
self.assertEqual(m1["identity"]["data_root"], data)
|
||||
self.assertEqual(m1["identity"]["storage_mode"], "container")
|
||||
self.assertEqual(m1["identity"]["env_class"], "prod")
|
||||
self.assertEqual(m1["identity"]["files_root"], str(files))
|
||||
self.assertTrue(m1["identity"]["base_db_ref"].startswith("dsn_ref:"))
|
||||
self.assertTrue(m1["identity"]["rdm_db_ref"].startswith("dsn_ref:"))
|
||||
self.assertTrue(m1["identity"]["kb_db_ref"].startswith("dsn_ref:"))
|
||||
|
||||
def test_CS_U1_relative_files_root_rejected(self):
|
||||
self.seed_agent("cs2", 9472)
|
||||
self.start_ok("cs2")
|
||||
self.svc("cs2", "stop")
|
||||
man = self.root / "scripts" / "svc_manifest.py"
|
||||
env_dir = self.runtime("cs2")
|
||||
proc = subprocess.run(
|
||||
["python3", str(man), str(env_dir), "set", "--runtime", str(env_dir.parent),
|
||||
"files_root", "relative/not-abs"],
|
||||
cwd=str(self.root), env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
self.assertNotEqual(proc.returncode, 0, proc.stdout)
|
||||
self.assertIn("绝对路径", proc.stdout)
|
||||
self.assertNotEqual(self.manifest("cs2")["identity"].get("files_root"),
|
||||
"relative/not-abs")
|
||||
|
||||
def test_CS_U1_missing_binding_rejected(self):
|
||||
"""container 模式缺任一必需绑定不得开启(不静默默认)。"""
|
||||
self.seed_agent("cs3", 9473)
|
||||
self.start_ok("cs3")
|
||||
self.svc("cs3", "stop")
|
||||
man = self.root / "scripts" / "svc_manifest.py"
|
||||
env_dir = self.runtime("cs3")
|
||||
rt = str(env_dir.parent)
|
||||
proc = subprocess.run(
|
||||
["python3", str(man), str(env_dir), "set", "--runtime", rt,
|
||||
"storage_mode", "container"],
|
||||
cwd=str(self.root), env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
self.assertNotEqual(proc.returncode, 0, proc.stdout)
|
||||
self.assertIn("缺少必需绑定", proc.stdout)
|
||||
self.assertNotEqual(self.manifest("cs3")["kv"].get("storage_mode"), "container")
|
||||
|
||||
def test_CS_U1_wrong_files_root_and_db_verify_rejected(self):
|
||||
self.seed_agent("cs4", 9474)
|
||||
self.start_ok("cs4")
|
||||
self.svc("cs4", "stop")
|
||||
files = self.root / "files-cs4"
|
||||
files.mkdir()
|
||||
self._apply_container_bindings("cs4", "svc-cs4", ("25532", "25533", "25534"), files)
|
||||
man = self.root / "scripts" / "svc_manifest.py"
|
||||
env_dir = self.runtime("cs4")
|
||||
bad_files = subprocess.run(
|
||||
["python3", str(man), str(env_dir), "verify", "--require-storage",
|
||||
"--files-root", str(self.root / "other-files")],
|
||||
cwd=str(self.root), env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
self.assertNotEqual(bad_files.returncode, 0, bad_files.stdout)
|
||||
self.assertIn("文件根", bad_files.stdout)
|
||||
bad_db = subprocess.run(
|
||||
["python3", str(man), str(env_dir), "verify", "--require-storage",
|
||||
"--files-root", str(files),
|
||||
"--base-db-ref", "dbref:foreign/x"],
|
||||
cwd=str(self.root), env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
self.assertNotEqual(bad_db.returncode, 0, bad_db.stdout)
|
||||
self.assertIn("base 库绑定漂移", bad_db.stdout)
|
||||
|
||||
def test_CS_U1_compose_and_port_conflict_before_modify(self):
|
||||
self.seed_agent("cs5a", 9475)
|
||||
self.seed_agent("cs5b", 9476)
|
||||
self.start_ok("cs5a")
|
||||
self.start_ok("cs5b")
|
||||
self.svc("cs5a", "stop")
|
||||
self.svc("cs5b", "stop")
|
||||
files_a = self.root / "files-cs5a"
|
||||
files_b = self.root / "files-cs5b"
|
||||
files_a.mkdir()
|
||||
files_b.mkdir()
|
||||
self._apply_container_bindings("cs5a", "svc-shared", ("25632", "25633", "25634"), files_a)
|
||||
man = self.root / "scripts" / "svc_manifest.py"
|
||||
env_b = self.runtime("cs5b")
|
||||
rt = str(env_b.parent)
|
||||
# 同 compose 名:修改前失败
|
||||
sec = self._storage_secret_dir()
|
||||
for k, v in (("env_class", "test"), ("files_root", str(files_b)),
|
||||
("compose_project", "svc-shared")):
|
||||
proc = subprocess.run(
|
||||
["python3", str(man), str(env_b), "set", "--runtime", rt, k, v],
|
||||
cwd=str(self.root), env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
if k == "compose_project":
|
||||
self.assertNotEqual(proc.returncode, 0, proc.stdout)
|
||||
self.assertIn("Compose 项目名冲突", proc.stdout)
|
||||
else:
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout)
|
||||
# 端口冲突
|
||||
self.seed_agent("cs5c", 9477)
|
||||
self.start_ok("cs5c")
|
||||
self.svc("cs5c", "stop")
|
||||
files_c = self.root / "files-cs5c"
|
||||
files_c.mkdir()
|
||||
env_c = self.runtime("cs5c")
|
||||
for k, v in (
|
||||
("env_class", "verify"),
|
||||
("files_root", str(files_c)),
|
||||
("compose_project", "svc-cs5c"),
|
||||
("base_dsn_ref", str(sec / "base.dsn")),
|
||||
("base_role_ref", str(sec / "base.role")),
|
||||
("base_port", "25632"),
|
||||
):
|
||||
proc = subprocess.run(
|
||||
["python3", str(man), str(env_c), "set", "--runtime", rt, k, str(v)],
|
||||
cwd=str(self.root), env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
if k == "base_port":
|
||||
self.assertNotEqual(proc.returncode, 0, proc.stdout)
|
||||
self.assertIn("端口冲突", proc.stdout)
|
||||
else:
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout)
|
||||
|
||||
def test_CS_U1_other_checkout_keeps_absolute_files_root(self):
|
||||
"""另一 cwd/checkout 调用仍定位同一绝对 files_root。"""
|
||||
self.seed_agent("cs6", 9478)
|
||||
self.start_ok("cs6")
|
||||
self.svc("cs6", "stop")
|
||||
files = self.root / "files-cs6"
|
||||
files.mkdir()
|
||||
self._apply_container_bindings("cs6", "svc-cs6", ("25732", "25733", "25734"), files)
|
||||
iid = self.manifest("cs6")["instance_id"]
|
||||
fr = self.manifest("cs6")["identity"]["files_root"]
|
||||
elsewhere = tempfile.mkdtemp(prefix="svc-cs-elsewhere-")
|
||||
self.addCleanup(shutil.rmtree, elsewhere, True)
|
||||
man = self.root / "scripts" / "svc_manifest.py"
|
||||
proc = subprocess.run(
|
||||
["python3", str(man), str(self.runtime("cs6")), "status"],
|
||||
cwd=elsewhere, env=self.env, text=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30)
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout)
|
||||
payload = json.loads(proc.stdout.strip())
|
||||
self.assertEqual(payload["instance_id"], iid)
|
||||
self.assertEqual(payload["identity"]["files_root"], fr)
|
||||
self.assertTrue(os.path.isabs(fr), fr)
|
||||
|
||||
|
||||
class AtomicWriteTests(unittest.TestCase):
|
||||
"""崩溃窗口单元级:os.replace 失败时原文件必须完好(I3 半写不可能)。"""
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
"""生命周期隔离:调用方的 AGENT_SYSTEM_* 不得进入 serve,serve.env 为 0600。"""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _port_alloc import free_test_port # noqa: E402
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1]
|
||||
_spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py")
|
||||
tsr = importlib.util.module_from_spec(_spec)
|
||||
@@ -17,7 +22,8 @@ class LifecycleIsolationTests(tsr.SvcAutoRecoverTests):
|
||||
self.root / "scripts" / "svc_manifest.py")
|
||||
|
||||
def test_caller_identity_env_does_not_reach_serve(self):
|
||||
self.seed_agent("iso", 9461)
|
||||
port = free_test_port()
|
||||
self.seed_agent("iso", port)
|
||||
env = dict(self.env)
|
||||
env["AGENT_SYSTEM_DSN"] = "postgres://leak-user:leak-pass@127.0.0.1/leak"
|
||||
env["AGENT_SYSTEM_DATA"] = "/tmp/leak-data"
|
||||
@@ -38,7 +44,8 @@ class LifecycleIsolationTests(tsr.SvcAutoRecoverTests):
|
||||
self.svc("iso", "stop")
|
||||
|
||||
def test_two_bare_restarts_keep_identity(self):
|
||||
self.seed_agent("iso2", 9462)
|
||||
port = free_test_port()
|
||||
self.seed_agent("iso2", port)
|
||||
self.svc("iso2", "start")
|
||||
manifest = json.loads((self.runtime("iso2") / "manifest.json").read_text())
|
||||
iid = manifest["instance_id"]
|
||||
@@ -52,41 +59,3 @@ class LifecycleIsolationTests(tsr.SvcAutoRecoverTests):
|
||||
self.assertEqual(again["identity"]["data_root"], data)
|
||||
self.assertEqual(again["identity"].get("db_marker"), "established")
|
||||
self.svc("iso2", "stop")
|
||||
|
||||
def test_caller_storage_env_does_not_reach_serve(self):
|
||||
"""调用方 AGENT_SYSTEM_FILES_ROOT / ENV_CLASS / COMPOSE 不得进入 serve。"""
|
||||
self.seed_agent("iso3", 9463)
|
||||
env = dict(self.env)
|
||||
env["AGENT_SYSTEM_FILES_ROOT"] = "/tmp/leak-files-root"
|
||||
env["AGENT_SYSTEM_ENV_CLASS"] = "prod"
|
||||
env["AGENT_SYSTEM_COMPOSE_PROJECT"] = "leak-compose"
|
||||
env["AGENT_SYSTEM_BASE_DB_REF"] = "dbref:leak/base"
|
||||
env["AGENT_SYSTEM_RDM_DB_REF"] = "dbref:leak/rdm"
|
||||
env["AGENT_SYSTEM_KB_DB_REF"] = "dbref:leak/kb"
|
||||
self.svc("iso3", "start", env=env)
|
||||
pid = self.pid_of("iso3")
|
||||
self.assertIsNotNone(pid)
|
||||
raw = Path("/proc/%d/environ" % pid).read_bytes()
|
||||
self.assertNotIn(b"/tmp/leak-files-root", raw)
|
||||
self.assertNotIn(b"leak-compose", raw)
|
||||
self.assertNotIn(b"dbref:leak/base", raw)
|
||||
self.assertNotIn(b"dbref:leak/rdm", raw)
|
||||
self.assertNotIn(b"dbref:leak/kb", raw)
|
||||
serve_env = self.runtime("iso3") / "serve.env"
|
||||
self.assertTrue(serve_env.is_file())
|
||||
text = serve_env.read_text()
|
||||
self.assertNotIn("AGENT_SYSTEM_FILES_ROOT", text)
|
||||
self.assertNotIn("leak-files-root", text)
|
||||
self.assertNotIn("leak-compose", text)
|
||||
self.svc("iso3", "stop")
|
||||
|
||||
def test_env_config_resets_storage_globals(self):
|
||||
"""棘轮:env_config 必须清空存储绑定全局与调用方身份环境。"""
|
||||
import subprocess
|
||||
out = subprocess.run(
|
||||
["bash", "-c",
|
||||
'source "$1"; declare -f env_config | grep -c \'AGENT_SYSTEM_FILES_ROOT\'',
|
||||
"x", str(SOURCE / "scripts" / "svc.sh")],
|
||||
capture_output=True, text=True, cwd=str(SOURCE), timeout=30)
|
||||
self.assertEqual(out.returncode, 0, out.stderr)
|
||||
self.assertGreaterEqual(int(out.stdout.strip()), 1)
|
||||
|
||||
@@ -246,198 +246,6 @@ class ResolverStateIsolation(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(out.strip(), "1")
|
||||
|
||||
def test_env_config_resets_storage_identity_env(self):
|
||||
# 棘轮:env_config 必须剥离调用方 FILES_ROOT / ENV_CLASS / COMPOSE(容器存储 U1)。
|
||||
out = _bash(
|
||||
'source "$1"; declare -f env_config | '
|
||||
'grep -c \'unset AGENT_SYSTEM_FILES_ROOT AGENT_SYSTEM_ENV_CLASS AGENT_SYSTEM_COMPOSE_PROJECT\''
|
||||
)
|
||||
self.assertEqual(out.strip(), "1")
|
||||
|
||||
|
||||
class StorageBindingsPrecheck(unittest.TestCase):
|
||||
"""容器存储 U1:resolve_storage_bindings 与冲突预检(不启真实服务)。"""
|
||||
|
||||
def setUp(self):
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self.rt = Path(self._tmp.name) / "runtime"
|
||||
self.rt.mkdir()
|
||||
self.sec = Path(self._tmp.name) / "secrets"
|
||||
self.sec.mkdir()
|
||||
self.files = Path(self._tmp.name) / "files"
|
||||
self.files.mkdir()
|
||||
for slot in ("base", "rdm", "kb"):
|
||||
(self.sec / ("%s.dsn" % slot)).write_text("x\n")
|
||||
(self.sec / ("%s.role" % slot)).write_text("y\n")
|
||||
# 复制 manifest 脚本到可读路径(与仓内一致)
|
||||
self.man = REPO / "scripts" / "svc_manifest.py"
|
||||
|
||||
def tearDown(self):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _init_agent(self, name):
|
||||
env = self.rt / name
|
||||
data = env / "data"
|
||||
data.mkdir(parents=True)
|
||||
(env / "svc.env").write_text(
|
||||
"kind=agent\nenv=%s\naddr=127.0.0.1:18080\n"
|
||||
"exec=--addr 127.0.0.1:18080 --data %s\n" % (name, data),
|
||||
encoding="utf-8",
|
||||
)
|
||||
proc = subprocess.run(
|
||||
["python3", str(self.man), str(env), "migrate"],
|
||||
capture_output=True, text=True, cwd=str(REPO), timeout=30)
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
return env
|
||||
|
||||
def _set(self, env, key, value):
|
||||
proc = subprocess.run(
|
||||
["python3", str(self.man), str(env), "set",
|
||||
"--runtime", str(self.rt), key, str(value)],
|
||||
capture_output=True, text=True, cwd=str(REPO), timeout=30)
|
||||
return proc
|
||||
|
||||
def test_resolve_storage_bindings_legacy_ok(self):
|
||||
env = self._init_agent("leg")
|
||||
out = _bash(
|
||||
'source "$1"; shift; RUNTIME="$1"; ENV_NAME=leg; ENV_DIR="$2"; '
|
||||
'if resolve_storage_bindings; then echo rc=ok; else echo rc=fail; fi; '
|
||||
'printf "MODE=%s\\n" "$STORAGE_MODE"',
|
||||
str(self.rt), str(env),
|
||||
)
|
||||
self.assertIn("rc=ok", out)
|
||||
self.assertIn("MODE=legacy", out)
|
||||
|
||||
def test_resolve_incomplete_container_refuses(self):
|
||||
env = self._init_agent("inc")
|
||||
# 只写 storage_mode 会被 manifest 拒绝;直接改 kv 模拟半套后 resolve 拒绝
|
||||
proc = self._set(env, "env_class", "prod")
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
# 手工把 storage_mode 写成 container(绕过 mutate 完整校验)以测 resolve
|
||||
import json
|
||||
mp = env / "manifest.json"
|
||||
m = json.loads(mp.read_text(encoding="utf-8"))
|
||||
m["kv"]["storage_mode"] = "container"
|
||||
m["identity"]["storage_mode"] = "container"
|
||||
mp.write_text(json.dumps(m, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
(env / "svc.env").write_text(
|
||||
(env / "svc.env").read_text(encoding="utf-8") + "storage_mode=container\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
try:
|
||||
_bash(
|
||||
'source "$1"; shift; RUNTIME="$1"; ENV_NAME=inc; ENV_DIR="$2"; '
|
||||
'resolve_storage_bindings',
|
||||
str(self.rt), str(env),
|
||||
)
|
||||
self.fail("incomplete container must refuse")
|
||||
except AssertionError as e:
|
||||
self.assertIn("存储绑定不完整", str(e))
|
||||
|
||||
def test_relative_files_root_refused_on_set(self):
|
||||
env = self._init_agent("rel")
|
||||
proc = self._set(env, "files_root", "not/absolute")
|
||||
self.assertNotEqual(proc.returncode, 0)
|
||||
self.assertIn("绝对路径", proc.stdout + proc.stderr)
|
||||
|
||||
def test_compose_project_conflict_refused(self):
|
||||
a = self._init_agent("ca")
|
||||
b = self._init_agent("cb")
|
||||
for k, v in (
|
||||
("env_class", "prod"),
|
||||
("files_root", str(self.files)),
|
||||
("compose_project", "same-proj"),
|
||||
("base_dsn_ref", str(self.sec / "base.dsn")),
|
||||
("base_role_ref", str(self.sec / "base.role")),
|
||||
("base_port", "26001"),
|
||||
("rdm_dsn_ref", str(self.sec / "rdm.dsn")),
|
||||
("rdm_role_ref", str(self.sec / "rdm.role")),
|
||||
("rdm_port", "26002"),
|
||||
("kb_dsn_ref", str(self.sec / "kb.dsn")),
|
||||
("kb_role_ref", str(self.sec / "kb.role")),
|
||||
("kb_port", "26003"),
|
||||
("storage_mode", "container"),
|
||||
):
|
||||
proc = self._set(a, k, v)
|
||||
self.assertEqual(proc.returncode, 0, "%s: %s" % (k, proc.stdout + proc.stderr))
|
||||
(self.files / "b").mkdir(exist_ok=True)
|
||||
proc = self._set(b, "env_class", "test")
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
proc = self._set(b, "files_root", str(self.files / "b"))
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
proc = self._set(b, "compose_project", "same-proj")
|
||||
self.assertNotEqual(proc.returncode, 0)
|
||||
self.assertIn("Compose 项目名冲突", proc.stdout + proc.stderr)
|
||||
|
||||
def test_storage_port_conflict_refused(self):
|
||||
a = self._init_agent("pa")
|
||||
b = self._init_agent("pb")
|
||||
for k, v in (
|
||||
("env_class", "prod"),
|
||||
("files_root", str(self.files / "pa")),
|
||||
("compose_project", "proj-pa"),
|
||||
("base_dsn_ref", str(self.sec / "base.dsn")),
|
||||
("base_role_ref", str(self.sec / "base.role")),
|
||||
("base_port", "26101"),
|
||||
("rdm_dsn_ref", str(self.sec / "rdm.dsn")),
|
||||
("rdm_role_ref", str(self.sec / "rdm.role")),
|
||||
("rdm_port", "26102"),
|
||||
("kb_dsn_ref", str(self.sec / "kb.dsn")),
|
||||
("kb_role_ref", str(self.sec / "kb.role")),
|
||||
("kb_port", "26103"),
|
||||
("storage_mode", "container"),
|
||||
):
|
||||
(self.files / "pa").mkdir(exist_ok=True)
|
||||
proc = self._set(a, k, v)
|
||||
self.assertEqual(proc.returncode, 0, "%s: %s" % (k, proc.stdout + proc.stderr))
|
||||
(self.files / "pb").mkdir(exist_ok=True)
|
||||
for k, v in (
|
||||
("env_class", "test"),
|
||||
("files_root", str(self.files / "pb")),
|
||||
("compose_project", "proj-pb"),
|
||||
("base_dsn_ref", str(self.sec / "base.dsn")),
|
||||
("base_role_ref", str(self.sec / "base.role")),
|
||||
("base_port", "26101"),
|
||||
):
|
||||
proc = self._set(b, k, v)
|
||||
if k == "base_port":
|
||||
self.assertNotEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
self.assertIn("端口冲突", proc.stdout + proc.stderr)
|
||||
else:
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
|
||||
def test_resolve_complete_container_ok(self):
|
||||
env = self._init_agent("okc")
|
||||
(self.files / "okc").mkdir(exist_ok=True)
|
||||
for k, v in (
|
||||
("env_class", "verify"),
|
||||
("files_root", str(self.files / "okc")),
|
||||
("compose_project", "proj-okc"),
|
||||
("base_dsn_ref", str(self.sec / "base.dsn")),
|
||||
("base_role_ref", str(self.sec / "base.role")),
|
||||
("base_port", "26201"),
|
||||
("rdm_dsn_ref", str(self.sec / "rdm.dsn")),
|
||||
("rdm_role_ref", str(self.sec / "rdm.role")),
|
||||
("rdm_port", "26202"),
|
||||
("kb_dsn_ref", str(self.sec / "kb.dsn")),
|
||||
("kb_role_ref", str(self.sec / "kb.role")),
|
||||
("kb_port", "26203"),
|
||||
("storage_mode", "container"),
|
||||
):
|
||||
proc = self._set(env, k, v)
|
||||
self.assertEqual(proc.returncode, 0, "%s: %s" % (k, proc.stdout + proc.stderr))
|
||||
out = _bash(
|
||||
'source "$1"; shift; RUNTIME="$1"; ENV_NAME=okc; ENV_DIR="$2"; '
|
||||
'if resolve_storage_bindings; then echo rc=ok; else echo rc=fail; fi; '
|
||||
'printf "MODE=%s\\nENV=%s\\nFILES=%s\\nCOMPOSE=%s\\n" '
|
||||
'"$STORAGE_MODE" "$ENV_CLASS" "$FILES_ROOT" "$COMPOSE_PROJECT"',
|
||||
str(self.rt), str(env),
|
||||
)
|
||||
self.assertIn("rc=ok", out)
|
||||
self.assertIn("MODE=container", out)
|
||||
self.assertIn("ENV=verify", out)
|
||||
self.assertIn("COMPOSE=proj-okc", out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+29
-12
@@ -11,12 +11,17 @@
|
||||
夹具复用 tests/test_svc_recover.py(桩不需要运行——台账操作是 SQLite 直读直写)。
|
||||
"""
|
||||
import importlib.util
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _port_alloc import free_test_port # noqa: E402
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1]
|
||||
|
||||
_spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py")
|
||||
@@ -64,21 +69,24 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- show ----
|
||||
|
||||
def test_show_prints_ledger_and_flags_unknown_owner(self):
|
||||
self.seed_agent("ra1", 9461)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra1", port)
|
||||
self.seed_dbs("ra1", ledger_owner="u7-admin", base_owners=("weichen",))
|
||||
out = self.svc("ra1", "rdm-admin", "show").stdout
|
||||
self.assertIn("RDM 域管理员 u7-admin", out)
|
||||
self.assertIn("不在基座用户表", out) # u7-admin 不在 users → 残留警示
|
||||
|
||||
def test_show_known_owner_no_warning(self):
|
||||
self.seed_agent("ra2", 9462)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra2", port)
|
||||
self.seed_dbs("ra2", ledger_owner="weichen")
|
||||
out = self.svc("ra2", "rdm-admin").stdout # 缺省子命令 = show
|
||||
self.assertIn("RDM 域管理员 weichen", out)
|
||||
self.assertNotIn("不在基座用户表", out)
|
||||
|
||||
def test_show_empty_ledger_hint(self):
|
||||
self.seed_agent("ra3", 9463)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra3", port)
|
||||
self.seed_dbs("ra3", ledger_owner=None)
|
||||
out = self.svc("ra3", "rdm-admin").stdout
|
||||
self.assertIn("台账为空", out)
|
||||
@@ -87,7 +95,8 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- transfer ----
|
||||
|
||||
def test_transfer_rewrites_single_row_with_provenance(self):
|
||||
self.seed_agent("ra4", 9464)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra4", port)
|
||||
self.seed_dbs("ra4", ledger_owner="u7-admin")
|
||||
out = self.svc("ra4", "rdm-admin", "transfer", "--to", "weichen").stdout
|
||||
self.assertIn("受信部署通道", out)
|
||||
@@ -97,13 +106,15 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests):
|
||||
self.assertEqual(added_by, "svc:rdm-admin:u7-admin") # 前任可追溯
|
||||
|
||||
def test_transfer_idempotent_when_already_owner(self):
|
||||
self.seed_agent("ra5", 9465)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra5", port)
|
||||
self.seed_dbs("ra5", ledger_owner="weichen")
|
||||
out = self.svc("ra5", "rdm-admin", "transfer", "--to", "weichen").stdout
|
||||
self.assertIn("已是 weichen", out)
|
||||
|
||||
def test_transfer_rejects_owner_missing_from_base_users(self):
|
||||
self.seed_agent("ra6", 9466)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra6", port)
|
||||
self.seed_dbs("ra6", ledger_owner="weichen")
|
||||
proc = self.svc("ra6", "rdm-admin", "transfer", "--to", "nobody", check=False)
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout)
|
||||
@@ -111,7 +122,8 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests):
|
||||
self.assertEqual(self.ledger_owner("ra6")[0], "weichen") # 台账未被动
|
||||
|
||||
def test_transfer_rejects_empty_ledger(self):
|
||||
self.seed_agent("ra7", 9467)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra7", port)
|
||||
self.seed_dbs("ra7", ledger_owner=None)
|
||||
proc = self.svc("ra7", "rdm-admin", "transfer", "--to", "weichen", check=False)
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout)
|
||||
@@ -120,7 +132,8 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- 绑定合同与用法 ----
|
||||
|
||||
def test_refuses_when_rdm_db_missing(self):
|
||||
self.seed_agent("ra8", 9468)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra8", port)
|
||||
self.seed_dbs("ra8") # 建基座库后删 rdm.db
|
||||
(self.runtime("ra8") / "data" / "rdm.db").unlink()
|
||||
proc = self.svc("ra8", "rdm-admin", check=False)
|
||||
@@ -128,13 +141,15 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests):
|
||||
self.assertIn("无 RDM 域库", proc.stdout)
|
||||
|
||||
def test_refuses_when_base_db_missing(self):
|
||||
self.seed_agent("ra9", 9469)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ra9", port)
|
||||
proc = self.svc("ra9", "rdm-admin", check=False)
|
||||
self.assertEqual(proc.returncode, 1, proc.stdout)
|
||||
self.assertIn("既有库不存在", proc.stdout)
|
||||
|
||||
def test_unknown_subcommand_usage_exit_2(self):
|
||||
self.seed_agent("raa", 9470)
|
||||
port = free_test_port()
|
||||
self.seed_agent("raa", port)
|
||||
proc = self.svc("raa", "rdm-admin", "bogus", check=False)
|
||||
self.assertEqual(proc.returncode, 2, proc.stdout)
|
||||
self.assertIn("用法", proc.stdout)
|
||||
@@ -142,14 +157,16 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- status 巡检行 ----
|
||||
|
||||
def test_status_lists_rdm_admin_row_and_warning(self):
|
||||
self.seed_agent("rab", 9471)
|
||||
port = free_test_port()
|
||||
self.seed_agent("rab", port)
|
||||
self.seed_dbs("rab", ledger_owner="u7-admin", base_owners=("weichen",))
|
||||
out = self.svc("rab", "status").stdout
|
||||
self.assertIn("RDM 域管理员 u7-admin", out)
|
||||
self.assertIn("不在基座用户表", out)
|
||||
|
||||
def test_status_silent_without_rdm_db(self):
|
||||
self.seed_agent("rac", 9472)
|
||||
port = free_test_port()
|
||||
self.seed_agent("rac", port)
|
||||
self.seed_dbs("rac")
|
||||
(self.runtime("rac") / "data" / "rdm.db").unlink()
|
||||
out = self.svc("rac", "status").stdout
|
||||
|
||||
@@ -56,7 +56,7 @@ def ci_git_env():
|
||||
"GIT_CONFIG_KEY_1": "http.%s/.extraheader" % CI_SERVER,
|
||||
"GIT_CONFIG_VALUE_1": "AUTHORIZATION: token " + CI_TOKEN,
|
||||
"GIT_CONFIG_KEY_2": "url.%s/weichen/.insteadOf" % CI_SERVER,
|
||||
"GIT_CONFIG_VALUE_2": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/",
|
||||
"GIT_CONFIG_VALUE_2": "ssh://git@gitea.naitangbaobao.cn:52222/weichen/",
|
||||
"GIT_CONFIG_KEY_3": "core.hooksPath",
|
||||
"GIT_CONFIG_VALUE_3": "/dev/null",
|
||||
}
|
||||
|
||||
+18
-7
@@ -12,9 +12,13 @@ import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _port_alloc import free_test_port # noqa: E402
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1]
|
||||
|
||||
_spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py")
|
||||
@@ -106,7 +110,8 @@ class U2ReleaseTests(tsi.IdentityTests):
|
||||
clock.chmod(0o755)
|
||||
self.env["PATH"] = str(clock_bin) + os.pathsep + self.env["PATH"]
|
||||
self.make_src_repo() # 预克隆 srccache(本用例走 --from local,仅保环境一致)
|
||||
self.seed_agent("u2a", 9461)
|
||||
port = free_test_port()
|
||||
self.seed_agent("u2a", port)
|
||||
self.start_ok("u2a") # legacy 运行中(期望态=running)
|
||||
self.assertTrue(self.running("u2a"))
|
||||
|
||||
@@ -147,7 +152,8 @@ class U2ReleaseTests(tsi.IdentityTests):
|
||||
# ---- T2:构建失败 → 旧服务保持运行、current 不动 ----
|
||||
|
||||
def test_U2_build_failure_keeps_old_running(self):
|
||||
self.seed_agent("u2b", 9462)
|
||||
port = free_test_port()
|
||||
self.seed_agent("u2b", port)
|
||||
self.set_makefile(self.stub)
|
||||
self.svc("u2b", "deploy", "--from", "local")
|
||||
cur1 = self.cur_release("u2b")
|
||||
@@ -164,7 +170,8 @@ class U2ReleaseTests(tsi.IdentityTests):
|
||||
# ---- T3:中断切换(journal switched)→ start 入口回退并恢复 ----
|
||||
|
||||
def test_U2_journal_interrupt_resolved_on_start(self):
|
||||
self.seed_agent("u2c", 9463)
|
||||
port = free_test_port()
|
||||
self.seed_agent("u2c", port)
|
||||
self.set_makefile(self.stub)
|
||||
self.svc("u2c", "deploy", "--from", "local")
|
||||
v1 = self.cur_release("u2c")
|
||||
@@ -190,7 +197,8 @@ class U2ReleaseTests(tsi.IdentityTests):
|
||||
# ---- T4:schema 前进 → 回退阻断,不触碰业务库 ----
|
||||
|
||||
def test_U2_schema_advance_blocks_rollback(self):
|
||||
self.seed_agent("u2d", 9464)
|
||||
port = free_test_port()
|
||||
self.seed_agent("u2d", port)
|
||||
self.set_makefile(self.stub)
|
||||
self.svc("u2d", "deploy", "--from", "local")
|
||||
v1 = self.cur_release("u2d")
|
||||
@@ -230,7 +238,8 @@ class U2ReleaseTests(tsi.IdentityTests):
|
||||
# ---- T5:legacy 布局未迁移 → 裸 start/restart 仍走原路径(兼容) ----
|
||||
|
||||
def test_U2_fault_after_switch_rolls_back_on_next_start(self):
|
||||
self.seed_agent("u2f", 9466)
|
||||
port = free_test_port()
|
||||
self.seed_agent("u2f", port)
|
||||
self.set_makefile(self.stub)
|
||||
self.svc("u2f", "deploy", "--from", "local")
|
||||
v1 = self.cur_release("u2f")
|
||||
@@ -248,7 +257,8 @@ class U2ReleaseTests(tsi.IdentityTests):
|
||||
self.svc("u2f", "stop")
|
||||
|
||||
def test_U2_health_timeout_rolls_back(self):
|
||||
self.seed_agent("u2g", 9467)
|
||||
port = free_test_port()
|
||||
self.seed_agent("u2g", port)
|
||||
self.set_makefile(self.stub)
|
||||
self.svc("u2g", "deploy", "--from", "local")
|
||||
v1 = self.cur_release("u2g")
|
||||
@@ -277,7 +287,8 @@ class U2ReleaseTests(tsi.IdentityTests):
|
||||
return p
|
||||
|
||||
def test_U2_legacy_layout_bare_lifecycle_still_works(self):
|
||||
self.seed_agent("u2e", 9465)
|
||||
port = free_test_port()
|
||||
self.seed_agent("u2e", port)
|
||||
self.start_ok("u2e")
|
||||
self.assertFalse((self.runtime("u2e") / "current").exists())
|
||||
self.assertTrue((self.runtime("u2e") / "bin" / "agent-system").exists())
|
||||
|
||||
@@ -9,9 +9,13 @@ import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from _port_alloc import free_test_port # noqa: E402
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1]
|
||||
|
||||
_spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py")
|
||||
@@ -57,7 +61,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T1 合法 manifest:user list 绑定 data_root + --open-existing ----
|
||||
|
||||
def test_T1_user_list_binds_manifest_data(self):
|
||||
self.seed_agent("ub1", 9441)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ub1", port)
|
||||
self.start_ok("ub1")
|
||||
self.svc("ub1", "stop")
|
||||
root = self.manifest("ub1")["identity"]["data_root"]
|
||||
@@ -74,7 +79,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T2 禁止覆盖 --data;未绑定默认目录拒绝 ----
|
||||
|
||||
def test_T2_reject_override_data_flag(self):
|
||||
self.seed_agent("ub2", 9442)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ub2", port)
|
||||
self.start_ok("ub2")
|
||||
self.svc("ub2", "stop")
|
||||
self.touch_db("ub2")
|
||||
@@ -106,7 +112,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T3 删除 data 根:OpenExisting 失败,不建空库 ----
|
||||
|
||||
def test_T3_missing_data_root_rejected(self):
|
||||
self.seed_agent("ub3", 9444)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ub3", port)
|
||||
self.start_ok("ub3")
|
||||
self.svc("ub3", "stop")
|
||||
root = Path(self.manifest("ub3")["identity"]["data_root"])
|
||||
@@ -124,7 +131,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T4 隐藏 flock:user/deploy 拒绝;已运行服务仍在 ----
|
||||
|
||||
def test_T4_no_flock_rejects_user_and_deploy(self):
|
||||
self.seed_agent("ub4", 9445)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ub4", port)
|
||||
self.start_ok("ub4")
|
||||
self.assertTrue(self.running("ub4"))
|
||||
self.touch_db("ub4")
|
||||
@@ -158,7 +166,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests):
|
||||
# ---- T5 recovery U1 身份冒烟(完整套件见 test_svc_identity)----
|
||||
|
||||
def test_T5_identity_regression_smoke(self):
|
||||
self.seed_agent("ub5", 9446)
|
||||
port = free_test_port()
|
||||
self.seed_agent("ub5", port)
|
||||
self.start_ok("ub5")
|
||||
iid = self.manifest("ub5")["instance_id"]
|
||||
self.svc("ub5", "stop")
|
||||
|
||||
Reference in New Issue
Block a user