新增E2E认证前置统一任务记录 [09-07-e2e-auth-preconditions]
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

从主工作区未跟踪目录合并提交。P1、in_progress,独立于 release-gate-integrity,
调查合成浏览器验收的认证起点(登录/会话恢复/注销边界),已完成阶段 0 本地
观测:bootstrap 在线/离线信号、refresh 触发路径、24 文件字面交集与矛盾路径
静态盘点;不预设批量迁移 spec,未获批量修改授权。

未改运行时代码,未运行产品测试或部署。

Co-Authored-By: Claude Sonnet 5 <[email protected]>
这个提交包含在:
2026-09-25 20:41:00 +08:00
共同撰写人 Claude Sonnet 5
父节点 efe2df4da3
当前提交 096576d882
共修改 6 个文件,包含 412 行新增和 0 行删除
@@ -0,0 +1,202 @@
# E2E 认证前置统一 — 阶段化设计
2026-09-07 最新状态:阶段 0 与在线对照均已获用户复核认可;原 31+2 迁移继续暂缓。环境实现合同唯一入口为 .trellis/tasks/09-06-release-gate-integrity/design.md §10(待审),本任务保留取证,不平行维护另一套 runner 方案。207 浏览器清单已撤销决策资格,原始观测及脚本保留;这不是逐条认定 207 全为假失败,也不代表其他真实红项已查清。
## 1. 已有 trace:cookie 不是已证明的先因
本轮读取 trace-comparison-20260907-01 的 single 合同只读用例,未重跑浏览器。原件位于 artifacts/qa-flow/release-gate-integrity/trace-comparison-20260907-01/single/report/test-results/ 下的该用例目录;SHA-256 为 4b0f4c51736817f1e7035f553fbd9546a171a650ea88ab3dd9ce61a826027121。
检查 archive 的 21 条 network 记录及 browser trace 的 Route.fulfill 事件。相对首次 goto 起点:
| 事件 | 起始偏移 | 直接观测 |
| --- | --- | --- |
| GET /login | +61.3ms | 200;request Cookie header 不存在,cookies 数组没有 has_session=1;该响应未记录设置此标记 |
| POST /api/auth/refresh | +1129.3ms | 200;request Cookie header 不存在,cookies 数组没有 has_session=1 |
| Route.fulfill,call@28 | +1145.3ms | 参数 status=200 且 headers 指定 Set-Cookie: has_session=1;调用完成且没有错误 |
| GET /api/me | +1376.3ms | 200;request Cookie header 和 cookies 数组均有会话标记 |
- 全 trace 没有 /api/auth/login 请求,且只有上述一个包含会话标记的 fulfill 调用;payment-reviewer-permissions.spec.ts:194 的合并处理器在 :198 指定该标记。结合时序、调用和源码,首次可见设置动作对应 refresh 合成响应,不是 /login 前已经观察到的 cookie。
- network 序列本身没有保留此 Set-Cookie header。必须写成“Route.fulfill 参数里观察到”,不能伪装成从 network response header 直接读到;请求开始时间也不等于响应完成时间。
- 请求及处理器参数不是任意 JS cookie 写入的完整审计。本证据反驳“refresh 必然证明事先已有 cookie”的推导,不声称排除全部未记录写入。
- AuthContext.tsx:365 在离线且有可用快照时走离线快照;:370 在无会话提示且在线时直接未认证。离线且无可用快照时,即使没有会话标记,也可能继续 refresh。
- 旧 trace 未捕获 navigator.onLine、离线快照可用性或 JS 调用栈,不把“可能离线”定为根因,也不将未来观测追认成旧现场值。
## 2. 认证夹具状态模型
| 起点/事件 | refresh | 场景行为 |
| --- | --- | --- |
| guest,未显式登录 | 未认证 | 可驱动表单,不预先返回身份 |
| guest → 登录成功 | authenticated | 登录及 /me 成功,后续刷新可恢复 |
| authenticated 显式起点 | 成功恢复 | 直接进入业务入口,不再填表单 |
| authenticated → 注销 | guest | 后续 refresh 未认证,旧身份不复活 |
参照 local-candidate-witnesses.spec.ts:199 的初始 false、:241 的登录、:252 的 refresh 判断和 :260 的注销。其状态合同正确不等于该文件的原失败通过,也不构成默认重写授权。
login-portal-ux 混合访客、Portal 登录及 bootstrap 恢复场景;mobile-approvals-offline 前两条使用本地真实 API,第三条才安装合成 mock。迁移以场景调用图为单位,不全文替换。
可采用共享有状态 helper,具体接口和写入范围在阶段 0 后交审。不得修改 AuthContext、覆盖 navigator.onLine、删除 full navigation 或不再检查 /me 以规避矛盾;PWA/offline 场景保留专用网络语义,不无差别关闭 Service Worker。
## 3. 阶段 0:本地取证与静态盘点(2026-09-07 已独立复核批准)
1. 冻结候选、获准 21 文件摘要、24 文件清单及工具/构建条件。使用现有独占临时库、无外网本地合成通道,不接生产或 live。
2. 优先复用可核验的现有构建和 runner;先检查源码绑定及资源归属。前提已清理或无法核实时按现有隔离流程重建,不冒称旧构建属于当前候选。
3. 只预定一条代表性合同用例,1 worker、零重试、trace=on,原超时和业务断言不变。观察首次 bootstrap 周边的会话标记、navigator.onLine、离线快照可用性及 refresh 调用入口;不同时再启全套或追加重试。
4. 仅输出布尔值、函数名、相对时序、路径/状态及摘要,不输出身份、credential 或存储内容。如需观测注入,只能在独占实验副本中添加观测,不改真实工作树、状态判定或 mock 返回;记录副本差异,不冒充未修改候选的普通门禁。
5. 若一次观测仍不能区分入口或条件,保留未知并报告最小后续实验,不追加运行。首次问题取得证据后,再追踪非 manual spec 的 helper、TEST_IDS 和 route 注册,产出场景清单及拟写入清单。
6. 此阶段不要求整条用例绿:认证前置仍失败如实报告;若越过前置而停在旧“更多”,也只报告该进展。原 trace、截图、日志和派生索引分列,清理失败不倒写为自动通过。
本阶段不修改真实工作树中的业务或测试源文件,不开展文件外 80 条的独立修复,不实现门禁。静态新发现仅是候选,不构成写入授权。
### 3.1 本轮观测结果与边界
证据目录为 artifacts/qa-flow/e2e-auth-preconditions/phase0-20260907-03/。source-before.json 绑定 HEAD=66f25b1、dirty 源码摘要及获准 21 文件;不是仅用 commit 冒充带工作树差异的候选。来源快照共 1052 文件(client 436、server 522、scripts 79、Trellis 15),收尾逐项核对未变。构建在独占临时副本中完成,未覆盖 client/dist;观测副本的差异单独保留。
| 首次入口 | sessionHint | navigator.onLine | 可用离线快照 |
| --- | --- | --- | --- |
| mount-effect | false | false | 此调用未读取 |
| bootstrap-entry | false | false | false |
| bootstrap-primary-refresh | false | false | false |
| runRefresh-entry | false | false | 此调用未读取 |
网络依次为 GET /login 200(请求无会话标记)、POST /api/auth/refresh 200(请求无标记)、GET /api/me 200(请求有标记);全程没有 /api/auth/login 请求。这证明本轮是“无提示 + 离线 + 无快照”没有命中 AuthContext.tsx:365/:370 的两个早退条件,进入 bootstrap 主 refresh 分支,再被成功 mock 恢复身份。不是登录前已观察到 cookie,不是业务断言通过。
- 观测只添加一个日志 helper 和 11 个调用;去除这些节点后 AST 与原 AuthContext 完全一致,回执位于 phase0-20260907-01/observation-ast-check.json。未改条件、返回值、mock 或原测试源码。
- 后续还有第二组 mount/bootstrap 日志,但实际网络 refresh 只有一次;不把函数日志重复当成网络重试,也不无证据归因 StrictMode。
- 本轮只在 loopback/无外网隔离环境实际观测到 online=false。未做网络环境的单变量对照,不宣称 unshare 必然导致该值;旧 trace 的 online 值仍未观测,不能倒填。修复目标仍是夹具状态合同,不是强行将 navigator.onLine 改成 true。
- 唯一实际执行的原用例为 payment-reviewer-permissions.spec.ts:380,选择标题“合同只读人员看不到新增编辑续约和归档动作”,workers=1、trace=on、retries=0。原始结果 1 timedOut / retry 0 / 216617ms;错误仍指向 login-identifier 的 locator.fill,续约/归档断言没有到达。
- 原始 Playwright JSON、trace.zip、observation-result.json 及静态清单分别保留和哈希;trace SHA-256 为 fc1bb04629f8dcba772959db1534ec1a24ffa2dee7a67c9def97275428150f00。观测副本回执固定无发布资格,harness exit 2 不表示测试通过。
两次准备错误不能隐去,也不能算作浏览器重试:phase0-20260907-01 缺 QA_DIAGNOSTIC_PWA_ROOT,在启动 Playwright 前退出;phase0-20260907-02 的锚定 grep 未匹配 qualified title,收集 0 条并报 No tests found。两者实际浏览器用例均 0,原错误及分类保留。03 改用已验证的无锚点标题选择器,先 --list 确认恰好 1 条,再执行唯一一次浏览器用例。未重试任何业务断言。
资源按各次 private 归属记录定点核验:3 个独占容器和 3 个临时副本目录均已不存在,未遍历或操作生产容器。01/03 有独立的剩余库计数 0;02 只有原阶段 cleanupExit=0,没有单独计数文件,不补造数字。最终来源/资源回执为 closeout-receipt-v2.json;首版校验错误地要求每次都有计数文件而返回 false,原件保留,修正版明确区分证据类型。
### 3.2 静态分母与历史迁移清单(暂缓,不是缺陷数)
下表及 static-census.json 保留阶段 0 的静态标签和提案来源。在线对照已证明代表性用例在原生在线时无需改变 mock 即可完成登录与会话恢复,因此“成功 refresh 与表单共存”本身不足以认定内在矛盾。执行端撤回默认迁移 31 文件的建议;原件不覆盖,最新结论以 §6.1 为准。
static-census.json 从文件系统枚举当前 65 个非 manual spec,追踪静态 import/export、局部函数、场景/钩子调用、route 和 TEST_IDS;33 个 spec 直接出现 refresh 路由。依赖闭包记录 42 个非 spec 模块,本地 import 无未解析项,未发现动态 import/require,依赖模块内未发现 refresh 路由或间接 route 注册。函数调用索引只是审阅导航,不是任意 JavaScript 数据流的完备证明;条件分支、动态拼接和将来新增文件仍需审查,不把“未命中”写成运行通过。
| 分类 | 文件数 | 本次提案 |
| --- | --- | --- |
| 成功 refresh 与驱动登录表单存在同场景矛盾 | 30(原 23 + 新 7) | 仅迁移矛盾合成路径,保留原表单及业务断言 |
| 成功 refresh 与访客提示页存在矛盾 | 1 | 单列 wecom-first-bind,不并入“30 个填表单”口径 |
| 状态合同正确 / 显式已认证起点 | 2 | local-candidate-witnesses、PWA 不迁移 |
| 恢复建模缺项,非相同成功身份恢复 | 2 | user-onboarding、visual-portal 延后核对,不列入拟写入清单 |
| 仅业务 mock / 访客供应商入口 mock | 2 | leave-team-calendar、wecom-entry 不迁移 |
| spec 及静态依赖没有 route 注册 | 28 | 保留原本后端、live 入口或纯源码断言,不宣称正常或授权运行 live |
以下路径均位于 client/tests/e2e/。表中列出需改的认证处理器与表单入口坐标;完整场景/钩子、函数调用及依赖锚点见 static-census.json。默认方案是保持现有登录 helper,guest 起步,成功登录后才允许 refresh 和 /me 恢复该场景身份,不批量改成默认管理员或直接跳过登录。行号以当前源码摘要为准。
| spec | refresh 处理器行 | 入口/场景锚点 | 来源及局部边界 |
| --- | --- | --- | --- |
| biz8-ap-ledger-project.spec.ts | 105 | 202/208/217 | 原 23;仅合成认证路径 |
| contract-detail-workspace.spec.ts | 123 | 232/234/246 | 原 23;不改详情业务断言 |
| dg7-mobile-information-dedupe.spec.ts | 501 | 753/760/898 | 原 23;保留按场景角色 |
| internal-readiness-ui.spec.ts | 163 | 297/301/318 | 原 23 |
| login-portal-ux.spec.ts | 81 | mockPasswordLogin:33 / Portal:554 / prepareEmployeePortal:628 | 原 23 的混合文件;仅该 helper 的表单调用链 |
| mobile-approvals-offline.spec.ts | 291 | 第三场景:184 / 表单:322 | 原 23;不动前两条真实 API 场景及离线语义 |
| payment-reviewer-permissions.spec.ts | 194 | 341/343/384 | 原 23;旧“更多”及续约/归档仍归门禁阶段 B |
| pc-core-workflow-visual-wave2.spec.ts | 148 | 306/315/307 | 原 23 |
| pc-detail-mobile-visual-wave3.spec.ts | 679 | 1217/1219/1233 | 原 23 |
| pc-finance-admin-visual-wave4.spec.ts | 489 | 1001/1004/1041 | 原 23 |
| r100-design-baseline.spec.ts | 66 | 188/190/252 | 原 23 |
| r101-mobile-wip-closeout.spec.ts | 134 | 319/321/368 | 原 23 |
| r102-reimbursement-main-flow.spec.ts | 148 | 346/348/357 | 原 23;不改“编辑”相关业务断言 |
| r103-cross-page-ui-consistency.spec.ts | 318 | 609/611/688 | 原 23 |
| r111-invoice-reimbursement-guard.spec.ts | 310 | 681/687/799 | 原 23 |
| r124-mobile-entry-matrix.spec.ts | 168 | 425/432/492 | 原 23 |
| r125-invoice-reupload-after-delete.spec.ts | 190 | 355/361/383 | 原 23 |
| r5-real-device-feedback-mobile-ux.spec.ts | 75 | 243/249/365 | 原 23 |
| r6-mobile-responsive-system-hotfix.spec.ts | 90 | 415/421/534 | 原 23 |
| r6-mobile-unified-design-rollout.spec.ts | 143 | 492/498/623 | 原 23 |
| r6-wecom-mobile-rendering-hardening.spec.ts | 101 | 363/375/523 | 原 23 |
| r89-mobile-login-navigation.spec.ts | 223 | 438/440/471 | 原 23;保留真实登录导航意图 |
| visual-shell.spec.ts | 87 | 340/343/365 | 原 23 |
| announcements-policy-ack.spec.ts | 148(fulfillJson:29 默认 200) | 282/293/303 → helpers/auth.ts:96 | 新 7;保留登录后 currentUser 切换 |
| global-search.spec.ts | 46/48 | 180/208/228/248 → helpers/auth.ts:96 | 新 7 |
| leave-flow.spec.ts | 132(fulfillJson:16 默认 200) | 302/317 → helpers/auth.ts:96 | 新 7;保留 activeUser 选择及注销 |
| r92-mobile-invoice-usability.spec.ts | 53(fulfillJson:10 默认 200) | 286(TEST_IDS)/300/319/345 | 新 7;间接 testid 命中 |
| system-action-confirmation.spec.ts | 338/340 | 615 起 → helpers/auth.ts:96 | 新 7 |
| workflow-action-confirm-ui.spec.ts | 150/152 | 301 起 → helpers/auth.ts:96 | 新 7 |
| workflow-quick-approve.spec.ts | 97/99 | 218 → helpers/auth.ts:96 | 新 7 |
| wecom-first-bind.spec.ts | 60/62,me:74 | 180/181 | 新增访客提示路径,不填表单;固定 guest |
wecom-first-bind 的成功 refresh 和 /me 会满足 GuestOnly(client/src/router/guards.tsx:48)的重定向条件;/login 的 wrapper(client/src/router/index.tsx:103)没有 wecom query 例外。它与待看见的 login-wecom-notice 有静态前置矛盾,但本轮没有用新运行给其旧失败定因。
保留与延后项具体合同:
- login-portal-ux 的 mockGuestAuthChrome 访客场景、邮件验证码及找回密码场景保留;:776 的显式会话提示 + refresh 首次 500 后 200 + 直接 /portal 是产品 bootstrap 恢复回归,不替换成永久 401,也不把产品内部恢复重试等同于 Playwright retry。只迁移 mockPasswordLogin 所服务的表单路径。
- mobile-approvals-offline 只处理第三场景内的合成认证;PWA 的 :509 显式 addCookies 和 :516 直接 /portal 属于合法已认证起点,不迁移,不改 Service Worker 或专用输入供给。
- user-onboarding 只 mock login 和 /me,未 mock refresh,不能计入无条件成功 refresh;visual-portal 的全 API fallback 在 :185 返回 200/空 data,不含恢复身份的 accessToken,也不能并入同一类。两者只是静态可见的恢复建模缺项,另列待核对,不借本次范围顺手修复。
- 28 个无 route 文件逐个登记 import、helper、TEST_IDS 及场景/钩子。business-detail-routing、interaction-pattern-unification、management-navigation-contract、reimbursement-content-consistency 是不依赖浏览器登录的源码/纯函数断言;其余保留现有真实后端或受环境开关控制的入口,未授权开启 live。
- 原 23 文件的 133 条观测(127 失败、6 通过)及文件外 80 条保持历史口径。新 7/访客提示/延后项没有从 80 中扣除,也没有把 207 转成已归因的产品缺陷数。
## 4. 后续批准边界
- 阶段 1:阶段 0 结论、精确清单及 helper 接口单独获批后,先补状态合同回归,再实施最小迁移;保留正常访客、恢复会话和真实 API 样本。
- 阶段 2:按届时批准的固定矩阵验证,零重试和默认全套并发不变;定向认证前置与整套诊断结果分列。全套需完整前提、资源归属和独立回执;中断不补造状态、不自动重跑。
- 认证前置解释清楚后,文件外 80 条和新增红项再单列同类候选、独立问题、前置阻断或证据不足。仅再获批清单可写,不承诺清空所有失败。
- 本任务负责认证前置;release-gate-integrity 阶段 B 仍负责合同详情入口和续约/归档断言。机制推进遵循原任务批准范围,本轮不启动其他阶段 A 内容。
### 4.1 阶段 1 原接口与写入提案(已暂缓,非推荐默认路径)
拟新增 client/tests/e2e/helpers/syntheticAuth.ts 和 client/tests/e2e/synthetic-auth-contract.spec.ts;连同 §3.2 的 31 个现有 spec,共 33 个代码/测试文件。仅当前任务的 PRD/设计/执行记录随结果更新;不改已有门禁 21 文件、产品源码、helpers/auth.ts、配置、依赖或发布入口。新增合同 spec 放在非 manual 集合内,不借新增测试缩减原 65 文件分母。
helper 采用每次安装独立的闭包状态,不用模块级可变会话或共享 storageState:
1. createSyntheticAuthSession({ initialState }) 必须显式指定 guest 或 authenticated。本次 30 个表单矛盾路径使用 guest,wecom-first-bind 的提示场景也保持 guest;已有正确的 authenticated/PWA/恢复场景不迁移。
2. loginSucceeded() 只在场景原有的登录身份选择和成功判定之后切为 authenticated;logout() 清为 guest。原有角色选择、计数、失败注入和响应数据继续由场景提供,helper 不默认赋角色、不存储或输出凭据。
3. refresh 与 /me 通过同一闭包的认证检查:guest 返回既有 AUTH_001/401 结构且不种会话提示;authenticated 调用场景提供的成功响应函数。登录成功保留会话提示,注销清除提示,随后 full navigation 可恢复或保持未认证。refresh 不是永久 401。
4. 接入现有 route 分支;拆开 login/refresh 共用成功分支,不额外注册能被 catch-all 覆盖的竞争处理器。选择性 route 的 login-portal-ux 仍保留原优先级和 :776 自有恢复处理器;不得覆盖该场景。
5. 新合同 spec 先覆盖状态隔离、登录前拒绝、成功登录后 refresh、失败登录不认证、注销后 refresh 拒绝及显式 authenticated;再按获批矩阵验证真实导航/表单/恢复。沿用现有 Playwright,不新引入单测框架,不修改 navigator.onLine 凑绿。
推荐下一批先批准这份 31+2 清单和接口,再实施;浏览器矩阵另批。针对性建议选新合同 spec、合同权限、间接 shared login、角色切换、login-portal-ux 的访客/Portal/恢复、wecom-first-bind 六类边界,固定每条次数、零重试、trace=on。新合同的纯状态断言不启动浏览器;需要浏览器的观测仍应逐条写清授权。后续全套诊断必须绑定迁移后摘要,默认并发不变,不把局部 PASS 当整套结果。
## 5. 验收、回滚及风险
- AC1/AC2 先验收观测与范围,AC3/AC4 后验收获批实施,AC5/AC6 保持剩余问题及发布边界。阶段 0 完成不等于任务完成。
- 普通 full-local 全绿耗时及稳定性仍未知;旧 197 分钟和中断运行不进入发布成本估算。本任务完成不启用 R1。
- 回滚仅涉及本任务以后获准的差异,不回退他人或门禁任务已批准改动。提交、push、归档及部署各自授权。
- 防止把文件内共现误当场景关系,或把恢复会话永久改成 401:使用场景清单、四类状态合同和正确样本,不以重试或弱化断言收口。
## 6. 已批准的在线对照(2026-09-07,仅实验,不迁移)
- 用户批准阶段 0,暂缓阶段 1,仅授权一次在线定向实验。复用原合同单条用例和观测构建,在相同隔离、数据、mock、断言及零重试条件下比较认证前置;不运行整个合同文件的其他用例、不启动全套。
- 网络隔离实际存在于证据目录中的 run-isolated.sh(unshare -Urn)与 run-inner.sh,不在用户列出的受管 runner 内;不能因此推断宿主机本身离线,也不能将外层包装条件漏记。数据库容器继续 network=none,仅使用本轮独占 Unix socket。
- 唯一拟改变的启动前提:在本轮独占网络命名空间内增加一对 up 的 veth 及文档保留地址,不接宿主网卡、不配默认路由、不开放公网。不修改 navigator.onLine,也不通过 CDP 强制在线来替代原生测量。
- 业务用例启动前使用空白页读取原生 online,必须为 true,否则停止、不运行用例。同一浏览器的两个独立 context 中,仅对其中一个 setOffline(true),验证另一个仍 online=true;关闭后再创建新 context,验证在线状态及合成 cookie 隔离。此项是无业务请求的前置检查,结束并关闭浏览器后才运行原用例;不能外推为全仓自定义 fixture 已完全排除泄漏。
- 正式用例固定 payment-reviewer-permissions.spec.ts:380 的原标题,workers=1、retries=0、trace=on;先 --list 确认恰好一条。仍用未改变条件的观测副本,记录实际用例首个 bootstrap 的 online/hint/快照值及 refresh/login 顺序,不只信前置探针。
- 判据按阶段拆分:在线时是否命中 guest-ready、出现 login 请求并越过登录前置;原整条用例最终结果另列。已知旧“更多”断言仍可能超时,不能把该下游红项判成认证实验失败;同样,一条认证前置通过不能证明其余 30 文件均无需修改。阶段 1 的方向仍由用户按结果决定。
- 旧 trace 中登录成功不单独证明当时没有 refresh 或 online=true,历史未观测条件继续保留未知。实验原件、候选/构建摘要、外层脚本差异、context 探针及资源清理分别留痕;不修改产品、测试源或旧 21 文件。
### 6.1 在线对照结果(已获用户复核认可)
证据目录:artifacts/qa-flow/e2e-auth-preconditions/online-20260907-01/。本轮只运行一次原合同用例;prepare.mjs、用例选择、mock、产品判定、断言及观测副本均与阶段 0 相同,1052 份来源文件和 114 份观测构建文件摘要一致。外层差异在 wrapper.diff:仅在独占命名空间内增加 up 的 veth 对及保留地址,并追加在线/context 前置检查和本次授权标签。没有默认路由、宿主外网接口或 navigator 属性改写;原生 online=true 不等于公网可达,也不是一次普通发布绿路径运行。
| 实际用例首个 bootstrap | 阶段 0 离线对照 | 本轮在线对照 |
| --- | --- | --- |
| sessionHint | false | false |
| navigator.onLine | false | true |
| 可用离线快照 | false | false |
| 入口 | bootstrap-primary-refresh | bootstrap-guest-ready |
| 登录请求前 refresh 数 | 1,且没有登录请求 | 0 |
| /api/auth/login | 未调用 | 1 次,200 |
| 登录/合同前置 | 登录表单等待超时 | 通过,到达旧“更多” |
| 整条结果 | timedOut / retry 0 | timedOut / retry 0 |
本轮实际网络顺序为 GET /login → POST /api/auth/login → GET /api/me → GET /contracts-registry → POST /api/auth/refresh → GET /api/me。首个文档及 login 请求不带会话标记;标记出现在登录后。第二次完整文档导航才通过 refresh 恢复身份,这正是该 mock 在在线条件下被真实使用的会话恢复路径,不是应当永久禁止的请求。
trace 中 sourceLine 387/388/389/390/396 的断言均完成且无错误:合同 URL、标题、新增/编辑不可见及合同行可见;:397 的“更多”点击超时。整条原始结果为 1 timedOut / retry 0 / 214570ms,commandExit=1,harness exit=2 / PARTIAL,无发布资格。续约、归档仍未到达,不计入权限验收通过,也不在本轮修改。
原生 online 与 context 隔离前置结果:两个新 context 初始均 true;第一个 setOffline(true) 后该 context 为 false,第二个仍 true;关闭后新 context 仍 true,合成探针 cookie 没有泄漏。检查只使用空白页,不执行额外业务用例。network-preflight.json 的 externalRequests=0 是探针未主动发起外部导航的声明,不是浏览器后台请求的仪表计数;无外网边界由独立命名空间及无外连路由维持。不能据此宣称全套所有自定义 fixture 已排除状态复用。
原始全套诊断的隔离也有明确出处:artifacts/qa-flow/release-gate-integrity/diagnostic-run-20260906-01/run-isolated.sh:43 调 unshare -Urn,:45 只启 loopback。它处于受管 runner 外层;仅搜索 full-local-diagnostic/run-web-gates 看不到这层包装,并不能归因宿主机网络常态。本轮不观测宿主真实网络,也不倒填该旧运行的 navigator 值。
结论与下一项:
- 对这条代表性合同用例,改变原生在线启动条件即可越过认证前置,无需改它的认证 mock;这足以否定“必须先批量迁移 31 文件才能恢复它”的判断。
- 一条用例不能证明全部 30/31 文件均无需修改,也不能凭完整用例仍红就判迁移必要。127/207/80 的旧统计保持不变,没有重新分配或扣减。
- 原 31+2 方案暂缓,不再作为默认建议。下一项建议先定环境启动合同:普通在线验收在实际 context 入口验证 online;有意置离线的场景必须保留自己的语义。此处只提后续方向,尚未实现、未授予门禁或外层环境修改权限。
- 长期规则暂存当前任务,不修改受冻结的模块 spec 或门禁 21 文件;待修复对象与范围获批后再将可复现的执行合同沉淀到对应规约。
closeout-receipt.json 已核对:原 21 文件及 1052 来源文件未变、114 构建文件相同,启动后实验脚本摘要未变;原 cleanupExit=0、剩余独占库计数 0,本轮容器和临时副本按归属定点确认不存在。原始 trace SHA-256:069f15ae266f052898ed41853a56e135ce1e2cc5b1149dc956244694a431ab96。无提交、push、归档、部署或生产操作,场景 8、经理缺口和 R1 继续挂起。
@@ -0,0 +1,93 @@
# E2E 认证前置统一 — 执行与批准清单
## 当前状态
- in_progress / inline。创建轮使用 task.py create --no-start;用户随后明确批准阶段 0,已 task start 切换到本任务,不授予阶段 1 批量修改权限。
- 门禁任务获准 21 文件逐字节未动;HEAD=66f25b1,本地 origin/main=492920c。新任务独立、非子任务,未转移合同阶段 B 的业务验收责任。
- 创建轮只读已有 single trace;随后在阶段 0 授权内实际执行一次原合同用例的观测实验,结果 timedOut,未改真实源码。新现场条件与旧 trace 严格分开,见 design.md §1/§3.1。
- 两个 manifest 保持为空,符合 inline 合同;受影响规约按步骤读取指定章节,不将超出注入上限的大文件登记成可完整自动注入的上下文。未来若切 sub-agent,必须另行准备真实、适量的上下文,空清单不兼容该模式。
## 规划检查
- [x] 记录独立立项、认证状态合同及 24/23/80 的口径边界。
- [x] 核实请求、fulfill 与 /me 顺序,不预设 cookie 污染。
- [x] PRD、设计、执行清单分开;PRD 按最终结构收敛,无 TBD 或重复开放的已答事实。
- [x] 运行上下文、治理、敏感内容及新增文件 whitespace 检查,核对旧 21 文件摘要未变。
- [x] 用户批准阶段 0,已 task start;仅执行该阶段。
## 阶段 0(已批准,2026-09-07)
- [x] 读取任务及受影响规约,冻结候选、原回执及原件摘要。
- [x] 按 design.md §3 核验或重建独占本地前提,留资源归属,不接生产、live 或真实身份。
- [x] 一次单条、1 worker、零重试、trace=on,保持原断言;观测副本差异和实际命令单列,真实候选不动。
- [x] 记录 cookie、online、离线快照和调用入口及缺项;不倒填历史、不自动追加实验。
- [x] 静态追踪 helper/TEST_IDS/route,交场景分类及拟写入清单,不开始迁移。
- [x] 准备 AC1/AC2、清理结果及阶段 1 方案交审;不是独立复核通过。文件外 80 条不逐项展开独立问题。
## 后续阶段(尚未批准)
- [ ] 清单和 helper 接口获批后,补状态合同回归并最小迁移。
- [ ] 保留业务断言及正确样本,记录是否越过认证前置;不修旧“更多”或产品权限缺口。
- [ ] 按固定矩阵验证,完整/中断/未运行状态及候选绑定分列,不重试至绿或冒充整套。
- [ ] 对剩余失败单独归类;新增红项不自动修。
- [ ] 主会话检查后提交独立复核,不自行签独立通过。
- [ ] 提交、push、归档、部署和 R1 启用均等待各自授权。
## 规划验证记录
以下是创建轮历史记录,当轮不运行产品测试、构建、数据库或浏览器;不用于描述随后已获批的阶段 0 实验。
- python3 .trellis/scripts/task.py validate .trellis/tasks/09-07-e2e-auth-preconditions:两个 inline manifest 均 0 entries,通过。
- node scripts/qa/check-trellis-governance.mjs --json:PASS,0 errors / 0 warnings。
- node scripts/qa/check-sensitive-content.mjs --json:PASS,0 findings。
- git diff --check 及六个新增任务文件分别执行 git diff --no-index --check:均通过;旧获准 21 文件逐项 SHA-256 一致。
- 首轮规划校验的 Scope/Test Plan 缺项已补齐;超出注入上限的整份模块文档没有继续登记在 inline 清单中。首轮日志保留于 artifacts/qa-flow/e2e-auth-preconditions/planning-20260907-01/,复验位于 validated/,最终交审快照与回执位于 final/;不覆盖旧门禁回执。
- 以上仅主会话文档验证,不是阶段 0 已执行、实施获批或独立复核通过。
## 阶段 0 运行记录
已收到阶段 0 的明确批准。仅在独占实验副本内为 AuthContext 添加安全观测,真实测试及应用源码保持不变;使用本地隔离库和无外网命名空间,不运行全套,不实施迁移。
| 证据目录后缀 | 实际浏览器用例 | 结果与限制 |
| --- | --- | --- |
| phase0-20260907-01 | 0 | 缺 QA_DIAGNOSTIC_PWA_ROOT,Playwright 前准备失败;观测副本构建及 AST 校验保留 |
| phase0-20260907-02 | 0 | 锚定标题没有匹配 qualified title,No tests found;原始错误保留,不算浏览器重试 |
| phase0-20260907-03 | 1 | 先 --list 确认一条原用例,再 workers=1 / retries=0 / trace=on;1 timedOut,仍在登录前置 |
- 源码绑定:HEAD=66f25b1、local origin/main=492920c,观测前 sourceDigest=b7c8edbe8c3c4005e8dead6cc57aa50a8722bf515f6663e4be8a013aa728cd5a。两次后续准备核对源摘要及构建文件摘要后复用观测构建,不重做产品构建、不覆盖 client/dist。
- 观测结果:首次 sessionHint=false / online=false / offlineSnapshotAvailable=false,入口 bootstrap-primary-refresh → runRefresh;实际网络 refresh 一次,login 零次。没有将本轮浏览器状态倒填旧 trace,也没有把重复函数日志算作网络重试。
- 原始用例耗时 216617ms、retry 0,错误仍为 login-identifier 的 locator.fill;该耗时不是普通 full-local 成本、不是通过证据。
- 依赖盘点:65 个非 manual spec,33 文件有直接 refresh 路由;30 个表单矛盾文件 + 1 访客提示矛盾文件。31 个现有 spec + 2 个新增 helper/合同 spec 是阶段 1 提案,不是写入授权。另两项恢复建模缺口分开延后,原 80 条文件外失败不重新计数。
- 回执位于 artifacts/qa-flow/e2e-auth-preconditions/phase0-20260907-03/。原始报告、trace、observation-result.json、static-census.json 各自保留;收尾 source/resource 核验见 closeout-receipt-v2.json。首版核验因误设“每次都有独立剩余库计数文件”失败,保留 closeout-receipt.json,不补造 02 的计数。
- 清理边界:3 个本轮容器及 3 个临时副本已定点确认不存在;01/03 的原计数为 0,02 原 cleanupExit=0 而无单独计数文件。检查没有修改资源或触碰生产。
## 阶段 0 交审检查
- [x] 再核对原 21 文件、1052 份运行来源文件及 HEAD/origin 引用未变;closeout-receipt-v2.json 的 valid=true。
- [x] 上下文(两个 inline manifest 均 0 entries)、治理(0 errors / 0 warnings)、敏感内容(0 findings)、tracked/new-task whitespace 检查完成。仅主会话检查,不代替独立复核。
- [x] 用户独立复核并批准阶段 0;不等于批准阶段 1 的 31+2 清单和接口。
阶段 0 已独立复核通过,阶段 1 暂缓;后续在线对照另行交审。没有提交、push、归档或部署。场景 8、经理缺口和 R1 均继续挂起。
文档检查原件位于 phase0-20260907-03/review/。首轮 whitespace shell 把 git diff --no-index 的“存在文件差异”退出 1 当作格式失败,实际诊断为空;whitespace-v2.json 保留逐文件退出码及空诊断,未为消除该状态改动内容。最终交审使用相对 planning-20260907-01/final/ 六文件快照的增量;回执不得把旧门禁 21 文件或临时观测副本算作本次新增实现。
## 在线对照(已批准,2026-09-07)
- [x] 冻结阶段 0 回执、原 21 文件和来源快照;在独占命名空间内准备原生 online=true 前提,记录外层包装差异。
- [x] 完成空白页在线与 context 隔离前置检查;实际均符合预期,不伪改 navigator。
- [x] 一次原合同单条、零重试、trace=on:认证及合同前置通过;旧“更多”点击超时,整条非 PASS。
- [x] 校验实际 trace 的 online、hint、调用入口及请求顺序,核验资源清理及来源不变,准备交审。
- [x] 用户认可本次在线结果,下一步先审环境启动合同;原 31+2 迁移不作为默认建议,不自动执行。
仅此在线实验获批;阶段 1 暂缓,场景 8、经理缺口和 R1 不变,无生产、提交、push、归档或部署授权。
## 在线运行回执
最新后续状态:环境合同规划归 release-gate-integrity 的 R2/R6、设计 §10;本轮仅修改两个任务的规划/事实文档和追加撤销资格侧记,不实现、不重跑、不修合同、不改产品或测试源。207 原浏览器清单从真实红项、工作量和 R1 等决策输入中撤销,raw JSON/trace/包装脚本不删除、不覆盖,其他原始证据按自己的范围保留。认证任务保持 in_progress,仅等待后续任务处置,不自行提交、归档或宣称阶段 1 完成。
- 实际命令沿用原选择器:npm run test:e2e:full -- tests/e2e/payment-reviewer-permissions.spec.ts --grep '合同只读人员看不到新增编辑续约和归档动作' --workers=1 --trace=on --retries=0 --reporter=list,json,html。先 --list 确认一条,没有额外业务重跑。
- 实际首个 bootstrap:online=true / sessionHint=false / offlineSnapshotAvailable=false,命中 guest-ready;登录前 refresh=0、login=1。登录后 full navigation 的 refresh 成功是保留的正常恢复路径。
- 387/388/389/390/396 的合同前置断言通过;397“更多”超时。1 timedOut / retry 0 / 214570ms;下游续约和归档未覆盖,不冒充整条通过或 R1 成本。
- 来源、观测构建和原 21 文件逐项未变;本轮容器及临时目录均确认已清理,原独占库剩余计数 0。详见 online-20260907-01/closeout-receipt.json,valid=true。
- 运行脚本、原始报告、trace、前置探针与派生结果各自保存,未覆盖阶段 0 原件。原 static-census.json 的“矛盾”标签保留为历史静态分类,不再当作已证明需修改的缺陷。
- 本次只更新当前任务事实和忽略目录证据,不改受冻结规约或任何产品/测试源码;执行端撤回默认迁移 31 文件的建议,等待用户决定环境合同优先的后续范围。
@@ -0,0 +1,83 @@
# E2E 认证前置统一
## Goal
先确定合成浏览器验收的运行前提与认证起点,再决定修正环境合同还是认证夹具;不预设需要批量迁移 spec。覆盖登录、会话恢复和注销的真实边界,任务独立于 release-gate-integrity,不以修复全部浏览器红项或启用发布门禁作为完成条件。
## Confirmed Background
- 用户同意另立任务及认证状态规则;创建轮只做规划,随后明确批准阶段 0 的一次本地观测及静态依赖盘点,不授予批量修改权限。使用 inline,不派 implement/check 代理。
- 已有 24 文件字面交集中,22 文件合并 login/refresh 无条件返回成功,login-portal-ux 另有混合场景;共 23 文件含矛盾路径。local-candidate-witnesses 的有状态处理器不属于此类。24 不是传递依赖扫描所得的全仓上限。
- 原完整诊断中,这 23 文件对应 133 条结果(127 失败、6 通过),文件外另有 80 条失败。这是观测及文件交集,不是逐条确证的同根因清单。来源:artifacts/qa-flow/release-gate-integrity/auth-refresh-readonly-20260907-01/inventory.json。
- 代表性合同用例在单条 1 worker 和全套 4 workers 中都卡登录前置。单条 trace 的首文档和 refresh 请求均未带会话 cookie,后续才出现带标记的 mock fulfill 和 /me 请求。“谁在 /login 前种下 cookie”不能作为已成立的前提;原始证据与限制见 design.md §1。
- AuthContext.tsx:370 的条件还取决于 navigator.onLine;无会话提示不意味着必定跳过 refresh。旧 trace 没有记录该浏览器值或证明首次 refresh 的调用入口,仍不得倒填历史条件。
- 阶段 0 新现场已记录:首次 bootstrap 的 sessionHint=false、online=false、offlineSnapshotAvailable=false,经 bootstrap-primary-refresh → runRefresh 发出一次网络 refresh;无 login 请求,原合同用例仍在登录表单前置 timedOut。仅实验副本追加观测,未改真实源码或判定条件。详见 design.md §3.1。
- 静态盘点覆盖当前 65 个非 manual spec 及本地 import/export 依赖:30 文件存在成功 refresh 与表单路径共存(原 23 + 新 7),另 1 文件为访客提示页路径。原“矛盾”标签只表示条件性风险候选,不能直接当成内在缺陷或迁移必要性。另 2 个认证恢复建模缺项继续分开列示。
- 已批准的在线对照已完成:相同源码、mock 和 114 份观测构建文件,实际首个 bootstrap 的 online=true、hint=false、无快照,进入 guest-ready;登录前 refresh=0、login=1,登录及合同前置断言通过。整条用例仍为 1 timedOut,停在旧“更多”按钮,续约/归档未到达。执行端撤回默认迁移 31 文件的建议,详见 design.md §6.1。
## Scope
阶段 0 已独立复核批准;后续单条在线对照已执行,当前交审。阶段 1 的原 31+2 清单及接口保留为历史提案,暂缓且不再作为默认下一步。环境启动合同或夹具改动、后续运行矩阵均须重新明确范围并批准;本次在线授权已用完,不追加运行。
## Requirements
以下 R1–R5 为原阶段 1 设计约束,不表示已经确诊 31 文件必须修改。在线对照后,修复对象及范围仍待决定;只有本轮取证与边界约束生效,不启动迁移或环境门禁实现。
### R1 — 显式认证状态
- 驱动表单的场景以未认证状态开始;显式登录前 refresh 返回未认证,不能凭固定成功 mock 恢复身份。
- 登录成功后允许 refresh 恢复会话,注销后恢复未认证;不能把 refresh 永久设为 401。
- 权限投影、视觉及业务流可显式从已认证状态开始,但不再重复驱动表单;保留导航、刷新、/me 和原业务断言。
- 正确访客、有状态和真实后端登录场景不得被无差别替换。参照 client/tests/e2e/local-candidate-witnesses.spec.ts:199、:241、:252、:260。
### R2 — 触发条件先于修复
- 阶段 0 仅在获批本地合成环境观察会话标记存在性、navigator.onLine、离线快照可用性及首次 refresh 调用入口,不采集 credential、身份或存储原文。
- 已有 trace、新实验和源码推论分别标注;不把新观测倒写成旧现场,不预设 cookie 污染或离线环境问题。
- 观测不改变产品认证语义、不改写网络状态凑通过,也不具有发布资格。阶段 0 运行授权独立于任务创建授权。
### R3 — 场景级范围
- 在已有 24 文件清单基础上追踪共享 helper、TEST_IDS、route 注册和调用路径,登记场景起点、refresh 行为、登录方式及证据锚点。
- 以场景而非整文件决定迁移。阶段 1 的精确写入清单须单独获批;新发现同类文件不自动进入写入范围。
- 文件外 80 条本轮不继续归因。已知认证前置解释清楚后,再单列同类候选、独立问题、前置阻断和证据不足;不将它们整体纳入修复承诺。
### R4 — 保留验证意图
- 不放宽业务、权限、导航和会话恢复断言;不增加 retry、缩减集合、移入 manual、开启 live 或降低全局并发来制造通过。
- 零重试保持;单 worker 定向证据与默认 4 workers 全套诊断分列。重复运行事先固定目的和次数,不重试至绿。
- 认证前置恢复后暴露的业务失败只报告。合同“更多”漂移、详情入口及续约/归档断言仍由门禁任务阶段 B 处理,不自动视为通过。
### R5 — 完成不等于发布
- 本任务交付认证夹具合同、获批迁移、针对性验证和剩余失败分类,不要求无关红项清零。
- 不修改发布入口、R1/R2/R3 机制、skip 或回执消费。R1 启用仍需红项处置、普通 full-local 全绿实测成本、可复现性证据及单独批准。
## Acceptance Criteria
- [x] AC1(R2):阶段 0 给出来源绑定的条件观测或明确缺项;区分请求 cookie、mock 调用和未观测的浏览器状态,不预设 cookie 污染。阶段 0 已获用户独立复核批准;后续在线对照另行交审。
- [ ] AC2(R3):形成包括间接 helper 的场景清单及冻结写入范围;保留原 24/23 口径与新增发现差异,不覆盖旧回执。静态清单已复核,31+2 迁移暂缓,不能把候选清单当成最终写入授权。
- [ ] AC3(R1):获批夹具覆盖登录前未认证、登录后恢复、注销后未认证及显式已认证起点;正常访客和真实后端场景不被误迁移。
- [ ] AC4(R4):命令及 trace 绑定候选和实际用例集合,零重试、原集合及业务断言不变;认证前置到达下一阶段有可核对证据,不冒称整条用例通过。
- [ ] AC5(R3/R4):后续诊断保留剩余失败分类、失败位置及未覆盖状态;合同后续断言、场景 8 和其他问题没有夹带修复。
- [ ] AC6(R5):交审 diff 与获批范围一致;主会话验证不代替独立复核,完成不授予 R1 启用、提交、push、归档或部署资格。
## Out Of Scope
产品认证或权限逻辑、经理及付款主体数据、场景 8、合同业务入口修复、发布门禁机制及启用、真实账号和生产数据、live/公网测试、完整绿路径成本测量及未经批准的其他浏览器红项修复。
## Test Plan
阶段 0 实际只执行一条原合同用例,1 worker、零重试、trace=on,结果 timedOut;两次准备/收集错误均执行 0 条浏览器用例,分列保留。静态盘点及源码/资源/文档验证不增加浏览器运行。后续实施拟验证四类认证状态及原业务断言,针对性矩阵与全套诊断须各自批准;不把未运行或中断结果补成通过。
后续另批的在线对照也只执行一次相同原用例,1 worker、零重试、trace=on;空白页原生 online/context 隔离检查通过,实际用例 trace 再次确认 online=true。认证前置通过与下游“更多”超时分别计入回执,不记为整条 PASS,不用于普通全绿成本。
## Approval And Next Decision
- 已批准:独立立项方向、认证状态合同及范围边界。
- 创建轮已做:planning task、已有 trace 核对和方案;未运行实验。
- 最新批准(2026-09-07):用户批准 design.md §3 的阶段 0,已 task start;仅一次本地定向启动条件取证及静态盘点。阶段 1 的迁移清单和运行矩阵仍待后续批准。
- 上轮交审:阶段 0 结果及阶段 1 的 31+2 文件/接口提案;该提案随后暂缓,未实施。
- 后续批准(2026-09-07):阶段 0 已经用户独立复核批准;阶段 1 暂缓,仅批准一次确认原生 navigator.onLine=true 的本地合同定向实验,零重试、trace=on。当前状态仍为 in_progress,不是 planning;已有清单不构成迁移授权。
- 当前交审:在线对照及来源/资源回执。建议下一项先审环境启动前提与显式 offline 用例的边界,不继续原 31 文件默认迁移;这不是环境修复授权。场景 8、经理缺口和 R1 不变。
- 最新结论(2026-09-07):用户认可在线对照及撤回默认迁移。207 原浏览器清单整体撤销真实红项/工作量/R1 等决策资格,原件保留,不能倒写成逐条均已证明假失败。环境合同归 release-gate-integrity 的 R2/R6 与设计 §10,本 task 不重复定义实现合同;31+2 方案继续暂缓,阶段 B 仅保留原合同业务范围。当前仅规划交审,无实施或运行授权扩展。
@@ -0,0 +1,34 @@
{
"id": "e2e-auth-preconditions",
"name": "e2e-auth-preconditions",
"title": "E2E 认证前置统一",
"description": "阶段 0 及在线对照已获用户复核认可;31+2 迁移暂缓。207 原浏览器清单撤销决策资格且原件保留;环境合同规划归 release-gate-integrity R2/R6 与设计 §10,本任务保留取证,不实施或重跑。",
"status": "in_progress",
"dispatch_mode": "inline",
"dev_type": "frontend",
"scope": null,
"package": null,
"priority": "P1",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-09-07",
"completedAt": null,
"branch": "main",
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [
"client/tests/e2e/payment-reviewer-permissions.spec.ts",
"client/tests/e2e/local-candidate-witnesses.spec.ts",
"client/tests/e2e/login-portal-ux.spec.ts",
"client/tests/e2e/helpers/auth.ts",
"client/src/auth/AuthContext.tsx",
".trellis/tasks/09-06-release-gate-integrity/prd.md"
],
"notes": "最新处置:用户已复核认可在线对照,207 原浏览器清单撤销决策资格;环境合同仅在 release-gate-integrity R2/R6 与 §10 规划交审,31+2 暂缓,本轮不实现或运行。以下保留在线交审时的历史说明:2026-09-07 阶段 0 已获用户独立复核批准;保持 in_progress / inline,不是 planning。另批在线对照仅一次原合同单条,零重试、trace=on:首个 bootstrap online=true/hint=false/无快照,guest-ready,登录前 refresh=0/login=1;登录后的整页导航正常恢复会话。整条 1 timedOut 卡旧更多按钮,续约/归档未覆盖。独占命名空间内 veth 提供原生在线信号,无外连默认路由或 navigator 覆写;context 离线及探针 cookie 隔离检查通过。原 21 文件、1052 来源及 114 构建文件摘要一致,资源已按归属清理。30+1 是条件性候选,不是已确诊内在缺陷;31+2 方案暂缓,执行端撤回默认迁移建议,等待本次复核及后续范围批准。relatedFiles 不授予写入权限;场景 8、经理缺口、R1 不变。未委派、提交、push、归档、部署或接触生产。",
"meta": {}
}