docs+fix: sweep stale and phantom references; log the actual bound port
Code (2 sites):
- main.rs logged bootstrap_config.port, which is always the hardcoded 8080 in
the two-variable production config; the listener actually binds the port
parsed at main.rs:31-34. Log the bound port.
- That exposed Config.port as genuinely unread (clippy -D warnings), so mark it
with #[expect(dead_code, reason=...)] instead of silently deleting a
documented env-parsing site.
Docs, phantom identifiers (names that do not exist in src/):
- RuntimeConfig x3 -> OutboundConfig via RuntimeConfigProvider (state.rs:34/41)
- LlmClient::noop() x2 -> None in Option<Arc<LlmClient>> (main.rs:125)
- main.rs::validate_env_or_exit x2 -> config.rs:338 read, main.rs:75 validation
- TelegramChannel::send_message -> TelegramClient::send_text (channel.rs:134)
Docs, stale facts:
- LLM table: ENABLED and ALLOW_NET defaulted true,true -> false,false;
API_KEY, BASE_URL, MODE were "required" -> only required when enabled. This
contradicted design.md:385-387, which already had the correct values.
- design.md:560 said RUN_MODE "is actually consumed"; it is only read and
validated and never affects behaviour (main.rs:73-74).
- RUN_MODE operator docs (README, reference, deployment, .env.example) now say
both accepted values produce identical behaviour.
- retired.md: 5 intents -> 6 (/search shipped, worker.rs:517); 4 LLM env vars
-> 6; main.rs tracing events 6 -> 7.
- design.md:494 tracing count 6 -> 7, debug-gate warning added to the list.
- design.md:350 code-gap count 2 -> 1 (multi-instance window done in 6c99ce5).
- design.md:554 marks the 45-paused stage-0 baseline explicitly as historical.
- .env.example said the Redis ACL password was the bootstrap trust root; it is
CONFIG_ENCRYPTION_KEY (main.rs:88-91).
Docs, re-anchored after the main.rs shrink (6 refs shifted by -2).
GATE-P0 62 passed / 0 failed / 1 ignored; GATE-DOCS 4/4 clean
(228 line anchors, 211 section refs, 0 errors).
This commit is contained in:
parent
c75338b305
commit
b70d188864
10
.env.example
10
.env.example
|
|
@ -1,12 +1,14 @@
|
||||||
# MessageWeave production bootstrap (C-REDIS-ONLY-STATE)
|
# MessageWeave production bootstrap (C-REDIS-ONLY-STATE)
|
||||||
#
|
#
|
||||||
# The process requires only this environment variable. Redis ACL credentials are
|
# The process requires only these two variables. CONFIG_ENCRYPTION_KEY is the SPA
|
||||||
# both the application connection credential and the one-shot bootstrap trust root.
|
# admin credential and the one-shot bootstrap trust root; the Redis ACL password
|
||||||
# Never commit or log a real URL/password.
|
# in REDIS_URL only gates the database connection (src/main.rs:88-91).
|
||||||
|
# Never commit or log a real URL/password/key.
|
||||||
REDIS_URL=redis://:REPLACE_WITH_REDIS_ACL_PASSWORD@redis.example.com:6379/0
|
REDIS_URL=redis://:REPLACE_WITH_REDIS_ACL_PASSWORD@redis.example.com:6379/0
|
||||||
# Required 32-byte random hex key (64 hex characters). Never store in Redis or logs.
|
# Required 32-byte random hex key (64 hex characters). Never store in Redis or logs.
|
||||||
CONFIG_ENCRYPTION_KEY=REPLACE_WITH_64_HEX_CHAR_RANDOM_KEY
|
CONFIG_ENCRYPTION_KEY=REPLACE_WITH_64_HEX_CHAR_RANDOM_KEY
|
||||||
|
|
||||||
# PORT/RUN_MODE are optional platform overrides (defaults: 8080/webhook). Business
|
# PORT is an optional platform override (default: 8080). RUN_MODE is accepted but
|
||||||
|
# currently has no effect — both modes share one router (default: webhook). Business
|
||||||
# secrets (Telegram/JMAP/LLM/auth) must be supplied once via HTTPS /api/bootstrap;
|
# secrets (Telegram/JMAP/LLM/auth) must be supplied once via HTTPS /api/bootstrap;
|
||||||
# they are then persisted in Redis and are never echoed by the API.
|
# they are then persisted in Redis and are never echoed by the API.
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@
|
||||||
- 正文转义、sinceState 存 Redis + 模拟 Redis 删除后由对账 `FLOW-RECONCILE` 恢复、通知去重。
|
- 正文转义、sinceState 存 Redis + 模拟 Redis 删除后由对账 `FLOW-RECONCILE` 恢复、通知去重。
|
||||||
- **入口鉴权(`SAF-AUTH-*`)**:`/reconcile`/`/webhook/tg`/`/push/jmap` 在缺失或错误凭证下返回 `401` 且**无副作用**(无 Redis 写入、无 JMAP/AI 调用);正确凭证放行。
|
- **入口鉴权(`SAF-AUTH-*`)**:`/reconcile`/`/webhook/tg`/`/push/jmap` 在缺失或错误凭证下返回 `401` 且**无副作用**(无 Redis 写入、无 JMAP/AI 调用);正确凭证放行。
|
||||||
- **健康探针(`SAF-PROBE-PUBLIC`)**:`/healthz` 返回 `200`;`/ready` 做端到端探测(`ARCH-READY-BASELINE`:配置完整性 + Redis 可达性 + 出站只读探测 JMAP session `GET` / TG `getMe`,各 3000ms,任一失败返 `503`)——测试**可以**断言 `/ready` 在配置 / Redis / 上游探针失败时返回 `503`;两个探针(`probe_jmap_session` / `probe_telegram_get_me`)是可复用纯函数,④ 远程 debug 直接调用,**不要另写一套**;两者响应体均不含敏感信息。
|
- **健康探针(`SAF-PROBE-PUBLIC`)**:`/healthz` 返回 `200`;`/ready` 做端到端探测(`ARCH-READY-BASELINE`:配置完整性 + Redis 可达性 + 出站只读探测 JMAP session `GET` / TG `getMe`,各 3000ms,任一失败返 `503`)——测试**可以**断言 `/ready` 在配置 / Redis / 上游探针失败时返回 `503`;两个探针(`probe_jmap_session` / `probe_telegram_get_me`)是可复用纯函数,④ 远程 debug 直接调用,**不要另写一套**;两者响应体均不含敏感信息。
|
||||||
- **远程 debug(`MOD-DEBUG`/`SAF-DEBUG-GATE`/`SAF-DEBUG-AUTH`/`REQ-DEBUG-ENDPOINTS`)**:缺 `--debug` 或 `DEBUG_TOKEN` 缺失/为空时,`/debug/*` **一律 `404`**(路由根本没挂载);双因子齐备时挂载,缺 `Bearer` 或 token 不符返回 `401`,正确 token 放行;`GET /debug/config` 响应体**不得出现任何 secret 原文**(`SAF-NO-SECRET-ECHO`,测试可断言只含布尔/计数/结论字段);出站只读探针直接复用 `probe_jmap_session` / `probe_telegram_get_me`(`ARCH-READY-BASELINE`),不另写实现;`POST /debug/notify` 复用真实出站路径(`TelegramChannel::send_message`),不得另建第二套 Telegram 客户端。
|
- **远程 debug(`MOD-DEBUG`/`SAF-DEBUG-GATE`/`SAF-DEBUG-AUTH`/`REQ-DEBUG-ENDPOINTS`)**:缺 `--debug` 或 `DEBUG_TOKEN` 缺失/为空时,`/debug/*` **一律 `404`**(路由根本没挂载);双因子齐备时挂载,缺 `Bearer` 或 token 不符返回 `401`,正确 token 放行;`GET /debug/config` 响应体**不得出现任何 secret 原文**(`SAF-NO-SECRET-ECHO`,测试可断言只含布尔/计数/结论字段);出站只读探针直接复用 `probe_jmap_session` / `probe_telegram_get_me`(`ARCH-READY-BASELINE`),不另写实现;`POST /debug/notify` 复用真实出站路径(`TelegramClient::send_text`,`src/channel.rs:134`),不得另建第二套 Telegram 客户端。
|
||||||
- **JMAP 只读 adapter(`MOD-JMAP-CLIENT`/`GATE-G1-JMAP-READONLY`)**:mock 测试覆盖 session/account 选择(`ACCOUNT_ID` 空→主账户、显式值校验)、**URL 归一化**(`JMAP_SESSION_URL` 基地址与完整 `…/.well-known/jmap` 两种输入结果一致、无重复路径、`http://` 拒绝、内嵌凭据拒绝,`REQ-JMAP-SESSION-URL`/`SAF-JMAP-URL`)、`list_folders`/`list_emails`(`limit` 边界)/`read_email`(多 part 拼接与"无可用部分"明确错误,`REQ-JMAP-RAW-MULTIPART`)、`received_at` 解析;真机测试用 `#[ignore]` 标记、经环境变量驱动(运行:`cargo test -- --ignored jmap::`;仅编译:`cargo test --no-run`),**缺环境时清晰跳过且不泄密**,CI 默认不跑真机用例。**G1/D-G1-1 代码已实现,待真实 `cargo test -- --ignored jmap::` 验证;未实际运行 `--ignored` 前不得声称"真机通过"。**
|
- **JMAP 只读 adapter(`MOD-JMAP-CLIENT`/`GATE-G1-JMAP-READONLY`)**:mock 测试覆盖 session/account 选择(`ACCOUNT_ID` 空→主账户、显式值校验)、**URL 归一化**(`JMAP_SESSION_URL` 基地址与完整 `…/.well-known/jmap` 两种输入结果一致、无重复路径、`http://` 拒绝、内嵌凭据拒绝,`REQ-JMAP-SESSION-URL`/`SAF-JMAP-URL`)、`list_folders`/`list_emails`(`limit` 边界)/`read_email`(多 part 拼接与"无可用部分"明确错误,`REQ-JMAP-RAW-MULTIPART`)、`received_at` 解析;真机测试用 `#[ignore]` 标记、经环境变量驱动(运行:`cargo test -- --ignored jmap::`;仅编译:`cargo test --no-run`),**缺环境时清晰跳过且不泄密**,CI 默认不跑真机用例。**G1/D-G1-1 代码已实现,待真实 `cargo test -- --ignored jmap::` 验证;未实际运行 `--ignored` 前不得声称"真机通过"。**
|
||||||
- 领域/渠道解耦测试:领域模块(`src/domain.rs` / `src/ai.rs` / `src/worker.rs`)的公开接口不出现任何渠道 SDK 类型(编译期检查);`JmapBackend` 的 `MockBackend` 可驱动全部 JMAP 领域流程(不联网)。
|
- 领域/渠道解耦测试:领域模块(`src/domain.rs` / `src/ai.rs` / `src/worker.rs`)的公开接口不出现任何渠道 SDK 类型(编译期检查);`JmapBackend` 的 `MockBackend` 可驱动全部 JMAP 领域流程(不联网)。
|
||||||
- 文件行数抽查:>500 行的 `.rs` 文件在 PR 说明中可见(软性,不自动 fail)。
|
- 文件行数抽查:>500 行的 `.rs` 文件在 PR 说明中可见(软性,不自动 fail)。
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,9 @@ Three layers. They are not interchangeable.
|
||||||
| `PORT` | no | `8080` |
|
| `PORT` | no | `8080` |
|
||||||
| `RUN_MODE` | no | `webhook` |
|
| `RUN_MODE` | no | `webhook` |
|
||||||
|
|
||||||
`RUN_MODE` accepts `webhook` or `reconcile`. `.env.example` ships only the two required
|
`RUN_MODE` accepts `webhook` or `reconcile`, but **both currently produce identical
|
||||||
|
behavior** (the two modes share one router), so it is a forward placeholder: keep the
|
||||||
|
default `webhook`. `.env.example` ships only the two required
|
||||||
variables; the rest are documented defaults.
|
variables; the rest are documented defaults.
|
||||||
|
|
||||||
**Business configuration — Redis, written by the browser.**
|
**Business configuration — Redis, written by the browser.**
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@
|
||||||
|
|
||||||
**双因子启用条件(`SAF-DEBUG-GATE`,两者必须同时成立)**
|
**双因子启用条件(`SAF-DEBUG-GATE`,两者必须同时成立)**
|
||||||
|
|
||||||
1. 进程命令行必须带 `--debug`(读取于 `src/main.rs:99`)。
|
1. 进程命令行必须带 `--debug`(读取于 `src/main.rs:96`)。
|
||||||
2. `DEBUG_TOKEN` 环境变量必须存在且非空(读取于 `src/main.rs:100-103`)。
|
2. `DEBUG_TOKEN` 环境变量必须存在且非空(读取于 `src/main.rs:97-100`)。
|
||||||
|
|
||||||
缺一即不挂载:`debug_router()` 本身会构造出全部 7 条路由(`src/debug.rs:72-80`),但主入口**只在双因子成立时才合并它**(`src/notify.rs:1320-1321`)。所以未开启时 `/debug/*` 路由根本不存在,请求走 axum 兜底返回普通 `404 not found`——**不是** 401,也不会泄露「此路径存在」。开启成功时打一条 WARN 日志标记该面已打开(`src/main.rs:107-109`,`SAF-LOG-PURITY`:只记开启状态,从不记录 token 值)。
|
缺一即不挂载:`debug_router()` 本身会构造出全部 7 条路由(`src/debug.rs:72-80`),但主入口**只在双因子成立时才合并它**(`src/notify.rs:1320-1321`)。所以未开启时 `/debug/*` 路由根本不存在,请求走 axum 兜底返回普通 `404 not found`——**不是** 401,也不会泄露「此路径存在」。开启成功时打一条 WARN 日志标记该面已打开(`src/main.rs:107-109`,`SAF-LOG-PURITY`:只记开启状态,从不记录 token 值)。
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ HTTP 端口并以 `/healthz` 做 interval 30 秒、timeout 5 秒的健康检查
|
||||||
| `CHAT_ALLOWLIST` | ✅ | 聊天白名单(**硬约束 `SAF-CHAT-ALLOWLIST`**):逗号分隔整数 chat id;处理任何事件前先校验,非白名单直接拒绝 |
|
| `CHAT_ALLOWLIST` | ✅ | 聊天白名单(**硬约束 `SAF-CHAT-ALLOWLIST`**):逗号分隔整数 chat id;处理任何事件前先校验,非白名单直接拒绝 |
|
||||||
| `REDIS_URL` | ✅ | **外部 Redis(用户托管 + AOF)**(`C-REDIS-ONLY-STATE`/`C-REDIS-MANAGED-AOF`):session / dedup / Streams / fuse / sinceState 全部在此;支持 `redis://` 与带默认 ACL 用户的 `rediss://default:<url-encoded-password>@host:6379/0`,Redis 进程不在此 compose 内 |
|
| `REDIS_URL` | ✅ | **外部 Redis(用户托管 + AOF)**(`C-REDIS-ONLY-STATE`/`C-REDIS-MANAGED-AOF`):session / dedup / Streams / fuse / sinceState 全部在此;支持 `redis://` 与带默认 ACL 用户的 `rediss://default:<url-encoded-password>@host:6379/0`,Redis 进程不在此 compose 内 |
|
||||||
| `PORT` | 默认 8080 | 单监听端口(`C-NO-TCP-EXPOSE`) |
|
| `PORT` | 默认 8080 | 单监听端口(`C-NO-TCP-EXPOSE`) |
|
||||||
| `RUN_MODE` | 默认 `webhook` | `webhook` / `reconcile` 二选一(`NG-SERVER-MODE` 已删除) |
|
| `RUN_MODE` | 默认 `webhook` | `webhook` / `reconcile`(`NG-SERVER-MODE` 已删除);**两种取值当前行为完全相同**(共享同一套路由表),仅作前向占位,保持默认即可 |
|
||||||
| `RECONCILE_TOKEN` | ✅ | `/reconcile` 的 `Authorization: Bearer <token>` 承载令牌(`SAF-AUTH-RECONCILE`)。因 `/reconcile` 路由**始终挂载**,此变量为**必填**(`SecretString`) |
|
| `RECONCILE_TOKEN` | ✅ | `/reconcile` 的 `Authorization: Bearer <token>` 承载令牌(`SAF-AUTH-RECONCILE`)。因 `/reconcile` 路由**始终挂载**,此变量为**必填**(`SecretString`) |
|
||||||
| `WORKER_TOKEN` | ✅ | `/worker` 的有界处理令牌;管理 API 兼容接受该 Bearer 值,SPA 使用短期 Redis admin session |
|
| `WORKER_TOKEN` | ✅ | `/worker` 的有界处理令牌;管理 API 兼容接受该 Bearer 值,SPA 使用短期 Redis admin session |
|
||||||
| `TG_WEBHOOK_SECRET` | ✅ | `/webhook/tg` 校验请求头 `X-Telegram-Bot-Api-Secret-Token`(`SAF-AUTH-TG-WEBHOOK`)。须与 Telegram `setWebhook` 的 `secret_token` **完全一致**(`SecretString`) |
|
| `TG_WEBHOOK_SECRET` | ✅ | `/webhook/tg` 校验请求头 `X-Telegram-Bot-Api-Secret-Token`(`SAF-AUTH-TG-WEBHOOK`)。须与 Telegram `setWebhook` 的 `secret_token` **完全一致**(`SecretString`) |
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ message-weave/
|
||||||
| `notify` | axum;redis | HTTP 鉴权、全局开关、`/push/jmap` 入队、`Email/changes` 对账和游标提交 | webhook 校验失败路径覆盖 403 | 已覆盖真实对账路径;仍需真实 Stalwart 环境做端到端验收 |
|
| `notify` | axum;redis | HTTP 鉴权、全局开关、`/push/jmap` 入队、`Email/changes` 对账和游标提交 | webhook 校验失败路径覆盖 403 | 已覆盖真实对账路径;仍需真实 Stalwart 环境做端到端验收 |
|
||||||
| `web` | axum | `/config` 静态页 + `include_str!` 嵌入 + CSP | 前端由 `web/config.test.mjs` 覆盖 | 已实现 |
|
| `web` | axum | `/config` 静态页 + `include_str!` 嵌入 + CSP | 前端由 `web/config.test.mjs` 覆盖 | 已实现 |
|
||||||
|
|
||||||
> 注:「现状」列是**模块级**口径(模块已落地),不代表行为完备。行为级缺口不在本表内:见 `docs/roadmap.md`「代码缺口」(2 条:多实例重复投递残余窗口、`SAF-DEBUG-ALLOWLIST`)。`worker` 模块的 `/search` 路径已随 `bfe0fd8` 落地。
|
> 注:「现状」列是**模块级**口径(模块已落地),不代表行为完备。行为级缺口不在本表内:`docs/roadmap.md`「代码缺口」中 `/search`、Telegram 429 退避、多实例重复投递窗口均已实现,仅剩 1 条按产品决策保留不改(`SAF-DEBUG-ALLOWLIST`)。`worker` 模块的 `/search` 路径已随 `bfe0fd8` 落地。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -371,7 +371,7 @@ message-weave/
|
||||||
| 变量 | 必填 | 默认 | 说明 |
|
| 变量 | 必填 | 默认 | 说明 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `PORT` | 否 | `8080` | 单监听端口(`C-NO-TCP-EXPOSE`) |
|
| `PORT` | 否 | `8080` | 单监听端口(`C-NO-TCP-EXPOSE`) |
|
||||||
| `RUN_MODE` | 否 | `webhook` | `webhook` / `reconcile`(`NG-SERVER-MODE` 已删除) |
|
| `RUN_MODE` | 否 | `webhook` | `webhook` / `reconcile`(`NG-SERVER-MODE` 已删除);**两种取值当前不改变任何运行时行为**(两模式共享同一套路由表),仅作前向占位 |
|
||||||
| `BOT_TOKEN` | 是 | — | Telegram Bot Token(`SecretString`) |
|
| `BOT_TOKEN` | 是 | — | Telegram Bot Token(`SecretString`) |
|
||||||
| `TG_WEBHOOK_SECRET` | 是 | — | `/webhook/tg` 鉴权:请求头 `X-Telegram-Bot-Api-Secret-Token`(`SAF-AUTH-TG-WEBHOOK`;`SecretString`) |
|
| `TG_WEBHOOK_SECRET` | 是 | — | `/webhook/tg` 鉴权:请求头 `X-Telegram-Bot-Api-Secret-Token`(`SAF-AUTH-TG-WEBHOOK`;`SecretString`) |
|
||||||
| `CHAT_ALLOWLIST` | 是 | — | 逗号分隔整数 chat id;**白名单硬约束**(`SAF-CHAT-ALLOWLIST`) |
|
| `CHAT_ALLOWLIST` | 是 | — | 逗号分隔整数 chat id;**白名单硬约束**(`SAF-CHAT-ALLOWLIST`) |
|
||||||
|
|
@ -491,7 +491,7 @@ pub enum BotError {
|
||||||
|
|
||||||
### 8.3 可观测性
|
### 8.3 可观测性
|
||||||
- `tracing`(直接依赖)+ `tracing-subscriber`(fmt + EnvFilter)。
|
- `tracing`(直接依赖)+ `tracing-subscriber`(fmt + EnvFilter)。
|
||||||
- `tracing`(直接依赖)+ `tracing-subscriber`(fmt + EnvFilter)。**当前只记录启动期事件**:`src/main.rs` 共 6 处 `info!`/`warn!`(RUN_MODE、加密密钥缺失、Redis URL 缺失、JMAP 连接不可用降级、启动横幅等)。请求级事件(Push 回调、Reconcile 拉取、渠道推送、LLM 耗时)**尚未实现**——`src/` 里除 `main.rs` 外没有任何 tracing 调用,也没有命名 span(无 `#[instrument]` / `span!`)。
|
- `tracing`(直接依赖)+ `tracing-subscriber`(fmt + EnvFilter)。**当前只记录启动期事件**:`src/main.rs` 共 7 处 `info!`/`warn!`(`RUN_MODE` 取值横幅×2、启动变量缺失降级到 setup 模式、启动横幅、debug 端点开启、JMAP 服务不可用降级×2)。请求级事件(Push 回调、Reconcile 拉取、渠道推送、LLM 耗时)**尚未实现**——`src/` 里除 `main.rs` 外没有任何 tracing 调用,也没有命名 span(无 `#[instrument]` / `span!`)。
|
||||||
- 指标(可选 `metrics` crate):Push 回调到达数、去重命中率、Redis Streams 积压深度(pending)、DLQ 条数、对账补差条数、JMAP 请求延迟、推送失败率。**当前未接入任何指标后端**(`metrics` 不在 `Cargo.toml`)。
|
- 指标(可选 `metrics` crate):Push 回调到达数、去重命中率、Redis Streams 积压深度(pending)、DLQ 条数、对账补差条数、JMAP 请求延迟、推送失败率。**当前未接入任何指标后端**(`metrics` 不在 `Cargo.toml`)。
|
||||||
- 优雅退出:**当前未实现**——`src/` 里没有信号处理(无 `tokio::signal` / `ctrl_c`),也没有 Streams pending 条目的 flush 逻辑;容器停机即终止进程。Redis 侧 `C-NO-STATEFUL-RECOVERY` 保证重启后从 Redis 重建,不依赖进程内状态。
|
- 优雅退出:**当前未实现**——`src/` 里没有信号处理(无 `tokio::signal` / `ctrl_c`),也没有 Streams pending 条目的 flush 逻辑;容器停机即终止进程。Redis 侧 `C-NO-STATEFUL-RECOVERY` 保证重启后从 Redis 重建,不依赖进程内状态。
|
||||||
- **可靠性目标与策略**(Streams ACK/retry、幂等去重、Push 重试、对账恢复、指标/告警、**≥99.9% 通知可用性及边界**)见 deployment.md §6.4/§6.5(`NFR-NOTIFY-SLA`)。
|
- **可靠性目标与策略**(Streams ACK/retry、幂等去重、Push 重试、对账恢复、指标/告警、**≥99.9% 通知可用性及边界**)见 deployment.md §6.4/§6.5(`NFR-NOTIFY-SLA`)。
|
||||||
|
|
@ -551,13 +551,13 @@ pub enum BotError {
|
||||||
- axum 采用 **0.8**(`ARCH-AXUM-08`);如后续审核决定调整版本,以 Cargo.toml 为准并同步本节。
|
- axum 采用 **0.8**(`ARCH-AXUM-08`);如后续审核决定调整版本,以 Cargo.toml 为准并同步本节。
|
||||||
- 当前不实现 SSE/WebSocket/长轮询/SQLite/本地卷(`C-NO-LONG-CONN`/`NG-SQLITE-PERSIST`/`NG-LOCAL-VOLUME`);入口鉴权(`R1`/`SAF-AUTH-*`)作为 fail-closed 硬门禁落地,鉴权之后的 Push、Streams worker 和 `/reconcile` 业务路径已实现。
|
- 当前不实现 SSE/WebSocket/长轮询/SQLite/本地卷(`C-NO-LONG-CONN`/`NG-SQLITE-PERSIST`/`NG-LOCAL-VOLUME`);入口鉴权(`R1`/`SAF-AUTH-*`)作为 fail-closed 硬门禁落地,鉴权之后的 Push、Streams worker 和 `/reconcile` 业务路径已实现。
|
||||||
|
|
||||||
**阶段0 P0 门禁(`GATE-P0`)——已通过(冻结基线 45 passed / 0 failed / 1 ignored,见 `docs/roadmap.md` 头部)。以下为当时的判据,保留作历史记录:**
|
**阶段0 P0 门禁(`GATE-P0`)——已通过。首次冻结时为 45 passed / 0 failed / 1 ignored,此后随 ④–⑥ 轮实现持续增长,当前基线以 `docs/roadmap.md` 头部为准。以下为阶段0 当时的判据,保留作历史记录:**
|
||||||
1. `cargo fmt --check` 通过(无格式差异)。
|
1. `cargo fmt --check` 通过(无格式差异)。
|
||||||
2. `cargo clippy --all-targets -- -D warnings` 通过(零告警;禁 crate 级 `allow`)。
|
2. `cargo clippy --all-targets -- -D warnings` 通过(零告警;禁 crate 级 `allow`)。
|
||||||
3. `cargo test` 通过(含路由/配置最小测试)。
|
3. `cargo test` 通过(含路由/配置最小测试)。
|
||||||
4. 上述三条**在 Debian `rust:1-slim-bookworm` 容器内**执行通过(`C-DEBIAN-SLIM`)。
|
4. 上述三条**在 Debian `rust:1-slim-bookworm` 容器内**执行通过(`C-DEBIAN-SLIM`)。
|
||||||
5. 配置读取为 env-only(无 figment/TOML);`SecretString` 包裹秘密且 `Debug` 不泄密;`JMAP_PASSWORD` 安全访问器保留。
|
5. 配置读取为 env-only(无 figment/TOML);`SecretString` 包裹秘密且 `Debug` 不泄密;`JMAP_PASSWORD` 安全访问器保留。
|
||||||
6. `RUN_MODE` 被实际消费(当前为 `webhook` 入口的单一路由表,无副作用;`reconcile` 作为独立 HTTP 端点 `POST /reconcile` 提供,不通过 `RUN_MODE` 分派,见 `src/main.rs::validate_env_or_exit`)。
|
6. `RUN_MODE` **不**参与路由分派:`webhook` 与 `reconcile` 共享同一套路由表,`reconcile` 作为独立 HTTP 端点 `POST /reconcile` 提供。`RUN_MODE` 仅在启动时读取并校验取值(`src/main.rs:75`,只接受 `webhook`/`reconcile`),**当前不影响任何运行时行为**,留待后续阶段在此挂载差异化副作用(见 §12.2 的运行模式说明)。
|
||||||
7. `.gitignore` 存在(排除 `target/` 等;**不**擅自初始化 git)。
|
7. `.gitignore` 存在(排除 `target/` 等;**不**擅自初始化 git)。
|
||||||
8. 所有新增代码有引用稳定 ID 的必要注释;单 `.rs` ≤ 500 行。
|
8. 所有新增代码有引用稳定 ID 的必要注释;单 `.rs` ≤ 500 行。
|
||||||
|
|
||||||
|
|
@ -689,7 +689,7 @@ Push 事件经 Streams 消费并投递到 Telegram,其关键路径交付语义
|
||||||
- **多发件身份**(Q12):`Identity` 未使用,单账户(`REQ-SINGLE-ACCOUNT`)。
|
- **多发件身份**(Q12):`Identity` 未使用,单账户(`REQ-SINGLE-ACCOUNT`)。
|
||||||
- **unsafe**(Q13):`src/` 中没有 `unsafe`,但也没有加 `#![forbid(unsafe_code)]`。
|
- **unsafe**(Q13):`src/` 中没有 `unsafe`,但也没有加 `#![forbid(unsafe_code)]`。
|
||||||
- **监控**(Q15):实现为 `/healthz` + `/ready` 两个 HTTP 探针,不引入 Prometheus/Exporter;运行平台配置见 deployment.md。
|
- **监控**(Q15):实现为 `/healthz` + `/ready` 两个 HTTP 探针,不引入 Prometheus/Exporter;运行平台配置见 deployment.md。
|
||||||
- **LLM 提供方 / 网出许可**(Q25、Q26、Q30):`LLM_BASE_URL` 由部署方指定(只校验 https),`LLM_ALLOW_NET` 默认 `true`;`LLM_ENABLED=false` 时直接走 `LlmClient::noop()`,不探测(见 §12.2)。
|
- **LLM 提供方 / 网出许可**(Q25、Q26、Q30):`LLM_BASE_URL` 由部署方指定(只校验 https);`LLM_ENABLED` 与 `LLM_ALLOW_NET` **默认均为 `false`**,二者须同时为真才构造客户端,否则 `llm` 字段为 `None`(`Option<Arc<LlmClient>>`,仓库内不存在 `noop()` 实现),不探测(见 §12.2)。
|
||||||
- **熔断冷却 / 阈值**(Q27):不适用——熔断本身未实现(见 §12.4)。
|
- **熔断冷却 / 阈值**(Q27):不适用——熔断本身未实现(见 §12.4)。
|
||||||
|
|
||||||
部署/平台类决策已无未决项:`Q-DEP-A`(平台 URL / 域名与证书由谁配置,由部署环境在发布时确定)与 `Q-DEP-B`(外部 Cron 用哪个调度器,不限定实现)均已决策,归档到 `docs/deployment.md` 的已确认决策一节,不在本文重复。
|
部署/平台类决策已无未决项:`Q-DEP-A`(平台 URL / 域名与证书由谁配置,由部署环境在发布时确定)与 `Q-DEP-B`(外部 Cron 用哪个调度器,不限定实现)均已决策,归档到 `docs/deployment.md` 的已确认决策一节,不在本文重复。
|
||||||
|
|
@ -714,20 +714,20 @@ LLM 相关代码只有**一个文件**:
|
||||||
```
|
```
|
||||||
src/ai.rs # LlmClient(唯一实现):summarize() 打 OpenAI 兼容 /chat/completions
|
src/ai.rs # LlmClient(唯一实现):summarize() 打 OpenAI 兼容 /chat/completions
|
||||||
```
|
```
|
||||||
配置装载在 `src/config.rs` 的 `LlmConfig`,运行时参数在 `src/state.rs` 的 `RuntimeConfig`。
|
配置装载在 `src/config.rs` 的 `LlmConfig`,运行时参数在 `src/state.rs` 的 `OutboundConfig`(经 `RuntimeConfigProvider` 下发)。
|
||||||
**不存在** `src/domain/llm/` 目录,也不存在其中的 `mod.rs` / `client.rs` / `config.rs` / `policy.rs` / `fallback.rs` / `audit.rs`——熔断、规则回退、审计 span 全部未实现(见 §12.4–12.7)。
|
**不存在** `src/domain/llm/` 目录,也不存在其中的 `mod.rs` / `client.rs` / `config.rs` / `policy.rs` / `fallback.rs` / `audit.rs`——熔断、规则回退、审计 span 全部未实现(见 §12.4–12.7)。
|
||||||
调用方只有 `worker.rs` 的 `MetadataWorker`(已授权摘要)和 `notify.rs`(新邮件通知)。
|
调用方只有 `worker.rs` 的 `MetadataWorker`(已授权摘要)和 `notify.rs`(新邮件通知)。
|
||||||
|
|
||||||
### 12.2 环境变量(当前实现)
|
### 12.2 环境变量(当前实现)
|
||||||
| 变量 | 类型 | 默认 | 用途 |
|
| 变量 | 类型 | 默认 | 用途 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `LLM_ENABLED` | bool | `true` | 总开关 |
|
| `LLM_ENABLED` | bool | `false` | 总开关;默认关闭,未启用时不校验 `LLM_API_KEY`/`LLM_BASE_URL`/`LLM_MODEL` |
|
||||||
| `LLM_ALLOW_NET` | bool | `true` | 运行时出站许可;`LLM_ENABLED && LLM_ALLOW_NET` 同时为真才构造 `LlmClient`,否则用 `LlmClient::noop()`(`src/main.rs:128`) |
|
| `LLM_ALLOW_NET` | bool | `false` | 运行时出站许可;`LLM_ENABLED && LLM_ALLOW_NET` 同时为真才构造 `LlmClient`,否则 `llm` 字段为 `None`(`src/main.rs:125`) |
|
||||||
| `LLM_API_KEY` | string | 必填 | Bearer token;缺失即视为未启用 |
|
| `LLM_API_KEY` | string | 可省略 | Bearer token;仅 `LLM_ENABLED=true` 时必填 |
|
||||||
| `LLM_BASE_URL` | URL | 必填 | 必须 `https`,否则 `AiError::InvalidEndpoint` |
|
| `LLM_BASE_URL` | URL | 可省略 | 仅启用时必填,且必须 `https`,否则 `AiError::InvalidEndpoint` |
|
||||||
| `LLM_MODEL` | string | 必填 | 透传给 `/chat/completions` 的 `model` |
|
| `LLM_MODEL` | string | 可省略 | 仅启用时必填;透传给 `/chat/completions` 的 `model` |
|
||||||
| `LLM_SUMMARY_TARGET_CHARS` | int | `1024` | `LlmClient::max_chars`,对返回摘要文本做字符截断(**代码侧确有截断**) |
|
| `LLM_SUMMARY_TARGET_CHARS` | int | `1024` | `LlmClient::max_chars`,对返回摘要文本做字符截断(**代码侧确有截断**) |
|
||||||
| `max_retries`(运行参数) | int | `3` | 由 `RuntimeConfig` 下发,硬上限 5;LLM 与 Telegram 出站共用 |
|
| `max_retries`(运行参数) | int | `3` | 由 `OutboundConfig` 下发(`RuntimeConfigProvider`),硬上限 5;LLM 与 Telegram 出站共用 |
|
||||||
| `llm_timeout_ms`(运行参数) | int | `30000` | 硬下限 100ms |
|
| `llm_timeout_ms`(运行参数) | int | `30000` | 硬下限 100ms |
|
||||||
|
|
||||||
**不存在** `LLM_TEMPERATURE` / `LLM_MAX_TOKENS` / `LLM_TIMEOUT_SECS` / `LLM_MAX_RETRIES`:请求体只有 `model` / `messages` / `max_tokens`(= `max_chars * 2`),没有 temperature,也没有独立于 `llm_timeout_ms` 的超时开关。API key 只从配置读,不入源码、不打日志(`SAF-LOG-PURITY`),运行期 secret 注入方式见 deployment.md(`C-NO-SECRET-IN-IMAGE`)。
|
**不存在** `LLM_TEMPERATURE` / `LLM_MAX_TOKENS` / `LLM_TIMEOUT_SECS` / `LLM_MAX_RETRIES`:请求体只有 `model` / `messages` / `max_tokens`(= `max_chars * 2`),没有 temperature,也没有独立于 `llm_timeout_ms` 的超时开关。API key 只从配置读,不入源码、不打日志(`SAF-LOG-PURITY`),运行期 secret 注入方式见 deployment.md(`C-NO-SECRET-IN-IMAGE`)。
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ All registered in `router_with_worker_state_runtime_bootstrap_config` (notify.rs
|
||||||
| GET | `/`, `/assets/config.js`, `/assets/styles.css` | `index` / `web_config` |
|
| GET | `/`, `/assets/config.js`, `/assets/styles.css` | `index` / `web_config` |
|
||||||
|
|
||||||
The remote-debug surface (`src/debug.rs:72-80`) is merged **only when the process is started
|
The remote-debug surface (`src/debug.rs:72-80`) is merged **only when the process is started
|
||||||
with `--debug` and `DEBUG_TOKEN` is non-empty** (`SAF-DEBUG-GATE`, `src/main.rs:99-103`,
|
with `--debug` and `DEBUG_TOKEN` is non-empty** (`SAF-DEBUG-GATE`, `src/main.rs:96-100`,
|
||||||
`src/notify.rs:1320-1321`); otherwise none of these routes exist and requests fall through to
|
`src/notify.rs:1320-1321`); otherwise none of these routes exist and requests fall through to
|
||||||
axum's generic `404`, not a 401. All seven require `Authorization: Bearer <DEBUG_TOKEN>`,
|
axum's generic `404`, not a 401. All seven require `Authorization: Bearer <DEBUG_TOKEN>`,
|
||||||
checked by `debug_authorized` (`src/debug.rs:45`), which delegates to the production
|
checked by `debug_authorized` (`src/debug.rs:45`), which delegates to the production
|
||||||
|
|
@ -315,7 +315,9 @@ Three distinct layers. They are not interchangeable.
|
||||||
|
|
||||||
`RUN_MODE` accepts only `webhook` or `reconcile`; anything else is rejected at boot
|
`RUN_MODE` accepts only `webhook` or `reconcile`; anything else is rejected at boot
|
||||||
(main.rs:78-81). Both modes currently route to the same no-op router; the distinction is
|
(main.rs:78-81). Both modes currently route to the same no-op router; the distinction is
|
||||||
load-bearing for future splits, not for behaviour today.
|
load-bearing for future splits, not for behaviour today. Note the value is only read on
|
||||||
|
the legacy env path (`config.rs:338`), so with the current two-variable production config
|
||||||
|
it always resolves to the hardcoded default — setting it to `reconcile` has no effect.
|
||||||
|
|
||||||
**Missing a required variable does not crash the process.** It logs a warning and serves
|
**Missing a required variable does not crash the process.** It logs a warning and serves
|
||||||
`router_configuration_setup`, which builds a router over `MemoryState` with empty tokens and
|
`router_configuration_setup`, which builds a router over `MemoryState` with empty tokens and
|
||||||
|
|
@ -341,7 +343,7 @@ The single prefixed name is `TELEGRAM_CHAT_ID`. Anything else documented as
|
||||||
`TELEGRAM_BOT_TOKEN` or similar is a documentation error, not a supported variable.
|
`TELEGRAM_BOT_TOKEN` or similar is a documentation error, not a supported variable.
|
||||||
|
|
||||||
**Trust root.** The SPA admin credential is `CONFIG_ENCRYPTION_KEY` itself: read at startup
|
**Trust root.** The SPA admin credential is `CONFIG_ENCRYPTION_KEY` itself: read at startup
|
||||||
(main.rs:49) and held as `admin_token` (main.rs:95), it is checked in constant time by
|
(main.rs:49) and held as `admin_token` (main.rs:92), it is checked in constant time by
|
||||||
`worker_authorized` (notify.rs:445; compare at notify.rs:1135) at the top of both
|
`worker_authorized` (notify.rs:445; compare at notify.rs:1135) at the top of both
|
||||||
`POST /api/bootstrap` (notify.rs:604) and `POST /api/admin/session` (notify.rs:774). It is
|
`POST /api/bootstrap` (notify.rs:604) and `POST /api/admin/session` (notify.rs:774). It is
|
||||||
only compared against the request bearer — never echoed, logged, or stored. The session
|
only compared against the request bearer — never echoed, logged, or stored. The session
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
| `src/domain/llm/` 整个模块族 | 虚构模块树 | `domain/` 下只有 `jmap.rs` 与 `jmap/client.rs`;LLM 只有 `src/ai.rs` 一个文件(`LlmClient`) | 无 |
|
| `src/domain/llm/` 整个模块族 | 虚构模块树 | `domain/` 下只有 `jmap.rs` 与 `jmap/client.rs`;LLM 只有 `src/ai.rs` 一个文件(`LlmClient`) | 无 |
|
||||||
| `ai::config` / `ai::fallback` / `ai::policy` / `ai::audit` | 虚构模块 | LLM 配置在 `config.rs::LlmConfig`,运行时参数在 `state.rs::RuntimeConfig`,无 policy/audit 概念 | 无 |
|
| `ai::config` / `ai::fallback` / `ai::policy` / `ai::audit` | 虚构模块 | LLM 配置在 `config.rs::LlmConfig`,运行时参数在 `state.rs::OutboundConfig`(经 `RuntimeConfigProvider` 下发),无 policy/audit 概念 | 无 |
|
||||||
| `channel/mod.rs` | 虚构文件 | 渠道层就是 `src/channel.rs`,不是 `channel/` 目录 | 无 |
|
| `channel/mod.rs` | 虚构文件 | 渠道层就是 `src/channel.rs`,不是 `channel/` 目录 | 无 |
|
||||||
| `notify::push_handler` / `notify::worker` / `notify::reconcile` 作为模块路径 | 虚构模块路径 | 这些是 `src/notify.rs` 内的自由函数(`jmap_push` / `worker` / `reconcile`),不是模块路径 | 无 |
|
| `notify::push_handler` / `notify::worker` / `notify::reconcile` 作为模块路径 | 虚构模块路径 | 这些是 `src/notify.rs` 内的自由函数(`jmap_push` / `worker` / `reconcile`),不是模块路径 | 无 |
|
||||||
| `mod_dedup` / `mod_streams` / `mod_sincestate` | 虚构模块名 | `state.rs` / `notify.rs` 都是平铺文件,无子模块;去重与 Streams 逻辑以自由函数存在 | 无 |
|
| `mod_dedup` / `mod_streams` / `mod_sincestate` | 虚构模块名 | `state.rs` / `notify.rs` 都是平铺文件,无子模块;去重与 Streams 逻辑以自由函数存在 | 无 |
|
||||||
|
|
@ -90,14 +90,14 @@
|
||||||
| `CancellationToken` | 虚构类型 | 未使用;无优雅关闭、无信号处理(`src/` 零命中 `tokio::signal` / `ctrl_c`) | 无 |
|
| `CancellationToken` | 虚构类型 | 未使用;无优雅关闭、无信号处理(`src/` 零命中 `tokio::signal` / `ctrl_c`) | 无 |
|
||||||
| `Preview` 类型 / 4000 字符长邮件保护 / `[继续查看原文]` 按钮 / `/llm-fallback` 按钮 | 虚构类型与 UI | 全部未实现;授权后把全文交给 LLM,失败即回退前 300 字符,无任何截断标注或按钮 | 无 |
|
| `Preview` 类型 / 4000 字符长邮件保护 / `[继续查看原文]` 按钮 / `/llm-fallback` 按钮 | 虚构类型与 UI | 全部未实现;授权后把全文交给 LLM,失败即回退前 300 字符,无任何截断标注或按钮 | 无 |
|
||||||
| `LlmErr`(5 变体) | 虚构枚举 | 真实是 `AiError`,仅 3 个变体(`InvalidEndpoint` / `Request` / `Response`) | 无 |
|
| `LlmErr`(5 变体) | 虚构枚举 | 真实是 `AiError`,仅 3 个变体(`InvalidEndpoint` / `Request` / `Response`) | 无 |
|
||||||
| `LLM_TEMPERATURE` / `LLM_MAX_TOKENS` / `LLM_TIMEOUT_SECS` / `LLM_MAX_RETRIES` 环境变量 | 虚构环境变量 | `src/` 零命中;真实只有 `LLM_API_KEY` / `LLM_BASE_URL` / `LLM_MODEL` / `LLM_SUMMARY_TARGET_CHARS` 四个环境变量,加 `llm_timeout_ms` / `max_retries` 两个运行时参数 | 见 `docs/reference.md` AI 授权态一节 |
|
| `LLM_TEMPERATURE` / `LLM_MAX_TOKENS` / `LLM_TIMEOUT_SECS` / `LLM_MAX_RETRIES` 环境变量 | 虚构环境变量 | `src/` 零命中;真实有 6 个:`LLM_ENABLED` / `LLM_ALLOW_NET` / `LLM_API_KEY` / `LLM_BASE_URL` / `LLM_MODEL` / `LLM_SUMMARY_TARGET_CHARS`,加 `llm_timeout_ms` / `max_retries` 两个运行时参数 | 见 `docs/reference.md` AI 授权态一节 |
|
||||||
| `llm.call` tracing span | 虚构观测点 | `src/` 中除 `main.rs`(6 个事件)外**零 tracing 事件、零 span**;LLM 调用无任何日志 | 无 |
|
| `llm.call` tracing span | 虚构观测点 | `src/` 中除 `main.rs`(7 个事件)外**零 tracing 事件、零 span**;LLM 调用无任何日志 | 无 |
|
||||||
| 熔断器 / 半开态 / 熔断后 60s 冷却 / 规则兜底(Redis 共享计数) | 未实施设计 | 只有超时 + 重试;LLM 失败静默降级为前 300 字回退,无用户侧提示、无状态记录 | 无 |
|
| 熔断器 / 半开态 / 熔断后 60s 冷却 / 规则兜底(Redis 共享计数) | 未实施设计 | 只有超时 + 重试;LLM 失败静默降级为前 300 字回退,无用户侧提示、无状态记录 | 无 |
|
||||||
| 定时摘要 / 每日邮件摘要推送 | 未实施 | 未实现 | 无 |
|
| 定时摘要 / 每日邮件摘要推送 | 未实施 | 未实现 | 无 |
|
||||||
| 附件下载(`send_document` / `Blob/get` / 下载按钮) | 未实施 | JMAP 侧只读;邮件附件仅以 `has_attachment: bool` 形式出现 | 无 |
|
| 附件下载(`send_document` / `Blob/get` / 下载按钮) | 未实施 | JMAP 侧只读;邮件附件仅以 `has_attachment: bool` 形式出现 | 无 |
|
||||||
| `/flag` / `/unseen` / 发信命令 | 未实施 | 当前只识别 5 个意图(帮助 / 同意 / 摘要 / 普通消息 / 未识别) | 无 |
|
| `/flag` / `/unseen` / 发信命令 | 未实施 | 当前识别 6 个意图(帮助 / 同意 / 摘要 / 搜索 / 普通消息 / 未识别);其中 `/search` 已实现(`worker.rs:517`) | 无 |
|
||||||
| `Identity` 概念 | 未实施 | 账号识别只依赖 `ACCOUNT_ID`,无身份层抽象 | 无 |
|
| `Identity` 概念 | 未实施 | 账号识别只依赖 `ACCOUNT_ID`,无身份层抽象 | 无 |
|
||||||
| `run_mode.rs`(作为单独文件) | 虚构文件 | RUN_MODE 校验内联在 `main.rs::validate_env_or_exit` | 无 |
|
| `run_mode.rs`(作为单独文件) | 虚构文件 | RUN_MODE 由 `config.rs:338` 读取、在 `src/main.rs:75` 校验取值;不存在 `validate_env_or_exit` 这类函数 | 无 |
|
||||||
| docker-compose `message-weave health --addr` 示例 | 虚构命令 | 应用无 CLI 子命令;健康检查端点是 `GET /healthz` 与 `GET /ready` | 见 `docs/deployment.md` 的 Health-check 表 |
|
| docker-compose `message-weave health --addr` 示例 | 虚构命令 | 应用无 CLI 子命令;健康检查端点是 `GET /healthz` 与 `GET /ready` | 见 `docs/deployment.md` 的 Health-check 表 |
|
||||||
| 早期设计辩论问题(消息格式 / 长邮件阈值 / 附件策略 / Identity / 监控 / LLM 供应商 / 熔断等 17 条) | 已由代码回答 | 均已被实现的代码给出答案,不再属于待确认项 | 见 `docs/design.md` 的「已由代码回答的早期问题」一节 |
|
| 早期设计辩论问题(消息格式 / 长邮件阈值 / 附件策略 / Identity / 监控 / LLM 供应商 / 熔断等 17 条) | 已由代码回答 | 均已被实现的代码给出答案,不再属于待确认项 | 见 `docs/design.md` 的「已由代码回答的早期问题」一节 |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,12 @@ pub fn encryption_key_from_env() -> Result<[u8; 32], BotError> {
|
||||||
/// Runtime-only settings. SecretString prevents accidental formatting/logging of credentials.
|
/// Runtime-only settings. SecretString prevents accidental formatting/logging of credentials.
|
||||||
/// C-AUTH-APP-BASIC and REQ-SINGLE-ACCOUNT are intentionally represented explicitly.
|
/// C-AUTH-APP-BASIC and REQ-SINGLE-ACCOUNT are intentionally represented explicitly.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
// The listener binds from main.rs before Config exists, so the field is parsed
|
||||||
|
// here only for validation parity; main.rs reads PORT directly.
|
||||||
|
#[expect(
|
||||||
|
dead_code,
|
||||||
|
reason = "parsed in from_env() for validation parity; listener binds from main.rs"
|
||||||
|
)]
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub run_mode: String,
|
pub run_mode: String,
|
||||||
pub telegram: TelegramConfig,
|
pub telegram: TelegramConfig,
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,7 @@ async fn main() -> Result<(), error::BotError> {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tracing::info!(
|
tracing::info!(port, "starting webhook HTTP entrypoint");
|
||||||
port = bootstrap_config.port,
|
|
||||||
"starting webhook HTTP entrypoint"
|
|
||||||
);
|
|
||||||
let redis =
|
let redis =
|
||||||
RedisState::connect_with_encryption(redis_url.expose_secret(), Some(encryption_key))
|
RedisState::connect_with_encryption(redis_url.expose_secret(), Some(encryption_key))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue