Commit Graph

98 Commits

Author SHA1 Message Date
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 6334ae541d docs(charter): move both baselines to 638c3a8
638c3a8 added one marker line to the top of six source files and re-based all
144 line anchors in the same commit, so that commit is the baseline for both
the code anchors and the document section refs. e0882c7 and b70d188 are no
longer valid baselines.
2026-09-29 01:17:29 +08:00
anonsaber c8af429ffe fix(mode): restore executable bit on the doc validators
audit_anchors.py lost its exec bit and check_file_size.py was created without
one, so the scripts/ directory no longer had uniform permissions. run_all.sh
invokes them through python3 so nothing broke, but every sibling validator was
100755.
2026-09-29 01:16:43 +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 e0882c7c68 docs: de-projectize AGENTS.md into language-agnostic norms; add docs/charter.md
AGENTS.md (276 -> 239 lines) is now a language-agnostic engineering-norms
document: code style, config and secrets handling, build environment,
testing gates, document governance. It states no project fact and names no
language, framework, dependency, or file. Verified: zero hits for JMAP,
Telegram, Redis, axum, Cloudflare, LLM, *.rs, and every project env var.

All project-specific content moved to the new docs/charter.md (~276 lines):
project goals, locked technology choices, 23 numbered security invariants,
implementation stages, prohibitions, test/acceptance gates with the full
GATE-P0 command and baseline, document boundary rules, and the stable ID
registry.

docs/charter.md §8 is the single authority index for stable IDs: 83 rows,
no duplicates, ID | definition file | one-line statement | category. Four
IDs that were referenced across the docs but never registered are now
registered: ARCH-STATE-REDIS, C-REDIS-EXTERNAL, GATE-UPTIME-KUMA, GATE-DOCS.
All previously registered IDs are preserved.

Defect fixed while rebuilding the registry: SAF-ADMIN-SESSION cited
src/config.rs `admin_session_digest`, a name that has 0 hits in src/ -- a
phantom identifier that survived in the authoritative registry since before
this restructure. Corrected to `session_digest` (src/config.rs:7). All 8
distinct backticked symbols in the registry's definition-file column now
verify against src/.

Two false project facts removed from the docs (both claimed the SPA writes
through POST /api/bootstrap, which web/config.js never calls -- 0 hits; the
actual path is PUT /api/business-config): README.md, README.zh-CN.md,
docs/reference.md. The first successful write creates the configuration and
later writes hot-reload it; POST /api/bootstrap remains the one-shot
automation endpoint.

Cross-reference redirections in 9 files: README.md, README.zh-CN.md,
docs/deployment.md, docs/design.md, docs/reference.md,
cloudflare-worker/README.md, cloudflare-worker/src/index.js,
cloudflare-worker/wrangler.toml now point project facts at charter.md and
language-agnostic quality rules at AGENTS.md.

Four validators extended to cover docs/charter.md: DOCS lists in
audit_anchors.py, check_tables.py, validate_docs.py, check_sec_refs.py, plus
charter.md added to check_sec_refs.py's TARGET_BEFORE regex and to
validate_docs.py's index-file cross-doc exemption.

Gates: GATE-DOCS PASS 4/4 (VALIDATION 0 errors, TABLE STRUCTURE 0
mismatches, ANCHOR AUDIT 227 line-refs / 0 errors, SECTION-REF AUDIT 191
refs / 0 errors). GATE-P0 PASS, baseline unchanged 62 passed / 0 failed /
1 ignored. Anchor audit proves line reachability only, not that the line
contains what the prose claims.
2026-09-28 23:39:26 +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 c75338b305 docs: 修正 5 个漂移的行锚点(notify.rs 超时校验 411-414 → 491-494)
audit_anchors.py 只证明锚点可达,证明不了内容;本轮对 156 个唯一锚点
(150 个 .rs + 6 个 .js)做了逐条内容复核,抓到 2 处指向错误:

