Commit Graph

11 Commits

Author SHA1 Message Date
anonsaber af13e507a0 docs: bump verification baselines to b2dbe7c
Four doc headers reference the code commit whose line anchors they were
read against. The trait deletion moved src/channel.rs (361 -> 340) and
src/domain.rs (16 -> 10), so the anchors are re-based to that commit.

GATE-DOCS: PASS 5/5 (VALIDATION 0, TABLE 0, ANCHOR 248 line-refs / 0,
SECTION-REF 216 / 0, SIZE CHECK 0).
2026-09-29 02:24:17 +08:00
anonsaber b2dbe7ccbb refactor: delete three zero-implementor 'stage 0 placeholder' channel traits
src/channel.rs had Channel / Notifier / MessageAdapter, each marked
#[expect(dead_code, reason = "稳定ID+阶段0占位…")]. All three had no
implementor, no caller and no dyn binding: Telegram outbound actually goes
through channel::telegram::TelegramClient::send_notification -> send_text,
held directly by worker.rs's MetadataWorker and notify.rs. The traits were
never on any path.

- delete the three traits + their #[async_trait] attributes
- drop the async_trait import from channel.rs (unused afterwards); the
  dependency stays in Cargo.toml, still used by 5 other files
- drop the domain import from channel.rs (its only uses were inside the
  deleted traits; the telegram module has its own Notification import)
- delete UserCommand from src/domain.rs -- its only consumer was Channel.
  Notification stays (worker.rs:234 builds it)
- rewrite channel.rs's module doc: "only an adapter boundary in stage 0
  (no bot commands yet)" was already false -- /help, /search, /summary
  and /ai on|off are all parsed in worker.rs::parse_intent

The three dead-code attributes were the last three #[expect(dead_code)] in
src/. Two #[expect(clippy::too_many_arguments)] remain in notify.rs; those
are justified lint suppressions on router wiring, not placeholders.

External contracts unchanged: HTTP routes, Redis keys, error codes and the
error envelope are untouched.

GATE-P0: fmt / check / clippy -D warnings green, 61 passed / 0 failed / 1
ignored (unchanged).
2026-09-29 02:23:38 +08:00
anonsaber 4c09310a2f docs: bump verification baselines to 2da11b9
Line anchors in reference.md / retired.md were re-read against the compat-cleanup
commit, and the test baseline in charter.md and roadmap.md moves 62 -> 61 to match
the deleted from_env()-driven case.

GATE-DOCS: PASS 5/5, ANCHOR 247 line-refs / 0, SECTION-REF 216 / 0.
2026-09-29 02:03:49 +08:00
anonsaber 2da11b9f92 docs+fix: delete the legacy env compat layer; sync every doc that described it
Code (src/config.rs 521->394, src/main.rs 232->200):
- Config::from_env() + required_secret / required_nonblank / env_bool deleted.
  config.rs now performs no std::env read; all business fields come from Redis
  business config (BusinessConfigWire -> validate_business_wire -> validate_nonblank).
- RUN_MODE deleted: variable, Config.run_mode field and the main.rs validation
  block. The two modes never changed runtime behaviour -- webhook and reconcile
  share one router, POST /reconcile is a standalone endpoint.
- jmap_password() accessor deleted; the field name is kept in
  BusinessConfig / BusinessConfigWire because it is real data, not a stub.
- Config.port and Config.redis_url deleted: both had zero readers, and port was
  hardcoded to 8080 in both constructors while the listener binds straight from
  the PORT env var, so the field could silently disagree with the real port.
  Config is now 6 fields: telegram / jmap / account_id / llm / auth / worker_token.
- redis_only() and from_business(value) signatures simplified accordingly.
- #[expect(dead_code)] count in src/ is now 0.

Test coverage: the from_env()-driven startup_rejects_blank_auth_secret case is
gone with the path it tested. The non-leak assertion it carried was reattached
to the real validation route (BusinessConfigWire -> TryFrom -> validate_nonblank)
with a stronger third assertion that the rejected value is not echoed either.

