Files
sub2api-cn-relay-manager/docs/plans/2026-05-28-phase1-logical-routing-foundation-plan.md
2026-05-28 15:37:13 +08:00

23 KiB
Raw Blame History

Phase 1 实施计划:逻辑分组与路由基础设施

日期2026-05-28

目标

把插件新增方向收敛成第一阶段可开工任务,只做“基础设施闭环”,范围限定为:

  1. SQLite migration 设计
  2. logical_group / route repo 与 API
  3. 路由日志 repo 与写入器
  4. Redis sticky 接口封装

本阶段不做

  • 完整前置路由数据面转发
  • 普通用户 Portal 改造
  • 供应商帐号库存页
  • route health dashboard

但本阶段完成后,必须满足:

  • 插件数据库里已经能表达 logical_group -> route -> shadow_group
  • 插件数据库里已经能接收结构化路由日志
  • 插件代码里已经有独立的 sticky store 抽象
  • 每个闭环功能完成后,都必须部署到 remote43 验证,不只停留在本地测试

总体约束

技术约束

  • 主状态库继续使用 SQLite
  • 路由运行态缓存使用 Redis
  • 智能路由日志必须最终落入插件 SQLite
  • 不修改宿主源码
  • 不直写宿主数据库
  • 仅通过宿主 HTTP API 与宿主交互

质量门禁

每个任务完成后必须通过:

gofmt -l .
go vet ./...
go test -cover ./internal/...
go test ./tests/integration/... -count=1

如有脚本变更,还必须通过:

bash ./scripts/test/test_real_host_scripts.sh
bash ./scripts/test/test_tksea_portal_assets.sh

远端验证门禁

每个“闭环功能”完成后,不允许只在本地宣布完成,必须执行:

  1. 提交代码
  2. 推送远端仓库
  3. 上传到 remote43
  4. 重启/部署对应服务
  5. remote43 或公网入口完成验证
  6. 生成或补充验证证据
  7. 更新 docs/EXECUTION_BOARD.md

这里的验证对象默认是:

  • CRMremote43 上的控制面
  • 公网admin 入口 https://sub.tksea.top/portal/admin/
  • 若涉及真实导入或路由闭环,则补 artifacts/real-host-acceptance/...

0. 当前环境基线

数据库

  • 当前插件数据库SQLite
  • 配置项:SUB2API_CRM_SQLITE_DSN
  • 默认值:file:/data/sub2api-cn-relay-manager.db?_foreign_keys=on&_busy_timeout=5000

服务器基线

0.1 当前代码缺口

Phase 1 不是从零开始,但也不是在已有实现上只补一两个字段。当前代码基线里,和本阶段直接相关的缺口有:

  • internal/store/migrations/ 里还没有 logical_group / route / route logging 相关表
  • internal/store/sqlite/db.go 里还没有这些 repo 的挂载入口
  • internal/app/http_api.go 里还没有 logical_group / route 的管理 API
  • internal/config/config.go 里还没有 Redis 运行态配置
  • internal/routing/ 目录当前还不存在,StickyStore 与路由日志写入器都还未成形
  • 当前 remote43 验证脚本主要覆盖 provider import / portal不覆盖 logical routing foundation

因此本阶段不是“补 UI”而是先建立

  1. 可迁移的 SQLite 结构
  2. 可管理的控制面 API
  3. 可审计的路由日志基础设施
  4. 可替换的 Redis sticky 适配层

1. 实施顺序

P1-T1 SQLite schema foundation
P1-T2 logical_group / route repo + admin API
P1-T3 route logging repo + async writer
P1-T4 Redis sticky store abstraction

说明:

  • P1-T1 先建结构
  • P1-T2 让结构可读写
  • P1-T3 让智能路由日志可落库
  • P1-T4 先把 sticky 抽象封装好,为 Phase 2 路由器实现铺路

1.1 任务依赖矩阵

任务 依赖 可并行度 完成后解锁
P1-T1 不可跳过 P1-T2P1-T3
P1-T2 P1-T1 可与 P1-T3 部分交叉,但建议先后执行 Phase 2 管理页接线
P1-T3 P1-T1 可与 P1-T2 交叉 Phase 2 路由器写日志
P1-T4 无强依赖,但建议在 P1-T2 之后落配置 可独立推进 Phase 2 RouteResolver

