跳转至

🎯 AgentMeshOS Console 统一会话与认证过期体验整改计划(2026-10-24)

计划状态:已完成

阶段归属:并行工程专项,不属于当前 P19 阶段,不改变 docs/plans/state.json

当前工作包:CS-8 生产灰度、监控、回退与归档

📌 结论摘要

本计划解决 Console 登录会话过期后出现的“部分功能仍可用、部分功能报 Failed to fetch、部分入口出现嵌套登录页”问题。问题不是节点网络故障,也不是业务数据丢失,而是 A-Auth 会话生命周期、Nginx 认证失败响应和 Console 前端会话状态三者没有形成统一状态机。

当前已确认 A-Auth 默认会话有效期是固定 28800 秒,即 8 小时绝对有效期;控制台只在页面初次加载、点击入口或周期刷新时发现过期;Nginx 对受保护 API 使用 302 登录跳转,浏览器 fetch 跟随跨域跳转后可能因 CORS 失败,只能显示 Failed to fetch。依据是 tools/auth-site/src/main.py、scripts/maintenance/agentmeshos-main-origin-gateway.sh 与 tools/console-site/src/app.js 的现场代码,以及 2026-10-24 对 https://auth.yohan.fun/login 的只读响应头回读。

本计划采用“统一会话、明确过期、一次重新登录”的方案:Console 及其同信任管理功能共用 A-Auth;API 过期返回标准 401 JSON;前端只显示一个居中会话失效弹窗;重新登录后回到原视图;真正独立的账号系统明确标记并默认新窗口打开。

登录页防 iframe 不直接套用全局 frame-ancestors 'none'。当前 /login、/api-key-login 的 CSP 仍允许 https://console.yohan.fun 与本地调试来源,而 Console 的“账户与安全”确实通过 iframe 打开 https://auth.yohan.fun/account。因此本计划设置独立兼容性门禁:先证明所有登录跳转、账户页、嵌入式入口和移动端页面在受限帧策略下仍正常,再只收紧登录类路由,避免引入隐性白屏或循环跳转。

核心目标:一次会话、一次过期、一次登录。

本次只新增计划与导航入口,没有修改生产 Auth、Console、Nginx 或节点配置。

🗂️ 目录

  1. 背景与范围
  2. 现状与证据
  3. 根因分析
  4. 目标行为
  5. 会话与接口契约
  6. 工作包与执行顺序
  7. 防嵌套登录与 iframe 兼容门禁
  8. 实施步骤
  9. 发布、验收与回退
  10. 分布式底座接入声明
  11. 当前验证证据
  12. 风险与未决项

🧩 背景与范围

用户在浏览器登录 Console 后长期未操作,再次进入不同功能时看到以下混合状态:

页面或入口 当前表现 用户感知
节点管理 页面壳和公开状态仍可显示 误以为整个系统仍在线且已登录
网络态势 网格区显示 网络拓扑暂不可用: Failed to fetch 误以为拓扑服务或节点网络故障
任务调度 各区域显示 Nomad 状态读取失败: Failed to fetch 误以为 Nomad 集群不可用
MCP 记忆服务 Console 内容区出现 A-Auth 登录页 出现嵌套登录,流程不透明
API 网关 Console 内容区出现 A-Auth 登录页 不确定是否形成第二套账户或会话

本计划包含以下范围:

  1. A-Auth 会话模型、过期判断、活动续期和只读备用兼容。
  2. Nginx 对 HTML 导航、XHR/API、iframe 和外部入口的差异化认证失败响应。
  3. Console 全局会话状态、过期弹窗、请求拦截、跨标签同步和登录后恢复视图。
  4. MCP、邮箱、API 网关、Nomad 等同信任入口的嵌套登录治理。
  5. 2S-UI、Cloudreve、Nextcloud、Headplane 等独立或附加身份层的界面标识。
  6. 登录页 CSP 防嵌入的兼容性验证、选择性实施、自动化测试与回退。

本计划不包含以下范围:

  1. 不修改 Headscale、Tailnet、DERP、Peer Relay、Edge 或 DNS。
  2. 不修改 Runtime、AI Boss、Nomad 调度、Cloudreve 数据面或 Provider 配置。
  3. 不替代 2S-UI、Cloudreve、Nextcloud 或 Headplane 自己的认证。
  4. 不为普通会话过期引入人工审批、Runtime Plan/Task 或告警流程。
  5. 不通过无限延长 Cookie 或永久登录掩盖交互问题。