- reference.md §6.1:四个超时参数的「validated notify.rs:411/412/413/414」
  全部指向 notify.rs 的投递去重错误分支(error_response 的 4 行调用),
  而非校验谓词。真实位置在 491-494,原引用偏移恰好 80 行——是旧版本
  notify.rs 的残留锚点。
- reference.md §2.2:consent:ai:{chat_id} 的 EX 写为 state.rs:512,
  实际 .arg("EX") 在 511,512 是 TTL 取值行。

代码未改动,锚点基线 bfe0fd8 仍然成立,AGENTS.md 基线不 bump。
GATE-DOCS 仍 4/4 clean。
2026-09-28 22:04:58 +08:00
anonsaber a2297c21e8 tools: 固化文档校验器为 GATE-DOCS(此前只存在于 /tmp,保证不可复现)
四个校验器原先散落在 /tmp,本会话 /tmp 就被清过一次(validate_docs.py
中途消失),仓库里没有任何引用——「222 个行号锚点 0 error」这个结论在脚本
被清之后就无从再证,属于静默漂移。现固化进 scripts/docs_check/:

- validate_docs.py  代码块围栏闭合 / 行内反引号偶数 / 本地链接与 #锚点可达
- check_tables.py   表格表头、分隔行、数据行列数一致(行内代码里的 | 不计)
- audit_anchors.py  每个 foo.rs:N / foo.rs:N-M 锚点指向存在且非空的目标行
- check_sec_refs.py 每个 §N.N 交叉引用解析到归属文档的真实标题
- run_all.sh        驱动脚本,任一失败即 exit 1,仿 GATE-P0 的用法

改动说明(相对 /tmp 原版)
- ROOT 由硬编码绝对路径改为 Path(__file__).resolve().parents[2],干净 clone
  即可运行,无需改任何路径。四个文件已 diff 确认仅此一处差异。
- audit_anchors.py 加了 docstring 写明能力边界:只证明锚点可达,不证明该行
  内容与所述一致,无法发现「指向错行」的锚点。
- run_all.sh 用 grep -m1 '^===' 取摘要而非 head -1:validate_docs.py 摘要前
  带一个空行(原版自带瑕疵,保留原样以免偏离已验证版本),按位置取会取错。

双向验证:干净树 PASS/exit 0;注入 worker.rs:99999 与 §99.9 后分别被
audit_anchors 与 check_sec_refs 报出、exit 1;git checkout 还原后回到 PASS。

AGENTS.md 记录跑法与「可达 ≠ 准确」的区分。GATE-P0(cargo)不受影响,本次
无任何 src/ 改动。
2026-09-28 21:58:07 +08:00
anonsaber c2f6f32093 docs: roadmap 补全 XAUTOCLAIM 阈值公式因子,AGENTS 文档基线 bump
- roadmap.md:26「超时预算 × 安全系数 × 批大小」→ 写全真实公式
  (max_retries+1) × (jmap+telegram+llm 超时) × 2 × 批大小,并注明 300s 下限 /
  6h 上限与 reference.md §6.3 交叉引用;原来的压缩写法漏了 attempts 因子
- AGENTS.md 版本基线:文档 399b269(⑤)→ c746ead(锚点核验基线);代码仍 bfe0fd8。
  本次改动仅触两处文档文字、未引入任何新行号锚点,故 c746ead 仍是有效的
  锚点核验基线
- design.md §10.5 历史已修复清单里的「XAUTOCLAIM 空闲阈值按批大小缩放」按原样
  保留:它是当时的真实状态,非当前状态,不改写历史条目
2026-09-28 21:49:31 +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 bfe0fd88aa feat: Telegram /search 邮件搜索(email/query Filter + SearchSnippet/get 高亮)
⑥-c 落地代码缺口:/search <关键词>

实现
- JmapBackend::search_emails 加 trait 默认实现(mock 后端返回 InvalidRequest,
  真机后端用 Email/query + text Filter 取 ID,再 SearchSnippet/get 取高亮)
- JmapService::search_emails 暴露搜索入口,与 list_emails_page 共享超时/错误映射
- worker:Intent::Search + /search 分发分支 + /help 说明行