工程上建议仍然按 T1 -> T2 -> T3 -> T4 串行推进,原因是每个任务结束后都要提交、推送、部署 remote43、做服务器验证串行更容易隔离回归和定位问题。


2. P1-T1 SQLite Schema Foundation

目标闭环

让插件 SQLite 正式支持以下核心对象:

  • logical_groups
  • logical_group_models
  • logical_group_routes
  • logical_group_route_models

完成后,插件数据库应能完整表达:

logical_group
  -> public models
  -> routes
  -> each route -> shadow group

需要新增的 migration

建议新增:

  • internal/store/migrations/0010_logical_groups_and_routes.sql

建议建表:

logical_groups

CREATE TABLE logical_groups (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    logical_group_id TEXT NOT NULL UNIQUE,
    display_name TEXT NOT NULL,
    status TEXT NOT NULL,
    description TEXT NOT NULL DEFAULT '',
    route_policy TEXT NOT NULL DEFAULT 'priority',
    sticky_mode TEXT NOT NULL DEFAULT 'conversation_preferred',
    conversation_ttl_seconds INTEGER NOT NULL DEFAULT 7200,
    user_model_ttl_seconds INTEGER NOT NULL DEFAULT 1800,
    failover_threshold INTEGER NOT NULL DEFAULT 2,
    cooldown_seconds INTEGER NOT NULL DEFAULT 600,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

logical_group_models

CREATE TABLE logical_group_models (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    logical_group_id TEXT NOT NULL,
    public_model TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (logical_group_id) REFERENCES logical_groups(logical_group_id) ON DELETE CASCADE,
    UNIQUE (logical_group_id, public_model)
);

logical_group_routes

CREATE TABLE logical_group_routes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    route_id TEXT NOT NULL UNIQUE,
    logical_group_id TEXT NOT NULL,
    name TEXT NOT NULL,
    status TEXT NOT NULL,
    priority INTEGER NOT NULL,
    weight INTEGER NOT NULL DEFAULT 100,
    shadow_group_id TEXT NOT NULL,
    shadow_host_id TEXT NOT NULL,
    upstream_base_url_hint TEXT NOT NULL DEFAULT '',
    cooldown_until TEXT NOT NULL DEFAULT '',
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (logical_group_id) REFERENCES logical_groups(logical_group_id) ON DELETE CASCADE
);

logical_group_route_models

CREATE TABLE logical_group_route_models (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    route_id TEXT NOT NULL,
    public_model TEXT NOT NULL,
    shadow_model TEXT NOT NULL DEFAULT '',
    status TEXT NOT NULL DEFAULT 'active',
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (route_id) REFERENCES logical_group_routes(route_id) ON DELETE CASCADE,
    UNIQUE (route_id, public_model)
);

文件范围

  • Add: internal/store/migrations/0010_logical_groups_and_routes.sql
  • Add tests:
    • internal/store/sqlite/db_test.go
    • 如有必要新增 repo test skeleton

入场条件

  • 重新核对当前 migration 序列,确认下一号位确实应为 0010
  • 明确 logical_group_id / route_id / public_model 的唯一性契约
  • 明确 remote43 当前数据库文件位置与 CRM 启动方式,避免上线后只能看到 /healthz 失败却无法解释

产出清单

  • 一份新 migration 文件
  • 至少一组 migration smoke tests
  • db.Open() 后对新表存在性的自动验证
  • docs/EXECUTION_BOARD.md 中新增一条“0010 已上线验证”的真实记录

本地验证

至少覆盖:

  • migration 可顺序执行
  • 外键与唯一约束正确
  • 重复运行 migration 不报错
  • SQLite Open() 后新表可查询

上传与服务器验证 Gate

上传动作

  1. 提交并推送当前任务代码
  2. 把 CRM 新二进制部署到 remote43

服务器验证

验证目标:

  • CRM 进程能在 remote43 上正常启动
  • 新 migration 不会导致 SQLite 启动失败
  • 旧数据不会因 migration 失败导致 CRM 无法启动

