# 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 grant;route 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、授权或主机身份事实。