📊 现状与证据

当前认证与会话事实

事实 证据 影响
A-Auth 当前默认会话为 8 小时固定有效期 tools/auth-site/src/main.py 默认 AUTH_SESSION_TTL_SECONDS=28800 使用中不会滑动续期,8 小时后必然过期
Cookie 为 HttpOnly、Secure、SameSite=Lax、域 .yohan.fun set_session_cookie() 实现 安全边界合理,可继续作为统一会话载体
Console 只在初始化和业务请求时检查会话 tools/console-site/src/app.js 的 loadSession() 与 15 秒轮询 已加载页面不会立即切换为统一的过期状态
API 路径在 401 后被 Nginx 改成登录页 302 agentmeshos-main-origin-gateway.sh 的多处 error_page 401 = @..._login XHR 无法按标准 401 处理,跨域跳转可能表现为网络失败
Console 的认证跳转检测依赖可读取的 response.redirected isAuthenticationRedirect() 与 redirectToLogin() 跨域重定向被浏览器阻断时,该判断无法执行
当前登录页 CSP 允许 Console 和本地调试来源嵌入 2026-10-24 curl -D - https://auth.yohan.fun/login 回读包含 frame-ancestors https://console.yohan.fun http://127.0.0.1:* http://localhost:* 登录页目前可被 Console iframe 装载,嵌套登录在技术上成立
A-Auth 账户页被 Console 作为嵌入视图使用 tools/console-site/src/app.js 的 account 视图为 type: "embed",tools/console-site/tests/embed-contract.sh 固定该入口 不能对全部 A-Auth 页面直接设置 frame-ancestors 'none'

当前问题不是哪一类故障

判断 结论 依据
是否节点网络故障 不是首要原因 节点管理和公开状态仍可显示,多个受保护入口同时出现认证跳转
是否 Nomad 服务故障 不能由 Failed to fetch 直接判断 请求可能在到达 Nomad 前已被 A-Auth 拒绝
是否产生第二套节点资料 不会 登录只产生会话记录,不复制节点、任务、拓扑或业务数据
是否会产生多套浏览器会话 同信任入口仍是同一个 A-Auth Cookie,但独立系统各有自己的会话 不同子域、不同应用可能分别过期,当前界面没有说明
是否只是会话过期 过期是触发条件,混合 UI 和错误提示是实现缺陷 会话过期本身符合安全预期,异常是页面没有统一收口

🔍 根因分析

会话状态没有单一权威

A-Auth 是服务端权威,但 Console 只在局部请求中被动读取;页面已加载后保留旧角色、旧视图和旧数据,没有把服务端过期事实转换成全局 UI 状态。影响是同一页面里的不同请求可以独立失败,用户无法判断是登录过期、权限不足还是服务宕机。

依据是 tools/auth-site/src/main.py 的 /session 服务端检查,以及 tools/console-site/src/app.js 中只在初始化和 15 秒轮询时读取会话的实现;下一步是增加前端全局会话状态机。

API 和页面使用了同一套登录跳转

浏览器直接打开 HTML 页面时,302 到登录页是合理的;XHR 或 API 请求需要可机读的 401 JSON。现在两者共用 error_page 401 -> 302,前端无法稳定识别认证失败。跨域重定向还可能被 CORS 拦截,最终错误文本退化为 Failed to fetch。

依据是 scripts/maintenance/agentmeshos-main-origin-gateway.sh 中 /__agentmeshos/boss/、/__agentmeshos/network-probe/ 和 api.yohan.fun 的 error_page 401 命名跳转;下一步是按请求类型拆分响应。

嵌入入口没有父页面与子页面的认证契约

MCP、邮箱和账户页通过 iframe 或 Console 内容区导航。A-Auth 过期后,子页面直接跳登录页,父页面并不知道子页面已经失去认证,于是形成嵌套登录。当前也没有统一的 postMessage、状态事件或父页面预检查。

依据是 tools/console-site/src/app.js 中 mcpMemory、mailbox 和 account 的 type: "embed" 配置,以及 https://auth.yohan.fun/login 当前允许 Console 嵌入的 CSP;下一步是让父页面先检查会话,再装载嵌入页。

统一登录与独立登录没有在界面上分层