最低验证方式:

  1. 更新 remote43 CRM 二进制
  2. 重启 CRM
  3. 验证:
    • /healthz 返回 ok
  4. 如能 SSH 到远端,再执行一轮 SQLite 表存在性检查

建议远端只读验证内容:

  • logical_groups
  • logical_group_models
  • logical_group_routes
  • logical_group_route_models

证据要求

  • docs/EXECUTION_BOARD.md 记录:
    • migration 名称
    • remote43 启动验证结果
    • 是否做了 SQLite 表存在性确认
  • 如做远端 SQL 验证,保留命令摘要或表名检查摘要

3. P1-T2 logical_group / route Repo + Admin API

目标闭环

管理员已经能通过插件 API 完整维护:

  • 逻辑分组
  • 逻辑分组模型
  • route
  • route 的公开模型覆盖

完成后,管理面必须满足:

  1. 能创建 logical group
  2. 能列出 logical group
  3. 能查看单个 logical group
  4. 能为 logical group 增加 route
  5. 能为 route 增加公开模型

需要新增的 SQLite repo

建议新增:

  • internal/store/sqlite/logical_groups_repo.go
  • internal/store/sqlite/logical_group_models_repo.go
  • internal/store/sqlite/logical_group_routes_repo.go
  • internal/store/sqlite/logical_group_route_models_repo.go

建议 repo 方法最小集合:

LogicalGroupsRepo

  • Create
  • GetByLogicalGroupID
  • List
  • Update
  • Delete

LogicalGroupModelsRepo

  • Create
  • ListByLogicalGroupID
  • DeleteByLogicalGroupIDAndModel

LogicalGroupRoutesRepo

  • Create
  • GetByRouteID
  • ListByLogicalGroupID
  • Update
  • DeleteByRouteID

LogicalGroupRouteModelsRepo

  • Create
  • ListByRouteID
  • DeleteByRouteIDAndModel

需要新增的 Admin API

建议新增接口:

  • POST /api/logical-groups
  • GET /api/logical-groups
  • GET /api/logical-groups/{group_id}
  • PUT /api/logical-groups/{group_id}
  • DELETE /api/logical-groups/{group_id}
  • POST /api/logical-groups/{group_id}/models
  • GET /api/logical-groups/{group_id}/models
  • DELETE /api/logical-groups/{group_id}/models/{model}
  • POST /api/logical-groups/{group_id}/routes
  • GET /api/logical-groups/{group_id}/routes
  • PUT /api/logical-groups/{group_id}/routes/{route_id}
  • DELETE /api/logical-groups/{group_id}/routes/{route_id}
  • POST /api/logical-groups/{group_id}/routes/{route_id}/models
  • GET /api/logical-groups/{group_id}/routes/{route_id}/models

文件范围

  • Add sqlite repos and repo tests
  • Modify:
    • internal/store/sqlite/db.go
    • internal/app/http_api.go
  • Add HTTP tests

入场条件

  • P1-T1 已上线并在 remote43 验证通过
  • 结构字段和状态枚举已稳定,不在 API 层临时发明第二套命名
  • 管理员 session 登录链路在 remote43 仍然可用,避免 API 做完却无真实入口可验

产出清单

  • 4 组 SQLite repo
  • 1 组 logical-group 管理 API
  • 1 组 route 管理 API
  • 完整的 handler tests / repo tests / integration tests
  • 一份真实 API 验证记录

本地验证

至少覆盖:

  • repo CRUD test
  • HTTP handler test
  • 鉴权 test
  • 非法输入校验 test
  • 一条 logical group + route + route model 的完整 create/list/get 闭环 test

上传与服务器验证 Gate

上传动作

  1. 提交并推送
  2. 部署新 CRM 到 remote43

服务器验证

最低验证:

  • 通过公网 admin 同域 API 或直接 CRM API
    • 创建一个测试 logical_group
    • 给它创建一条测试 route
    • 再查询回来

建议验证路径:

  • https://sub.tksea.top/portal-admin-api/...

