Pin backend to origin/main after #175
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# feature/fe-me-refresh 任务卡
|
||||
|
||||
任务主记录:<PR 链接,交付前填写>;状态:进行中;owner:weichen
|
||||
任务主记录:PR #407 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/407(merge 475654363d);状态:已完成(回填);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
@@ -203,3 +203,9 @@
|
||||
"receipt_sha256": "a71909956a3b92e424ef3a3a33d3729c46ba3e9102f51da625a6e9039df5294b"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge 475654363de3ec72df68d246ffab8217b1cfeab6(PR #407)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Bump frontend pin: refresh me on auth state (frontend #61)' (#407) from feature/fe-me-refresh into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
# feature/svc-deploy-u1-account-binding 任务卡
|
||||
|
||||
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/429 ;状态:进行中(待合入);owner:weichen
|
||||
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/429 ;状态:已完成(回填);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
@@ -207,3 +207,9 @@
|
||||
"receipt_sha256": "acfabd6f97b558bfbac539e5aa41c3155db7fcc81bbb6c1dabb1c202f30abfcb"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge 65175c65488b8f791ef51b65bf997d7c85e09708(PR #429)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Bind svc user CLI to instance manifest and reject flock bypass' (#429) from feature/svc-deploy-u1-account-binding into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
# feature/svc-deploy-u4-login-key 任务卡
|
||||
|
||||
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/428 ;Backend:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Backend/pulls/171 ;状态:进行中(待合入);owner:weichen
|
||||
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/428 ;Backend:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Backend/pulls/171 ;状态:已完成(回填);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
@@ -204,3 +204,9 @@
|
||||
"receipt_sha256": "b66e330aadb9e63bb989e185514d632236b4ab1bc8b3c4509f6ef47e1d1553bf"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge 5568ca1efe8c472ae9fa12ef77a8713da6448eb9(PR #428)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Implement U4 one-time login keys and OpenExisting (Backend pin)' (#428) from feature/svc-deploy-u4-login-key into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
# feature/svc-unified-deploy-login-key 任务卡
|
||||
|
||||
任务主记录:modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md r1;主记录 PR:Documents #79 → Dev #422;状态:进行中;owner:weichen
|
||||
任务主记录:PR #422 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/422(merge 673e687c5c);主记录 PR:Documents #79 → Dev #422;状态:已完成(回填);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
## 结果与验证
|
||||
|
||||
- 结果:本地候选 r1 提案、INDEX/PROJECT 入口、U1/U4 规格与任务卡草稿已写入工作区(待提交固定)
|
||||
- 验证:SELF 启动 precheck READY(收据见下方);业务实施/测试/部署/运行验收均 NOT_RUN
|
||||
@@ -165,3 +165,9 @@
|
||||
"receipt_sha256": "e2ba6f4afc4993d13a12fa0a9b8aeb4154f2a780780f53d82abe37c78bf3bae2"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge 673e687c5c85b9af3ce13ca30845d5e68de5dfa0(PR #422)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Record svc unified deploy proposal r1 specs and planning entry' (#422) from feature/svc-unified-deploy-login-key into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
# U1 实例与数据身份 manifest 合同(设计 v0.1)
|
||||
|
||||
状态:已批准(2026-10-12 用户“按建议办”,四未决项按 §8 建议定案),进入实施 I2。主记录:服务重启与全量数据恢复方案 r2(`modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md` §7 U1 与 KTD1/KTD2;该提案落在本仓 feature/service-restart-recovery-plan 分支待推送合入,合入后即为上述路径);本任务卡 `feature/recovery-u1-instance-identity`。授权:用户批准 r2(2026-10-12)并指示启动 U1;实施(改 `scripts/svc.sh` 与测试)待本设计评审通过。
|
||||
基线:Dev `89174a0`(含 svc-autorecover 合入:autostart 期望态、`recover`、watchdog、`.start.lock`)。事实来源 SELF·源码观察;本文所有"已核实"均可在该基线复核。
|
||||
状态:已批准(2026-10-12 用户“按建议办”,四未决项按 §8 建议定案)。**I2 已合入**(Dev PR #415);**I3(DB marker)实施中**(`feature/recovery-u1-db-identity`:表 `agent_system_identity`、CLI `identity ensure|verify`、svc 接线)。主记录:服务重启与全量数据恢复方案 r2(`modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md` §7 U1 与 KTD1/KTD2)。
|
||||
基线:Dev `89174a0`(含 svc-autorecover);I2 合入 tip 见 PR #415。事实来源 SELF·源码观察;本文所有"已核实"均可在该基线复核。
|
||||
|
||||
## 1. 目标与不变量
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
- 解析:CLI 显式覆盖(仅 `listen.*`、`program.source` 等白名单字段)> manifest > 环境变量(迁移后仅作显式 override 记录,不再隐式成为下次事实)。
|
||||
- 启动校验(全部通过才 exec):
|
||||
1. schema/revision 合法且 revision ≥ 在位值;
|
||||
2. **DB marker**:PG 专用表 `agent_system_identity(instance_id, dbref, schema_version, created_at)`;SQLite 同表。首建仅发生在 `svc init`;常规启动要求 manifest.instance_id 与 marker 匹配,缺失/不匹配即阻塞(I4,对齐 resolve_db_config 现有"绑定失效拒绝启动")。
|
||||
2. **DB marker**:PG 专用表 `agent_system_identity(instance_id, dbref, schema_version, created_at)`;SQLite 同表(Backend migration v27;v26 为 login_keys)。首建:`svc init` 或 start 路径上的幂等 `identity ensure`(仅表空插入;已有行不一致 fail-closed,不改 instance_id)。常规启动 `identity verify` + serve 在 `AGENT_SYSTEM_INSTANCE_ID` 下二次门禁;缺失/不匹配即阻塞(I4)。与 serve `--instance`(runs 进程标识)无关。
|
||||
3. **数据根 marker**:`<data_root>/.agentx/data-marker.json`(instance_id + roots 清单摘要);不匹配/丢失阻塞。
|
||||
4. `dsn_ref` 指向文件存在且权限 ≤0600。
|
||||
- 显式入口:`svc init`(首次:创建 manifest+双 marker,要求目标为空);`svc restore`(灾难恢复:新目标装配 manifest+marker,走 U6 验证合同)。`start` 永不隐式 init。
|
||||
|
||||
+1
-1
Submodule modules/backend updated: 741b5b0fd1...dcc180dcad
+92
-4
@@ -955,6 +955,7 @@ svc_manifest_migrate() {
|
||||
}
|
||||
# svc_manifest_verify:启动前身份校验——数据 marker 存在且 instance_id 匹配;
|
||||
# 生效 --data / 数据库绑定与冻结锚点一致。失败即拒绝启动(KTD1/KTD2,I4)。
|
||||
# 顺序:文件 marker → DB ensure(空表幂等补)→ DB verify(缺/错即拦)。
|
||||
svc_manifest_verify() {
|
||||
manifest_ok || return 0
|
||||
local out
|
||||
@@ -964,6 +965,86 @@ svc_manifest_verify() {
|
||||
c_err " 数据根/数据库绑定是冻结身份;换根/重绑须显式迁移或登记命令(docs/recovery/u1-identity-manifest.md §5)"
|
||||
return 1
|
||||
fi
|
||||
# U1 I3:文件校验通过后再碰库——避免损坏 manifest 时 ensure 先 traceback。
|
||||
svc_db_identity_ensure || return 1
|
||||
svc_db_identity_verify || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# svc_db_identity_*:DB marker(设计 §5.2)。与 --instance(runs 进程标识)无关。
|
||||
# dbref 稳定串:pg 用托管绑定或 "pg";其余归一为 "sqlite"。
|
||||
svc_db_identity_args() {
|
||||
# 输出:INSTANCE_ID\tDBREF\tDATA(供 ensure/verify 共用)
|
||||
manifest_ok || return 1
|
||||
python3 - "$ENV_DIR" "${ENV_DATA:-}" <<'PY'
|
||||
import json, os, sys
|
||||
env_dir, data_fallback = sys.argv[1], sys.argv[2]
|
||||
m = json.load(open(os.path.join(env_dir, "manifest.json"), encoding="utf-8"))
|
||||
ident = m.get("identity") or {}
|
||||
mode = ident.get("db_mode") or ""
|
||||
ref = ident.get("db_ref") or ""
|
||||
if mode == "pg":
|
||||
dbref = ref or "pg"
|
||||
else:
|
||||
dbref = "sqlite"
|
||||
data = ident.get("data_root") or data_fallback
|
||||
print("%s\t%s\t%s" % (m["instance_id"], dbref, data))
|
||||
PY
|
||||
}
|
||||
|
||||
svc_bin_has_identity() {
|
||||
[ -x "${ENV_BIN:-}" ] || return 1
|
||||
local out
|
||||
out="$("$ENV_BIN" identity 2>&1 || true)"
|
||||
printf '%s' "$out" | grep -q ensure
|
||||
}
|
||||
|
||||
svc_db_identity_ensure() {
|
||||
# 显式建立 / I2→I3 引导:表空插入;一致幂等;不一致 fail-closed。
|
||||
# 非 agent 或无 identity 子命令的旧产物:跳过(不硬断 I2)。
|
||||
manifest_ok || return 0
|
||||
local kind; kind="$(svc_get kind)"
|
||||
[ "$kind" = agent ] || return 0
|
||||
svc_bin_has_identity || return 0
|
||||
local line iid dbref data
|
||||
line="$(svc_db_identity_args)" || return 1
|
||||
IFS=$'\t' read -r iid dbref data <<<"$line"
|
||||
local args=(identity ensure --instance-id "$iid" --dbref "$dbref" --data "$data")
|
||||
if [ "${DB_MODE:-}" = pg ] && [ -n "${DB_DSN:-}" ]; then
|
||||
args+=(--dsn "$DB_DSN")
|
||||
fi
|
||||
local out rc=0
|
||||
out="$("$ENV_BIN" "${args[@]}" 2>&1)" || rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
c_err "[$ENV_NAME] DB 身份 ensure 失败:$out"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
svc_db_identity_verify() {
|
||||
manifest_ok || return 0
|
||||
local kind; kind="$(svc_get kind)"
|
||||
[ "$kind" = agent ] || return 0
|
||||
svc_bin_has_identity || return 0
|
||||
local line iid dbref data
|
||||
line="$(svc_db_identity_args)" || return 1
|
||||
IFS=$'\t' read -r iid dbref data <<<"$line"
|
||||
local args=(identity verify --instance-id "$iid" --dbref "$dbref" --data "$data")
|
||||
if [ "${DB_MODE:-}" = pg ] && [ -n "${DB_DSN:-}" ]; then
|
||||
args+=(--dsn "$DB_DSN")
|
||||
fi
|
||||
local out rc=0
|
||||
out="$("$ENV_BIN" "${args[@]}" 2>&1)" || rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
c_err "[$ENV_NAME] DB 身份校验失败,拒绝启动:$out"
|
||||
c_err " 先 scripts/svc.sh $ENV_NAME init(或补跑 ensure);错库/错实例须人工处置(docs/recovery/u1-identity-manifest.md §5)"
|
||||
return 1
|
||||
fi
|
||||
# 供 serve 二次门禁;verify 成功后导出。
|
||||
AGENT_SYSTEM_INSTANCE_ID="$iid"
|
||||
AGENT_SYSTEM_DBREF="$dbref"
|
||||
export AGENT_SYSTEM_INSTANCE_ID AGENT_SYSTEM_DBREF
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1977,8 +2058,7 @@ agent_do_start() {
|
||||
hosts="$(env_var "$ENV_NAME" ALLOWED_HOSTS)"
|
||||
[ -n "$insecure" ] && guard_env+=("AGENT_SYSTEM_INSECURE_LISTEN=$insecure")
|
||||
[ -n "$hosts" ] && guard_env+=("AGENT_SYSTEM_ALLOWED_HOSTS=$hosts")
|
||||
# U1 身份校验:manifest 治理下,数据 marker 与冻结锚点不符即拒绝启动(在一切
|
||||
# 副作用之前;登记不变更)。
|
||||
# U1 身份校验:文件 marker + DB marker(ensure 幂等补空表;不一致 fail-closed)。
|
||||
svc_manifest_verify || { hist_event start fail "" "manifest 身份校验失败"; return 1; }
|
||||
# 预检(快速拦截):非回环地址且认证判定为关时提前拒绝,报错可操作——
|
||||
# 不等二进制 listen guard 以"进程即退"暴露;二进制守卫仍是权威。
|
||||
@@ -2014,6 +2094,11 @@ agent_do_start() {
|
||||
fi
|
||||
guard_env+=("AGENT_SYSTEM_DSN=$DB_DSN")
|
||||
fi
|
||||
# U1 I3:把服务实例身份交给 serve 二次门禁(与 --instance 进程标识无关)。
|
||||
if [ -n "${AGENT_SYSTEM_INSTANCE_ID:-}" ]; then
|
||||
guard_env+=("AGENT_SYSTEM_INSTANCE_ID=$AGENT_SYSTEM_INSTANCE_ID")
|
||||
guard_env+=("AGENT_SYSTEM_DBREF=${AGENT_SYSTEM_DBREF:-sqlite}")
|
||||
fi
|
||||
# 9>&-:显式关闭锁 fd——服务进程绝不能继承 .start.lock(长驻持有会令后续
|
||||
# 生命周期操作在锁上自锁死等;2026-10-11 自恢复任务实测发现)。
|
||||
if [ "${#guard_env[@]}" -gt 0 ];then
|
||||
@@ -4242,8 +4327,8 @@ main() {
|
||||
start) do_start ;;
|
||||
restart) do_restart ;;
|
||||
init)
|
||||
# 显式建立/查看身份(manifest+数据 marker)。迁移钩子已先行:有 svc.env 无
|
||||
# manifest 时 migrate 已建好,此处只报告;无 svc.env 引导走首次 start。
|
||||
# 显式建立/查看身份(manifest+数据 marker+DB marker)。迁移钩子已先行:有
|
||||
# svc.env 无 manifest 时 migrate 已建好,此处只报告;无 svc.env 引导走首次 start。
|
||||
command -v python3 >/dev/null 2>&1 && [ -f "$MANIFEST_PY" ] || {
|
||||
c_err "[$ENV_NAME] init 需要 python3 与 scripts/svc_manifest.py"; exit 2; }
|
||||
if [ -f "$ENV_DIR/manifest.json" ]; then
|
||||
@@ -4254,6 +4339,9 @@ main() {
|
||||
else
|
||||
c_err "[$ENV_NAME] 尚无 svc.env:先用 start 完成首次登记;init 用于显式化已有实例"; exit 2
|
||||
fi
|
||||
# I3:尽力解析 DB 后 Ensure(sqlite 无需 DSN;pg 需绑定已登记)。
|
||||
resolve_db_config 2>/dev/null || true
|
||||
svc_db_identity_ensure || { c_err "[$ENV_NAME] DB 身份 ensure 失败"; exit 1; }
|
||||
python3 "$MANIFEST_PY" "$ENV_DIR" status ;;
|
||||
status) env_status_detail ;;
|
||||
logs) [ -f "$ENV_LOG" ] && tail -n "${CLI_LOGS_N:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;;
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -370,9 +371,24 @@ func (e *env) killNine(t *testing.T, inst *instance) {
|
||||
|
||||
// loginAlice 引导 alice 账号并发放 API key(CLI 直连测试库;key 存共享 PG,
|
||||
// 对两实例同时有效——程序化验收的首选凭证,SSE 亦可用 ?key= 兜底)。
|
||||
// 建号后 must_change=1:API Key 在改密完成前不能走业务接口(login_keys 门禁),
|
||||
// 故先经会话完成 alicePass0→alicePass,再发 Key。
|
||||
func (e *env) loginAlice(t *testing.T) *userClient {
|
||||
t.Helper()
|
||||
e.cli(t, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--password", alicePass)
|
||||
e.cli(t, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--password", alicePass0)
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sess := &http.Client{Timeout: httpWait, Jar: jar}
|
||||
// e.uc 尚未赋值 → baseFor 走 A;会话 cookie 落 jar 后改密清 must_change。
|
||||
e.do(t, sess, "POST", "/api/auth/login", map[string]any{
|
||||
"owner": aliceOwner, "password": alicePass0,
|
||||
}, nil)
|
||||
e.do(t, sess, "POST", "/api/auth/password", map[string]any{
|
||||
"current": alicePass0, "next": alicePass,
|
||||
}, nil)
|
||||
|
||||
key := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance")
|
||||
uc := &userClient{
|
||||
api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: key}},
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
"""svc.sh 实例身份回归(U1 设计 §7 T1–T6,manifest 治理)。
|
||||
"""svc.sh 实例身份回归(U1 设计 §7 T1–T6 + I3 DB marker,manifest 治理)。
|
||||
|
||||
覆盖:连续裸启停身份稳定(T1)、只改端口锚点不动(T2)、数据根漂移拒绝(T3)、
|
||||
迁移窗口/损坏阻塞不猜测(T4)、marker 缺失/外宾/manifest 丢失均为事件(T5)、
|
||||
cwd 无关性(T6)、原子写崩溃窗口单元级(原文件不受损)。
|
||||
桩二进制与夹具复用 tests/test_svc_recover.py。真实 PG 身份专项(DB marker SQL 层)
|
||||
NOT_RUN:需真实实例,SVC_TEST_PG=1 且具备环境时另行执行,本文件不模拟。
|
||||
cwd 无关性(T6)、SQLite DB marker ensure/错 id 阻塞(I3 桩路径)、原子写崩溃窗口。
|
||||
桩二进制与夹具复用 tests/test_svc_recover.py。真实 PG 身份专项(跨库)在
|
||||
SVC_TEST_PG=1 且具备环境时执行;否则 test_Z_real_pg_not_run 显式 NOT_RUN。
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -192,12 +193,64 @@ class IdentityTests(tsr.SvcAutoRecoverTests):
|
||||
self.assertEqual(self.manifest("id9")["instance_id"], iid, "换 cwd 不得换身份")
|
||||
self.svc("id9", "stop")
|
||||
|
||||
# ---- 真实 PG 专项:显式 NOT_RUN 标注(不模拟) ----
|
||||
# ---- I3 DB marker(SQLite 桩路径;与真实 PG 跨库专项分离) ----
|
||||
|
||||
def db_identity_row(self, name):
|
||||
m = self.manifest(name)
|
||||
if not m:
|
||||
return None
|
||||
db = Path(m["identity"]["data_root"]) / "agent-system.db"
|
||||
if not db.exists():
|
||||
return None
|
||||
con = sqlite3.connect(str(db))
|
||||
try:
|
||||
rows = list(con.execute(
|
||||
"SELECT instance_id, dbref FROM agent_system_identity"))
|
||||
finally:
|
||||
con.close()
|
||||
return rows
|
||||
|
||||
def test_I3_start_ensures_sqlite_identity(self):
|
||||
self.seed_agent("idb1", 9441)
|
||||
self.start_ok("idb1")
|
||||
iid = self.manifest("idb1")["instance_id"]
|
||||
rows = self.db_identity_row("idb1")
|
||||
self.assertEqual(rows, [(iid, "sqlite")], rows)
|
||||
self.svc("idb1", "stop")
|
||||
self.start_ok("idb1") # ensure 幂等 + verify
|
||||
self.assertEqual(self.db_identity_row("idb1"), [(iid, "sqlite")])
|
||||
self.svc("idb1", "stop")
|
||||
|
||||
def test_I3_wrong_db_identity_blocks_start(self):
|
||||
self.seed_agent("idb2", 9442)
|
||||
self.start_ok("idb2")
|
||||
iid = self.manifest("idb2")["instance_id"]
|
||||
self.svc("idb2", "stop")
|
||||
db = Path(self.manifest("idb2")["identity"]["data_root"]) / "agent-system.db"
|
||||
con = sqlite3.connect(str(db))
|
||||
con.execute("DELETE FROM agent_system_identity")
|
||||
con.execute(
|
||||
"INSERT INTO agent_system_identity(instance_id,dbref,schema_version,created_at)"
|
||||
" VALUES(?,?,?,?)",
|
||||
("svc-foreign-deadbeef", "sqlite", 26, "2026-01-01T00:00:00Z"))
|
||||
con.commit()
|
||||
con.close()
|
||||
result = self.svc("idb2", "start", check=False)
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
self.assertTrue(any(k in result.stdout for k in ("DB 身份", "ensure", "mismatch", "校验失败")),
|
||||
result.stdout)
|
||||
self.assertFalse(self.running("idb2"))
|
||||
# 外宾行不得被 start 静默改写
|
||||
self.assertEqual(self.db_identity_row("idb2"),
|
||||
[("svc-foreign-deadbeef", "sqlite")])
|
||||
self.assertNotEqual(iid, "svc-foreign-deadbeef")
|
||||
|
||||
# ---- 真实 PG 专项:跨库身份;缺环境显式 NOT_RUN ----
|
||||
|
||||
def test_Z_real_pg_not_run(self):
|
||||
# DB marker(agent_system_identity 表)与跨库身份断言属真实 PG 专项,
|
||||
# 由隔离真实实例执行;本桩套件明确 NOT_RUN,不以桩冒充(设计 §7)。
|
||||
self.skipTest("真实 PG 身份专项 NOT_RUN:需真实实例(SVC_TEST_PG 环境另行执行)")
|
||||
if os.environ.get("SVC_TEST_PG") == "1" and os.environ.get("AGENT_PG_TEST_DSN"):
|
||||
self.skipTest("真 PG 专项应由隔离套件执行(本桩文件不连真实 PG)")
|
||||
self.skipTest("真实 PG 身份专项 NOT_RUN:需真实实例(SVC_TEST_PG=1 + AGENT_PG_TEST_DSN)")
|
||||
|
||||
|
||||
class AtomicWriteTests(unittest.TestCase):
|
||||
|
||||
@@ -41,6 +41,68 @@ case "${1:-}" in
|
||||
fi
|
||||
fi
|
||||
exit 0 ;;
|
||||
identity)
|
||||
# U1 I3:与真实二进制同表契约(SQLite);PG DSN 在桩上拒绝(真 PG 专项另跑)。
|
||||
shift
|
||||
sub="${1:-}"; shift || true
|
||||
iid=""; dbref=""; data="./data"; dsn=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--instance-id) iid="$2"; shift 2 ;;
|
||||
--dbref) dbref="$2"; shift 2 ;;
|
||||
--data) data="$2"; shift 2 ;;
|
||||
--dsn) dsn="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "用法:agent-system identity <ensure|verify> --instance-id ID [--dbref REF] [--data DIR] [--dsn DSN]"
|
||||
exit 0 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
if [ -z "$sub" ] || [ -z "$iid" ]; then
|
||||
echo "用法:agent-system identity <ensure|verify> --instance-id ID [--dbref REF] [--data DIR] [--dsn DSN]" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ -n "$dsn" ]; then
|
||||
echo "identity: stub 不支持 --dsn(真 PG 专项用真实二进制)" >&2
|
||||
exit 1
|
||||
fi
|
||||
[ -n "$dbref" ] || dbref=sqlite
|
||||
mkdir -p "$data"
|
||||
python3 - "$sub" "$iid" "$dbref" "$data" <<'PYID' || exit $?
|
||||
import sqlite3, sys, time
|
||||
sub, iid, dbref, data = sys.argv[1:5]
|
||||
path = data.rstrip("/") + "/agent-system.db"
|
||||
con = sqlite3.connect(path)
|
||||
ddl = (
|
||||
"CREATE TABLE IF NOT EXISTS agent_system_identity ("
|
||||
"instance_id TEXT NOT NULL PRIMARY KEY,"
|
||||
"dbref TEXT NOT NULL,"
|
||||
"schema_version INTEGER NOT NULL,"
|
||||
"created_at TEXT NOT NULL)"
|
||||
)
|
||||
con.execute(ddl)
|
||||
rows = list(con.execute("SELECT instance_id, dbref FROM agent_system_identity"))
|
||||
if sub == "ensure":
|
||||
if len(rows) > 1:
|
||||
print("identity ensure: too many rows", file=sys.stderr); sys.exit(1)
|
||||
if len(rows) == 1:
|
||||
if rows[0][0] != iid or rows[0][1] != dbref:
|
||||
print("identity ensure: mismatch", file=sys.stderr); sys.exit(1)
|
||||
print("ensured instance_id=%s dbref=%s" % (iid, dbref)); sys.exit(0)
|
||||
con.execute(
|
||||
"INSERT INTO agent_system_identity(instance_id,dbref,schema_version,created_at) VALUES(?,?,?,?)",
|
||||
(iid, dbref, 26, time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())))
|
||||
con.commit()
|
||||
print("ensured instance_id=%s dbref=%s" % (iid, dbref)); sys.exit(0)
|
||||
if sub == "verify":
|
||||
if not rows:
|
||||
print("identity verify: missing", file=sys.stderr); sys.exit(1)
|
||||
if len(rows) > 1 or rows[0][0] != iid or rows[0][1] != dbref:
|
||||
print("identity verify: mismatch", file=sys.stderr); sys.exit(1)
|
||||
print("verified instance_id=%s dbref=%s" % (iid, dbref)); sys.exit(0)
|
||||
print("identity: unknown subcommand", sub, file=sys.stderr); sys.exit(2)
|
||||
PYID
|
||||
exit $? ;;
|
||||
serve)
|
||||
shift
|
||||
case "${1:-}" in
|
||||
@@ -49,10 +111,15 @@ case "${1:-}" in
|
||||
exit 0 ;;
|
||||
esac
|
||||
printf 'args: %s\n' "$*" > "$trace_dir/serve.txt"
|
||||
addr=""
|
||||
addr=""; data="./data"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in --addr) addr="$2"; shift 2 ;; *) shift ;; esac
|
||||
case "$1" in --addr) addr="$2"; shift 2 ;; --data) data="$2"; shift 2 ;; *) shift ;; esac
|
||||
done
|
||||
# serve 二次门禁:与真实二进制 AGENT_SYSTEM_INSTANCE_ID 对齐。
|
||||
if [ -n "${AGENT_SYSTEM_INSTANCE_ID:-}" ]; then
|
||||
"$0" identity verify --instance-id "$AGENT_SYSTEM_INSTANCE_ID" \
|
||||
--dbref "${AGENT_SYSTEM_DBREF:-sqlite}" --data "$data" || exit 1
|
||||
fi
|
||||
port="${addr##*:}"; host="${addr%:*}"
|
||||
trap 'kill "$child" 2>/dev/null; exit 0' TERM INT
|
||||
python3 - "$port" "$host" <<'PYSRV' >/dev/null 2>&1 &
|
||||
|
||||
Reference in New Issue
Block a user