Console、API、Nomad、MCP 等同信任服务需要统一会话;2S-UI、Cloudreve、Nextcloud 是独立身份系统;Headplane 在 A-Auth 之后仍要求自己的 API Key。当前菜单虽然功能可用,但没有把“统一登录”“独立登录”“附加凭据”清楚分类,用户容易理解为同一个系统发生了多层登录。

依据是 Console views 元数据同时混有 embed 与 external,且独立系统文档分别声明自己的登录边界;下一步是增加认证类型标签并统一新窗口策略。

🎯 目标行为

场景 目标行为 不允许的行为
主控制台会话即将过期 到期前 2 分钟显示唯一居中提醒,可见剩余时间 无提醒直接失效
主控制台会话已过期 锁定受保护视图,停止管理轮询,显示“会话已过期”自定义弹窗 各模块分别显示 Failed to fetch
用户点击重新登录 顶层浏览器跳转 A-Auth,成功后返回原哈希视图 在 Console iframe 内登录
API 请求遇到会话过期 返回 401 JSON,前端统一处理一次 Nginx 返回登录页 HTML 或跨域 302
用户只有普通权限 保留 403 权限不足语义 把角色权限失败误判为会话过期
多标签页同时打开 任一标签发现过期后,其他标签同步锁定 多个标签同时弹多个登录框
打开 MCP、邮箱、API 网关 只使用统一 A-Auth,不显示第二套登录 在内容区嵌套 A-Auth 登录页
打开 2S-UI、Cloudreve、Nextcloud 新窗口打开,标注“独立登录” 嵌入 Console 或伪装成同一会话
打开 Headplane 标注“统一认证后仍需 API Key” 把 API Key 错误提示成系统不可用
登录页被外部页面嵌入 浏览器拒绝装载并保留顶层访问能力 因全局头误伤 /account 或正常入口

🧩 会话与接口契约

会话参数

建议采用双时限模型,防止“页面一直开着就永久在线”:

参数 建议默认 规则
空闲超时 60 分钟 只有真实用户操作可以续期,后台轮询不续期
绝对最长会话 12 小时 从登录成功开始计算,任何续期都不得超过
到期提醒 提前 2 分钟 前端显示倒计时和重新登录入口
活动续期节流 最多每 5 分钟一次 避免每次鼠标或键盘事件都写数据库
102 只读备用 不续期 renewable=false,保持 P16 只读边界

明确补充:空闲阈值允许后续配置为 15 至 120 分钟,但默认选择 60 分钟;这不是鼓励延长到数天。若未来启用二次认证或可信设备,再单独评审更长会话。

数据模型

在不复制业务数据的前提下,为 sessions 增加以下可空兼容字段:

字段 含义 迁移规则
last_activity_at 最后一次真实用户活动时间 旧记录回填 created_at
absolute_expires_at 会话绝对截止时间 迁移时取旧 expires_at 与安全上限中的较早值
expires_at 继续作为空闲截止时间 不删除旧字段,兼容现有查询和回滚

旧会话只读兼容,不延长其绝对有效期。新登录才启用双时限;需要更长会话时重新登录即可,不做批量会话延期。

API 契约

方法与路径 成功 未登录 只读备用
GET /session 200,含 expires_at、absolute_expires_at、renewable 401 {"code":"session_expired"} 200 且 renewable=false
POST /session/activity 200,返回新的空闲截止时间 401 409 或 200 renewable=false,不写数据库
POST /logout 204 并清除 Cookie 幂等 204 只清 Cookie,不写主库
受保护 XHR/API 正常业务响应 401 JSON,不使用 Location 只读或明确的只读拒绝
顶层 HTML 导航 正常页面 302 到登录页 只读页面或降级提示

401 JSON 至少包含 code、message、login_url 和 request_id,不得包含内部路径、令牌、主机凭据或业务正文。

Console 状态机

Console 会话状态固定为 checking、active、warning、expired、locked 和 logout。进入 expired 后停止受保护轮询、锁住管理内容并只允许“重新登录”和“退出”两个动作;重新登录返回后重新执行 GET /session,不得直接相信旧页面中的角色对象。

所有 fetchConsole() 请求共用同一拦截器。首个 401 打开弹窗,其余并发失败只加入诊断日志,不重复弹窗。403 进入权限不足状态,500/502/503/504 进入服务故障状态,TypeError: Failed to fetch 才是网络失败候选,四类不得混写。