降级路径(不 panic、不回栈给用户)
- SearchSnippet/get 不支持(unknownMethod/timeout)→ 仍返回匹配 ID 列表,
  渲染降级为高亮片段暂不可用
- 无匹配 → 没有找到匹配「…」的邮件。
- JMAP 侧失败 → SearchReply::Retry,交由协调器 503 + Retry-After 重试
- 搜索关键词为空 → 引导提示,不发请求

安全边界
- subject/preview 只做展示:strip_mark_tags 剥 <mark> 高亮(大小写不敏感、
  未闭合标记丢弃,不泄漏原始标记),unescape_html_entities 单趟解码,
  先剥标签再解码实体以保留邮箱正文里字面 <mark>
- 无匹配 ID / 主题 / preview 分别有截断上限(120/120/160 字符)
- 每条命中带 email_id,可直接接 /summary <email_id>
- 中文关键词仅前缀匹配,避免帮我搜一下发票被劫持成搜索、或覆盖授权意图

测试 +7(55 → 62 passed, 0 failed, 1 ignored)
2026-09-28 21:09:06 +08:00
anonsaber 6c99ce51ab state: XAUTOCLAIM 空闲阈值改由运行超时配置推导,封顶 6h(01a0e75f-f9b1)
现状:read_batch 里 const SINGLE_EVENT_CEILING_MS = 300_000 是硬编码,
而真实单事件上限 = (max_retries+1) x (jmap+telegram+llm 三个 timeout),
运维把任一超时调大后实际耗时可能远超 300s → 空闲阈值欠保护,
多实例会把还在处理的条目提前认领,造成重复投递。

改动(ROADMAP 缺口③「多实例重复窗口」):
- single_event_ceiling_ms(config) 纯函数:(max_retries+1) x (jmap+telegram+llm) x 2
  (SAFETY),并用 .max(SINGLE_EVENT_CEILING_FLOOR_MS=300_000) 兜底,
  零值配置不会把窗口缩到 legacy 常量之下。
- reclaim_idle_threshold_ms(config, count) 纯函数:count.max(1) x 单事件上限,
  再 .min(RECLAIM_IDLE_THRESHOLD_CAP_MS = 6h) 防误调把恢复拖到天级。
- read_batch 调用 self.get_outbound_config()(trait 方法,同实现内已有,未改签名),
  Ok 走推导值,Err 回退 count x 300_000(不破坏投递主流程、不传播 Redis 错误)。

既有闸门不动:notify.rs 投递前查 delivery:committed、60s delivery:inflight 在途租约、
retry_or_dlq 3 次。本次只修阈值本身,性质保持「永不丢、只可能重复;单实例不受影响」。

:356-364 那段注释改写为描述真实机制(旧文「~300s 上限」已不准确),
未写入任何未经测试证明的断言。

测试:3 个纯函数单测覆盖 —— 默认配置 (15+10+30)x4x2=440s >= 300s floor、
大超时配置阈值更大、零值配置不缩到 floor 之下、荒唐超时被封顶 6h、count=0 用 1 事件兜底。
GATE-P0 全绿:fmt --check、check --locked、clippy --locked --all-targets -D warnings、
test --locked → 55 passed / 0 failed / 1 ignored。
2026-09-28 18:38:00 +08:00
anonsaber f4cae00adc telegram: 重试之间加指数退避,429 尊重服务端 retry_after(01a0e75f-f7e3)
现状:send_text 的 for _ in 0..=max_retries 重试循环在两次尝试之间零延迟,
任何 5xx/超时/429 都以最短间隔连续打 API,429 场景下反而更容易触发二次限流。

改动(ROADMAP 缺口②「Telegram 429 退避」):
- backoff_delay_ms(attempt: u8) 纯函数:起步 250ms、每轮 x2、单轮封顶 4s。
  调用方 sleep 只走这一个出口,便于单测直接断言序列而无需真实等待。