Docs: design.md 7.1 re-scoped to the Redis business config table; reference.md
5.1/5.3 rewritten (no legacy env path); deployment.md 5 and the runtime-mode
table rewritten; retired.md gains section 4 registering every deletion;
README x2, .env.example, charter.md updated.

GATE-P0: fmt / check / clippy -D warnings green, 61 passed / 0 failed / 1
ignored (was 62; the deleted case). GATE-DOCS: PASS 5/5, ANCHOR 247 line-refs /
0, SECTION-REF 216 / 0.
2026-09-29 02:02:44 +08:00
anonsaber 638c3a8cbc docs: consume all four self-checks; make the file-size rule enforceable
Every member's audit is now read and each real finding fixed, not just reported.

AGENTS.md §2.1
- The 500-line soft cap now has a machine-checkable exception: an over-limit
  file must carry a one-line `SPLIT-EVAL:` marker stating the split decision.
- Six files were over the cap with no justification: notify.rs 2036, state.rs
  1586, worker.rs 1045, config.js 1079, client.rs 601, config.rs 521. All now
  carry the marker. client.rs was missed by my own earlier line count, which
  did not descend into src/domain/jmap/.
- New validator scripts/docs_check/check_file_size.py; GATE-DOCS 4/4 -> 5/5.
  It proves the marker exists, not that the written reason is sound.
- Re-based 144 line anchors across six docs for the +1 line each file gained;
  every shifted anchor verified against the code, not just for non-blankness.

audit-arch.md
- design.md §6.1 module matrix omitted the `debug` module entirely; added.
- Seven numeric duplications in design.md now defer to reference.md §6.1 as
  the single authority instead of restating the values.
- reference.md §5.1 was missing DEBUG_TOKEN; §5.3 listed six recognised names
  and guessed `JMAP_*`. It now lists all 20 exactly as config.rs reads them.