🧾 工作包与执行顺序

编号 工作包 状态 前置依赖 主要产出
CS-0 基线与兼容性矩阵冻结 已完成 本计划获批 当前入口、iframe、Cookie、重定向和测试基线
CS-1 A-Auth 双时限会话与活动续期 已完成 CS-0 数据库兼容迁移、会话 API、单元测试
CS-2 Nginx 认证失败响应分流 已完成 CS-0 HTML 302、API 401 JSON、CORS 头契约
CS-3 Console 全局会话管理与过期弹窗 已完成 CS-1、CS-2 单弹窗、倒计时、锁定、登录后恢复原视图
CS-4 嵌入服务与同信任入口治理 已完成 CS-3 父页面预检查、iframe 过期事件、无嵌套登录
CS-5 独立认证入口分类与标识 已完成 CS-3 统一登录、独立登录、附加 API Key 明确标识
CS-6 登录页防嵌入兼容验证 已完成 CS-2、CS-3、CS-4 路由级 CSP、XFO、浏览器兼容矩阵
CS-7 多标签、权限与网络错误负向回归 已完成 CS-3 至 CS-6 401/403/5xx/断网/多标签一致性测试
CS-8 生产灰度、监控、回退与归档 已完成 CS-1 至 CS-7 生产发布证据、回退点和完成归档

依赖顺序

  1. CS-0 先完成只读盘点和测试基线,不修改生产。
  2. CS-1、CS-2 可以分别开发,但必须用同一 session_expired 错误契约联调。
  3. CS-3 只能在前两项接口稳定后接管统一 UI。
  4. CS-4、CS-5 与 CS-3 共用入口元数据,避免各自维护认证分类。
  5. CS-6 必须在嵌套登录已被消除后执行,不能先收紧帧策略再等待前端补偿。
  6. CS-7 完成后才允许 CS-8 生产灰度。

CS-0 基线清单

执行下列只读检查并把结果写入 temp/console-session-hardening/baseline/:

  1. 列出所有跳转到 https://auth.yohan.fun/login 的 Nginx 路由与 Console 入口。
  2. 列出所有 type: "embed"、type: "external"、iframe 和 window.open。
  3. 回读 /login、/api-key-login、/account、Console、API、Nomad、MCP、邮箱、mesh 的响应头和重定向链。
  4. 记录当前 Cookie 名称、域、SameSite、Secure、HttpOnly 和默认 TTL,不读取 Cookie 值。
  5. 记录 102 standby_readonly 的会话验证路径,确认不会把活动续期写入只读库。
  6. 生成桌面与移动端截图基线,覆盖 Console、网络态势、任务调度、MCP、API 网关和账户页。

🛡️ 防嵌套登录与 iframe 兼容门禁

用户要求先验证禁止 iframe 后网页是否仍能正常打开,因此本计划不允许直接把整个 A-Auth 域名设为 frame-ancestors 'none'。实施顺序固定为:

门禁:兼容矩阵全部通过才上线。

路由级策略

A-Auth 路由 是否允许被嵌入 目标响应策略 依据
/login 禁止 frame-ancestors 'none',可附加 X-Frame-Options: DENY 登录必须发生在顶层页面
/api-key-login 禁止 与 /login 相同 不能在第三方内容区收集 API Key
/account 仅允许当前 Console CSP 精确允许 https://console.yohan.fun,不设置会覆盖 CSP 的宽松 XFO 当前 Console 账户视图确为 embed
/session、/internal/auth 不适用页面嵌入 401 JSON 或无缓存响应 只供程序请求与内部子请求

如果 CS-4 决定把“账户与安全”改为新窗口,则可进一步提高为账户页也禁止嵌入;该变更必须单独验证并保留旧入口回退,不在本计划首轮强行切换。

处理前必须完成的兼容矩阵

