Initial LisgloSIPS V2 implementation

This commit is contained in:
hectorzhao
2026-06-22 10:56:38 +08:00
commit 5fa1bd35e9
303 changed files with 35644 additions and 0 deletions
+568
View File
@@ -0,0 +1,568 @@
# LisgloSIPS V2 实施状态
> 本文件用于跨 Codex 会话持续交接。每次新会话开始必须先读取本文件和 `SOFTSWITCH_PLATFORM_DESIGN_V2.md`;每次任务完成、失败或被阻塞后必须更新本文件。
## 当前状态
- 当前任务:无,S00-S30 本地 V2 闭环已完成
- 总体状态:S30 已完成;本地 KVM 开发环境 V2 闭环冻结在 `s28-v2-20260621220924`,阿里云迁移前需按 S30 Runbook 重新演练
- 最后更新:2026-06-22 10:35 +08:00
- 当前阻塞:无。遗留风险为 Redis 恢复后 OpenSIPS 热路径可能需要重启 `opensips` 才恢复,已纳入 S30 Runbook,根因级修复需后续单独处理。
- 当前环境:A/B/T 为本地 KVM 开发服务器,通过 Tailscale 联调;开发完成后再迁移阿里云
- 私密访问资料:`.codex-private/SERVER_ACCESS.md`,只在实际连接时读取,禁止回显
## 服务器索引
| 代号 | 地址 | 用户 | 用途 |
| --- | --- | --- | --- |
| A | `100.90.90.90` | `hector` | OpenSIPS、RTPEngine、录音内存盘、HEP、Exporter |
| B | `100.90.90.91` | `hector` | API、Worker、Redis、MySQL、录音、HOMER、Prometheus/Grafana |
| T | `100.93.185.30` | `hector` | 已安装 OpenSIPS,模拟客户 SIP 注册与呼叫 |
## 任务状态表
状态只能使用:`待开始``进行中``已完成``已阻塞``已跳过`
| ID | 任务 | 状态 | 主要产物/验收 |
| --- | --- | --- | --- |
| S00 | SSH 安全接入与凭据迁移 | 已完成 | 三机 SSH Key、密码轮换、访问验证、访问 Runbook;后续 SSH 加固只调整管理端口并保留 Key 登录 |
| S01 | 三机只读资产盘点 | 已完成 | `docs/inventory-A.md``inventory-B.md``inventory-T.md`、汇总报告 |
| S02 | 网络与安全组验收 | 已完成 | `docs/infra-check.md`,含本地端口矩阵和未来阿里云安全组清单 |
| S03 | Server B 基础系统初始化 | 已完成 | `docs/SERVER_B_BASELINE_RUNBOOK.md``infra/server-b/s03/`、远端备份 |
| S04 | Server B MySQL 与 Redis | 已完成 | `docs/SERVER_B_DATA_SERVICES_RUNBOOK.md``infra/server-b/s04/`、备份恢复演练 |
| S05 | Server B Node.js、Nginx 与 TLS 基线 | 已完成 | `docs/SERVER_B_WEB_RUNTIME_RUNBOOK.md``infra/server-b/s05/`、HTTPS 与重启验收 |
| S06 | Server B HOMER 与基础监控 | 已完成 | HEP 接收、HOMER API、Prometheus、Grafana、exporters、A Node Exporter |
| S07 | V2 后端 Monorepo 骨架 | 已完成 | NestJS/Fastify/Prisma、日志、配置、健康检查、测试框架;本地和 B 均可构建 |
| S08 | 数据库 Schema 与迁移 | 已完成 | Prisma Schema、初始化迁移、内置角色/权限种子、B 临时空库迁移和备份恢复测试 |
| S09 | 登录认证基础 | 已完成 | 登录、刷新、退出、Argon2id、Session/Token 安全 |
| S10 | 用户、角色、权限与审计 | 已完成 | RBAC、内置角色保护、重置密码、审计拦截器 |
| S11 | 客户管理 API | 已完成 | 客户 CRUD、启停、余额/授信读取、单元与集成测试 |
| S12 | 充值与不可变余额流水 | 已完成 | 客户/供应商充值、幂等、事务、审计、并发测试 |
| S13 | 客户网关 API | 已完成 | IP/SIP 注册认证配置、启停、密码安全存储 |
| S14 | 客户网关策略与 Redis 配置发布 | 已完成 | 主被叫匹配、优先级、Outbox、配置版本和回滚 |
| S15 | 供应商管理 API | 已完成 | 供应商 CRUD、余额/授信、启停 |
| S16 | 落地网关 API | 已完成 | CPS/并发/时段/编码/号码转换/周期费率/启停 |
| S17 | 落地线路组 API | 已完成 | 成员增删、优先级重排、并发汇总、完整性校验 |
| S18 | Server A OpenSIPS 基线 | 已完成 | 15060、认证、防扫描、MI 本地绑定、Redis/HEP 模块 |
| S19 | Server A RTPEngine 与录音 tmpfs | 已完成 | 内核转发、RTP 端口段、3 GiB tmpfs、录音 ready 规则 |
| S20 | Server A Redis 热路径与 HEP/指标 | 已完成 | Lua 原子检查、配置读取、HEP 9060、Node Exporter |
| S21 | Server T 客户模拟配置 | 已完成 | IP/SIP 注册两套测试账号、呼叫脚本、失败场景脚本 |
| S22 | CDR Redis Stream | 已完成 | 标准事件、XADD/XREADGROUP、ACK、Pending、死信、幂等 |
| S23 | 最小计费与余额扣减 | 已完成 | Decimal 计费、客户费用/成本、重复 CDR 不重复扣费 |
| S24 | 录音搬运与播放 | 已完成 | A->B 私网搬运、校验、Range、权限、源文件安全删除 |
| S25 | 质检后端 | 已完成 | 抽检规则、稳定抽样、录音详情、质检保存和审计 |
| S26 | Dashboard 聚合 | 已完成 | 实时占位/聚合指标、趋势、失败码、异常网关、待质检 |
| S27 | React Demo 接入真实 API | 已完成 | API Client、核心运营页真实 API、错误/加载/空状态 |
| S28 | 三机端到端联调 | 已完成 | T 发起呼叫,A 转发录音,B 出 CDR/费用/信令/质检 |
| S29 | 性能、故障与安全测试 | 已完成 | CPS/并发/录音、Redis/MySQL/Worker 故障、越权与重放 |
| S30 | 备份、Runbook、灰度与上线 | 已完成 | 恢复演练、上线/回滚脚本、灰度记录、验收报告 |
## 每次新会话的固定开场指令
可直接向 Codex 发送:
```text
请先读取 SOFTSWITCH_PLATFORM_DESIGN_V2.md、IMPLEMENTATION_STATUS.md
以及本任务关联的现有脚本/代码。只执行状态文件里的当前任务,不提前执行后续任务。
开始前复核上次交接记录;涉及服务器写操作、服务重启、防火墙、密码、数据库迁移或数据删除时先说明影响和回滚点。
完成后运行验收检查,并更新 IMPLEMENTATION_STATUS.md 的任务状态、产物、验证结果、遗留问题和下一任务。
```
## 单任务交接记录模板
每完成一个任务,在本节顶部新增记录:
```markdown
### YYYY-MM-DD HH:mm - Sxx 任务名称
- 状态:已完成 / 已阻塞
- 操作服务器:A / B / T / 本地
- 完成内容:
- 修改文件:
- 安装软件及固定版本:
- 执行的关键命令:只记录无密码版本
- 验证结果:
- 回滚方式:
- 未解决问题:
- 下一任务:Sxx
```
## 交接记录
### 2026-06-22 10:35 - S30 备份、Runbook、灰度与上线完成
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:完成 S30 本地 KVM 开发环境上线前冻结与演练。新增 S30 总控 Runbook、最终验收报告、B release preflight/rollback 脚本和 A OpenSIPS 配置恢复脚本;触发 B MySQL/Redis 新备份并完成隔离恢复演练;使用 T 发起灰度呼叫并核对 B raw CDR、rated CDR 和 READY 录音;实际运行 B preflightA OpenSIPS 当前配置语法检查通过;三机关键服务最终复核 active。
- 修改文件:`IMPLEMENTATION_STATUS.md``docs/S30_RELEASE_BACKUP_RUNBOOK.md``docs/S30_FINAL_ACCEPTANCE_REPORT.md``infra/server-b/s30/lisglosips-release-preflight.sh``infra/server-b/s30/lisglosips-release-rollback.sh``infra/server-a/s30/lisglosips-opensips-config-restore.sh`
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.x`、RTPEngine `11.5.1.18-1ubuntu1.2`、Node.js `v22.22.2`、pnpm `10.33.0`、Prisma `6.19.0`、MySQL `8.0.46`、Redis `7.0.15`
- 执行的关键命令:B `systemctl start lisglosips-backup.service`、MySQL 备份恢复到临时库 `lisglosips_s30_restore`、Redis RDB 恢复到隔离 Unix socket 临时实例、`bash -n` 检查 S30 脚本、`/tmp/lisglosips-release-preflight.sh` preflightA `opensips -C -f /etc/opensips/opensips.cfg`T `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 12 --hold 3`。所有 sudo/数据库/API secret 均从 `.codex-private/` 或远端 env 读取并只在进程内使用,未写入文档。
- 验证结果:MySQL 备份 `/data/backups/mysql/20260622T022811Z``all-databases.sql.gz``lisglosips.sql.gz``metadata.tsv` 校验通过,临时库恢复计数 `raw_cdrs=33``recordings=27``customer_recharges=34``users=2` 后已删除;Redis 备份 `/data/backups/redis/20260622T022812Z``dump.rdb``metadata.txt` 校验通过,隔离实例核对 `DBSIZE=51``stream:cdr_payload=54``cfg:active_version=s28-v1` 后已关闭并清理。灰度 Call-ID `s28-1782095548702-luppqjd4@lisglosips-t` 收到 SIP `100/200/BYE 200`B 生成 raw `raw_7a6356cc76974652aa30a9d7a350041b`、rated `rated_e0eb8cbe1b90454cb3cf2d9a3bbc9290``billSec=6`、客户费用/供应商成本均 `0.012000`、录音 `rec_5fdf599d679f493a83fc7a2d205a128a``READY` 且 55758 bytes。B preflight 通过,A/B/T 关键服务最终 active,S30 文档/脚本未检出待补充或明文 secret。
- 回滚方式:B 应用按 `docs/S30_RELEASE_BACKUP_RUNBOOK.md` 使用 `infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/<previous-release>` 切回上一 releaseA OpenSIPS 配置按 `infra/server-a/s30/lisglosips-opensips-config-restore.sh <backup-opensips.cfg>` 先语法检查再恢复;Redis 配置切回上一 `cfg:active_version`,不删除 Stream;数据库优先向前修复迁移,只有完整备份和维护窗口下才恢复数据。S30 产生的测试 CDR、录音和备份作为验收证据保留。
- 未解决问题:本地开发环境不是阿里云生产环境;正式迁移前必须重新完成 VPC/安全组、正式 DNS/TLS、独立数据盘、备份恢复、故障注入和端到端呼叫验收。S29 遗留 Redis 恢复后 OpenSIPS 热路径可能需要重启 `opensips` 的根因级修复仍需后续单独处理;真实多 A、多 B、高可用和完整生产路由不属于本地 S30 闭环。
- 下一任务:无;S00-S30 本地 V2 闭环完成,后续应按 S30 Runbook 进入阿里云迁移/生产化专项
### 2026-06-22 10:25 - S29 性能、故障与安全测试完成
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:完成本地 KVM 开发环境内的 S29 小规模性能、故障与安全测试。5 路并发和 12 路短突发呼叫均完成 SIP 100/200/BYE 200、CDR、计费、录音 READY 入库;Recording Worker 停止期间的录音恢复、CDR Worker 停止期间的 Redis pending 恢复均通过;Redis 停止期间 A 正确返回 `503 Redis Unavailable`,恢复后发现 OpenSIPS 热路径连接不自动恢复,已通过重启 A `opensips` 做运行时恢复并复测成功;MySQL 停止期间发现 CDR 被 deadletter 的可恢复性缺陷,已修复并部署到 B 当前 release,复测 MySQL 恢复后 pending CDR 自动入库且无新增 deadletter;完成 API 未认证、RBAC 越权、录音播放保护、充值幂等重放和 SIP 非法来源探针。S29 报告已写入 `docs/S29_PERFORMANCE_FAULT_SECURITY_REPORT.md`
- 修改文件:`IMPLEMENTATION_STATUS.md``docs/S29_PERFORMANCE_FAULT_SECURITY_REPORT.md``infra/server-t/s28/lisglosips-s28-sip.py``packages/redis/src/cdr-stream.ts``packages/redis/src/cdr-stream.spec.ts``apps/worker-cdr/src/main.ts``apps/worker-cdr/src/rating.ts`T 的 `/opt/lisglosips-s28/lisglosips-s28-sip.py`B 当前 release `/opt/lisglosips/releases/s28-v2-20260621220924/apps/worker-cdr/``/opt/lisglosips/releases/s28-v2-20260621220924/packages/redis/`
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.x`、RTPEngine `11.5.1.18-1ubuntu1.2`、Node.js `v22.22.2`、pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run packages/redis/src/cdr-stream.spec.ts apps/worker-cdr/src/rating.spec.ts``corepack pnpm@10.33.0 --filter @lisglosips/redis build``corepack pnpm@10.33.0 --filter @lisglosips/worker-cdr build`T 多路 `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 10 --hold 1/3`B/A 使用 `systemctl stop/start/restart` 做受控故障注入和恢复;B 通过 API 与 Prisma 只读/小额写入探针核对 RBAC、录音播放保护和充值幂等。所有 sudo/数据库/API secret 均从 `.codex-private/` 或远端 env 读取并只在进程内使用,未写入文档。
- 验证结果:5 路并发生成 5 条 raw CDR、5 条 rated CDR、5 条 READY 录音;12 路短突发生成 `raws=12``rated=12``recordingsReady=12``totalDuration=72`、录音大小 `55758-56078` bytesRecording Worker 恢复后补搬运录音;CDR Worker 恢复后 Redis `pending=0``lag=0` 并入库;MySQL 故障修复后复测 Call-ID `s28-1782093477852-a8bqqohb@lisglosips-t` 自动入库 raw/rated CDR,无新增 deadletterAPI `/api/v2/users` 未认证返回 `401 AUTH_REQUIRED`,质检角色访问用户管理返回 `403 RBAC_FORBIDDEN`,录音播放未认证返回 `401 AUTH_REQUIRED`,伪造录音 ID 播放返回 `404 RECORDING_NOT_READY`;充值重放两次返回同一充值 ID `rch_da0bba1c7b1d44428ab92d77a55e503d`,数据库 `customer_recharges.rows=1``idempotency_keys.rows=1`,余额只增加 `0.000001` 一次;B 实际 Prometheus 服务 `lisglosips-prometheus.service` active 且 readyA/B/T 关键服务复核 active。
- 回滚方式:T 可恢复 S29 前 `/opt/lisglosips-s28/lisglosips-s28-sip.py` 或继续使用修复后的动态端口脚本;B 可用本次部署前保留在 `/tmp/s29-worker-cdr-backup-*``/tmp/s29-redis-backup-*` 的备份恢复 worker-cdr 与 packages/redis 后重启 `lisglosips@cdr-worker`;A Redis 故障恢复只做过服务重启,无配置文件改动。S29 产生的测试 CDR、录音、充值、幂等键和测试用户作为审计证据保留,不直接删除;如需清理应单独说明影响与回滚点。
- 未解决问题:Redis 恢复后 A/OpenSIPS 热路径不会自动从 `Redis Unavailable` 恢复,当前运行时修复是重启 A `opensips`,根因级修复需继续排查 OpenSIPS Redis 连接池/重连策略;SIP 非法来源探针表现为无响应且未进入 OpenSIPS 日志,后续建议增加明确拒绝日志/指标;S29 期间 Tailscale 管理链路出现短时高延迟/丢包,记录为开发环境风险;真实多 A、多 B 和完整生产路由仍不在本任务范围。
- 下一任务:`S30 - 备份、Runbook、灰度与上线`,尚未开始
### 2026-06-22 09:32 - S28 三机端到端联调完成
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:补齐 S28 剩余录音与质检闭环。T 呼叫脚本新增双向 PCMU RTP 发送,UAS 在 200 OK 后向 A/RTPEngine 回送 RTP,主叫侧在 ACK 后向应答 SDP 媒体地址发送 RTPA 的 S28 OpenSIPS 路由启用 `record-call=on`,保持 INVITE/ACK/BYE 经原 A,仍绑定本机 RTPEngine;修正 A `/dev/shm/voip_rec` 历史 root-owned 日期目录权限;修复 A recording finalizer 过早搬运问题,跳过 0 字节、仍被打开或未稳定的录音文件;B Recording Worker 新增按 RTPEngine 文件名反解 Call-ID 的 fallback 关联逻辑,使录音可自动关联 `raw_cdrs`,从而让质检抽样按客户/线路组命中。新增 S28 质检测试用户 `usr_s28_quality`、抽检规则 `qs_s28_e2e_20260622`,并通过 API 保存最新录音质检记录。
- 修改文件:`infra/server-t/s28/lisglosips-s28-sip.py``infra/server-a/s28/opensips.cfg``infra/server-a/s19/scripts/lisglosips-recording-finalize``apps/worker-recording/src/transfer.ts``apps/worker-recording/src/transfer.spec.ts``IMPLEMENTATION_STATUS.md`A 的 `/etc/opensips/opensips.cfg``/usr/local/sbin/lisglosips-recording-finalize``/dev/shm/voip_rec` 目录属主/权限;T 的 `/opt/lisglosips-s28/lisglosips-s28-sip.py`B 当前 release `/opt/lisglosips/releases/s28-v2-20260621220924/apps/worker-recording/` 的源码与 dist 产物
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.x`、RTPEngine `11.5.1.18-1ubuntu1.2`、Node.js `v22.22.2`、pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run apps/worker-recording/src/transfer.spec.ts``corepack pnpm@10.33.0 --filter @lisglosips/worker-recording build`A `opensips -C -f /tmp/opensips-s28.rendered.cfg` 后重启 `opensips`,更新 finalizer 后保持 timer activeT `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 10 --hold 3`B 替换 worker-recording 产物并重启 `lisglosips@recording-worker`,通过 MySQL 和 API 只读/写入测试核对。所有 sudo/数据库/API secret 均从 `.codex-private/` 或远端 env 读取并只在进程内使用,未输出或写入明文密码。
- 验证结果:最终验收 Call-ID `s28-1782091725991-ewqrpe7w@lisglosips-t`T 收到 `100 Giving it a try``200 OK`、BYE `200 OK`B `raw_cdrs` 生成 `raw_aa6cbaf7758f41ea895a2889f558559d``duration_sec=6``rating_status=RATED``rated_cdrs` 对应客户费用和供应商成本均为 `0.012000``bill_sec=6`B `recordings` 生成 `rec_24b56d60dfd14937ae2e4c09802a91be`,自动关联该 raw CDR`status=READY``bytes=55758`、SHA-256 前缀 `e2cfaf8c7bbc6023`API `/api/v2/health/ready` 返回 ok`GET /api/v2/recordings/rec_24b56d60dfd14937ae2e4c09802a91be` 返回 customer/lineGroup/callId/CDR 摘要,抽样规则 `qs_s28_e2e_20260622` 命中 `selected=true``PUT /api/v2/recordings/:id/review` 成功保存 `qrev_9672ee20119049a1a56082c35dc42e0f``GET /play` 返回 `200``content-type: audio/wav` 和内部 `x-accel-redirect`。A `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-recording-finalize.timer` activeB `lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``mysql``redis-server``heplify-server` activeT `lisglosips-s28-uas``opensips` active。
- 回滚方式:T 回滚恢复或删除 `/opt/lisglosips-s28/lisglosips-s28-sip.py`,停止 `lisglosips-s28-uas.service`A 回滚可用 `/var/backups/lisglosips-s28/20260621T145920Z/opensips.cfg` 恢复 `/etc/opensips/opensips.cfg` 并语法检查后重启 `opensips`finalizer 可恢复 S24 备份 `/var/backups/lisglosips-s24/20260621T121130Z/lisglosips-recording-finalize`,但旧脚本会重现提前搬运风险;B 回滚可将 worker-recording 产物恢复到本次替换前的 release 版本或把 `/opt/lisglosips/current` 指回前一 release 后重启相关服务。测试 CDR、余额流水、录音和质检记录作为验收证据保留,不直接删除;如必须清理,应另开维护步骤并先说明影响与回滚点。
- 未解决问题:A 当前仍是 S28 单一测试落地端最小路由,不是完整多落地网关生产路由;录音 `duration_sec` 仍为 0,后续可在 Recording Worker 中读取 WAV 时长或由媒体侧补充;S28 调试期间存在一条 0 字节早期录音 `rec_f9b1457861444d4ebbddc11adb208d13` 作为问题证据保留;多 A 真实部署和多 B 高可用仍留给后续阶段。
- 下一任务:S29 - 性能、故障与安全测试
### 2026-06-21 23:06 - S28 三机端到端联调推进
- 状态:进行中
- 操作服务器:A、B、T、本地
- 完成内容:将 B 从 S05 placeholder 发布为完整 V2 release `/opt/lisglosips/releases/s28-v2-20260621220924``/opt/lisglosips/current` 已切换到该 release;执行业务库迁移,补齐 `20260621093000_auth_sessions` additive 迁移记录,权限/角色种子已落库;`lisglosips@api``lisglosips@cdr-worker` activeAPI `/api/v2/health/live``/api/v2/health/ready` 返回 ok;补齐 B 录音拉取 `/etc/lisglosips/recording/ssh_config``lisglosips@recording-worker` 已恢复 active。新增 S28 测试业务数据:客户 `cus_s28_t`、客户网关 `cgw_s28_t_ip`、供应商 `ven_s28_t`、落地网关 `vgw_s28_t_uas`、线路组 `llg_s28_t` 和策略 `cgp_s28_t_default`Redis 热路径配置版本为 `s28-v1`。在 T 部署专用落地模拟服务 `lisglosips-s28-uas.service`,监听 `100.93.185.30:50620`,只终结呼叫不回送 A,避免循环。A 部署 S28 最小真实路由:Lua 热路径 allow 后执行 RTPEngine offer/answer、转发到 T 专用落地端、写成功 CDR 到 Redis Stream,并为 T 源 IP 的 ACK/BYE 添加 fallback,确保回到原 A 后转给 T 落地端。
- 修改文件:`infra/server-t/s28/lisglosips-s28-sip.py``infra/server-a/s28/opensips.cfg``pnpm-lock.yaml``IMPLEMENTATION_STATUS.md`B 的 `/opt/lisglosips/releases/s28-v2-20260621220924``/etc/lisglosips/api.env``/etc/lisglosips/cdr-worker.env``/etc/lisglosips/recording-worker.env``/etc/lisglosips/recording/ssh_config``/etc/systemd/system/lisglosips@recording-worker.service.d/10-s28-recording.conf`T 的 `/opt/lisglosips-s28/lisglosips-s28-sip.py``/etc/systemd/system/lisglosips-s28-uas.service`A 的 `/etc/opensips/opensips.cfg`
- 安装软件及固定版本:未安装新系统软件;B 继续使用 Node.js `v22.22.2`、Corepack `0.34.6`、pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 install --lockfile-only``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 fetch`B 端离线 `pnpm install --frozen-lockfile --offline --ignore-scripts`、MySQL 迁移/种子、systemd restart/enableA `opensips -C -f /tmp/opensips-s28.rendered.cfg` 后重启 `opensips`T 启动 `lisglosips-s28-uas.service`。凭据均从 `.codex-private/` 读取并通过 stdin/base64 参数传递,未写入文档或输出明文密码。
- 验证结果:最终候选呼叫 Call-ID `s28-1782054233152-f4eiz799@lisglosips-t`T 收到 `100 Giving it a try``200 OK`,随后 BYE 返回 `200 OK`A 日志显示 hotpath allow、成功 CDR XADD `1782054232944-0`、ACK/BYE 均从 T 回到 ARTPEngine session 最终回到 0B MySQL `raw_cdrs` 生成 `raw_2903404fa10b47a6a4b7968e3662cdd5``sip_code=200``hangup_reason=NORMAL_CLEARING``customer_id=cus_s28_t``vendor_gateway_id=vgw_s28_t_uas``duration_sec=6``rated_cdrs` 生成客户费用/供应商成本 `0.012000`、计费秒数 `6`;客户 `cus_s28_t` 余额扣减到 `99.928000`(包含本次和前面 S28 调试成功 CDR);HOMER `hep_proto_1_call` 可查到该 Call-ID 的 `INVITE``ACK``BYE`B API ready ok`lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker` 均 activeT S28 UAS active。
- 回滚方式:B 回滚先停止 `lisglosips@cdr-worker``lisglosips@recording-worker`,将 `/opt/lisglosips/current` 指回备份记录中的 S05 placeholder release,并重启 `lisglosips@api`B 迁移前备份位于 `/var/backups/lisglosips-s28/20260621T140940Z`,如需撤回数据库写入应走备份恢复或受控反向账务处理,不直接删除 CDR/余额流水。A 回滚使用 `/var/backups/lisglosips-s28/20260621T145920Z/opensips.cfg` 恢复 `/etc/opensips/opensips.cfg`,语法检查后重启 `opensips`。T 回滚停止并删除 `lisglosips-s28-uas.service``/opt/lisglosips-s28`。Redis 配置可将 `cfg:active_version` 切回 `cfg:previous_version` 或清理 `s28-v1` 测试 keys,清理前需确认没有后续测试依赖。
- 未解决问题:本次 T 脚本只做 SIP/SDP,不发送 RTP 音频包,因此 RTPEngine 未产生真实录音文件;Recording Worker 虽已 active,但尚未完成真实通话 `.ready` 文件搬运、`recordings` 入库和质检抽样/保存核对。S28 仍需补“RTP 音频生成 -> A 录音 ready -> B Recording Worker 入库 -> 质检接口核对”后才能标记已完成。当前 A 配置中的 S28 路由是单一测试落地端 fallback,不是完整多落地网关生产路由。
- 下一任务:继续 `S28 - 三机端到端联调`,补齐录音和质检闭环;不得进入 S29。
### 2026-06-21 21:47 - S28 三机端到端联调
- 状态:已阻塞
- 操作服务器:A、B、T;仅执行一次 S28 测试 INVITE 和只读核对,未修改服务器配置,未重启服务,未执行数据库迁移、数据删除或防火墙变更
- 完成内容:按 S28 目标先做三机链路探针。T 使用 S21 脚本向 A `100.90.90.90:15060` 发起 IP 认证 INVITECall-ID 为 `s21-invite-1782049439554-088rul75@lisglosips-t`A 返回 `SIP/2.0 503 Config Missing`;A OpenSIPS 日志确认热路径拒绝原因为 `CONFIG_MISSING`,并成功向 B Redis Stream 写入失败 CDRB Redis Stream `stream:cdr_payload` 可按 Call-ID 查到该事件,字段包含 `node_id=a1``opensips_instance=opensips-a1``ingress_a_ip=100.90.90.90``rtpengine_node=a1``sip_code=503``hangup_reason=CONFIG_MISSING`B HOMER `hep_proto_1_call` 最近记录的 `sid``data_header.callid` 均为该 Call-ID`data_header.method=INVITE`,说明 HEP 信令镜像入库正常。
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件
- 执行的关键命令:T `/opt/lisglosips-s21/s21-call-a-ip`A `journalctl -u opensips --since '10 minutes ago'` 按 Call-ID 查询;B `redis-cli XREVRANGE stream:cdr_payload` 按 Call-ID 查询;B PostgreSQL `homer_data.hep_proto_1_call` 只读查询;B 本机 API/数据库只读探针。所有凭据均从 `.codex-private/` 读取并通过 stdin 使用,未输出明文密码。
- 验证结果:T->A SIP 输入、A->B Redis Stream 失败 CDR、A->B HOMER HEP 信令三段可按 Call-ID 串联;但未进入真实路由、媒体、录音、计费或质检闭环。B 当前 `/api/v2/health/live` 返回 HTTP 404`lisglosips` 业务库表数为 `0`,说明 current release 仍不是完整 V2 API/WorkerA 当前 `/etc/opensips/opensips.cfg` 仍为 S20/S22 占位逻辑,配置缺失时返回 `Config Missing`,即便配置 allow 也会返回 `Routing Not Ready`,没有真实落地网关选择、RTPEngine offer/answer、t_relay 和对话内 ACK/BYE/re-INVITE 路由。
- 回滚方式:本次未修改服务器配置和数据库结构,无需服务回滚;产生的一条测试 INVITE、HOMER 记录和 Redis Stream 失败 CDR 作为联调证据保留,不做直接删除。若后续需要清理测试数据,应先单独说明影响和回滚点。
- 未解决问题:S28 阻塞在两个前置缺口:一是 B 需要受控发布完整 V2 API、Web 和 CDR/Recording Worker,并在空业务库上执行迁移/种子和健康检查;二是 A 需要新增真实呼叫路由配置,至少包含从 Redis 配置读取线路组/落地网关、供应商目标地址、RTPEngine offer/answer、t_relay、onreply/failure/dialog 内路由和成功/失败 CDR 事件。当前不能进入 S29。
- 下一任务:继续 `S28 - 三机端到端联调`,先解除上述阻塞;不得提前执行 S29。
### 2026-06-21 21:32 - S27 React Demo 接入真实 API
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:新增 Web API Client,默认使用 `/api/v2`,开发环境通过 Vite proxy 转发到本地 API `127.0.0.1:3000`,可选从浏览器 `localStorage` 读取 `lisglosips.accessToken` 作为 Bearer Token,不写入或输出任何明文凭据;Dashboard 接入 `dashboard/summary``dashboard/trends`;客户、供应商、充值记录、用户、角色、操作日志接入真实列表 API;客户/供应商新增、编辑、充值调用真实 API 并在成功后重新拉取数据;页面新增统一加载、错误和空状态,未登录或无权限时显示鉴权提示;顶部状态从“纯前端 Mock”改为真实 API 状态。CDR、计费、SIP 运维、监控告警、系统设置仍保留为未完成业务页面/静态运维原型,等待对应后端查询接口或后续延期页面设计,不在本任务伪造真实数据。
- 修改文件:`apps/web/src/api.js``apps/web/src/App.jsx``apps/web/src/styles.css``apps/web/vite.config.js``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用 React `18.2.0`、Vite `5.4.21`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 --filter @lisglosips/web build``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`、本地启动 `corepack pnpm@10.33.0 --filter @lisglosips/web dev`
- 验证结果:Web 单包 build 通过;全 workspace `typecheck``lint``build` 均通过;本地 Vite 页面 `http://127.0.0.1:5173/` 返回 HTTP 200。API 服务若未启动或未登录,前端会展示错误态而不是回退到静态运营数据。
- 回滚方式:删除 `apps/web/src/api.js`,恢复 `apps/web/src/App.jsx` 中本次 API 数据加载、真实接口操作、空状态和顶部 API 状态改动,恢复 `apps/web/src/styles.css` 的空状态样式和 `apps/web/vite.config.js` proxy 配置,重新执行 `corepack pnpm@10.33.0 --filter @lisglosips/web build`;S27 未修改远端服务或数据库,无远端数据需回滚。
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,本次未部署到 B 当前 Web/API 服务;前端尚无登录页,真实接口需要已有 Bearer Token 或同源 Cookie 才能返回业务数据;CDR 查询、计费页面、自研 SIP 运维、监控告警、系统设置仍缺少对应产品化后端页面接口或属于延期页面,S27 未提前实现。
- 下一任务:`S28 - 三机端到端联调`
### 2026-06-21 21:20 - S26 Dashboard 聚合
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:新增 `DashboardModule`,实现 `GET /api/v2/dashboard/summary``GET /api/v2/dashboard/trends`;接口统一要求 `dashboard.view` 权限;summary 按 `Asia/Shanghai` 今日窗口聚合 raw CDR、rated CDR、启用客户/网关、待质检数量、失败响应码和失败落地网关 Top 10;trends 支持 `hours=1..168``bucketMinutes=5|15|60`,按固定桶返回通话、接通率、时长、客户费用、供应商成本和毛利;实时在线通话和注册数当前返回 `0` 并标记 `source: "not_configured"`,等待后续接入 Redis/OpenSIPS 指标。未新增预聚合表,未做 `worker-metrics`,未进入 S27 前端接入。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/dashboard/``docs/DASHBOARD_AGGREGATION_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用既有 NestJS/Fastify、Prisma `6.19.0`、Vitest `4.0.14`、TypeScript `5.9.3`
- 执行的关键命令:`corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/dashboard/dashboard.service.spec.ts``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:S26 聚焦测试 1 个文件 4 条通过,覆盖上海自然日边界、接通率、趋势桶和参数校验;全 workspace `typecheck``lint``build` 均通过
- 回滚方式:从 `AppModule` 移除 `DashboardModule`,删除 `apps/api/src/modules/dashboard/``docs/DASHBOARD_AGGREGATION_RUNBOOK.md`,重新执行 `corepack pnpm@10.33.0 build`;S26 未对远端数据库写入,无远端数据需清理
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,本次未部署到 B 当前 API 服务;Dashboard 设计要求的真正实时在线通话/注册数仍需后续接入 Redis/OpenSIPS 指标;24 小时趋势最终应由 `worker-metrics` 或 CDR Worker 增量写入预聚合表,本次只做时间窗口受限查询,避免扫描全量 CDR。
- 下一任务:`S27 - React Demo 接入真实 API`
### 2026-06-21 21:05 - S25 质检后端
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:新增 `QualityModule`,实现 `GET/POST/PATCH/DELETE /api/v2/quality/rules` 及规则启停,支持按客户、线路组、比例、生效时间和过期时间配置抽检规则;新增稳定抽样算法 `sha256(rule_id:recording_id)`,同一规则与录音重复查询结果稳定;扩展 `RecordingsModule`,实现 `GET /api/v2/recordings``GET /api/v2/recordings/:id``PUT /api/v2/recordings/:id/review`,返回录音 CDR 摘要、最新质检、历史质检、上下条导航和抽样命中信息;保存质检结果写入 `quality_reviews`,不覆盖历史;规则变更和质检保存均接入 `AuditAction` 元数据;权限使用 `quality.view``quality.manage``recordings.play`。未做 Dashboard 聚合,未扩展 S26 内容。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/quality/``apps/api/src/modules/recordings/recordings.controller.ts``apps/api/src/modules/recordings/recordings.repository.ts``apps/api/src/modules/recordings/recordings.service.ts``apps/api/src/modules/recordings/recordings.module.ts``apps/api/src/modules/recordings/recordings.service.spec.ts``docs/QUALITY_BACKEND_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用既有 NestJS/Fastify、Prisma `6.19.0`、Vitest `4.0.14`、TypeScript `5.9.3`
- 执行的关键命令:`corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/quality/sampling.spec.ts apps/api/src/modules/recordings/recordings.service.spec.ts``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:S25 聚焦测试 2 个文件 6 条通过,覆盖稳定抽样可重复、比例边界、录音列表抽样状态、保存质检输入校验和 S24 内部播放路径安全;全 workspace `typecheck``lint``build` 均通过
- 回滚方式:删除或恢复 `apps/api/src/modules/quality/`,将 `apps/api/src/modules/recordings/` 恢复到 S24 版本,从 `AppModule` 移除 `QualityModule`,重新执行 `corepack pnpm@10.33.0 build`;S25 未对远端数据库写入,无远端数据需清理;若后续已运行 API 并产生 `quality_reviews`,不直接删除历史记录,应通过审计可见的更正记录或受控维护窗口处理
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,本次未部署到 B 当前 API 服务;真实录音和 raw CDR 关联仍依赖 S28 端到端闭环产生;Dashboard 的待质检数量、趋势和聚合指标留给 S26。
- 下一任务:`S26 - Dashboard 聚合`
### 2026-06-21 20:20 - S24 录音搬运与播放
- 状态:已完成
- 操作服务器:A、B、本地;T 未操作
- 完成内容:新增 Recording Worker 搬运服务,支持扫描 A `/dev/shm/voip_rec/ready/**/*.ready`、SSH 私网拉取、文件大小校验、SHA-256、B 端 `.part-*` 临时文件原子改名、写入 `recordings` 表、校验成功后才删除 A 源 `.ready`;新增 `GET /api/v2/recordings/:id/play`,要求 `recordings.play` 权限,状态必须为 `READY`,通过 `X-Accel-Redirect` 转发到 Nginx 内部 `/_recordings/`,不暴露真实 `/data/recordings` 路径;A 端 finalizer ready 目录改为 `0770`,使专用拉取用户可在校验后删除源文件;B 建立专用拉取 key 和 known_hostsA 建立专用低权限用户 `lisglo-rec-pull` 并加入 `rtpengine` 组。未调整 SSH 管理端口,未禁止 SSH key 登录,未重启 OpenSIPS、RTPEngine、Nginx 或 API。
- 修改文件:`apps/worker-recording/src/main.ts``apps/worker-recording/src/transfer.ts``apps/worker-recording/src/transfer.spec.ts``apps/worker-recording/package.json``apps/worker-recording/tsconfig.json``apps/api/src/modules/app.module.ts``apps/api/src/modules/recordings/``pnpm-lock.yaml``infra/server-a/s19/scripts/lisglosips-recording-finalize``docs/RECORDING_TRANSFER_PLAYBACK_RUNBOOK.md``IMPLEMENTATION_STATUS.md`A 的 `/usr/local/sbin/lisglosips-recording-finalize``/var/lib/lisglo-rec-pull/.ssh/authorized_keys``/dev/shm/voip_rec/ready` 权限;B 的 `/etc/lisglosips/recording/``/data/recordings/s24-smoke/s24-smoke.wav`
- 安装软件及固定版本:未安装新软件;继续使用既有 OpenSSH、Node.js `v22.22.2`、pnpm `10.33.0`、Nginx `1.24.0-2ubuntu7.12`。新增系统用户 `lisglo-rec-pull`,未新增系统包。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run apps/worker-recording/src/transfer.spec.ts apps/api/src/modules/recordings/recordings.service.spec.ts``corepack pnpm@10.33.0 --filter @lisglosips/worker-recording build``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`A `bash -n /usr/local/sbin/lisglosips-recording-finalize`、创建 S24 测试 incoming 文件并运行 finalizerB 使用 `lisglo-recorder` 通过专用 key 从 A 拉取、校验、落盘、删除测试源文件。sudo 凭据通过 `.codex-private/` 读取并经 stdin 使用,未回显。
- 验证结果:本地 2 个测试文件 5 条测试通过,覆盖路径安全、`.ready` 转 storage key、复制/哈希/落盘/源删除、大小不匹配不删除源文件、API 内部路径穿越防护;`worker-recording` 单包 build、全 workspace `typecheck``lint``build` 均通过;A 测试文件从 `incoming` 进入 `ready`B 私网拉取到 `/data/recordings/s24-smoke/s24-smoke.wav`,大小 `31`,属主 `lisglo-recorder:lisglosips`SHA-256 为 `c72dd3909a317ad55817435b7ed5a0db6947455ffc66e09a2dde787fe4a8c348`,校验后 A 源 `.ready` 已删除;外部直接访问 `https://127.0.0.1/_recordings/s24-smoke/s24-smoke.wav` 返回 `404`Nginx 内部录音 location 未暴露。
- 回滚方式:A 备份目录 `/var/backups/lisglosips-s24/20260621T121130Z`,可恢复 `lisglosips-recording-finalize`、删除 `lisglo-rec-pull` 用户并将 `/dev/shm/voip_rec/ready` 恢复 `0750`B 备份目录 `/var/backups/lisglosips-s24/20260621T121100Z`,可删除 `/etc/lisglosips/recording``/data/recordings/s24-smoke`;代码回滚恢复本次新增/修改文件后重新 build。详见 `docs/RECORDING_TRANSFER_PLAYBACK_RUNBOOK.md`
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,尚未切换完整 monorepo API/Worker release,因此本次未启用正式 `lisglosips@recording-worker.service`,API 播放入口完成代码与构建但未在 B 当前 release 对外提供;真实通话录音字段仍依赖后续 S28 端到端路由/媒体闭环产生。
- 下一任务:`S25 - 质检后端`
### 2026-06-21 20:02 - B sudo 凭据复验
- 状态:已完成
- 操作服务器:B、本地;仅执行只读 SSH 与 sudo 校验,未修改服务器配置、未重启服务、未执行数据库写入或数据删除
- 完成内容:读取 `.codex-private/` 中 DPAPI 保存的 B 凭据且未回显密码;复验 B SSH Key 登录正常;确认 `hector` 属于 `sudo` 组;使用保存的 sudo 凭据执行 `sudo -v` 成功;使用 sudo 执行只读权限检查成功
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:无
- 执行的关键命令:`ssh -F .codex-private\ssh\config lisglosips-b ...`PowerShell `Import-Clixml .codex-private\SERVER_CREDENTIALS.clixml` 后通过 stdin 向远端 `sudo -S` 提供凭据,命令与输出均未包含明文密码
- 验证结果:B 返回主机名 `YanZi`,用户 `hector``sudo` 组;`sudo -v` 退出码为 0;只读 sudo 检查退出码为 0
- 回滚方式:无服务器变更无需回滚;如需撤销本地状态记录,恢复本文件本节和顶部阻塞说明
- 未解决问题:S13-S17 历史任务仍未补做 B 临时库冒烟;若后续补验会涉及数据库写入,应在执行前单独说明影响与回滚点
- 下一任务:`S24 - 录音搬运与播放`
### 2026-06-21 19:55 - S23 最小计费与余额扣减
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:在 `worker-cdr` 中接入最小计费服务,Redis Stream CDR 消费后写入 `raw_cdrs`、成功通话写入 `rated_cdrs`、按落地网关周期秒数和周期费率计算费用、扣减客户余额,并用负数 `customer_recharges` 记录不可变扣费流水;同一 `event_id` 或同一 `raw_cdr_id` 不重复扣费;失败 CDR、零时长 CDR、缺少客户或落地网关 ID 的 CDR 标记 `SKIPPED` 且不扣费。当前尚无客户侧费率表,S23 采用落地网关费率临时镜像为客户费用,供应商成本相同,毛利为 0,后续真实计费版本再扩展客户费率和利润计算。
- 修改文件:`apps/worker-cdr/src/main.ts``apps/worker-cdr/src/billing.ts``apps/worker-cdr/src/rating.ts``apps/worker-cdr/src/rating.spec.ts``apps/worker-cdr/package.json``apps/worker-cdr/tsconfig.json``apps/worker-cdr/dist/``docs/CDR_MINIMAL_BILLING_RUNBOOK.md``IMPLEMENTATION_STATUS.md`;本地因 `pnpm install` 受 registry DNS/metadata 影响,手工补齐 `apps/worker-cdr/node_modules/@lisglosips/database``packages/database` 的开发 junction
- 安装软件及固定版本:未安装新软件;继续使用既有 Node.js/pnpm、Prisma `6.19.0`、Vitest `4.0.14`、TypeScript `5.9.3`
- 执行的关键命令:`corepack pnpm@10.33.0 exec vitest run apps/worker-cdr/src/rating.spec.ts``corepack pnpm@10.33.0 --filter @lisglosips/worker-cdr build``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;尝试 `corepack pnpm@10.33.0 install``install --offline` 时被 npm registry DNS/本地 metadata 问题阻塞,未作为验收依据
- 验证结果:`apps/worker-cdr/src/rating.spec.ts` 3 条测试通过,覆盖周期计费、成功 CDR 扣费幂等、失败/零时长 CDR 不扣费;`worker-cdr` 单包 build 通过;全 workspace `typecheck``lint``build` 均通过
- 回滚方式:恢复 `apps/worker-cdr/src/main.ts` 到 S22 只记录 CDR 的版本,删除或恢复 S23 新增的 `billing.ts``rating.ts``rating.spec.ts`,恢复 `apps/worker-cdr/package.json``tsconfig.json` 后重新执行 `corepack pnpm@10.33.0 build`;S23 未执行真实数据库写入验证,无远端数据需要清理;若后续已运行 worker 并产生扣费,必须先停止 worker,再通过受控反向流水或备份恢复处理,不直接删除余额流水
- 未解决问题:B sudo 凭据仍不可用,本次未部署 B systemd worker、未对真实 B MySQL 执行写入式冒烟;当前最小计费暂时镜像落地网关费率为客户费用,客户费率表、利润规则、低余额拦截和 Dashboard 聚合留给后续任务;`pnpm install` 当前受 registry DNS/metadata 影响,后续若新增依赖需优先修复本地包管理器联网或缓存
- 下一任务:`S24 - 录音搬运与播放`
### 2026-06-21 19:34 - S22 CDR Redis Stream
- 状态:已完成
- 操作服务器:A、T、本地;B 仅通过 A 的 Redis AUTH proxy 写入 Redis Stream Consumer Group 元数据,未使用 B sudo,未改 B 服务
- 完成内容:新增 CDR Stream schema v1 和可靠消费库,支持 `stream:cdr_payload``stream:cdr_deadletter``billing-workers` Consumer Group、`XREADGROUP``XACK``XAUTOCLAIM` Pending 重领、死信写入和 `lock:cdr:{event_id}` 幂等占位;`worker-cdr` 从骨架改为 Redis Stream Worker,启动时确保 Consumer Group,循环先处理 Pending 再读取新消息,当前 handler 只接收/记录原始 CDR,不做计费和余额扣减;按多 A 后续演进预留 `node_id``opensips_instance``ingress_a_ip``rtpengine_node` 字段,当前单 A 值为 `a1``opensips-a1``100.90.90.90``a1`;升级 Server A OpenSIPS `route[S20_CDR_XADD]`,从 S20 冒烟 XADD 改为 S22 正式事件字段;在 Redis 创建 `billing-workers` Consumer Group。未调整 SSH 管理端口或密钥登录策略,未写入明文密码。
- 修改文件:`packages/redis/src/index.ts``packages/redis/src/cdr-stream.ts``packages/redis/src/cdr-stream.spec.ts``apps/worker-cdr/src/main.ts``packages/redis/dist/``apps/worker-cdr/dist/``infra/server-a/s22/opensips/opensips.cfg``docs/CDR_REDIS_STREAM_RUNBOOK.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`A 的 `/etc/opensips/opensips.cfg`Redis `stream:cdr_payload` Consumer Group `billing-workers`
- 安装软件及固定版本:未安装新系统软件;继续使用既有 Node.js/pnpm、OpenSIPS `3.6.7-1`、Redis `7.0.15`。新增代码复用既有 `ioredis 5.8.2`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run packages/redis/src/cdr-stream.spec.ts``corepack pnpm@10.33.0 lint/typecheck/build`A `opensips -C -f /etc/opensips/opensips.cfg``systemctl restart opensips`A 经本地 Redis proxy 执行 `XGROUP CREATE stream:cdr_payload billing-workers 0 MKSTREAM``XINFO GROUPS``XREVRANGE`T 执行 `/opt/lisglosips-s21/lisglosips-s21-sip.py invite --timeout 8`。sudo 凭据通过 `.codex-private/` 读取且未回显。
- 验证结果:Redis Stream 单元测试 6 条通过,覆盖发布/解析、Consumer Group 幂等创建、`XREADGROUP`/`XACK`、重复 `event_id` 幂等、死信、`XAUTOCLAIM` Pending 重领;`lint``typecheck`、全 workspace `build` 均通过;A OpenSIPS 配置语法检查通过并重启后 active;Redis `XINFO GROUPS stream:cdr_payload` 显示 `billing-workers`pending 为 0T 发 INVITE 后 A 返回 `SIP/2.0 503 Config Missing`,A 日志显示 CDR XADD 成功,Redis 最新事件包含 `schema_version=1``idempotency_key``node_id=a1``opensips_instance=opensips-a1``ingress_a_ip=100.90.90.90``rtpengine_node=a1``sip_code=503``hangup_reason=CONFIG_MISSING`。一次使用 S21 默认 3 秒超时的探针未收到响应,但 A 已写入 CDR;随后使用 8 秒超时复测收到预期 `503 Config Missing`
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s22/20260621T113157Z`,恢复其中 `opensips.cfg` 后执行 `opensips -C -f /etc/opensips/opensips.cfg` 并重启 `opensips`;代码回滚恢复本次新增/修改的 Redis/worker 文件后重新 buildRedis Consumer Group 可保留,停止 worker 即可,不删除 `stream:cdr_payload` 数据。详见 `docs/CDR_REDIS_STREAM_RUNBOOK.md`
- 未解决问题:S22 只完成原始 CDR Stream 契约和可靠消费基础,不写 MySQL raw/rated CDR、不计费、不扣余额,这些留给 S23;A 端当前仍未发布真实路由配置,因此测试 CDR 为 `CONFIG_MISSING` 失败事件;B sudo 凭据仍不可用,本次未执行 B 端服务部署或 systemd worker 验收;多 A 仅完成字段预留和 A1 默认值,真实 3A 部署留到单 A 闭环之后。
- 下一任务:`S23 - 最小计费与余额扣减`
### 2026-06-21 15:28 - S21 Server T 客户模拟配置
- 状态:已完成
- 操作服务器:T、本地;A 仅接收 T 发起的 SIP 探针,未写入;B 未操作
- 完成内容:在 Server T 建立 S21 客户模拟环境;新增测试域 `s21.lisglosips.test`;新增两套 SIP 注册测试账号 `s21-reg-1001``s21-reg-1002`OpenSIPS 改为 HA1 鉴权,`subscriber.password` 为空且不保存 SIP 明文密码;新增两套 IP 认证测试条目 `s21-ip-1001``s21-ip-1002` 指向 T `100.93.185.30/32`;部署 `/opt/lisglosips-s21` 自包含 SIP 脚本,支持注册成功、错误鉴权、向 A 发起 IP/SIP 模拟 INVITE、本地成功/忙/拒绝/超时场景和突发 CPS 探针;收敛 T 的 MI FIFO 权限为 `0660`TLS 私钥权限为 `0600`。未调整 SSH 管理端口或密钥登录策略,未写入明文密码。
- 修改文件:T 的 `/etc/opensips/opensips.cfg`、OpenSIPS `opensips` 数据库 `domain/subscriber/address/location` 测试数据、`/opt/lisglosips-s21/``/etc/lisglosips-s21/sip-accounts.env`;本地 `infra/server-t/s21/``docs/SERVER_T_CUSTOMER_SIMULATION_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 T 既有 OpenSIPS `3.6.6`、RTPEngine `11.5.1.18`、MariaDB `10.11.14` 和系统 Python 3。新增 SIP 测试脚本无第三方依赖。
- 执行的关键命令:`mysqldump --single-transaction --routines --events opensips``opensips -C -f /etc/opensips/opensips.cfg``systemctl restart opensips``mysql --protocol=socket -uroot``python3 -m py_compile``/opt/lisglosips-s21/s21-register-*``s21-local-*``s21-call-a-*``s21-over-cps-a`。sudo 凭据通过 `.codex-private/` 读取且未回显。
- 验证结果:备份目录 `/var/backups/lisglosips-s21/20260621T072316Z` 已生成并含 OpenSIPS 配置和数据库 dumpOpenSIPS 配置语法检查通过,`opensips.service` active`s21-register-primary``s21-register-secondary` 均先收到 `401 Unauthorized` challenge 后最终 `200 OK``s21-register-wrong-ha1` 最终 `401 Unauthorized``location` 表已有 `s21-reg-1001``s21-reg-1002` 注册位置;`subscriber.password` 长度为 0`ha1` 长度为 32;本地脚本分别返回 `200 OK``486 Busy Here``603 Decline``NO RESPONSE`;从 T 到 A 的 `s21-call-a-ip``s21-call-a-sip` 与 5 次 `s21-over-cps-a` 均稳定输出 Call-ID 并收到 A 当前预期的 `SIP/2.0 503 Config Missing`MI FIFO 为 `660 opensips:opensips`TLS 私钥文件为 `600 root:root`
- 回滚方式:停止 T 的 `opensips`,从 `/var/backups/lisglosips-s21/20260621T072316Z/opensips` 恢复 `/etc/opensips`,用该目录 `opensips.sql` 恢复 OpenSIPS 数据库,删除 `/opt/lisglosips-s21``/etc/lisglosips-s21`,执行 `opensips -C -f /etc/opensips/opensips.cfg` 后启动 `opensips`。详见 `docs/SERVER_T_CUSTOMER_SIMULATION_RUNBOOK.md`
- 未解决问题:A 端尚未发布真实客户/路由配置,因此 T 到 A 的 INVITE 当前按 S20 逻辑返回 `503 Config Missing`,真实路由、双向媒体、正式 CDR 和扣费留给 S22/S28T 的 RTPEngine 仍无内核加速,防火墙默认放行、Apache/rpcbind 暴露和克隆身份问题仍为历史风险,未在 S21 扩大处理。
- 下一任务:`S22 - CDR Redis Stream`
### 2026-06-21 15:05 - S20 Server A Redis 热路径与 HEP/指标
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:在 Server A 部署 Redis 热路径 Lua 原子检查,基于 `cfg:active_version` 和版本化 `cfg:v:{version}:*` 配置读取客户、客户网关、IP 认证、策略与线路组;OpenSIPS `INVITE` 接入 Lua 热路径,配置缺失返回 `503 Config Missing`Redis 不可用返回 `503 Redis Unavailable`,策略允许但路由未进入 S21/S28 前返回 `503 Routing Not Ready`;新增 CDR 冒烟 XADD 到 `stream:cdr_payload`;新增本地 Redis AUTH 代理,凭据通过 systemd encrypted credential 加载,OpenSIPS 只连接 `127.0.0.1:6380`;修正 HEP socket 为 `100.90.90.90:9061` 并发送至 B `100.90.90.91:9060`;新增 OpenSIPS Prometheus `script:` 指标和 Node Exporter textfile 指标;新增 S20 nftables 表阻断非本地 Redis 代理和非既有连接访问 HEP socket;未调整 SSH 管理端口或密钥登录策略,未写入明文密码。
- 修改文件:A 的 `/etc/opensips/opensips.cfg``/etc/opensips/lisglosips_hotpath.lua``/usr/local/sbin/lisglosips-redis-auth-proxy``/usr/local/sbin/lisglosips-redis-load-hotpath``/usr/local/sbin/lisglosips-opensips-preprocess``/usr/local/sbin/lisglosips-s20-metrics``/etc/systemd/system/lisglosips-redis-auth-proxy.service``/etc/systemd/system/lisglosips-redis-hotpath-load.service``/etc/systemd/system/opensips.service.d/20-lisglosips-s20.conf``/etc/systemd/system/lisglosips-s20-metrics.service``/etc/systemd/system/lisglosips-s20-metrics.timer``/etc/default/lisglosips-node-exporter``/etc/nftables.d/lisglosips-s20-observability.nft``/etc/systemd/system/lisglosips-a-firewall.service`;本地 `infra/server-a/s20/``docs/SERVER_A_REDIS_HEP_METRICS_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.7-1`、Redis Server B 既有 `7.0.15`、Node Exporter `1.7.0`、RTPEngine `11.5.1.18-1ubuntu1.2`。新增 systemd encrypted credential 文件 `/etc/credstore.encrypted/lisglosips-redis-password.cred`,未写明文凭据。
- 执行的关键命令:`opensips -C -f /etc/opensips/opensips.cfg``systemctl daemon-reload/enable/restart/start/stop``nft -c/-f``curl http://127.0.0.1:8888/metrics``curl http://100.90.90.90:9100/metrics`、从 T 发送 SIP `INVITE`A/B Redis RESP 冒烟,A `tcpdump` 观察 HEPB `journalctl -u heplify-server` 查询 HEP 统计。所有凭据均通过 `.codex-private/` 读取并已避免回显。
- 验证结果:A `systemctl is-system-running``running``opensips``lisglosips-redis-auth-proxy``lisglosips-s20-metrics.timer``lisglosips-node-exporter``lisglosips-a-firewall``rtpengine-daemon``rtpengine-recording-daemon` 均 activeS20 新增服务均 enabled`lisglosips-redis-hotpath-load.service` oneshot 结果为 `success`;监听端口为 `100.90.90.90:15060/udp``100.90.90.90:9061/udp``127.0.0.1:6380/tcp``127.0.0.1:8888/tcp``100.90.90.90:9100/tcp`S20 nftables 表存在;A 到 B Redis PING/AUTH 通过;无配置版本时 T 发 INVITE 返回 `SIP/2.0 503 Config Missing`,OpenSIPS 记录热路径拒绝且 CDR XADD 成功;临时 Redis 冒烟配置下 T 发 INVITE 返回 `SIP/2.0 503 Routing Not Ready`,返回字段含配置版本、客户网关、策略和线路组,测试 Redis keys 与 CDR 事件已清理;短暂停止 Redis 代理后 T 发 INVITE 返回 `SIP/2.0 503 Redis Unavailable`,日志明确记录 Redis 热路径不可用和 CDR XADD 失败,代理与 Lua loader 随后恢复;OpenSIPS Prometheus 暴露 `lisglosips_opensips_s20_*`B 可抓取 A Node Exporter textfile 指标 `lisglosips_redis_proxy_up`、录音 tmpfs、ready/part 文件数和 RTPEngine sessionsHEP 绑定修正后 A 侧抓包看到发往 B `9060/udp` 的 HEPB heplify 统计显示 HEP 收包且 Error 为 0,修正后 A 无新增 HEP 发送错误;S20 本地产物敏感扫描未发现明文凭据,命中的仅为 credential 名称或历史文档中的安全术语。
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s20/20260621T061900Z`,验证阶段的 OpenSIPS 配置副本位于该目录 `post-verify/` 下;回滚时停止 S20 metrics timer 和 Redis AUTH proxy,从备份恢复 `/etc/opensips/opensips.cfg`、S20 systemd/drop-in、Node Exporter 默认文件和 nftables 文件,执行 `opensips -C -f /etc/opensips/opensips.cfg` 后重启 `opensips``lisglosips-node-exporter``lisglosips-a-firewall`,不删除 Redis Stream 历史数据。详细步骤见 `docs/SERVER_A_REDIS_HEP_METRICS_RUNBOOK.md`
- 未解决问题:S20 只完成热路径检查、HEP 和指标,不实现真实落地路由、SIP 注册账号、双向媒体、正式 CDR 契约和计费;这些留给 S21/S22/S28。B sudo 凭据仍无法通过校验,本次 B 侧仅使用免 sudo 的只读 heplify 日志和已有服务凭据做验证;HOMER 数据库未稳定查到本次 OpenSIPS HEP Call-ID 行,但 heplify 已确认收包,后续 S28 端到端联调需继续核对 HOMER UI/库内 Call-ID。
- 下一任务:`S21 - Server T 客户模拟配置`
### 2026-06-21 14:00 - S19 Server A RTPEngine 与录音 tmpfs
- 状态:已完成
- 操作服务器:A、T、本地
- 完成内容:在 Server A 安装并固定 RTPEngine `11.5.1.18-1ubuntu1.2` 及 kernel DKMS/recording 组件;加载 `xt_RTPENGINE` 内核模块,DKMS 已为 `6.17.0-22-generic``6.17.0-35-generic` 构建;配置 RTPEngine 媒体地址 `100.90.90.90`、RTP `30000-40000/udp`、本地 NG/CLI/HTTP 控制口 `127.0.0.1:2223/2224/2225`;创建 `/dev/shm/voip_rec` 3 GiB tmpfs 与 `spool/incoming/ready/failed/tmp` 目录;部署录音 finalizer,按 `incoming -> ready/*.part -> ready/*.ready` 原子规则生成 ready 文件;新增 `lisglosips-a-firewall.service` 保障 S18/S19 nftables 表重启后自动加载;新增 OpenSIPS systemd drop-in 等待 Tailscale 地址后启动;覆盖 `rtpengine-recording-daemon.service`,移除包默认 NFS/rpcbind 依赖;停止并 mask S19 不使用的 `rpcbind.service/socket`。未调整 SSH 管理端口或密钥登录策略。
- 修改文件:A 的 `/etc/rtpengine/rtpengine.conf``/etc/rtpengine/rtpengine-recording.conf``/etc/default/rtpengine-daemon``/etc/default/rtpengine-recording-daemon``/etc/fstab``/etc/nftables.d/lisglosips-s19-rtpengine.nft``/etc/systemd/system/lisglosips-a-firewall.service``/etc/systemd/system/opensips.service.d/10-lisglosips-wait-tailscale.conf``/etc/systemd/system/rtpengine-recording-daemon.service``/etc/systemd/system/lisglosips-recording-finalize.service``/etc/systemd/system/lisglosips-recording-finalize.timer``/etc/tmpfiles.d/lisglosips-voip-rec.conf``/usr/local/sbin/lisglosips-recording-finalize`;本地 `infra/server-a/s19/``docs/SERVER_A_RTPENGINE_RECORDING_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:`rtpengine``rtpengine-daemon``rtpengine-recording-daemon``rtpengine-iptables``rtpengine-kernel-dkms``rtpengine-utils` 均为 `11.5.1.18-1ubuntu1.2` 并已 `apt-mark hold`;安装匹配内核 headers。未新增明文凭据。
- 执行的关键命令:`apt-get install``apt-mark hold``modprobe xt_RTPENGINE``dkms status``nft -c/-f``systemctl daemon-reload/enable/restart/reboot``systemd-tmpfiles --create``rtpengine-ctl -ip 127.0.0.1 -port 2224 list numsessions/interfaces``tcpdump`、从 T 发送 RTP UDP 与 SIP `OPTIONS`/`REGISTER` 测试包。
- 验证结果:A 二次重启后 `systemctl is-system-running``running` 且 0 failed unit`opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-recording-finalize.timer``lisglosips-a-firewall.service``lisglosips-node-exporter.service` 全部 active/enabled`/dev/shm/voip_rec` 为 3.0 GiB tmpfs,子目录均为 `rtpengine:rtpengine 0750``xt_RTPENGINE` 已加载,DKMS 状态 installed`rtpengine-ctl` 显示接口 `100.90.90.90` 和端口段 `30000 - 40000`nftables S18/S19 表重启后存在;从 T 到 A `30000/udp``40000/udp` 的 RTP 探针在 A `tailscale0` 抓包可见;从 T 发 SIP `OPTIONS` 返回 `SIP/2.0 200 Keepalive``REGISTER` 返回 `SIP/2.0 401 Authentication Required``WWW-Authenticate`;录音 finalizer 冒烟将测试 `.wav` 生成 `ready/...wav.ready`,权限 `0640`,无 `.part` 残留,测试文件已删除;本地 S19 产物敏感信息扫描未命中。
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s19/20260621T054645Z`;回滚时停止 `rtpengine-recording-daemon``lisglosips-recording-finalize.timer``rtpengine-daemon`,从备份恢复 `/etc/rtpengine/``/etc/default/``/etc/fstab` 等配置,删除 `inet lisglosips_s19_rtpengine` nftables 表,停用并删除 S19 systemd/tmpfiles/finalizer 文件,必要时 `apt-mark unhold` 后卸载 RTPEngine 包;详细步骤见 `docs/SERVER_A_RTPENGINE_RECORDING_RUNBOOK.md`
- 未解决问题:S19 尚未把 OpenSIPS 呼叫路由接入 RTPEngine,真实双向音频、NAT 媒体锚定和完整通话录音需在 S20/S21/S28 完成;Tailscale APT 源在 A 上仍有 DNS 解析失败历史但当前 Tailscale 网络和服务运行正常;B sudo 凭据阻塞仍未解决,不影响本次 A 端 S19。
- 下一任务:`S20 - Server A Redis 热路径与 HEP/指标`
### 2026-06-21 13:29 - S18 Server A OpenSIPS 基线
- 状态:已完成
- 操作服务器:A、本地
- 完成内容:在 Server A 添加 OpenSIPS 官方 APT 源并安装固定 OpenSIPS `3.6.7-1`;安装并 hold `opensips``opensips-cli``opensips-auth-modules``opensips-redis-module``opensips-http-modules``opensips-json-module``opensips-restclient-module``opensips-prometheus-module``opensips-tlsmgm-module``opensips-tls-module``opensips-tls-openssl-module`;部署 `/etc/opensips/opensips.cfg`OpenSIPS 仅监听 `100.90.90.90:15060/udp`HEP transport 占位监听 `127.0.0.1:9061/udp`MI HTTP 仅监听 `127.0.0.1:8888/tcp`;加载 `auth``pike``ratelimit``cachedb_redis``proto_hep``tracer``mi_http` 等模块;实现 S18 保守路由基线:`OPTIONS` 返回 `200 Keepalive``REGISTER` 返回带 Digest challenge 的 `401 Authentication Required``INVITE` 在 S20/S21 前返回 `503 Routing Not Ready`,避免开放中继;新增 `inet lisglosips_s18_opensips` nftables 表,只允许 T `100.93.185.30` 访问 A 的 UDP `15060`,丢弃其他 15060 来源,并阻断非本地 MI HTTP;未调整 SSH 管理端口或密钥登录策略。
- 修改文件:A 的 `/etc/apt/sources.list.d/opensips.list``/etc/apt/sources.list.d/opensips-cli.list``/etc/opensips/opensips.cfg``/etc/default/opensips``/etc/nftables.d/lisglosips-s18-opensips.nft`;本地 `infra/server-a/s18/``docs/SERVER_A_OPENSIPS_BASELINE_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:OpenSIPS `3.6.7-1`OpenSIPS CLI `0.4.0~20260522~570a9a9-1`;所有 S18 OpenSIPS 包已 `apt-mark hold`。未新增明文凭据。
- 执行的关键命令:`apt-get update``apt-get install``apt-mark hold``opensips -C -f /etc/opensips/opensips.cfg``systemctl enable --now/restart opensips``nft -c -f /etc/nftables.d/lisglosips-s18-opensips.nft``nft -f /etc/nftables.d/lisglosips-s18-opensips.nft``curl -X POST http://127.0.0.1:8888/mi`、从 T 发送 SIP `OPTIONS`/`REGISTER` 测试包。
- 验证结果:A 系统状态 `running``opensips.service` active/enabledOpenSIPS 配置语法检查通过;监听端口为 `100.90.90.90:15060/udp``127.0.0.1:9061/udp``127.0.0.1:8888/tcp`S06 的 `100.90.90.90:9100/tcp` 保持;MI HTTP JSON-RPC `version` 返回 `OpenSIPS (3.6.7 (x86_64/linux))`;从 T `100.93.185.30` 发往 A `15060/udp` 的 SIP `OPTIONS` 返回 `SIP/2.0 200 Keepalive`SIP `REGISTER` 返回 `SIP/2.0 401 Authentication Required` 且包含 `WWW-Authenticate`nftables S18 表加载成功;本地 S18 产物敏感信息扫描未命中。
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s18/20260621T051403Z`;回滚时执行 `systemctl stop opensips`,从备份恢复 `/etc/opensips/``/etc/default/opensips`,删除 `inet lisglosips_s18_opensips` nftables 表,必要时 `apt-mark unhold` 并卸载 S18 OpenSIPS 包;详细步骤见 `docs/SERVER_A_OPENSIPS_BASELINE_RUNBOOK.md`
- 未解决问题:Tailscale APT 源在 A 上一度 DNS 解析失败,但 Tailscale 服务和当前连接未受影响;A 仍存在 S01 记录的本地 ens18 双地址/路由历史风险,S18 的 OpenSIPS 绑定 Tailscale 地址规避了 SIP 监听歧义;当前 HEP trace 已有目标配置但未查询 B 的 HOMER 入库,完整 HEP/Redis 热路径和指标留给 S20RTPEngine、RTP 端口和录音 tmpfs 留给 S19。
- 下一任务:`S19 - Server A RTPEngine 与录音 tmpfs`
### 2026-06-21 13:06 - S17 落地线路组 API
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 LandingLineGroupsModule,提供 `GET /landing-line-groups``GET /landing-line-groups/:id``POST /landing-line-groups``PATCH /landing-line-groups/:id``POST /landing-line-groups/:id/enable``POST /landing-line-groups/:id/disable``DELETE /landing-line-groups/:id``POST /landing-line-groups/:id/items``PATCH /landing-line-groups/items/:itemId``DELETE /landing-line-groups/:id/items/:gatewayId``POST /landing-line-groups/:id/items/reorder`;支持线路组启停、软删除、成员增删改、成员优先级重排、权重、成员并发上限和成员状态;返回 `itemCount``enabledItemCount``concurrencyCapSum``gatewayConcurrencyLimitSum``policyCount`,用于组内并发汇总与引用可见性;新增引用校验,成员只能引用未删除落地网关,软删除线路组前阻止仍被未删除客户网关策略引用的线路组删除;读操作绑定 `line_groups.view`,写操作绑定 `line_groups.manage` 和审计动作;线路组与成员变更写入 `line_group_config` OutboxConfig Publisher 扩展线路组快照并发布 `cfg:v:{version}:line_group:*``line_group:*:items`
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/landing-line-groups/``apps/worker-config-publisher/src/publisher.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- landing-line-groups``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:typecheck 通过;S17 相关 Vitest 14 个测试文件 44 条测试通过,其中 `landing-line-groups.e2e.spec.ts` 覆盖线路组列表并发汇总、写权限拒绝、创建、成员新增/更新/删除、成员参数校验、优先级重排、启停、软删除和审计记录;全量 Vitest 14 个测试文件 44 条测试通过;lint 通过;workspace build 通过,包含 `apps/worker-config-publisher` 构建通过。
- 回滚方式:移除 `LandingLineGroupsModule` 导入并删除 `apps/api/src/modules/landing-line-groups/`;恢复 `apps/worker-config-publisher/src/publisher.ts` 中 S17 新增的 `line_group_config` Outbox 处理和线路组快照发布逻辑。S17 未新增数据库迁移,B 端未创建临时库、未改服务、未写 Redis 或 MySQL。
- 未解决问题:B 写入式临时库冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;S17 不实现 OpenSIPS 呼叫路由或 Server A 配置,这些从 S18 开始。
- 下一任务:`S18 - Server A OpenSIPS 基线`
### 2026-06-21 11:57 - S16 落地网关 API
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 VendorGatewaysModule,提供 `GET /vendor-gateways``GET /vendor-gateways/:id``POST /vendor-gateways``PATCH /vendor-gateways/:id``POST /vendor-gateways/:id/enable``POST /vendor-gateways/:id/disable`;支持供应商落地网关 `IP``SIP_DIGEST``MIXED` 三种认证模式,创建和更新时校验 host/port/transport、SIP 用户名与密码强度,并以 `username:host:password` 生成 HA1API 响应只返回 `hasSipCredential` 不返回明文密码或 HA1;支持 CPS、并发上限、禁呼时段、编码优先级、主叫/被叫号码前缀转换、计费周期秒数和周期费率,响应补充按分钟折算的 `minuteRate`;写操作绑定 `vendor_gateways.manage` 和审计动作,读操作绑定 `vendor_gateways.view`;落地网关创建、更新、启停写入 `vendor_gateway_config` OutboxConfig Publisher 扩展供应商落地网关快照并发布到 Redis 版本化配置。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/vendor-gateways/``apps/worker-config-publisher/src/publisher.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- vendor-gateways``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:typecheck 通过;S16 相关 Vitest 13 个测试文件 40 条测试通过,其中 `vendor-gateways.e2e.spec.ts` 覆盖列表脱敏、写权限拒绝、完整 SIP Digest 网关创建、审计敏感字段脱敏、切换 IP 模式清空 SIP 凭据、重复编码优先级校验和启停;全量 Vitest 13 个测试文件 40 条测试通过;lint 通过;workspace build 通过,包含 `apps/worker-config-publisher` 构建通过。
- 回滚方式:移除 `VendorGatewaysModule` 导入并删除 `apps/api/src/modules/vendor-gateways/`;恢复 `apps/worker-config-publisher/src/publisher.ts` 中 S16 新增的 `vendor_gateway_config` Outbox 处理和落地网关快照发布逻辑。S16 未新增数据库迁移,B 端未创建临时库、未改服务、未写 Redis 或 MySQL。
- 未解决问题:B 写入式临时库冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;S16 不实现落地线路组成员、优先级重排或并发汇总,这些留给 S17。
- 下一任务:`S17 - 落地线路组 API`
### 2026-06-21 11:45 - S15 供应商管理 API
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 VendorsModule,提供 `GET /vendors``GET /vendors/:id``POST /vendors``PATCH /vendors/:id``POST /vendors/:id/enable``POST /vendors/:id/disable``DELETE /vendors/:id`;供应商返回余额、授信额度、可用余额和落地网关数量;余额保持只读,供应商充值仍通过 S12 的不可变充值流水;支持联系人、电话、邮箱、结算方式、备注、授信额度和启停状态维护;软删除前阻止仍有关联未删除落地网关的供应商删除;读操作绑定 `vendors.view`,写操作绑定 `vendors.manage` 和审计动作。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/vendors/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- vendors``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:typecheck 通过;S15 相关 Vitest 12 个测试文件 36 条测试通过;全量 Vitest 12 个测试文件 36 条测试通过,覆盖供应商余额/授信/网关数量读取、写权限拒绝、新增、编辑、启停、软删除和审计记录;lint 通过;workspace build 通过。
- 回滚方式:移除 `VendorsModule` 导入并删除 `apps/api/src/modules/vendors/`;S15 未新增数据库迁移。B 端未创建临时库、未改服务、未写 Redis 或 MySQL。
- 未解决问题:B 写入式临时库冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;S15 不实现落地网关、费率、CPS/并发或号码转换,这些留给 S16。
- 下一任务:`S16 - 落地网关 API`
### 2026-06-21 11:39 - S14 客户网关策略与 Redis 配置发布
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 CustomerGatewayPoliciesModule,提供 `GET /customer-gateways/:id/policies``POST /customer-gateways/:id/policies``PATCH /customer-gateway-policies/:id``DELETE /customer-gateway-policies/:id``POST /customer-gateways/:id/policies/reorder`;支持主叫/被叫 `ANY``EQUALS``PREFIX` 匹配,校验非 `ANY` 模式必须提供匹配值;支持显式优先级和自动追加优先级,重排时要求一次提交全部有效策略 ID 并用两阶段负优先级避免唯一索引冲突;客户网关创建、更新、启停以及策略创建、更新、删除、重排均在同一事务内写入 `outbox_events`,不直接双写 RedisConfig Publisher 从骨架升级为处理 `customer_gateway_config` Outbox 的 worker,生成完整 Redis 配置快照,写入 `cfg:v:{version}:customer:*``customer_gateway:*``auth:ip:*``auth:sip:*` 和策略列表,原子切换 `cfg:active_version`,并维护 `cfg:previous_version` 作为回滚点;新增 `rollbackActiveConfig` 函数用于切回上一版本。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/customer-gateways/customer-gateways.repository.ts``apps/api/src/modules/customer-gateway-policies/``apps/worker-config-publisher/src/main.ts``apps/worker-config-publisher/src/publisher.ts``apps/worker-config-publisher/package.json``apps/worker-config-publisher/tsconfig.json``packages/redis/src/index.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增需要联网下载的 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`、ioredis `5.8.2`(由 `@lisglosips/redis` 继续提供)。
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- customer-gateway``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;曾尝试 `corepack pnpm@10.33.0 install`,因本机 DNS 无法解析 `registry.npmjs.org` 失败,随后移除新增联网依赖,最终无需更新锁文件。
- 验证结果:typecheck 通过;S14 相关 Vitest 11 个测试文件 33 条测试通过;全量 Vitest 11 个测试文件 33 条测试通过,覆盖策略列表、权限拒绝、策略创建/更新/重排/删除、审计记录和匹配值校验;lint 通过;workspace build 通过,包含 `apps/worker-config-publisher` 构建通过。
- 回滚方式:移除 `CustomerGatewayPoliciesModule` 导入并删除 `apps/api/src/modules/customer-gateway-policies/`;恢复 `apps/api/src/modules/customer-gateways/customer-gateways.repository.ts` 中的 Outbox 写入前版本;恢复 `apps/worker-config-publisher/src/main.ts` 为骨架并删除 `publisher.ts`;恢复 `packages/redis/src/index.ts` 的 S14 新增 Redis 常量和 helper。S14 未新增数据库迁移,B 端未创建临时库、未改服务、未写 Redis。
- 未解决问题:B 写入式临时库/Redis 冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;Config Publisher 当前完成客户/客户网关/策略快照发布,落地网关、线路组成员和供应商侧热路径会随 S15-S17 数据模型 API 补齐后扩展到快照。
- 下一任务:`S15 - 供应商管理 API`
### 2026-06-21 11:28 - S13 客户网关 API
- 状态:已完成
- 操作服务器:本地;B 仅读取 SSH/Node/pnpm 状态并尝试临时库验收,未修改正式库或服务
- 完成内容:新增 CustomerGatewaysModule,提供 `GET /customer-gateways``GET /customer-gateways/:id``POST /customer-gateways``PATCH /customer-gateways/:id``POST /customer-gateways/:id/enable``POST /customer-gateways/:id/disable`;支持 `IP``SIP_DIGEST``MIXED` 三种认证模式;创建和更新时校验来源 IP、SIP 用户名、SIP 域和密码强度;SIP 注册密码仅用于生成 OpenSIPS Digest HA1API 响应只返回 `hasSipCredential`,不返回 `sipPassword``sipHa1`;SIP 用户名或域名变更时要求同时提交新密码,避免旧 HA1 与新身份不匹配;所有写操作绑定 `customer_gateways.manage` 和审计动作,读操作绑定 `customer_gateways.view`;日志脱敏路径新增 `sipHa1`
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/customer-gateways/``packages/observability/src/index.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`B 端执行只读 `hostname && node --version && pnpm --version`;尝试通过 SSH 隧道连接 B MySQL 并用 `MYSQL_MIGRATE` 创建 S13 临时库,因账号无 `mysql` 系统库访问权限返回 `P1010`;B sudo 凭据校验失败,未继续执行需要 sudo 的 MySQL 创建/清理命令。
- 验证结果:本地 typecheck 通过;Vitest 10 个测试文件 29 条测试通过,新增覆盖客户网关只读权限、写权限拒绝、IP 网关返回脱敏、SIP Digest 网关创建、审计敏感字段脱敏、SIP 身份变更强制新密码、切换 IP 模式清空 SIP 凭据、启停接口;lint 通过;workspace build 通过;B 的 Node `v22.22.2` 和 pnpm `10.33.0` 只读确认通过;B 临时库冒烟未完成,未创建临时库,SSH 隧道、本地 S13 临时 SQL 和远端 `/tmp/lisglosips-s13-create-*.sql` 已清理。
- 回滚方式:移除 `CustomerGatewaysModule` 导入并删除 `apps/api/src/modules/customer-gateways/`;从 `packages/observability/src/index.ts` 移除 `*.sipHa1` 脱敏项;S13 未新增数据库迁移。B 端未创建临时库、未修改服务,仅使用短时 SSH 隧道和 `/tmp` 临时 SQL 文件,已清理。
- 未解决问题:S13 未执行 B 临时库冒烟,因为当前 `.codex-private` 中的 B sudo 凭据无法通过校验,`MYSQL_MIGRATE` 也无权在 `mysql` 系统库创建临时库;后续需要修复 B sudo 凭据或提供受控临时库创建账号。S13 不实现客户网关策略、Outbox 或 Redis 发布,这些留给 S14。
- 下一任务:`S14 - 客户网关策略与 Redis 配置发布`
### 2026-06-21 11:14 - S12 充值与不可变余额流水
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:新增 RechargesModule,提供 `POST /customers/:id/recharges``POST /vendors/:id/recharges``GET /recharges`;客户和供应商充值在同一数据库事务内完成账户行锁定、余额读取、不可变充值流水写入、余额更新和幂等结果保存;重复相同幂等键返回原充值结果,不重复加余额;同一幂等键不同请求返回冲突;充值写操作绑定 `recharges.manage` 和审计动作,查询绑定 `recharges.view`;并发写冲突和唯一键竞争只对 Prisma `P2034`/`P2002` 做短退避重试。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/recharges/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;B 端通过 SSH 隧道连接临时 MySQL 库执行 `prisma migrate deploy``db:seed``prisma migrate status`,并使用编译后的充值仓库执行客户/供应商充值、幂等重放、幂等冲突和并发充值冒烟。
- 验证结果:本地 typecheck 通过;Vitest 9 个测试文件 24 条测试通过,覆盖充值权限拒绝、客户充值、供应商充值、幂等重放、幂等冲突、流水查询和审计记录;lint 通过;workspace build 通过;B 临时空库 `lisglosips_s12_verify_20260621111322` 成功应用 S08/S09 共 2 个迁移,seed 执行成功,`migrate status` 显示 schema up to date;B 临时库验证客户充值前后余额、供应商充值前后余额、重复幂等不重复入账、幂等冲突拒绝、5 路并发客户充值后余额 `15.000000`、客户流水 6 条、供应商流水 1 条、成功幂等记录 7 条;B 残留检查确认无 S12 临时库、临时用户和 `/tmp/lisglosips-s12-*` 文件。
- 回滚方式:移除 `RechargesModule` 导入并删除 `apps/api/src/modules/recharges/`;S12 未新增数据库迁移。B 端仅创建临时库、临时用户和 SSH 隧道,已删除或关闭。
- 未解决问题:S12 只实现手工充值入账;CDR 扣费、余额扣减和扣费幂等留给 S22/S23;供应商管理 CRUD 仍按任务顺序留给 S15。
- 下一任务:`S13 - 客户网关 API`
### 2026-06-21 10:57 - S11 客户管理 API
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:新增 CustomersModule,提供客户列表、详情、新增、编辑、启用、停用和软删除 API;返回余额、授信额度、最低余额、可用余额和启用网关数量;客户余额只读,避免绕过 S12 充值流水;所有写操作均绑定 `customers.manage` 和审计动作,读操作绑定 `customers.view`;软删除前阻止仍有关联启用网关的客户删除。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/customers/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;B 端通过 SSH 隧道连接临时 MySQL 库执行 `prisma migrate deploy``db:seed``prisma migrate status` 和客户 CRUD 冒烟脚本。
- 验证结果:本地 typecheck 通过;Vitest 8 个测试文件 21 条测试通过,覆盖客户读权限、写权限拒绝、新增、启停、软删除、余额/授信字段和审计记录;lint 通过;workspace build 通过;B 临时空库 `lisglosips_s11_verify_20260621105537` 成功应用 S08/S09 共 2 个迁移,seed 执行成功,`migrate status` 显示 schema up to dateB 临时库客户创建、状态更新、Decimal 精度、网关计数和软删除冒烟通过;B 残留检查确认无 S11 临时库、临时用户和 `/tmp/lisglosips-s11-*` 文件。
- 回滚方式:移除 `CustomersModule` 导入并删除 `apps/api/src/modules/customers/`;S11 未新增数据库迁移。B 端仅创建临时库、临时用户和 SSH 隧道,已删除或关闭。
- 未解决问题:S11 不实现充值入账和余额变更事务,余额仍只能通过后续 S12 不可变流水更新;客户网关数量当前依赖后续 S13 的网关数据。
- 下一任务:`S12 - 充值与不可变余额流水`
### 2026-06-21 10:46 - S10 用户、角色、权限与审计
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:实现全局 Access Token 认证 Guard、RBAC Guard、`@Public``@RequirePermissions` 与当前用户注入;新增用户、角色、权限和审计日志查询 API;实现用户创建、资料更新、重置密码和角色权限维护;内置角色禁止改名、停用和变更权限;重置密码仅保存 Argon2id 哈希并要求下次改密;新增审计拦截器,按接口元数据记录 request_id、操作者、角色、IP、User-Agent、模块、动作、对象、成功/失败、错误码和脱敏后的 before/after 摘要。
- 修改文件:`packages/auth/src/index.ts``packages/database/src/index.ts``apps/api/src/modules/app.module.ts``apps/api/src/modules/auth/auth.controller.ts``apps/api/src/modules/health/health.controller.ts``apps/api/src/modules/security/``apps/api/src/modules/audit/``apps/api/src/modules/audit-logs/``apps/api/src/modules/users/``apps/api/src/modules/roles/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;B 端通过 SSH 隧道连接临时 MySQL 库执行 `prisma migrate deploy``db:seed``prisma migrate status`B 端检查并清理 `lisglosips_s10_verify_*``s10v_*``/tmp/lisglosips-s10-*` 临时残留。
- 验证结果:本地 typecheck 通过;Vitest 7 个测试文件 18 条测试通过,覆盖未认证拒绝、越权拒绝、重置密码审计脱敏、内置角色保护和审计日志查询;lint 通过;workspace build 通过;B 临时空库 `lisglosips_s10_verify_20260621104300` 成功应用 S08/S09 共 2 个迁移,seed 执行成功,`migrate status` 显示 schema up to dateB 残留检查确认无 S10 临时库、临时用户和 `/tmp/lisglosips-s10-*` 文件。
- 回滚方式:恢复上述本地文件到 S10 前版本;S10 未新增数据库迁移,若需回滚只需移除新增模块导入和相关接口代码。B 端仅创建临时库、临时用户和 `/tmp` 临时文件,已删除。
- 未解决问题:S10 只完成后端安全与审计基础;后续业务 API 必须继续显式标注权限和审计动作,客户、充值、网关等业务模块从 S11 开始实现。
- 下一任务:`S11 - 客户管理 API`
### 2026-06-21 10:15 - S09 登录认证基础
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:实现登录、刷新和退出接口;新增 Argon2id 密码哈希与校验,优先使用 Node 原生实现并在 Node 22 上回退到 vendored WASM;实现 HMAC access token、HttpOnly refresh cookie、refresh token SHA-256 摘要入库、会话撤销与轮换、失败计数和临时锁定;新增 Prisma `user_sessions` 模型和迁移;补充 Auth API、服务层、密码/Token 原语和 schema 合同测试。
- 修改文件:`packages/auth/src/index.ts``packages/auth/src/index.spec.ts``packages/auth/vendor/argon2id/``apps/api/src/modules/auth/``apps/api/src/modules/database/``apps/api/src/modules/app.module.ts``apps/api/src/shared/config.ts``apps/api/tsconfig.json``apps/api/package.json``.env.example``eslint.config.mjs``prisma/schema.prisma``prisma/migrations/20260621093000_auth_sessions/migration.sql``packages/database/src/schema-contract.spec.ts``pnpm-lock.yaml``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;vendored npm 包 `argon2id` `1.0.1`,MIT 许可证,WASM 哈希记录于 `packages/auth/vendor/argon2id/UPSTREAM.md`;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 prisma:validate``prisma:generate``typecheck``test``lint``build`B 端通过临时 `/tmp` 包验证 Node 22 下 Argon2id WASM 哈希与校验;通过 SSH 隧道连接 B 的临时 MySQL 库执行 `prisma migrate deploy``prisma migrate status`
- 验证结果:本地 Prisma schema validate 通过;Prisma Client 生成通过;typecheck 通过;Vitest 6 个测试文件 15 条测试通过,覆盖 Argon2id、Token、登录成功、失败锁定、refresh 轮换、旧 refresh 拒绝、logout 后拒绝和健康检查;lint 通过;workspace build 通过;B Node `v22.22.2` 上 Argon2id WASM 校验结果为正确值通过、随机值拒绝;B 临时空库 `lisglosips_s09_verify_20260621101249` 成功应用 S08 与 S09 共 2 个迁移,`migrate status` 显示 schema up to date;B 残留检查确认临时库、临时用户和 `/tmp/lisglosips-s09-*` 文件已清理。
- 回滚方式:恢复上述本地文件到 S09 前版本;删除 `prisma/migrations/20260621093000_auth_sessions/` 并从 `prisma/schema.prisma` 移除 `UserSession` 关系和模型。B 端仅创建临时库、临时用户和 `/tmp` 临时文件,已删除。
- 未解决问题:生产 access token secret 必须由受控环境变量注入;当前 S09 只实现认证基础,RBAC Guard、内置角色保护、重置密码和审计拦截器留给 S10。
- 下一任务:`S10 - 用户、角色、权限与审计`
### 2026-06-21 09:28 - S08 数据库 Schema 与迁移
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:实现 V2 第 10 章核心数据模型,包括客户、客户网关、策略、供应商、落地网关、线路组、充值、CDR、录音、质检、用户、角色、权限、审计、Outbox 和幂等键;生成初始化迁移;新增内置权限/角色 seed;补充数据库 Schema Runbook 和 schema 合同测试;迁移 Prisma seed 配置到 `prisma.config.ts`
- 修改文件:`prisma/schema.prisma``prisma/migrations/20260621090000_init_v2_schema/migration.sql``prisma/seed.ts``prisma.config.ts``package.json``packages/database/src/schema-contract.spec.ts``docs/DATABASE_SCHEMA_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Prisma `6.19.0`、MySQL `8.0.46`、pnpm `10.33.0`
- 执行的关键命令:`prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script``corepack pnpm@10.33.0 prisma:generate``prisma:validate``typecheck``test``lint``build`;通过 SSH 隧道连接 B 的临时 MySQL 库执行 `prisma migrate deploy``prisma db seed`、重复 seed、重复 migrate deploy、`prisma migrate status`;使用 `mysqldump --no-tablespaces` 做临时库备份恢复验证。
- 验证结果:本地 Prisma schema validate 通过;Prisma Client 生成通过;lint 通过;typecheck 通过;Vitest 3 个测试文件 6 条测试通过;workspace build 通过;B 上临时空库 `lisglosips_s08_verify_20260621091537` 成功应用 1 个迁移;seed 重复执行安全;重复 `migrate deploy` 显示无待迁移;`migrate status` 显示 schema up to date;备份恢复后计数为 `permissions=22``roles=5``role_permissions=53``_prisma_migrations=1`
- 回滚方式:恢复上述本地文件到 S08 前版本;删除 `prisma/migrations/20260621090000_init_v2_schema/`。B 端仅创建临时库和临时用户,已删除 `lisglosips_s08_verify_*``s08v_*``/tmp/lisglosips-s08-*.sh` 和临时 dump 文件。
- 未解决问题:生产/准生产迁移仍需在正式发布窗口前做全量备份和恢复演练;本次只验证临时空库初始化、seed 幂等和 dump/restore。
- 下一任务:`S09 - 登录认证基础`
### 2026-06-21 08:55 - S07 V2 后端 Monorepo 骨架完成
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:恢复 B 开机后完成 S07 的 B 端构建验收;补充 `scripts/pnpm-run.mjs`,让根脚本复用当前 pnpm 运行器,兼容本机 Corepack 调用和 B 上已安装的 `/usr/bin/pnpm`;在 `package.json` 中声明允许固定依赖的 pnpm build scripts,并加入 Linux x64 glibc optional dependency 支持,便于 B 端验证 Rollup/esbuild/Prisma。
- 修改文件:`package.json``pnpm-lock.yaml``scripts/pnpm-run.mjs``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未在 B 安装新系统软件;B 使用既有 Node.js `v22.22.2`、Corepack `0.34.6`、pnpm `10.33.0`。本地和 B 的项目依赖仍按 `pnpm-lock.yaml` 固定。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 lint/typecheck/test/prisma:validate/build`B `/tmp/lisglosips-s07-20260621081708` 中执行 `pnpm install --frozen-lockfile`、离线 store 安装、`pnpm lint``pnpm typecheck``pnpm test``pnpm prisma:validate``pnpm build`、临时 API `node apps/api/dist/main.js` 健康检查。
- 验证结果:本地 lint、typecheck、Vitest 2 个测试文件 3 条测试、Prisma schema validate、workspace build 全部通过;B 端 lint、typecheck、Vitest 2 个测试文件 3 条测试、Prisma schema validate、workspace build 全部通过;B 本机临时 API `/api/v2/health/live` 返回 `ok``/api/v2/health/ready` 返回 `ok``/api/v2/openapi.json` 返回 HTTP 200。
- 回滚方式:恢复本次新增/修改的 monorepo 文件;如需恢复旧前端结构,将 `apps/web/src` 移回根目录 `src``apps/web/index.html` 移回根目录,并恢复旧 `package.json`/`package-lock.json`。B 端仅使用 `/tmp` 临时目录和临时 pnpm store,已删除 `/tmp/lisglosips-s07-*``/tmp/lisglosips-s07-src-*.zip``/tmp/lisglosips-s07-pnpm-store-v10.tar.gz``/tmp/rollup-linux-x64-gnu-4.62.2.tgz` 及 API 冒烟临时文件。
- 未解决问题:B 到 npm registry 的 TLS 连接不稳定,在线安装多次 `ECONNRESET`;本次通过本地 pnpm store 临时上传完成 B 端构建验收。后续正式部署建议使用内网 npm 缓存、制品仓库或在 CI 产出完整 release artifact。
- 下一任务:`S08 - 数据库 Schema 与迁移`
### 2026-06-21 08:10 - S07 V2 后端 Monorepo 骨架
- 状态:已阻塞
- 操作服务器:本地;Server B 仅尝试临时源码包上传和只读 SSH 连通性检查,未确认远端执行
- 完成内容:将现有 React Demo 纳入 pnpm monorepo 的 `apps/web`;创建 `apps/api` NestJS + Fastify 后端骨架;创建 `apps/worker-cdr``apps/worker-recording``apps/worker-config-publisher` worker 入口;创建 `packages/contracts``domain``database``redis``auth``observability` 公共包;建立 Prisma MySQL schema 占位、结构化日志脱敏、配置校验、OpenAPI JSON/Swagger 入口、健康检查、Vitest/Supertest 测试和 lint/typecheck/build/ci 命令。
- 修改文件:`package.json``pnpm-workspace.yaml``pnpm-lock.yaml``tsconfig.base.json``tsconfig.build.json``eslint.config.mjs``vitest.config.ts``.env.example``.gitignore``apps/``packages/``prisma/schema.prisma`;原 `src/``index.html` 已移动到 `apps/web/`
- 安装软件及固定版本:本地通过 Corepack 使用 pnpm `10.33.0`;关键依赖固定包括 NestJS `11.1.9`、Fastify `5.6.2`、Prisma `6.19.0`、ioredis `5.8.2`、pino `9.14.0`、Vitest `4.0.14`、TypeScript `5.9.3`、Vite `5.4.21`
- 执行的关键命令:`corepack pnpm@10.33.0 install``corepack pnpm@10.33.0 prisma:generate``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 build`、本地 API `node apps/api/dist/main.js` 冒烟检查。
- 验证结果:本地 `lint` 通过;`typecheck` 通过;Vitest 2 个测试文件 3 条测试通过;Prisma schema validate 通过;全 workspace build 通过;本地 API 冒烟 `/api/v2/health/live` 返回 `ok``/api/v2/health/ready` 返回 `ok``/api/v2/openapi.json` 返回 HTTP 200;测试和脚本只使用无密码占位连接串。
- 回滚方式:恢复本次新增/修改的 monorepo 文件;如需恢复旧前端结构,将 `apps/web/src` 移回根目录 `src``apps/web/index.html` 移回根目录,并恢复旧 `package.json`/`package-lock.json`
- 未解决问题:未完成 B 端构建验收。本机到 `100.90.90.91:22` SSH 超时;本机无 `tailscale` CLI`scp` 曾尝试上传 `/tmp/lisglosips-s07-src-20260621080628.zip`,返回码为 0 但随后提示连接关闭,因 SSH 不通无法确认远端是否存在该临时文件,恢复连通后应检查并删除。
- 下一任务:恢复 Server B SSH/Tailscale 可达性后继续完成 `S07 - V2 后端 Monorepo 骨架` 的 B 端构建验收;不要进入 S08。
### 2026-06-21 00:20 - SSH 策略文档修订
- 状态:已完成
- 操作服务器:本地
- 完成内容:将 SSH 加固原则调整为“只调整 SSH 管理端口和允许来源,不禁止 SSH 密钥登录”;端口号统一使用 `<SSH_ADMIN_PORT>` 占位,待用户确认具体端口后再实施。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md``docs/SSH_ACCESS_RUNBOOK.md``docs/LISGLOSIPS_DEPLOYMENT_GUIDE.md``docs/infra-check.md``docs/SERVER_B_BASELINE_RUNBOOK.md`
- 安装软件及固定版本:无。
- 执行的关键命令:`rg` 文档复查。
- 验证结果:需求/部署文档中已无“仅 Key 登录”“禁用密码登录”作为强制策略的表述;历史盘点文件保留当时实际 `TCP 22` 快照,`docs/infra-check.md` 中历史验收项已标注后续迁移为 `<SSH_ADMIN_PORT>`
- 回滚方式:恢复上述文档至本次修改前版本。
- 未解决问题:尚未确认 `<SSH_ADMIN_PORT>` 的具体端口号,未执行服务器侧 SSH 端口迁移。
- 下一任务:`S07 - V2 后端 Monorepo 骨架`
### 2026-06-20 23:20 - S06 Server B HOMER 与基础监控
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:在 B 部署 PostgreSQL 16 HOMER 数据库、Heplify HEP 接收、Homer App、Prometheus、Grafana、MySQL/PostgreSQL/Redis/Node/Heplify exporters 和 Nginx 监控入口;在 A 部署 Node Exporter,并用独立 nftables 表限制仅 B 可抓取;重新签发开发证书,增加 `grafana.lisglosips.local``homer.lisglosips.local`;补充 S06 Runbook 和可重复部署脚本。
- 修改文件:B 的 `/etc/homer/``/etc/lisglosips/secrets/``/etc/prometheus/``/etc/grafana/``/etc/nginx/sites-available/monitoring.conf``/etc/systemd/system/` S06 服务、`/data/homer/postgresql`A 的 `/etc/nftables.d/lisglosips-s06-exporter.nft``/etc/systemd/system/lisglosips-exporter-firewall.service``/etc/systemd/system/lisglosips-node-exporter.service`;本地 `infra/server-b/s06/``infra/server-a/s06/``docs/SERVER_B_HOMER_MONITORING_RUNBOOK.md``docs/LISGLOSIPS_DEPLOYMENT_GUIDE.md``.codex-private/SERVICE_CREDENTIALS.clixml`
- 安装软件及固定版本:PostgreSQL `16.14`、Prometheus `2.45.3`、Grafana `13.0.2`、Node Exporter `1.7.0`、MySQL Exporter `0.15.0`、PostgreSQL Exporter `0.15.0`、Redis Exporter `1.86.0`Heplify Server `v1.60.2-0.20260512101233-c74dc3d216ac`Homer App `v0.0.0-20251021161517-9b1336352aa0`Docker `29.1.3` 与 Compose `2.40.3` 已安装但禁用,不作为 S06 运行时。
- 执行的关键命令:`apt-get install`、Go `install/build``psql``mysql``redis-cli``promtool check config/rules``nginx -t``systemctl daemon-reload/enable/restart/reboot``curl``nft -c/-f`、HEPv3 UDP 测试脚本。
- 验证结果:B 重启后系统 `running` 且 0 failed unitA 重启后系统 `running` 且 0 failed unitB 的 PostgreSQL、MySQL、Redis、Nginx、Heplify、Homer、Grafana、Prometheus 和 4 个 exporter 全部 activePrometheus 7 个 target 全部 `up`A Node Exporter 仅监听 `100.90.90.90:9100`,B 抓取返回 200,T 访问超时;HEPv3 `INVITE` 从 A 发往 B 后写入 `homer_data.hep_proto_1_call`,方法和源/目标 IP 正确;Grafana HTTPS health 通过,已加载 `Prometheus``HOMER PostgreSQL` datasource 和 `LisgloSIPS Infrastructure Overview` dashboardHomer HTTPS 返回 200Homer/Grafana/MySQL Exporter 近 10 分钟无错误日志。
- 回滚方式:B 备份位于 `/var/backups/lisglosips-s06/20260620T132440Z`A 备份位于 `/var/backups/lisglosips-s06/20260620225517`;停止 S06 服务、恢复配置、保留并单独备份 `/data/homer/postgresql` 后再处理数据目录;A 回滚只删除 `inet lisglosips_s06` 表和两个 S06 服务,不触碰 OpenSIPS/RTP。
- 未解决问题:外部对象存储下载被代理链路阻断,未继续采用 HOMER10 Docker 架构;当前 HOMER Web 是最小状态页,HOMER API、HEP 入库和 Grafana 可用,完整 HOMER UI 静态包待网络条件稳定后补;A 的 9100 保护只是 S06 局部规则,完整 A 防火墙留到 S18/S20。
- 下一任务:`S07 - V2 后端 Monorepo 骨架`
### 2026-06-20 20:44 - S05 Server B Node.js、Nginx 与 TLS 基线
- 状态:已完成
- 操作服务器:B、T、本地
- 完成内容:固定 Node.js 22 LTS、Corepack、pnpm 和 Nginx 版本;建立 `/opt/lisglosips/releases` 与原子 `current` 发布模型;部署受限 `lisglosips@.service` 模板和无依赖 API 健康占位服务;配置 Nginx 静态站点、API 代理、登录/API 限流、安全响应头、内部录音 Range 入口;签发仅供开发使用的私有 CA 证书;另建全项目独立部署指南。
- 修改文件:B 的 `/etc/lisglosips/web-runtime-versions.env``/etc/lisglosips/api.env``/etc/lisglosips/pki/``/etc/nginx/` LisgloSIPS 配置、`/etc/systemd/system/lisglosips@.service``/usr/local/sbin/lisglosips-issue-dev-cert``/opt/lisglosips/releases/s05-placeholder-20260620`;本地 `infra/server-b/s05/``docs/SERVER_B_WEB_RUNTIME_RUNBOOK.md``docs/LISGLOSIPS_DEPLOYMENT_GUIDE.md``.codex-private/tls/lisglosips-dev-ca.crt`
- 安装软件及固定版本:Node.js `v22.22.2`(包 `22.22.2-1nodesource1`)、Corepack `0.34.6`、pnpm `10.33.0`、Nginx `1.24.0-2ubuntu7.12`Node/Nginx 包已 hold,升级前必须复核安全公告与兼容性。
- 执行的关键命令:`apt-get install``apt-mark hold``openssl``nginx -t``systemctl daemon-reload/enable/restart/reboot``curl``systemd-analyze security``sha256sum`
- 验证结果:内部和管理端 HTTPS 健康检查通过;证书链与 `100.90.90.91` SAN 校验通过;仅 TLS 1.2/1.3;首页 HSTS/CSP/防嵌套等安全头完整;登录限流测试出现预期 429;`/_recordings/` 外部访问为 404;应用只监听 `127.0.0.1:3000`,本地管理机和 T 均不能直连 3000,T 不能访问 443;systemd 安全评级为 OK;重启后 Nginx、API、MySQL、Redis 和备份 timer 正常,0 个 failed unit;11 个仓库配置与服务器文件哈希一致;文档敏感信息扫描通过。
- 回滚方式:切换 `/opt/lisglosips/current` 可回滚应用;S05 变更前备份位于 `/var/backups/lisglosips-s05/20260620T123432Z`,配置/证书/服务恢复步骤见 `docs/SERVER_B_WEB_RUNTIME_RUNBOOK.md`
- 未解决问题:当前是开发私有 CA,迁移阿里云必须替换正式域名证书;80 已监听但仍由 nftables 拒绝,生产是否开放取决于 ACME 方案;NodeSource 在本地代理链路上偶发 TLS 握手中断,现有固定包和运行不受影响;当前 API 仅为 S05 健康占位服务,S07 替换。
- 下一任务:`S06 - Server B HOMER 与基础监控`
### 2026-06-20 20:22 - S04 Server B MySQL 与 Redis
- 状态:已完成
- 操作服务器:B、A、T、本地
- 完成内容:在 B 安装并固定 MySQL 8.0.46 与 Redis 7.0.15;建立 `/data/mysql``/data/redis` bind mount;完成 MySQL binlog、UTC、utf8mb4、慢查询和最小权限账号配置;完成 Redis Tailscale 私网绑定、ACL、AOF/RDB 和 `noeviction` 配置;建立 Tailscale 地址就绪依赖,消除 Redis 开机绑定竞态;配置每日 MySQL/Redis 自动备份及 14 天保留。
- 修改文件:B 的 `/etc/mysql/mysql.conf.d/99-lisglosips.cnf``/etc/redis/redis-lisglosips.conf``/etc/redis/users.acl``/etc/fstab`、相关 systemd unit/drop-in、备份脚本及 `/etc/lisglosips/secrets/`;本地 `docs/SERVER_B_DATA_SERVICES_RUNBOOK.md``infra/server-b/s04/``.codex-private/SERVICE_CREDENTIALS.clixml`
- 安装软件及固定版本:MySQL `8.0.46-0ubuntu0.24.04.2`Redis `5:7.0.15-1ubuntu0.24.04.4`;相关 MySQL/Redis 包已 `apt-mark hold`,升级前须先评估安全公告与兼容性。
- 执行的关键命令:`apt-get install``apt-mark hold``mysql``mysqlcheck``redis-cli``redis-check-rdb``systemctl``findmnt``sha256sum``systemctl reboot`
- 验证结果:MySQL 仅监听本机,应用账号 DML 通过且 DDL 被拒;Redis 未认证访问被拒,应用账号读写通过且危险命令被拒,A 可通过 Tailscale 认证访问,T 到 3306/6379 被防火墙拒绝;MySQL 与 Redis 备份哈希通过,并分别完成隔离恢复和数据一致性验证;重启后 mount、服务、数据和 timer 均正常,Redis `NRestarts=0`0 个 failed unit11 个仓库配置与服务器文件 SHA-256 完全一致;仓库敏感信息、私密文件忽略规则和 ACL 检查通过。
- 回滚方式:变更前及迁移前数据保存在 B 的 `/var/backups/lisglosips-s04/20260620195847`;配置、数据目录和服务恢复步骤见 `docs/SERVER_B_DATA_SERVICES_RUNBOOK.md`
- 未解决问题:开发阶段 `/data` 仍位于根盘,正式上云需改为独立数据盘;固定版本需要纳入安全补丁复核;Tailscale 首次直连协商偶尔产生短时延迟,稳态访问正常。
- 下一任务:`S05 - Server B Node.js、Nginx 与 TLS 基线`
### 2026-06-20 19:01 - S03 Server B 基础系统初始化
- 状态:已完成
- 操作服务器:B、本地
- 完成内容:升级 101 个系统包并启用 6.17.0-35 内核;安装基础工具;建立 chrony、nftables、fail2ban、sysstat;创建三个 LisgloSIPS 系统用户和 `/data` 目录基线;修复 NFS 为 Tailscale 就绪后的 automount;完成重启验收。
- 修改文件:B 的 `/etc/fstab``/etc/nftables.conf``/etc/nftables.d/lisglosips.nft``/etc/chrony/chrony.conf``/etc/chrony/sources.d/lisglosips.sources``/etc/fail2ban/jail.d/lisglosips.local``/etc/tmpfiles.d/lisglosips.conf`;本地 `docs/SERVER_B_BASELINE_RUNBOOK.md``infra/server-b/s03/`
- 安装软件及固定版本:chrony 4.5-1ubuntu4.2、nftables 1.0.9-1ubuntu0.1、fail2ban 1.0.2-3ubuntu0.1;其余基础工具版本见 Runbook 和远端 post-change 包清单。
- 执行的关键命令:`apt-get dist-upgrade/install``chronyc``nft``fail2ban-client``systemd-tmpfiles``findmnt``systemctl reboot`
- 验证结果:新内核启动;0 个 failed unit;无待升级包、无 reboot-requiredchrony Stratum 3/Leap NormalNFS automount 重启后可访问;fail2ban sshd jail activenftables 重启后默认拒绝,开发机与 A 允许路径通过,T 到 B 的 22/443/6379/9060 被拒绝;远端配置与仓库副本哈希一致。
- 回滚方式:变更前备份 `/var/backups/lisglosips-s03/20260620184404`;防火墙可执行其中 `rollback-firewall.sh`;其余恢复方式见 Runbook。
- 未解决问题:开发阶段 `/data` 仍位于根盘;桌面/CUPS/Avahi/rpcbind 尚未精简但已被防火墙隔离;ens18 双地址/双默认路由和克隆 machine-id 仍待单独维护窗口处理。
- 下一任务:`S04 - Server B MySQL 与 Redis`
### 2026-06-20 18:37 - S02 网络与安全组验收
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:确认本地开发服务器和 Tailscale 专网边界;采集六个方向稳态 RTT;验证 SIP、RTP、Web、HEP、Redis、监控与 SSH 网络路径;形成开发最小暴露矩阵和未来阿里云安全组清单。
- 修改文件:`docs/infra-check.md``docs/inventory-A.md``docs/inventory-B.md``docs/inventory-summary.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:无。
- 执行的关键命令:`tailscale status/ping/netcheck``ping`、Python 短时 TCP/UDP 监听与 ACK、SIP OPTIONS、`ss`、OpenSIPS/MariaDB 只读核验。
- 验证结果:A/B/T 六方向 0% 丢包,预热后平均 RTT 0.434-0.648 ms1200 字节 DF 报文通过;T->A 15060/UDP 与 RTP 30000/40000 双向通过;A->T SIP 5060 返回合法 484;开发机->B 443、A->B 9060/6379、B->A 9100/22 通过;所有临时监听器均已退出。
- 回滚方式:未修改远端持久配置;短时监听器已自动退出,无需回滚。
- 未解决问题:主机防火墙尚未落地;Tailscale direct 路径曾短暂重新协商导致 SSH 超时;ens18 双地址/双默认路由仍存在;T 的权限与 RTPEngine 内核模块问题留待对应任务处理。
- 测试数据:T 的 `sip_trace` 新增 1 条 S02 OPTIONS 记录,`acc``missed_calls` 未增加。
- 下一任务:`S03 - Server B 基础系统初始化`
### 2026-06-20 18:00 - S01 三机只读资产盘点
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:只读采集三机 OS、内核、CPU、内存、磁盘、网络、路由、时间、端口、服务、软件和防火墙;检查 T 的 OpenSIPS、RTPEngine、MariaDB、录音目录、配置结构与运行统计。
- 修改文件:`docs/inventory-A.md``docs/inventory-B.md``docs/inventory-T.md``docs/inventory-summary.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:无。
- 执行的关键命令:`hostnamectl``lscpu``lsblk``ip``ss``systemctl``ufw status``nft list ruleset``opensips -C``opensips-cli``rtpengine-ctl`、MariaDB 只读查询。
- 验证结果:A/B/T SSH 复验通过;T 的 OpenSIPS、RTPEngine、录音守护进程、MariaDB、Apache 均 activeOpenSIPS 主配置哈希与盘点时一致;报告完整性及敏感信息扫描通过。
- 回滚方式:S01 未修改远端配置、数据或服务,无需回滚。
- 未解决问题:三机克隆身份相同;均有双 IPv4/双默认路由;UFW 未启用;B 的 NFS mount 失败;T 的 TLS 私钥为 0644、MI FIFO 为 0666、RTPEngine 无内核模块;当前 T 无注册账号和路由数据。
- 下一任务:`S02 - 网络与安全组验收`
### 2026-06-20 15:08 - S00 SSH 安全接入与凭据迁移
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:生成项目专用 Ed25519 Key;三机安装公钥;发现并修复克隆服务器共享 SSH host key;轮换 `hector` 密码;建立严格 known_hosts 和 SSH 别名。后续 SSH 变更按最新要求只调整管理端口,不禁止 SSH 密钥登录。
- 修改文件:`.codex-private/ssh/lisglosips_codex_ed25519``.codex-private/ssh/config``.codex-private/ssh/known_hosts``.codex-private/SERVER_CREDENTIALS.clixml``docs/SSH_ACCESS_RUNBOOK.md`
- 安装软件及固定版本:本地私密目录安装 Paramiko 5.0.0,仅用于首次安全接入自动化。
- 验证结果:A/B/T 均通过 Key 新会话;`sshd -t` 通过;SSH 密钥登录保持开启;新 sudo 密码逐台验证通过。
- 回滚方式:见 `docs/SSH_ACCESS_RUNBOOK.md`,三台服务器 `/var/backups/lisglosips-s00/` 保留轮换前和加固前备份。
- 未解决问题:尚未盘点三机软件、磁盘、服务和网络;容量参数与 RTP 端口段仍未确认。
- 下一任务:`S01 - 三机只读资产盘点`
### 2026-06-20 - 计划初始化
- 状态:已完成
- 操作服务器:本地
- 完成内容:建立 Codex 原子任务列表、服务器索引和跨会话交接规范。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md``.gitignore``.codex-private/SERVER_ACCESS.md`
- 验证结果:等待开始 S00。
- 未解决问题:尚未连接服务器;尚未确认容量参数和 RTP 端口段。
- 下一任务:`S00 - SSH 安全接入与凭据迁移`