- Corrected two wrong claims in the audit itself: debug.rs is compiled
  unconditionally, not under #[cfg(debug_assertions)] (there are no cargo
  features at all), and /debug/* exposes 7 routes, not the invented session
  list / resend / rebuild surface.

audit-fileplanner.md
- reference.md cited a nonexistent `jmap_service.rs`; replaced with the real
  adapter chain worker.rs -> domain/jmap.rs -> domain/jmap/client.rs.
- cloudflare-worker/README.md was outside every validator's scope; all four
  doc validators now include it (208 section refs, 250 line refs).
- README.zh-CN.md stated only half of what README.md says about RUN_MODE; the
  forward-placeholder sentence is now bilingual.
- charter.md §7 boundary table was missing rows for itself and for web/.

audit-rust.md / audit-frontend.md
- Error envelope corrected: `error` is a bare machine code, no `message`.
- Status table, AI-consent labels and ~15 stale or phantom facts fixed against
  notify.rs; `TelegramClient::SendMessage` and `web_config` phantoms removed.
- GET|PUT /api/enabled forwarded by the worker so the SPA kill switch works.
- Ten UI strings that called the admin credential the Redis ACL password now
  say CONFIG_ENCRYPTION_KEY.
- POST /healthz returns 405; dead HTTP_STATUS removed.
- audit_anchors.py now flags identifiers that do not exist in the file they
  are anchored to.

charter.md
- `SAF-ENABLE-FLAG` registered (85 IDs); `SAF-ADMIN-SESSION` row corrected;
- `REQ-PUSH-TYPES` registered; the stale `admin_session_digest` removed.

Gates: GATE-DOCS PASS 5/5 (250 line-refs, 208 section refs, 0 errors);
GATE-P0 62 passed / 0 failed / 1 ignored, baseline unchanged; worker and SPA
36 tests, 0 failures.

Real Stalwart / Telegram / managed Redis verification stays blocked on missing
credentials and remains labelled as unverified.
2026-09-29 01:16:11 +08:00
anonsaber 526a73b8b2 docs: bump anchor verification baseline to b70d188
main.rs shrank by 2 lines in b70d188, moving 6 line anchors. The feature
markers for /search (bfe0fd8) are left untouched as historical references.
2026-09-28 22:34:52 +08:00
anonsaber b70d188864 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).
2026-09-28 22:33:27 +08:00
anonsaber c746ead184 docs: 同步 /search 落地与 ⑥-a/⑥-b 后的断言、重锚 worker.rs/reference.md
⑥-d 文档同步(01a0e760-b6f5),无代码改动。

/search 落地(bfe0fd8)
- design.md 新增 §5.7「邮件搜索」:Filter::text + SearchSnippet/get、高亮降级、
  纯文本出站、失败映射、截断上限、正文级高亮在锁定版本做不到(含原因)
- design.md §2.3 接口表、§3.2 search_emails 方法行、§5.6 Intent 6 种、§10.5 待办收口
- reference.md §8「Not implemented」:删掉「/search 未实现 / SearchSnippet 零命中」
  两条已失效断言,改为「正文级高亮不可能」这一真实缺口
- reference.md §1.2 补 /search 触发词(仅前缀匹配,不授予授权、不写 Redis 键)
- AGENTS.md 阶段 5 标注搜索已完成

⑥-a/⑥-b 遗留的陈旧断言(本轮清掉)
- design.md §12.8 速率:429 从「不做专门的自动退避」改为按 parameters.retry_after
  退避(截断到 60s 预算,缺字段回退指数退避 250ms→4s)
- design.md §10.6 已知边界 + §9:XAUTOCLAIM 阈值从「批大小 × 300s」改为按运行配置
  推导,公式、300s 下限与 6h 上限指向 reference.md §6.3
- reference.md §6.3 重写阈值公式,新增 §6.4 搜索上限常量表

重锚
- worker.rs:569 → 613(help_message,⑥-c 插入后下移)
- AGENTS.md 版本基线 → bfe0fd8;retired.md 验证基线 → bfe0fd8;
  retired.md read_batch 锚点 343→384 / 379→425 / 376→394-401
- design.md §3.5 交叉引用 §12.9 → §12.8
- SAF-DEBUG-ALLOWLIST 保持「不改代码」原状,按产品决策未动
2026-09-28 21:45:05 +08:00
anonsaber 399b26918e docs: ④ /debug/* 配套文档 + 全量锚点重锚 + AGENTS v3 版本基线(01a0e6de)
## 锚点重锚(afa6cec 之后)

④ 对 notify.rs 的编辑使一批 `notify.rs:N` 引用整体失效。全量重扫 8 个 md、
165 个 src 锚点,0 硬错误(越界 / 区间全空 / 单行指空行三类全查)。修正要点:
- reference.md(30+ 处):dedup/committed/inflight/ratelimit 键调用点、
  error_response 320-328、error_response_with_id 1087-1093、readiness 161、
  router 1275 + routes 1304-1318、worker 330、main.rs 40/50/34/78-81、
  NoopWorker 由 notify.rs 更正为 worker.rs:142-144、
  PROBE_TIMEOUT 73 与 tokio::join! 147、state.rs 639。
- deployment.md:149-152→227-230、159/172→239/252、index.js 40-55→45-60。
- retired.md:704→717、887-890→897-900、326→330、873→884。
- roadmap.md:95→175;design.md:main.rs 109→128。
- cloudflare-worker 侧 4 处 stale:ROUTE_METHODS 45-60、404 77-78、
  405 80-83、503 85-93。

## /debug/* 文档

- deployment.md 新增 §2.1:双因子门禁、7 条路由状态码表、/debug/config 字段清单、
  三条部署约束、生产建议;§5 env 表补 --debug / DEBUG_TOKEN。
- README.md / README.zh-CN.md 各加一段「远程联调面(可选,默认关闭)」,指向 §2.1。
- design.md 新增 §7.6「为何默认绝对关闭」:双因子挂载、404 而非 401
  (401 会泄漏路由存在性)、无默认开启回退、不在网关白名单内 fail-closed。

## 文档纠错(与任务书相反,已按源码核实)

- /debug/worker 是 GET 不是 POST(debug.rs:79)。
- /debug/config 不是「只出布尔」:明文回 jmap.{session_url,username}、
  telegram.chat_id、llm.{base_url,model} 与全部 timeout/retry,
  仅 5 个凭据字段出 *_configured。文档措辞改为「不含凭据,不是全脱敏」。
- 门禁位置更正:--debug 读 main.rs:99、DEBUG_TOKEN 读 main.rs:100-103、
  merge 在 notify.rs:1320-1321(无 DEBUG_BASE 常量,路径为字面量)。

## AGENTS.md

- 版本基线 v3(代码 afa6cec / 文档 e502c40),三处文档基线号 c5bc7b8→afa6cec。
- 新增 2 个 ID:SAF-DEBUG-ORIGIN-ONLY(不在网关 14 条白名单,Worker 404
  route not forwarded,只能直连 origin)、SAF-DEBUG-ALLOWLIST。
- §6 deployment.md 行、§7 六行 ID 定义点回填文档位置。

## 未决项登记

- roadmap.md 新增:SAF-DEBUG-ALLOWLIST —— debug.rs:232-235 的白名单判定
  「非空且不含」导致业务白名单为空时 /debug/notify 的 chat_id 不拦截。
  本轮不改代码(该面默认关闭且需 Bearer,空白名单下主产品本就接受任意 chat);
  若要收紧需改为「空白名单则拒绝」+ 补测试 + 跑 gate。

## 校验

文档校验器 0 error;负向对照断链/死锚点/奇数反引号/页内锚点 4 类注入全命中。
48 张 markdown 表 0 处单元格数不符。行数合计 2415。纯文档,未触碰 src/ web/
cloudflare-worker/。代码基线仍 afa6cec,gate 49 passed / 0 failed / 1 ignored。
2026-09-28 16:20:18 +08:00
anonsaber cc645020d9 docs: /ready 端到端就绪语义同步 + 删除 Redis 错误映射虚构断言
两件事一批提交(全部 docs-only,无代码改动):

① /ready 语义升级为端到端就绪(对应 commit c5bc7b8 的实际行为)
- ARCH-READY-BASELINE 从「配置完整性 + Redis 可达性」改为:配置完整性
  + Redis 可达 + JMAP session 探针 + Telegram getMe 探针,四者全过返 200
  与就绪报告,任一失败返 503 + 标准错误 envelope + Retry-After: 30
- 探针细节:各 PROBE_TIMEOUT 3000ms、tokio::join! 并行、最坏约 3s
  (先前文档写「串行 / 最坏 6s」是错的,已全量修正 10 处,含 AGENTS.md /
  design.md / deployment.md / roadmap.md / reference.md / README 中英)
- LB 探针超时下限从「> 6s」改为「> 3s(建议 >= 10s)」
- JMAP 探针实际行为:取 JMAP_SESSION_URL 的 origin 后 GET
  {origin}/.well-known/jmap 并带配置的 Basic 认证(不是打裸 session URL);
  原因是裸 URL 不带认证会永远 not-ready,进而让 ingress 停止路由
- deployment.md 新增 /ready 出站 egress 硬依赖说明:egress 不通则长期 503,
  而 reqwest 以 default-features=false 编译不解析 HTTPS_PROXY
- reference.md Retry-After 规则改准:error_response 仅在 retry=true 时设置;
  error_response_with_id 无条件设置但仅用于 push register/disable 的
  push_state_unavailable / push_destroy_failed(恒 503);503 错误码表补上
  这两个此前漏掉的码
- GATE-READY-DEPS 门禁已闭合,从 AGENTS.md 与 roadmap.md 删除该 ID

② 删除「Redis 错误映射」这条虚构断言(代码缺口 4 -> 3)
- roadmap.md 与 design.md:634 的同一条断言删除;design.md 脚注「4 条」->「3 条」
- 根因:消费入口是 HTTP handler worker(notify.rs),不是后台循环
  —— 全仓 src/ 零 select!、无信号处理、无常驻 worker 进程;read_batch 与
  retry_or_dlq 的 Err 均经 ? 上抛,调用方一律返 503 + retryable=true。
  原先描述的「循环不因单次失败退出」这个前提本身不成立
- retired.md 新增一条虚构断言记录,完整保留原始断言与推翻依据
- roadmap 验收待办同步:/ready 端到端探测转已完成,并注明两个探针是可复用
  纯函数、④ 远程 debug 应直接复用同一实现,不要另写一套

③ 顺手修正本轮代码移动导致的失效行号锚
- retired.md:refresh_business_config 628->704;lock:push-register 810-819->887-890;
  register_push 668->873;notify::push_handler 改引真实函数名 jmap_push /
  worker / reconcile(原行号 250/97/136 全错,且 push_handler 非真实函数名);
  worker safelist fail-closed index.js:69-82 -> :77-92
- reference.md:router 1238-1265 -> 1250-1277;/worker 314 -> 326;
  路由表补上此前漏掉的 /、/assets/config.js、/assets/styles.css

基线:三处「验证基线」(roadmap.md:5、retired.md:7、reference.md:6)由
ed93358 更新为 c5bc7b8。

校验:8 个 md 代码块偶数、无行尾空格、无 CR;5 个文件全部表格列数一致
(剥掉 code span 后再计数);相对链接与锚点全部可达。
Rust 门禁不受影响(无 src/ 改动),基线仍为 45 passed / 0 failed / 1 ignored。
2026-09-28 12:08:33 +08:00
anonsaber e35cc64ce3 docs: 清理废弃/虚构条目,新增 retired.md 独立废弃记录
新增 docs/retired.md(114 行)作为唯一废弃/虚构条目登记处:
- 12 条未采用的路线(teloxide / feature 集 / teloxide-core 回退 /
  grammers / BotError 六个未落地变体 / 速率桶+429 分支 / 多步 FSM /
  模块子树拆分 / telegram_dispatch 测试 / docker healthcheck 示例)
- 原 teloxide 候选对比评估记录 + FSM 五态状态表(保留决策依据,非当前事实)
- 24 条虚构条目(文档曾写、src/ 从未有):幽灵模块树、delivery:pending 键、
  check_config_reload、PushVerification、CancellationToken、Preview、
  LlmErr、四个不存在的 LLM 环境变量、llm.call span、熔断/半开设计等
- 2 条已删除文档(docs/todo.md、HANDOFF.md,后者从未进入 git 历史)
- 3 步重启流程

docs/design.md 850 → 764 行:17 处目标形态改写为可核实的当前事实。
重点:§4 压缩并指向 retired.md;§5.2 补 Channel/Notifier/MessageAdapter
三 trait 零实现;§6 树改为真实 12 文件结构;§8.1 换成真实 src/error.rs
(原目标形态 6 变体整体移至 retired.md);§8.3 明标无 tracing/metrics/
优雅关闭;§10.0 GATE-P0 已过;§10.5 明标 notify.rs 平铺单文件;
§11.4-11.7 的 17 条 Q 收敛为一节(均已由已实现代码回答);
§12 全节重写(LlmClient 非 AiClient、AiError 3 变体、无熔断、
无附件、无长邮件保护、read_email 实取全文送 LLM);§12.8/§12.9 合并。

docs/reference.md:删 delivery:pending:{stream} 换真实 stalwart:{jmap,telegram}
流名;删 cloudflare-worker/src/commands.rs 论断;键锚点校正;补网关
fail-closed 无开关说明。

AGENTS.md:§6 注册 retired.md;新增 §6.3 幽灵名规则(文档出现的模块/函数/
键必须是 src/ 真实存在,要留不存在的名字必须同时登记 retired.md);
teloxide 专属措辞通用化为「渠道层不引入第三方 Bot 框架类型」;
保留「不得声称使用未引入依赖」约束。

docs/roadmap.md:item 2/26 按真实数字重写(18 条路由 12 条经 Worker 转发,
不转发的 6 条中仅 2 条需业务直连);决策待定补处置建议。
README / README.zh-CN 阅读表加 retired.md,保持 1:1 对等。

待决策项收敛结论:真剩 2 项(Q-DEP-A Worker 白名单、Q-DEP-B 平台选型),
均为上线时运维选择,不影响代码结构与门禁,可挂起不产生技术债。

src/、web/、cloudflare-worker/ 零改动。基线仍 45 passed / 0 failed / 1 ignored。
2026-09-27 21:53:58 +08:00