🎯 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 或节点配置。
🗂️ 目录
🧩 背景与范围
用户在浏览器登录 Console 后长期未操作,再次进入不同功能时看到以下混合状态:
| 页面或入口 | 当前表现 | 用户感知 |
|---|---|---|
| 节点管理 | 页面壳和公开状态仍可显示 | 误以为整个系统仍在线且已登录 |
| 网络态势 | 网格区显示 网络拓扑暂不可用: Failed to fetch |
误以为拓扑服务或节点网络故障 |
| 任务调度 | 各区域显示 Nomad 状态读取失败: Failed to fetch |
误以为 Nomad 集群不可用 |
| MCP 记忆服务 | Console 内容区出现 A-Auth 登录页 | 出现嵌套登录,流程不透明 |
| API 网关 | Console 内容区出现 A-Auth 登录页 | 不确定是否形成第二套账户或会话 |
本计划包含以下范围:
- A-Auth 会话模型、过期判断、活动续期和只读备用兼容。
- Nginx 对 HTML 导航、XHR/API、iframe 和外部入口的差异化认证失败响应。
- Console 全局会话状态、过期弹窗、请求拦截、跨标签同步和登录后恢复视图。
- MCP、邮箱、API 网关、Nomad 等同信任入口的嵌套登录治理。
- 2S-UI、Cloudreve、Nextcloud、Headplane 等独立或附加身份层的界面标识。
- 登录页 CSP 防嵌入的兼容性验证、选择性实施、自动化测试与回退。
本计划不包含以下范围:
- 不修改 Headscale、Tailnet、DERP、Peer Relay、Edge 或 DNS。
- 不修改 Runtime、AI Boss、Nomad 调度、Cloudreve 数据面或 Provider 配置。
- 不替代 2S-UI、Cloudreve、Nextcloud 或 Headplane 自己的认证。
- 不为普通会话过期引入人工审批、Runtime Plan/Task 或告警流程。
- 不通过无限延长 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 | 生产发布证据、回退点和完成归档 |
依赖顺序
- CS-0 先完成只读盘点和测试基线,不修改生产。
- CS-1、CS-2 可以分别开发,但必须用同一
session_expired错误契约联调。 - CS-3 只能在前两项接口稳定后接管统一 UI。
- CS-4、CS-5 与 CS-3 共用入口元数据,避免各自维护认证分类。
- CS-6 必须在嵌套登录已被消除后执行,不能先收紧帧策略再等待前端补偿。
- CS-7 完成后才允许 CS-8 生产灰度。
CS-0 基线清单
执行下列只读检查并把结果写入 temp/console-session-hardening/baseline/:
- 列出所有跳转到
https://auth.yohan.fun/login的 Nginx 路由与 Console 入口。 - 列出所有
type: "embed"、type: "external"、iframe和window.open。 - 回读
/login、/api-key-login、/account、Console、API、Nomad、MCP、邮箱、mesh 的响应头和重定向链。 - 记录当前 Cookie 名称、域、SameSite、Secure、HttpOnly 和默认 TTL,不读取 Cookie 值。
- 记录 102
standby_readonly的会话验证路径,确认不会把活动续期写入只读库。 - 生成桌面与移动端截图基线,覆盖 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。
禁止项:不能在内容区登录。
🔧 实施步骤
- 执行 CS-0 基线盘点,确认所有登录跳转、iframe、独立认证入口和 102 备用路径,产物仅写
temp/。 - 为 Auth 数据库增加兼容字段和迁移测试,先完成单实例测试,不直接接触生产数据库。
- 实现
GET /session新字段和POST /session/activity,覆盖空闲续期、绝对上限、只读备用、退出和撤销。 - 修改 Nginx 模板:HTML 导航继续
302,API/XHR 返回401 JSON,动态 CORS 响应只在明确来源上生效。 - 在 Console 中实现全局
sessionManager、统一的fetchConsole()、单例过期弹窗、倒计时、锁定和原视图恢复。 - 把管理轮询、网络态势、Nomad、AI Boss、邮箱、MCP 和 API 网关全部接入同一会话状态,禁止各自弹登录。
- 增加入口认证分类元数据和 UI 标签,统一登录入口禁止 iframe 登录,独立账号系统使用新窗口。
- 在本地或隔离环境先注入登录页 CSP,执行防嵌入兼容矩阵;全部通过后才进入生产灰度。
- 执行 Playwright 桌面与移动端回归,模拟 401、403、5xx、网络断开、多标签、登录返回和 102 只读降级。
- 分两批发布:第一批 A-Auth 与 Nginx 兼容改动,第二批 Console 体验与入口分类;每批都保留独立回滚点。
- 完成生产验收后更新 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 | 只移除对应路由级响应头 | 先恢复顶层登录,再排查规则 |
| 入口分类 | 恢复旧视图元数据 | 不影响独立系统自身登录 |
任何回退都必须先恢复“顶层登录可达”,再处理样式或状态一致性;不能为了保留新弹窗而让用户无法登录。
🧩 分布式底座接入声明
- 接入结论:本计划不接入 Nomad、Compute、StorageAdapter、ToolAdapter 或模型系统。
- 使用层:只使用现有公网 Edge、RR Nginx、A-Auth、Console、API Gateway 和 102 只读备用;不新增 Worker。
- 执行映射:101 负责代码、测试、GitHub Actions 与发布协调;RR 部署生产 Auth/Nginx/Console;102 只验证只读会话兼容。
- 数据路径:浏览器 Cookie 到 A-Auth SQLite;Console 只读取会话元数据;没有业务文件、模型上下文或 Runtime Artifact 数据进入本链路。
- 凭据路径:现有
AUTH_SESSION_SECRET与 root-only 环境文件保持不变;新增代码不得输出 Cookie、令牌或密钥。 - 生命周期:会话由空闲截止和绝对截止控制;轮询不续期;退出和密码修改保持现有撤销语义。
- 验收证据: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 |
| 用户已有未保存输入 | 会话过期弹窗可能覆盖当前表单 | 弹窗不刷新页面;登录成功后返回原哈希,不承诺保存输入内容 |
已确认并执行的决策:
- 接受默认空闲会话 60 分钟、绝对上限 12 小时和提前 2 分钟提醒。
- 首轮保留 Console 内“账户与安全” iframe,只为
/login与/api-key-login启用禁止嵌入;若改为新窗口,再单独提高/account限制。 - 首轮只治理 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 的开机排序环。