docs: state the gateway's actual design intent — a free-tier load balancer, not a security layer

Reframe the Cloudflare Worker gateway across the entry points where a
reader forms an impression of it, in both languages:

- README §2 / §5 (en+zh): the gateway is optional and exists because
  Cloudflare's Load Balancer product is not on the free plan. Drop the
  comparison row that listed 'only the 19 safelisted paths' against
  'every route' as if the safelist were a benefit; replace it with
  backend reachability, which is identical either way, and move the
  route-count fact to its own neutral row. Add one sentence to §5 that
  the gateway is outside the secret boundary.
- deployment.md §10 (en+zh): purpose is load balancing, not protection;
  no intent to hide or harden the backends, no access control at the
  edge, the allowlist is not a firewall.
- cloudflare-worker README (en+zh): new 'why it exists' / 'why it is
  not a security feature' block, plus a 'not a security layer' bullet.
  Drop 'access-hiding capability' from the non-capabilities list, since
  claiming invisibility as something we refuse to provide implies it is
  the interesting axis.
- reference.md §4 / §9.2 (en+zh) and deployment.md zh §2: stop saying
  bootstrap and /debug/* are 'not exposed' / 'kept off the public path'.
  They are simply not in the route set, which is why a request to the
  Worker never reaches them — reachable at the backend's own address
  either way.
- charter ARCH-LB-WORKER (en+zh): 'only external entrance' -> 'only
  *registered* entrance', which is what C-LB-SINGLE-REG-URL actually
  says, and backends stay directly reachable.

SAF-LB-PASSTHRU already records the security-side invariant (no auth
or body rewriting, backend may be directly connected to the public
network), so no new stable ID is needed. No code changes; GATE-DOCS
6/6.
This commit is contained in:
anonsaber 2026-10-01 17:47:49 +08:00
parent 1c1b1e644e
commit ed0f8172eb
10 changed files with 93 additions and 34 deletions

View File

@ -32,12 +32,23 @@ State is an architectural boundary here, not a preference.
One image, one Redis, one SPA. The only difference is what sits in front and which URL you hand
to Telegram and Stalwart.
**The Cloudflare Worker gateway is optional.** Nothing in the backend requires it, and the
backend runs identically with or without it. It exists for one reason: Cloudflare's Load
Balancer product is not available on the free plan, so a Worker is the way a free-plan account
puts load sharing and failover in front of several backends.
**It is also not a security layer, and it is not meant to be one.** There is no intent to hide
the backends and no intent to harden them; the origins stay directly reachable whether the
gateway is present or not, and no access control is added at the edge. The fixed route safelist
is what makes it a load balancer for a bounded set of paths, not a firewall.
| | Form A: backend origin only | Form B: behind the Cloudflare Worker gateway |
|---|---|---|
| What answers requests | one backend host | one Worker URL, N backend origins |
| Failover | none — a down origin stays down | bounded: 2 attempts by default, only timeout and `5xx` retry |
| Callback URL you register | `https://a.example` | `https://lb.example` |
| Reachable routes | every route, including `/api/bootstrap` and `/debug/*` | only the 19 safelisted paths |
| Backend access | direct from the network | equally direct — the gateway adds no access control |
| Gateway route set | n/a — there is no gateway | fixed at 19 paths; unknown paths answer `404` |
| Health to watch | `GET /ready` on the origin | `GET /ready` proxied, plus `GET /healthz-worker` for the gateway aggregate |
| Extra moving parts | none | one Worker deploy, one origin list |
| Docs | `docs/deployment.md §2` | `docs/deployment.md §10`, `cloudflare-worker/README.md` |
@ -56,8 +67,10 @@ business config, because a callback can land on any instance and no instance can
the one asked for (`C-LB-SHARED-SECRETS`). Mis-matched secrets show up as a random 401, not as
a routing error.
`POST /api/bootstrap` and `/debug/*` stay origin-only in both forms, so neither is exposed
through the gateway.
`POST /api/bootstrap` and `/debug/*` are not in the gateway's route set, so requests addressed
to the Worker URL never reach them. That is the safelist working as designed for the paths that
need to be load balanced; it is not hiding anything, because the same origins are reachable at
their own addresses in both forms.
## 3. 5-minute setup
@ -136,6 +149,11 @@ startup. It is only compared in constant time, and is never echoed, logged, or s
Redis ACL password, if you have one, authenticates the Redis connection alone and is not
the credential for any HTTP endpoint.
The optional Cloudflare Worker gateway in front of the backends is not part of this boundary.
It holds no credential and checks no auth header, and the backends behind it are directly
reachable either way, so nothing about it changes what stands between an attacker and your
mail. See §2.
## 6. Where to read more
| Document | What it answers |

View File

@ -29,23 +29,28 @@ MessageWeave 位于 JMAP 邮件服务器(例如 Stalwart)与 Telegram bot
同一个镜像、同一个 Redis、同一套 SPA。唯一的区别是前面放什么,以及你把哪个 URL 交给 Telegram 和 Stalwart。
**Cloudflare Worker 网关是可选项。** 后端没有任何东西依赖它,有它和没有它跑起来完全一样。它存在的理由是单一的:Cloudflare 的 Load Balancer 产品在免费套餐上不可用,所以 Worker 是免费套餐账户在多个后端前面做负载均衡与故障转移的办法。
**它也不是安全层,本意不是拿来干这个的。** 没有隐藏后端的意图,也没有加固后端的意图;网关在不在,origin 都同样可以直接访问,边缘上也没有加任何访问控制。固定的路由白名单是让它在有限的一组路径上做负载均衡的手段,不是防火墙。
| | 形态 A:仅后端 origin | 形态 B:前置 Cloudflare Worker 网关 |
|---|---|---|
| 谁应答请求 | 一个后端主机 | 一个 Worker URL 对 N 个后端 origin |
| 故障转移 | 没有——origin 挂了就一直挂着 | 有界——默认 2 次尝试,只重试超时与 `5xx` |
| 你注册的回调 URL | `https://a.example` | `https://lb.example` |
| 可达路由 | 全部路由,含 `/api/bootstrap` 与 `/debug/*` | 只有 19 个白名单路径 |
| 后端可达性 | 网络上直连可达 | 同样直连可达——网关不加访问控制 |
| 网关路由集 | 不适用——没有网关 | 固定 19 个路径;未知路径返回 `404` |
| 要看哪个健康接口 | origin 上的 `GET /ready` | 透传的 `GET /ready`,外加网关聚合 `GET /healthz-worker` |
| 额外组件 | 无 | 一个 Worker 部署、一份 origin 列表 |
| 文档 | `docs/deployment.md §2` | `docs/deployment.md §10`、`cloudflare-worker/README.zh-CN.md` |
单 origin 且没有 HA 要求时选形态 A;两个及以上 origin、或想在滚动与重建某个实例时保持一个稳定的回调 URL,就选形态 B。
切换是一次重新注册,不是迁移。注册是幂等的,注册新地址会注销旧订阅,所以从形态 A 换到形态 B(或反过来)只是 SPA 里改一个字段。不要同时注册两个 origin:注册槽只有一个,平台入口必须保持内网(`C-LB-SINGLE-REG-URL`)。
切换是一次重新注册,不是迁移。注册是幂等的,注册新地址会注销旧订阅,所以从形态 A 换到形态 B(或反过来)只是 SPA 里改一个字段。不要同时注册两个 origin:注册槽只有一个,后端的平台入口不对外注册(`C-LB-SINGLE-REG-URL`)。注意这里说的是「不注册」,不是「不可达」——两种形态下 origin 都同样可以直接访问。
形态 B 里没有任何 secret 搬到网关。它不持有任何业务凭据,也不校验任何认证头。真正要求一致的是每个后端都携带同一组 `SAF-AUTH-*` secret 与同一份加密业务配置——因为回调可能落在任何一个实例上,而任何实例都证明不了自己是被点名的那一个(`C-LB-SHARED-SECRETS`)。凭据不一致表现为随机 401,而不是路由错误。
`POST /api/bootstrap` 与 `/debug/*` 在两种形态下都只保留在 origin 直连,不经过网关暴露。
`POST /api/bootstrap` 与 `/debug/*` 不在网关的路由集里,所以发给 Worker URL 的请求到不了它们。这是白名单在需要被负载均衡的那些路径上按设计工作,并不是在隐藏什么——两种形态下 origin 都同样可以通过自己的地址直接访问。
## 3. 五分钟跑起来
@ -107,6 +112,8 @@ Telegram 发送。它由双因子开关控制——进程必须带 `--debug` **
有一条推论与部署直接相关:SPA 管理凭据——也就是 bootstrap 的信任根——就是 `CONFIG_ENCRYPTION_KEY` 本身,即启动时提供的那个 32 字节 hex 值。它只做常数时间比较,绝不回显、记录或落盘。Redis ACL 密码(如有)只认证 Redis 连接本身,不是任何 HTTP 端点的认证凭据。
前置在后端前面的可选 Cloudflare Worker 网关不在这个边界内。它不持有任何凭据,也不校验任何认证头;网关在不在,后端都同样可以直接访问,所以它不会改变攻击者与你的邮件之间的任何东西。见 §2。
## 6. 去哪读更多
| 文档 | 回答什么 |

View File

@ -10,11 +10,21 @@ multi-origin failover (HA/LB sub-project). **Pass-through model**: the Worker do
business payloads and forwards the request as-is to the backend https origins; only *timeout /
5xx* triggers a bounded failover to another instance.
> This component is a **safelist-restricted edge load balancer**: the route set is fixed (19
> whitelisted paths), the backend origins are fixed at deploy time and https-only, and every
> unknown path gets a `404`. It only forwards requests to those fixed backends and fails over
> between them. It does not accept an arbitrary target host and provides no general-purpose
> traffic-relay or access-hiding capability.
> This component is a **load balancer for the fixed set of routes MessageWeave needs behind one
> URL**: the route set is fixed (19 paths), the backend origins are fixed at deploy time and
> https-only, and every unknown path gets a `404`. It only forwards requests to those fixed
> backends and fails over between them. It does not accept an arbitrary target host and
> provides no general-purpose traffic-relay capability.
>
> **Why it exists.** Cloudflare's Load Balancer product is not available on the free plan, so
> this is the way a free-plan account gets multi-origin load sharing and failover in front of
> its MessageWeave backends. That is the whole reason for it, and the gateway is optional: the
> backend runs identically without it.
>
> **Why it is not a security feature.** There is no intent here to hide the backends and no
> intent to harden them. The origins stay directly reachable with or without the gateway and no
> access control is added at the edge, so the route safelist is not a firewall and nothing in
> this document is meant to be read as protection.
> Design basis: `docs/design.md §11.3` (`NFR-HA-MULTI-INSTANCE`) and `docs/deployment.md §10`.
> Security baseline and prohibitions: `docs/charter.md §3`, `docs/charter.md §5`; stable-ID
@ -29,6 +39,10 @@ business payloads and forwards the request as-is to the backend https origins; o
the same set of `SAF-AUTH-*` secrets — `RECONCILE_TOKEN`, `TG_WEBHOOK_SECRET` and the
encrypted business config — because a callback can land on any instance and no instance can
prove which one was asked (`C-LB-SHARED-SECRETS`). Per-instance secrets mean a random 401.
- **Not a security layer.** It is a load balancer, not an access-control boundary. The backend
origins are directly reachable with or without it, and nothing here hides or hardens them. The
safelist's fail-closed answer on unknown paths is what makes the balancer well-defined for a
fixed set of paths, not a protection to be relied on.
- **No origin discovery.** Origins come from the deploy-time origin list only.
- **Zero runtime dependencies.** Pure ES2022 plus the platform `fetch`, `Headers`, `Request`,
`Response` and `URL`. `wrangler` is a devDependency, used only by `check`, `deploy` and

View File

@ -4,7 +4,11 @@
面向一个或多个 MessageWeave 后端的统一 HTTPS 入口,带**有界多 origin 故障转移**(HA/LB 子项目)。**透传模型**:Worker 不解析业务载荷,把请求原样转发给后端 https origin;只有*超时 / 5xx* 才触发有界的故障转移到另一个实例。
> 本组件是**基于白名单限制的边缘负载均衡器**:路由集合固定(19 个白名单路径)、后端 origin 在部署时固定且仅允许 https、任何未知路径一律 `404`。它只把这些固定后端之间的请求做转发与故障转移,不接受任意目标主机,也不提供任何通用的流量中转或隐身能力。
> 本组件是**针对 MessageWeave 需要放在同一个 URL 后面的一组固定路由的负载均衡器**:路由集合固定(19 个路径)、后端 origin 在部署时固定且仅允许 https、任何未知路径一律 `404`。它只把这些固定后端之间的请求做转发与故障转移,不接受任意目标主机,也不提供通用的流量中转能力。
>
> **它的用途是负载均衡,不是防护。** Cloudflare 的 Load Balancer 产品在免费套餐上不可用,所以这是免费套餐账户在它的那些 MessageWeave 后端前面做多 origin 负载分摊与故障转移的办法。这就是它存在的唯一理由,网关是可选的:后端有它和没有它跑起来完全一样。
>
> **它不是安全特性。** 这里没有隐藏后端的意图,也没有加固后端的意图。网关在不在,origin 都同样可以直接访问,边缘上也没有加任何访问控制,所以路由白名单不是防火墙,本文档任何一处都不该被读成防护。
> 设计依据:`docs/design.md §11.3`(`NFR-HA-MULTI-INSTANCE`)与 `docs/deployment.md §10`。
> 安全基线与禁令:`docs/charter.md §3`、`docs/charter.md §5`;稳定 ID 注册表 `docs/charter.md §8`。
@ -13,6 +17,7 @@
- **只转发。** method、headers(含认证头)、body 一律原样转发。Worker 自身不做任何认证,也不改写任何内容。
- **无 secret。** 没有 Telegram bot token,没有 JMAP 密码,没有 session secret。它收到什么认证头就转发什么,自己一个都不校验。真正共享的是后端之间必须一致的同一组 `SAF-AUTH-*` secret——`RECONCILE_TOKEN`、`TG_WEBHOOK_SECRET` 与加密后的业务配置——因为回调可能落在任何一个实例上,而任何实例都证明不了自己是被点名的那一个(`C-LB-SHARED-SECRETS`)。凭据不一致的表现是随机 401,而不是路由错误。
- **不是安全层。** 它是负载均衡器,不是访问控制边界。后端 origin 有没有它都同样直连可达,这里既没有隐藏也没有加固它们。白名单对未知路径的 fail-closed 应答,是让负载均衡器在一组固定路径上定义明确的手段,不是可以依赖的防护。
- **不做 origin 发现。** origin 只来自部署时的 origin 列表。
- **零运行时依赖。** 纯 ES2022 加平台提供的 `fetch`、`Headers`、`Request`、`Response`、`URL`。`wrangler` 是 devDependency,只用于 `check`、`deploy`、`dev`。
- **零状态。** 不连 Redis、不连 JMAP、不连数据库(`C-NO-DB`、`C-REDIS-ONLY-STATE`)。唯一的可变状态是 isolate 内的健康探测缓存。

View File

@ -265,7 +265,7 @@ Answer only one question per document; **Tables, lists, or values ​​may not
| `SAF-JMAP-URL` | docs/design.md §7.1 / §4 | JMAP URL constraints: HTTPS only, disallow inline credentials, deny dangerous query | Security |
| `REQ-JMAP-RAW-MULTIPART` | docs/design.md §3.2/§10.1 | `read_email` Multi-part Original text: Splice the parts "with part_id and bodyValue" in text_body order; no available part → clear error | Requirements |
| `GATE-G1-JMAP-READONLY` | docs/design.md §10.1 / §6 | G1 access control: read-only adapter **Code has been implemented** (mock + `#[ignore]` real machine test), **to be real `cargo test -- --ignored jmap::` verification** | Process |
| `ARCH-LB-WORKER` | docs/deployment.md §10 | Multi-instance LB/HA: Free Cloudflare Worker as the only external entrance + failover, the backend is multi-platform with the same mirror | Architecture |
| `ARCH-LB-WORKER` | docs/deployment.md §10 | Multi-instance LB/HA: a free-plan Cloudflare Worker as the only *registered* entrance, plus failover. The backends stay directly reachable and it adds no access control | Architecture |
| `C-LB-SINGLE-REG-URL` | docs/deployment.md §10.1 | Telegram/Push/Cron only registers the stable URL of the Worker; the backend platform entrance does not register externally | Constraints |
| `C-LB-SHARED-SECRETS` | docs/deployment.md §10.3 | Multiple instances must share the same set of `SAF-AUTH-*` secrets, otherwise a random 401 | Constraints |
| `SAF-LB-PASSTHRU` | docs/deployment.md §10.3 / §3 | Trust model = transparent transmission: Worker does not overwrite authentication; the backend must continue fail-closed verification (the backend may be directly connected to the public network) | Security |

View File

@ -265,7 +265,7 @@ Stalwart 与 Telegram 的业务凭据已在真机联调通过——出站、查
| `SAF-JMAP-URL` | docs/design.md §7.1 / §4 | JMAP URL 约束:仅 HTTPS、禁止内嵌凭据、拒绝危险 query | 安全 |
| `REQ-JMAP-RAW-MULTIPART` | docs/design.md §3.2/§10.1 | `read_email` 多 part 原文:按 text_body 顺序拼接"有 part_id 且 bodyValue"的部分;无可用部分→明确错误 | 需求 |
| `GATE-G1-JMAP-READONLY` | docs/design.md §10.1 / §6 | G1 门禁:只读 adapter **代码已实现**(mock + `#[ignore]` 真机测试),**待真实 `cargo test -- --ignored jmap::` 验证** | 流程 |
| `ARCH-LB-WORKER` | docs/deployment.md §10 | 多实例 LB/HA:免费 Cloudflare Worker 作唯一对外入口 + 故障转移,后端为多平台同镜像 | 架构 |
| `ARCH-LB-WORKER` | docs/deployment.md §10 | 多实例 LB/HA:免费套餐 Cloudflare Worker 作唯一*注册*入口 + 故障转移;后端仍可直接访问,网关不加访问控制 | 架构 |
| `C-LB-SINGLE-REG-URL` | docs/deployment.md §10.1 | Telegram/Push/Cron 只登记 Worker 的稳定 URL;后端平台入口不对外登记 | 约束 |
| `C-LB-SHARED-SECRETS` | docs/deployment.md §10.3 | 多实例必须共享同一组 `SAF-AUTH-*` secret,否则随机 401 | 约束 |
| `SAF-LB-PASSTHRU` | docs/deployment.md §10.3 / §3 | 信任模型=透传:Worker 不改写鉴权;后端必须继续 fail-closed 校验(后端可能被公网直连) | 安全 |

View File

@ -42,7 +42,8 @@ only port `8080`. Do not mount a data or log volume. Full image contents are in
Open the backend's configuration page at its origin for initial setup. Use
`CONFIG_ENCRYPTION_KEY` to establish the admin session, then complete the one-time bootstrap
form. The bootstrap route is not exposed by the Worker gateway. The one-time bootstrap and
form. The bootstrap route is not in the gateway's route set, so it is only reachable at the
backend's own address. The one-time bootstrap and
admin-session handlers are `src/notify.rs:834` and `src/notify.rs:1187`; saving business
configuration uses `src/notify.rs:694`. Later edits can use the protected page through the Worker URL.
@ -176,10 +177,18 @@ The stable IDs in this section point to the project charter's registry.
## 10. Optional Cloudflare Worker gateway
The Worker provides one public HTTPS entry point for multiple backend origins. All backends
must share the same Redis and business configuration. The Worker forwards only its fixed
route allowlist; bootstrap and `/debug/*` remain origin-only. Detailed route facts are in
the [gateway route matrix](reference.md#4-gateway-vs-backend-route-matrix).
The gateway is optional; nothing in the backend requires it. It provides one public HTTPS
entry point for multiple backend origins. All backends must share the same Redis and business
configuration. Detailed route facts are in the
[gateway route matrix](reference.md#4-gateway-vs-backend-route-matrix).
Its purpose is load balancing, not protection. Cloudflare's Load Balancer product is not
available on the free plan, and this is how a free-plan account puts load sharing and failover
in front of several origins. There is no intent to hide the backends and no intent to harden
them: the origins are directly reachable whether the gateway is present or not, and no access
control is added at the edge. The fixed route allowlist is what bounds the paths the balancer
forwards — bootstrap and `/debug/*` are not in it, so requests addressed to the gateway never
reach them — but it is not a firewall.
### 10.1 Topology

View File

@ -41,7 +41,7 @@ docker run --rm --env-file .env -p 8080:8080 messageweave:latest
在原点打开后端的配置页面进行初始设置。使用
`CONFIG_ENCRYPTION_KEY` 建立管理会话,然后完成一次性引导
表单。Worker 网关不会公开 bootstrap 路由。bootstrap 和管理会话处理器位于
表单。bootstrap 路由不在网关路由集内,因此只能在后端自己的地址上访问。bootstrap 和管理会话处理器位于
`src/notify.rs:834` 与 `src/notify.rs:1187`;业务配置保存处理器位于
`src/notify.rs:694`。后续修改可通过 Worker URL 打开受保护的配置页。
@ -156,10 +156,16 @@ Telegram 和 Stalwart 的稳定 Worker URL;不要在网关处公开凭据。
## 10. 可选的 Cloudflare Worker 网关
Worker 为多个后端源提供一个公共 HTTPS 入口点。所有后端
必须共享相同的Redis和业务配置。 Worker 仅转发其固定的
路由白名单; bootstrap 和 `/debug/*` 仍然仅限原始。详细路线事实在
[网关路由矩阵](reference.zh-CN.md#4-网关与后端路由对照表)。
网关是可选项,后端没有任何东西依赖它。它为多个后端 origin
提供一个公共 HTTPS 入口。所有后端必须共享同一 Redis 和业务配置。
详细的路由事实在[网关路由矩阵](reference.zh-CN.md#4-网关与后端路由对照表)中。
它的用途是负载均衡,不是防护。Cloudflare 的 Load Balancer 产品在免费
套餐上不可用,这就是免费套餐账户在多个 origin 前面做负载均衡与故障转移
的办法。没有隐藏后端的意图,也没有加固后端的意图:网关在不在,origin
都同样可以直接访问,边缘上也没有加任何访问控制。固定的路由白名单只是
限定负载均衡器转发哪些路径——bootstrap 和 `/debug/*` 不在其中,所以发给
网关的请求到不了它们——但它不是防火墙。
### 10.1 部署拓扑

View File

@ -319,8 +319,8 @@ backend pool returns **503** rather than passing the request through.
| Path | Why it is absent from the gateway |
|---|---|
| `POST /api/bootstrap` | One-shot trust bootstrap; kept off the public path |
| `/debug/*` (7 routes) | Opt-in remote-debug surface (`SAF-DEBUG-GATE`); absent from `SAFE_ROUTES`, so it is reachable **only** by talking to the backend origin directly |
| `POST /api/bootstrap` | One-shot trust bootstrap; not in the route set, so it is only reachable at the backend's own address |
| `/debug/*` (7 routes) | Opt-in remote-debug surface (`SAF-DEBUG-GATE`); not in `SAFE_ROUTES`, so it is reachable **only** at the backend origin's own address |
`GET, PUT /api/enabled` (the `SAF-ENABLE-FLAG` kill switch) **is** forwarded, because the
admin SPA serves it at the Worker URL and toggles it from the service card (`loadEnabled`
@ -700,9 +700,9 @@ variable semantics and Redis business configuration fields are in [§5](#5-envir
Telegram, JMAP, allowlist, worker, reconcile, and LLM business settings are Redis-resident,
not process environment variables. The field list and validation rules in [§5.2](#52-redis-resident-business-configuration)
are authoritative. A successful initial save can use the one-shot `/api/bootstrap` endpoint
on the backend origin; that route is not exposed through the Worker. Subsequent edits use the
protected configuration API.
are authoritative. A successful initial save can use the one-shot `/api/bootstrap` endpoint at
the backend's own address; it is not in the gateway's route set, so a request addressed to the
Worker never reaches it. Subsequent edits use the protected configuration API.
### 9.3 Callback registration and scheduled work

View File

@ -314,8 +314,8 @@ all:`SAFE_ROUTES` 中缺少的路径返回 **404**,一个已注册的路径
|路径|为什么网关上没有它 |
|---|---|
| `POST /api/bootstrap` |一次性信任引导;远离公共道路|
| `/debug/*`(7 条路线)|选择加入远程调试界面(`SAF-DEBUG-GATE`); “SAFE_ROUTES”中不存在,因此**只能**通过直接与后端源通信来访问它 |
| `POST /api/bootstrap` | 一次性信任引导;不在路由集内,只能在后端自己的地址上访问 |
| `/debug/*`(7 条路线)| 选择加入的远程调试界面(`SAF-DEBUG-GATE`);不在 `SAFE_ROUTES` 中,因此**只能**在后端源自己的地址上访问 |
“GET, PUT /api/enabled”(“SAF-ENABLE-FLAG”终止开关)**被转发,因为
管理 SPA 在 Worker URL 上提供服务,并从服务卡(`loadEnabled`
@ -690,9 +690,9 @@ HostStack 部署不使用仓库的 Dockerfile;镜像构建和运行时由平
Telegram、JMAP、allowlist、worker、reconcile 和 LLM 业务设置驻留在 Redis 中,
不是进程环境变量。 [§5.2](#52-存储在-redis-中的业务配置)中的字段列表和验证规则
是权威的。成功的初始保存可以使用一次性“/api/bootstrap”端点
在后端原点;该路由不通过 Worker 公开。后续编辑使用
受保护的配置 API。
是权威的。成功的初始保存可以使用一次性 `/api/bootstrap` 端点,在后端
自己的地址上完成;该路由不在网关路由集内,所以发给 Worker 的请求到不了它。
后续编辑使用受保护的配置 API。
### 9.3 回调注册与定时任务