如果还没做前端页面,也至少用 API 验证:

  1. POST /api/logical-groups
  2. POST /api/logical-groups/{group}/routes
  3. GET /api/logical-groups/{group}
  4. GET /api/logical-groups/{group}/routes

建议额外验证:

  1. POST /api/logical-groups/{group}/models
  2. POST /api/logical-groups/{group}/routes/{route}/models
  3. 再次 GET /api/logical-groups/{group},确认聚合视图已包含 models 与 routes

证据要求

  • 至少保留一组 request/response 摘要
  • 在执行板记录:
    • API 已能真实创建/读取 logical group
    • remote43 已通过验证
    • 是否完成了 route model 真实写入与回读

4. P1-T3 Route Logging Repo + Async Writer

目标闭环

插件已经能把智能路由相关结构化日志写进自己的数据库,即使真正的数据面路由还没上线,也要先把日志基础设施建好。

完成后必须满足:

  1. 插件能写 route decision log
  2. 插件能写 route failover event
  3. 插件能写 sticky audit
  4. 写入方式是可复用的异步 writer而不是散落的裸 SQL

需要新增的 migration

建议新增:

  • internal/store/migrations/0011_route_logging.sql

建议建表:

route_decision_logs

CREATE TABLE route_decision_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    request_id TEXT NOT NULL,
    logical_group_id TEXT NOT NULL,
    public_model TEXT NOT NULL,
    user_key TEXT NOT NULL DEFAULT '',
    conversation_key TEXT NOT NULL DEFAULT '',
    sticky_key TEXT NOT NULL DEFAULT '',
    sticky_key_type TEXT NOT NULL DEFAULT '',
    sticky_hit INTEGER NOT NULL DEFAULT 0,
    selected_route_id TEXT NOT NULL,
    selected_shadow_group_id TEXT NOT NULL,
    fallback_used INTEGER NOT NULL DEFAULT 0,
    error_class TEXT NOT NULL DEFAULT '',
    upstream_status INTEGER NOT NULL DEFAULT 0,
    latency_ms INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

route_failover_events

CREATE TABLE route_failover_events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    request_id TEXT NOT NULL,
    logical_group_id TEXT NOT NULL,
    public_model TEXT NOT NULL,
    from_route_id TEXT NOT NULL,
    to_route_id TEXT NOT NULL,
    reason TEXT NOT NULL,
    failure_count INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

route_sticky_audit

