Files
lisglosips/IMPLEMENTATION_STATUS.md
T

569 lines
107 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 安全接入与凭据迁移`