# 用例 通过条件 失败时
1 直接打开 https://auth.yohan.fun/login HTTP 200,表单和脚本正常,无 CSP 自阻断 禁止上线帧策略
2 Console 顶层未登录跳转登录页 整个窗口进入登录页,登录后回到 Console 禁止上线帧策略
3 API、Nomad、mesh、mem 顶层重定向 进入顶层登录页,无 X-Frame-Options 错误页 禁止上线帧策略
4 Console 内打开“账户与安全” /account 仍能嵌入并通过现有账户接口读取会话 保留 /account 专用允许策略
5 Console 会话过期后打开 MCP 父页面先显示唯一过期弹窗,不加载登录 iframe 继续调整 CS-3/CS-4
6 Console 会话过期后打开邮箱 与 MCP 相同 继续调整父子认证契约
7 在外部测试页 iframe /login 浏览器阻止登录页框架,顶层直接访问仍正常 修正 CSP 路由匹配
8 在外部测试页 iframe /api-key-login 浏览器阻止装载 修正 CSP 路由匹配
9 桌面、窄屏和移动端 顶层登录、Console 弹窗和账户页无横向溢出 修复响应式后再上线
10 102 只读备用 能验证旧会话并显示只读,不尝试写活动续期 回滚备用适配

防嵌入验收原则

登录页的防嵌入必须以浏览器实际结果为准,不能只通过 curl 是否存在响应头来判定。自动化测试至少使用 Playwright 打开顶层页面、创建跨源 iframe、读取浏览器控制台与网络状态,并保留截图和日志。X-Frame-Options 只作为兼容旧浏览器的附加保护,现代浏览器主判据是 CSP frame-ancestors。

禁止项:不能在内容区登录。

🔧 实施步骤

  1. 执行 CS-0 基线盘点,确认所有登录跳转、iframe、独立认证入口和 102 备用路径,产物仅写 temp/。
  2. 为 Auth 数据库增加兼容字段和迁移测试,先完成单实例测试,不直接接触生产数据库。
  3. 实现 GET /session 新字段和 POST /session/activity,覆盖空闲续期、绝对上限、只读备用、退出和撤销。
  4. 修改 Nginx 模板:HTML 导航继续 302,API/XHR 返回 401 JSON,动态 CORS 响应只在明确来源上生效。
  5. 在 Console 中实现全局 sessionManager、统一的 fetchConsole()、单例过期弹窗、倒计时、锁定和原视图恢复。
  6. 把管理轮询、网络态势、Nomad、AI Boss、邮箱、MCP 和 API 网关全部接入同一会话状态,禁止各自弹登录。
  7. 增加入口认证分类元数据和 UI 标签,统一登录入口禁止 iframe 登录,独立账号系统使用新窗口。
  8. 在本地或隔离环境先注入登录页 CSP,执行防嵌入兼容矩阵;全部通过后才进入生产灰度。
  9. 执行 Playwright 桌面与移动端回归,模拟 401、403、5xx、网络断开、多标签、登录返回和 102 只读降级。
  10. 分两批发布:第一批 A-Auth 与 Nginx 兼容改动,第二批 Console 体验与入口分类;每批都保留独立回滚点。
  11. 完成生产验收后更新 Console/Auth README、工程规范中的会话契约和本计划状态;未满足门禁时保持“待执行”或“未完成”。

🧪 发布、验收与回退

发布批次

批次 内容 进入条件 退出条件
A Auth 双时限、会话 API、兼容迁移 单测和旧会话兼容通过 Auth 健康、现有登录、102 只读验证通过
B Nginx 401/302 分流与 CORS 路由契约测试通过 Console/API/Nomad 真实请求符合预期
C Console 会话弹窗、锁定与恢复 浏览器契约和移动布局通过 无嵌套登录、无重复弹窗、原视图恢复
D 入口分类与登录页防嵌入 兼容矩阵 10 项通过 顶层登录正常、iframe 登录被阻止、账户页正常
E 生产观察与归档 前四批全部通过 监控、真实用户路径、回退材料和文档全部留档

必需验收

验收项 判据
会话绝对上限 空闲续期不能突破 12 小时;超过后所有受保护入口返回标准过期
空闲续期 真实点击或输入可续期,后台 15 秒轮询不续期
单次重登录 多个并发 401 只出现一个弹窗,登录后返回原视图
多标签 一个标签过期后其他标签进入同一锁定状态
错误分类 401、403、5xx 和网络断开分别显示不同文案
嵌套登录 MCP、邮箱、API 网关过期时不在内容区显示 A-Auth 登录页
独立入口 2S-UI、Cloudreve、Nextcloud 新窗口打开并标为独立登录
Headplane 明确显示 A-Auth 后仍需 API Key,不把 Key 失败说成系统不可用
登录页顶层访问 /login 与 /api-key-login 顶层打开正常
登录页嵌入 外部 iframe 无法装载两个登录页
账户页兼容 Console 的 /account 嵌入仍可用,或按独立决策改成新窗口
102 只读 不写会话表,不提供活动续期,继续显示只读来源与快照年龄

