Go to file
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
.github/workflows feat: initial MessageWeave release 2026-09-25 09:14:04 +08:00
cloudflare-worker worker: /reconcile 走 per-route 长超时且绝不故障转移(01a0e595) 2026-09-28 12:18:04 +08:00
docs worker: /reconcile 走 per-route 长超时且绝不故障转移(01a0e595) 2026-09-28 12:18:04 +08:00
src fix(notify): /ready JMAP 探针对齐真实客户端(Basic 认证 + 路径归一) 2026-09-28 10:37:22 +08:00
web feat(web): add SPA i18n with browser-language detection (zh/en, default en) 2026-09-27 12:53:36 +08:00
.dockerignore feat: initial MessageWeave release 2026-09-25 09:14:04 +08:00
.env.example refactor: rename project to MessageWeave 2026-09-25 10:29:18 +08:00
.gitignore chore(worker): rename proxy to lb and ignore build cache 2026-09-27 12:52:55 +08:00
AGENTS.md docs: /ready 端到端就绪语义同步 + 删除 Redis 错误映射虚构断言 2026-09-28 12:08:33 +08:00
Cargo.lock fix(redis): install explicit rustls crypto provider 2026-09-25 18:02:21 +08:00
Cargo.toml fix(redis): install explicit rustls crypto provider 2026-09-25 18:02:21 +08:00
Dockerfile chore(worker): rename proxy to lb and ignore build cache 2026-09-27 12:52:55 +08:00
README.md docs: /ready 端到端就绪语义同步 + 删除 Redis 错误映射虚构断言 2026-09-28 12:08:33 +08:00
README.zh-CN.md docs: /ready 端到端就绪语义同步 + 删除 Redis 错误映射虚构断言 2026-09-28 12:08:33 +08:00
hoststack.yaml docs(hoststack): add native Rust deployment config 2026-09-25 18:51:36 +08:00

README.md

MessageWeave

中文文档 / Chinese documentation → README.zh-CN.md

One Telegram bot per mailbox. New mail arrives as metadata; the original is always fetched read-only from your own JMAP server.

1. What it is

MessageWeave sits between a JMAP mail server (for example Stalwart) and a Telegram bot.

  • Inbound. A JMAP push subscription tells the service that a message changed. Nothing is polled at rest.
  • Outbound. Each event becomes a short, metadata-only Telegram notification: sender, subject, timestamp. Mail bodies are never sent to Telegram.
  • On demand. When you ask, the service reads the original through a fresh JMAP read call. Nothing is cached in between.
  • Optionally summarised. If you explicitly opt in, an OpenAI-compatible LLM returns a short summary. Requests, responses and attachment bytes are never persisted.

State is an architectural boundary here, not a preference.

  • Redis is the only state store. No database, no embedded store, no second store.
  • The process writes nothing to disk. No log files, no data files, no local volume.
  • No long-lived connections. No SSE, no WebSocket, no long polling.
  • Logs go to stdout only. The platform's collector owns log storage and retention.
  • What it is not. It is not a mail client, not a proxy, and not a relay. It forwards metadata to one bot; it does not read arbitrary URLs, open tunnels, or broker traffic.

2. 5-minute setup

Requires a reachable Redis 7 instance and a JMAP session plus a Telegram bot token.

cp .env.example .env           # fill REDIS_URL and CONFIG_ENCRYPTION_KEY
docker build -t messageweave:latest .
docker run --env-file .env -p 8080:8080 messageweave:latest

Then open http://localhost:8080 in a browser. The first-boot SPA calls POST /api/bootstrap, stores the business secrets in Redis, and creates the admin session. From that point the browser configures the service through Redis — no environment variable is read or written again for business configuration.

For local builds without Docker: cargo build --locked produces the message-weave binary.

If the service answers requests but does nothing useful, check the logs for a startup warning: a missing required variable does not crash the process. It serves a read-only setup router over in-memory state instead. See §3.

3. Configuration entry points

Three layers. They are not interchangeable.

Boot-time environment — 2 required, 2 optional.

Variable Required Default
REDIS_URL yes —
CONFIG_ENCRYPTION_KEY yes —
PORT no 8080
RUN_MODE no webhook

RUN_MODE accepts webhook or reconcile. .env.example ships only the two required variables; the rest are documented defaults.

Business configuration — Redis, written by the browser. POST /api/bootstrap is the one-shot trust bootstrap; PUT /api/business-config hot- reloads individual settings within about one second. The process never reads business secrets from the environment in normal operation.

Health. GET /healthz returns 200 unconditionally — it is liveness, not readiness. GET /ready returns 503 until configuration, Redis, and both upstreams (a JMAP session GET and Telegram getMe, 3s each) are reachable. Probe /ready, not /healthz, when deciding whether to route traffic — but /ready is the heavier probe (~3s worst case), so a monitor that must stay cheap should watch the gateway's aggregated /healthz.

4. Security boundary, in one sentence

All state lives in Redis; the process reads only two environment variables and writes nothing to disk.

One corollary matters for setup: the bootstrap one-shot token is the password component of REDIS_URL. The Redis ACL credential is therefore both the application connection credential and the trust root for bootstrap — keep it private and rotate it as one value.

5. Where to read more

Document What it answers
docs/design.md Why the system is shaped this way: data flow, JMAP semantics, Redis streams, AI consent rules
docs/deployment.md How to deploy: Dockerfile, secrets, Redis hosting, webhook/push/reconcile routing, multi-instance load balancing
docs/reference.md Single source of truth for verifiable facts — Redis keys and TTLs, error codes, routes, environment layers, budgets
docs/roadmap.md Gaps, blockers and the next phase
docs/retired.md What was tried and dropped — abandoned routes, unreleased designs, and names that never existed
AGENTS.md Contribution rules, hard boundaries, the stable-ID index for cross-document references

Facts that matter are traceable. If two documents disagree, docs/reference.md wins.