已合并
feat(session-manager): 场景 F 快失败——拆除有界等待队列,scope 满立即 503 SCOPE_FULL #491
feat(session-manager): 场景 F 快失败——拆除有界等待队列,scope 满立即 503 SCOPE_FULL #491
已合并
王明琦创建于 2 天前
36 个文件变更+408-768
@@ -24,11 +24,11 @@
24```bash24```bash
25cd applications/agent_runtime25cd applications/agent_runtime
26uv sync --extra local26uv sync --extra local
27-uv run pytest # 442 个用例:状态层 Lua(含 EVICT/TOUCH/ROUTE_PLACE 残骸自卫) / 路由匹配纯函数(含表达式解析) / config 层(含三段式契约与容器表水合、config_refresh 强制刷新、写库单事务+锁看门狗) / 容器规范形(container_spec) / envFrom / 组件全链路(含 sidecars 多容器与卷挂载) / HTTP 冒烟 / corner case / 双实例多副本 / 停机韧性 / 审计实锤回归(test_audit_repro) / 强制刷新自然老化(test_force_refresh) / 健壮性故障注入(test_k8s_io_timeouts / test_infra_faults / test_main_lifecycle,见 docs/feature/2026-09-robustness-hardening.md)27+uv run pytest # 433 个用例:状态层 Lua(含 EVICT/TOUCH/ROUTE_PLACE 残骸自卫) / 路由匹配纯函数(含表达式解析) / config 层(含三段式契约与容器表水合、config_refresh 强制刷新、写库单事务+锁看门狗) / 容器规范形(container_spec) / envFrom / 组件全链路(含 sidecars 多容器与卷挂载) / HTTP 冒烟 / corner case / 双实例多副本 / 停机韧性 / 审计实锤回归(test_audit_repro) / 强制刷新自然老化(test_force_refresh) / 健壮性故障注入(test_k8s_io_timeouts / test_infra_faults / test_main_lifecycle,见 docs/feature/2026-09-robustness-hardening.md)
28```28```
29 29 
30- 构造 `ServiceManager` 必须传 `deploy_mode="subprocess"`(默认 k8s 会挂死测试)。30- 构造 `ServiceManager` 必须传 `deploy_mode="subprocess"`(默认 k8s 会挂死测试)。
31-- fakeredis 陷阱:消费组 id=`"0"` bug、pubsub 需共享 FakeServer、EVAL PUBLISH 需实测31+- fakeredis 陷阱:消费组 id=`"0"` bug;所有门面共享同一 client;EVAL(Lua)依赖 lupa,缺失自动 skip
32- 双实例测试(`tests/integration/test_multi_replica.py` + `_dual_harness.py`):同进程两 App 共享一组 fakeredis/SQLite/FakeK8s,httpx ASGITransport 单事件循环驱动;lifespan 必须先手动驱动(否则 RestAdapter 惰性二建 sysctx 绕过后台 Job)。32- 双实例测试(`tests/integration/test_multi_replica.py` + `_dual_harness.py`):同进程两 App 共享一组 fakeredis/SQLite/FakeK8s,httpx ASGITransport 单事件循环驱动;lifespan 必须先手动驱动(否则 RestAdapter 惰性二建 sysctx 绕过后台 Job)。
33- 旧 SDK 已知失败用例(非回归)见外层 jiuwenclaw 仓库 CLAUDE.md 末尾清单。33- 旧 SDK 已知失败用例(非回归)见外层 jiuwenclaw 仓库 CLAUDE.md 末尾清单。
34 34 
@@ -44,7 +44,7 @@ cd applications/agent_runtime
44 `./scripts/integration_smoke.sh --image swr.cn-north-4.myhuaweicloud.com/openjiuwen/jiuwenclaw-agentserver-amd64:<tag> --sidecar-image swr.cn-north-4.myhuaweicloud.com/openjiuwen/jiuwenclaw-sandbox-amd64:<tag> --health-path /api/v1/health --sse-path /api/v1/events/stream --agent-env '{"AGENT_HTTP_ENABLED":"true","AGENT_HTTP_HOST":"0.0.0.0","AGENT_HTTP_PORT":"8086"}' --with-sidecar --with-mounts`44 `./scripts/integration_smoke.sh --image swr.cn-north-4.myhuaweicloud.com/openjiuwen/jiuwenclaw-agentserver-amd64:<tag> --sidecar-image swr.cn-north-4.myhuaweicloud.com/openjiuwen/jiuwenclaw-sandbox-amd64:<tag> --health-path /api/v1/health --sse-path /api/v1/events/stream --agent-env '{"AGENT_HTTP_ENABLED":"true","AGENT_HTTP_HOST":"0.0.0.0","AGENT_HTTP_PORT":"8086"}' --with-sidecar --with-mounts`
45 (冒烟脚本参数直通 e2e_hld_acceptance.py;替身 influxdb:1.8 仅用于快速回归,默认契约 /health+8086;真镜像门禁必须带三件套契约参数——脚本模板的 health_path/sse_path/agent_env 由这三个参数注入,漏带则 readiness 永不通过、阶段 2 起全红;`--with-sidecar --sidecar-image` 开双真镜像 sidecar 全规格,`--with-mounts` 开全量真实规格阶段——主容器 cm/hp/pvc 三挂载 + PVC 静态预置 + 逐字段断言,2026-08-28 起为门禁标配)45 (冒烟脚本参数直通 e2e_hld_acceptance.py;替身 influxdb:1.8 仅用于快速回归,默认契约 /health+8086;真镜像门禁必须带三件套契约参数——脚本模板的 health_path/sse_path/agent_env 由这三个参数注入,漏带则 readiness 永不通过、阶段 2 起全红;`--with-sidecar --sidecar-image` 开双真镜像 sidecar 全规格,`--with-mounts` 开全量真实规格阶段——主容器 cm/hp/pvc 三挂载 + PVC 静态预置 + 逐字段断言,2026-08-28 起为门禁标配)
46- 冒烟内置回归网:阶段 1b(无请求预热)、阶段 5b(自然老化零回拨)、阶段 11b(内部不变量巡检:idle⊆pods:all / idle_since 存在 / 静息 deploying=0 / 快照 deploy_ver==RM cfg)——2026-08-26 真环境实测缺陷①②④⑤的固化。46- 冒烟内置回归网:阶段 1b(无请求预热)、阶段 5b(自然老化零回拨)、阶段 11b(内部不变量巡检:idle⊆pods:all / idle_since 存在 / 静息 deploying=0 / 快照 deploy_ver==RM cfg)——2026-08-26 真环境实测缺陷①②④⑤的固化。
47-- 经多副本 LB 亦可跑(实测 65/65),前提:部署带 `AGENT_RUNTIME_SCOPE_FULL_TIMEOUT`(deploy 模板默认 8,**须显著小于模板 session_ttl**,否则等待者 deadline 与会话到期碰撞产生混合结果);排查实录见 `docs/spec/e2e-test-cases.md` §8.1。47+- 经多副本 LB 亦可跑(实测 65/65);历史排查实录见 `docs/spec/e2e-test-cases.md` §8.1。
48- cleanup 空目标必须用**无匹配 label_selector**,不得指向业务 ns(同 label 真实 AgentServer 会被误删)或不存在的 ns(in-cluster SA 对其 403 而非空列表)。48- cleanup 空目标必须用**无匹配 label_selector**,不得指向业务 ns(同 label 真实 AgentServer 会被误删)或不存在的 ns(in-cluster SA 对其 403 而非空列表)。
49 49 
50### 多副本(真环境)50### 多副本(真环境)
Mapplications/agent_runtime/README.md+8-10文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/deploy/agent_runtime.env.example+0-3文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/deploy/agent_runtime.template.yaml+0-5文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/docs/design/Agent-Runtime-HLD.md+34-46文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/docs/design/session-manager-design.md+17-48文件内容审核中,请稍后刷新重试
Aapplications/agent_runtime/docs/feature/2026-09-scope-full-fastfail.md+105-0文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/docs/feature/README.md+1-0文件内容审核中,请稍后刷新重试
@@ -10,11 +10,11 @@
10|---|---|---|10|---|---|---|
11| 本文件 | 架构一页纸 + 键前缀总览 + 测试/部署入口 | 先读 |11| 本文件 | 架构一页纸 + 键前缀总览 + 测试/部署入口 | 先读 |
12| [service-core.md](service-core.md) | 组装(main)/CLI/配置(`AGENT_RUNTIME_*`)/错误码契约/字段分类/工具/部署 | 改装配、配置、错误契约、部署时 |12| [service-core.md](service-core.md) | 组装(main)/CLI/配置(`AGENT_RUNTIME_*`)/错误码契约/字段分类/工具/部署 | 改装配、配置、错误契约、部署时 |
13-| [session-manager.md](session-manager.md) | SM:route/touch/config_sync/config_refresh/cleanup 编排、7 个 Lua、SM 键表 | 改会话编排/配置层时 |13+| [session-manager.md](session-manager.md) | SM:route/touch/config_sync/config_refresh/cleanup 编排、6 个 Lua、SM 键表 | 改会话编排/配置层时 |
14| [resource-manager.md](resource-manager.md) | RM:acquire/后台任务/K8s 适配、6 个 Lua、RM 键表 | 改 Pod 池/扩缩容/清理时 |14| [resource-manager.md](resource-manager.md) | RM:acquire/后台任务/K8s 适配、6 个 Lua、RM 键表 | 改 Pod 池/扩缩容/清理时 |
15| [e2e-test-cases.md](e2e-test-cases.md) | 全部 e2e 用例的场景/输入/预期输出 | 写或跑 e2e 时 |15| [e2e-test-cases.md](e2e-test-cases.md) | 全部 e2e 用例的场景/输入/预期输出 | 写或跑 e2e 时 |
16| `../design/Agent-Runtime-HLD.md` | 架构总览/接口契约/场景 A–N/Redis 键表(语义权威) | 语义不确定时 |16| `../design/Agent-Runtime-HLD.md` | 架构总览/接口契约/场景 A–N/Redis 键表(语义权威) | 语义不确定时 |
17-| `../design/session-manager-design.md` | SM 详细设计(7 个 Lua 全文) | 深挖 SM 设计动机 |17+| `../design/session-manager-design.md` | SM 详细设计(6 个 Lua 全文) | 深挖 SM 设计动机 |
18| `../design/resource-manager-design.md` | RM 详细设计(6 个 Lua 全文) | 深挖 RM 设计动机 |18| `../design/resource-manager-design.md` | RM 详细设计(6 个 Lua 全文) | 深挖 RM 设计动机 |
19 19 
20## 一页纸架构20## 一页纸架构
@@ -38,7 +38,7 @@ claw mgr ──config_sync──► ├─ session_manager 持 App,5 个 HTT
38## Redis 键前缀总览(逐键明细见各模块 spec)38## Redis 键前缀总览(逐键明细见各模块 spec)
39 39 
40```40```
41-{session_manager}:… SM 编排态(会话四处/scope 闸门/等待队列/候选集/注册表/路由快照)41+{session_manager}:… SM 编排态(会话四处/scope 闸门/候选集/注册表/路由快照)
42{resource_manager}:… RM 编排态(per-scope Pod 池/idle 暖池/deploy 占位/follower 等待室/选主锁)42{resource_manager}:… RM 编排态(per-scope Pod 池/idle 暖池/deploy 占位/follower 等待室/选主锁)
43agent_runtime:job:… 后台任务选主执行锁(main.py:_build_jobs 注册)43agent_runtime:job:… 后台任务选主执行锁(main.py:_build_jobs 注册)
44{agent_runtime:job:…}:winner/candidates:… 选主抽签键(与执行锁同底,hash tag 同槽)44{agent_runtime:job:…}:winner/candidates:… 选主抽签键(与执行锁同底,hash tag 同槽)
@@ -16,7 +16,7 @@
16| 层 | 入口 | 规模 | 依赖环境 | 退出码 |16| 层 | 入口 | 规模 | 依赖环境 | 退出码 |
17|---|---|---|---|---|17|---|---|---|---|---|
18| 进程内双实例 | `uv run pytest tests/integration/test_multi_replica.py` | 14 用例 | 无(离线,fakeredis) | pytest 标准 |18| 进程内双实例 | `uv run pytest tests/integration/test_multi_replica.py` | 14 用例 | 无(离线,fakeredis) | pytest 标准 |
19-| **审计实锤回归网** | `uv run pytest tests/integration/test_audit_repro.py` | 16 用例 | 无(离线,fakeredis) | pytest 标准 |19+| **审计实锤回归网** | `uv run pytest tests/integration/test_audit_repro.py` | 14 用例 | 无(离线,fakeredis) | pytest 标准 |
20| 集成冒烟(M6) | `./scripts/integration_smoke.sh`(sidecar 阶段加 `--with-sidecar`,全量规格加 `--with-mounts`) | 121 项断言(全规格形态;2026-09-01 真镜像 0.0.9s 门禁 121/121) | 单实例 server 模式 + 真 Redis/MySQL/K8s | 0/1/2 |20| 集成冒烟(M6) | `./scripts/integration_smoke.sh`(sidecar 阶段加 `--with-sidecar`,全量规格加 `--with-mounts`) | 121 项断言(全规格形态;2026-09-01 真镜像 0.0.9s 门禁 121/121) | 单实例 server 模式 + 真 Redis/MySQL/K8s | 0/1/2 |
21| 多副本 e2e(M7) | `uv run --no-sync python scripts/e2e_multi_replica.py` | 35 项断言 | K8s 多副本 + Service LB + 真 Redis | 0/1/2 |21| 多副本 e2e(M7) | `uv run --no-sync python scripts/e2e_multi_replica.py` | 35 项断言 | K8s 多副本 + Service LB + 真 Redis | 0/1/2 |
22| 压测/浸泡 | `uv run --no-sync python scripts/load_test.py` | 3 场景 | 任意入口(建议 LB) | 0/1 |22| 压测/浸泡 | `uv run --no-sync python scripts/load_test.py` | 3 场景 | 任意入口(建议 LB) | 0/1 |
@@ -53,8 +53,7 @@
53 53 
54| error_code | HTTP | retry_after | 语义 |54| error_code | HTTP | retry_after | 语义 |
55|---|---|---|---|55|---|---|---|---|
56-| `SCOPE_QUEUE_FULL` | 503 | ✅ | 等待队列满,快失败 |56+| `SCOPE_FULL` | 503 | ✅ | scope /达总容量,立即快失败(2026-09 起;原 503 队列满/504 等待超时两码已随等待队列拆除;总预算超限同属过载,粗化为 NO_POD_AVAILABLE) |
57-| `SCOPE_FULL_TIMEOUT` | 504 | ✅ | 队列内等待超时(2026-09 起另有推导式总预算 `scope_full_timeout+ready_timeout+10s`,need_acquire 无上界循环同款 504;冷部署不受队列预算误伤) |
58| `NO_POD_AVAILABLE` | 503 | ✅ | acquire 失败(MaxPodsReached/DeployFailed 映射) |57| `NO_POD_AVAILABLE` | 503 | ✅ | acquire 失败(MaxPodsReached/DeployFailed 映射) |
59| `STATE_UNAVAILABLE` | 503 | ✅ | 状态后端(Redis/DB)连接级故障,handler 层翻译(2026-09;真环境注入手段有限,单测 `test_infra_faults.py` 覆盖) |58| `STATE_UNAVAILABLE` | 503 | ✅ | 状态后端(Redis/DB)连接级故障,handler 层翻译(2026-09;真环境注入手段有限,单测 `test_infra_faults.py` 覆盖) |
60| `CONFIG_NOT_FOUND` | 503 | ❌ | 无匹配规则/模板禁用 |59| `CONFIG_NOT_FOUND` | 503 | ❌ | 无匹配规则/模板禁用 |
@@ -63,8 +62,7 @@
63 62 
64### 2.3 Redis 真相源(e2e 直接断言的键)63### 2.3 Redis 真相源(e2e 直接断言的键)
65 64 
66-- `{session_manager}:scope:{sid}:sessions`(SET,SCARD=scope 闸门)/`:pods`(ZSET,first-fit 候选)65+- `{session_manager}:scope:{sid}:sessions`(SET,SCARD=scope 闸门)/`:pods`(ZSET,first-fit 候选);
67- /`:waiters`(**ZSET**,score=deadline;2026-08-28 起 SET→ZSET,断言用 ZCARD);
68 `{session_manager}:routing:snapshot`(STRING,路由快照)66 `{session_manager}:routing:snapshot`(STRING,路由快照)
69- `{session_manager}:session:{sid}`(HASH)/`session_expiry`(ZSET)/`pods:registered`(SET)67- `{session_manager}:session:{sid}`(HASH)/`session_expiry`(ZSET)/`pods:registered`(SET)
70- `{resource_manager}:resource:scope:{sid}:pods|idle|config|deploying`(deploying 为 **ZSET**,68- `{resource_manager}:resource:scope:{sid}:pods|idle|config|deploying`(deploying 为 **ZSET**,
@@ -229,7 +227,7 @@ sidecar 镜像随 `--sidecar-image` 分流:真 jiuwenbox 用 8321 + `JIUWENBOX_L
229 227 
230| # | 输入 | 预期(内部状态变迁) |228| # | 输入 | 预期(内部状态变迁) |
231|---|---|---|229|---|---|---|
232-| 18 | —(自然到期,阶段 2 落位起 ≤45s) | `scope:sessions` 清空(sweeper 1s tick 扫 `session_expiry` 到期集→逐个 EVICT+PUBLISH free) |230+| 18 | —(自然到期,阶段 2 落位起 ≤45s) | `scope:sessions` 清空(sweeper 1s tick 扫 `session_expiry` 到期集→逐个 EVICT) |
233| 19 | — | 会话四处全清(session HASH/expiry/pod 集/scope 集) |231| 19 | — | 会话四处全清(session HASH/expiry/pod 集/scope 集) |
234| 20 | — | 空 Pod pass → idle_consider → RM `idle` 暖池 2 个(空闲 Pod 回暖池等待回收/复用) |232| 20 | — | 空 Pod pass → idle_consider → RM `idle` 暖池 2 个(空闲 Pod 回暖池等待回收/复用) |
235| 21 | — | 不变量 5:`pods:registered` 仍 2 个(`--with-sidecar` 时 3,box Pod 长存;`--with-mounts` 时再 +1,mnt Pod 2c 已 route;待 RM 回收后清) |233| 21 | — | 不变量 5:`pods:registered` 仍 2 个(`--with-sidecar` 时 3,box Pod 长存;`--with-mounts` 时再 +1,mnt Pod 2c 已 route;待 RM 回收后清) |
@@ -267,16 +265,16 @@ sidecar 镜像随 `--sidecar-image` 分流:真 jiuwenbox 用 8321 + `JIUWENBOX_L
267 265 
268> 留白:create 成功但不 Ready 的失败形态(审计网 C4a)与取消中途形态(C4b)在替身镜像下不可构造(不可拉镜像 create 后立即 Pending,走同一条超时路径但物理清理断言需 FakeK8s);物理孤儿 Pod 的兜底删除未在此断言。266> 留白:create 成功但不 Ready 的失败形态(审计网 C4a)与取消中途形态(C4b)在替身镜像下不可构造(不可拉镜像 create 后立即 Pending,走同一条超时路径但物理清理断言需 FakeK8s);物理孤儿 Pod 的兜底删除未在此断言。
269 267 
270-**阶段 7:F 容量满/队列**(前置:e2e-f(cc=2/pc=1)空闲;max_waiters=2×cc=4;5 项)268+**阶段 7:F 容量满快失败**(前置:e2e-f(cc=2/pc=1)空闲;5 项;2026-09 起无等待队列)
271 269 
272| # | 输入 | 预期(内部状态变迁) |270| # | 输入 | 预期(内部状态变迁) |
273|---|---|---|271|---|---|---|
274| 29–30 | `route(f1/f2, e2e-f)` 串行 | 各 200,2 Pod 占满(scope 额度 2/2) |272| 29–30 | `route(f1/f2, e2e-f)` 串行 | 各 200,2 Pod 占满(scope 额度 2/2) |
275-| 31 | 5 并发 `route(f-over-0..4)` | ≥1 503 `SCOPE_QUEUE_FULL`(队列满快失败:LUA_WAITER_GATE 先清过 ZADD 先行+超限自退) |273+| 31 | 5 并发 `route(f1,f2,f-over-0..2)`(前 2 个为亲和续期) | **恰好 2×200(亲和续期)+ 503 `SCOPE_FULL`**(Lua 闸门毫秒级拒新会话;用亲和续 200 是为冷突发下确定性——并发冷 `need_acquire` 输家得 NO_POD_AVAILABLE 而非 SCOPE_FULL) |
276-| 32 | 同上 | ≥2 504 `SCOPE_FULL_TIMEOUT`(队列内等待至 deadline;信号唤醒或 0.5s 轮询重仲裁 route_place 为唯一仲裁) |274+| 32 | 同上 | 5 并发总耗时 <1s(等待);拒绝响应 `retry_after` |
277-| 33 | — | `scope:{FSCOPE}:waiters` ZCARD=0(等待者 finally 出队) |275+| 33 | — | `scope:{FSCOPE}:waiters` 键不存在(2026-09 拆除净空:拒绝路径零写入) |
278 276 
279-> 留白:丢唤醒窗口(publish 早于 subscribe)与崩溃遗留 waiter deadline 自清在审计网 C7/C9 覆盖(需确定性序注入);evict 唤醒等待者后「被唤醒者抢到/抢不到」两种落点未分别断言277+> 历史:2026-08 版阶段 7 断言 503 队列满 + 504 等待超,随等待队列拆除作废(见 §8.1 历史化记录与 docs/feature/2026-09-scope-full-fastfail.md)
280 278 
281**阶段 8:H min_idle 热备**(前置:e2e-warm(min_idle=1)、阶段 1b 已预热 1 个、w1 未路由;3 项)279**阶段 8:H min_idle 热备**(前置:e2e-warm(min_idle=1)、阶段 1b 已预热 1 个、w1 未路由;3 项)
282 280 
@@ -347,9 +345,9 @@ sidecar 镜像随 `--sidecar-image` 分流:真 jiuwenbox 用 8321 + `JIUWENBOX_L
347| 47 | — | `{session_manager}:pod:*` 注册态全清 |345| 47 | — | `{session_manager}:pod:*` 注册态全清 |
348 346 
349**阶段 12b:表达式 or 支(清场后确定性验证,1 项)**:原位置在阶段 2 尾,但彼时 e2e-main 已被347**阶段 12b:表达式 or 支(清场后确定性验证,1 项)**:原位置在阶段 2 尾,但彼时 e2e-main 已被
350-s1–s3 占满(cc=3),or 支 route 只能排队 504——原断言仅在「部署慢、会话先过期」时序下碰巧348+s1–s3 占满(cc=3),or 支 route 只能 503 快失败拒绝——原断言仅在「部署慢、会话先过期」时序下
351-200(2026-08-27 快跑实测暴露,镜像预分发后跑必现);移到阶段 12 清场后(Pod/会话全空、配置349+碰巧 200(2026-08-27 快跑实测暴露,时为排队 504;2026-09 起为失败);移到阶段 12 清场后
352-仍在)e2e-main 空闲,确定性 200:350+(Pod/会话全空、配置仍在)e2e-main 空闲,确定性 200:
353 351 
354| # | 场景 | 输入 | 预期输出/断言 |352| # | 场景 | 输入 | 预期输出/断言 |
355|---|---|---|---|353|---|---|---|---|
@@ -418,10 +416,10 @@ s1–s3 占满(cc=3),or 支 route 只能排队 504——原断言仅在「部署
418| # | 输入 | 预期 |416| # | 输入 | 预期 |
419|---|---|---|417|---|---|---|
420| 1–2 | 串行 `route(mr-f1/mr-f2)` | 各 200,占满 |418| 1–2 | 串行 `route(mr-f1/mr-f2)` | 各 200,占满 |
421-| 3 | 8 并发 `route(mr-burst-*)` 经 LB | **0 200**(闸门跨副本全局生效) |419+| 3 | 8 并发 `route(mr-f1,mr-f2,mr-burst-0..5)` 经 LB(前 2 个亲和续期) | **恰好 200 + 6×503 `SCOPE_FULL`**(闸门跨副本全局生效;2026-09 起无等待队列) |
422-| 4 | 同上 | 4×503 `SCOPE_QUEUE_FULL` + 4×504 `SCOPE_FULL_TIMEOUT`(max_waiters=4;30s 超属预期) |420+| 4 | 同上 | 拒绝均带 retry_after;总耗<1s |
423| 5 | — | `scope:sessions` SCARD=2(不超收) |421| 5 | — | `scope:sessions` SCARD=2(不超收) |
424-| 6 | —(≤45s 轮询) | waiters 清空(ZCARD=0;残留时打印成员便于定位) |422+| 6 | — | `scope:{scope}:waiters` 键不存在(拆除净空) |
425| 7 | — | `deploying` ZCARD=0(占位清空) |423| 7 | — | `deploying` ZCARD=0(占位清空) |
426 424 
427**S5 幂等跨副本重放**(2 项):425**S5 幂等跨副本重放**(2 项):
@@ -466,20 +464,19 @@ httpx ASGITransport 单事件循环并发驱动。**输入全部走完整 HTTP**
466|---|---|---|---|464|---|---|---|---|
467| 1 | 身份与共享态 | route 经 A,touch 经 B | instance_id 互异且 RM 镜像;B touch 到 A 建的会话 `touched=true` |465| 1 | 身份与共享态 | route 经 A,touch 经 B | instance_id 互异且 RM 镜像;B touch 到 A 建的会话 `touched=true` |
468| 2 | 交替亲和 | 同 session A→B→A→B route | 恒同 Pod;SCARD=1 |466| 2 | 交替亲和 | 同 session A→B→A→B route | 恒同 Pod;SCARD=1 |
469-| 3 | 跨副本突发不超收 | cc=2/pc=1 占满后 8 并发交替 A/B | 0×200;4×503 队列满 + 4×504 超时;终态 SCARD=2、waiters=0、deploying=0 |467+| 3 | 跨副本突发不超收 | cc=2/pc=1 占满后 8 并发(2 亲和续期+6 新)交替 A/B | 恰好 2×200 + 6×503 SCOPE_FULL 快失败;终态 SCARD=2、waiters、deploying=0 |
470| 4 | deploy 锁串行化 + follower 复用 | SlowFakeK8s(deploy 0.4s),A/B 并发冷启动 + 追加 s3 | 并发对**恰好 1 次部署**(输家进等待室复用同 Pod);pod1 满后 s3 才第 2 次部署;窗口零重叠;占位/等待室清空 |468| 4 | deploy 锁串行化 + follower 复用 | SlowFakeK8s(deploy 0.4s),A/B 并发冷启动 + 追加 s3 | 并发对**恰好 1 次部署**(输家进等待室复用同 Pod);pod1 满后 s3 才第 2 次部署;窗口零重叠;占位/等待室清空 |
471| 5 | 输家复用暖 Pod | 手持 deploy 锁 + 后台注册 idle Pod 后释放;A route | 返回他副本 Pod;本侧零部署;占位清空 |469| 5 | 输家复用暖 Pod | 手持 deploy 锁 + 后台注册 idle Pod 后释放;A route | 返回他副本 Pod;本侧零部署;占位清空 |
472-| 6 | 跨副本唤醒 | A 占满→A 排队→回拨过期→**B** touch | B 的 touch 返回 `touched=false`(惰性驱逐);A 的等待者 <2s 被唤醒并占释放额度 |470+| 6 | 幂等跨副本 | request_id A 首发、B 重放 | 响应一致;仅一会话 |
473-| 7 | 幂等跨副本 | request_id A 首发、B 重放 | 响应一致;仅一会话 |471+| 7 | 配置失效传播 | B session_ttl,A route 新会话 | 缓存即 DEL;会话 expiry=now+90 |
474-| 8 | 配置失效传播 | B session_ttl,A 再 route 新会话 | 缓存即 DEL;新会话 expiry=now+90 |472+| 8 | 单选主验证 | 采样 sm_sweep/rm_autoscale 5.5s | epoch winner∈candidates;candidates 并集=双实例(winner 轮换仅记录) |
475-| 9 | 单选主验证 | 采样 sm_sweep/rm_autoscale 5.5s | epoch winner∈candidates;candidates 并集=双实例(winner 轮换仅记录) |473+| 9 | sweeper 互斥 | 手持 lock:sweep A sweep_once | 直退不误扫;锁释放后补扫完成 |
476-| 10 | sweeper 互斥 | 手持 lock:sweep 后 A sweep_once | 直退不误扫;锁释放后补扫完成 |474+| 10 | 并发收敛 | A/B sweep_once 并发 gather | 无异常;全部老化;正常释放;`pods:registered` 不变 |
477-| 11 | 并发收敛 | A/B sweep_once 并发 gather | 无异常;全部老化;锁正常释放;`pods:registered` 不变 |475+| 11 | /healthz | 分别 GET App | 200 + 各自 instance_id |
478-| 12 | /healthz | 分别 GET App | 200 + 各自 instance_id |476+| 12 | follower 上限严格快失败 | cc=8/pc=2,4 并发冷启动(deploy 0.4s) | 200(同 Pod)+ 2×503 NO_POD_AVAILABLE(闸门拒);恰好 1 次部署;占位/等待室清空 |
479-| 13 | follower 上限严格快失败 | cc=8/pc=2,4 并发冷启动(deploy 0.4s) | 2×200(同 Pod)+ 2×503 NO_POD_AVAILABLE(闸门拒);恰好 1 次部署;占位/等待室清 |477+| 13 | leader 失败 follower 不接管 | deploy 慢速失败(0.5s 后抛),2 并发 | 503 NO_POD_AVAILABLE;占位/等待室清 |
480-| 14 | leader 失败 follower 不接管 | deploy 慢速失败(0.5s 后抛),2 并发 | 双 503 NO_POD_AVAILABLE;占位/等待室全清 |
481 478 
482-### 5.2 审计实锤回归网:`tests/integration/test_audit_repro.py`(16 用例)479+### 5.2 审计实锤回归网:`tests/integration/test_audit_repro.py`(14 用例;C7/C9 已随 2026-09 场景 F 快失败拆除——等待机制不存在,缺陷面消失)
483 480 
4842026-08-27 全量审计(五维:状态层原子性/停机并发/外部契约/测试缺口/业务语义)产出4812026-08-27 全量审计(五维:状态层原子性/停机并发/外部契约/测试缺口/业务语义)产出
485~40 条假设,先以本套用例**实锤**再修(16/16 全部成立;修复后全绿转回归网)。482~40 条假设,先以本套用例**实锤**再修(16/16 全部成立;修复后全绿转回归网)。
@@ -502,19 +499,17 @@ FakeK8s 忽略探测参数的保真度缺口(按 (ip,port,path) 判定——2026
502| C4b | deploy 取消不留物理孤儿(C4) | 替身在 create 后卡在等 Ready 窗口 | 0.3s 后 cancel(优雅停机语义) | Redis 占位清(⑤)+ 物理 Pod 清(deploy 层清理契约) |499| C4b | deploy 取消不留物理孤儿(C4) | 替身在 create 后卡在等 Ready 窗口 | 0.3s 后 cancel(优雅停机语义) | Redis 占位清(⑤)+ 物理 Pod 清(deploy 层清理契约) |
503| C5 | REGISTER 步失败不占容量(C5) | max_pods=2/pod_concurrency=1;注入 REGISTER 步抛错一次 | 三连 route(第 1 个触发注错) | 第 3 个会话仍能扩到 2 个真实 Pod——注册步失败不得永久虚占 max_pods |500| C5 | REGISTER 步失败不占容量(C5) | max_pods=2/pod_concurrency=1;注入 REGISTER 步抛错一次 | 三连 route(第 1 个触发注错) | 第 3 个会话仍能扩到 2 个真实 Pod——注册步失败不得永久虚占 max_pods |
504| C6 | 死 Pod 不回放(C6) | 会话 Pod 经 watch 判死 PURGE(注册全清) | 同 `request_id` 重试 route(网关幂等重试语义) | 拿到**新** Pod——idem 命中须校验存活,不得复活死 Pod 喂死地址 |501| C6 | 死 Pod 不回放(C6) | 会话 Pod 经 watch 判死 PURGE(注册全清) | 同 `request_id` 重试 route(网关幂等重试语义) | 拿到**新** Pod——idem 命中须校验存活,不得复活死 Pod 喂死地址 |
505-| C7 | 丢唤醒后轮询重仲裁(C7) | scope 满(cc=1),sess_2 入队等待 | 注入:释放信号恰在 subscribe 完成前发布(天然并发窗口) | ≤0.5s 轮询重仲裁拿到空闲额度,而非空等满 2s 后 504 |
506| C8 | 亲和 Pod 被清后换绑(C8) | 按 notify_pod_dead 真实顺序驱动窗口:evict 已枚举会话→窗口内新会话落上该 Pod→cleanup 收口(info 已清) | 再 route 该会话(线程+硬 join 有界 5s) | 2s 内换绑新 Pod——refresh 须有存活守卫,不得无限自旋(每圈续期 expiry 的绝症) |502| C8 | 亲和 Pod 被清后换绑(C8) | 按 notify_pod_dead 真实顺序驱动窗口:evict 已枚举会话→窗口内新会话落上该 Pod→cleanup 收口(info 已清) | 再 route 该会话(线程+硬 join 有界 5s) | 2s 内换绑新 Pod——refresh 须有存活守卫,不得无限自旋(每圈续期 expiry 的绝症) |
507-| C9 | 幽灵 waiter 自清(C9) | scope 满;经真实闸门塞 2 个 **deadline 已过期**的幽灵 waiter(模拟崩溃副本遗留) | 新请求 route | 能入队、等满 0.5s 得 504——而非被永久 503 SCOPE_QUEUE_FULL |
508| C10a | 畸形 int 400(C10) | 配置在 | config_sync `session_ttl="abc"` | 400 VALIDATION——int 畸形不得裸抛 ValueError 成 500 |503| C10a | 畸形 int 400(C10) | 配置在 | config_sync `session_ttl="abc"` | 400 VALIDATION——int 畸形不得裸抛 ValueError 成 500 |
509-| C10b | 0 值策略字段拒绝(C10) | 配置在 | config_sync `pod_concurrency=0` | 400 VALIDATION——0 值是拒绝服务配置(满 max_pods 个必用不上的 Pod 后永久 scope_full) |504+| C10b | 0 值策略字段拒绝(C10) | 配置在 | config_sync `pod_concurrency=0` | 400 VALIDATION——0 值是拒绝服务配置(满 max_pods 个必用不上的 Pod 后每个 route 恒 503 SCOPE_FULL) |
510| C11 | 幻影 scope 收敛(C11) | 双 scope 下发;注入 drain 推送(min_idle=0 无 spec)失败一次(模拟滚动重启中断扩散③) | 删 scope 后 2×(sync+autoscale) | 幻影 scope 零 Pod——drain 目标集以 RM 已知 scope 为真源,重试必补推 |505| C11 | 幻影 scope 收敛(C11) | 双 scope 下发;注入 drain 推送(min_idle=0 无 spec)失败一次(模拟滚动重启中断扩散③) | 删 scope 后 2×(sync+autoscale) | 幻影 scope 零 Pod——drain 目标集以 RM 已知 scope 为真源,重试必补推 |
511| C12 | 重试补跑软摘除(C12) | 会话在老版本 Pod 上;注入软摘除步失败一次(写 DB 后) | 同载荷重试 config_sync + route 新会话 | 新会话不落旧版本 Pod——候选集版本收敛是声明式每拍重算,不由 diff 驱动 |506| C12 | 重试补跑软摘除(C12) | 会话在老版本 Pod 上;注入软摘除步失败一次(写 DB 后) | 同载荷重试 config_sync + route 新会话 | 新会话不落旧版本 Pod——候选集版本收敛是声明式每拍重算,不由 diff 驱动 |
512| C13 | 路径归一(C13) | — | config_sync `sse_path="api/v1/stream"`(缺前导 /) | URL `port=8080` 可解析且 path=/api/v1/stream——缺 / 会拼出 `http://ip:8080api/...` 非法端口 → 健康 Pod 被探死循环 |507| C13 | 路径归一(C13) | — | config_sync `sse_path="api/v1/stream"`(缺前导 /) | URL `port=8080` 可解析且 path=/api/v1/stream——缺 / 会拼出 `http://ip:8080api/...` 非法端口 → 健康 Pod 被探死循环 |
513 508 
514> **本层留白(需真环境/真镜像)**:C3/C4/C13 的替身保真度依赖可编程 K8s 双打——509> **本层留白(需真环境/真镜像)**:C3/C4/C13 的替身保真度依赖可编程 K8s 双打——
515> 真 K8s 的删除异步性、CrashLoopBackOff 判死、真 AgentServer 的 health 契约仍只在510> 真 K8s 的删除异步性、CrashLoopBackOff 判死、真 AgentServer 的 health 契约仍只在
516-> 门禁层可见;C9 的硬崩形态(kill -9 遗留 waiter)在本层用过期 deadline 模拟,511+> 门禁层可见;硬崩遗留(RM deploy 占位)在本层用 deadline 过期模拟,真进程硬崩的
517-> 真进程硬崩的端到端未覆盖;reclaim 与 acquire 的 TOCTOU(在用 Pod 被回收)两轮512+> 端到端未覆盖(SM waiter 面 2026-09 已拆除);reclaim 与 acquire 的 TOCTOU(在用 Pod 被回收)两轮
518> 审计均确认存在但确定性复现需真时序,列为已知 P1 遗留(feature 记录遗留清单)。513> 审计均确认存在但确定性复现需真时序,列为已知 P1 遗留(feature 记录遗留清单)。
519 514 
520### 5.3 强制刷新自然老化网:`tests/integration/test_force_refresh.py`(4 用例)515### 5.3 强制刷新自然老化网:`tests/integration/test_force_refresh.py`(4 用例)
@@ -536,13 +531,12 @@ FakeK8s 忽略探测参数的保真度缺口(按 (ip,port,path) 判定——2026
536|---|---|---|531|---|---|---|
537| `route` | 每 scope 50 并发容量,8 会话/scope 轮转 route | 全 200;p50/p90/p99 报告;冷启动 max≈deploy 等待 |532| `route` | 每 scope 50 并发容量,8 会话/scope 轮转 route | 全 200;p50/p90/p99 报告;冷启动 max≈deploy 等待 |
538| `route_touch` | 同上 + 半数请求 touch 保活 | 同上(实测 2 副本 LB:16186 请求**零错误**,p50 7.3ms,p99 24.3ms) |533| `route_touch` | 同上 + 半数请求 touch 保活 | 同上(实测 2 副本 LB:16186 请求**零错误**,p50 7.3ms,p99 24.3ms) |
539-| `queued` | cc=2/pc=2 小容量模板 | 直方图出现 `SCOPE_QUEUE_FULL`/`SCOPE_FULL_TIMEOUT` **属预期**(排队路径被刻意打到),只报告不判败 |534+| `queued` | cc=2/pc=2 小容量模板 | 直方图出现 503 `SCOPE_FULL` **属预期**(容量满快失败路径被刻意打到),只报告不判败 |
540 535 
541- 速率:闭环(并发全速)或开环(`--rps` 令牌桶);`--duration` 长 → 浸泡536- 速率:闭环(并发全速)或开环(`--rps` 令牌桶);`--duration` 长 → 浸泡
542 (`--report-interval` 周期增量报告);Ctrl-C 优雅部分报告。537 (`--report-interval` 周期增量报告);Ctrl-C 优雅部分报告。
543- 安全边界:全程只走 HTTP,无 FLUSHDB、默认不调 cleanup 端点(会删 ns 下全部538- 安全边界:全程只走 HTTP,无 FLUSHDB、默认不调 cleanup 端点(会删 ns 下全部
544 AgentServer Pod);模板/规则/组按 run-id 命名空间化,靠 TTL 老化。539 AgentServer Pod);模板/规则/组按 run-id 命名空间化,靠 TTL 老化。
545-- 服务侧注意:每排队请求持一条 Redis pubsub 连接(`maxclients` 默认 10k)。
546 540 
547---541---
548 542 
@@ -595,7 +589,7 @@ uv run --no-sync python scripts/load_test.py \
595 589 
596## 8. 已知语义差异与暂缓项590## 8. 已知语义差异与暂缓项
597 591 
598-### 8.1 「经多副本 LB 跑 M6 冒烟 63/65」排查实录(2026-08-18,已全部修复)592+### 8.1 「经多副本 LB 跑 M6 冒烟 63/65」排查实录(2026-08-18,已全部修复;**2026-09 历史化**——第 1 条的等待队列机制已随场景 F 快失败整体拆除,该变量/碰撞问题不复存在;第 2 条 cleanup 403/404 语义仍有效)
599 593 
600两处失败当初被初步归因为「多副本冷突发语义」,**深入排查后证明均另有根因**——594两处失败当初被初步归因为「多副本冷突发语义」,**深入排查后证明均另有根因**——
601都是「该集群部署与单实例环境的配置差异」,修复后经 LB 稳定 **65/65**:595都是「该集群部署与单实例环境的配置差异」,修复后经 LB 稳定 **65/65**:
@@ -644,8 +638,8 @@ uv run --no-sync python scripts/load_test.py \
644 (§5.2 C3 在替身层已覆盖逻辑,真 AgentServer 契约须真镜像门禁)。638 (§5.2 C3 在替身层已覆盖逻辑,真 AgentServer 契约须真镜像门禁)。
645- deploy 失败/取消的物理孤儿在真 K8s 的清理(§5.2 C4 为替身双打;真 K8s 的639- deploy 失败/取消的物理孤儿在真 K8s 的清理(§5.2 C4 为替身双打;真 K8s 的
646 删除异步性、节点驱逐形态未覆盖)。640 删除异步性、节点驱逐形态未覆盖)。
647-- 进程硬崩(kill -9)遗留的端到端自愈:waiter/占位 deadline 在本层以过期641+- 进程硬崩(kill -9)遗留的端到端自愈:RM deploy 占位 deadline 在本层以过期
648- deadline 模拟,真进程硬崩 + 重启收敛未覆盖。642+ deadline 模拟,真进程硬崩 + 重启收敛未覆盖(SM waiter 面 2026-09 已拆除)
649- 依赖故障注入:Redis 运行中闪断、DB 不可达时的 route/config_sync/启动降级、643- 依赖故障注入:Redis 运行中闪断、DB 不可达时的 route/config_sync/启动降级、
650 K8s API 5xx/超时、create 409 名字冲突——全部零覆盖。644 K8s API 5xx/超时、create 409 名字冲突——全部零覆盖。
651- 真实配置形状的**镜像侧行为**(2026-08-28 阶段 0m/2c 已把配置渲染与挂载链路645- 真实配置形状的**镜像侧行为**(2026-08-28 阶段 0m/2c 已把配置渲染与挂载链路
@@ -662,11 +656,11 @@ uv run --no-sync python scripts/load_test.py \
662- reclaim 与 acquire 复用的 TOCTOU(在用 Pod 被物理删):两轮审计确认,656- reclaim 与 acquire 复用的 TOCTOU(在用 Pod 被物理删):两轮审计确认,
663 LUA_RELEASE/PURGE 无「仍在 idle」守卫;确定性复现需真时序。657 LUA_RELEASE/PURGE 无「仍在 idle」守卫;确定性复现需真时序。
664- route 全程不重 resolve:config_sync 与长 route 并发时按旧 scope/模板仲裁、658- route 全程不重 resolve:config_sync 与长 route 并发时按旧 scope/模板仲裁、
665- 对已删 scope 满超时659+ 对已删 scope 按旧快照仲裁一次(route 已无待面,窗口=单次请求,2026-09 起)
666- B 类调小(session/cc)低于现活跃数的回收语义:存量超限不驱逐只老化回落,660- B 类调小(session/cc)低于现活跃数的回收语义:存量超限不驱逐只老化回落,
667 行为未固化无断言。661 行为未固化无断言。
668- 停机预算:uvicorn 无 `timeout_graceful_shutdown` + 30s 宽限 vs 300s 在飞请求662- 停机预算:uvicorn 无 `timeout_graceful_shutdown` + 30s 宽限 vs 300s 在飞请求
669- ——优雅停机可能整体跳过(占位/等待已 deadline 自愈,锁内中断仍依赖 finally)。663+ ——优雅停机可能整体跳过(占位 deadline 自愈,锁内中断仍依赖 finally;SM 等待已拆除,在飞面仅剩 acquire)。
670 664 
671**C. 加速手法残留(与「零回拨」硬标准的差距)**665**C. 加速手法残留(与「零回拨」硬标准的差距)**
672- 冒烟阶段 4(D 老化)与阶段 5(K reclaim 主体)仍回拨 expiry/idle_since——666- 冒烟阶段 4(D 老化)与阶段 5(K reclaim 主体)仍回拨 expiry/idle_since——
Mapplications/agent_runtime/docs/spec/service-core.md+4-6文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/docs/spec/session-manager.md+13-27文件内容审核中,请稍后刷新重试
@@ -26,7 +26,7 @@ AgentServer 原生支持 GET /health 后补验(单测已覆盖)。
26 D 老化回收 / E 保活 → 阶段 4(session_ttl 到期 → idle 暖池)26 D 老化回收 / E 保活 → 阶段 4(session_ttl 到期 → idle 暖池)
27 K reclaim 自治 → 阶段 5(回拨 idle_since,真删 K8s Pod)27 K reclaim 自治 → 阶段 5(回拨 idle_since,真删 K8s Pod)
28 I acquire deploy 失败分支 → 阶段 6(不可拉镜像 → NO_POD_AVAILABLE + 占位清)28 I acquire deploy 失败分支 → 阶段 6(不可拉镜像 → NO_POD_AVAILABLE + 占位清)
29- F 容量满(队列 + 快失败/超时 → 阶段 7(并发 5 请求 → 503 + 50429+ F 容量满(快失败) → 阶段 7(并发 5 请求 → 2×200 + 3×503 SCOPE_FULL
30 H min_idle 热备 → 阶段 8(autoscale 预建热备 Pod)30 H min_idle 热备 → 阶段 8(autoscale 预建热备 Pod)
31 G 死 Pod 会话清洗 / J 死 Pod 探测 → 阶段 9(kubectl 删 Pod → watch 兜底 → 会话失效)31 G 死 Pod 会话清洗 / J 死 Pod 探测 → 阶段 9(kubectl 删 Pod → watch 兜底 → 会话失效)
32 N 半死探测 → 【暂缓】待 AgentServer 原生支持 GET /health32 N 半死探测 → 【暂缓】待 AgentServer 原生支持 GET /health
@@ -573,8 +573,8 @@ async def stage2_route_abc(c: Client, r) -> dict:
573 first.get("pod_id") == second.get("pod_id")573 first.get("pod_id") == second.get("pod_id")
574 and await r.scard(f"{SM_PREFIX}scope:{MAIN}:sessions") == 3)574 and await r.scard(f"{SM_PREFIX}scope:{MAIN}:sessions") == 3)
575 # 表达式 or 支(user 白名单跨 group 命中 e2e-main)在阶段 12b 验证:575 # 表达式 or 支(user 白名单跨 group 命中 e2e-main)在阶段 12b 验证:
576- # 此处 e2e-main 已被 s1–s3 占满(cc=3),or 支 route 只会排队 504——576+ # 此处 e2e-main 已被 s1–s3 占满(cc=3),or 支 route 只会 503 SCOPE_FULL
577- # 原位置仅在「部署慢、s1 先过期」的时序下碰巧 200(2026-08-27 快跑实测 504)577+ # 快失败——原位置仅在「部署慢、s1 先过期」的时序下碰巧 200。
578 return state578 return state
579 579 
580 580 
@@ -1114,26 +1114,26 @@ async def stage6_deploy_failure(c: Client, r) -> None:
1114 1114 
1115 1115 
1116async def stage7_queue(c: Client, r) -> None:1116async def stage7_queue(c: Client, r) -> None:
1117- print("\n== 阶段 7:场景 F —— 容量满:等待队列 + 快失败/超时 ==")1117+ print("\n== 阶段 7:场景 F —— 容量满:立即快失败(2026-09 起 SCOPE_FULL)==")
1118 for sid in ("f1", "f2"): # 2 Pod 全满(cc=2, pc=1, max=2)1118 for sid in ("f1", "f2"): # 2 Pod 全满(cc=2, pc=1, max=2)
1119 code, raw, _ = await c.post("route", session_id=sid, group="e2e-f")1119 code, raw, _ = await c.post("route", session_id=sid, group="e2e-f")
1120 check(f"F-部署并占满 {sid}", code == 200 and raw.get("pod_id"), str(raw)[:120])1120 check(f"F-部署并占满 {sid}", code == 200 and raw.get("pod_id"), str(raw)[:120])
1121 t0 = time.monotonic()1121 t0 = time.monotonic()
1122+ # 5 并发:f1/f2 亲和续期(200)+ 3 个新会话撞 scope 闸门(503 SCOPE_FULL)
1122 results = await asyncio.gather(*[1123 results = await asyncio.gather(*[
1123- c.post("route", session_id=f"f-over-{i}", group="e2e-f") for i in range(5)])1124+ c.post("route", session_id=sid, group="e2e-f")
1124- codes = [code for code, _, _ in results]1125+ for sid in ("f1", "f2", "f-over-0", "f-over-1", "f-over-2")])
1125- queue_full = [b for code, _, b in results if code == 503
1126- and b.get("error_code") == "SCOPE_QUEUE_FULL"]
1127- full_timeout = [b for code, _, b in results if code == 504
1128- and b.get("error_code") == "SCOPE_FULL_TIMEOUT"]
1129 took = time.monotonic() - t01126 took = time.monotonic() - t0
1130- check("F-队列满(max_waiters=2×cc=4)→ 快失败 503 SCOPE_QUEUE_FULL",1127+ ok = [raw for code, raw, _ in results if code == 200]
1131- len(queue_full) >= 1, f"codes={codes} ({took:.0f}s)")1128+ rejected = [(code, b.get("error_code")) for code, _, b in results if code != 200]
1132- check("F-队列内等待 超时 504 SCOPE_FULL_TIMEOUT",1129+ check("F-并发 5 = 恰好 2×200(亲和续期)+ 3×503 SCOPE_FULL",
1133- len(full_timeout) >= 2, str([b.get("error_code") for _, _, b in results]))1130+ len(ok) == 2 and rejected == [(503, "SCOPE_FULL")] * 3,
1134- await asyncio.sleep(1)1131+ str([(code, b.get("error_code")) for code, _, b in results]))
1135- check("F-等待者全部出队finally 清理)",1132+ check("F-快失败不等待(<1s)", took < 1.0, f"{took:.2f}s")
1136- await r.zcard(f"{SM_PREFIX}scope:{FSCOPE}:waiters") == 0)1133+ check("F-过载响应带 retry_after",
1134+ all(b.get("retry_after") for _, _, b in results if b.get("error_code")))
1135+ check("F-拆除净空:无 waiters 键",
1136+ not await r.keys(f"{SM_PREFIX}scope:{FSCOPE}:waiters"))
1137 1137 
1138 1138 
1139async def stage8_warm(c: Client, r) -> dict:1139async def stage8_warm(c: Client, r) -> dict:
@@ -1380,8 +1380,9 @@ async def stage12_reconcile_cleanup(c: Client, r) -> None:
1380async def stage12b_or_branch(c: Client, r) -> None:1380async def stage12b_or_branch(c: Client, r) -> None:
1381 """表达式 or 支(阶段 12 清场后):e2e-main 此时空闲,or 支命中可确定性 200。1381 """表达式 or 支(阶段 12 清场后):e2e-main 此时空闲,or 支命中可确定性 200。
1382 1382 
1383- 原位置(阶段 2 尾)被 s1–s3 占满 cc=3,or 支 route 只能排队 504——只有1383+ 原位置(阶段 2 尾)被 s1–s3 占满 cc=3,or 支 route 只能 503 SCOPE_FULL
1384- 「部署慢、会话先过期」的时序下碰巧 200(2026-08-27 快跑实测暴露)。1384+ 快失败——只有「部署慢、会话先过期」的时序下碰巧 200(2026-08-27 快跑
1385+ 实测暴露,时为排队 504;2026-09 起为快失败)。
1385 """1386 """
1386 print("\n== 阶段 12b:表达式 or 支(清场后确定性验证)==")1387 print("\n== 阶段 12b:表达式 or 支(清场后确定性验证)==")
1387 code, raw_vip, _ = await c.post("route", session_id="s-vip",1388 code, raw_vip, _ = await c.post("route", session_id="s-vip",
Mapplications/agent_runtime/scripts/e2e_multi_replica.py+13-20文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/scripts/load_test.py+5-5文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/scripts/verify_redis_cluster.py+1-5文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/config.py+0-11文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/errors.py+15-9文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/main.py+2-3文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/metrics.py+1-1文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/session_manager/facade.py+2-2文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/session_manager/lua_scripts.py+2-30文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/session_manager/orchestrator.py+39-115文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/session_manager/state.py+7-49文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/src/agent_runtime/visualization_api.py+2-5文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/conftest.py+3-4文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/integration/_dual_harness.py+3-4文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/integration/test_audit_repro.py+2-59文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/integration/test_corner_cases.py+8-71文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/integration/test_multi_replica.py+21-65文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/integration/test_route_flow.py+18-77文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/integration/test_visualization_api.py+0-1文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/resource_manager/test_rm_state.py+1-1文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/session_manager/test_sm_state.py+13-13文件内容审核中,请稍后刷新重试
Mapplications/agent_runtime/tests/test_metrics.py+6-6文件内容审核中,请稍后刷新重试