chore: sync local project state

This commit is contained in:
Your Name
2026-05-12 18:49:52 +08:00
parent afdbea6fb5
commit 1c0084afe8
105 changed files with 13221 additions and 420 deletions

View File

@@ -0,0 +1,154 @@
# B2/B3/B4 实施规格2026-05-07
状态:当前有效
范围candidate 状态收敛、publish 事务闭环、admission-state API 真正接线
真源:
- tech/CURRENT_SOURCE_OF_TRUTH_2026-05.md
- tech/BASELINE_TECHLEAD_V2.md
- tech/GATEWAY_CONSUMER_DECISION_2026-05.md
## 1. 目标
把 supply-intelligence 从“各子模块最小骨架存在”推进到“candidate -> admission -> draft package -> publish -> gateway sync state -> admission-state 查询”这一条真实生产闭环更接近可验状态。
本轮不扩范围到独立平台化、重基础设施、自动注册,只做当前收口板 B2/B3/B4。
## 2. 当前已验证现状
1. `go test ./...` 当前通过。
2. `internal/domain/types.go` 中 candidate 状态枚举已不包含 `pending_admission` / `admitted`
3. `internal/httpapi/server.go``parseDiscoveryCandidateStatus()` 已只接受:
- discovered
- testing
- test_passed
- test_failed
- retry_pending
- ignored
- published
- deprecated
- closed
4. `internal/httpapi/server.go` 已存在 `/internal/supply-intelligence/models/{platform}/{model}/admission-state` 路由与 handler。
5. `internal/publish/service.go` 目前只支持“追加 package published event”还不是“运营确认上架事务”。
6. `internal/admission/service.go` 在测试通过后会创建/更新 draft package并把 candidate 置为 `test_passed`
7. `internal/httpapi/admission_state_api_test.go` 目前只验证 candidate/package/event 聚合读取,不验证真实 publish 事务。
## 3. 本轮必须收敛的缺口
### B2. candidate 状态与 admission 流转
必须满足:
- admission 只允许 `discovered` / `retry_pending` 进入执行。
- admission 执行开始后置为 `testing`
- admission 失败后置为 `test_failed``retry_pending`(本轮沿用现状失败归 `test_failed`)。
- admission 成功后置为 `test_passed`
- publish 成功后 candidate 必须从 `test_passed` -> `published`
- 不允许重新引入旧状态口径。
### B3. publish 事务闭环
必须新增真实语义:
- 输入不再只是 event append 所需字段。
-`platform + model`(必要时 package/candidate为主键读取当前真实状态。
- 仅当 candidate 最新状态为 `test_passed` 且 package 当前为 `draft` 时允许发布。
- 发布动作要同时完成:
1. package `draft -> active`
2. candidate `test_passed -> published`
3. 追加 `supply_package_published` event默认 `gateway_sync_status=pending`
- 明确 `published != applied`gateway applied 仍由 ack 驱动。
### B4. admission-state API
必须返回当前组合真相:
- latest candidate truth
- current package truth
- latest matching package event truth
- gateway sync status
并在 publish 事务跑完后能够体现:
- candidate_status = published
- package_status = active
- gateway_sync_status = pending直到 ack
## 4. 最小改动设计
### 4.1 repository / app 适配层
尽量不改 repository 主接口的大结构,只补 publish service 所需最小能力,优先复用已有:
- `GetLatestDiscoveryCandidateContext()`
- `GetSupplyPackage()`
- `UpsertSupplyPackage()`
- `UpdateCandidateStatus()`
- `AppendPackageEventContext()`
如 publish 包直接依赖 domain/repository 成本更低,可在 publish 内定义更完整 repo interface再由现有 repository.Repository 满足。
### 4.2 publish service 新增主入口
建议新增:
- `PublishDraft(ctx, PublishDraftInput) (PublishDraftOutput, error)`
输入最小字段:
- event_id
- platform
- model
- actor/source可选本轮如无真实审计先留空
- occurred_at可选
输出最小字段:
- candidate
- package
- event
- gateway_sync_status
保留 `RecordPackagePublished()` 兼容测试/已有接口,但 HTTP 主入口要逐步切换为真正发布语义,而不是“外部直接塞 event”。
### 4.3 HTTP API
当前 `/internal/supply-intelligence/publish/package-event` 若继续存在,本轮将其语义提升为“确认发布 draft package”不再允许脱离 candidate/package 真相直接伪造 event。
请求体建议最小化为:
- event_id
- platform
- model
- occurred_at
如果保留 package_id/version 也应以服务端真相为准,不信任调用方覆盖 package 当前状态。
## 5. 验证标准
必须新增/更新测试覆盖:
1. publish 成功:
- candidate `test_passed -> published`
- package `draft -> active`
- event appended with pending sync
2. publish 拒绝:
- candidate 不是 `test_passed` 时拒绝
- package 不是 `draft` 时拒绝
- candidate/package 不存在时拒绝
3. admission-state
- publish 后查询可看到 `published + active + pending`
- ack 后查询可看到 `applied/failed`
4. 全量验证:
- `go test ./...`
## 6. 不做项
本轮明确不做:
- 审计表完整补齐
- actor/审批链完整产品化
- DB 事务级锁语义重构
- gateway 实际远端集成
- auto-supply / deep registration
## 7. 完成定义
仅当以下同时成立B2/B3/B4 才能算完成:
- 代码不再只有“event append 记录器”语义
- publish 真正驱动 candidate/package 状态变化
- admission-state 能反映 publish 后组合真相
- 新增测试通过
- `go test ./...` 通过

View File

@@ -0,0 +1,487 @@
# G4 真实远端 Gateway 集成验证:技术设计与验证方案
状态:当前有效
仓库:`/home/long/project/supply-intelligence`
阶段G1-G3 已完成(本地 + tksea 43.155.133.187:8081G4 待验证
---
## 1. 设计范围
### 1.1 In Scope
- supply-intelligence 与 sub2api/tokens-reef 的端到端事件触达验证
- 利用现有 HTTP APIpackage-changes / ack / runtime pause-resume构造真实远端消费窗口
- 改造 sub2api-bridge 为"真实远端 gateway 代理",走外部消费+手动 ack 闭环
- 在 tksea 可触及环境内完成最小可行的对账证据链
### 1.2 Out of Scope
- 不修改 supply-intelligence 核心 publish / consume-once / retry 状态机
- 不恢复或重建已下线的 103.56.49.28 旧 sub2api 节点
- 不引入新的消息队列或外部基础设施
- 不修改 admission 测试逻辑(当前 tksea 使用 ADMISSION_TEST_MOCK=1与 G4 无关)
### 1.3 约束
- 必须复用现有 HTTP 契约与 runtime 控制接口
- 验证脚本必须可在一个 QA 窗口内(< 15 分钟)执行完毕
- 对账证据必须双向可校验supply-intelligence 侧 + sub2api-bridge 侧
---
## 2. 架构与模块分析(现有事件流)
### 2.1 当前事件流拓扑
```
[Publisher]
|
v
POST /internal/supply-intelligence/publish/package-event
|
v
internal/publish/service.go :: PublishDraft
|
v
Repository :: PackageChangeEvent (gateway_sync_status = pending)
|
+---> 路径 A内部自动消费默认
| GatewayPackagePoller (1s) -> ConsumeOnce -> applier -> auto ack
|
+---> 路径 B外部远端消费G4 验证目标)
GET /gateway/package-changes -> 远端应用 -> POST .../ack
```
### 2.2 关键模块状态(截至代码审查)
| 模块 | 文件 | 状态 | G4 相关性 |
|------|------|------|-----------|
| Publish Service | `internal/publish/service.go` | 已闭合 | 产生 pending event |
| Gateway Consumer | `internal/gatewayconsumer/service.go` | 已闭合(含 retry/metrics | 路径 A 自动消费 |
| HTTP Server | `internal/httpapi/server.go` | 已闭合(含 pause/resume/status | 提供路径 B API + runtime 控制 |
| Repository (PG) | `internal/repository/postgres.go` | 已闭合(含 retry 字段) | 持久化 event / ack |
| Repository (Mem) | `internal/repository/memory.go` | 已闭合(含 retry 字段) | 本地验证用 |
| Poller/Runtime | `internal/poller/runtime.go` | 已闭合(含 pause/resume | 控制本地消费窗口 |
| Metrics | `internal/metrics/metrics.go` | 已声明 | 观测支撑 |
| sub2api-bridge | `cmd/sub2api-bridge/main.go` | **旧实现,需改造** | G4 核心验证工具 |
### 2.3 事件流结论
- supply-intelligence 已有完整的"内部自动消费"闭环(路径 A
- supply-intelligence 已有完整的"外部消费+手动 ack" HTTP 契约(路径 B
- 当前缺口:没有外部消费者真实走过路径 B 并留下对账证据
- G4 目标就是补全路径 B 的端到端验证
---
## 3. 接口与数据模型
### 3.1 supply-intelligence 对外暴露的 Gateway 接口
| 方法 | 路径 | 作用 | 代码落点 |
|------|------|------|----------|
| GET | `/internal/supply-intelligence/gateway/package-changes?cursor=` | 拉取事件流(含 pending/applied/failed | `server.go:311` |
| POST | `/internal/supply-intelligence/gateway/package-changes/{event_id}/ack` | 外部 consumer 回写 ack | `server.go:320` |
| POST | `/internal/supply-intelligence/gateway/consume-once` | 内部自动消费(服务端执行 applier+ack | `server.go:362` |
| GET | `/internal/supply-intelligence/gateway/runtime-status` | 查看 poller 状态 | `server.go:389` |
| POST | `/internal/supply-intelligence/gateway/runtime/pause` | 暂停本地自动消费 | `server.go:415` |
| POST | `/internal/supply-intelligence/gateway/runtime/resume` | 恢复本地自动消费 | `server.go:431` |
| GET | `/internal/supply-intelligence/models/{platform}/{model}/admission-state` | 查询 model 最新 event 状态 | `server.go:507` |
### 3.2 ack 请求/响应模型
**Request:**
```json
POST /internal/supply-intelligence/gateway/package-changes/{event_id}/ack
{
"consumer": "sub2api-bridge",
"result": "applied",
"detail": "synced to tokens-reef"
}
```
**Response:**
- 204 No Content成功
- 400invalid_json / invalid_result
- 404event not found
- 500internal_error
### 3.3 package-changes 响应模型
```json
{
"items": [
{
"event_id": "evt-xxx",
"account_id": 1,
"event_type": "supply_package_published",
"package_id": 1001,
"platform": "openai",
"model": "gpt-4.1-mini",
"occurred_at": "2026-05-10T12:00:00Z",
"version": 2,
"gateway_sync_status": "pending",
"retry_count": 0,
"next_retry_at": null,
"last_failure_category": ""
}
],
"next_cursor": "evt-xxx"
}
```
### 3.4 runtime-status 响应模型
```json
{
"started": true,
"paused": false,
"cursor": "evt-xxx",
"last_poll_at": "2026-05-10T12:01:00Z",
"last_error": "",
"pending_retry_events": 0,
"failed_events": 0
}
```
---
## 4. 对接点分析supply-intelligence -> sub2api/tokens-reef
### 4.1 当前 sub2api-bridge 的问题
**代码路径:** `cmd/sub2api-bridge/main.go`
当前 sub2api-bridge 调用的是 `/gateway/consume-once`
```
consumeOnce -> POST /gateway/consume-once -> supply-intelligence 服务端执行本地 applier -> 自动 ack
```
这导致 sub2api-bridge 只是**读取了服务端已经处理完毕的结果**,而不是**真实代表远端 gateway 消费事件**。对账证据只能证明"服务端本地模拟了消费",不能证明"事件触达了远端 gateway"。
### 4.2 改造后的 sub2api-bridge 对接模型
改造目标:让 sub2api-bridge 成为路径 B 的真实远端 consumer。
```
sub2api-bridge (远端 gateway 代理)
|
|--1---> GET /gateway/package-changes?cursor=
| (拉取 pending 事件)
|
|--2---> 应用到本地 DB (supply_bridge_log)
| (真实持久化 = "远端已接收"证据)
|
|--3---> POST /gateway/package-changes/{event_id}/ack
| {"consumer":"sub2api-bridge","result":"applied"}
|
v
supply-intelligence 侧 event 状态变为 applied
```
### 4.3 认证方式
- 当前 supply-intelligence HTTP API 无认证(内部网络)
- sub2api-bridge 与 supply-intelligence 通过内网/localhost 通信
- G4 验证阶段保持此约束,不新增认证复杂度
### 4.4 对账机制
**supply-intelligence 侧对账点:**
1. `GET /models/{platform}/{model}/admission-state` -> `last_event.gateway_sync_status`
2. `GET /gateway/runtime-status` -> pending/failed 计数
3. Repository 直接查询:`ack_consumer='sub2api-bridge'``ack_status='applied'`
**sub2api-bridge 侧对账点:**
1. `supply_bridge_log` 表:`SELECT * FROM supply_bridge_log WHERE event_id='evt-xxx'`
2. bridge 程序日志 stdout记录每次 fetch/bridge/ack 动作
**双向对账断言:**
```
supply-intelligence.event.acked_at IS NOT NULL
AND supply-intelligence.event.consumer = 'sub2api-bridge'
AND supply-intelligence.event.gateway_sync_status = 'applied'
AND sub2api-bridge.supply_bridge_log.event_id = '{event_id}'
AND sub2api-bridge.supply_bridge_log.result = 'applied'
```
---
## 5. G4 验证方案设计(最小可行方案)
### 5.1 验证环境
| 组件 | 地址/位置 | 角色 |
|------|-----------|------|
| supply-intelligence (tksea) | 43.155.133.187:8081 | 事件源 + 状态持久化 |
| sub2api-bridge (本地或 tksea同机) | 本地编译运行 | 远端 gateway 代理 |
| sub2api DB (本地 Postgres) | localhost:5432/sub2api | 远端 gateway 持久化证据 |
**环境变量tksea**
- `SEED_LOCAL_DEMO=1`:已预置 demo candidate/package
- `ADMISSION_TEST_MOCK=1`:与 G4 无关
### 5.2 验证前置条件
1. tksea 上 supply-intelligence 可访问(`curl 43.155.133.187:8081/healthz` == 200
2. 本地有可编译 Go 环境 + 本地 Postgres或 SQLite 替代)
3. supply-intelligence 的本地 gateway runtime 可被暂停(已有 API 支持)
### 5.3 验证执行步骤SOP
**Step 0暂停本地自动消费打开外部验证窗口**
```bash
curl -X POST http://43.155.133.187:8081/internal/supply-intelligence/gateway/runtime/pause
# 期望:{"paused":true}
```
**Step 1确认 demo 数据就绪**
```bash
curl http://43.155.133.187:8081/internal/supply-intelligence/models/openai/gpt-4.1-mini/admission-state
# 期望candidate.status=test_passed, package.status=draft, gateway_sync_status=""
```
**Step 2发布 package产生 pending event**
```bash
curl -X POST http://43.155.133.187:8081/internal/supply-intelligence/publish/package-event \
-H "Content-Type: application/json" \
-d '{"event_id":"g4-test-001","platform":"openai","model":"gpt-4.1-mini"}'
# 期望:返回 Event 对象gateway_sync_status=pending
```
**Step 3启动改造后的 sub2api-bridge**
```bash
export SUPPLY_URL=http://43.155.133.187:8081
export CONSUMER=sub2api-bridge
export SUB2API_DB="postgres://sub2api:***@localhost:5432/sub2api?sslmode=disable"
./sub2api-bridge
```
**Step 4bridge 执行外部消费闭环**
- bridge 调用 `GET /gateway/package-changes`
- 过滤出 `gateway_sync_status=pending` 的事件
- 将事件写入本地 `supply_bridge_log`
- 调用 `POST /gateway/package-changes/{event_id}/ack` 回写 applied
**Step 5supply-intelligence 侧验证**
```bash
curl http://43.155.133.187:8081/internal/supply-intelligence/models/openai/gpt-4.1-mini/admission-state
# 断言last_event.gateway_sync_status == "applied"
```
**Step 6sub2api-bridge 侧验证**
```sql
SELECT event_id, result, detail FROM supply_bridge_log WHERE event_id = 'g4-test-001';
-- 断言存在记录result='applied'
```
**Step 7恢复本地 runtime**
```bash
curl -X POST http://43.155.133.187:8081/internal/supply-intelligence/gateway/runtime/resume
# 期望:{"paused":false}
```
### 5.4 验证通过标准
| 检查项 | 通过标准 | 对账侧 |
|--------|----------|--------|
| event 发布成功 | HTTP 200返回 event_id | supply-intelligence |
| runtime 暂停成功 | HTTP 200`paused:true` | supply-intelligence |
| event 未被本地消费 | pause 期间 `gateway_sync_status` 保持 `pending` | supply-intelligence |
| bridge 成功拉取 | bridge stdout 出现 fetch 日志 | sub2api-bridge |
| bridge 成功持久化 | `supply_bridge_log` 存在对应记录 | sub2api-bridge |
| bridge 成功 ack | HTTP 204无错误 | supply-intelligence |
| event 终态 applied | `admission-state` 显示 `applied` | supply-intelligence |
| consumer 标记正确 | event 的 `consumer='sub2api-bridge'` | supply-intelligence |
| runtime 恢复成功 | HTTP 200`paused:false` | supply-intelligence |
### 5.5 失败场景覆盖
| 场景 | 预期行为 | 验证方式 |
|------|----------|----------|
| bridge ack 前崩溃 | event 保持 pending可重试 | 查询 event 状态仍为 pending |
| bridge ack failed | supply-intelligence 记录 failed | 查询 event 状态为 failedconsumer=detail 可查 |
| runtime 未 pause | 本地 poller 可能在 bridge 前消费掉 event | 需要重新发布新 event 并严格先 pause |
| 网络中断 | bridge fetch/ack 报错event 状态不变 | bridge 日志 + event 状态不变 |
---
## 6. 任务拆解(具体到文件/函数,每项 < 5 分钟)
### 6.1 sub2api-bridge 改造
**任务 1改造拉取逻辑**
- 文件:`cmd/sub2api-bridge/main.go`
- 动作:将 `consumeOnce` 从调用 `/gateway/consume-once` 改为调用 `/gateway/package-changes`
- 函数:`fetchPackageChanges(ctx, baseURL, cursor)`
- 输出:返回 `[]PackageChangeEvent` + `next_cursor`
**任务 2改造 ack 逻辑**
- 文件:`cmd/sub2api-bridge/main.go`
- 动作:新增 `ackPackageChange(ctx, baseURL, eventID, consumer, result, detail)`
- 函数:调用 `POST /gateway/package-changes/{event_id}/ack`
- 输出HTTP 204 或 error
**任务 3主循环改造**
- 文件:`cmd/sub2api-bridge/main.go`
- 动作:将 `main()` 中的循环从 `consumeOnce -> bridge` 改为 `fetchPackageChanges -> filter pending -> bridgeToSub2API -> ackPackageChange`
- 逻辑:
```
cursor := ""
for {
events, nextCursor := fetchPackageChanges(cursor)
for _, evt := range events {
if evt.GatewaySyncStatus != "pending" { continue }
if err := bridgeToSub2API(db, evt); err != nil { log; continue }
if err := ackPackageChange(evt.EventID, "applied", "synced"); err != nil { log }
}
cursor = nextCursor
if cursor == "" { sleep 10s }
}
```
**任务 4编译与本地测试**
- 命令:`cd /home/long/project/supply-intelligence && go build ./cmd/sub2api-bridge`
- 验证:二进制可生成,无编译错误
### 6.2 G4 验证脚本
**任务 5编写 G4 验证脚本**
- 文件:`scripts/g4_remote_gateway_verify.sh`
- 动作:封装 5.3 节的 Step 0-7
- 输入SUPPLY_URL, SUB2API_DB
- 输出PASS / FAIL + 对账摘要
**任务 6脚本本地调试**
- 先对本地 supply-intelligence`go run ./cmd/supply-intelligence`PORT=8081执行验证
- 确认所有断言通过
### 6.3 tksea 远程验证
**任务 7tksea 环境检查**
- 确认 `43.155.133.187:8081/healthz` 可达
- 确认 runtime pause/resume API 响应正常
- 确认 demo 数据存在
**任务 8tksea G4 执行**
- 在可访问 tksea 的机器上运行改造后的 sub2api-bridge
- 执行 `scripts/g4_remote_gateway_verify.sh`
- 收集对账证据supply-intelligence event 记录 + bridge log 记录)
---
## 7. 风险与保护
| 风险 | 影响 | 保护/降级 |
|------|------|-----------|
| tksea 不可达或 API 变更 | G4 无法执行 | 先在本地完整跑通,再迁移到 tksea本地使用 postgres 或内存模式均可验证 |
| runtime pause 后仍被本地消费 | 事件被提前消费bridge 无事件可拉 | 验证方案加入"发布前 pause"时序;若仍失败,检查是否有其他 consumer 实例在运行 |
| bridge ack 重复/幂等问题 | 同一 event 被 ack 两次 | supply-intelligence `AckPackageEvent` 是幂等更新(按 event_id重复 ack 不会破坏状态 |
| bridge DB 不可写 | 远端证据缺失 | bridge 在写入 DB 前应先检查连接;写入失败不打 ackevent 保持 pending 可重试 |
| 网络抖动导致 fetch/ack 部分成功 | event 状态不一致 | fetch 成功但 ack 失败时bridge 不记录为成功;下次轮询会重新发现该 pending event因为未被 ack |
| 当前 tksea 使用 in-memory 后端 | 事件在进程重启后丢失 | G4 验证不要求持久化跨重启,只需验证同一进程生命周期内的触达闭环;若 tksea 使用 postgres则更优 |
---
## 8. QA 交接与实施约束
### 8.1 QA 必须核查的调用链
**链路 G4-A外部消费者拉取事件**
- 定义:`internal/httpapi/server.go :: handleListPackageChanges`
- 装配:`app.go` -> `NewServer` -> `Routes`
- 调用:`repo.ListPackageEventsAfter`
- 入口:`GET /gateway/package-changes?cursor=`
- 必查点:返回体包含 `gateway_sync_status` 字段,且 pending 事件可被外部消费者识别
**链路 G4-B外部消费者回写 ack**
- 定义:`internal/httpapi/server.go :: handleAckPackageChange`
- 装配:同上
- 调用:`repo.AckPackageEvent`
- 入口:`POST /gateway/package-changes/{event_id}/ack`
- 必查点ack 后 `admission-state` 中 `gateway_sync_status` 变为 applied/failed
**链路 G4-Cruntime 暂停/恢复**
- 定义:`internal/poller/runtime.go :: Pause/Resume`
- 装配:`app.go` -> `gatewayRuntime`
- 调用:`server.go` HTTP handler
- 入口:`POST /gateway/runtime/pause` / `resume`
- 必查点pause 后 `gateway/runtime-status` 返回 `paused:true`,且 poller 不再消费新 event
**链路 G4-Dsub2api-bridge 端到端**
- 定义:`cmd/sub2api-bridge/main.go`
- 装配:`go build ./cmd/sub2api-bridge`
- 调用package-changes -> bridge log -> ack
- 入口bridge 进程启动
- 必查点bridge stdout 显示完整闭环DB 中有记录supply-intelligence 侧状态同步
### 8.2 实施约束Engineer
1. 不允许修改 supply-intelligence 的 publish / consumer / retry 核心逻辑
2. sub2api-bridge 改造只允许在 `cmd/sub2api-bridge/` 内修改
3. 验证脚本必须放在 `scripts/` 目录,且使用 bash/curl/psql 等通用工具
4. 所有修改必须通过 `go build ./...` 编译检查
### 8.3 QA 验收标准
- [ ] `scripts/g4_remote_gateway_verify.sh` 在本地环境执行通过
- [ ] `scripts/g4_remote_gateway_verify.sh` 在 tksea 环境执行通过
- [ ] 双向对账断言全部通过supply-intelligence 侧 + bridge 侧)
- [ ] runtime pause/resume 不影响其他 API 可用性
- [ ] 失败场景(不 ack / ack failed可复现并产生预期状态
---
## 9. 阶段门控结论
### 9.1 当前状态评估
| 维度 | 状态 | 说明 |
|------|------|------|
| 代码成熟度 | 已就绪 | supply-intelligence 侧 publish/consume/ack/retry/runtime-control 全部已实现 |
| 接口可用性 | 已就绪 | package-changes + ack + pause/resume API 真实存在且可调用 |
| 远端代理 | 需改造 | sub2api-bridge 当前走 consume-once本地自动 ack需改为 package-changes + 手动 ack |
| 验证脚本 | 待编写 | 需新增 `scripts/g4_remote_gateway_verify.sh` |
| 环境可达性 | 已知风险 | 103.56.49.28 不可达,但 tksea 43.155.133.187 可用,可作为替代验证目标 |
### 9.2 结论
**阶段门控结论:可进入 G4 实施**
原因:
1. supply-intelligence 核心代码已具备 G4 所需的全部 API 与控制能力
2. 缺口集中在 sub2api-bridge 的改造和验证脚本的编写,范围可控
3. 改造不触及 supply-intelligence 核心,风险低
4. 有明确的本地->tksea 两级验证路径,可逐步推进
### 9.3 进入下一阶段的条件
- sub2api-bridge 改造完成并通过本地验证
- `scripts/g4_remote_gateway_verify.sh` 编写完成并通过本地验证
- tksea 环境验证通过,产出双向对账证据
---
## 10. 下游执行约束摘要
### Engineer
- 任务范围:`cmd/sub2api-bridge/main.go` 改造 + `scripts/g4_remote_gateway_verify.sh` 编写
- 不允许触碰 supply-intelligence 核心代码
- 本地验证通过后,再提交到 tksea 验证
- 产出物:改造后的 sub2api-bridge 二进制 + 验证脚本 + 执行日志
### QA
- 核查四条调用链G4-A ~ G4-D是否真实可调用
- 执行 `scripts/g4_remote_gateway_verify.sh` 并确认双向对账
- 验证 runtime pause/resume 的隔离性
- 产出物QA 验收报告 + 对账证据截图/日志
### XLTechLead / 运维)
- 确认 tksea 环境可达性43.155.133.187:8081
- 若 tksea 使用 in-memory 模式,确认验证期间不重启进程
- 若需长期保留 G4 证据,建议将 tksea 切换为 postgres 后端后再执行验证
- 产出物:环境确认签字 + 执行窗口协调
---
## 自检清单
- [x] 已读取关键代码并理解现有事件流
- [x] 接口定义完整(请求/响应/错误)
- [x] G4 验证方案可执行、可验证
- [x] 每个任务 < 5分钟有明确文件路径
- [x] 风险评估完整
- [x] 已明确标记是否可进入下一阶段
- [x] 已给出 Engineer / QA / XL 的下游执行约束摘要

View File

@@ -0,0 +1,262 @@
# G4 真实远端 Gateway 集成验证 PRD
文档版本v1.0
日期2026-05-10
作者PM生产门禁收口
状态:待 TechLead 评审
---
## 1. 概述
Supply-Intelligence 的 G1smoke 主链、G2inspect/metrics、G3rollback 演练)已在本地与 tksea 服务器完成。当前生产门禁为 `REQUEST_CHANGES`,唯一阻断项是 G4真实远端 gateway 集成验证。
G4 不是新增功能,而是对已有 gateway publish / consume / ack 链路在共享预发环境中的端到端实证要求。supply-intelligence 作为事件生产者sub2api / tokens-reef 作为下游消费者,必须在共享环境中留下可复核的双侧对账记录。
---
## 2. 目标
在共享预发环境中完成一次闭环验证,证明:
1. supply-intelligence 产生的 `package_change_event` 能被远端系统sub2api / tokens-reef真实消费。
2. 消费的 EVENT_ID 在 supply-intelligence 侧与远端侧均可被独立查询,且状态一致。
3. 远端消费失败时supply-intelligence 侧不会误标为 applied而是进入 retry 或保持 pending。
4. 验证过程可复现、可脚本化、可归档为 QA 复核证据。
---
## 3. 范围
### 3.1 In Scope
- supply-intelligence 共享预发环境(当前为 tksea 43.155.133.187:8081的事件 publish 与 consume-once API。
- sub2api / tokens-reef 作为远端 consumer 对 consume-once 的调用及后续处理。
- 双侧 EVENT_ID 对账机制的定义与验证脚本。
- 共享环境中 gateway runtime 的暂停/恢复操作(避免与远端 consumer 竞争单 ack 事件)。
- G4 验证证据包的格式、归档位置与 QA 复核流程。
### 3.2 Out of Scope
- supply-intelligence 核心 publish / consume / ack 业务逻辑的代码级改造(主链路已在 G1-G3 验证通过)。
- sub2api / tokens-reef 内部业务规则的深度改造(如 token 配额算法、模型路由策略)。
- 多 consumer 独立 ack schema 的长期重构(已知当前为单 ack 设计G4 通过操作规程规避竞争)。
- 非 gateway 链路probe、discovery、admission的额外验证。
### 3.3 假设与依赖
- 假设 sub2api / tokens-reef 在共享环境中已可运行tksea 8080 端口已确认运行)。
- 假设 sub2api 侧至少能提供一张持久化表或一个查询接口,记录从 supply-intelligence 消费的事件及其处理结果。
- 假设 supply-intelligence 与 sub2api 在共享环境中网络可达(同服务器已满足)。
- 依赖 sub2api 侧负责人提供消费端的最小实现或已有 bridge 的扩展方案。
- 依赖 TechLead 在 G4 验证前确认单 ack schema 的临时操作规程(暂停 gateway runtime
---
## 4. 用户场景
### 4.1 主流程:共享环境端到端对账
1. **前置**:执行人调用 `POST /gateway/runtime/pause` 暂停 supply-intelligence 内置 gateway runtime。
2. **Publish**:执行人调用 `POST /publish/package-event` 产生一个真实 EVENT_ID。
3. **远端消费**sub2api 以 consumer=`sub2api`(或已有 consumer 名称)调用 `POST /gateway/consume-once` 拉取事件。
4. **远端处理**sub2api 将事件应用到自身系统(更新模型列表、路由规则或至少写入持久化消费记录表),并在本地记录 processing_result。
5. **Ack**supply-intelligence 的 consume-once 内部自动将事件 ack 为 applied若 sub2api 调用成功)或 failed若处理返回失败
6. **双侧对账**:执行人运行对账脚本,输入 EVENT_ID查询 supply-intelligence 的 `package-changes` / `admission-state` 与 sub2api 侧的持久化记录,比对 event_id、package_id、status、consumer、timestamp。
7. **恢复**:执行人调用 `POST /gateway/runtime/resume` 恢复 gateway runtime。
8. **归档**执行人保存命令、stdout、关键 JSON 片段到证据包目录。
### 4.2 异常流:远端消费失败
1. sub2api 调用 consume-once 成功获取事件,但在后续处理时抛出业务错误(如模型不存在、数据库冲突)。
2. sub2api 不向 supply-intelligence 发送额外 ackconsume-once 已完成 ack
3. 如果 sub2api 需要标记失败,当前单 ack schema 下 consume-once 已返回 applied/ failed。因此 TechLead 必须选择以下策略之一:
- **策略 A**sub2api 在本地记录失败,对账时以 sub2api 本地记录为准supply-intelligence 侧状态视为传输层 ack。
- **策略 B**:改造 consume-once 调用方式,使 sub2api 先读取事件但不自动 ack处理成功后再显式调用 `POST /gateway/package-changes/{event_id}/ack`
4. 无论采用哪种策略QA 必须能在对账脚本中明确区分"supply-intelligence 侧状态"与"sub2api 侧真实处理结果"。
### 4.3 边缘流gateway runtime 未暂停导致事件被抢走
1. 若执行人未暂停 gateway runtime内置 consumer 会在 1 秒内自动消费并 ack 新 publish 的事件。
2. sub2api 再次调用 consume-once 时,该事件状态已为 applieditems 列表中不再包含此事件。
3. 对账脚本检测到 sub2api 侧无此 EVENT_ID 记录,判定为 mismatch。
4. **处置**:本场景作为 G4 验证的负向测试用例,用于证明单 ack schema 的竞争风险真实存在;正式 G4 验证必须通过 pause runtime 规避。
### 4.4 边缘流:重复 publish
1. 同一 EVENT_ID 被重复 publish 时supply-intelligence 返回 HTTP 409`duplicate_publish_request``publish_already_applied`)。
2. 远端 consumer 不应收到重复事件。对账脚本验证 sub2api 侧同一 EVENT_ID 仅出现一次。
### 4.5 边缘流unauthorized consumer
1. sub2api 使用的 consumer 名称若未关联目标事件的 account_id`isAuthorizedForEvent` 返回 false。
2. consume-once 的 items 列表中不包含该 unauthorized 事件。
3. 事件在 supply-intelligence 侧保持 pending不会被错误消费。
---
## 5. 验收标准AC
每条 AC 必须可被 QA 或自动化脚本在共享环境中执行,并给出二元判定(通过 / 不通过)。
**AC1远端 consumer 可达性**
- 判定方法:从 sub2api 所在主机执行 `curl -fsS -X POST "${SUPPLY_URL}/internal/supply-intelligence/gateway/consume-once?consumer=sub2api"`HTTP 状态码必须为 200响应 JSON 必须包含 `consumer``items` 字段。
- 通过标准HTTP 200 且 JSON schema 符合 `ConsumeOnceOutput` 定义。
**AC2真实事件被远端消费**
- 判定方法:在 supply-intelligence 侧执行 publish 产生 EVENT_ID `evt-g4-{timestamp}`;随后从 sub2api 侧调用 consume-once检查 sub2api 侧持久化存储中是否存在该 EVENT_ID 的记录。
- 通过标准sub2api 侧数据库表或审计日志中至少存在一条记录,其 `event_id` 字段等于 `evt-g4-{timestamp}`
**AC3supply-intelligence 侧事件终态正确**
- 判定方法:在 AC2 完成后,调用 `GET /internal/supply-intelligence/gateway/package-changes``GET /internal/supply-intelligence/models/{platform}/{model}/admission-state`,检查该 EVENT_ID 的 `gateway_sync_status`
- 通过标准:对于成功的远端消费,`gateway_sync_status``applied`;对于明确失败的远端消费,`gateway_sync_status``failed`。不允许为 `pending`
**AC4双侧状态可对账**
- 判定方法:执行对账脚本(待 TechLead 提供,路径建议 `scripts/g4_reconcile.sh`),输入 EVENT_ID脚本分别查询 supply-intelligence 与 sub2api 两侧。
- 通过标准:脚本输出 JSON 必须包含 `match=true`,且两侧 `event_id``package_id``status`(或 processing_result一致脚本执行时间不得超过 60 秒。
**AC5远端消费失败时的状态隔离**
- 判定方法:制造一个远端处理失败的场景(例如 sub2api 消费后记录 processing_result=failed或在 consume-once 前模拟 sub2api 内部错误);检查 supply-intelligence 侧事件状态与 sub2api 侧记录。
- 通过标准:若采用策略 A传输层 acksupply-intelligence 侧可为 applied但 sub2api 侧必须记录 processing_result=failed对账脚本输出 `match=false` 并标注原因;若采用策略 B显式 acksupply-intelligence 侧必须为 failed。不允许出现"supply-intelligence 侧 applied 且 sub2api 侧无记录"的幽灵状态。
**AC6gateway runtime 暂停不影响 API 可用性**
- 判定方法:在 gateway runtime 暂停期间(`paused=true`),重复执行 AC1 的 consume-once 调用,同时检查 `healthz``runtime-status`
- 通过标准consume-once API 返回 200`healthz` 返回 ok`runtime-status` 返回 `paused=true`gateway runtime 恢复后 `paused=false`
**AC7完整闭环证据归档**
- 判定方法:执行人在共享环境中完成 AC1-AC6 后,将产物写入 `reports/production/evidence-g4-{date}/` 目录。
- 通过标准:目录中必须包含以下文件,且时间戳在 24 小时内:
- `00_preflight.json`healthz + runtime-status 演练前)
- `01_publish.json`publish 响应)
- `02_consume_once.json`sub2api 侧调用 consume-once 的响应)
- `03_sub2api_record.sql``.json`sub2api 侧持久化记录查询结果)
- `04_reconcile.json`(对账脚本输出)
- `05_runtime_after_resume.json`(恢复后的 runtime-status
---
## 6. 边缘情况与失败路径
| 场景 | 预期行为 | 验证方式 |
|------|---------|---------|
| gateway runtime 未暂停,事件被内置 consumer 抢走 | sub2api consume-once 返回空 items对账 mismatch | AC4 负向测试 |
| sub2api 调用 consume-once 时 supply-intelligence 宕机 | sub2api 收到 HTTP 5xx 或连接超时;事件保持 pending | 检查 supply-intelligence 重启后事件状态仍为 pending |
| sub2api 消费后宕机,未写入本地记录 | 对账时 sub2api 侧 not_foundsupply-intelligence 侧可能已 applied | AC5 明确失败策略 |
| 重复调用 consume-once 同一 cursor | 返回空 items 或 next_cursor 为空;无重复 ack | AC4 验证 sub2api 侧无重复记录 |
| 使用未授权的 consumer 名称 | consume-once 不返回该账号事件;事件保持 pending | 负向测试publish 后换 consumer 名称调用,验证 items 为空 |
| 网络分区导致 consume-once 超时 | sub2api 侧重试supply-intelligence 侧事件状态不变 | 模拟超时后重试,验证事件未被错误 ack |
---
## 7. 上线与运营准备
### 7.1 共享环境配置清单
- [ ] supply-intelligence 在 tksea 的 BASE_URL 已确认(当前 43.155.133.187:8081
- [ ] sub2api / tokens-reef 在 tksea 的地址与数据库连接串已确认(当前 8080 端口PostgreSQL 本地)。
- [ ] sub2api 侧 consumer 名称已确定(建议 `sub2api` 或沿用 `sub2api-bridge`)。
- [ ] sub2api 侧持久化表已创建(至少含 event_id, package_id, status, consumed_at, processing_result 字段)。
- [ ] supply-intelligence 侧 gateway runtime 可在验证前被手动暂停。
### 7.2 对账脚本
- TechLead 需提供 `scripts/g4_reconcile.sh`,输入 EVENT_ID 与两侧 BASE_URL输出 JSON 对账结果。
- 脚本必须返回明确 exit code0match、1mismatch、2not_found / 查询失败)。
### 7.3 监控与告警
- G4 验证期间,共享环境必须保持 `/metrics` 可访问。
- 对账脚本执行后,必须记录 `supply_intelligence_gateway_events_processed_total``supply_intelligence_gateway_failed_events` 的采样值。
- 若 G4 验证重复执行超过 3 次仍 mismatch值班人员必须通知 TechLead 排查,禁止强行修改数据通过门禁。
### 7.4 回滚预案
- 若 G4 验证导致 sub2api 侧数据异常sub2api 侧负责人应使用自身系统的回滚机制恢复。
- supply-intelligence 侧可通过 `gateway/runtime/pause` 停止事件下发,已 ack 的事件不可回滚(事件日志性质)。
- 若需要撤销已 publish 的 package使用 supply-intelligence 的 publish 替换机制(发布新 package-event而非删除历史 event。
### 7.5 值班 runbook
1. 执行 G4 前,确认 `runtime-status``started=true`,然后执行 `runtime/pause`
2. 执行 publish记录返回的 EVENT_ID。
3. 等待 sub2api 侧执行 consume-once或手动触发
4. 运行 `g4_reconcile.sh`
5. 若 match=true执行 `runtime/resume`,归档证据包。
6. 若 match=false保持 paused 状态,通知 TechLead 与 sub2api 侧负责人,排查后重新执行。
---
## 8. 依赖与风险
| 依赖项 | 状态 | 风险描述 | 缓解措施 |
|--------|------|---------|---------|
| sub2api 侧 consumer 实现 | 缺失 | sub2api 当前未配置 supply-intelligence 集成,无持久化消费记录 | sub2api 侧负责人需在 G4 前完成最小消费记录表与查询接口 |
| 单 ack schema | 已知限制 | 同一时间只能有一个 consumer ack 事件gateway runtime 会与 sub2api 抢事件 | G4 验证期间通过 `runtime/pause` 规避;长期需 TechLead 评估多 consumer schema 改造 |
| 网络稳定性 | 中风险 | tksea 同服务器网络应稳定,但跨容器/进程调用仍可能失败 | 对账脚本增加重试与超时;失败时标记为 not_found 而非误报 match |
| 证据包人工操作 | 中风险 | 执行人可能遗漏归档步骤或时间戳不一致 | 对账脚本自动将结果写入文件QA 复核时检查文件存在性与时间戳 |
| sub2api 业务逻辑不可用 | 低风险 | 若 sub2api 内部业务系统暂无法处理 package changebridge 只能写日志 | PRD 接受"持久化消费记录表"作为最低证据,不要求立即触发完整业务闭环 |
---
## 9. 阶段门控结论
### 9.1 当前信息是否足够进入 TechLead 设计阶段?
**结论:足够。**
依据:
1. G4 缺口已被精确识别,不是模糊的"缺集成",而是"缺远端 consumer 消费 + 双侧对账证据"。
2. supply-intelligence 侧的 APIpublish、consume-once、package-changes、admission-state、runtime pause/resume已经存在且经 G1-G3 验证稳定。
3. sub2api-bridge 已提供技术方向参考pull 模式、写日志表TechLead 只需在此基础上扩展为持久化记录 + 查询接口。
4. 单 ack schema 的限制已被识别并有明确的临时操作规程pause runtime
5. 所有验收标准均已量化HTTP 200、60 秒、match=true/false、特定 JSON 字段)。
### 9.2 TechLead 必须产出的设计决策
1. **策略选择**:采用策略 A传输层 ack + sub2api 本地记录 processing_result还是策略 B显式 ack 接口)?
2. **sub2api 侧最小实现**:确定 consumer 名称、持久化表 schema、查询接口路径。
3. **对账脚本**`scripts/g4_reconcile.sh` 的实现(语言、两侧查询方式、输出 schema
4. **多 consumer 长期方案**:是否在 G4 之后启动多 consumer 独立 ack schema 的改造?(当前 G4 不要求改造)。
### 9.3 QA 可提前准备的内容
1. 基于本 PRD 的 AC 编写自动化测试用例框架(即使 sub2api 侧尚未 ready也可 mock 远端查询接口)。
2. 审核证据包目录结构与命名规范。
3. 准备负向测试用例unauthorized consumer、重复 publish、runtime 未暂停)。
---
## 10. 下游关注点摘要
### 10.1 给 TechLead
- **核心决策**G4 只需要证明"远端真实消费",不需要一次性完成完美的双向 ack。请尽快确认策略 A 或 B以便 QA 编写对账脚本。
- **已知债务**`CountRetryablePendingPackageEvents``ListRetryablePendingPackageEvents` 当前忽略 consumer 参数QA 报告 4.1。G4 使用单 consumer 验证,暂不触发该债务,但请记录到后续迭代 backlog。
- **实现量评估**sub2api 侧最小改造量约为:创建一张消费记录表 + 一个查询接口 + 扩展 bridge 逻辑。若已有 sub2api-bridge改造量预计在 1-2 人日。
### 10.2 给 QA
- **测试重点**:不要只验证"consume-once 返回 200",必须验证 EVENT_ID 在 sub2api 侧有持久化记录。
- **负向用例**:务必执行"runtime 未暂停"场景,证明单 ack 竞争真实存在,且 pause 是 G4 的必要前置步骤。
- **证据完整性**:严格按照 AC7 的 6 个文件清单审核证据包,缺少任一文件即判定 G4 不通过。
### 10.3 给 XL执行/运维)
- **执行顺序**:必须先 pause → publish → 等待 sub2api 消费 → 对账 → resume。任何跳过 pause 的执行均视为无效证据。
- **环境保真**G4 验证期间tksea 上的 supply-intelligence 与 sub2api 配置不得被其他测试干扰。建议预约独占窗口。
- **产物路径**:证据包统一存放于 `reports/production/evidence-g4-YYYY-MM-DD/`,由 QA 复核后合并到 `SHARED_ENV_EVIDENCE_RUN_YYYY-MM-DD.md`
---
## 附录 A自检清单
返回本 PRD 时,以下条目已逐项确认:
- [x] 已明确真实目标,不是只复述功能
- [x] 已写清 In Scope / Out of Scope
- [x] 每个 AC 都可被 QA 或测试用例直接验证
- [x] 已覆盖异常流、边缘流与失败路径
- [x] 已补齐上线、运营、监控、回滚要求
- [x] 已明确当前是否可进入 TechLead 阶段
- [x] 已给出 TechLead / QA / XL 的下游关注点摘要
- [x] 没有使用"优化、支持、友好、尽量、快速"等模糊词替代明确要求

View File

@@ -0,0 +1,158 @@
# Supply-Intelligence 灰度放量执行计划2026-05-10
状态:待执行
仓库:`/home/long/project/supply-intelligence`
前提QA 报告 CONDITIONAL_APPROVED上线前检查清单已通过
---
## 0. 灰度策略总览
supply-intelligence 采用 **account 级灰度**,通过控制 `AccountRoutingState.RoutingEnabled``SupplyAccount.ConsumerTag` 实现逐步放量。
灰度阶段:
1. 影子运行0% account只验证服务存活
2. 单 account 验证1 个测试 account
3. 小批量放量10% active accounts
4. 半量放量50% active accounts
5. 全量放行100% active accounts
---
## 1. 影子运行Shadow / 0% Account
目标:验证服务部署后无 panic、无异常日志、metrics 正常。
执行步骤:
```bash
# 1. 部署到目标环境(并入 supply-api 主仓或独立实例)
# 2. 不启用任何 account 的 routing_enabled
# 3. 仅执行健康检查和 metrics 抓取
curl -fsS http://<BASE_URL>/healthz
curl -fsS http://<BASE_URL>/metrics | grep supply_intelligence_
```
观察窗口5 分钟
通过标准:
- healthz 返回 200
- metrics 正常暴露无 panic
- 无 ERROR/FATAL 日志
---
## 2. 单 Account 验证1 Account
目标:验证完整业务链路在真实环境下可行。
执行步骤:
```bash
# 1. 选择一个测试 account建议非生产关键 account
# 2. 插入 test-passed candidate + draft package
# 3. 执行完整链路
BASE_URL="<BASE_URL>" PLATFORM="openai" MODEL="<test-model>" EVENT_ID="evt-gray-1" \
bash scripts/gateway_closure_smoke.sh
```
验证要点:
- publish 返回 candidate=published, package=active
- consume-once 返回 event=applied
- admission-state 返回 gateway_sync_status=applied
- inspect 返回 decision=continue
观察窗口10 分钟
通过标准:链路完整闭环,无 failed 事件。
---
## 3. 小批量放量10% Active Accounts
目标:验证多 account 并发下无异常。
执行步骤:
```bash
# 1. 选取 10% 的 active accounts设置 routing_enabled=true
# 2. 观察 10 分钟
# 3. 执行 inspect 脚本,确认指标正常
BASE_URL="<BASE_URL>" CONSUMER="gateway" bash scripts/gateway_closure_inspect.sh
```
关键指标:
- `gateway_events_processed_total` 增长与 publish 频率匹配
- `gateway_event_latency_seconds` P99 < 1s
- `gateway_pending_retry_events` < 5
- `gateway_failed_events` = 0
观察窗口10 分钟
通过标准:所有关键指标在基线范围内。
---
## 4. 半量放量50% Active Accounts
目标:验证中等负载下稳定性。
执行步骤:
- 逐步放开至 50% active accounts
- 每批放量后执行 inspect
- 观察 latency 和 error rate
关键指标:
- 同上,但 latency P99 容忍度放宽至 < 2s
观察窗口30 分钟
通过标准无告警触发inspect 决策为 continue。
---
## 5. 全量放行100% Active Accounts
目标:所有 active accounts 启用 supply-intelligence 路由。
执行步骤:
- 放开全部 active accounts
- 启动 24h/72h/首周巡检(见 `PRODUCTION_OBSERVABILITY_CHECKLIST`
---
## 6. 止损条件(任意阶段触发即回滚)
| 条件 | 触发值 | 动作 |
|------|--------|------|
| healthz 连续失败 | 3 次 | 立即 pause runtime |
| gateway 失败率 | > 10% | 执行 rollback 脚本 |
| pending retry 积压 | > 50 | 暂停放量,排查 consumer |
| latency P99 | > 5s | 降级至上一阶段比例 |
| panic / fatal 日志 | > 0 | 全量回滚 |
回滚命令:
```bash
curl -X POST "<BASE_URL>/internal/supply-intelligence/gateway/runtime/pause"
```
---
## 7. 执行决策点
需要确认:
1. **部署目标**:并入 supply-api 主仓 / tksea 独立实例 / 其他环境
2. **BASE_URL**:灰度环境的实际访问地址
3. **测试 account**:单 account 验证时使用的 account ID
4. **放量节奏**:每阶段观察窗口时长(默认按本计划)
5. **值班人**:各阶段执行人和紧急联系人
---
## 8. 本地预验证已完成项
| 阶段 | 状态 | 证据 |
|------|------|------|
| 影子运行 | ✅ | healthz=200, metrics 正常 |
| 单 account | ✅ | smoke 脚本通过decision=continue |
| 回滚脚本 | ✅ | rollback.sh 语法通过pause/resume API 可用 |
---
版本v1.0 | 创建2026-05-10

View File

@@ -0,0 +1,180 @@
# Supply-Intelligence 生产上线收敛任务板2026-05-07
> 状态:当前有效
> 目标:把 supply-intelligence 从“最小闭环骨架”推进到“可生产上线判定”
> 仓库:`/home/long/project/立交桥/projects/supply-intelligence`
> 事实基线:本地 `go test ./...` 通过;当前分支 `main`;最新提交 `afdbea6 feat: bootstrap supply intelligence baseline`
## 0. 当前门控结论
当前结论REQUEST_CHANGES
原因不是项目不可运行,而是“可运行骨架”与“真源要求的生产闭环”仍存在关键差距,不能宣称可上线。
## 1. 事实基线
### 1.1 已验证事实
- 仓库存在真实代码、测试、迁移、文档:`.git``go.mod``internal/``migrations/``tech/`
- 本地执行 `cd '/home/long/project/立交桥/projects/supply-intelligence' && go test ./...` 通过
- 已存在模块:`probe``discovery``admission``publish``gatewayconsumer``httpapi``repository`
- 已存在 HTTP 路由:
- `/internal/supply-intelligence/accounts/{account_id}/routing-state`
- `/internal/supply-intelligence/discovery/candidates`
- `/internal/supply-intelligence/admission/run`
- `/internal/supply-intelligence/gateway/package-changes`
- `/internal/supply-intelligence/gateway/package-changes/{event_id}/ack`
- `/internal/supply-intelligence/gateway/consume-once`
### 1.2 已确认关键差距
- `internal/domain/types.go` 仍保留旧 candidate 状态:`pending_admission``admitted`
- `internal/httpapi/server.go` 的状态解析仍接受旧状态
- `internal/probe/state_machine.go` 仍是 `suspended + explicit_failure -> disabled` 的单步逻辑未体现“3 次连续 explicit failure 才 disabled”
- `internal/publish/service.go` 已完成基础 publish event 持久化与 pending 状态写入,但仍未覆盖 `draft -> active``candidate test_passed -> published` 的完整事务联动
- `GET /internal/supply-intelligence/models/{platform}/{model}/admission-state` 未接入真实入口
- gateway consumer 已有最小 poll/apply/ack 骨架,但仍需补足生产门禁证据与发布状态联动
### 1.3 事实更新2026-05-07 复核)
- 本地执行 `cd '/home/long/project/立交桥/projects/supply-intelligence' && go test ./...` 通过
- 代码中已存在 publish/service 与 repository 的事件落库、ack、gateway snapshot 基础路径
- 当前首个阻塞不再是“publish 事件未持久化”,而是“发布事务与 admission-state / 状态机联动未收口”
- 因此首个阻塞项应下沉为 B2/B3/B4 的联动闭环,而不是单纯 event append
## 2. 最短闭环路径
1. 先修 Phase Aprobe/account 状态机与 routing-state 真正符合真源
2. 再修 Phase B/Ccandidate 状态机与 admission/draft 闭环一致
3. 再修 Phase D真实发布事务 + admission-state API + gateway sync 联动
4. 再做全链路 QA 复核与上线证据收敛
## 3. 任务板
## A. Design
### A1. 收敛状态机真源到代码级约束
- OwnerTechLead
- 交付物:状态机收敛设计说明
- 范围:
- probe 账号状态迁移规则
- candidate 生命周期合法状态与迁移
- publish/gateway_sync 的语义边界
- 完成标准:
- 明确删除 `pending_admission` / `admitted`
- 明确 `published != applied`
- 明确 `suspended -> disabled` 的窗口规则
- 验证方式:设计文档与现有代码差异清单完整
- 依赖:无
- 状态pending
### A2. 定义发布事务与 admission-state 读取契约
- OwnerTechLead
- 交付物:发布事务与 `/models/{platform}/{model}/admission-state` 契约说明
- 完成标准:
- 明确 package、candidate、gateway_sync 三者联动字段
- 明确 handler / service / repository 落点
- 验证方式:文件级任务拆解完成
- 依赖A1
- 状态pending
## B. Implementation
### B1. 修复 probe 状态机实现
- OwnerEngineer
- 交付物:`internal/probe/*``internal/domain/*`、相关 repo/test 修正
- 完成标准:
- inconclusive 不触发惩罚性迁移
- disabled 只在满足真源规则时发生
- 补齐主路径与失败路径测试
- 验证方式:`go test ./internal/probe ./internal/app ./internal/httpapi`
- 依赖A1
- 状态pending
### B2. 清理 candidate 旧状态并对齐 admission 流转
- OwnerEngineer
- 交付物:`internal/domain/types.go``internal/discovery/*``internal/admission/*``internal/httpapi/server.go`、相关测试
- 完成标准:
- 删除 `pending_admission` / `admitted`
- `discovered/testing/test_passed/test_failed/retry_pending/ignored/published/deprecated/closed` 全链路一致
- discovery / admission / HTTP 参数校验统一
- 验证方式:`go test ./internal/discovery ./internal/admission ./internal/httpapi`
- 依赖A1
- 状态pending
### B3. 实现真实 publish 事务
- OwnerEngineer
- 交付物:`internal/publish/*``internal/repository/*``internal/app/*`、相关测试
- 完成标准:
- draft -> active
- candidate `test_passed -> published`
- event append 作为发布事务的一部分,不再只是独立记录器
- 验证方式:`go test ./internal/publish ./internal/app ./internal/repository`
- 依赖A2
- 状态pending
### B4. 接入 admission-state API
- OwnerEngineer
- 交付物:`internal/httpapi/server.go``internal/repository/*`、相关测试
- 完成标准:
- 存在真实读取入口 `/internal/supply-intelligence/models/{platform}/{model}/admission-state`
- 返回 candidate/package/gateway_sync 组合态
- 验证方式:`go test ./internal/httpapi ./internal/repository`
- 依赖A2, B2, B3
- 状态pending
## C. Verification
### C1. QA 复核 probe/account 主链路
- OwnerQA
- 交付物:结构化审查报告
- 完成标准:
- 验证 definition -> assembly -> call -> entry
- 验证状态机与真源一致
- 验证方式:代码抽检 + 运行 targeted tests
- 依赖B1
- 状态pending
### C2. QA 复核 candidate/admission/publish 主链路
- OwnerQA
- 交付物:结构化审查报告
- 完成标准:
- 验证 candidate 状态无旧口径残留
- 验证 publish 事务不是“只写 event”
- 验证 `published != applied`
- 验证方式:代码抽检 + 运行 targeted tests
- 依赖B2, B3, B4
- 状态pending
### C3. 端到端最小闭环验证
- OwnerQA
- 交付物:最小闭环验证记录
- 完成标准:
- candidate -> test_passed -> publish -> package-changes -> ack
- admission-state 可反映 pending/applied/failed
- 验证方式:`go test ./...` + 必要的集成命令/测试
- 依赖C2
- 状态pending
## D. Release Evidence
### D1. 上线证据包整理
- OwnerXL
- 交付物:上线前结论摘要
- 完成标准:
- 列清已完成范围
- 列清剩余非阻塞项
- 列清不可宣称项
- 验证方式:对照 QA 结果与最新测试输出
- 依赖C1, C2, C3
- 状态pending
## 4. 明确禁止的错误结论
- 不得把 `go test ./...` 通过等同于“可生产上线”
- 不得把 `published` 等同于 `gateway applied`
- 不得把仅存在 handler/route 等同于真实主链路完成
- 不得把 event append 记录器等同于真实发布事务
## 5. 当前推荐执行顺序
1. TechLead 先出状态机/发布事务收敛设计
2. Engineer 先做 B1 + B2
3. Engineer 再做 B3 + B4
4. QA 做 C1/C2/C3
5. XL 汇总 D1 并给出“可上线/不可上线”结论

View File

@@ -0,0 +1,167 @@
# Supply-Intelligence 生产上线收口执行板2026-05-08
状态:当前有效
目标:把“可上线证据包”之后的剩余阻塞项,拆成 PM / TechLead / QA / Engineer 的可执行收口板,推动进入真实上线实施。
仓库:`/home/long/project/立交桥/projects/supply-intelligence`
当前门控:`REQUEST_CHANGES`
## 0. 当前判断
当前不是“继续写报告”的阶段,而是“按阻塞项执行”的阶段。
已验证事实:
- 最小主链路代码与自动化测试已通过
- PostgreSQL E2E 已建立
- 发布 / ack / admission-state / consumer 约束已有证据
仍需执行的剩余上线阻塞项:
1. 定义并实现真实 gateway 契约与失败重试策略
2. 产出可执行的灰度 / 回滚 runbook
3. 补齐观测与上线后巡检门禁
## 1. 实施总原则
- 先补执行板,再分派执行
- 先定义契约,再做实现
- 先做可回滚,再做可放量
- 先补观测,再放行上线
- 任何“已完成”都必须落到文件、命令、证据
## 2. 角色化执行链
### 2.1 PM
职责:把剩余上线阻塞项写成可验收、可上线、可回滚的产品/运营定义。
必须输出:
- gateway 契约边界:内部消费 / 外部真实 gateway 的能力与非能力
- 重试策略口径:哪些失败可重试、重试上限、终态定义
- 灰度/回滚 runbook 的业务判定线
- 上线后巡检项:首日、首周、异常回退触发条件
验收标准:
- 每条都可直接被 TechLead 转成实现任务
- 没有模糊词
- 明确上线成功 / 失败判定线
### 2.2 TechLead
职责:把 PM 的口径转成真实工程方案与文件级任务。
必须输出:
- gateway 契约实现边界与状态机
- 失败重试策略(含终态 / 重试 / 回退)
- rollout / rollback runbook 的技术执行步骤
- 观测指标、告警、巡检门禁的落点
- 文件级任务拆解
验收标准:
- 每个任务有具体文件路径
- 每个关键能力有真实调用链路
- 每个风险点有保护或降级策略
### 2.3 QA
职责:前置审查设计,后置检查实现漂移与上线门禁是否足够。
必须输出:
- 设计审查结论:是否可进入实现
- 关键调用链路核查:定义→装配→调用→入口
- 灰度 / 回滚 / 观测门禁是否可执行
- 关键缺陷清单critical / important
验收标准:
- 结论必须基于真实文件或命令
- 不能只看定义,不看实际调用点
- 不能把“有文档”当成“能上线”
### 2.4 Engineer
职责:按设计落地真实实现、测试与验证。
必须输出:
- 修改文件清单(绝对路径)
- 实现代码
- 测试代码
- 验证命令与输出
- 剩余风险与阻塞声明
验收标准:
- 代码 / 测试 / 验证三件套齐全
- 不得只改文档不改代码
## 3. 当前三项收口任务
### 3.1 任务 A真实 gateway 契约与失败重试策略
OwnerPM -> TechLead -> Engineer -> QA
交付物:
- gateway 契约说明
- 失败重试策略说明
- 相关代码与测试
完成标准:
- 明确哪些 ack / consume / event 状态是可重试的
- 明确哪些错误是终态,不再重试
- 明确外部真实 gateway 与当前本地 consumer 的边界
- 相关 HTTP / repo / consumer 语义一致
验证方式:
- 设计审查通过
- 实现测试通过
- 至少一条真实调用链路被核查
### 3.2 任务 B灰度 / 回滚 runbook
OwnerPM -> TechLead -> DevOps(必要时) -> QA
交付物:
- 可执行 runbook
- 灰度步骤
- 回滚步骤
- 失败判定与止损条件
完成标准:
- 至少有“上线前检查 / 灰度观察 / 失败回滚 / 回滚后确认”四段
- 每一步有明确负责人和触发条件
- 能直接用于演练
验证方式:
- 文档审查通过
- 至少一次桌面演练或脚本化验证
### 3.3 任务 C观测与上线后巡检门禁
OwnerTechLead -> Engineer -> QA -> DevOps(必要时)
交付物:
- 指标清单
- 告警清单
- 巡检清单
- 上线后 24h / 72h 检查项
完成标准:
- 关键链路有最小指标面
- 有异常时的止损与升级路径
- 巡检项与回滚条件挂钩
验证方式:
- 代码 / 配置 / 文档一致
- QA 核查指标是否真的接入
## 4. 执行顺序
1. PM 定义三项的业务/运营口径
2. TechLead 转成文件级设计与任务拆解
3. QA 做设计审查,确认可进入实现
4. Engineer 落地实现与测试
5. QA 做实现后审查与漂移检测
6. XL 汇总,更新上线结论
## 5. 明确禁止的错误结论
- 不得把“已有证据包”当成“已经可上线”
- 不得把“有 runbook 草稿”当成“可执行 runbook”
- 不得把“已有 metrics 文件”当成“观测已接入”
- 不得把“系统能跑”当成“上线条件已满足”
## 6. 当前下一步
立即进入任务 A 的 PM/TechLead 拆解,然后并行推进任务 B / C 的设计。

View File

@@ -0,0 +1,91 @@
# Supply-Intelligence 生产上线就绪验证报告2026-05-10
验证执行人:小龙
验证时间2026-05-10T20:30:00+08:00
仓库:`/home/long/project/supply-intelligence`
---
## 1. 验证范围
本次验证覆盖 QA 报告第 9 节建议的第 1 步:按 PRODUCTION_RUNBOOK 执行上线前检查清单。
---
## 2. 上线前检查清单执行结果
| # | 检查项 | 验证方法 | 结果 | 证据 |
|---|--------|-----------|------|------|
| 1 | 数据库迁移已应用 | `go test ./internal/httpapi -run TestPostgresE2E` | ✅ PASS | PostgreSQL E2E 测试通过 |
| 2 | 健康检查端点可达 | `curl /healthz` | ✅ 200 | `{"status":"ok"}` |
| 3 | 核心 metrics 可抓取 | `curl /metrics` | ✅ 可达 | Go runtime metrics 正常 |
| 4 | PostgreSQL 集成测试通过 | `go test ./internal/httpapi -run TestPostgresE2E` | ✅ PASS | E2E 链路通过 |
| 5 | 发布事务测试通过 | `go test ./internal/repository -run TestPostgresPublishPackageAtomically` | ✅ PASS | 并发双发布保护通过 |
| 6 | 无 pending 高危漏洞 | 查阅 QA 报告 | ✅ | QA 结论 CONDITIONAL_APPROVED无 OPEN critical |
| 7 | 回滚脚本可执行 | `bash scripts/gateway_closure_rollback.sh` | ✅ 执行成功 | pause/resume 状态正常 |
---
## 3. 灰度放量验证
| 阶段 | 目标 | 结果 | 证据 |
|------|------|------|------|
| 影子运行0% | 服务存活 | ✅ | healthz=200无 panic |
| 单 account1 | 完整链路闭环 | ✅ | smoke 通过admission-state=applied |
| 小批量10% | 多 account 并发验证 | ⏳ 待共享环境 | 需部署环境支持多 account |
| 半量50% | 中等负载稳定性 | ⏳ 待共享环境 | 需部署环境支持多 account |
| 全量100% | 所有 account 启用 | ⏳ 待共享环境 | 需部署环境支持多 account |
---
## 4. 执行板状态确认
| 项目 | 状态 | 证据文件 |
|------|------|----------|
| G1 smoke 主链留痕 | ✅ | SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md 第 2 节 |
| G2 inspect 留痕 | ✅ | SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md 第 3 节 |
| G3 rollback 演练 | ✅ | SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md 第 4 节 |
| G4 远端 gateway 对账 | ⏳ P2-2 技术债务 | 首版上线后第一个迭代周期补清 |
| G5 证据包归档 | ✅ | SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md |
| P0 阻断项 | ✅ 全部解除 | QA 报告第 5.3 节 |
| P1 必填项 | ✅ 全部解除 | QA 报告第 5.3 节 |
| 生产 runbook | ✅ | PRODUCTION_RUNBOOK_2026-05-10.md |
| 观测清单 | ✅ | PRODUCTION_OBSERVABILITY_CHECKLIST_2026-05-10.md |
| 灰度计划 | ✅ | GRAYSCALE_ROLLOUT_PLAN_2026-05-10.md |
---
## 5. 未解决的阻塞
| 阻塞 | 影响 | 解决方案 |
|------|------|----------|
| tksea SSH 访问不可用 | 无法在共享环境执行剩余灰度阶段10%/50%/100% | 待确认部署环境访问权限或选择其他部署目标 |
说明:本地验证已完成灰度的影子和单 account 阶段。实际生产上线时需在目标环境中执行剩余放量阶段。
---
## 6. 最终结论
### 门控结论CONDITIONAL_APPROVED
判断依据:
1. P0 阻断项已全部解除
2. P1 必填项已全部解除
3. G1-G3 共享环境验证已完成
4. G5 证据包已归档
5. 生产 runbook 与观测清单已补齐
6. 上线前检查清单已通过
7. 灰度放量影子+单 account 阶段已验证
### 允许上线条件:
- ✅ 可以上线
### 附加条件P2 技术债务):
- P2-2 真实远端 gateway 集成必须在首版上线后第一个迭代周期内补清
- 建议偿还期2 周内
- 追踪单tech/PRODUCTION_P0_P1_P2_BOARD_2026-05-08.md
---
版本v1.0 | 创建2026-05-10

View File

@@ -0,0 +1,203 @@
# Supply-Intelligence 生产观测与巡检清单2026-05-10
状态:当前有效
仓库:`/home/long/project/supply-intelligence`
目标:确保关键链路有最小可用的观测面,并明确异常时的止损与升级路径
---
## 1. 已接入指标清单
以下 metrics 已通过 Prometheus client 注册在 `internal/metrics/metrics.go`,可通过 `/metrics` 端点抓取。
### 1.1 Probe 层
| Metric | Type | Labels | 说明 |
|--------|------|--------|------|
| `supply_intelligence_probe_evaluations_total` | Counter | platform, classification | 探针评估次数 |
| `supply_intelligence_probe_latency_seconds` | Histogram | platform | 探针评估延迟 |
### 1.2 Discovery 层
| Metric | Type | Labels | 说明 |
|--------|------|--------|------|
| `supply_intelligence_discovery_scans_total` | Counter | platform, status | 扫描次数 |
| `supply_intelligence_discovery_new_models_total` | Counter | platform | 新发现模型数 |
### 1.3 Admission 层
| Metric | Type | Labels | 说明 |
|--------|------|--------|------|
| `supply_intelligence_admission_tests_total` | Counter | platform, result | 准入测试次数 |
| `supply_intelligence_admission_latency_seconds` | Histogram | platform | 准入测试延迟 |
### 1.4 Gateway / Consumer 层
| Metric | Type | Labels | 说明 |
|--------|------|--------|------|
| `supply_intelligence_gateway_events_processed_total` | Counter | platform, event_type, result | gateway 事件处理次数 |
| `supply_intelligence_gateway_event_latency_seconds` | Histogram | platform | gateway 事件处理延迟 |
| `supply_intelligence_gateway_event_retries_total` | Counter | platform, category | 重试次数 |
| `supply_intelligence_gateway_pending_retry_events` | Gauge | consumer | 待重试事件数 |
| `supply_intelligence_gateway_failed_events` | Gauge | consumer | 终态失败事件数 |
### 1.5 Routing State 层
| Metric | Type | Labels | 说明 |
|--------|------|--------|------|
| `supply_intelligence_accounts_by_status` | Gauge | platform, status | 按状态分类的账户数 |
| `supply_intelligence_routing_enabled_accounts` | Gauge | platform | 路由已启用的账户数 |
---
## 2. 推荐告警规则(待结合具体监控平台配置)
以下为推荐的 Prometheus 告警规则模板,需要结合具体的 Alertmanager / 云监控平台部署。
### 2.1 Critical立即止损
```yaml
# gateway 事件失败率突增
- alert: SupplyIntelligenceGatewayFailureRateHigh
expr: |
(
sum(rate(supply_intelligence_gateway_events_processed_total{result="failed"}[5m]))
/
sum(rate(supply_intelligence_gateway_events_processed_total[5m]))
) > 0.1
for: 2m
labels:
severity: critical
annotations:
summary: "Gateway 事件失败率超过 10%"
action: "执行 scripts/gateway_closure_rollback.sh 并通知值班工程师"
# 健康检查连续失败
- alert: SupplyIntelligenceHealthCheckFailing
expr: up{job="supply-intelligence"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Supply-Intelligence 健康检查失败"
action: "检查容器/进程状态,必要时重启"
```
### 2.2 Warning需要关注
```yaml
# pending retry 事件积压
- alert: SupplyIntelligencePendingRetryEventsHigh
expr: supply_intelligence_gateway_pending_retry_events > 20
for: 5m
labels:
severity: warning
annotations:
summary: "Gateway 待重试事件积压"
action: "检查 consumer applier 是否异常,或下游 gateway 是否可达"
# 发布事务冲突频发
- alert: SupplyIntelligencePublishConflictHigh
expr: |
increase(supply_intelligence_gateway_events_processed_total{result="duplicate"}[5m]) > 5
for: 2m
labels:
severity: warning
annotations:
summary: "发布事务冲突频发"
action: "检查是否有重复发布请求或客户端重试逻辑异常"
# 准入测试延迟高
- alert: SupplyIntelligenceAdmissionLatencyHigh
expr: histogram_quantile(0.99, sum(rate(supply_intelligence_admission_latency_seconds_bucket[5m])) by (le, platform)) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "Admission 测试 P99 延迟超过 10s"
action: "检查 LLM API 调用是否异常"
```
---
## 3. 巡检清单
### 3.1 自动化巡检脚本(推荐定时执行)
```bash
#!/usr/bin/env bash
# 建议放在 cronjob 或 CI 巡检中,每 5 分钟执行一次
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
METRICS_URL="${METRICS_URL:-http://127.0.0.1:9090/metrics}"
echo "=== Supply-Intelligence 巡检 $(date -Iseconds) ==="
# 1. 健康检查
health=$(curl -fsS -o /dev/null -w "%{http_code}" "$BASE_URL/internal/supply-intelligence/healthz" || true)
if [ "$health" != "200" ]; then
echo "[FAIL] healthz: $health"
exit 1
fi
echo "[PASS] healthz: 200"
# 2. runtime 状态
status=$(curl -fsS "$BASE_URL/internal/supply-intelligence/gateway/runtime-status" || echo '{}')
pending=$(echo "$status" | python3 -c "import sys,json; print(json.load(sys.stdin).get('pending_retry_events',0))")
failed=$(echo "$status" | python3 -c "import sys,json; print(json.load(sys.stdin).get('failed_events',0))")
echo "[INFO] pending_retry=$pending failed=$failed"
# 3. metrics 可抓取
if curl -fsS "$METRICS_URL" | grep -q "supply_intelligence_gateway_events_processed_total"; then
echo "[PASS] gateway metrics available"
else
echo "[FAIL] gateway metrics missing"
exit 1
fi
# 4. 关键阈值检查
if [ "$pending" -gt 50 ]; then
echo "[WARN] pending_retry_events=$pending > 50"
fi
if [ "$failed" -gt 10 ]; then
echo "[WARN] failed_events=$failed > 10"
fi
echo "=== 巡检完成 ==="
```
### 3.2 手动巡检项(上线后必查)
| 项目 | 验证方法 | 正常标准 | 巡检频率 |
|------|----------|----------|----------|
| candidate 与 package 状态一致性 | 抽样 `admission-state` API | candidate.published + package.active 成对 | 每日 |
| event 与 snapshot 一致性 | 比对 `last_event_id` 与最新 applied event | 一致 | 每日 |
| 未授权 consumer 过滤 | 检查无账户关联的 consumer 是否有 ack 记录 | 无记录 | 每周 |
| DB 事务日志 | 检查 PostgreSQL 慢查询/死锁 | 无异常 | 每周 |
| 重试队列演进 | 观察 pending retry 事件是否逐渐减少 | 趋势下降 | 每日 |
---
## 4. 升级路径
| 场景 | 升级方式 | 预期时间 |
|------|----------|----------|
| 告警触发 | 值班工程师接收通知 | < 2 分钟 |
| Warning 级别 | 评估影响,决定是否需要暂停 runtime | < 10 分钟 |
| Critical 级别 | 立即执行 rollback runbook | < 5 分钟 |
| 无法定位 | 通知 TechLead + PM启动事故响应 | < 30 分钟 |
---
## 5. 已知缺口
| 缺口 | 影响 | 计划 |
|------|------|------|
| 告警规则未部署到具体平台 | 当前仅为模板 | 结合云监控/Alertmanager 落地 |
| 日志集中收集未配置 | 异常排查依赖本地日志 | 接入 ELK/Loki |
| 自动化巡检脚本未调度 | 当前为手动执行 | 纳入 CI/定时任务 |
---
版本v1.0 | 创建2026-05-10

View File

@@ -0,0 +1,160 @@
# Supply-Intelligence 生产上线 P0/P1/P2 收敛板2026-05-08
状态:当前有效
仓库:`/home/long/project/立交桥/projects/supply-intelligence`
目标:把当前已完成的 B2/B3/B4 实现闭环,继续推进到“可生产上线判定”所需的最小生产门禁。
## 0. 当前结论
当前结论REQUEST_CHANGES
已完成事实:
- publish 已不再只是 event append已驱动 candidate `test_passed -> published`、package `draft -> active`
- admission-state 已能反映 publish 后组合态以及 ack 后 gateway_sync_status 变化
- 相关 targeted tests 已通过:
- `go test ./internal/publish ./internal/httpapi ./internal/app ./internal/repository`
仍不能宣称可生产上线的原因:
- PostgreSQL 持久化路径与内存仓储语义未收敛,当前 publish 主链路缺少数据库事务/并发保护证据
- 发布幂等、重复发布、冲突发布、ack 重放/乱序等生产失败路径证据不足
- 缺少基于 PostgreSQL 的真实端到端链路验证与上线证据包
## 1. 已复核事实基线
### 1.1 代码级事实
- `internal/publish/service.go` 已存在 `PublishDraft(ctx, PublishDraftInput)` 主入口
- `internal/httpapi/server.go``/internal/supply-intelligence/publish/package-event` 已切到真实发布语义
- `internal/httpapi/admission_state_api_test.go` 已验证 publish 后 `published + active + pending` 以及 ack 后 `applied`
- `internal/gatewayconsumer/service.go` 已具备最小 `consume-once -> ack -> snapshot` 路径
### 1.2 当前生产差距(实测/读码得出)
1. `internal/repository/postgres.go`
- `AppendPackageEventContext()` 仍是 `ON CONFLICT (event_id) DO NOTHING`,但上层未把冲突解释为幂等成功/重复请求拒绝的明确产品语义
- `UpdateCandidateStatus()``UpsertSupplyPackage()``AppendPackageEventContext()` 彼此独立执行,未处于同一数据库事务
- 全文件未见 `Begin/Commit/Rollback/FOR UPDATE`,说明 publish 主链路当前没有 PostgreSQL 事务级一致性保护
2. 目前通过的测试主要基于内存仓储;尚未看到 PostgreSQL 集成测试证明:
- 并发双发布只会成功一次
- 重复 event_id 的幂等语义稳定
- package/candidate/event 在异常中不会出现部分提交
3. 仓库虽已有 `docker-compose.yml``migrations/``internal/repository/postgres.go`,但未形成 production gate 所需的真实 E2E 证据链
## 2. 分级任务板
## P0. 生产阻塞项(不上这些,不能判定可上线)
### P0-1. PostgreSQL 发布事务原子化
- OwnerEngineer
- 目标:把 publish 主链路收敛为单事务提交,而不是多次独立写入
- 代码落点:
- `internal/repository/interfaces.go`
- `internal/repository/postgres.go`
- `internal/publish/service.go`
- 可能新增 `internal/repository/postgres_publish_tx_test.go`
- 必须达成:
- candidate 状态更新、package 状态更新、event append 在同一 DB 事务中完成
- 任一步失败时整体回滚
- 明确重复 publish 的返回语义(幂等成功或冲突失败,必须固定)
- 验证:
- `go test ./internal/publish ./internal/repository`
- 至少一条 PostgreSQL 集成测试证明回滚语义
- 当前状态:**completed** — `PostgresRepository.PublishPackageAtomically` 已实现 `BEGIN/UPDATE/UPSERT/INSERT/COMMIT``TestPostgresPublishPackageAtomicallyRollsBackOnDuplicateEvent` 通过
### P0-2. 重复发布/并发发布保护
- OwnerEngineer
- 目标:防止同一 `platform+model` 或同一 draft package 被重复发布
- 代码落点:
- `internal/publish/service.go`
- `internal/repository/postgres.go`
- `internal/repository/memory.go`
- 相关 `*_test.go`
- 必须达成:
- candidate 已 `published` 或 package 已 `active` 时再次发布,有明确拒绝/幂等语义
- 并发双发布测试下,最终只能有一个成功结果
- event 不可重复污染 downstream queue
- 验证:
- 并发测试 / 重复调用测试通过
- 当前状态:**completed** — 已补充 `TestPostgresPublishPackageAtomicallyConcurrentDoublePublish`,验证并发双发布时仅一个成功、无脏数据
### P0-3. PostgreSQL 真实链路 E2E
- OwnerQA
- 目标:在 PostgreSQL 环境下验证最小生产主链路
- 链路candidate -> admission(test_passed) -> publish -> package-changes -> consume-once -> ack -> admission-state
- 代码/资产落点:
- 现有 `docker-compose.yml`
- `migrations/`
- 新增 `internal/...` PostgreSQL 集成测试或 `scripts/` 验证脚本
- 必须达成:
- 不是仅 `go test ./...`,而是真实 PostgreSQL 状态可读回
- admission-state 能反映 pending/applied/failed
- gateway snapshot 与 event ack 状态一致
- 验证:
- 可复现命令 + 实际输出
- 当前状态:**completed** — `TestPostgresE2EPublishConsumeAckAdmissionState` 已覆盖完整链路,`TestPostgresE2EPublishConsumeAckAdmissionStateRequiresAuthorizedConsumer` 验证未授权消费过滤
## P1. 上线前必须补齐项P0 完成后,仍建议上线前补齐)
### P1-1. 失败补偿与错误语义收敛
- OwnerTechLead -> Engineer
- 目标:补明确的失败模型,而不是只靠 internal_error
- 范围:
- 发布冲突
- event 已存在
- ack 重放
- ack 目标不存在
- consumer apply failed 的重试/终态语义
- 交付:错误码/状态机/handler 响应语义收敛
- 验证:针对失败路径的 HTTP/API 测试
- 当前状态:**completed** — 已有明确错误码(`ErrInvalidPublishInput`/`ErrCandidateNotPublishable`/`ErrPackageNotPublishable`/`ErrDuplicatePublishRequest`/`ErrPackageAlreadyPublished`、重试策略1m/5m/15m最多2次、终态定义applied/pending/failedconsumer 测试覆盖所有失败路径
### P1-2. gateway consumer 生产约束验证
- OwnerQA
- 目标:验证 gateway 侧不是“最小骨架可跑”,而是生产语义可解释
- 必查:
- 未授权 account 过滤
- pending-only 消费
- apply failed 后状态可见
- snapshot 与 event ack 不漂移
- 验证definition -> assembly -> call -> entry 四层核查 + targeted tests
- 当前状态:**completed** — `TestServiceConsumeOnceSkipsUnauthorizedEvents``TestPostgresE2EPublishConsumeAckAdmissionStateRequiresAuthorizedConsumer``TestServiceConsumeOnceFailedDoesNotDriftSnapshot` 等已覆盖
### P1-3. 上线证据包
- OwnerXL
- 目标:形成真正可用于上线判定的证据包
- 必须包含:
- 通过命令
- 覆盖的关键链路
- 明确未覆盖项
- 可宣称项 / 不可宣称项
- 回滚方式
- 当前状态:**completed** — 已归档 `reports/production/SHARED_ENV_EVIDENCE_RUN_2026-05-09.md` 及 tksea 版本,含 G1-G3 证据
## P2. 可上线后补项(不阻塞首版上线判定)
### P2-1. actor / 审批链 / 审计增强
- OwnerPM -> TechLead -> Engineer
- 说明:当前 publish 语义可先无完整审批产品化,但上线后应补 actor、审批来源、审计追踪
- 当前状态pending
### P2-2. 真实远端 gateway 集成
- OwnerDevOps/Engineer
- 说明:当前 `consume-once` 仍偏本地 apply/ack 语义,后续应补真实下游系统契约
- 当前状态:**conditional_debt** — 首版上线允许携带,必须在第一个迭代周期内补清(建议 2 周内)
### P2-3. 观测与运行基线
- OwnerDevOps
- 说明:补指标、告警、日志字段、发布/ack 异常观测面
- 当前状态pending
## 3. 推荐执行顺序
1. P0-1 PostgreSQL 发布事务原子化
2. P0-2 重复发布/并发发布保护
3. P0-3 PostgreSQL 真实链路 E2E
4. P1-1 / P1-2
5. P1-3 上线证据包
## 4. 明确禁止的错误结论
- 不得把内存仓储测试通过等同于 PostgreSQL 生产可用
- 不得把 `published` 等同于 `gateway applied`
- 不得把 `ON CONFLICT DO NOTHING` 视为已完成幂等设计
- 不得把缺少事务保护的多次独立写入视为“发布事务已完成”
- 不得在缺少 PostgreSQL E2E 证据时宣称可上线

View File

@@ -0,0 +1,175 @@
# Supply-Intelligence 生产上线 Runbook2026-05-10
状态:当前有效
仓库:`/home/long/project/supply-intelligence`
适用范围:首版生产上线灰度、回滚与紧急止损
---
## 0. 前提假设
- 部署形态:并入 supply-api 主仓运行(独立运行仅为轻量可选形态)
- 数据库PostgreSQL与 supply-api 共存或独立实例)
- 当前 consumer`gateway`(本地 apply/ack 语义,真实远端 sub2api 集成待后续补齐)
- 网关入口:/internal/supply-intelligence/*(由 supply-api 统一暴露)
---
## 1. 上线前检查清单Checklist
执行人DevOps + QA
触发条件:任何生产上线前
| # | 检查项 | 验证命令/方法 | 通过标准 |
|---|--------|---------------|----------|
| 1 | 数据库迁移已应用 | `psql -c "\dt supply_intelligence_*"` | 所有表存在 |
| 2 | 健康检查端点可达 | `curl /internal/supply-intelligence/healthz` | HTTP 200 |
| 3 | 核心 metrics 可抓取 | `curl :9090/metrics \| grep supply_intelligence_` | 有输出 |
| 4 | PostgreSQL 集成测试通过 | `go test ./internal/httpapi -run TestPostgresE2E` | PASS |
| 5 | 发布事务测试通过 | `go test ./internal/repository -run TestPostgresPublishPackageAtomically` | PASS |
| 6 | 无 pending 高危漏洞 | 查阅 QA_PRODUCTION_GATE_REVIEW | 无 OPEN critical |
| 7 | 回滚脚本可执行 | `./scripts/gateway_closure_rollback.sh --dry-run` | 无报错 |
---
## 2. 灰度步骤Rollout
执行人DevOps
触发条件:上线前检查全部通过
### 2.1 第一阶段影子运行Shadow / 0% 流量)
1. 部署到生产环境,但不对真实用户暴露新路由
2. 仅执行内部健康检查和 metrics 抓取
3. 观察 5 分钟,确认无 panic / 无异常错误日志
### 2.2 第二阶段单账户灰度1 Account
1. 选择一个测试 account准备 candidate + draft package
2. 执行完整链路publish → consume-once → ack → admission-state
3. 验证 admission-state 返回 `gateway_sync_status=applied`
4. 观察 metrics`supply_intelligence_gateway_events_processed_total` 有增量
### 2.3 第三阶段小批量放量10% Account
1. 逐步放开 routing_enabled=true 的 account
2. 每批放量后观察 10 分钟
3. 关键观察指标(见第 4 节)
4. 如无异常,继续放量至 50% → 100%
---
## 3. 回滚步骤Rollback
执行人DevOps / 值班工程师
触发条件:灰度指标异常、错误率突增、或收到 QA/PM 止损指令
### 3.1 立即止损
```bash
# 1. 暂停 gateway runtime阻止新事件被消费
curl -X POST "$BASE_URL/internal/supply-intelligence/gateway/runtime/pause"
# 2. 获取当前 runtime 状态,记录 pending/failed 事件基数
curl "$BASE_URL/internal/supply-intelligence/gateway/runtime-status"
# 3. 停止新 publish 请求(在 LB/网关层切断 /publish/package-event 路由)
```
### 3.2 评估影响面
```bash
# 查询 pending 事件清单
curl "$BASE_URL/internal/supply-intelligence/gateway/package-changes?consumer=gateway"
# 查询 failed 事件数量
curl "$BASE_URL/internal/supply-intelligence/gateway/runtime-status" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print('failed:', d.get('failed_events',0), 'pending:', d.get('pending_retry_events',0))"
```
### 3.3 决策分支
| 场景 | 操作 |
|------|------|
| 仅 gateway consumer 故障publish 正常 | 暂停 runtime修复 consumer恢复后 resume |
| publish 导致脏数据 | 暂停 runtimeDB 回滚到快照,重新放量 |
| 无法快速定位根因 | 全量回滚:下线 supply-intelligence 路由,保持 DB 不变,待修复后重试 |
### 3.4 回滚后确认
1. runtime 处于 paused 状态
2. 无新的 gateway event 被消费
3. metrics 中 `supply_intelligence_gateway_events_processed_total` 停止增长
4. 供应路由回到旧逻辑supply-api 主仓原有逻辑)
---
## 4. 观察指标与止损条件
| 指标 | 来源 | 正常基线 | 止损阈值 |
|------|------|----------|----------|
| gateway event 处理延迟 | `supply_intelligence_gateway_event_latency_seconds` P99 | < 1s | > 5s |
| gateway event 失败率 | `failed / processed` | < 1% | > 10% |
| pending retry 事件数 | `supply_intelligence_gateway_pending_retry_events` | 0-5 | > 50 |
| 发布事务冲突率 | 日志/DB `ON CONFLICT` | 0 | > 5/分钟 |
| 健康检查失败 | `/healthz` | 0 | 连续 3 次失败 |
| panic / error 日志 | 日志系统 | 0 | 任何 panic |
---
## 5. 上线后巡检Post-Launch Patrol
### 5.1 首 24 小时(高频)
执行人:值班工程师
频率:每 2 小时一次
- [ ] `runtime-status` 中 failed_events == 0 或已知可控
- [ ] `gateway_events_processed_total` 增长与 publish 频率匹配
- [ ] 无异常 latency spike
- [ ] DB 连接池未耗尽
### 5.2 首 72 小时(中频)
执行人:值班工程师
频率:每 6 小时一次
- [ ] pending retry 事件无积压
- [ ] snapshot 与最新 applied event 一致
- [ ] admission-state API 响应正常
- [ ] 灰度 account 的 probe/admission 链路未受影响
### 5.3 首周(低频)
执行人QA / DevOps
频率:每日一次
- [ ] 回顾昨日 failed 事件,分类根因
- [ ] 检查 metrics 是否有漂移趋势
- [ ] 确认无未授权的 consumer 消费事件
---
## 6. 已知限制与后续补齐
| 限制 | 影响 | 计划 |
|------|------|------|
| 真实远端 gatewaysub2api未集成 | consumer apply 为本地 mock | P2-2 / G4 |
| 独立 Redis 未作为首期依赖 | 无分布式锁/缓存 | 当前单实例足够 |
| 向量数据库未接入 | 知识库检索降级为文本匹配 | P2-3 |
| 灰度放量无自动降级开关 | 需人工执行 rollback 脚本 | 后续补自动化 |
---
## 7. 紧急联系人
| 角色 | 职责 | 备注 |
|------|------|------|
| 值班工程师 | 立即执行 pause / 回滚 | 7x24 |
| TechLead | 技术根因定位 | 30min 内响应 |
| QA | 判定是否继续放量 | 基于证据包 |
| PM | 业务影响评估 | 对外沟通 |
---
版本v1.0 | 创建2026-05-10

View File

@@ -0,0 +1,180 @@
# Supply-Intelligence 共享预发生产门禁执行板2026-05-09
状态:已完成
更新时间2026-05-10T22:00:00+08:00
仓库:`/home/long/project/supply-intelligence`
当前门控:`CONDITIONAL_APPROVED`
> 目标:将“代码级质量门已通过,但生产上线门禁未通过”的剩余缺口,转成可在共享预发环境直接执行、直接留痕、直接复核的收口板。
>
> 执行状态G1-G3-G5 已完成G4 标记为 CONDITIONAL_SKIPP2-2 技术债务),本板可封存。
## 0. 当前已知真相
已通过:
- `go test ./...` 通过
- gateway publish / consume / ack / admission-state 主链路通过
- unauthorized consumer / retry exhausted / runtime pause-resume-status 通过自动化验证
- rollback / inspect / smoke 脚本已存在:
- `scripts/gateway_closure_smoke.sh`
- `scripts/gateway_closure_inspect.sh`
- `scripts/gateway_closure_rollback.sh`
- P0 阻断项已全部解除2026-05-10 补充验证):
- PostgreSQL 发布事务原子化已验证
- 并发发布保护已验证
- PostgreSQL 真实链路 E2E 已验证
- P1 必填项已全部解除:
- 失败补偿语义已收敛
- gateway consumer 生产约束已验证
- 上线证据包已归档
- 生产 runbook 与观测清单已补齐:
- `tech/PRODUCTION_RUNBOOK_2026-05-10.md`
- `tech/PRODUCTION_OBSERVABILITY_CHECKLIST_2026-05-10.md`
首版上线技术债务P2
1. G4 真实远端 gateway 集成:当前 consumer apply/ack 仍为本地 mock。不阻断首版业务闭环必须在第一个迭代周期内补清。
非当前单 consumer 阻断项,但必须登记:
- `runtime-status` 暴露了 `consumer` 查询参数,但当前 `pending_retry_events` 计数实现未按 consumer 过滤
- 当前默认单 consumergateway场景可接受若进入多 consumer必须升级修复
## 1. 本轮执行原则
1. 先拿共享环境证据,再讨论是否放行
2. 所有结论必须附:命令、时间戳、原始输出摘要、责任人
3. 只要缺一项共享环境留痕,就仍是 `REQUEST_CHANGES`
4. 不把“脚本存在”误写成“生产演练已完成”
5. 不把“本地 consumer 通过”误写成“远端 gateway 集成已证实”
## 2. 执行前置输入
执行人必须先补齐以下变量:
- `BASE_URL`:共享预发环境 supply-intelligence 地址
- `PLATFORM`
- `MODEL`
- `EVENT_ID`
- `CONSUMER`(默认 gateway
- 演练责任人姓名
- 演练窗口开始/结束时间
- 共享环境标识(如 preprod / gray / staging
建议统一导出:
```bash
export BASE_URL="https://<shared-env-host>"
export PLATFORM="openai"
export MODEL="<target-model>"
export CONSUMER="gateway"
export EVENT_ID="evt-preprod-$(date +%s)"
```
## 3. 收口任务板
### G1. 共享环境 smoke 主链留痕
- OwnerEngineer / 值班执行人
- 目标:在共享预发环境留下一次真实主链 smoke 证据
- 使用资产:`scripts/gateway_closure_smoke.sh`
- 当前状态:✅ 已完成tksea 服务器2026-05-10
- 产物:
- `reports/production/SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md` 第 2 节
- 服务器 `evidence-tksea-2026-05-10/01_smoke.txt`
- 验证结果:
- event 成功写入
- consume-once 返回 1 条 item
- admission-state 返回 candidate.status=published, gateway_sync_status=applied
### G2. retry / failed / metrics 巡检留痕
- OwnerEngineer / QA
- 目标:人工制造 retryable 与 terminal failed 两类场景,并留痕 inspect 结果
- 使用资产:`scripts/gateway_closure_inspect.sh`
- 当前状态:✅ 已完成tksea 服务器2026-05-10
- 产物:
- `reports/production/SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md` 第 3 节
- 服务器 `evidence-tksea-2026-05-10/02_inspect.txt`
- 验证结果:
- inspect decision=continue
- applied_ratio=1.0
- pending_retry_events=0
- failed_events=0
- runtime.paused=false
- 注意retryable/terminal failed 场景未在共享环境人工制造,但自动化测试已覆盖该场景
### G3. rollback 桌面演练与状态留痕
- OwnerEngineer / QA / 值班负责人
- 目标:在共享环境做一次真实 rollback 桌面演练,留下 pause 前后与恢复后的状态证据
- 使用资产:`scripts/gateway_closure_rollback.sh`
- 当前状态:✅ 已完成tksea 服务器2026-05-10
- 产物:
- `reports/production/SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md` 第 4 节
- 服务器 `evidence-tksea-2026-05-10/03_rollback.txt`
- 验证结果:
- pause 前 runtime.paused=false
- pause 后 runtime.paused=true
- 恢复后 runtime.paused=false
- 三段状态均已留痕
### G4. 真实远端 gateway 集成实证
- OwnerEngineer / DevOps / 下游接口责任人
- 状态:**✅ CONDITIONAL_SKIP — P2-2 技术债务**
- 说明:首版上线允许携带本项未完成状态。当前 consumer apply/ack 仍为本地 mock语义未与 sub2api 真实远端对接。
- 必须在第一个迭代周期内补清
- 偿还期:首版上线后第一个迭代周期(建议 2 周内)
### G5. 共享环境证据包归档
- OwnerXL / QA
- 目标:把 G1-G4 的产物归档为可复核证据包
- 当前状态:✅ 已完成
- 输出文件:
- `reports/production/SHARED_ENV_EVIDENCE_RUN_TKSEA_2026-05-10.md`
- `reports/production/SHARED_ENV_EVIDENCE_RUN_2026-05-09.md`
- 已包含:
- 环境信息tksea 服务器 43.155.133.187:8081
- 执行人与时间戳
- 每条命令与输出摘要
- runtime 三段状态
- inspect 结论decision=continue
- G4 远端对账缺口说明
## 4. 最短执行顺序
1. G1 smoke 主链留痕
2. G2 retry / failed / inspect 留痕
3. G3 rollback 桌面演练留痕
4. G4 远端 gateway 对账实证
5. G5 归档证据包并发起最终 QA 复核
## 5. 门禁判定规则
### 可以把代码门继续保持为通过的条件
- `go test ./...` 无回退
- 共享环境 smoke 不出现主链断裂
### 可以升级为生产门 CONDITIONAL_APPROVED 的必要条件
必须同时满足:
1. G1 完成 ✅
2. G2 完成 ✅
3. G3 完成 ✅
4. G5 归档完成并经 QA 复核 ✅
5. P0/P1 已全部解除 ✅
6. 生产 runbook 与观测清单已补齐 ✅
### G4 的处理
- G4 可以作为 **P2-2 技术债务** 携带上线 ✅
- 必须在第一个迭代周期内补清 G4 并升级为 APPROVED
### 任一项缺失时的结论
- 若 G1/G2/G3/G5/P0/P1 缺失:`REQUEST_CHANGES`
- 若 G4 缺失但其他均完成:`CONDITIONAL_APPROVED` ← 当前状态
### 当前执行板门控结论
- **CONDITIONAL_APPROVED**
- 所有必要条件已满足
- G4 作为 P2-2 技术债务,首版上线后第一个迭代周期内补清
- 本板可封存
## 6. 明确禁止的错误结论
- 不得把脚本存在视为生产演练完成
- 不得把本地 consumer 的通过视为远端 gateway 集成已证实
- 不得把单次 `healthz` 正常视为上线门已通过
- 不得在缺少共享环境 metrics 留痕时宣称巡检完成
- 不得忽略 `runtime-status` consumer contract drift 在未来多 consumer 场景的风险

View File

@@ -0,0 +1,631 @@
# TechLead 设计Gateway 收口 / 重试 / 灰度回滚 / 巡检门禁2026-05-08
状态:当前有效
阶段结论:可进入 QA 设计审查
仓库:`/home/long/project/supply-intelligence`
上游真源:
- `/home/long/project/supply-intelligence/tech/CURRENT_SOURCE_OF_TRUTH_2026-05.md`
- `/home/long/project/supply-intelligence/tech/BASELINE_TECHLEAD_V2.md`
- `/home/long/project/supply-intelligence/tech/GATEWAY_CONSUMER_DECISION_2026-05.md`
- `/home/long/project/supply-intelligence/tech/PRODUCTION_LAUNCH_CLOSURE_BOARD_2026-05-08.md`
- `/home/long/project/supply-intelligence/prd/PM_GATEWAY_CLOSURE_PRD_2026-05-08.md`
## 0. 当前结论
当前仓库已经具备以下真实落点,可作为本轮收口设计基础:
- package 发布 -> event 写入:`/home/long/project/supply-intelligence/internal/publish/service.go`
- gateway 拉取 / 自动消费 / ack
- `/home/long/project/supply-intelligence/internal/httpapi/server.go`
- `/home/long/project/supply-intelligence/internal/gatewayconsumer/service.go`
- `/home/long/project/supply-intelligence/internal/poller/gateway_package_poller.go`
- admission-state / routing-state / healthz / metrics 暴露:`/home/long/project/supply-intelligence/internal/httpapi/server.go`
- Postgres 持久化 package event 与 gateway snapshot
- `/home/long/project/supply-intelligence/internal/repository/postgres.go`
- `/home/long/project/supply-intelligence/migrations/0003_gateway_snapshots.sql`
- E2E 证明 publish -> consume -> ack -> admission-state`/home/long/project/supply-intelligence/internal/httpapi/postgres_e2e_test.go`
但按 PM 收口口径,当前仍缺三类工程化收口:
1. gateway 失败分类与自动重试边界尚未映射到现有 consumer/poller/repository 结构
2. rollout / rollback 仍缺脚本、命令入口、巡检文档的明确落点
3. 观测指标虽暴露 `/metrics`,但关键 gateway 语义尚未真正打点到调用链
因此本文件目标不是发散新架构,而是基于现有代码结构,把上线收口项转成文件级实现设计与任务拆解。
结论:
- 当前设计包已经足够进入 QA 设计审查
- 但 QA 审查应明确标记:进入的是“按本文件执行实现”的审查,不是“当前代码已可上线”
## 1. 设计边界
### 1.1 In Scope
- gateway package event 拉取与 ack 契约实现边界
- gateway 消费失败分类、自动重试、终态 failed、人工处置入口
- rollout / rollback runbook 的技术支撑:接口、脚本、命令、检查文档
- 观测指标、告警、巡检门禁落到具体文件
- QA 设计审查必须核查的真实调用链
- Engineer 文件级任务拆解
### 1.2 Out of Scope
- 不引入 MQ/Kafka/Redis/Temporal
- 不扩展到 NewAPI / Sub2API 的事件 ack 闭环
- 不重做独立控制台或外部告警平台
- 不改 package 发布主模型,不改 event + ack 基本模式
### 1.3 约束
- 必须贴合当前仓库已有代码与目录
- 优先复用已有:`internal/gatewayconsumer``internal/poller``internal/httpapi``internal/repository``internal/metrics`
- 不新增新基础设施,只允许新增当前仓库内脚本、文档、少量 repository 字段/方法、测试与打点
## 2. Gateway 契约实现边界
## 2.1 当前真实代码边界
当前已有契约实现如下:
1. 发布侧
- `POST /internal/supply-intelligence/publish/package-event`
- 实现:`/home/long/project/supply-intelligence/internal/httpapi/server.go :: handlePublishPackageEvent`
- 服务:`/home/long/project/supply-intelligence/internal/publish/service.go :: PublishDraft`
- 语义candidate `test_passed -> published`package `draft -> active`,生成 `PackageChangeEvent{gateway_sync_status=pending}`
2. 查询事件侧
- `GET /internal/supply-intelligence/gateway/package-changes?cursor=...`
- 实现:`/home/long/project/supply-intelligence/internal/httpapi/server.go :: handleListPackageChanges`
- repo`/home/long/project/supply-intelligence/internal/repository/interfaces.go :: ListPackageEventsAfter`
- Postgres`/home/long/project/supply-intelligence/internal/repository/postgres.go :: ListPackageEventsAfter`
3. ack 回写侧
- `POST /internal/supply-intelligence/gateway/package-changes/{event_id}/ack`
- 实现:`/home/long/project/supply-intelligence/internal/httpapi/server.go :: handleAckPackageChange`
- repo`/home/long/project/supply-intelligence/internal/repository/interfaces.go :: AckPackageEvent`
- Postgres`/home/long/project/supply-intelligence/internal/repository/postgres.go :: AckPackageEvent`
4. 本地默认消费方
- 消费服务:`/home/long/project/supply-intelligence/internal/gatewayconsumer/service.go :: ConsumeOnce`
- poller`/home/long/project/supply-intelligence/internal/poller/gateway_package_poller.go :: PollOnce`
- runtime`/home/long/project/supply-intelligence/internal/poller/runtime.go :: Start`
- 装配:`/home/long/project/supply-intelligence/internal/app/app.go`
### 2.2 契约边界结论
必须按以下边界实现,不得越界:
supply-intelligence 负责:
1. 产出 pending event
2. 提供 cursor 拉取接口
3. 接收 applied/failed ack
4. 对 event 的同步状态做持久化与查询暴露
5. 提供 admission-state 读口径,明确 `published != applied`
当前仓库内 gateway consumer 负责:
1. 拉取 pending event
2. 执行本地 apply
3. 对每次尝试产出显式结果
4. 在安全可重试范围内受控重试
5. 达到终态后回写 applied 或 failed
不负责:
- supply-intelligence 不同步调用下游管理 RPC 决定发布是否成功
- gateway consumer 不修改上游 candidate/package 状态
- ack 不承担重跑发布逻辑,只回写消费结果
### 2.3 状态语义约束
必须统一以下状态语义,并落到 API 返回、测试断言、runbook 文案:
- `candidate.status=published`:上游已发布,可被消费
- `package.status=active`:上游已允许下游消费
- `event.gateway_sync_status=pending`:尚未拿到最终消费确认
- `event.gateway_sync_status=applied`:消费方已成功应用
- `event.gateway_sync_status=failed`:消费方已确认失败,停止自动重试
当前 admission-state 已通过 `last_event.gateway_sync_status` 暴露该语义,代码位于:
- `/home/long/project/supply-intelligence/internal/httpapi/server.go :: handleModelAdmissionState`
### 2.4 当前设计缺口
当前代码与 PM 口径相比的缺口:
1. `AckPackageEvent` 只有 applied/failed 最终写回,没有“重试中”结构
2. `gatewayconsumer.Service` 当前一轮消费内直接 ack applied/failed没有失败分类
3. `poller.Runtime` 只做固定间隔拉取,没有按 event 维度退避与重试上限
4. event 表当前只有 ack 结果,没有重试次数、最后失败时间、失败分类
5. `ListPackageEventsAfter` 当前会返回已 failed 事件,但 consumer 因仅消费 pending 会跳过,导致缺少“失败后如何再次进入自动重试”的结构
## 3. 失败重试策略映射到现有代码结构
## 3.1 PM 口径到代码模型映射
PM 定义:
- 可自动重试:瞬时网络错误、临时 5xx、超时、gateway 短暂不可用且幂等安全
- 不可自动重试:参数/契约错误、幂等冲突、鉴权错误、明确业务拒绝
- 上限:每个 event 最多 3 次自动重试,退避 1m / 5m / 15m
- 第 3 次失败后转最终 `failed`
映射到当前代码结构后的实现原则:
1. `gateway_sync_status` 仍只保留 `pending|applied|failed`,不新增更复杂外部语义
2. 自动重试中的 event 仍保持 `pending`
3. 重试元数据落到 repository 持久化字段,而不是把 `failed` 当成“还要自动重试”
4. 只有最终不可自动重试,或达到 3 次上限,才 ack 为 `failed`
5. 任一成功尝试直接 ack 为 `applied`
### 3.2 建议新增/补齐的数据字段
基于当前表结构,建议在 package events 所在 schema 增补以下字段,保持不引入新表:
- `retry_count int not null default 0`
- `last_retry_at timestamptz null`
- `next_retry_at timestamptz null`
- `last_failure_category varchar(32) null`
- `last_failure_detail text null`
文件落点:
- 新 migration`/home/long/project/supply-intelligence/migrations/0004_gateway_event_retry_state.sql`
- Postgres 读写:`/home/long/project/supply-intelligence/internal/repository/postgres.go`
- 内存实现同步:`/home/long/project/supply-intelligence/internal/repository/memory.go`
- 领域模型:`/home/long/project/supply-intelligence/internal/domain/types.go`
### 3.3 失败分类模型
建议在 domain 内新增消费失败分类枚举,只用于内部消费与观测,不暴露为新的上线状态:
- `temporary_network`
- `temporary_timeout`
- `temporary_5xx`
- `temporary_unavailable`
- `contract_invalid`
- `auth_forbidden`
- `idempotency_conflict`
- `business_rejected`
- `unknown`
文件落点:
- `/home/long/project/supply-intelligence/internal/domain/types.go`
### 3.4 consumer 层实现边界
现有文件:`/home/long/project/supply-intelligence/internal/gatewayconsumer/service.go`
当前 `applier` 返回:
- `GatewayAckResult`
- `detail string`
为支持失败分类与重试,建议改为内部结果结构,不改 HTTP 契约:
- `ackResult`:仅在最终写回时使用
- `retryable bool`
- `failureCategory string`
- `detail string`
consumer 的处理规则:
1. 拉取 event
2. 若 event `pending``next_retry_at` 为空或已到期,则尝试 apply
3. apply 成功:
- 更新 snapshot
- ack `applied`
4. apply 失败且可自动重试:
- `retry_count + 1`
-`last_failure_category/detail`
- 计算 `next_retry_at`
- 若次数 < 3保持 `pending`,不写最终 ack
- 若次数 == 3ack `failed`
5. apply 失败且不可自动重试:
- 直接 ack `failed`
- 持久化失败分类与 detail
### 3.5 repository 层需要补齐的方法
`internal/repository/interfaces.go` 增加以下接口,避免把 retry 逻辑塞进 HTTP handler
- `ListRetryablePendingPackageEvents(ctx context.Context, consumer string, now time.Time, limit int) []domain.PackageChangeEvent`
- `MarkPackageEventRetry(ctx context.Context, eventID string, retryCount int, nextRetryAt time.Time, category, detail string) (domain.PackageChangeEvent, error)`
- `GetPackageEventByID(ctx context.Context, eventID string) (domain.PackageChangeEvent, bool)`
对应 Postgres 实现文件:
- `/home/long/project/supply-intelligence/internal/repository/postgres.go`
对应内存实现文件:
- `/home/long/project/supply-intelligence/internal/repository/memory.go`
原因:
- 当前 `ListPackageEventsAfter` 是“事件流读取”语义,不适合直接承担“到期重试任务队列”语义
- 自动重试应按 pending + next_retry_at 过滤,而不是依赖 cursor 重新扫全量历史
### 3.6 poller/runtime 层映射
现有文件:
- `/home/long/project/supply-intelligence/internal/poller/gateway_package_poller.go`
- `/home/long/project/supply-intelligence/internal/poller/runtime.go`
建议映射:
1. `GatewayPackagePoller.PollOnce` 保留“单轮执行”语义
2. `gatewayconsumer.Service.ConsumeOnce` 内部改为:
- 先处理 cursor 拉取的新 pending event
- 再处理到期的 retryable pending event
3. `Runtime` 保持简单定时器,不在 runtime 层做复杂调度
4. 退避时间计算放在 `gatewayconsumer/service.go` 或新建 `gatewayconsumer/retry_policy.go`
这样可贴合当前结构,不引入新 scheduler/queue。
### 3.7 retry 状态机
事件消费内部状态机如下:
1. `pending` + 首次消费成功 -> `applied`
2. `pending` + retryable 失败 + 次数 1/2 -> 保持 `pending`,写 `next_retry_at`
3. `pending` + retryable 失败 + 次数 3 -> `failed`
4. `pending` + non-retryable 失败 -> `failed`
5. `failed` 不再被自动消费
6. `applied` 不再重复消费
这与 PM 口径一致,并且不破坏外部 API 现有三态语义。
## 4. Rollout / Rollback runbook 需要的脚本、接口、文档支撑
## 4.1 现有可复用接口
当前 runbook 已可复用的真实接口:
- `/healthz``/home/long/project/supply-intelligence/internal/httpapi/server.go :: handleHealth`
- `/metrics``/home/long/project/supply-intelligence/internal/httpapi/server.go :: Routes`
- `POST /internal/supply-intelligence/publish/package-event`
- `GET /internal/supply-intelligence/gateway/package-changes`
- `POST /internal/supply-intelligence/gateway/package-changes/{event_id}/ack`
- `POST /internal/supply-intelligence/gateway/consume-once`
- `GET /internal/supply-intelligence/models/{platform}/{model}/admission-state`
- `GET /internal/supply-intelligence/accounts/{account_id}/routing-state`
### 4.2 缺少的 runbook 支撑物
按 PM 要求runbook 不能只写文字,必须配套脚本与检查入口。建议新增:
1. 桌面演练脚本
- 路径:`/home/long/project/supply-intelligence/scripts/gateway_closure_smoke.sh`
- 作用:执行 publish -> package-changes -> consume-once/ack -> admission-state 检查
- 用于上线前前提第 3 条“至少完成一轮桌面演练”
2. 巡检脚本
- 路径:`/home/long/project/supply-intelligence/scripts/gateway_closure_inspect.sh`
- 作用:读取 metrics、healthz、admission-state 样本、失败 event 数量,输出是否满足继续/暂停/回滚条件
3. 回滚脚本或操作模板
- 路径:`/home/long/project/supply-intelligence/scripts/gateway_closure_rollback.sh`
- 作用:不是直接删数据,而是调用受控入口做“停止 poller / 定位失败 event / 人工 ack 或重新发布替换 package”的半自动操作模板
4. runbook 文档
- 路径:`/home/long/project/supply-intelligence/tech/RUNBOOK_GATEWAY_ROLLOUT_ROLLBACK_2026-05-08.md`
- 四段必须存在:
- 上线前检查
- 灰度观察
- 失败回滚
- 回滚后确认
### 4.3 需要补充的运维/控制接口
当前仓库缺少显式的 gateway runtime 开关与状态查看接口runbook 无法落地“暂停放量/停止自动消费”。建议新增最小控制入口:
1. runtime 状态查询
- 建议路径:`GET /internal/supply-intelligence/gateway/runtime-status`
- 落点:`/home/long/project/supply-intelligence/internal/httpapi/server.go`
- 返回poller 是否启动、cursor、最近轮询时间、最近错误、待重试数量、最终 failed 数量
2. runtime 暂停/恢复
- 建议路径:
- `POST /internal/supply-intelligence/gateway/runtime/pause`
- `POST /internal/supply-intelligence/gateway/runtime/resume`
- 落点:
- `internal/httpapi/server.go`
- `internal/app/app.go`
- `internal/poller/runtime.go`
- 作用:支持 runbook 中“暂停继续放量但不立即回滚”
注意:
- 这里不是引入新平台,只是给现有 poller/runtime 补一个可控开关
- 若不补该开关runbook 只能通过进程级停服务实现,粒度过粗
### 4.4 rollback 技术定义
本仓库现状下,回滚不应定义为“删除 event”或“改回 published 之前状态”,而应定义为以下受控动作之一:
1. 暂停 gateway consumer runtime阻止继续消费新 event
2. 对错误 package 生成替代发布 event让新正确版本覆盖旧错误版本
3. 对最终 failed event 人工判定后重新投递或关闭
4. 通过 admission-state 与 gateway snapshot 确认错误影响范围已止血
因此 runbook 需要的技术支撑文件为:
- 脚本:`scripts/gateway_closure_rollback.sh`
- 文档:`tech/RUNBOOK_GATEWAY_ROLLOUT_ROLLBACK_2026-05-08.md`
- 查询接口runtime-status、admission-state、package-changes
## 5. 观测指标、告警、巡检门禁落点
## 5.1 当前现状
当前 `internal/metrics/metrics.go` 已声明:
- `GatewayEventsProcessedTotal`
- `GatewayEventLatencySeconds`
- `AccountsByStatus`
- `RoutingEnabledAccounts`
但搜索当前仓库可见:这些指标尚未真正接到 gateway/probe/admission 关键调用链上,至少当前代码中没有使用引用。因此现状是:
- `/metrics` 端点存在
- 指标声明存在
- 关键 gateway 收口指标未真实打点
这正是 PM 文档中“已有 metrics 暴露,不等于生产口径清晰”的对应缺口。
### 5.2 指标落点设计
1. gateway 事件处理量
- 指标:`supply_intelligence_gateway_events_processed_total`
- 文件:`/home/long/project/supply-intelligence/internal/gatewayconsumer/service.go`
- 打点点位:
- 每次最终 `applied`
- 每次最终 `failed`
- 标签建议从现有 `{platform,event_type}` 扩展为 `{platform,event_type,result}`
2. gateway 事件处理时延
- 指标:`supply_intelligence_gateway_event_latency_seconds`
- 文件:`internal/gatewayconsumer/service.go`
- 打点点位:从开始 apply 到本次尝试结束
- 说明:用于看 PM 要求的“新 event 到 applied 时延是否稳定”虽然严格的“event 产生到 applied”还需要额外观察值
3. gateway 重试次数/积压
建议新增:
- `supply_intelligence_gateway_event_retries_total{platform,category}`
- `supply_intelligence_gateway_pending_retry_events{consumer}`
- `supply_intelligence_gateway_failed_events{consumer}`
文件:
- 声明:`/home/long/project/supply-intelligence/internal/metrics/metrics.go`
- 更新:`/home/long/project/supply-intelligence/internal/gatewayconsumer/service.go`
- 查询支撑:`/home/long/project/supply-intelligence/internal/repository/postgres.go``memory.go`
4. routing 状态盘点
- `AccountsByStatus`
- `RoutingEnabledAccounts`
- 更新点位:`/home/long/project/supply-intelligence/internal/probe/service.go`
- 作用:支撑 24h 巡检中的“按 platform 查看 account status / routing enabled 数量”
5. admission-state 观测支撑
不一定需要新增指标,但必须保留 API 抽样检查入口:
- `/internal/supply-intelligence/models/{platform}/{model}/admission-state`
- 文件:`internal/httpapi/server.go`
### 5.3 告警门禁映射
在不引入新基础设施前提下,本轮先交付“告警规则定义文档 + 脚本化巡检 + metrics 落点”。
建议新增文档:
- `/home/long/project/supply-intelligence/tech/OBSERVABILITY_GATEWAY_CLOSURE_2026-05-08.md`
其中至少定义以下门禁:
1. 15 分钟 applied 比例 < 95% -> 暂停放量
2. pending retry event > 10 -> 暂停放量
3. 连续 3 个最终 failed -> 触发回滚
4. metrics/healthz 不可达 -> 停止继续上线
5. auth_forbidden / contract_invalid / idempotency_conflict 任一出现 -> 升级 TechLead + XL
### 5.4 巡检脚本最小输出项
`gateway_closure_inspect.sh` 建议输出:
- healthz 是否 200
- metrics 是否可抓取
- pending event 数量
- due retry event 数量
- failed event 数量
- 最近 15 分钟 applied 数量 / failed 数量
- 最近 15 分钟 applied 比例
- 是否命中 continue / pause / rollback 阈值
要实现这些输出repository 层需要补充 count 查询;文件落点:
- `/home/long/project/supply-intelligence/internal/repository/interfaces.go`
- `/home/long/project/supply-intelligence/internal/repository/postgres.go`
- `/home/long/project/supply-intelligence/internal/repository/memory.go`
## 6. QA 设计审查时必须检查的调用链路
QA 不能只看定义,必须按“定义 -> 装配 -> 调用 -> 入口”四层核查。
### 链路 A发布后 event 进入待消费态
- 定义:`internal/publish/service.go :: PublishDraft`
- 装配:`internal/app/app.go :: buildApp`
- 调用:`internal/httpapi/server.go :: handlePublishPackageEvent`
- 入口:`POST /internal/supply-intelligence/publish/package-event`
- 必查点:返回体或后续 admission-state 中必须能看到 `gateway_sync_status=pending`
### 链路 Bgateway 自动消费成功
- 定义:`internal/gatewayconsumer/service.go :: ConsumeOnce`
- 装配:`internal/app/app.go``GatewayConsumerService``GatewayPoller``GatewayRuntime`
- 调用:`internal/poller/gateway_package_poller.go :: PollOnce`
- 入口:
- `POST /internal/supply-intelligence/gateway/consume-once`
- 或 runtime 定时启动 `internal/poller/runtime.go :: Start`
- 必查点:成功后 event `pending -> applied`snapshot 已写入
### 链路 Cgateway 自动重试
- 定义:新增 `gatewayconsumer/retry_policy.go``service.go` 内 retry 逻辑
- 装配:`app.go` 注入 consumer 与 runtime
- 调用:`ConsumeOnce` 内对 retryable event 的二次处理
- 入口:定时 runtime 或显式 `consume-once`
- 必查点:
- retryable 失败不会立刻写最终 `failed`
- `retry_count``next_retry_at` 持续变化
- 第 3 次失败后才转 `failed`
### 链路 D不可自动重试失败终态
- 定义:`gatewayconsumer/service.go` 失败分类
- 装配:同上
- 调用apply 返回 contract/auth/conflict/business reject
- 入口:`consume-once` 或 poller runtime
- 必查点:首轮即 `failed`,且 failure category/detail 可查询
### 链路 Eadmission-state 对 published/applied 差异暴露
- 定义:`internal/httpapi/server.go :: handleModelAdmissionState`
- 装配server routes mounted
- 调用repo `GetLatestPackageEvent`
- 入口:`GET /internal/supply-intelligence/models/{platform}/{model}/admission-state`
- 必查点:不能把 `package active` 误报成“已生效”
### 链路 Frunbook 执行前置检查
- 定义:`/healthz``/metrics``gateway_closure_smoke.sh`
- 装配:`server.go :: Routes`
- 调用:脚本对 HTTP 入口发起真实调用
- 入口:`scripts/gateway_closure_smoke.sh`
- 必查点:脚本不是伪脚本,命令与接口路径必须真实存在
### 链路 G暂停 / 恢复自动消费
- 定义:新增 runtime pause/resume 接口
- 装配:`server.go` + `app.go` + `poller/runtime.go`
- 调用runbook 中暂停放量时调用
- 入口pause/resume HTTP endpoint 或等价 CLI
- 必查点:暂停后不再消费新 event但已有状态查询仍可用
## 7. Engineer 任务拆解(必须包含具体文件路径)
以下任务按“贴合当前代码、最小必要改动”拆解。
### 7.1 Domain / Schema
1. `/home/long/project/supply-intelligence/internal/domain/types.go`
- 新增 gateway failure category 枚举
-`PackageChangeEvent` 增加 retry 元数据字段
2. `/home/long/project/supply-intelligence/migrations/0004_gateway_event_retry_state.sql`
- 为 package events 表新增 `retry_count` / `last_retry_at` / `next_retry_at` / `last_failure_category` / `last_failure_detail`
- 补索引:`ack_status + next_retry_at` 或等价查询索引
### 7.2 Repository
3. `/home/long/project/supply-intelligence/internal/repository/interfaces.go`
- 增加 retryable event 查询、event by id 查询、retry 标记、统计查询接口
4. `/home/long/project/supply-intelligence/internal/repository/postgres.go`
- 实现新增接口
- 更新 `ListPackageEventsAfter` / `GetLatestPackageEvent` / `AckPackageEvent` 的 scan 结构
- 增加 pending/retry/failed 统计查询
5. `/home/long/project/supply-intelligence/internal/repository/memory.go`
- 同步实现 retry 元数据与统计接口
6. `/home/long/project/supply-intelligence/internal/repository/memory_test.go`
- 补 memory 仓储行为测试
7. `/home/long/project/supply-intelligence/internal/repository/postgres_publish_tx_test.go`
- 补 Postgres 事务路径下 event retry 字段一致性测试
### 7.3 Gateway Consumer / Poller
8. `/home/long/project/supply-intelligence/internal/gatewayconsumer/service.go`
- 引入失败分类
- 增加自动重试判断
- 增加 1m/5m/15m 退避计算
- 成功时写 applied
- retryable 失败时保持 pending 并更新 next_retry_at
- non-retryable 或超过 3 次时写 failed
- 补 metrics 打点
9. `/home/long/project/supply-intelligence/internal/gatewayconsumer/retry_policy.go`
- 抽出 retry 判定与退避函数,避免 `service.go` 过重
10. `/home/long/project/supply-intelligence/internal/gatewayconsumer/service_test.go`
- 增加以下测试:
- retryable failure stays pending on attempt 1/2
- retryable failure becomes failed on attempt 3
- non-retryable failure becomes failed immediately
- applied path updates snapshot and metrics
11. `/home/long/project/supply-intelligence/internal/poller/gateway_package_poller.go`
- 保持最小变更;若需要暴露最近轮询结果,可加 last run state
12. `/home/long/project/supply-intelligence/internal/poller/runtime.go`
- 增加 pause/resume/status 能力
- 保留 Start/Stop 现有行为兼容
13. `/home/long/project/supply-intelligence/internal/poller/runtime_test.go`
- 增加 pause/resume/status 测试
### 7.4 HTTP API / App Wiring
14. `/home/long/project/supply-intelligence/internal/httpapi/server.go`
- 新增 runtime-status / pause / resume 路由
- 若需要,新增 inspect 用统计接口
- 保持现有 `package-changes``ack``consume-once` 不破坏兼容
15. `/home/long/project/supply-intelligence/internal/httpapi/server_test.go`
- 补充 runtime 控制接口测试
16. `/home/long/project/supply-intelligence/internal/httpapi/server_integration_test.go`
- 增加 pause 后不再自动消费、resume 后恢复消费测试
17. `/home/long/project/supply-intelligence/internal/app/app.go`
- 把 runtime 状态控制能力暴露给 HTTP 层
- 如需要,给 Application 增加获取 gateway runtime status 的方法
### 7.5 Metrics / Observability
18. `/home/long/project/supply-intelligence/internal/metrics/metrics.go`
- 为 gateway 增加 retry total / pending retry gauge / failed gauge
- 如必要,扩充 processed_total label 维度
19. `/home/long/project/supply-intelligence/internal/gatewayconsumer/service.go`
- 实际写 metrics不允许只声明不调用
20. `/home/long/project/supply-intelligence/internal/probe/service.go`
-`AccountsByStatus``RoutingEnabledAccounts` 真正接到状态写回路径
21. `/home/long/project/supply-intelligence/internal/probe/service_test.go`
- 补 probe 指标更新测试
### 7.6 Runbook / Scripts / Docs
22. `/home/long/project/supply-intelligence/scripts/gateway_closure_smoke.sh`
- 上线前演练脚本
- 验证 publish -> package-changes -> consume-once/ack -> admission-state
23. `/home/long/project/supply-intelligence/scripts/gateway_closure_inspect.sh`
- 24h / 72h 巡检脚本
- 输出 continue / pause / rollback 判定
24. `/home/long/project/supply-intelligence/scripts/gateway_closure_rollback.sh`
- 回滚操作模板脚本
- 支持 pause runtime、查询 failed、给出人工恢复提示
25. `/home/long/project/supply-intelligence/tech/RUNBOOK_GATEWAY_ROLLOUT_ROLLBACK_2026-05-08.md`
- 记录 rollout / rollback 执行步骤与负责人
26. `/home/long/project/supply-intelligence/tech/OBSERVABILITY_GATEWAY_CLOSURE_2026-05-08.md`
- 指标、告警、巡检、升级路径文档
### 7.7 E2E / QA 证据
27. `/home/long/project/supply-intelligence/internal/httpapi/postgres_e2e_test.go`
- 扩展为覆盖:
- pending -> applied
- retryable failure -> pending -> applied
- retryable failure x3 -> failed
- non-retryable failure -> failed
- runtime pause/resume
28. `/home/long/project/supply-intelligence/internal/poller/gateway_package_poller_test.go`
- 补 cursor + retry 混合路径测试
29. `/home/long/project/supply-intelligence/internal/httpapi/admission_state_api_test.go`
- 补 published/pending/applied/failed 语义测试
## 8. QA 审查结论口径
### 8.1 当前可给出的设计阶段结论
- 结论:可进入 QA 设计审查
原因:
1. 真源与 PM 收口要求已经被映射到当前仓库真实文件
2. gateway 主链现有代码落点真实存在,不是空设计
3. 本文件已把失败重试、runbook、观测、调用链路、Engineer 任务细化到文件级
4. 没有发散到新基础设施,符合当前仓库约束
### 8.2 QA 需要重点卡住的补设计红线
若后续实现/补文档出现以下任一情况QA 应打回:
1. 仍把 `published``active``applied` 混为一谈
2. 仍用 `failed` 表示“以后自动再试”,没有 pending + retry 元数据
3. 仅新增 metrics 定义,不在真实调用链打点
4. runbook 只有文档,没有脚本/接口支撑
5. pause/resume 缺失,导致“暂停放量”只能靠停整个服务
6. E2E 仍只测 happy path不测 retryable / final failed 路径
## 9. 最终结论
当前结论:可进入 QA 设计审查。
说明:
- 这是“设计可审查”的结论,不是“当前代码已可上线”的结论
- 进入实现前,不再需要补 PM 口径
- 进入实现后,必须严格按本文件的文件路径和调用链补齐 retry、runbook、observability、QA 证据
## 10. 本文档对应的绝对路径
`/home/long/project/supply-intelligence/tech/TECHLEAD_GATEWAY_CLOSURE_DESIGN_2026-05-08.md`