- retry_after_ms(body) 纯函数:解析 429 响应体 parameters.retry_after(秒),
  转毫秒后钳到 60s 上限;缺字段/非数字/非正/非有限值一律回退到指数退避。
- 总退避预算上限 60s:耗尽后 delay 归零不再 sleep,避免单次 send_text 长时间挂起。
- 429 单独分支,错误串改为 telegram request rate limited (429) 以便区分限流与一般失败。

语义不变:max_retries 仍 cap 5、每次请求仍受 telegram_timeout_ms 约束、
返回 Result<(), String>、错误串不泄漏 token。

测试:client() 通过 with_sleep_override 注入 no-op sleep,退避逻辑完整走通但不真实等待。
新增 3 个测试:429 端到端(mock 返回 parameters.retry_after=1,断言 2..=3 次请求)、
backoff 序列(250/500/1000/2000/4000 后封顶)、retry_after 解析与钳制。
GATE-P0 全绿:fmt --check、check --locked、clippy --locked --all-targets -D warnings、
test --locked → 52 passed / 0 failed / 1 ignored。
2026-09-28 18:13:08 +08:00
anonsaber 0ffe19cfe6 docs: README 中英双版补「保持 debug 关闭」的运维规则
用户结论:SAF-DEBUG-ALLOWLIST 那项不需要修,只要控制好不打开 debug 即可;
这条运维规则应写进面向用户的 README,不能只留在 deployment.md。

README.md / README.zh-CN.md(1:1)在既有「远程联调面」段各补两句:
- 唯一的运行规则就是不要打开——启动命令不加 `--debug`、环境变量不设
  `DEBUG_TOKEN`,缺任意一个 `/debug/*` 就不存在。
- 若确需为一次排障临时开启,先配好 chat 白名单——白名单为空时
  `POST /debug/notify` 的测试发送不受限到任何 chat。

无新链接、无新锚点、无表格,纯散文;`docs/deployment.md` §2.1 引用沿用段内
既有引用。代码未改动,gate 基线不变(afa6cec,49/0/1)。
文档校验器 0 error(exit 0)。
2026-09-28 17:18:59 +08:00
anonsaber 0522d6901d docs: AGENTS.md v3 基线文档哈希更正为 399b269(⑤ 收口提交)
e502c40 是这行基线写作时的文档 HEAD,但不含本轮 ⑤ 落地的 deployment §2.1、
design §7.6、README 联调段与全量锚点重锚——按它 checkout 看不到本轮文档状态,
具误导性。399b269 才是 ⑤ 的落地提交。

本次仅元数据修订,不改变任何行号锚点基线:行号锚点以代码 afa6cec 为准,
代码侧已冻结(reference.md:6 / retired.md:7 / roadmap.md:5 三处指向代码哈希,
不动)。§7 中 MOD-DEBUG、SAF-DEBUG-GATE 两处指向 docs/design.md §7.6 的
定义点追加保持保留:索引里查不到「为何默认关闭」的落点就失去意义。
2026-09-28 16:37:47 +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 e502c40a9e docs: SPA 管理凭据 trust-root 同步(README/zh-CN/reference 三处措辞 + 锚点修正)
承接 800d6e0(SPA 凭据改用 CONFIG_ENCRYPTION_KEY):

- README.md §5、README.zh-CN.md §5:trust-root 段落补齐「配置密钥即管理密钥」
  的事实陈述,消除「Redis ACL 密码」的残留耦合叙述。
- docs/reference.md §3 Trust root:1:1 重写为配置密钥派生路径,移除不存在的
  "refresh loop" 叙述(实际为 refresh_business_config 单次调用 + guard)。
- docs/reference.md §3 锚点修正:expires_in 引用 notify.rs:700→:787
  (refresh_business_config 现位于 :717,expires_in 在 :787)。