监控与证据

监控只记录非敏感字段:会话事件类型、入口、HTTP 状态、是否只读备用、是否发生重定向、错误代码和耗时。禁止记录 Cookie、令牌、密码、API Key、完整 URL 查询中的敏感信息或页面正文。

归档证据必须包含 Git 提交、GitHub Actions、镜像摘要、Nginx -t、Auth 健康、SQLite quick_check、浏览器截图、CSP 响应头、Playwright 日志、回退文件 SHA-256 和 102 只读验证结果。

回退方案

变更 回退方式 保留边界
Auth Schema 保留新增可空字段,回滚代码不删除列 不删除会话和账户数据
Auth 镜像 回退到上一已验证镜像 旧会话仍按旧 expires_at 处理
Nginx 模板 恢复变更前配置备份并执行 nginx -t 后 reload 不使用未经语法检查的手工片段
Console 镜像 回退上一 SHA 镜像 不修改后端会话状态
CSP/XFO 只移除对应路由级响应头 先恢复顶层登录,再排查规则
入口分类 恢复旧视图元数据 不影响独立系统自身登录

任何回退都必须先恢复“顶层登录可达”,再处理样式或状态一致性;不能为了保留新弹窗而让用户无法登录。

🧩 分布式底座接入声明

  1. 接入结论:本计划不接入 Nomad、Compute、StorageAdapter、ToolAdapter 或模型系统。
  2. 使用层:只使用现有公网 Edge、RR Nginx、A-Auth、Console、API Gateway 和 102 只读备用;不新增 Worker。
  3. 执行映射:101 负责代码、测试、GitHub Actions 与发布协调;RR 部署生产 Auth/Nginx/Console;102 只验证只读会话兼容。
  4. 数据路径:浏览器 Cookie 到 A-Auth SQLite;Console 只读取会话元数据;没有业务文件、模型上下文或 Runtime Artifact 数据进入本链路。
  5. 凭据路径:现有 AUTH_SESSION_SECRET 与 root-only 环境文件保持不变;新增代码不得输出 Cookie、令牌或密钥。
  6. 生命周期:会话由空闲截止和绝对截止控制;轮询不续期;退出和密码修改保持现有撤销语义。
  7. 验收证据:Auth 单测、Nginx 契约测试、Console 浏览器测试、Playwright 帧测试、SQLite 完整性、102 只读测试和真实生产响应头。

🧪 当前验证证据

2026-10-24 的本地实现与门禁结果如下。所有结果均来自仓库当前工作树,尚未替代生产发布后的真实浏览器与 102 只读验收。

验证对象 命令或路径 结果 当前边界
Auth 双时限与兼容迁移 .venv/bin/python -m pytest -q tools/auth-site/tests/test_auth.py 22 passed 覆盖空闲续期、绝对上限、登录页帧策略、iframe 账户失效和 102 只读
Console 工作区静态契约 bash tools/console-site/tests/workspace-contract.sh PASS 覆盖会话弹窗、活动接口、iframe 事件和认证分类标识
Console 浏览器契约 bash tools/console-site/tests/browser-contract.sh PASS 覆盖临期提醒、继续使用、401 锁定、恢复和父页面事件
RR 回源网关契约 bash scripts/maintenance/tests/main-origin-gateway-test.sh main-origin-gateway-test=pass 覆盖 API 401、CORS 来源、iframe 事件和活动续期路由
Edge 路由级帧策略 bash scripts/deploy/edge-nginx/tests/edge-nginx-deploy-contract-test.sh edge_nginx_deploy_contract_test=passed 登录路由禁止嵌入,账户路由保留 Console 精确允许
102 备用控制面 bash scripts/maintenance/tests/control-plane-standby-install-test.sh control_plane_standby_install_test=pass 仅证明部署与同步契约,生产写入边界待实机验证
全量本地门禁 bash scripts/ci/run-local-tests.sh PASS Runtime 211 passed、Auth 22 passed,MkDocs --strict 成功