CREATE TABLE route_sticky_audit (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    sticky_key TEXT NOT NULL,
    sticky_key_type TEXT NOT NULL,
    logical_group_id TEXT NOT NULL,
    public_model TEXT NOT NULL,
    route_id TEXT NOT NULL,
    action TEXT NOT NULL,
    expires_at TEXT NOT NULL DEFAULT '',
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

需要新增的 repo / service

建议新增:

  • internal/store/sqlite/route_decision_logs_repo.go
  • internal/store/sqlite/route_failover_events_repo.go
  • internal/store/sqlite/route_sticky_audit_repo.go
  • internal/routing/logwriter.go

RouteDecisionLogger

建议做成接口:

type RouteDecisionLogger interface {
    AppendDecision(ctx context.Context, event RouteDecisionEvent) error
    AppendFailover(ctx context.Context, event RouteFailoverEvent) error
    AppendStickyAudit(ctx context.Context, event RouteStickyAuditEvent) error
}

Writer 策略

第一版建议:

  • 内存 channel 缓冲
  • 定时批量 flush
  • flush 失败打日志
  • 关键事件支持同步兜底

入场条件

  • P1-T1 已完成并在 remote43 验证通过
  • 已明确日志写入是“插件真相日志”,不是宿主 access log 透传
  • 明确 hot path 不允许因 SQLite 写锁把请求链路拖死

产出清单

  • 1 份新 migration0011_route_logging.sql
  • 3 组日志 repo
  • 1 个异步 writer
  • 1 套 event types
  • 最小可复现的“写一条日志再查回来”验证路径

本地验证

至少覆盖:

  • repo create/list smoke tests
  • writer 能写入 SQLite
  • writer 批量 flush 正常
  • flush 失败不致进程崩溃
  • event 字段完整保留

上传与服务器验证 Gate

上传动作

  1. 提交并推送
  2. 部署 CRM 到 remote43

服务器验证

最低验证:

  • 通过一个内部测试接口或最小临时 wiring写入一条 route decision log
  • 再查询 SQLite 中确实存在

建议优先顺序:

  1. 若已接入内部 admin test endpoint则走 API 写入/回读
  2. 若暂时没有查询 API则允许用 remote43 只读 SQLite 查询验证
  3. 不允许只看 stdout 推断“应该写进库了”

如果当前还没有公开查询 API允许通过 SSH 在 remote43 本机做一次只读 SQLite 查询验证。
但要求验证动作必须可复现,不能只口头说“我看到了”。

证据要求

  • 执行板记录:
    • 路由日志表 migration 成功
    • 写入器已在 remote43 验证可落库
    • 说明验证方式是“API 回读”还是“remote43 本机 SQLite 只读查询”

5. P1-T4 Redis Sticky Store Abstraction

目标闭环

在还没做真正路由器前,先把 sticky store 抽象稳定下来。

完成后必须满足:

  1. 代码里有统一 StickyStore 接口
  2. RedisStickyStore 实现
  3. InMemoryStickyStore 测试替身或 fallback
  4. route sticky / route fail count / route cooldown 的 key 规则已固化

注意

本任务是“接口封装 + 适配层”,不是要在这一任务里把完整 route resolver 做完。

需要新增内容

建议新增:

  • internal/routing/sticky.go
  • internal/routing/sticky_redis.go
  • internal/routing/sticky_memory.go

建议接口:

type StickyStore interface {
    Get(ctx context.Context, key string) (StickyBinding, bool, error)
    Set(ctx context.Context, key string, binding StickyBinding, ttl time.Duration) error
    Delete(ctx context.Context, key string) error

    GetRouteFailure(ctx context.Context, routeID string) (RouteFailureState, bool, error)
    SetRouteFailure(ctx context.Context, routeID string, state RouteFailureState, ttl time.Duration) error
    ClearRouteFailure(ctx context.Context, routeID string) error

    GetCooldown(ctx context.Context, routeID string) (RouteCooldownState, bool, error)
    SetCooldown(ctx context.Context, routeID string, state RouteCooldownState, ttl time.Duration) error
    ClearCooldown(ctx context.Context, routeID string) error
}

Redis key 规范

sticky

lg:{logical_group_id}:m:{public_model}:conv:{conversation_id}
lg:{logical_group_id}:m:{public_model}:sess:{session_id}
lg:{logical_group_id}:m:{public_model}:user:{user_id}

route failure

routefail:{route_id}

cooldown

routecool:{route_id}

配置建议

本任务建议同时补上新的配置结构,但可以先不强制 remote43 真正启用:

  • SUB2API_CRM_REDIS_ADDR
  • SUB2API_CRM_REDIS_PASSWORD
  • SUB2API_CRM_REDIS_DB
  • SUB2API_CRM_ROUTE_RUNTIME_BACKEND=memory|redis

第一版建议默认:

  • 没配 Redis 时使用 memory
  • 配了 Redis 且连接成功时启用 redis

入场条件

  • 已确认当前项目没有现成 Redis runtime abstraction可直接新建 internal/routing/
  • 明确 remote43 栈里 Redis 是否已存在、连接参数从哪里注入
  • 明确本任务只做“抽象 + backend 适配”,不偷偷把 RouteResolver 一起塞进来

产出清单

  • StickyStore 接口
  • MemoryStickyStore
  • RedisStickyStore
  • 新配置项与启动配置解析
  • 最小 set/get/failure/cooldown 的测试覆盖

本地验证

至少覆盖:

  • in-memory store test
  • redis adapter test如果能做假客户端或集成测试
  • key 生成稳定性 test
  • TTL 行为 test

上传与服务器验证 Gate

上传动作

  1. 提交并推送
  2. 部署 CRM 到 remote43

服务器验证

最低验证:

  • CRM 在无 Redis 配置时仍能启动
  • 若 remote43 栈已有 Redis则打开 Redis backend 配置后启动成功
  • 做一次最小 sticky set/get 验证

如果 Phase 1 还没有开放测试 API可通过

  • 临时内部 health probe
  • 或 SSH + 本地辅助脚本

完成远端验证。

建议最小远端验证拆成两段:

  1. memory 模式:
    • 不配 Redis
    • CRM 能启动
    • sticky test path 能 set/get 成功
  2. redis 模式:
    • 配 Redis
    • CRM 能启动
    • sticky test path 能 set/get 成功
    • cooldown / route failure 能读写成功

证据要求

  • 执行板记录:
    • sticky store 抽象已落地
    • remote43 在 memory 模式启动通过
    • 如启用 Redis也记录 Redis 模式验证结果

5.1 Phase 1 统一配置增量

为了避免每个任务各自偷加一套配置名Phase 1 里建议一次性统一收口以下新增环境变量:

  • SUB2API_CRM_REDIS_ADDR
  • SUB2API_CRM_REDIS_PASSWORD
  • SUB2API_CRM_REDIS_DB
  • SUB2API_CRM_ROUTE_RUNTIME_BACKEND

其中:

  • P1-T1 不需要使用这些变量
  • P1-T2 不应该依赖这些变量
  • P1-T3 可先不依赖 Redis
  • P1-T4 负责正式接入并验证

6. 每个闭环功能的统一发布流程

以下步骤是 Phase 1 每个任务完成后的统一动作,不可跳过。

Step A本地质量门禁

gofmt -l .
go vet ./...
go test -cover ./internal/...
go test ./tests/integration/... -count=1

Step B提交与推送

  • 提交本任务代码
  • 推送到 3 个远端

Step C部署到 remote43

按任务内容选择:

  • CRM / stack使用 scripts/deploy/setup_remote43_patched_stack.sh 的既有流程更新 remote43 CRM
  • portal 资产:若有前端改动,再用 scripts/deploy/deploy_tksea_portal.sh

Step D服务器验证

至少验证:

  • /healthz
  • 对应新增 API 或功能
  • 若涉及真实导入/真实运行,补 acceptance 证据

Step E证据沉淀

  • 更新 docs/EXECUTION_BOARD.md
  • 如产生真实运行验证,补:
    • artifacts/real-host-acceptance/...

6.1 统一证据模板

为了让后续每个任务的“已完成”可审计,建议每次至少记录以下 8 项:

  1. 提交 SHA
  2. 推送的远端列表
  3. remote43 部署时间
  4. remote43 CRM 版本或 HEAD
  5. /healthz 结果
  6. 任务对应的功能验证结果
  7. 失败时的回滚动作
  8. 证据位置

如需要单独存 Phase 1 证据,建议目录模板:

artifacts/phase1-verification/YYYYMMDD_remote43_p1-tX_<short-name>/

如果实际没有新 artifact也必须在执行板里写清楚验证方式不允许空口确认。

6.2 回滚原则

每个任务都要带回滚预案,否则不算闭环。

  • P1-T1 / P1-T3 涉及 migration
    • 不能假设 SQLite 方便回退 schema
    • 真实回滚策略应是“回滚二进制 + 保证新表对旧代码无害”
  • P1-T2 涉及新 API
    • 出问题先回滚二进制,保留表结构
  • P1-T4 涉及 runtime backend
    • Redis backend 异常时必须可降级回 memory

7. Phase 1 完成定义

只有同时满足下面 7 条,才能宣称 Phase 1 完成:

  1. SQLite 已有 logical group / route 结构表
  2. Admin API 已能 CRUD logical group / route
  3. 路由日志表已存在
  4. 路由日志写入器已存在且可落库
  5. Sticky store 抽象已存在
  6. 所有任务都已至少在 remote43 部署验证一次
  7. 执行板已更新真实状态,不留“本地已完成、远端未验证”的假完成

一句话结论

Phase 1 不追求把智能路由全部做完,而是先把 SQLite 结构、管理 API、路由日志、Redis sticky 抽象 这四个地基打好。
并且从这一阶段开始,每个闭环功能完成后都必须上传到 remote43 再验证,不允许只凭本地测试宣布完成。