feat: establish NekoNest Cloud control and relay

This commit is contained in:
2026-08-12 23:25:43 +08:00
commit f27606b709
222 changed files with 71456 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
# 免费闭测运营指标
闭测后台用于发现接入摩擦、验证主机席位模型和估算共享 Relay 成本。它不是
用户行为画像,也不能把控制面状态冒充为真实会话可用性。
## 控制面漏斗
所有滚动指标按 D1 的 UTC 时间计算,展示时才使用用户时区:
| 指标 | 口径 |
|---|---|
| 新增账户 | 近 30 天创建的控制面账户 |
| 闭测需求 | 申请数、申请席位数、系统偏好;不读取 `use_case` 正文做分析 |
| 申请批准率/耗时 | 已决申请中的批准比例与提交到处理的自然时间 |
| 获批后认领率 | 获批后至少成功认领一个主机身份的申请比例;不代表 Relay 已可用 |
| 配对认领率/耗时 | 配对请求变成 `claimed` 的比例及耗时;取消、过期、锁定保留在分母 |
| 容量拒绝 | 因第 N+1 个未撤销主机身份而返回 `device_capacity_exceeded` 的次数 |
| Handoff 失败 | 过期、重放、错误 origin 和无效手机身份的最小错误码计数 |
| 反馈解决率 | 站内反馈收到管理员回复的比例与自然周转时间;不是人工工时 |
处理中记录不算成功。闭测指标不得读取或复制原生 coding-agent 会话、项目
文件、提示词、附件正文、设备/手机令牌、配对码、私钥或签名密钥。
## Relay 节点与租户 Engine
Relay 节点使用独立身份向控制面 heartbeat。后台区分 active、延迟、offline、
draining 与 retired,并显示节点声明的租户容量;heartbeat 不等于某个租户会话
健康。每个租户只允许当前 placement generation 的一个可写 Engine。
必须从 Relay 自身最小化采集下列运行指标,且不带应用正文、路径或会话标题:
- 当前 Engine 数、创建/关闭失败、每租户连接数与拒绝数;
- WebSocket 握手、重连、背压断开与 `device_already_connected`
- 授权快照刷新、revision delta 延迟、撤销到断开的时间、快照到期关闭;
- 每租户 SQLite/附件密文占用,进程 CPU/内存、Push 请求和网络字节;
- 备份/恢复、区域迁移、回滚和 purge 的状态及最小错误码。
这些信号只用于容量、安全和可靠性。不得记录 prompt/response/tool 正文、密钥包
正文、附件正文、绝对项目路径或可重建用户工作内容的高基数字段。
## Daemon 稳定端点信号
注册与新连接必须实时通过控制面授权。设备凭据记录可以覆盖一条最近成功授权
时间,用于判断控制面是否近期见过该身份;不保存逐次来源和请求正文。该时间只说明
凭据曾通过授权,不说明主机此刻在线。
`service_provisioning``route_unavailable``region_unavailable` 的计数与持续时间
必须单独展示。客户端始终重试同一 Connect origin;任何指标或后台页面都不得显示
租户后端 URL。
## 到期数据维护
每日维护只处理合同中已有明确 TTL 的技术记录:过期配对摘要、来源限速窗口、
配对尝试、幂等记录、未激活的 pending 手机凭据和过期/已消费 handoff ticket。pending
凭据必须先于对应 ticket 删除;已激活手机凭据不受该清理影响。任务保持单实例、可接管,并只保存
最近状态、累计次数、连续失败与最小错误码。真实托管环境的首次 Cron 仍需验收。
## 目前不能声称已经测得
- 真实公网的会话成功率、往返延迟和跨地区体验;
- 每租户可靠成本与未来价格;
- 15 秒撤销 SLO、5 分钟快照失效边界;
- 备份恢复 RPO/RTO、区域迁移和永久删除耗时;
- 实际支持工时和值守负担。
只有同一精确构建在真实 Daemon、Cloud PWA、Relay、D1 与备份恢复链上跑通后,
这些指标才可进入商业决策。
+47
View File
@@ -0,0 +1,47 @@
# NekoNest Cloud 免费公测与商业边界
状态:当前有效。付费、订单、自动续费、退款和发票仍未启用。
未来具体价格均未确定,当前免费公测不会自动转付费,也不会自动扣款。
## 产品边界
NekoNest Cloud 是托管的 coding-agent 会话中继。Agent、项目、CLI 凭据与原生会话库留在用户电脑;Cloud 不运行模型,也不出售模型 Token。自托管 NekoNest 保持开源、Cloud 无感知且默认不限设备。
Cloud 一个账号对应一个租户/Nest。用户购买或受邀获得 N 个主机席位后,可以保留 N 个未撤销主机身份;设备离线不释放席位,明确撤销才释放。手机和平板不占主机席位。
完整复制主机身份私钥无法被纯软件可靠识别为另一台物理机器。Cloud 的可执行保证是:复制品仍代表同一个 host/席位,并且共享 Relay 对相同 `device_id` 的第二个并发连接使用 `device_already_connected` 拒绝新连接,不能把一个席位并发扩成两台活跃主机。
## 免费公测
- 不绑定支付方式,不创建报价或订单,不自动转付费。
- 不创建报价、订单、付款单、积分、钱包、余额或充值额度。
- 申请时间不构成名额承诺或队列优先级。
- 当前没有可配置的宽限天数或隐藏倒计时;资格只由明确的 entitlement 状态决定。
- 当前 entitlement/capacity 是席位权威;未来支付只改变 entitlement,不进入 Relay Core。
- 配对创建会预留容量,claim 时再次原子核对。容量已满返回 `device_capacity_exceeded`
- 同一 pairing、设备身份、签名证明和 daemon retry key 的十分钟内重试返回同一凭据响应,不重复创建 host 或占用席位;冲突身份返回 `device_identity_conflict`
- 降低容量时如果现有主机超额,拒绝直接降级并要求用户先明确撤销主机;不随机踢设备。
- 撤销主机立即撤销其凭证、释放席位并推进 authorization revision;正常 Relay 应在 15 秒 revision 检查窗口内断开目标设备。
- 免费资格变化不应绕过撤销、取消配对、导出等安全出口。
## 共享 Relay 模型
Cloud 不再为每租户运行一个独立 Server 容器。区域 Relay 池共享 Go 进程,但每个租户保持独立 Engine、SQLite 与附件目录。商业策略只在 Cloud 控制面;开放 Relay Core 只接收通用授权、撤销、限流和错误。
租户固定 home region 和唯一写入节点。稳定 Connect origin 隐藏节点位置;不使用携带 bearer 的重定向,也不向 Daemon 返回永久租户 URL。
Cloud 强制 sealed。Cloud 登录只允许创建短时 PWA handoff。目标 Relay Engine 才能创建 phone token;控制面只消费 ticket 和保存 route handle/phone credential 摘要。摘要先保持 `pending`,只有首次请求证明同时持有 token 与 route handle 后才激活并进入授权快照;未取得响应的 handoff 不会留下可用 principal。登录或 handoff 不自动授予 phone → device grant,不得绕过现有 E2E 配对。
## 发布门禁
本地 schema、API 和单元测试通过不等于生产可用。公开公测前至少需要:
- 真实 Go Cloud Relay/WSS 与每租户存储;
- 节点 mTLS、凭证轮换和 KMS/Secret Manager
- 两租户跨 WS/REST/附件/密钥包的负向隔离测试;
- SQLite/附件备份恢复与账号永久删除;
- 节点重启、快照过期、15 秒撤销、5 分钟控制面故障边界实测;
- 隐私保留、运营值班、域名与身份合规确认。
在这些证据完成前,不宣称生产可用、零知识、已上线全球多区域或可公开收费。
+58
View File
@@ -0,0 +1,58 @@
# Cloud daemon 分发与校验
Cloud 接入使用独立的公开下载页 `/download`。它不是 GitHub `latest` 的无条件跳转器,而是一个 fail-closed 发布目录:只有版本、来源和每个平台摘要都完整可信时才出现直接下载按钮。
## 为什么不能直接链接 latest
自托管 daemon 的最新公开版本不一定已经实现 Cloud 稳定端点合同。Cloud 注册要求 daemon 支持协议 1.3、只保存一个 `server_url`、识别 `ready | provisioning`,并按结构化错误在同一 `/ws/daemon` 退避;不得轮询控制面或接受替换用 Relay URL。最低兼容版本固定为 `0.2.6`
截至 2026-08-12GitHub 最新稳定 Release 是 `v0.2.5`。它包含三个 daemon 平台包和 `checksums.txt`,但没有新的稳定端点合同,因此下载页不会展示它。该版本用于自托管不受影响。
## 发布目录配置
只有完成兼容 Release 验证后才设置:
| 环境变量 | 要求 |
|---|---|
| `NEKONEST_CLOUD_DAEMON_RELEASE_VERSION` | 稳定语义版本 `X.Y.Z`,不得低于 `0.2.6` |
| `NEKONEST_CLOUD_DAEMON_RELEASE_BASE_URL` | 可选;无用户名、密码、query 或 fragment 的 HTTPS 目录;默认指向该精确 GitHub tag |
| `NEKONEST_CLOUD_DAEMON_WINDOWS_AMD64_SHA256` | `nekonest-daemon-windows-amd64.zip` 的 64 位十六进制摘要 |
| `NEKONEST_CLOUD_DAEMON_LINUX_AMD64_SHA256` | `nekonest-daemon-linux-amd64.tar.gz` 的 64 位十六进制摘要 |
| `NEKONEST_CLOUD_DAEMON_LINUX_ARM64_SHA256` | `nekonest-daemon-linux-arm64.tar.gz` 的 64 位十六进制摘要 |
任一项不完整或不合法时,三个直接下载入口一起关闭,避免只给部分用户分发未核对的构建。页面不会回退到旧版本或 `releases/latest/download`
## 上架步骤
1. 从干净、已验收且三个版本面一致的源码创建不可变 tag;等待 Release workflow 完整通过。
2. 运行 `npm run release:verify -- vX.Y.Z`。核验器只接受不低于最低兼容版本的稳定精确 tag,并下载三个 daemon 压缩包与 `checksums.txt`;它会同时比较 Release 身份、精确 URL、重复/缺失资产、GitHub API digest、声明大小、清单摘要和实际下载字节。
3. 只有核验全部通过时,工具才在标准输出给出五个 Cloud 环境配置值;它不会自动写 `.env`、修改 Sites 环境、提交或部署。需要机器读取时使用 `--json`。GitHub API 限速场景可临时设置 `GITHUB_TOKEN`,令牌只发送给固定的 GitHub API 请求,不附带到公开资产下载,也不出现在结果或错误信息中。
4. 在 Windows amd64、Linux amd64、Linux arm64 上分别核对压缩包内 `VERSION``nekonest-daemon -version`
5. 使用临时 Cloud 环境完成注册、设备令牌保存、`provisioning` 同端点重试、结构化拒绝和稳定 Connect 重连回归。
6. 若使用境内 HTTPS 镜像,从已经核对的同一压缩包复制;镜像文件摘要必须与 GitHub 精确 tag 保持一致。
7. 设置工具输出的环境变量并重新构建/部署 Cloud;打开 `/download` 复核版本、文件名、链接和摘要。
8. 从真实 Windows/Linux 主机执行页面给出的校验、解压、注册和启动旅程。
## 当前信任边界
- SHA-256 证明下载字节与 Cloud 发布目录登记值一致,不证明作者身份。
- 当前 GitHub workflow 生成 `checksums.txt`,但没有对清单或 Windows 二进制做 Authenticode 等发布者签名。
- 未完成代码签名前,不得把页面描述为“已签名安装包”,也不提供关闭 SmartScreen、杀毒软件或系统安全策略的引导。
- 自建镜像不能自行重新打包;否则摘要变化,目录应保持关闭,直到完成新的受控发布。
- Cloud 环境配置是公开分发控制,不包含私密令牌;敏感凭据不得放入下载 URL。
- 公开下载清单未就绪时,新增主机页会要求闭测参与者明确确认已经从管理员处取得并核验不低于最低版本的构建,才允许生成十分钟配对码;该确认只防止误操作,不替代管理员对闭测文件的来源和 SHA-256 核验。
- 核验器不使用 `latest`、不接受 prerelease 或 draft,也不会把 API 返回的下载地址当作任意可信镜像;所有 GitHub URL 必须与仓库、精确 tag 和固定文件名完全一致。
## 注册版本上报
`POST /api/devices/register` 可选接收 `daemon_version`。已上报值必须是规范稳定版本 `X.Y.Z`,并且不得低于本页同一最低兼容版本;格式异常返回 `invalid_daemon_version`,明确低版本返回 `daemon_version_incompatible`。通过认领后,版本与主机记录在同一 D1 写入中保存;恢复已有主机时,缺失版本不会覆盖已经保存的值。
当前闭测阶段仍兼容缺失的版本字段;未知不代表已经通过兼容性验证。正式 daemon 先稳定发送版本并完成安装、升级与降级拒绝回归,Cloud 才能把缺失值改成强制拒绝;在此之前不能用版本门禁阻断既有闭测参与者。
## 尚未完成
- 发布最低 `v0.2.6` 的兼容资产并填入真实摘要;
- 让正式 daemon 在 Cloud 注册时发送自身稳定版本;覆盖升级后上报、旧版拒绝和版本缺失迁移后,再决定是否强制上报;
- Windows 发布者代码签名和签名证书运营;
- 境内下载可用性、失败回退和带宽成本实测;
- 三个平台真实安装、升级、降级拒绝和卸载/残留检查。
+89
View File
@@ -0,0 +1,89 @@
# Daemon 与 Cloud Relay 稳定端点契约
状态:控制面实现完成;真实 WSS Relay 部署仍是独立门禁。
## 不变服务地址
Daemon 只保存用户注册时填写的 `server_url`。自部署时它指向 Standalone Server;官方服务时它指向 Cloud Connect 稳定 origin。Cloud 不返回租户后端 URL,不要求 Daemon 感知区域、节点或控制面轮询。
注册成功响应:
```json
{
"device_id": "host_...",
"token": "...",
"name": "Home PC",
"transport_mode": "sealed",
"connection_state": "provisioning",
"retry_after_seconds": 5
}
```
`connection_state` 只有 `ready``provisioning``provisioning` 表示凭证已经安全落盘,Daemon 应在同一服务地址的 `/ws/daemon` 退避重试;不得轮询另一个控制面 API,也不得接受 HTTP redirect 携带 bearer。
旧的 `activation_poll_path``relay_url``relay_ready` 和每租户 Server handoff 已删除。旧未发布配置必须由 Daemon 明确要求重新注册,不能静默解释为新契约。
## 错误格式
所有新 API 错误返回:
```json
{
"error_code": "service_provisioning",
"error": "service_provisioning",
"message": "租户 Relay 正在准备",
"retryable": true,
"retry_after_seconds": 5
}
```
`error` 暂时作为兼容别名。Daemon 只按 `error_code``retryable` 和重试提示工作,不按 Cloud 品牌分支。
## Relay 内部授权
Cloud Relay 节点通过独立 mTLS 证书身份访问:
- `POST /api/internal/relay/authorize-device`
- `POST /api/internal/relay/authorization-delta`
- `POST /api/internal/relay/authorization-snapshot`
- `POST /api/internal/relay/register-device`
- `POST /api/internal/relay/heartbeat`
- `POST /api/internal/relay/authorize-phone`
- `POST /api/internal/relay/revoke-phone`
- `POST /api/internal/relay/resolve-device-route`
- `POST /api/internal/relay/resolve-phone-route`
- `POST /api/internal/relay/resolve-tenant-route`
- `POST /api/internal/relay/resolve-handoff-route`
- `POST /api/internal/relay/migrations/advance`
- `POST /api/internal/relay/purges/advance`
Relay 节点只使用独立 mTLS 证书身份,不共享全局 bearer。Worker 本身不能读取客户端证书,因此受信终止层必须剥离外部 `x-neko-mtls-*` 头、验证证书,再用 ingress secret 注入绑定 method/path 的 30 秒 HMAC assertion。缺少该配置或 assertion 时 fail closed。D1 身份行绑定 node ID、SPIFFE ID、证书 SHA-256 指纹、签发/到期和撤销状态;控制面不读取 `Authorization: Bearer`
设备授权请求发送 `device_id` 与设备 token 的 SHA-256 摘要。客户端不能提交 `tenant_id`。控制面从凭证反查租户与 placement,只允许当前写入节点取得签名快照。
快照使用域分离的规范 JSON 与 Ed25519,包含 tenant 状态、home region、节点、placement generation、authorization revision、有效设备身份、设备显示名、OS、Ed25519/X25519 公钥及凭证摘要。这些公开身份材料让重启后的空 Engine 重建真实 DeviceStore 和 E2E 配对目录,不得合成设备名或丢失公钥。TTL 最大 5 分钟;完整快照每 60 秒刷新,revision delta 默认每 5 秒检查,请求超时为 3 秒,以覆盖 15 秒撤销目标。新连接必须实时联系控制面;旧连接只能持续到现有快照过期。
60 秒完整刷新必须用内部 tenant ID 与当前 placement generation 调用 `authorization-snapshot`,不得复用此前客户端设备 token 摘要冒充刷新。错误节点、旧 generation、暂停租户或非 active placement 均 fail closed。节点签名 key 必须覆盖整份新快照有效期,旧 public key 的验证保留窗口必须超过最大 TTL。
稳定 Connect Relay 通过 node-mTLS 的 `register-device` 代理公开 `/api/devices/register`。Relay 只传自身生成的 opaque `source_hash`,不得转交或信任外部 forwarding IP 头;daemon 提供单次随机 `registration_retry_key`。控制面只保存用该 key 加密的同一 credential 响应,最多十分钟,因此响应丢失重试返回同一 token、同一 host 且不再占席位,D1 不保存明文 token。
签名公钥与私钥引用可进入 D1,私钥只能来自 Secret/密钥管理系统,不能写入 D1。
稳定入口不在 home 节点时,控制面只返回 opaque internal endpoint ref。入口从部署 allowlist 选出目标 HTTPS/WSS origin,经 TLS 1.3 mTLS 和绑定 method/path/源节点/目标节点/30 秒时间窗的附加断言转发;客户端看不到节点 URL,也不会携带 credential 跟随 redirect。部署和迁移细节见 `docs/relay-operations.md`
## 手机 PWA handoff
Dashboard 调用 `POST /api/pwa/handoff`,取得 60 秒单次 ticket 与包含 fragment 的 `pwa_url`。PWA 必须在发起网络请求前从地址栏清除 fragment,再向稳定 Connect origin 的 `POST /api/pwa/handoff/exchange` 提交:
```json
{
"ticket": "...",
"pwa_origin": "https://pwa.example",
"name": "My phone",
"phone_ed25519_public": "...",
"phone_x25519_public": "...",
"identity_fingerprint": "..."
}
```
该 public exchange 必须由 Go Cloud Relay 承载,而不是 Cloud 控制面 Worker。Relay 用已验证 mTLS 节点身份调用内部 `consume-phone-handoff`,以用途隔离的 HMAC 密钥从 handoff ID 和手机身份确定性派生 phone principal/token/route handle,再把摘要通过幂等 `complete-phone-handoff` 注册到控制面。完成动作只创建 `pending` principal/route,不进入授权快照;PWA 第一次携带完整 token 与 route handle 访问时,控制面用一次性 activation nonce 原子激活两者并推进授权 revision。pending 证明窗口为 5 分钟,过期后不能路由,并由保留任务在删除 ticket 前清理。因此即使所有 exchange 响应都在提交后丢失,客户端未取得凭证时也不会留下可用手机身份。相同 ticket、节点和 E2E 身份的重试必须得到完全相同的凭据;不同身份或完成参数一律冲突。PWA 清除 fragment 后只在内存中对网络错误或可重试服务错误作有界重试。只有 Relay 对 PWA 返回 `{phone_id, phone_token, route_handle}`;控制面永远不生成或接收明文 phone token。该流程不会创建任何 phone → device grant;用户仍需逐台配对设备。
+53
View File
@@ -0,0 +1,53 @@
# 免费公测数据清单
本文件是当前 NekoNest Cloud 控制平面的工程数据清单,不是最终隐私政策或合规结论。公开页面由 `app/privacy/data-inventory.ts` 的同一清单渲染,避免产品文案与代码边界分叉。
## 当前表级覆盖
| 数据组 | D1 表 | 公测状态 |
|---|---|---|
| 账户与登录身份 | `accounts` | 使用中 |
| 主机、设备与配对安全 | `hosts`, `pairing_requests`, `device_credentials`, `pairing_claim_rate_limits`, `pairing_claim_attempts` | 使用中 |
| 托管租户与 Relay 授权 | `tenant_instances`, `relay_regions`, `relay_nodes`, `tenant_placements`, `tenant_authorization_state`, `relay_migrations`, `relay_purge_jobs`, `relay_signing_keys`, `relay_node_credentials`, `phone_handoff_tickets`, `phone_route_handles`, `relay_phone_principals`, `device_registration_replays` | 封闭施工 |
| 免费公测资格 | `beta_programs`, `entitlement_grants`, `beta_access_requests` | 使用中 |
| 反馈与账户退出 | `beta_feedback`, `account_deletion_requests` | 使用中 |
| 状态、审计与幂等 | `launch_gates`, `service_incidents`, `audit_events`, `idempotency_records`, `maintenance_jobs`, `cloud_schema_migrations` | 使用中 |
| 收费结构 | `price_versions`, `orders`, `payment_attempts`, `invoices`, `refunds` | 休眠;写入入口关闭 |
回归测试会比较本清单使用的表集合与 `db/schema.ts` 中的全部 D1 表。新增表时必须先决定它属于哪个数据组、处理目的、保存边界和用户控制,再更新清单。
## 已明确的最小保存边界
- 明文配对码十分钟失效,只向用户显示,不写入 D1;成功认领会在同一原子写中烧毁原摘要,其他已过期摘要由维护动作替换为不可恢复 tombstone。
- 明文设备令牌只在签发时返回;D1 保存摘要,不进入账户导出。
- 成功的 Relay 设备授权只覆盖对应凭据的一条 `last_used_at`,控制台按主机派生最近控制面签到;不保存逐次请求内容,也不把该时间解释为 Relay 在线。凭据记录及其使用时间继续按安全边界排除在账户导出之外。
- Relay heartbeat 只保存节点最近状态、generation、容量与时间;不保存逐次签到历史、来源 IP、宿主日志、进程输出或 secret。授权快照只包含路由和凭据摘要,不包含会话、Prompt、路径或附件明文。
- 生产来源地址只用于生成带用途隔离密钥的 HMAC 限速摘要;来源窗口保留 24 小时,配对尝试保留 30 天并在后续请求中机会式清理。
- 幂等记录具有技术到期时间;Worker 每天北京时间 02:17 自动运行统一清理,管理员也可明确确认后手工回退。两条路径都只处理同一组已到期技术记录,成功动作与清理边界写入追加式审计。
- 自动任务只覆盖 `maintenance_jobs` 中一条最近状态、累计次数、连续失败数和最小错误码,不保存逐次执行历史、堆栈、请求内容或用户数据;超过 36 小时无成功、运行超过 30 分钟或最近失败都会在公测后台显式提示。
- 手机 handoff 完成后,凭据摘要先以 `pending` 保存且不进入授权快照。只有首次请求同时证明持有 phone token 与 route handle,控制面才用一次性激活栅栏把 principal/route 置为 `active` 并推进授权 revision;5 分钟内未证明持有的身份永远不能授权,保留任务会在删除 ticket 前先清除其 pending 摘要。
- 已确认注销申请可由管理员启动租户 Purge:先暂停授权并关闭 Engine,再删除实时 SQLite、附件和该租户全部备份,最后清除设备/手机凭据并保存最小摘要证据。只有这些后置状态和确定性完成审计均成立后,任务才以最后一条栅栏写入 `completed`;重试不会因旧的完成标记跳过未收口状态。该动作不可撤回;它是应用层逻辑删除,不宣称对 SSD 或云存储块做物理覆写。
- 免费公测期间报价、订单、付款、发票和退款写入由服务端拒绝。
- 闭测申请只保存用户主动填写的操作系统、1–3 台需求和使用场景;用户一次只能保留一条待处理申请,可以自行撤回。申请及审核结果当前随账户保留,最终保存期仍待公测数据决定。
## 用户当前可用的控制
- 下载账户范围的 Cloud 控制平面 JSON
- 取消未认领配对;
- 撤销主机令牌并释放槽位;
- 提交或撤回免费闭测申请,并查看管理员处理说明;
- 提交或撤回账户注销申请;
- 从公开状态页和上线门禁查看服务与证据状态。
账户导出有意排除明文/摘要凭据、配对码、内部密钥、worker 签到与租约、内部审计和迁移账本。原生 coding-agent 会话、项目文件和 CLI 凭据属于用户主机,不在 Cloud 导出范围内。
## 仍未完成
- 除上述四类到期技术记录外,其余每类记录的最终保存期和清理路径;
- Purge 在真实托管卷、对象备份与故障重试中的演练证据,以及法定保留例外、最终账户身份清除和身份防重建;
- 受托服务方、部署位置、境内/跨境事实及相应告知;
- 最终公开登录、账户恢复和隐私联系人;
- 托管部署后对定时触发、运行失败传播和后台逾期提示的真实执行核对;
- sealed 命令与附件在真实 relay、日志、数据库和备份中的端到端实证。
以上项目完成前,`privacy-retention` P0 门禁继续保持阻止。
+55
View File
@@ -0,0 +1,55 @@
# NekoNest Cloud 共享 Relay 实施状态
## 已完成:控制面契约
- [x] 复用账号、entitlement、pairing 和原子主机席位逻辑。
- [x] 注册响应改为同一端点的 `ready|provisioning`,移除 activation poll 与 relay URL。
- [x] D1 增加 region、node、placement、authorization revision、签名公钥元数据与节点凭证摘要。
- [x] D1 增加 60 秒单次 PWA handoff、routing-only handle 与 phone credential 摘要;phone principal/token 明确留在目标 Relay Engine。
- [x] Dashboard 创建 fragment handoff;目标 Relay 节点通过内部 consume/complete API 同步 Engine,且不创建 device grant。
- [x] 内部 API 只接受 mTLS 终止层注入的短时可信断言,并绑定 D1 中节点、SPIFFE、证书指纹、有效期与撤销状态;无共享 bearer。
- [x] 规范 JSON + WebCrypto Ed25519 签名快照,TTL 上限 5 分钟。
- [x] claim/revoke 成功才推进租户 authorization revision。
- [x] 通用 API 错误包含 `error_code/message/retryable` 和可选重试/action 字段。
- [x] 数据模型不保存 prompt、session、attachment 明文或签名私钥。
- [x] D1 schema 完整对齐的控制平面数据清单,并覆盖 handoff ticket 的 24 小时保留清理。
- [x] 空、加载、失败、离线、待核实和无资格状态均有明确恢复动作。
- [x] 配对页自动确认认领、过期、锁定或取消,并立即清除一次性页面状态。
- [x] 管理员后台即时汇总启用主机的控制面签到,但不冒充 Relay 在线状态。
## 已完成:单区域 Relay 代码路径
- [x] 私有 Go Cloud Relay 引用精确版本 Relay Core,不提交本地 `replace`
- [x] `TenantEngineRegistry`:每租户唯一 Engine、独立 SQLite/附件目录、路径与符号链接防护。
- [x] 同一 `device_id` 并发采用 `reject_new`
- [x] 稳定 Connect origin 的 WSS/REST/附件路由。
- [ ] mTLS 终止层与可信头剥离部署实证、节点 bootstrap/轮换、KMS 私钥加载。
- [x] PWA 用 handoff response 保存独立 phone token,并继续逐设备 E2E 配对。
- [ ] 一席位/两席位、撤销、重启、附件、Push、重连端到端验证。
## 已完成:区域与生命周期代码路径
- [x] home-region pinning、实时 placement 解析与认证内部 HTTPS/WSS 转发;客户端不重定向。
- [x] quiesce/copy/switch/drain 迁移与 generation fencing;切换后不回滚到落后的源节点。
- [x] 每租户 SQLite/附件备份、逐文件校验、幂等恢复与切换前回滚。
- [x] 注销 Purge 覆盖 Engine 数据、附件、全部租户备份、device/phone/route/handoff 凭据与注册重放密文。
## 待完成:真实基础设施与运营证据
- [ ] 两区域共享备份传输层、mTLS 终止层、头剥离和真实迁移/回滚/Purge 演练。
- [ ] 容量、heartbeat、快照刷新与撤销延迟告警。
- [ ] 真实 relay 接通后采集重连成功率、Relay 延迟、资源成本和实际支持工时。
- [ ] 最终账户身份清除、防重建、法定保留例外与云存储物理生命周期政策。
## 验收重点
- 第 N+1 台主机被拒;撤销释放席位并推进 revision。
- ticket 过期、重放、错误 origin 均失败;URL fragment 在 PWA 网络请求前清除。
- 新连接在控制面故障时立即失败;已有连接最迟在 5 分钟快照过期时关闭。
- 任意跨租户 device、phone、route handle、history、attachment、key package 均不能越权。
- 区域迁移中永远只有一个 generation 的一个可写 Engine。
Worker 只承载控制面;WSS 与数据面由 Go Relay 承载。旧 provisioner、公开 activation 注册路径、管理员执行入口及相关运行时类型已删除;历史 migration 仅用于安全升级旧 D1。
Daemon 下载仍使用 fail-closed 发布清单;Windows 发布者代码签名尚未完成,仍属于扩大公测前门禁。
公开下载未就绪时,只允许已经取得并核验兼容闭测构建的参与者继续配对。
+48
View File
@@ -0,0 +1,48 @@
# 免费公测与未来收费门禁
状态:本地原型允许施工;面向公众的生产公测保持阻止。本文是产品检查表,不替代正式的安全、运营或专业意见。
门禁不是展示用清单:普通账户的新配对创建和 daemon 认领都会在 D1 写入条件中重新检查全部必需 P0,缺行或证据不完整时 fail closed。管理员明确签发的有效闭测邀请可在小范围测试中继续使用;P0 回滚或邀请撤销不会自动断开既有主机。
## P0:未通过不得开放免费公测
| 门禁 | 当前 | 完成证据 |
|---|---|---|
| sealed 命令与附件端到端实证 | 阻止 | 在真实中继覆盖上传、重试、恢复和下载;流量、数据库、日志、追踪和备份均不出现应用明文或可用解密钥 |
| 到期操作边界 | 阻止 | Cloud 控制面已用矩阵与数据库回归证明:资格变化阻止新配对/未完成认领,保留主动撤销;仍需在真实 Relay 验证稳定端点重连、重放、会话 interrupt/cancel、授权快照过期和硬到期行为 |
| 租户隔离和生命周期 | 阻止 | 相似租户 ID 的域名、令牌、卷、备份、恢复、删除和失败重试全部 fail closed |
| 主机配对认领安全 | 阻止 | 本地 claim、一次性码摘要、限速、尝试预算、过期/重放拒绝、撤销及不落命令历史的 Windows/Linux 引导已实现;仍需真实 daemon/生产 D1 并发实证与异常告警后才能通过 |
| 国内个人用户身份 | 阻止 | 选择并实测公开登录、账户恢复、管理员身份和退出路径;当前 Sites / ChatGPT 身份只用于闭测施工 |
| 免费公测主体、域名与备案路径 | 阻止 | 免费测试以什么身份提供、部署/接入地、域名和所需备案路径已有可执行结论 |
| 隐私、保存和跨境 | 阻止 | 表级数据清单、处理目的、用户控制、已审计技术到期清理及每日自动调度已完成;仍需托管环境首轮实证、最终保存期、受托方、删除/备份擦除和境内/跨境事实核查 |
## P1:扩大公测前完成
- 备份/恢复演练,记录实际 RPO/RTO;
- 区域 Relay 池中每租户 Engine 的空闲/活跃 CPU、内存、SQLite、附件、Push、流量和支持工时测量;
- 公测容量、通知节奏和反滥用限制;
- 主机恢复、停用、身份丢失后重装、凭据轮换、安全复制式注册引导和 fail-closed 下载清单已实现;扩大公测前发布最低 v0.2.6 兼容包、配置实测 SHA-256、完成 Windows 发布者代码签名,并补真实安装升级、注册响应丢失和生产 D1 并发演练;
- Cloud 控制平面 JSON 导出、可撤回注销申请、不可撤回 Purge 状态机、Relay 卷/附件/备份逻辑删除和最小完成摘要已实现;仍需真实托管存储演练、保留例外、最终账户身份清除、物理生命周期证据和身份防重建;
- 公开服务状态页、手工故障公告/恢复说明、控制台提醒,以及站内用户反馈/管理员回复闭环已实现;安全事件分级、自动告警、外部通知和值守升级仍待完成;
- PWA 浏览器响应头基线与无远程第三方脚本已实现;仍需 nonce/hash 严格 CSP、构建来源与哈希、依赖锁定、服务工作者回滚和发布 provenance
- 构建工具链审计:生产依赖审计归零,受信任源码构建不处理用户提供的非可信图片。
## PAID:未来决定收费后再处理
这些项目不阻塞当前免费测试,但没有完成前不得开放任何报价、订单或收款入口:
- 收费单位、周期、价格和成本模型重新决策;
- 订单到权益的幂等、乱序和对账;
- 真实支付商户、回调验签、退款和对账;
- 销售/开票主体、含税口径、发票和退款红冲;
- 付费服务条款、取消、退款、宽限、客服和消费者规则。
## 证据不足时禁止使用的宣传语
- “生产可用”“已正式上线”“已经合规”;
- “绝对零知识”“Cloud 永远不可能看到明文”;
- “附件已端到端加密”;
- “安全隔离”“可靠备份”“灾难恢复”而没有测试报告;
- “立即购买”“支付已支持”或任何确定价格;
- 确定的公测结束、收费、退款或上线日期;
- 把“手机不占主机槽位”写成无限连接承诺。
+37
View File
@@ -0,0 +1,37 @@
# 区域共享 Relay 调度边界
旧的“每租户 Docker Server + manifest provisioner”路径已经退出 Cloud 控制面,相关执行器、公开 activation 接口和管理员操作入口均已删除。本文件描述当前共享 Relay 节点与租户 placement;历史 migration 只为现有 D1 升级保留,不得重新接入生产 API。
## 控制面对象
- `relay_regions`:稳定公开 origin 所覆盖的区域。
- `relay_nodes`:区域内的 Go Relay 节点、状态、容量和 heartbeat generation。
- `tenant_placements``tenant → home_region → relay_node`,带单调 generation 与状态。
- `tenant_authorization_state`:租户授权状态与单调 revision。
- `relay_node_credentials`:节点 mTLS SPIFFE ID、证书指纹、签发/到期和撤销时间;不保存共享 bearer。
- `relay_signing_keys``kid`、Ed25519 公钥、私钥引用和轮换窗口;不得保存私钥。
首版每个租户固定一个 home region、一个可写节点和一个 Engine。没有 active-active、跨节点 SQLite 共享或双写。`tenant_placements.state``active`,或切换后目标节点独占写入的 `draining`,且目标节点 `status=active` 时,设备注册报告 `connection_state=ready`
## 节点身份与 heartbeat
Cloud Worker 不能直接读取客户端证书。部署必须在 Worker 前使用 mTLS 终止层,剥离不受信的身份头,并注入绑定 method/path 的短时 HMAC 断言。控制面验证该断言,并把 node ID、SPIFFE ID、证书指纹与 D1 中未撤销、未过期的证书身份行绑定;任一不匹配均 fail closed。内部 API 不要求也不接受共享节点 bearer 作为身份替代。heartbeat generation 不能回退。
生产部署仍必须增加:
- mTLS 终止层、受信头剥离与 ingress assertion secret 的部署实证;
- 独立的证书 bootstrap/轮换操作面;
- Secret Manager/KMS 内的 Ed25519 私钥;
- 节点容量、过期 heartbeat 与 placement 队列告警。
## Placement 与迁移
迁移状态固定为:
`active → quiescing → copying → switching → draining → active`
迁移在复制和切换前停止新写,校验每租户 SQLite 与附件快照,再原子递增 placement generation 并切换目标节点。切换前失败时旧节点保持权威;切换后 `draining` 由目标节点作为唯一写入者,不能回切到可能落后的源数据。同一 generation 不得在两个节点同时可写。
当前仓库已经包含 Go Relay、租户 Engine Registry、授权刷新、稳定 Connect 数据面、节点间转发、备份恢复、迁移与 Purge 原语;它们仍需经过真实 mTLS ingress、WSS、共享备份传输和跨区演练,才能表述为公开上线服务。具体配置见 `docs/relay-operations.md`
Relay 每 60 秒以 tenant ID + placement generation 拉取完整快照,默认每 5 秒拉 revision deltadelta 和随后的完整刷新各有 3 秒超时;完整刷新不携带客户端设备 token。手机撤销由当前 placement 节点调用内部 revoke,原子撤销 phone principal 与全部 route handle、推进 authorization revision 并写审计后,Relay 才关闭 Engine 内身份。
+65
View File
@@ -0,0 +1,65 @@
# Cloud Relay 部署与生命周期操作
状态:代码路径可本地验证;真实区域、mTLS、共享备份存储和告警仍须部署演练。本文不能作为生产验收记录。
## 构建边界
`relay/go.mod` 必须精确依赖已发布的 `github.com/klarkxy/nekonest/relaycore vX.Y.Z`,不得提交指向相邻开源仓库的 `replace`。发布 Relay Core 后,从该精确 tag 构建 Cloud Relay,并把 NekoNest commit、Relay Core tag、Cloud commit 和镜像摘要记录为同一发布证据。
## 必需配置
| 环境变量 | 含义 |
|---|---|
| `NEKONEST_RELAY_DATA_ROOT` | 每租户 Engine 的 SQLite 与附件根目录 |
| `NEKONEST_RELAY_BACKUP_ROOT` | 与数据根分离的加密备份命名空间 |
| `NEKONEST_RELAY_NODE_ID` | D1 中登记的 `node_...` 身份 |
| `NEKONEST_RELAY_CONTROL_PLANE_URL` | 内部控制面 origin |
| `NEKONEST_RELAY_MTLS_CERT_FILE` / `NEKONEST_RELAY_MTLS_KEY_FILE` | 该节点独立的 mTLS 客户端身份 |
| `NEKONEST_RELAY_CONTROL_PLANE_CA_FILE` | 控制面内部 CA |
| `NEKONEST_RELAY_INTERNAL_CA_FILE` | Relay 节点间内部 CA |
| `NEKONEST_RELAY_INTERNAL_ENDPOINTS` | `internal_endpoint_ref → exact HTTPS origin` JSON allowlist |
| `NEKONEST_RELAY_PWA_ORIGINS` | 精确 Cloud PWA origin 列表 |
| `NEKONEST_RELAY_ROUTE_SECRET` | opaque route hint 的 AES-GCM 密钥材料 |
| `NEKONEST_RELAY_SOURCE_HASH_SECRET` | 注册来源限速摘要的用途隔离密钥 |
| `NEKONEST_RELAY_FORWARD_SECRET` | 节点间 mTLS 通道内、绑定 method/path/节点/30 秒窗口的附加断言密钥 |
| `NEKONEST_RELAY_HANDOFF_SECRET` | handoff 重试时确定性派生 phone ID/token/route handle 的独立 HMAC 密钥;同一 Relay 池必须一致、不得与其他密钥复用,轮换前须停止签发并等待至少 60 秒让旧 ticket 失效 |
| `NEKONEST_RELAY_SNAPSHOT_KEYS` | 当前与轮换重叠期内的 Ed25519 公钥 JSON |
| `NEKONEST_RELAY_TRUSTED_PROXY_CIDRS` | 可选,唯一可提供公网来源地址的代理网段 |
| `NEKONEST_RELAY_MAX_TENANTS` | 本进程同时打开的 Engine 上限 |
| `NEKONEST_VAPID_PUBLIC_KEY` / `NEKONEST_VAPID_PRIVATE_KEY` / `NEKONEST_VAPID_SUBJECT` | Cloud Web Push;缺少时 Push 明确禁用 |
三个 secret 至少是 32 个随机字节的 base64url。证书、私钥、VAPID 私钥和 secret 不进入 D1、日志或仓库。
## 稳定入口与内部转发
客户端始终连接稳定 Connect origin。入口节点用设备凭据摘要、手机 route handle + token 摘要、handoff ticket,或已认证 opaque route hint 向控制面实时解析 placement。目标是本节点时直接进入唯一 Engine;目标在其他节点时,按控制面返回的 opaque endpoint ref 从本地 allowlist 选择 HTTPS/WSS 地址,通过 TLS 1.3 mTLS 通道转发原始请求。客户端不收到重定向或节点 URL。
内部终止层必须验证节点证书并剥离外部 `X-Neko-Relay-*``X-Neko-mTLS-*` 头。应用层附加 HMAC 不是 mTLS 的替代品。未知 endpoint ref、重定向、过期/篡改断言和任意 URL 均 fail closed。
## 备份与区域迁移
状态固定为 `active → quiescing → copying → switching → draining → active`
1. 源节点关闭精确 generation 的 Engine,做 WAL checkpoint、SQLite integrity check,并生成逐文件 SHA-256 manifest。
2. 目标节点只恢复控制面给出的 opaque backup ref,验证 manifest、全部文件和 SQLite,再原子安装租户目录。
3. 控制面一次性切换 node 与 generation。切换前失败时源节点保持权威;切换后 `draining` 已由目标节点独占写入,后续失败也不得回切到可能落后的源数据。
4. 五分钟稳定窗口后 placement 回到 `active`。任何阶段都没有两个可写 Engine。
当前备份引用实现要求源、目标看到同一个经过认证且加密的备份命名空间,例如受限对象存储挂载或同语义的备份卷;它不是客户端可提交的路径。若区域之间没有该传输层,迁移必须保持阻止,不能假装本地测试等于跨区复制。
## 注销与永久逻辑 Purge
管理员只能对用户尚未处理的注销申请输入精确确认词并启动 Purge。控制面先暂停授权、推进 revision、把 placement 置为 `deleting`,因此新连接立即失败;Relay 默认每 5 秒检查 delta,并把 delta 请求与完整快照刷新各限制为 3 秒,健康控制面下现有连接在 15 秒目标内关闭。Relay 随 heartbeat 接收任务,关闭精确 generation,拒绝符号链接/特殊文件,删除实时 SQLite、附件和该租户全部备份。节点回传摘要后,控制面才清除活动设备凭据、手机 principal、route handle、handoff ticket 和注册重放密文,并推进 generation 与 tombstone 状态;所有关键状态必须各更新一行,否则返回不确定状态而不能宣称完成。
失败任务保持访问暂停并进入 `deletion_failed`,只能由管理员再次明确确认后重试。这里的“永久”是应用层不可恢复逻辑删除;SSD、快照、对象存储版本和法定保留的物理生命周期仍由基础设施与最终政策验收。
## 验证与发布门禁
```powershell
cd relay
go test -count=1 ./...
go vet ./...
go test -race ./...
```
本地验证还必须运行 Cloud Web 的类型检查、lint、build 与全部测试。公开服务前另需真实两区域演练:mTLS 头剥离、入口转发、迁移与切换后写入、节点重启、备份篡改、Purge 重试、Push、15 秒撤销、5 分钟快照过期、资源泄漏和恢复。缺少 C 编译器时 Windows 无法完成 `-race`,必须由 Linux CI 补证。
+26
View File
@@ -0,0 +1,26 @@
# 到期技术数据自动维护
这是免费公测阶段的数据最小化运行手册。它只自动化已经确定期限的技术记录,不替账户、审计、反馈、租户卷或备份决定最终保存期。
## 调度与范围
- Cloudflare Worker Cron 使用 `17 18 * * *`,即每天 UTC 18:17、北京时间 02:17。
- 自动任务与管理员手工回退调用同一套有界清理:过期配对码摘要转为不可恢复 tombstone;24 小时前的来源限速窗口、30 天前的配对尝试和自身已经到期的幂等记录被删除。
- 成功清理继续追加 `privacy.retention_cleanup` 审计。不会删除账户、主机、设备凭据、反馈、故障公告、审计、租户、开通任务、卷或备份。
## 防重与失败
`maintenance_jobs``retention_cleanup` 只保存一行。调度开始时通过一条条件 upsert 取得运行权;已有任务在 30 分钟内运行时,重复触发直接跳过。超过 30 分钟仍未收口时,下一次触发可以用新 run ID 接管,旧任务不能覆盖新状态。
成功时覆盖最近成功时间、结果摘要并清零连续失败数。失败时只保存稳定错误码和连续失败数,不保存异常堆栈、请求内容或用户数据;异常继续向 Worker 调度事件抛出,使托管平台把该次执行记为失败。
## 后台判读
- 最近 36 小时内有成功:正常;
- 超过 36 小时无成功:逾期;
- 当前运行不超过 30 分钟:运行中;
- 当前运行超过 30 分钟:卡住;
- 最近一次失败:失败,并显示最小错误码;
- 从未有任务记录:尚未自动运行。
部署后应等待首个调度窗口,确认后台出现一次成功、`maintenance_jobs` 只有一行,并在 Cloudflare 观测中核对该 Cron 事件。部署前的本地构建和 SQL 回归只能证明入口、配置与状态机存在,不能冒充真实托管执行证据。
+28
View File
@@ -0,0 +1,28 @@
# 浏览器安全响应头基线
NekoNest Cloud 在 Cloudflare Worker 的统一响应出口为页面、API 和图片响应设置浏览器安全头。它是免费公测的基础防护,不代表严格 CSP、可验证发布或 sealed 端到端实证已经完成。
## 当前策略
- `Content-Security-Policy` 默认只允许同源资源,禁止插件对象、被其他页面嵌入、站内 frame、跨源表单提交和媒体加载;图片只额外允许 `data:``blob:`Worker 只额外允许 `blob:`
- `X-Frame-Options: DENY` 作为旧浏览器的点击劫持兜底。
- `Referrer-Policy: no-referrer` 避免页面地址随跨站请求泄露。
- `X-Content-Type-Options: nosniff` 禁止内容类型猜测。
- `Permissions-Policy` 关闭当前产品不需要的摄像头、麦克风、定位、支付、USB 等浏览器能力。
- HTTPS 响应设置一年 HSTS;HTTP 本地开发响应不会设置 HSTS。当前不使用 `includeSubDomains` 或 preload,避免在正式域名与租户子域策略确定前扩大承诺范围。
- `/api``/dashboard``/admin` 及其子路径统一使用 `Cache-Control: private, no-store`,即使某个业务处理器遗漏设置,也不让账户或控制面数据被浏览器或共享缓存保存;公开页面原有的缓存策略不被覆盖。
当前页面没有远程第三方脚本、样式或字体。以后增加外部来源时,必须同时修改策略、测试和本文,不能临时放宽为通配来源。
## 尚未完成的严格 CSP
Vinext / React 当前渲染链仍需要兼容内联脚本和样式,因此 `script-src``style-src` 暂时包含 `'unsafe-inline'`。策略不允许 `'unsafe-eval'`,也不允许任意远程脚本来源,但这仍不是 nonce 或 hash 约束的严格 CSP。
扩大公测前仍需完成:
- 为框架输出接入 nonce 或 hash,并移除脚本侧 `'unsafe-inline'`
- 先以 report-only 方式观察真实违规,再收紧正式策略;
- 选择公开登录方案后,重测跳转、退出和账户恢复路径;
- 完成依赖锁定、构建哈希/provenance 与服务工作者回滚证据。
浏览器安全头不能解决托管方可更新 PWA 的根本信任问题,也不能替代 sealed 命令和附件的端到端测试。
+65
View File
@@ -0,0 +1,65 @@
# NekoNest Cloud 状态模型
## 账号、租户与席位
- 一个 Cloud 账号拥有一个 `tenant_instances` 记录。
- 一个 `active``slot_state=active` 的主机占一个席位,在线与否不改变计数。
- 未过期的 waiting pairing 只预留容量;取消、过期或成功 claim 后释放预留。
- pairing 创建与 claim 都在写入 SQL 内复核容量。第 N+1 台返回 `device_capacity_exceeded`
- 账户所有者可以轮询自己创建的配对状态,但不能读取其他账户的请求。
- 等待中的请求可由所有者取消;若 daemon 已先认领,则取消失败,不能撤销已提交的设备凭据。
- 主机撤销将主机标记为 deactivated、撤销所有设备凭证、释放席位,并推进 authorization revision。
- 同一身份恢复复用原 host ID;不能因复制公开指纹而获得第二个身份。
席位允许设备成为租户成员,不自动授权任何手机读取该设备。
## Placement
`tenant_placements` 是共享 Relay 的部署权威:
- `home_region_id` 固定租户主区域;
- `relay_node_id` 指向当前唯一可写节点;
- `generation` 只能单调增加;
- `state` 首版使用 `provisioning|active|quiescing|copying|switching|draining|failed`
客户端只看到稳定 Connect origin。`provisioning` 时在同一地址重试,不得到后端节点 URL。
## 授权 revision
每个租户有一条 `tenant_authorization_state`
- `status=active|suspended`
- `revision` 在成功 claim、主机撤销、手机撤销或租户暂停时单调增加;
- 变更只有在相应设备/主机写入已经成功时才推进,避免未提交操作造成虚假撤销。
Relay 获取最长 5 分钟的签名快照,每 60 秒完整刷新、默认每 5 秒检查 revision(delta 与随后的完整刷新各限制为 3 秒)。控制面不可达时不建立新 principal;现有连接最多存活到快照到期。
## 节点与签名密钥
节点状态为 `provisioning|active|draining|offline|retired`。heartbeat 只接受非回退 generation。节点以 D1 中未撤销、未过期的 SPIFFE ID 与证书 SHA-256 指纹记录为身份。所有内部调用必须先通过受信 mTLS 终止层注入绑定 method/path 的短时 HMAC assertion;控制面不要求共享 bearer。
Ed25519 私钥不进入 D1。`relay_signing_keys` 只保存 public JWK、`private_key_ref``kid`、有效期与状态。签名密钥必须至少有效到新快照的 `expires_at`;轮换重叠必须长于 5 分钟最大快照 TTL,旧 public JWK 也必须保留超过最后一份旧 key 快照的 TTL,保证验证窗口不断裂。
## Phone handoff
`phone_handoff_tickets` 生命周期:`issued → consumed``issued → expired`。ticket
- 随机 256 bit
- D1 只保存 SHA-256 摘要;
- 绑定账号、租户、精确 PWA origin;
- 60 秒有效;
- 条件 UPDATE 原子消费一次。
控制面只允许目标 placement 节点原子消费 ticket,并向该节点返回 tenant 与手机公钥。目标 Relay Engine 创建独立 phone principal/token 后,把 routing-only handle 摘要登记回来。控制面不生成或保存 phone token。该流程不创建 device grantroute handle 只负责解析 tenant placement,不能替代 phone token。
每次 phone WS/REST 入口都必须同时校验 route handle 摘要与 phone token 摘要,并要求两者绑定同一未撤销 phone principal 和 tenant;单独持有 route handle 不构成授权。已消费或已过期 ticket 只短暂保留摘要和公开身份字段用于防重放诊断,24 小时后由维护任务删除;D1 从未保存明文 ticket。
## 数据边界
D1 保存账号、权益、主机身份元数据、凭证摘要、placement、授权 revision、公钥、ticket/route 摘要和审计。新增表不得出现 prompt、session、attachment 明文或私钥。
控制面 `last_used_at` 只证明设备凭证最近成功通过授权,不证明租户 relay、WebSocket、Relay Engine 或本地 Agent 在线。
遗留 `grace_days` 不参与资格、设备认证或页面判断。
加载、离线和页面失败是客户端展示状态,不得改写控制面 placement、授权或主机身份事实。