交付方:文件规划助手(任务 01a0e379)。文档校验器 0 error,负向对照 4/4。
2026-09-28 15:08:04 +08:00
anonsaber afa6cec8ef debug: 新增 /debug/* 远程联调面,双因子门禁默认绝对关闭(01a0e319)
新增 src/debug.rs:/debug/ping、/config、/redis、/jmap、/telegram、/worker 六个只读
探针 + POST /debug/notify 一条测试出站消息。

SAF-DEBUG-GATE 双因子,缺任一即完全不挂载路由(请求落到通用 404,无"未配置即放行"
降级):
- 启动命令带 --debug(main.rs 最小 std::env::args 解析,未新增依赖)
- Secret 环境变量 DEBUG_TOKEN 非空

两层执行点:main.rs 推导 debug_token 并传入 router builder(None 则 debug_router 不被
merge);debug.rs 侧 auth_token 缺失时直接返回空 Router。

SAF-DEBUG-AUTH:每个 handler 先做 Authorization: Bearer DEBUG_TOKEN 常数时间比较
(复用 worker_authorized),失败 401,鉴权通过前不产生任何副作用。

探针只读侧全部复用现成实现,不另写一套:Redis 走 is_enabled,JMAP 走
probe_jmap_session,Telegram 走 probe_telegram_get_me,出站走唯一构造点
business_client(notify.rs 新增 pub(crate))到 send_text,/debug/notify 与
refresh_business_config 共用同一条客户端构造路径。

SAF-NO-SECRET-ECHO / SAF-LOG-PURITY:/debug/config 只回"是否配置"、计数与超时等
运行时参数,bot token / JMAP password / worker / webhook / reconcile token / LLM key
一律不出现在响应体;DEBUG_TOKEN 只在鉴权内部暴露,不回显、不入日志,日志仅一条
"debug endpoints enabled" 且不含令牌值。allowlist 用 chat_allowlist_snapshot 取生效值
(运行时业务配置优先、否则启动值),/debug/notify 显式 chat_id 不在 allowlist 则 403。

ARCH-LB-WORKER 白名单不含 /debug/*,该面仅供直连 origin 联调,公网入口不可达。

AGENTS.md 登记 4 个稳定 ID:MOD-DEBUG / SAF-DEBUG-GATE / SAF-DEBUG-AUTH /
REQ-DEBUG-ENDPOINTS(§2 第 19 条硬边界、§5 测试条、§7 索引 4 行)。

调试路由测试(4 项,notify.rs + debug.rs 内):Bearer 放行 200、缺失/错误令牌 401、
未提供 DEBUG_TOKEN 因子时 /debug/* 完全不注册而落 404、/debug/notify POST 未认证 401。

门禁四段全绿(rust:1-slim-bookworm):cargo fmt --all -- --check / cargo check --locked
/ cargo clippy --locked --all-targets -- -D warnings / cargo test --locked =
49 passed / 0 failed / 1 ignored。
2026-09-28 15:00:49 +08:00
anonsaber 800d6e0d00 notify: SPA 管理凭据改用 CONFIG_ENCRYPTION_KEY,解除与 Redis ACL 密码耦合
原实现把 SPA 管理凭据派生自 REDIS_URL 的 ACL 密码,并在 handler 侧用
`.is_empty()` 直接拒绝空凭据。托管 Redis 常见配置是 TLS-only 无密码,
那种情况下 `.is_empty()` 恒为真,管理界面永久 401 —— `POST /api/bootstrap`
与 `POST /api/admin/session` 双双不可用,无法完成初始化。

改为直接取 CONFIG_ENCRYPTION_KEY:它本就是启动必填 Secret(64 位 hex /
32 字节,见 config::encryption_key_from_env),启动时已校验,因此生产环境
不可能出现空凭据,handler 侧的空串守卫随之失效,一并移除。Redis ACL 密码
回归本职:只用于数据库连接。

- main.rs:env 只读一次(encryption_key_raw),admin_token 与已校验值不可能
  分叉;删除 Url::parse(redis_url).password() 分支。
- notify.rs:AppState 字段 bootstrap_token → admin_token(4 个构造点同步),
  两处 handler 删除空串守卫,bootstrap() 文档不再称 Redis ACL bootstrap。
- 测试更名 bootstrap_rejects_missing_admin_credential:断言语义不变(测试态
  空凭据收到 Bearer 仍 401),名称改为反映新语义。

门禁全绿:cargo fmt --check / check --locked / clippy --all-targets -D warnings
/ test --locked = 45 passed, 0 failed, 1 ignored。
2026-09-28 12:43:33 +08:00
anonsaber 82794c95cf worker: /reconcile 走 per-route 长超时且绝不故障转移(01a0e595)
POST /reconcile 是后端同步长任务:后端持集群级单飞锁 lock:reconcile(初租 300 s,
期间每 30 s 心跳续租至 90 s,notify.rs:159/172),但网关原先只有全局超时 10 s、
最多 2 次尝试。一次完整对账很容易撑过网关窗口被误判失败,随后故障转移到第二实例,
而第二实例必然立刻返回 409;客户端既拿不到 204 也拿不到 409,只能看到
"all backends failed"。

改动:
- handleFetch 为 /reconcile 单独覆盖超时与尝试次数:超时取 LB_RECONCILE_TIMEOUT_MS
 (默认 320000 ms,大于锁初租 300 s + 心跳余量),maxAttempts=1 绝不故障转移
  (切实例只会撞锁,没有任何收益)
- 其余快路径完全不受影响,仍用全局 LB_REQUEST_TIMEOUT_MS / LB_MAX_ATTEMPTS
- 新增测试:记录 setTimeout 收到的延迟来断言 /reconcile 走 320000 ms,并用 503 响应
  断言它只尝试 1 个 origin;同时断言 /webhook/tg 仍用 5000 ms 且仍故障转移到第二
  实例;并断言 env 覆盖生效而 maxAttempts 恒为 1
- worker README 补 LB_RECONCILE_TIMEOUT_MS 说明与 /reconcile 例外
- deployment.md §6.3.1 删掉「请把 LB_REQUEST_TIMEOUT_MS 调到大于锁租期」的旧 workaround
  建议(现已由 per-route 覆盖解决),§10 worker 故障转移条目补例外,secret 清单补新变量
- 顺手清掉 health.js 注释里已退役的 GATE-READY-DEPS,改为 ARCH-READY-BASELINE

验证:cloudflare-worker node --test 29/29 通过(原 28 + 新增 1);
web config.test.mjs 6/6 通过。无 src/ 改动,Rust 门禁不受影响。
2026-09-28 12:18:04 +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 c5bc7b8d82 fix(notify): /ready JMAP 探针对齐真实客户端(Basic 认证 + 路径归一)
架构师复核指出两点会致全站不可用的缺陷(notify.rs):

- 探针未带认证:真实 jmap_client 以 .credentials() 取 session,Stalwart
  session 端点需认证;无认证必 401 → /ready 永久 503 → ingress 停止路由。
  改为从 business_runtime 快照取 jmap_username/jmap_password,
  basic_auth 随请求发送(零新增依赖,凭据仅入请求头,绝不日志)。
- session URL 未归一:配置若是裸 origin,GET 根路径必 404 → 误报。
  改为复用 normalize_session_url(client.rs 改为 pub(crate))并请求
  标准 /.well-known/jmap,与 JmapClientBackend::connect_with_runtime 同路径。

顺带:两探针 tokio::join! 并行,/ready 最坏耗时 3s→3s(原 6s)。

门禁 4/4 绿(45 passed, 0 failed, 1 ignored)。
2026-09-28 10:37:22 +08:00
anonsaber c0aab5e966 feat(notify): /ready 接入 JMAP session 与 Telegram getMe 端到端探测
ARCH-READY-BASELINE 从「配置完整性 + Redis 可达性」升级为端到端就绪:

- PROBE_TIMEOUT=3000ms 各探针独立超时(notify.rs:69)
- cached_jmap_session_url / cached_bot_token 先克隆后 await,
  避免跨 .await 持有 RwLockReadGuard(非 Send)
- probe_jmap_session:GET JMAP session URL,error_for_status
- probe_telegram_get_me:bot token 仅用于拼 URL,绝不日志
- ready():四者全过返 200 + 就绪报告 JSON(jmap/telegram 真实结果),
  任一失败 503 + Retry-After: 30(标准错误 envelope)
- 探针只读、无状态写入;token 永不回显(SAF-NO-SECRET-ECHO)

门禁:fmt / check / clippy / test 4/4 绿(45 passed, 0 failed, 1 ignored)。
2026-09-28 10:15:52 +08:00
anonsaber a39ebcf9d1 feat(worker): safelist 纳入 push/register 与 push/disable
网关白名单此前无条件 fail-closed,后端注册的 18 条路由中只有
POST /api/push/register 与 POST /api/push/disable 两条需要业务流量却未
被转发,导致 PWA 订阅/退订必须绕过网关直连后端。

- SAFE_ROUTES 12 → 14 条(backends.js:9-24)
- ROUTE_METHODS 同步补两条 POST(index.js:40-55)
- 头注释路由清单同步
- backends.test.js 转发集合断言补齐两条
- index.test.js 新增转发端到端用例(含鉴权头透传路径)

单入口边界:两条路由仍经 SAFE_ROUTES 无条件校验,未放宽白名单语义。
2026-09-28 10:02:18 +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
anonsaber 0e10591534 docs(design): §8.1 BotError 目标形态代码块加消歧注释
Q5 一致处理:代码块内补一行「// 目标形态(teloxide 未引入)」,与上方免责互证,消除重构后唯一例外。
2026-09-27 20:49:53 +08:00
anonsaber 24cfbb45b0 docs: 重构文档体系 — README 瘦身中英对等 + 新增 reference.md 权威事实表
- README.md / README.zh-CN.md 瘦身至 ~100 行,五节 1:1 对等(是什么/5分钟/配置入口/安全边界/去哪读更多)
- 新增 docs/reference.md:Redis 键+TTL+写者+读者、错误 envelope+Retry-After、后端路由与网关白名单矩阵、env 三层等单一权威表(顶部 verified against ed93358)
- 新增 docs/roadmap.md 取代 docs/todo.md(只放缺口/阻塞/阶段目标/待决策)
- docs/design.md 保留架构语义,可查表改引 reference.md;修正 /search 未实现、XAUTOCLAIM 阈值矛盾、teloxide 幽灵引用等 9 处标注
- docs/deployment.md §8.1 重写,删除依赖不存在子命令/curl 的必然失败 healthcheck 示例
- AGENTS.md §6 登记 reference.md/roadmap.md + 冲突仲裁「以 reference.md 为准」+ 中英同轮同步/公共 API 变更规则
2026-09-27 18:55:38 +08:00
anonsaber ed93358e29 feat(notify): unify /ready not-ready 503 into the error envelope
- GET /ready: ready -> 200 + diagnostic report (unchanged success body);
  not ready -> 503 + {"error":"service_unavailable","request_id":id}
  + Retry-After: 30, consistent with the other public entry points
- drop the legacy {"status":"not_ready"} diagnostic block from the 503 body
  (backward compatibility not required)
- HTTP 200/503 semantics preserved, so Uptime Kuma HTTP(s) monitors
  (status-code based) keep working
- sync docs: README.md, README.zh-CN.md, docs/design.md, docs/deployment.md,
  AGENTS.md, docs/todo.md
2026-09-27 13:11:29 +08:00
anonsaber a8fad19daa docs: default README to English, reconcile docs, drop HANDOFF
- README.md is now English; add README.zh-CN.md with cross-links; both versions
  cover the same 40 sections and the same key facts
- correct stale docs/design.md 10.5.1 and docs/todo.md to the current code
  (XAUTOCLAIM threshold, malformed-entry ACK, error envelope now done)
- replace the temporary HANDOFF.md with docs/todo.md (unfinished work + blockers)
- adjust docs/deployment.md and AGENTS.md accordingly
2026-09-27 12:56:04 +08:00
anonsaber 6868d47cd0 feat(web): add SPA i18n with browser-language detection (zh/en, default en)
- detect locale from navigator.language (zh* -> zh, otherwise en) at startup;
  <html lang> switches accordingly; English is the default
- index.html carries English default text plus data-i18n /
  data-i18n-placeholder / data-i18n-aria attributes
- config.js holds inline en/zh dictionaries, t(key, vars) and detectLocale();
  dynamic Chinese strings become t() calls with {var} interpolation
- no new asset files (src/web.rs serves only config.js and styles.css);
  stays a build-less vanilla-JS single page
- config.test.mjs updated to the English defaults; adds dictionary key-parity
  and markup key-coverage tests
2026-09-27 12:53:36 +08:00
anonsaber f16b747672 feat(notify): unify public-route error envelope and add push route tests
- all public write routes now return {"error":code,"request_id":id};
  retryable 503 responses add Retry-After: 30; 204 and success bodies unchanged
- GET /ready keeps its structured diagnostic JSON (Uptime Kuma runtime contract)
- add HTTP tests for the push register/disable routes
2026-09-27 12:53:22 +08:00
anonsaber 309a38c503 fix(worker,state,jmap,config): close independent-review findings
- worker/state: paginate Email/changes by widening maxChanges on the same
  sinceState (jmap-client 0.4.2 has no upToId) to avoid skipping a batch when
  advancing per page; make dedup+enqueue atomic via claim_dedup_and_enqueue
  (Lua SET NX EX then XADD); scale the XAUTOCLAIM idle threshold by batch size;
  XACK payload-less malformed stream entries out of the PEL via ack_malformed;
  fail closed on unknown streams (Err); tighten SET NX EX TTL to .max(1);
  derive the CSPRNG fallback owner-token from time+PID+counter instead of a
  constant; clear the push verification digest on push:disable
- worker: /help menu now lists only the consent triggers the parser accepts
- jmap/config: complete client and configuration validation
2026-09-27 12:53:05 +08:00
anonsaber 4f5eb9293d chore(worker): rename proxy to lb and ignore build cache
- cloudflare-worker: unify terminology proxy -> lb/forward
  (PROXIED_ROUTES -> LB_ROUTES, proxyWithFailover -> forwardWithFailover,
  proxy.js -> lb.js, related tests and README); renames/wording only, no behavior change
- Dockerfile: crates.io index mirror rsproxy.cn -> mirrors.ustc.edu.cn
- .gitignore: ignore build verification cache .gate-cache/ (cargo registry + rustup, ~1.5G)
2026-09-27 12:52:55 +08:00
anonsaber 956ef6a1d3 feat(config): add global Redis-backed enable switch 2026-09-25 20:11:35 +08:00
anonsaber a5710331fa docs(hoststack): add native Rust deployment config 2026-09-25 18:51:36 +08:00
anonsaber 4ecfa44485 fix(redis): install explicit rustls crypto provider 2026-09-25 18:02:21 +08:00
anonsaber a57cb4f75d fix(redis): enable TLS for Upstash connections 2026-09-25 17:45:24 +08:00
anonsaber e18cecca16 refactor: remove legacy project identity 2026-09-25 10:40:10 +08:00
anonsaber c75858fda0 refactor: rename project to MessageWeave 2026-09-25 10:29:18 +08:00
anonsaber 8c32892206 fix: preserve desktop setup card width 2026-09-25 10:24:43 +08:00
anonsaber 9064fc5f1d fix: hide admin authorization in setup mode 2026-09-25 10:05:47 +08:00
anonsaber 8a24ec7e9e fix: report all missing setup requirements 2026-09-25 09:42:15 +08:00
anonsaber e5ec3aef4c feat: provide configuration setup mode 2026-09-25 09:30:45 +08:00
anonsaber fc43c6bf93 build: use rsproxy for container dependencies 2026-09-25 09:21:43 +08:00
anonsaber dceecaf2f4 feat: initial MessageWeave release 2026-09-25 09:14:04 +08:00