25 KiB
M1 核心里程碑实现计划
目标(一句话): 落地 PolyGateway M1 全部交付物,使 GovDoc 与 Video-Tree 能用
GatewayClient.from_env()+chat()替代各自GovernedLLMClient跑通真实治理调用。 方案概述: 严格按已批准设计designs/2026-07-20-m1-core-design.md(签名冻结的唯一依据,下称"设计")与 ROADMAP §2 七步依赖序实施;内核类型先行冻结,叶子纯函数次之,再 transport → 内存后端与中间件 → 结构化 → 装配层,最后端到端验证。任何与设计冲突之处以设计为准;想改签名必须回人类门。 技术: Python 3.11 / httpx / pydantic(+pydantic-settings);optional extras: redis、json_repair;pytest(asyncio_mode=auto)。conda 环境PolyGateway。 流程: 全部工作在 feature 分支feature/m1-core;每任务一个 commit(用commitskill);每个行为任务先写失败测试再实现,pytest 先红后绿输出即测试证据。
0. 文件结构(锁定分解)
src/polygateway/
├── types.py # LLMResponse/ChatRequest/SourceConfig/Usage/SourceStats/
│ # TransportResult/RetryPolicy/BreakerConfig/BackpressurePolicy/GlobalLimits
├── errors.py # 设计 §3 全部错误类
├── ports.py # 全部 Protocol + Permit + Decision/Update(含 __post_init__ 校验)
├── streaming.py # 三层活性看门狗(移植 VT)
├── providers.py # ProviderProfile 注册表(qwen/deepseek/openai)
├── sources.py # RoundRobinSelector / LeastInflightSelector / SourceCooldownMemo
├── transports/openai_compat.py # SSE 解析纯函数 + OpenAICompatTransport
├── backends/memory/limiter.py # InMemoryLimiter + _MemoryPermit
├── backends/memory/breaker.py # InMemoryGate(CHS gate 契约)
├── backends/memory/cache.py # InMemoryCache(dict+TTL)
├── backends/redis_cache.py # RedisCache(笨 KV,optional extra)
├── middleware/base.py # Middleware/CallNext 类型 + 洋葱组装函数
├── middleware/telemetry.py # TelemetryMW + TelemetryEmitter(全库唯一遥测调用点)
├── middleware/cache.py # CacheMW(key 公式/命中重建 structured)
├── middleware/structured.py # StructuredMW(D14 阶梯)
├── middleware/retry.py # RetryMW(尝试编排:选源→熔断门→限流→transport)
├── middleware/ratelimit.py # QuotaGate(permit 获取 + wait/fail_fast + 后端故障报错)
├── middleware/breaker.py # BreakerGate(gate 进出 + 冷却备忘协作)
├── structured/json_repair.py # JsonRepairStrategy(normalize 钩子)
├── structured/native_schema.py # NativeSchemaStrategy
├── telemetry/sqlite.py # SQLiteRecorder(18 列)
├── config.py # GatewaySettings + 多源/韧性键解析与校验(新增模块,任务 T12 同步补 ARCH §8 模块图)
└── client.py # GatewayClient + from_env/from_settings + gather_bounded
tests/
├── unit/… # 每个 src 模块一个 test_ 文件
├── contracts/test_limiter_contract.py / test_breaker_contract.py # 参数化后端 fixture
├── integration/… # 远程 Redis / SQLite 并发 / 治理组合 / 取消穿透
└── e2e/… # 真实网关冒烟 + GovDoc/VT 兼容冒烟(输出落 tests/outputs/)
跨任务消费的冻结签名(设计 §2-§4 为全文,此处摘执行者最常引用的三组):
# ports.py —— 洋葱与每次尝试的接缝
CallNext = Callable[[ChatRequest], Awaitable[LLMResponse]]
class Middleware(Protocol):
async def __call__(self, request: ChatRequest, call_next: CallNext) -> LLMResponse: ...
class Transport(Protocol):
async def complete(self, *, messages: list[dict[str, Any]], source: SourceConfig,
stream: bool, overlay: dict[str, Any], call_id: str) -> TransportResult: ...
# ports.py —— 限流契约(CHS limiter.py 形态)
class Permit(Protocol):
async def release(self) -> None: ... # 幂等
async def settle(self, actual_tokens: int) -> None: ... # 幂等,delta 结算
class RateLimiter(Protocol):
async def try_acquire(self, source_key: str, est_tokens: int) -> Permit | None: ...
async def acquire(self, source_key: str, est_tokens: int) -> Permit: ...
async def source_stats(self, source_key: str) -> SourceStats: ...
async def mark_progress(self) -> None: ...
async def progress_age_s(self) -> float: ...
# ports.py —— 熔断契约(CHS provider_gate.py 形态,时间一律秒)
class ProviderGate(Protocol):
async def try_enter(self, source_name: str, owner: str) -> GateDecision: ...
async def record_success(self, entry: GateDecision) -> GateUpdate: ...
async def record_failure(self, entry: GateDecision, reason: str, force_open: bool) -> GateUpdate: ...
async def release_probe(self, entry: GateDecision) -> GateUpdate: ...
async def retry_after_s(self, sources: tuple[str, ...]) -> float: ...
GateDecision(source_name, allowed, state, epoch, is_probe, probe_owner, retry_after_s)、GateUpdate(applied, state, epoch, failure_count, retry_after_s),__post_init__ 校验照 CHS ports.py:405-455 逐条移植。
任务清单
T0 分支与骨架
git checkout -b feature/m1-core;创建 §0 全部目录与空__init__.py(含tests/contracts/);src/polygateway/py.typed占位。- 验收:
conda run -n PolyGateway python -c "import polygateway"通过;make ci仍绿。 - 提交:
chore: scaffold m1 module skeleton
T1 types.py + errors.py(设计 §2.1-§2.3 / §3)
- 实现设计 §2.1
LLMResponse(11 旧字段逐字保序无默认 + 4 新字段带默认)、§2.2ChatRequest(chat() 参数全集 +overlay: dict,frozen)、§2.3SourceConfig(字段/默认/三态 enable_thinking/missing_done/trust_env +__post_init__不变式:0 < inter < ttft < timeout_s、tpm > 0 → est_tokens > 0、name/provider/base_url/api_key/model 非空)、Usage/SourceStats/TransportResult、韧性四件套(RetryPolicy/BreakerConfig/BackpressurePolicy/GlobalLimits,构造校验正数)。全部 frozen dataclass,中文 docstring。 errors.py: 设计 §3 类层级逐字实现;GatewayUnavailableError校验(scope 非空、reason ∈ scope 级 5 值、retry_after_s ≥ 0);ResultInvalidError携raw_text/repair_error/validation_errors。- 测试(先红后绿):
tests/unit/test_types.py——LLMResponse 位置构造 11 参兼容(模拟三项目 fake)、frozen 不可变、SourceConfig 各不变式逐条触发 ValueError;tests/unit/test_errors.py——reason 值域拒绝、retry_after_s 负数拒绝、继承关系(isinstance(CircuitOpenError(...), GatewayUnavailableError))。 - 验证:
conda run -n PolyGateway pytest tests/unit/test_types.py tests/unit/test_errors.py -v全 PASS。 - 保真: SourceConfig 不变式对照 CHS
config.py:66-82;错误构造形态对照 CHSerrors.py:82-176。 - 提交:
feat: add frozen core types and error taxonomy
T2 ports.py
- §0 三组签名 +
CacheBackend(get/set)、TelemetryRecorder(record_llm_call18 字段 keyword-only,字段名单见设计 §4.4)、SourceSelector(order(sources, stats))、StructuredOutputStrategy(request_overlay(schema) -> dict/parse(text) -> Any)、GateDecision/GateUpdate数据类。全部@runtime_checkable。ports.py只 importtypes.py/errors.py/标准库。 - 测试:
tests/unit/test_ports.py——最小 dummy 实现通过各 Protocol 的 isinstance;GateDecision__post_init__全部非法组合逐条拒绝(OPEN 准入 / HALF_OPEN 非探针准入 / 探针缺 owner / 非探针带 owner / 负 epoch / 负 retry_after_s)。 - 保真: GateDecision 校验逐段比对 CHS
ports.py:405-440。 - 提交:
feat: freeze all port protocols and gate snapshots
T3 streaming.py 看门狗(ROADMAP 2a)
- 移植 VT
adapters/streaming.py(与 GovDoc 逐字节一致):StreamLivenessTimeout(kind, elapsed_s, first_token_seen)+stream_with_liveness_timeouts(source, *, ttft_s, inter_token_s, total_s)。只包裹单次__anext__;asyncio.timeout的expired()区分本层 deadline;finallyaclose底层迭代器。 - 测试:
tests/unit/test_streaming.py——ttft/inter_token/total 三层分别用慢迭代器触发(小超时值,真实时钟);正常流原样透传;上游 TimeoutError 不被误吞为本层超时;消费方取消时底层迭代器被 aclose。 - 保真: 逐段比对 VT
adapters/streaming.py;语义零改动。 - 提交:
feat: port three-layer stream liveness watchdog
T4 providers.py 注册表(ROADMAP 2b)
ProviderProfile(设计 §7)+ 模块级注册表 dict +get_provider(name)(未注册抛RequestRejectedError语义的装配错误——用ValueError,装配期即炸)+register_provider(profile)。首发三条目: qwen({"enable_thinking": True}/{"enable_thinking": False},strip_think_tags=True)、deepseek({"thinking": {"type": "enabled"}}/{"thinking": {"type": "disabled"}})、openai(全空)。注册表实例归属装配层持有(from_env默认用模块级只读表)——无可变全局状态:register_provider返回新表或要求显式表实例,禁止运行时改共享 dict(纯 asyncio 中立铁律)。- 测试: 三 profile 内容断言;未注册名报错;自定义 profile 注册后可查。
- 保真: 注入片段比对 VT
llm.py:130-144与 CHSinvokers.py:230-238(三态语义是设计定稿的统一,允许双向覆盖)。 - 提交:
feat: add explicit provider profile registry
T5 transports/openai_compat.py(ROADMAP 3)
- SSE 纯函数移植:
_sse_data_payload/_sse_delta(usage 帧旁路;content 与 reasoning_content 区分)/_iter_sse_deltas([DONE]检测、畸形 JSON 抛 SSE 异常)——蓝本 VTllm.py:51-124。 OpenAICompatTransport: 每源持有预配 httpx.AsyncClient(Bearer/超时/trust_env);请求体 = model/messages/stream(+stream_options.include_usage仅流式)+ overlay 合并 + profile thinking 注入;流式路径走看门狗(ttft/inter/total 取自 SourceConfig,None 回退 timeout_s;任何增量含 reasoning 都算 token);非流式快路径(stream=False,单 JSON 响应,仅 total 超时);<think>剥离按 profile;usage 缺失按est_tokens兜底并标estimated(est_tokens=0 则 0+estimated);缺[DONE]:missing_done="retry"抛瞬时 /"salvage"用已收内容且强制 estimated;零内容 early_eof 恒抛瞬时。- 错误翻译(设计引用 ARCH §6.2 全表): httpx Timeout/Transport 族、SSE 异常、看门狗超时 →
TransientError;429 bodyinsufficient_quota/401/403 →SourceDeadError;429 其余 →TransientError(retry_after_s=秒数解析);400 →RequestRejectedError;≥500 →TransientError;其余 4xx →RequestRejectedError。 - 测试:
tests/unit/test_openai_compat.py用httpx.MockTransport+ 从三项目真实响应二次构造的 SSE 帧样本——正常流(含 usage 帧/reasoning 帧)、畸形帧、缺 DONE 两种策略、early_eof、非流式、每条翻译规则逐行断言异常类型与 retry_after_s;<think>剥离;usage 兜底与 usage_source。 - 保真: SSE 逐段比对 VT
llm.py;翻译逐段比对 CHSinvokers.py:127-227;Retry-After 仅秒数形态(invokers.py:127-141)。 - 提交:
feat: add openai-compatible transport with sse parsing and error translation(可拆 2 个 commit: 纯函数先行)
T6 backends/memory/ limiter + breaker + 契约测试(ROADMAP 4b)
InMemoryLimiter: 构造(scope, sources: dict[str, SourceConfig], global_limits: GlobalLimits, lease_ttl_s, now=time.monotonic);六道闸(全局/单源 × 并发/RPM/TPM,0 = 不启用);并发 = 带 lease_id+过期时刻的租约表(acquire 时惰性清理过期租约);RPM/TPM = 分钟滑动窗口计数;TPM 预扣 est、settle(actual)delta 结算可退款;try_acquire任一闸满返 None 零副作用;acquire轮询 +asyncio.sleep(可取消);mark_progress/progress_age_s。单事件循环内的并发安全靠"检查-占用"同步完成(无 await 穿插),不引入锁。InMemoryGate: CHS gate 契约的进程内实现——CLOSED/OPEN/HALF_OPEN 状态机、连续失败阈值、force_open、冷却到期半开单探针(带 probe_ttl_s 租约,持有者死亡后过期回收)、epoch 递增与 fencing(旧 epoch 写回applied=False)、release_probe幂等、retry_after_s(sources)取最早可尝试时刻。时钟构造注入。- 契约测试
tests/contracts/: limiter——移植 CHStests/contracts_limiter.py5 项(并发占满/RPM 不归还/TPM 预扣结算退款/release 幂等/progress)+ 新增 settle 幂等、try_acquire 失败零副作用、租约过期回收;breaker——阈值开路/冷却半开单探针(第二个 try_enter 被拒)/探针成功闭路/探针失败重开/force_open 一击/探针租约过期后新探针可入/epoch fencing 拒迟到写回/release_probe 幂等。fixture 参数化 backend(M1 仅 memory,M2 加 redis 零改测试)。 - 保真: 状态机比对 VT
breaker.py(含_half_open_inflight语义升级为租约)与 CHSprovider_gate.py;limiter 语义比对 CHSlimiter.py(_RedisPermit的 released/settled 幂等 flag、settle 落 acquire 窗口)。 - 验证:
conda run -n PolyGateway pytest tests/contracts/ -v全 PASS。 - 提交:
feat: add in-memory limiter and breaker satisfying backend contracts
T7 sources.py 选源与冷却(设计 §2.3/多源拍板)
RoundRobinSelector(内部游标轮转起点)与LeastInflightSelector(按 inflight 升序)——逐字移植 CHSselector.py:20,36;SourceCooldownMemo(dict[str, float] 冷却截止,set_until/active/skip_reason,时钟注入)。- 测试: 轮转顺序推进、least_inflight 排序稳定性、冷却备忘过期恢复。
- 提交:
feat: add source selectors and cooldown memo
T8 middleware/ 骨架 + RetryMW(ROADMAP 4a;D13 自研)
middleware/base.py:compose(middlewares, terminal) -> CallNext洋葱组装(外→内)。middleware/ratelimit.pyQuotaGate: 按quota_full(wait/fail_fast)获取 permit;fail_fast 满时抛AllSourcesExhausted(reason="quota_exhausted");后端操作异常包为GovernanceBackendError(报错不放行)。middleware/breaker.pyBreakerGate: try_enter/写回/release_probe 封装,同样后端异常报错。middleware/retry.pyRetryMW: 每次尝试 = selector.order → 跳过冷却源(计 gate_rejections)→try_acquire(None 跳过)→try_enter(不准入: settle(0)+release、读 retry_after_s 写冷却备忘、计 gate_rejections)→ transport(新 call_id/uuid4)。异常分派(蓝本 CHSgovernance.py:200-268逐段比对):RequestRejectedError网关已应答记成功否则探针释放,上抛;ResultInvalidError记成功上抛;CancelledError探针释放后重抛(永不吞);SourceDeadErrorforce_open+立即换源;TransientError记失败、actual=est 保守、退避min(base*2^n, max)*uniform(0.5,1.5)与 retry_after_s 取大后重试;尝试数达max_attempts(=总尝试次数)抛AllSourcesExhausted(reason="retry_exhausted");全源被门拒 →CircuitOpenError(per_source_reasons 逐源填);源列表空 →reason="no_sources"。成功: settle(actual)+mark_progress+record_success。finally 嵌套保证 settle 后必 release;取消路径 settle(0)。逐次遥测经注入的TelemetryEmitter。- 测试(fake transport/后端注入): 换源顺序、瞬时重试次数语义(max_attempts=3 → 恰 3 次尝试)、SourceDead 不退避立即换源、ResultInvalid 记成功且不重试、退避与 Retry-After 取大(注入 rng/sleep 断言时长)、取消穿透(cancel 后 permit 已释放、探针已还、CancelledError 冒出)、fail_fast 语义、per_source_reasons 内容、每次尝试 call_id 不同。
- 保真: 循环结构逐段比对 CHS
governance.py:107-268;退避公式比对 VTllm.py。 - 验证:
conda run -n PolyGateway pytest tests/unit/test_retry.py -v全 PASS。 - 提交:
feat: add retry middleware with per-attempt governance orchestration
T9 middleware/cache.py + 缓存后端(ROADMAP 4c)
- key 公式(ARCH §7.5):
pgw:cache:+sha256(canonical_json({model, messages_digest, namespace, salt}));messages_digest——文本 part 原文、多模态 part(含image_urldata URL)各自 sha256 后参与;namespace 取 per-callcache_namespace覆盖装配默认,缺失时(启用缓存而无 namespace)装配期已报错;salt 仅非 None 参与(VT 旧键语义)。 CacheMW: get 命中 → 反序列化(未知字段过滤/缺字段吃默认)→ 若本次调用带structured用注入的 strategy 零网络重建structured_data(失败按未命中 warning)→ 新cache_call_id构造 cache_hit=True 响应;未命中 → call_next → 成功且阶梯已通过(StructuredMW 在内层,能返回即已通过)写缓存(排除 structured_data 字段)。get/set 异常一律 warning 降级。backends/memory/cache.py(dict+过期时刻,测试用)与backends/redis_cache.py(笨 KV: get/set(ttl),redisimport 失败报"缺 extra"错误)。- 测试: key 稳定性(同请求同 key)、隔离性(model/namespace/salt/多模态字节任一变化 → key 变)、大 base64 不进 canonical_json(性能语义: digest 后长度恒定)、命中路径 structured 重建与 schema 变更重校验失败回源、后端炸时读写降级、TTL 过期。
- 保真: 降级与序列化行为比对 VT
redis_cache.py;key 公式是设计声明的替换(旧缓存冷启动已记迁移文档)。 - 提交:
feat: add response cache middleware with poisoning-safe keys
T10 遥测: telemetry/sqlite.py + middleware/telemetry.py(ROADMAP 4d)
SQLiteRecorder: 表llm_calls18 数据列 + created_at(旧 15 列名保留,model_name→model更名,新增source_name/usage_source/cost);WAL + busy_timeout=5000;单持久连接 +threading.Lock,写经asyncio.to_thread;INSERT OR IGNORE;初始化/写入失败全降级 warning(self._conn=None后续 no-op);close()。TelemetryEmitter: 全库唯一record_llm_call调用点;emit(request, *, response=None, error=None, call_id, source_name, latency_ms, ...)从请求与结果组装 18 字段;messages 落库前多模态 part 摘要(复用 T9 的 digest 函数);cost 恒 None(pricing M2)。TelemetryMW: 最外层,负责缓存命中与最终失败的记录;逐次记录由 RetryMW 持同一 Emitter 完成;取消路径 finally 尽力记 error="cancelled"。- 测试: 18 列 schema 断言、并发 50 协程写全落库、call_id 冲突幂等、db 路径不可写时全链路不抛、缓存命中记录(cache_hit=True, latency_ms=0)、失败记录 error 字段、取消记录;grep 断言
record_llm_call(在 src/ 仅出现于 emitter(铁律执法测试)。 - 保真: 比对 VT
telemetry.py(连接管理/PRAGMA/降级);字段对照 ARCH §7.8。 - 提交:
feat: add sqlite telemetry with single-emitter discipline
T11 结构化: strategies + middleware/structured.py(ROADMAP 5)
structured/json_repair.pyJsonRepairStrategy(normalize=None): 围栏剥离(VT_CODE_FENCE_RE蓝本)→json_repair.repair_json→json.loads→ normalize 钩子;json_repairimport 失败报"缺 extra polygateway[structured]";失败抛ResultInvalidError(raw_text, repair_error)。structured/native_schema.pyNativeSchemaStrategy:request_overlay(schema)产出{"response_format": {"type": "json_schema", "json_schema": {...}}};parse 复用修复链。StructuredMW(设计 §5): 三档分派——不传直通;"json"仅修复(dict/list 入structured_data);pydantic 模型 = overlay 注入(profile 支持 native 时)→ call_next → 修复 →model_validate(只形态)→ 失败且余额 >0 则按反馈模板重问(原 messages + assistant 坏输出 + user 纠错,错误前 3 条各截 200 字符;重问叠加 NativeSchema)→ 耗尽抛ResultInvalidError(raw_text, repair_error, validation_errors)。重问计数独立于 transport 重试;max_structured_retries默认 1。- 测试: 三档行为;修好的脏 JSON(真实样本: 围栏/尾逗号/单引号);修不好的;校验失败→重问 messages 形态断言(模板逐字)→ 第二次成功;retries=0 直接抛(CHS 语义);重问经过 call_next(即照过内层计数,用 fake 断言调用次数);normalize 钩子生效。
- 保真: 修复链比对 VT
loop.py:341-377;_normalize_action不入库(设计 §5 细则 3)。 - 提交:
feat: add structured output ladder with bounded feedback retries
T12 装配: config.py + client.py(ROADMAP 6)
config.pyGatewaySettings(pydantic-settings): 设计 §8 全部键——多源{SCOPE}__{PROVIDER}__{N}__{FIELD}(FIELD 全集含 MISSING_DONE/TRUST_ENV;段数≠4 或 GLOBAL 段跳过;未知 FIELD/未知 provider/缺必填字段报错)、scope 全局闸、per-scope 韧性键、平铺LLM_*简写(scope 键优先)、PGW_*装配键(启用缓存缺PGW_CACHE_NAMESPACE或 TTL≤0 报错)。装配守卫:timeout_s ≤ lease_ttl、有效熔断阈值max(threshold, concurrency*2)自动、max_attempts ≥ 1。client.py:GatewayClient(构造函数全量注入;chat()设计 §2.2 签名逐字;组装洋葱 遥测→缓存→结构化→重试;aclose()幂等 + async context manager)、from_env(scope, *, limiter/breaker/cache/telemetry 注入覆盖)、from_settings(settings, ...)、gather_bounded(aws, *, concurrency)。__init__.py顶层导出: GatewayClient、LLMResponse、全部错误类、SourceConfig、gather_bounded、register_provider。- 解除 import-linter 门控(Makefile 去掉跳过逻辑),契约按 ARCH §8 生效(必要时把
sources.py/config.py归入正确层)。回填.env.example(§8 键名全集注释模板)。 - 测试: from_env 缺键逐个报错、多源聚合(两源解析)、平铺与 scope 键优先级、注入覆盖生效(传入 fake limiter 断言被使用)、两个 client 共享同一 InMemoryLimiter 时全局并发闸生效(合计 inflight 封顶)、chat() 端到端(全 fake 后端+MockTransport: 命中缓存/瞬时重试/结构化三档 各一条)、aclose 幂等、gather_bounded 保序与并发上限、
isinstance结构性满足 GovDoc/VT 的 LLMProvider Protocol(只读 import reference)。 - 验证:
make ci绿(首次含 import-linter);conda run -n PolyGateway pytest tests/ -v全 PASS。 - 提交:
feat: add gateway client with env-driven assembly(config 与 client 可拆 2 commit)
T13 集成测试(需真实 Redis)
前置(人类输入): 实验室远程 Redis 连接串写入
.env(REDIS_URL)。测试 namespacepgw:test:{uuid4},teardown 全清理,禁止触碰既有键。
tests/integration/: 远程 Redis 缓存读写与 TTL、断连降级(错误 URL 注入 → get/set 静默、chat 照常)、SQLite 并发写、治理组合(内存后端: 熔断开路→冷却→半开探针恢复全链;限流 wait/fail_fast;换源)、取消穿透(mid-stream 与 mid-backoff cancel → permit/探针释放断言)。- 验证:
conda run -n PolyGateway pytest tests/integration/ -v全 PASS;覆盖率make test≥80%。 - 提交:
test: add integration suite for redis cache and governance combos
T14 e2e 冒烟(需真实网关凭据)
前置(人类输入): 真实网关
.env(至少一个LLM__{PROVIDER}__1__*源)。
tests/e2e/test_smoke_gateway.py: 流式/非流式/structured="json"/pydantic 模型 四条真实调用,结果 + 遥测行断言,结构化 Markdown 输出落tests/outputs/e2e/。tests/e2e/test_compat_govdoc.py/test_compat_videotree.py: 只读 import 两项目 Protocol 断言 isinstance;按其调用点形态(session_id/parent_call_id/cache_salt)真实调用一次;用 VT 现有键名(LLM_TIMEOUT等)装配成功。- 验证: e2e 全 PASS,
tests/outputs/有产物(不提交)。 - 提交:
test: add real-gateway and dual-project onboarding smoke
T15 收尾: 验证门与合并
- 派全新上下文 verifier subagent(
verification-before-completion): 对照设计逐节 + ROADMAP §2 验收出口逐条,证据必须指向本会话工具输出。 - 更新 ROADMAP §1 M1 状态、ARCH §8 模块图补
config.py;wiki 注册 plan 实体与 implements 边。 finishing-a-development-branchskill 走合并(合并前requesting-code-review)。- 验证:
make ci绿;覆盖 ≥80%;验证报告无未闭合项。
风险与回滚
| 风险 | 对策 |
|---|---|
| 签名实现时发现设计矛盾 | 停下回人类门,先修设计再继续;严禁现场改签名 |
| 远程 Redis/网关凭据未就绪 | T13/T14 前向人类索取;其余任务不阻塞 |
| import-linter 契约与实际 import 冲突 | T12 才解除门控,前序任务自觉遵守层次,T12 一次清算 |
| 覆盖率不达 80% | 缺口集中在错误分支——按 T5/T8 翻译表与异常分派逐行补 |
保真校验总注: 本计划 T3/T5/T6/T8/T9/T10/T11 均涉及 ARCH §1.4 移植蓝本,各任务已标注比对文件;凡与蓝本语义有出入之处必须能指到设计 §9 审计表的对应行(保留/替换/放弃),否则视为未声明的隐式丢弃。