生产发布与真实浏览器验收已经完成,CS-1 至 CS-8 全部闭环。发布前的 Auth SQLite 一致性备份位于 RR /backup/agentmeshos/auth-site/auth-pre-session-v2-20261024T052331Z.db,SHA-256 为 25460123a4e1c64ab90e1a0d51a74a73074211a3518afb31899c3cbbdfc3c96f。

生产验收证据

验证对象 结果 证据
RR 主 Auth v0.4.0,running/healthy,SQLite quick_check=ok 新增双时限字段已落库,空字段计数为 0
RR API 过期语义 HTTP 401 application/json,包含 session_expired 与 request_id Console 来源收到精确 Access-Control-Allow-Origin 和 credentials 头
RR iframe 过期语义 HTTP 401 text/html,向 Console 发送 agentmeshos:session-expired 不再在内容区加载 A-Auth 登录页
四台 Edge 帧策略 MM、d02、DD、dog-public 均为登录页 frame-ancestors 'none' + XFO DENY /account 保持 Console 与本地调试来源的精确允许策略
真实 Chromium 顶层登录、移动端布局、Console 未登录跳转、登录 iframe 被阻止 top_level_login=pass、mobile_layout=pass、console_redirect=pass、login_iframe_blocked=pass
真实管理员会话 Console、节点管理、账户 iframe、临期弹窗、活动续期、过期锁定、移动端弹窗 全部 pass;临时会话测试后已撤销,RR 与 102 会话计数均为 1
102 只读备用 Auth v0.4.0,standby_readonly,read_only=true,renewable=false 契约通过 端点 100.64.0.4:39150 健康,快照年龄正常;CI 与手动同步均通过
热修复提交门禁 62ea71ec5db208bed0fdd739c50376816cbcb29e post_push_verify=pass,必需工作流无失败

⚠️ 风险与未决项

风险或未决项 当前事实 处置
/account 当前依赖 iframe Console account 视图为 embed,Auth 页面 CSP 允许 Console 首轮保留 /account 专用允许策略;是否改新窗口另行决定
跨子域 Cookie 与 SameSite Cookie 域为 .yohan.fun,页面和 API 属同站点 在真实浏览器验证 401、CORS 和 Cookie 发送,不只依赖单测
102 只读备用 备用模式禁止写会话表 活动续期必须显式检测只读模式,返回不可续期
会话迁移 旧库已有 expires_at 和活动会话 只加兼容字段,不批量延期,不删除旧字段
多标签复杂度 当前没有会话事件广播 使用 BroadcastChannel 并提供 localStorage 事件回退
独立系统仍有自己的登录 2S-UI、Cloudreve、Nextcloud 不共享 A-Auth 只做标识和新窗口边界,不强行统一身份
防嵌入误伤 已完成路由级限制和真实验收,/account 仍能嵌入 保持 /login、/api-key-login 禁止嵌入,/account 精确允许 Console
用户已有未保存输入 会话过期弹窗可能覆盖当前表单 弹窗不刷新页面;登录成功后返回原哈希,不承诺保存输入内容

已确认并执行的决策:

  1. 接受默认空闲会话 60 分钟、绝对上限 12 小时和提前 2 分钟提醒。
  2. 首轮保留 Console 内“账户与安全” iframe,只为 /login 与 /api-key-login 启用禁止嵌入;若改为新窗口,再单独提高 /account 限制。
  3. 首轮只治理 Console 与同信任管理入口,不统一 2S-UI、Cloudreve、Nextcloud 的账号体系。

当前未决项:无。独立系统的账号体系不纳入本计划,是已确认的边界,不是遗留缺陷。

🔁 2026-10-24 归档后校正

生产回读发现,本计划给所有 Console fetch 增加的 X-Request-ID 会让跨域 GET 升级为 CORS 预检;RR 只对 /api/alerts 放行了 OPTIONS,因此 /api/status、/api/nomad/overview 和 /api/network/topology 的无 Cookie 预检返回 401,浏览器最终显示 Failed to fetch。校正措施是在 api.yohan.fun 的所有受保护路由上、auth_request 之前统一处理 OPTIONS,返回受控 CORS 头并以 204 结束;真实 GET 仍必须通过 A-Auth admin。另有两个由重启暴露的生命周期缺陷分别处理:101 记忆 Web 服务未持久自启,以及 102 OmniRoute socket 的开机排序环。