Просмотр исходного кода

docs(qa): audit three-end launch readiness with local evidence

彭峰 3 часов назад
Родитель
Сommit
6d4e042e77

+ 150 - 0
docs/qa/launch-audit-2026-10-13/README.md

@@ -0,0 +1,150 @@
+# 小树三端上线前审查(2026-10-13)
+
+## 结论与范围
+
+**当前版本不建议直接作为已验收版本发布。** 主体教学功能已经形成闭环,但账号安全、移动端登录、密码规则、工资结算入口与现有同步方案之间仍有明确问题。先处理 A01—A06,再进行发布验收。
+
+依用户要求,本次只审查当前代码、本地运行与本机已有运行记录;未部署、未操作生产会员/工资/预约。学生端和老师端共用移动应用,按两个身份分别检查。代码基线为 `9224f7a` 加工作区原有两项未提交修改:`learning-page.component.ts` 和 `home-learning-modules.integration.spec.ts`;报告不是仅对 Git HEAD 的审查。
+
+证据分为:**本地复现**、**源码确认**、**本机历史报告**、**发布前待验证**。浏览器使用隔离模拟账号与拦截接口;模拟页面通过不等于真实线上接口通过。审查覆盖关键业务和横切能力,未声称逐一人工点击全部历史迁移路由。
+
+## 优先处理的问题
+
+### A01 · P1 · 旧系统密码可以覆盖新系统已修改密码
+
+- 证据:`scripts/deploy-admin-functions.mjs` 的 `user_login_passwd`(约 2990 行)在新密码登录失败后调用 `migrateLegacyLogin`(2881 行)。后者只要旧系统验证成功,就执行 `target.setPassword(password)`,并清除 `passwordResetRequired`,没有“仅首次迁移”条件。
+- 本地复现:隔离内存中的账号已有新密码,旧密码验证成功后,新密码被替换,强制改密标记被清除;没有操作真实账号。见 `auth-reproduction.json`。
+- 影响:新系统改密不能可靠切断旧密码;学生、教师与复用该入口的后台账号均需核查。前提是旧系统仍接受该账号旧密码,不代表任何人无需密码即可登录。
+- 修复:只允许尚未完成密码迁移的账号进入旧验证;在一次性迁移、主动改密或管理员重置后永久关闭旧密码回退。显式维护迁移状态,并撤销应失效会话。
+- 验收:首次迁移成功;迁移后新密码成功、旧密码失败;网络故障不能触发改密;停用、跨帐套与重置密码场景均拒绝不合规回退。
+
+### A02 · P1 · 移动端旧请求失败会清除新会话
+
+- 证据:`projects/xiaoshu-mobile/src/app/core/api.service.ts` 的 `callCloud` 只把“是否需登录”的布尔值交给错误处理;`handleError` 收到 401 后调用 `expireSession`,未核对失败请求所属 token。
+- 本地复现:A 会话发起请求 → 换成 B 会话 → A 的迟到 401 返回 → B 会话被清空,跳到 `wxauth?expired=1`。
+- 影响:学生、老师操作中可能突然退出并清理本地学习草稿。后台已有 token 比较和会话复核,本问题主要在共用移动端服务。
+- 修复:传递实际请求 token;仅处理与当前会话匹配的失效响应;区分网络故障与真实失效;复用后台已实现的复核策略。
+- 验收:迟到 401 不影响新登录;当前会话确实失效才退出;503、断网不退出;并发失效只跳转一次。
+
+### A03 · P1 · 后台接受的密码,移动端可能无法登录
+
+- 证据:后台创建/导入/重置密码支持 8—72 位;移动端 `login-page.component.ts` 的 `submitClassic` 限制 6—18 位。会员导入模板也明示 8—72 位。
+- 本地复现:20 位密码在移动端被客户端直接拒绝,请求数为 0。
+- 修复:统一三端与服务端密码规则,登录端不要用注册旧规则拒绝现有合法密码;调整提示与导入模板。
+- 验收:后台创建的 8、18、19、72 位合法密码均能在移动端按统一规则登录,超界输入有一致提示。
+
+### A04 · P1 · 临时密码强制修改流程没有在移动端闭环
+
+- 证据:后台重置写入 `passwordResetRequired=true`,移动端没有消费此标记;`pages/member/passwd` 被 `feature-policy.ts` 的表单策略阻止。
+- 本地页面:进入“修改登录密码”显示“暂未开放,请联系管理员处理”。
+- 影响:管理员交付的临时密码无法由学生/老师自主修改,也没有强制改密约束。与 A01 组合后风险更高。
+- 修复:服务端返回并执行强制改密状态;实现当前密码验证、修改、撤销旧会话与重新登录;允许访问必要的改密/退出/帮助页面。
+- 验收:临时账号先改密再使用;旧密码和旧会话失效;失败保留输入并可重试。
+
+### A05 · P1 · 工资批次仍被旧同步桥验收条件阻挡
+
+- 证据:`scripts/cloud/payroll-integrity.js` 的 `payrollSettlementSyncStatus` 仍以同步桥配置、manifest、检查点及两分钟延迟为条件;`payrollMutation` 对 create/refresh/review 强制检查 `settlementReady`。`directPayrollSyncStatus` 的直接接口凭据固定 `settlementReady:false`;`scripts/cloud/legacy-direct-control.js:87` 的完成报告同样固定为 false。
+- 本机最新完整同步报告:旧接口已完成、9 个数据集业务编号对账通过、失败 0,但 `settlementReady:false`。见 `sync-readonly.json`。这是已发生的历史报告,不是本次生产实时查询。
+- 影响:每天 23 点同步成功并不等于工资可以生成、刷新或审核;页面仍可能提示未配置持续同步。现有提示还写着“手动同步”“每日自动同步尚未验收”,与已安装任务容易混淆。
+- 修复:接入直接接口的**工资截止范围核验**:按结算月份核对预约、完课和复习凭据;分类处理已保留的新系统修改与未解决差异,保存对账版本和确认结果;基于这些证据开放结算。不能把 false 改成 true,也不能将所有 1,330 条保留差异一概当作错误或一概忽略。
+- 验收:真实缺失/未解决工资冲突仍拦截;已核验范围可生成;核验后源数据变更会使确认失效;复习奖励、跨月补结和已发放金额不重复。
+
+### A06 · P1 · 依赖存在已知安全公告,上线前需要分级处理
+
+- 官方 npm registry 的本次完整扫描:85 个受影响依赖条目,其中 critical 6、high 49、moderate 22、low 8。**这是依赖树条目数,含构建/测试工具和传递依赖,不是 85 个已经证实可利用的产品漏洞。** 默认 npmmirror 不支持 audit,本次已改用官方 registry;未执行 `audit fix --force`。
+- 直接依赖涉及 Angular 18、`xlsx`、`html2pdf.js`、`jspdf` 等;详见 `dependency-summary.json` 的安装版本及公告链接。
+- 后台会员、词库 Excel 导入确实调用 `xlsx.read`,应优先处理 SheetJS 解析链;PDF 导出库也进入实际功能。Angular 的 i18n、SSR 等公告各有触发条件,不能仅凭版本认定当前 SPA 都可利用。
+- 修复:先梳理浏览器可达依赖,升级/替换已知风险导入导出库,再安排 Angular 支持版本迁移;单独处理开发工具。每组升级做对应功能回归,不自动强升整树。
+- 验收:产物依赖风险有处理结果或有依据的不可达说明;Excel 导入、PDF 多页导出、路由、表单与移动端全部回归。
+- 一级来源:[Angular 官方公告](https://github.com/angular/angular/security/advisories/GHSA-g93w-mfhg-p222)、[html2pdf 官方公告](https://github.com/eKoopmans/html2pdf.js/security/advisories/GHSA-w8x4-x68c-m6fc)、[SheetJS 公告](https://github.com/advisories/GHSA-4r6h-8v6p-xvw6)。扫描结果只代表审查时点。
+
+## 使用体验、稳定性与维护问题
+
+| 编号 | 级别 | 问题与证据 | 建议与验收 |
+|---|---|---|---|
+| B01 | P2 | 学生首页 `home-page.component.html:97–101` 将缺失统计/余额回退为 0,统计失败只给教师设置错误状态;模拟 503 时学生仍看到一排 0。部分余额来自登录快照,未标出更新时间。 | 未取得值显示“—/暂不可用”,已有值标为缓存并可刷新;真实 0 才显示 0。模拟失败、充值后刷新、跨端变化分别验收。 |
+| B02 | P2 | 学生/老师课表失败时同时显示“暂无排课”和错误提示;后台排课错误状态也仍显示 0 与空泳道。见 `local-page-probes.json`。 | 首次失败、真实为空、保留缓存三种状态分开;未成功查询前不下“没有课”的结论。 |
+| B03 | P2 | 隐私注销入口 `pages/member/acc_destory` 被功能策略阻断;本地显示暂未开放。登录协议走站内文章,用户中心另有旧域名文档来源。 | 提供可完成的申请/管理员处理入口,明确处理状态;核对并统一协议内容及版本。本项是功能与内容一致性问题,不代替法律审查。 |
+| B04 | P2 | `CloudFunctionsService` 的后台 admin/operations/app 请求没有统一超时;移动端也只有特定 action 设置 20 秒超时。 | 给读写分别设有限等待与取消/核对路径;写入超时显示“结果待核对”,重试必须沿用幂等键,避免误导重复操作。用悬挂响应测试释放 loading。 |
+| B05 | P2 | `operations-vocabulary.component.ts:138–141` 在解析 Excel 前没有文件大小/行数限制,直接主线程读取全表;会员导入已有 5 MB/200 行限制,可作参考。 | 解析前限制大小,限制解压/行数并考虑 Worker;大文件应快速拒绝且页面仍可操作。 |
+| B06 | P2 | `webview-page.component.ts` 接受任意 http/https 参数并绕过资源 URL 安全检查,iframe 允许脚本、同源、弹窗及下载。没有主机允许名单。未复现跨域脚本读取,也不能据此声称存在完整 XSS。 | 收敛到 HTTPS 允许域名,按来源最小化 sandbox 权限;明确外部站点提示;测试非法协议、恶意主机、嵌套编码与新窗口。 |
+| B07 | P2 | 移动端 SessionService 没有后台已有的跨标签 storage 监听;退出仅清理本地会话,未见此路径调用服务端撤销会话。 | 明确多端退出策略;同浏览器标签及时更新身份,退出后旧 token 按产品约定失效;不能只隐藏界面。当前为源码判断,未做生产 token 重放。 |
+| B08 | P2 | 全量前端测试有 7 个失败项,旧流程/日期/缓存命名的测试未同步维护。 | 修复测试契约并稳定日期时钟;建立完整构建与测试门禁。不要删掉断言或修改业务去迎合旧测试。详见下一节。 |
+| B09 | P2 | 夜间任务具备失败重试和私有报告,但执行器未见主动通知实现;只有日志不足以让人及时知道凭据过期或持续失败。 | 给最终失败、连续未完成、冲突显著增长设去重告警;成功保留安静。验证人为失败可被发现、恢复后告警消除。 |
+| B10 | P3 | 前端未找到全局异常采集与关联请求编号链路;现有多处错误只弹短暂 toast。 | 增加脱敏错误记录、版本号、请求编号和可重试提示,避免上报密码/token/学员完整信息。仓库未发现不等于部署平台没有监控。 |
+| B11 | P3 | 移动登录文本输入主要用 placeholder,缺少持久 label;部分后台图标按钮/自制 drawer 缺少可访问名称与焦点管理。 | 登录表单补 label,主要触控目标约 44px;对话框聚焦、Escape、关闭后回焦;键盘与读屏抽验。这里是源码抽查,未做全站无障碍认证。 |
+
+## 已执行的验证与通过部分
+
+| 检查 | 本次结果 | 证明范围 |
+|---|---|---|
+| `npm run build` | 移动端、后台均成功 | 当前工作区可生成生产包;已有样式预算/CommonJS 警告未阻断构建 |
+| 移动端全量测试 | 197 项,192 成功、5 失败 | 未全绿 |
+| 后台全量测试 | 256 项,254 成功、2 失败 | 未全绿;移动端失败使组合命令中断,所以另行运行后台 |
+| `node --test --test-concurrency=2 scripts/tests/*.test.mjs` | 163 成功、0 失败 | 本地 SQL/网关/业务策略测试,不是生产数据库测试 |
+| 云 action 清单核对 | requested 116、implemented 99、blocked 21、missing 0 | 没有未分类 action;三组非互斥,不能相加成覆盖率,也不代表 21 个关闭功能已实现 |
+| 报告与预约详情浏览器 | 375/812 宽度及横屏编辑;保存一次、无页面异常、无横向溢出 | 模拟接口;取消、报告跳转编辑、保存路径通过 |
+| 三端故障状态抽查 | 375/1366 宽度;首页、课表、用户中心、后台工作台/排课/工资;无页面异常/文档横向溢出 | 模拟 503,与健康真实数据 UI 分开记录 |
+| 未登录路由 | 移动预约跳登录、后台工资跳登录 | 前端 guard;服务端权限另由源码/SQL测试支撑 |
+| 身份与越权 | 本地测试覆盖普通会员无后台权限、运营不能提权、跨帐套拒绝、停用撤销、版本冲突 | 生产角色/ACL 是否与代码一致仍需发布者确认 |
+| 业务事务 | 结课幂等、写入失败回滚、课时预留、重复退款、并发抢最后课时、报告文字修改、奖励防重复均有通过用例 | PGlite/隔离测试,不能替代生产恢复演练 |
+
+### 7 个失败项的归因
+
+1. 移动端 3 个学习分组/跳过拼写用例使用旧缓存键 `...901.`;当前代码按 `...901.unbound.` 或 bindingId 隔离。**只在临时测试副本更新前缀后,3 项全通过**,没有修改业务逻辑。
+2. 移动端 2 个预约课程/单元用例仍模拟旧 `content_list`,没有提供新的 `app_lesson_courses` 契约,其中还断言“不应有课程选择框”,与用户要求显示学员全部已开通课程相反。源码证明测试过期;这两项尚未重写完整测试并复跑,不把它们认定为线上课程加载故障,也不声称已修好。
+3. 后台 2 个陪练月份用例硬编码 2026-09/9月;当前默认月份为 10 月。临时副本改为 10 月后该文件 4 项全通过。最终应使用稳定时钟/依赖注入而不是继续改硬编码月份。组件模块级 `CURRENT_MONTH` 还应在长期开页跨月场景重新计算。
+
+临时副本均已删除,原有未提交代码保持原样。全量基线仍为 7 个失败,不能用诊断副本成功代替门禁通过。
+
+## 分端覆盖与剩余验收
+
+| 范围 | 审查内容 | 结论 / 尚缺证据 |
+|---|---|---|
+| 学生 | 登录、个人资料入口、课程/排课、学习分组、抗遗忘、结课报告、评价、密码/注销 | 核心本地测试可用;A01—A04、B01/B03/B07 待修。手机真实音频、断网回前台与跨设备会话待验收。 |
+| 老师 | 学员绑定、预约当天开课、全部已开通词库选择、多词库学习、结课反馈与修改、复习奖励 | 本地权限/课时/报告事务用例通过;A02/A04 影响会话。长课中断、锁屏恢复和真实媒体待验收。 |
+| 后台 | 会员导入/开课、余额划拨、排课/取消、老师可用时间、工资、报表导出、词库、管理员角色 | 本地业务事务保护较完整;A01/A03/A05/A06 是重点。大表导入、真实打印文件与生产角色映射需后续验证。 |
+| 同步与数据 | 旧接口执行器、依赖顺序、原子主附表、差异保留、断点、业务编号对账、删除标记与阅读身份冲突 | 直接接口本地测试通过;已有完成报告。保留差异不是失败,但需按工资/课时影响分类处理。未重新同步真实数据。 |
+| 性能 | 生产初始包、懒加载、PDF/Excel按需导入、故障渲染 | 移动初始约 410.17 kB/估算传输112.89 kB;后台367.21/102.76 kB。仅体积,未得出端到端速度结论。需真数据/网络条件下测可交互 p75。 |
+| 安全与隐私 | 密码迁移、会话、权限、导入库、外链、隐私入口、日志与凭据 | 已确认 A01—A04;其余区分源码风险与实际利用。未输出执行凭据,审查文件仅模拟账号和汇总。 |
+| 发布与维护 | 构建、测试、依赖、打包、同步任务、回滚和备份证据 | 后台有 release prepare/verify;未找到对应移动发布打包和仓库CI流程。数据库备份/恢复、TLS/CORS/安全头属于发布环境待核验,不认定线上缺失。 |
+
+## 本机 23 点同步现状(只读)
+
+- LaunchAgent `cn.xiaoshu.legacy-direct-sync-nightly` 已加载;北京时间 23:00,RunAtLoad 开启,失败后 KeepAlive 重试、ThrottleInterval 300 秒。
+- 运行器及两个依赖文件与当前仓库内容一致;凭据文件权限 0600;最近记录 exit 0、runs 2。白天 state=not running 是正常的,不表示任务未安装。
+- 2026-10-12 完整报告:新增业务记录 53、更新 181、保留冲突 1,330、失败 0,9 类编号对账完成;次日 00:22 的 `already_completed` 是防重复,不是新一轮同步。
+- 本次读取的另一个 `ready` 是就绪检查,不是已同步。不能把它当当天同步成功。
+- Mac 作为运行主机已获用户确认。仍需开机并登录该用户;关机、用户退出等条件下不能保证 23 点执行。安装记录/一次完成不等于以后每晚定时触发已验证,下一次自然触发应核对 businessDate 和 finish 回执。
+- `sync-worker:nightly` 已走直接接口;package.json 中 `sync-worker:once/start` 仍指旧桥 worker,容易误用,应明确标记历史用途或收敛命令。
+
+## 修复顺序与发布交接
+
+1. **先修账号链路**:A01—A04 同一组处理,覆盖旧密码关闭、新密码统一、首次改密、迟到响应与跨标签登录。用隔离账号测试,保留迁移兼容但禁止覆盖已更新密码。
+2. **闭合工资核验**:A05,使用现有直接接口成功凭据与月份截止核验,不回到同步桥,不绕过差异与财务事务。
+3. **处理依赖与可靠性**:A06、B01—B09;先真实可达导入库、错误零值、超时、告警,再扩展到统一体验。
+4. **稳定验收门禁**:更新过期测试并使全量通过;固定时间、明确 fixture 契约;在隔离数据上完成学生—老师—后台全链路。
+5. **用户自行发布前核对**:前端 commit/hash、后端版本、SQL迁移版本一致;配置学生/老师端及后台域名、HTTPS、API CORS、SPA回退、缓存策略;备份并实际恢复一次;准备前端回滚包与后端兼容回滚方案。
+6. **发布后验收由发布者执行**:用隔离学生/老师/管理员验证登录→开课→排课→开始→多词库学习→结课→报告修改→学生评价→工资核对;再验 23 点自然触发、断网、iOS Safari/微信/Android、音频播放、PDF下载。不得用真实客户记录制造验收扣费或发薪。
+
+### 可用的发布验收清单
+
+- [ ] A01—A06 已关闭或有明确范围的风险处置结论。
+- [ ] 两端生产构建、前端全量、服务端本地测试全通过。
+- [ ] 真实环境的角色/帐套、禁用用户、错误身份、重复提交、并发版本冲突已验证。
+- [ ] 工资核验与旧接口同步凭据连通,保留差异已按影响处理。
+- [ ] 发布产物与源码版本可追溯,移动端与后台不会误用旧 dist 目录。
+- [ ] 备份、恢复、回滚、安全头、上传限制、登录限流和告警由发布者确认。
+- [ ] 真机核心教学流程与下一次 23 点自然触发通过。
+
+## 证据与复现
+
+- `verification-summary.json`:构建、测试、清单、报告UI摘要。
+- `auth-reproduction.json`:三项登录问题及注销入口的隔离复现。
+- `dependency-summary.json`:官方 registry 扫描摘要,含直接依赖公告。
+- `local-page-probes.json`:各身份/视口的模拟故障页面文本与溢出/错误检查。
+- `sync-readonly.json`:任务配置、脚本一致性与已有对账报告的脱敏汇总。
+- 两张 `*-home-unavailable.png`:本地故障态截图(模拟账号)。
+- `reproduce-auth.cjs`、`probe-local-pages.cjs`:从仓库根目录运行,需 Chrome 和 Playwright;可通过 `PLAYWRIGHT_MODULE` 指向本机模块位置。它们只针对本地4200/4300,不应改成生产地址运行。
+
+本次交付为审查报告和本地证据;未修改上述业务缺陷,未部署。原有测试未全通过,因此不把报告描述为“上线验收通过”。

+ 24 - 0
docs/qa/launch-audit-2026-10-13/auth-reproduction.json

@@ -0,0 +1,24 @@
+{
+  "legacyPasswordFallback": {
+    "resetCalls": 1,
+    "saves": 1,
+    "newPasswordOverwritten": true,
+    "resetRequiredCleared": true,
+    "mode": "local function with isolated in-memory adapters; no real account touched"
+  },
+  "longPassword": {
+    "requestCount": 0,
+    "message": "登录系统\n小树陪练\n\n登录后继续你的学习计划\n\n账号密码\n短信登录(维护中)\n我已阅读并同意 服务协议 和 隐私政策\n登录\n没有账号,立即注册\n密码长度应为6至18位"
+  },
+  "staleSession": {
+    "stored": {
+      "token": "",
+      "user": null,
+      "student": null
+    },
+    "url": "http://127.0.0.1:4200/pages/member/wxauth?returnUrl=%2Fpages%2Fhome%2Findex&expired=1"
+  },
+  "accountDeletion": {
+    "body": "隐私注销通道\n暂未开放\n\n此功能暂未开放,请联系管理员处理。\n\n返回首页意见反馈"
+  }
+}

+ 283 - 0
docs/qa/launch-audit-2026-10-13/dependency-summary.json

@@ -0,0 +1,283 @@
+{
+  "source": "npm audit --registry=https://registry.npmjs.org --json",
+  "scope": "full dependency tree including build/test tooling; not an exploitability count",
+  "metadata": {
+    "vulnerabilities": {
+      "info": 0,
+      "low": 8,
+      "moderate": 22,
+      "high": 49,
+      "critical": 6,
+      "total": 85
+    },
+    "dependencies": {
+      "prod": 39,
+      "dev": 1009,
+      "optional": 105,
+      "peer": 0,
+      "peerOptional": 0,
+      "total": 1058
+    }
+  },
+  "direct": [
+    {
+      "name": "@angular-devkit/build-angular",
+      "version": "18.2.21",
+      "severity": "high",
+      "advisories": []
+    },
+    {
+      "name": "@angular/animations",
+      "version": "18.2.14",
+      "severity": "moderate",
+      "advisories": []
+    },
+    {
+      "name": "@angular/cli",
+      "version": "18.2.21",
+      "severity": "high",
+      "advisories": []
+    },
+    {
+      "name": "@angular/common",
+      "version": "18.2.14",
+      "severity": "high",
+      "advisories": [
+        {
+          "title": "Angular is Vulnerable to XSRF Token Leakage via Protocol-Relative URLs in Angular HTTP Client",
+          "url": "https://github.com/advisories/GHSA-58c5-g7wp-6w37"
+        },
+        {
+          "title": "@angular/common: Denial of Service (DoS) via OOM in Date Formatting (formatDate)",
+          "url": "https://github.com/advisories/GHSA-48r7-hpm6-gfxm"
+        },
+        {
+          "title": "@angular/common: Weak 32-Bit Cache Key Hashing in `HttpTransferCache` Leading to Cross-Request Data Leakage and State Poisoning",
+          "url": "https://github.com/advisories/GHSA-39pv-4j6c-2g6v"
+        },
+        {
+          "title": "@angular/common: Denial of Service (DoS) via OOM in Number Formatting (digitsInfo)",
+          "url": "https://github.com/advisories/GHSA-p3vc-36g9-x9gr"
+        },
+        {
+          "title": "@angular/common: Information Leak via Default Caching of Credentialed Requests in HttpTransferCache",
+          "url": "https://github.com/advisories/GHSA-q6f4-qqrg-jv6x"
+        },
+        {
+          "title": "Angular: Cache-Key Ambiguity in HttpTransferCache Leading to Cross-Request Response Reuse and State Poisoning",
+          "url": "https://github.com/advisories/GHSA-jhpw-976m-542j"
+        },
+        {
+          "title": "Angular: Information Leak via `HttpTransferCache` Bypass When Using `withRequestsMadeViaParent`",
+          "url": "https://github.com/advisories/GHSA-p297-fm68-3q8c"
+        }
+      ]
+    },
+    {
+      "name": "@angular/compiler",
+      "version": "18.2.14",
+      "severity": "high",
+      "advisories": [
+        {
+          "title": "Angular vulnerable to XSS in i18n attribute bindings",
+          "url": "https://github.com/advisories/GHSA-g93w-mfhg-p222"
+        },
+        {
+          "title": "Angular has XSS Vulnerability via Unsanitized SVG Script Attributes",
+          "url": "https://github.com/advisories/GHSA-jrmj-c5cx-3cw6"
+        },
+        {
+          "title": "Angular Stored XSS Vulnerability via SVG Animation, SVG URL and MathML Attributes",
+          "url": "https://github.com/advisories/GHSA-v4hv-rgfq-gp49"
+        },
+        {
+          "title": "@angular/compiler: Two-Way Property Binding Sanitization Bypass (XSS)",
+          "url": "https://github.com/advisories/GHSA-58w9-8g37-x9v5"
+        },
+        {
+          "title": "Angular: Template and Attribute Namespace Sanitization Bypass (XSS)",
+          "url": "https://github.com/advisories/GHSA-f3m7-gqxr-g87x"
+        },
+        {
+          "title": "Angular i18n: Cross-Site Scripting (XSS) via event-handler attributes",
+          "url": "https://github.com/advisories/GHSA-jj27-h5hq-8x99"
+        },
+        {
+          "title": "Angular: Sanitization bypass via directive host bindings on concrete host elements in @angular/core and @angular/compiler",
+          "url": "https://github.com/advisories/GHSA-hh8m-fm6v-7cvg"
+        }
+      ]
+    },
+    {
+      "name": "@angular/compiler-cli",
+      "version": "18.2.14",
+      "severity": "moderate",
+      "advisories": []
+    },
+    {
+      "name": "@angular/core",
+      "version": "18.2.14",
+      "severity": "high",
+      "advisories": [
+        {
+          "title": "Angular i18n vulnerable to Cross-Site Scripting",
+          "url": "https://github.com/advisories/GHSA-prjf-86w9-mfqv"
+        },
+        {
+          "title": "Angular vulnerable to XSS in i18n attribute bindings",
+          "url": "https://github.com/advisories/GHSA-g93w-mfhg-p222"
+        },
+        {
+          "title": "Angular has XSS Vulnerability via Unsanitized SVG Script Attributes",
+          "url": "https://github.com/advisories/GHSA-jrmj-c5cx-3cw6"
+        },
+        {
+          "title": "Angular: Template and Attribute Namespace Sanitization Bypass (XSS)",
+          "url": "https://github.com/advisories/GHSA-f3m7-gqxr-g87x"
+        },
+        {
+          "title": "@angular/core: Angular Template and Dynamic Component Namespace Bypass leading to Cross-Site Scripting (XSS)",
+          "url": "https://github.com/advisories/GHSA-692r-grfm-v8x7"
+        },
+        {
+          "title": "Angular Client Hydration DOM Clobbering & Response-Cache Poisoning",
+          "url": "https://github.com/advisories/GHSA-rgjc-h3x7-9mwg"
+        },
+        {
+          "title": "Angular i18n: Cross-Site Scripting (XSS) via event-handler attributes",
+          "url": "https://github.com/advisories/GHSA-jj27-h5hq-8x99"
+        },
+        {
+          "title": "Angular: Sanitization bypass via directive host bindings on concrete host elements in @angular/core and @angular/compiler",
+          "url": "https://github.com/advisories/GHSA-hh8m-fm6v-7cvg"
+        }
+      ]
+    },
+    {
+      "name": "@angular/forms",
+      "version": "18.2.14",
+      "severity": "moderate",
+      "advisories": []
+    },
+    {
+      "name": "@angular/platform-browser",
+      "version": "18.2.14",
+      "severity": "moderate",
+      "advisories": []
+    },
+    {
+      "name": "@angular/platform-browser-dynamic",
+      "version": "18.2.14",
+      "severity": "moderate",
+      "advisories": []
+    },
+    {
+      "name": "@angular/router",
+      "version": "18.2.14",
+      "severity": "high",
+      "advisories": [
+        {
+          "title": "Angular Server-Side Rendering (SSR): Denial of Service via Numeric URL Matrix Parameters",
+          "url": "https://github.com/advisories/GHSA-ff3f-86qr-9cv3"
+        }
+      ]
+    },
+    {
+      "name": "html2pdf.js",
+      "version": "0.10.3",
+      "severity": "critical",
+      "advisories": [
+        {
+          "title": "html2pdf.js contains a cross-site scripting vulnerability",
+          "url": "https://github.com/advisories/GHSA-w8x4-x68c-m6fc"
+        }
+      ]
+    },
+    {
+      "name": "jspdf",
+      "version": "3.0.4",
+      "severity": "critical",
+      "advisories": [
+        {
+          "title": "jsPDF has Local File Inclusion/Path Traversal vulnerability",
+          "url": "https://github.com/advisories/GHSA-f8cm-6447-x5h2"
+        },
+        {
+          "title": "jsPDF has PDF Injection in AcroFormChoiceField that allows Arbitrary JavaScript Execution",
+          "url": "https://github.com/advisories/GHSA-pqxr-3g65-p328"
+        },
+        {
+          "title": "jsPDF Vulnerable to Denial of Service (DoS) via Unvalidated BMP Dimensions in BMPDecoder",
+          "url": "https://github.com/advisories/GHSA-95fx-jjr5-f39c"
+        },
+        {
+          "title": "jsPDF Vulnerable to Stored XMP Metadata Injection (Spoofing & Integrity Violation)",
+          "url": "https://github.com/advisories/GHSA-vm32-vv63-w422"
+        },
+        {
+          "title": "jsPDF has Shared State Race Condition in addJS Plugin",
+          "url": "https://github.com/advisories/GHSA-cjw8-79x6-5cj4"
+        },
+        {
+          "title": "jsPDF has a PDF Object Injection via Unsanitized Input in addJS Method",
+          "url": "https://github.com/advisories/GHSA-9vjf-qc39-jprp"
+        },
+        {
+          "title": "jsPDF Affected by Client-Side/Server-Side Denial of Service via Malicious GIF Dimensions",
+          "url": "https://github.com/advisories/GHSA-67pg-wm7f-q7fj"
+        },
+        {
+          "title": "jsPDF has a PDF Injection in AcroForm module allows Arbitrary JavaScript Execution (RadioButton.createOption and \"AS\" property)",
+          "url": "https://github.com/advisories/GHSA-p5xg-68wr-hm3m"
+        },
+        {
+          "title": "jsPDF has a PDF Object Injection via FreeText color",
+          "url": "https://github.com/advisories/GHSA-7x6v-j9x4-qf24"
+        },
+        {
+          "title": "jsPDF has HTML Injection in New Window paths",
+          "url": "https://github.com/advisories/GHSA-wfv2-pwc8-crg5"
+        }
+      ]
+    },
+    {
+      "name": "karma",
+      "version": "6.4.4",
+      "severity": "high",
+      "advisories": []
+    },
+    {
+      "name": "karma-jasmine",
+      "version": "5.1.0",
+      "severity": "high",
+      "advisories": []
+    },
+    {
+      "name": "karma-jasmine-html-reporter",
+      "version": "2.1.0",
+      "severity": "high",
+      "advisories": []
+    },
+    {
+      "name": "lucide-angular",
+      "version": "0.468.0",
+      "severity": "moderate",
+      "advisories": []
+    },
+    {
+      "name": "xlsx",
+      "version": "0.18.5",
+      "severity": "high",
+      "advisories": [
+        {
+          "title": "Prototype Pollution in sheetJS",
+          "url": "https://github.com/advisories/GHSA-4r6h-8v6p-xvw6"
+        },
+        {
+          "title": "SheetJS Regular Expression Denial of Service (ReDoS)",
+          "url": "https://github.com/advisories/GHSA-5pgg-2g8v-p4x9"
+        }
+      ]
+    }
+  ]
+}

+ 194 - 0
docs/qa/launch-audit-2026-10-13/local-page-probes.json

@@ -0,0 +1,194 @@
+[
+  {
+    "role": "student",
+    "width": 375,
+    "route": "/pages/home/index",
+    "url": "/pages/home/index",
+    "overflow": false,
+    "body": "线上与线下,一对一陪练\n\n你好,本地审查账号\n\n21天抗遗忘计划\n抗遗忘计划暂时无法加载,点击重试\n坚持每天学习,记得更牢,成绩更好!\n查看计划\n0\n累积生词\n0\n剩余陪练时长\n0\n累计陪练时长\n0\n30分钟软件时长\n0\n60分钟软件时长\n科学抗遗忘\n今日复习任务\n专项训练\n分类强化词汇\n学习记录\n查看历次学习\n词汇测试\n检查掌握程度\n自主学习\n生词本\n选生词,开始学习\n勾选想学的单词,按自己的节奏练习\n进入生词本\n主页\n伴学\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "student",
+    "width": 375,
+    "route": "/pages/home/shop",
+    "url": "/pages/home/shop",
+    "overflow": false,
+    "body": "实时同步暂时不可用,已保留当前内容\n陪练排课\n0 / 0 节\n刷新课表\n清除筛选\n今天\n明天\n本周\n全部\n全部 0\n计划未完成 0\n已结束 0\n‹\n2026年10月\n›\n一\n二\n三\n四\n五\n六\n日\n28\n29\n30\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n1\n2\n3\n4\n5\n6\n7\n8\n\n2026-10-13 · 0 节课\n\n2026-10-13 暂无排课\n\n请求暂未完成,请检查网络后重试;已填写内容会保留。\n重新加载\n上一页\n共 0 条 · 1 / 1\n下一页\n主页\n伴学\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "student",
+    "width": 375,
+    "route": "/pages/home/ucenter",
+    "url": "/pages/home/ucenter",
+    "overflow": false,
+    "body": "本地审查账号\n个人信息设置\n实时同步暂时不可用\n账号数据暂时无法加载\n重新加载\n账号与学习权益\n学生账号 · 大众会员\n待审核\n会员有效期\n未设置到期日\n累计陪练\n0 小时\n已结束 0 次\n0\n剩余陪练时长\n0\n剩余30分钟软件\n0\n剩余60分钟软件\n查看\n课时余额与预留\n0\n可查陪练时长\n我的课程\n科学抗遗忘\n学习新词\n从“我的内容”选择已绑定课程和学习单元\n抗遗忘数据暂时无法加载\n重新加载\n我的服务\n专项训练\n生词本\n学习记录\n意见反馈\n当前版本:2.0.0-angular\n隐私政策\n服务协议\n隐私注销通道\n退出登录\n南昌小树陪练科技有限公司\n主页\n伴学\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "student",
+    "width": 375,
+    "route": "/pages/member/passwd",
+    "url": "/pages/member/passwd",
+    "overflow": false,
+    "body": "修改登录密码\n暂未开放\n\n此功能暂未开放,请联系管理员处理。\n\n返回首页意见反馈",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "teacher",
+    "width": 375,
+    "route": "/pages/home/index",
+    "url": "/pages/home/index",
+    "overflow": false,
+    "body": "线上与线下,一对一陪练\n\n你好,本地审查账号\n\n陪练师工作台\n今日业务概览\n今日业务数据暂时无法加载\n重新加载\n关联学员复习情况\n抗遗忘带练\n抗遗忘计划暂时无法加载,点击重试\n查看全部学员复习计划\n陪练排课\n查看今天与未来课程\n销课记录\n核对已完成课程\n学员记录\n按学员查看学习成果\n抗遗忘工资\n查看抗遗忘复习工资\n主页\n陪练\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "teacher",
+    "width": 375,
+    "route": "/pages/home/shop",
+    "url": "/pages/home/shop",
+    "overflow": false,
+    "body": "实时同步暂时不可用,已保留当前内容\n陪练排课\n0 / 0 节\n刷新课表\n清除筛选\n今天\n明天\n本周\n全部\n全部 0\n计划未完成 0\n已结束 0\n‹\n2026年10月\n›\n一\n二\n三\n四\n五\n六\n日\n28\n29\n30\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n1\n2\n3\n4\n5\n6\n7\n8\n\n2026-10-13 · 0 节课\n\n2026-10-13 暂无排课\n\n请求暂未完成,请检查网络后重试;已填写内容会保留。\n重新加载\n上一页\n共 0 条 · 1 / 1\n下一页\n主页\n陪练\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "admin",
+    "width": 375,
+    "route": "/admin/dashboard",
+    "url": "/admin/dashboard",
+    "overflow": false,
+    "body": "小树陪练\n运营管理后台\n\n运营总览\n\n工作台\n\n核心运营\n\n会员管理\n门店管理\n排课中心\n学习报表\n陪练老师\n工资结算\n\n内容运营\n\n词库管理\n专项训练\n\n账号与安全\n\n运营账号管理\n\n个人设置\n\n修改密码\n退出登录\n工作台\nL\n\nOPERATIONS OVERVIEW\n\n运营工作台\n刷新数据\n暂时无法载入运营工作台\n本地审查:模拟服务暂不可用\n重新加载",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "admin",
+    "width": 375,
+    "route": "/admin/schedule",
+    "url": "/admin/schedule",
+    "overflow": false,
+    "body": "小树陪练\n运营管理后台\n\n运营总览\n\n工作台\n\n核心运营\n\n会员管理\n门店管理\n排课中心\n学习报表\n陪练老师\n工资结算\n\n内容运营\n\n词库管理\n专项训练\n\n账号与安全\n\n运营账号管理\n\n个人设置\n\n修改密码\n退出登录\n返回\n排课中心\nL\n\nSCHEDULING CENTER\n\n排课中心\n按陪练或时间快速排课,并在提交前发现冲突\n刷新\n新增排课\n日\n周\n列表\n日期\n2026年10月13日\n陪练筛选\n全部陪练\n全部陪练\n搜索排课\n业务状态\n全部状态\n待开始\n上课中\n陪练已结束\n学员已确认\n已完课\n已归档\n逾期未完成\n状态待核对\n已取消\n待分配 / 账号异常\n全部状态\n查找\n重置\n0\n条筛选结果\n当前范围共 0 条\n\n本地审查:模拟服务暂不可用\n\n预约到完课流程\n排课、上课、结课与评价\n老师一次结课,学生随后评价\n1\n运营创建预约\n选择学员已绑定课程、时间和陪练\n状态 0\n2\n陪练开始上课\n预约当天均可开始,记录实际上课时间\n上课中\n3\n老师结课并生成报告\n课堂结果、反馈与课时扣减一次完成\n已结课\n4\n学生评价\n学生查看报告后评分;未评价不阻塞结课\n评价独立\n当前范围课程\n0\n2026-10-13 至 2026-10-13\n待分配\n0\n未分配老师时不能开始\n在岗陪练\n0\n未配置可用时间会提示\n历史排课总量\n0\n— 至 —\n数据库更新\n新排课程、教学状态以新库为准\n陪练排课泳道\n点击排课查看明细与可用操作\n待开始\n上课中\n陪练已结束\n学员已确认\n已完课\n已归档\n逾期未完成\n状态待核对\n已取消\n当前范围暂无已排课的陪练",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "admin",
+    "width": 375,
+    "route": "/admin/payroll",
+    "url": "/admin/payroll",
+    "overflow": false,
+    "body": "小树陪练\n运营管理后台\n\n运营总览\n\n工作台\n\n核心运营\n\n会员管理\n门店管理\n排课中心\n学习报表\n陪练老师\n工资结算\n\n内容运营\n\n词库管理\n专项训练\n\n账号与安全\n\n运营账号管理\n\n个人设置\n\n修改密码\n退出登录\n返回\n工资结算\nL\n\nPAYROLL CENTER\n\n工资结算\n按有效上课记录与抗遗忘工资核算工资,完成月度审核与发放登记\n刷新统计\n导出 Excel\n\n收益统计: 本地审查:模拟服务暂不可用;工资批次: 本地审查:模拟服务暂不可用;历史单价: 本地审查:模拟服务暂不可用;工资标准: 本地审查:模拟服务暂不可用;老师列表: 本地审查:模拟服务暂不可用。对应区域保留上次成功结果(如有),请重试。\n\n收益统计\n抗遗忘工资\n工资批次\n排课与工资标准\n统计月份\n2026年10月\n门店\n全部门店\n全部门店\n陪练搜索\n查询\n重置\n缓存数据 · 查询于",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "student",
+    "width": 1366,
+    "route": "/pages/home/index",
+    "url": "/pages/home/index",
+    "overflow": false,
+    "body": "线上与线下,一对一陪练\n\n你好,本地审查账号\n\n21天抗遗忘计划\n抗遗忘计划暂时无法加载,点击重试\n坚持每天学习,记得更牢,成绩更好!\n查看计划\n0\n累积生词\n0\n剩余陪练时长\n0\n累计陪练时长\n0\n30分钟软件时长\n0\n60分钟软件时长\n科学抗遗忘\n今日复习任务\n专项训练\n分类强化词汇\n学习记录\n查看历次学习\n词汇测试\n检查掌握程度\n自主学习\n生词本\n选生词,开始学习\n勾选想学的单词,按自己的节奏练习\n进入生词本\n主页\n伴学\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "student",
+    "width": 1366,
+    "route": "/pages/home/shop",
+    "url": "/pages/home/shop",
+    "overflow": false,
+    "body": "实时同步暂时不可用,已保留当前内容\n陪练排课\n0 / 0 节\n刷新课表\n清除筛选\n今天\n明天\n本周\n全部\n全部 0\n计划未完成 0\n已结束 0\n‹\n2026年10月\n›\n一\n二\n三\n四\n五\n六\n日\n28\n29\n30\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n1\n2\n3\n4\n5\n6\n7\n8\n\n2026-10-13 · 0 节课\n\n2026-10-13 暂无排课\n\n请求暂未完成,请检查网络后重试;已填写内容会保留。\n重新加载\n上一页\n共 0 条 · 1 / 1\n下一页\n主页\n伴学\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "student",
+    "width": 1366,
+    "route": "/pages/home/ucenter",
+    "url": "/pages/home/ucenter",
+    "overflow": false,
+    "body": "本地审查账号\n个人信息设置\n实时同步暂时不可用\n账号数据暂时无法加载\n重新加载\n账号与学习权益\n学生账号 · 大众会员\n待审核\n会员有效期\n未设置到期日\n累计陪练\n0 小时\n已结束 0 次\n0\n剩余陪练时长\n0\n剩余30分钟软件\n0\n剩余60分钟软件\n查看\n课时余额与预留\n0\n可查陪练时长\n我的课程\n科学抗遗忘\n学习新词\n从“我的内容”选择已绑定课程和学习单元\n抗遗忘数据暂时无法加载\n重新加载\n我的服务\n专项训练\n生词本\n学习记录\n意见反馈\n当前版本:2.0.0-angular\n隐私政策\n服务协议\n隐私注销通道\n退出登录\n南昌小树陪练科技有限公司\n主页\n伴学\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "student",
+    "width": 1366,
+    "route": "/pages/member/passwd",
+    "url": "/pages/member/passwd",
+    "overflow": false,
+    "body": "修改登录密码\n暂未开放\n\n此功能暂未开放,请联系管理员处理。\n\n返回首页意见反馈",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "teacher",
+    "width": 1366,
+    "route": "/pages/home/index",
+    "url": "/pages/home/index",
+    "overflow": false,
+    "body": "线上与线下,一对一陪练\n\n你好,本地审查账号\n\n陪练师工作台\n今日业务概览\n今日业务数据暂时无法加载\n重新加载\n关联学员复习情况\n抗遗忘带练\n抗遗忘计划暂时无法加载,点击重试\n查看全部学员复习计划\n陪练排课\n查看今天与未来课程\n销课记录\n核对已完成课程\n学员记录\n按学员查看学习成果\n抗遗忘工资\n查看抗遗忘复习工资\n主页\n陪练\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "teacher",
+    "width": 1366,
+    "route": "/pages/home/shop",
+    "url": "/pages/home/shop",
+    "overflow": false,
+    "body": "实时同步暂时不可用,已保留当前内容\n陪练排课\n0 / 0 节\n刷新课表\n清除筛选\n今天\n明天\n本周\n全部\n全部 0\n计划未完成 0\n已结束 0\n‹\n2026年10月\n›\n一\n二\n三\n四\n五\n六\n日\n28\n29\n30\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n1\n2\n3\n4\n5\n6\n7\n8\n\n2026-10-13 · 0 节课\n\n2026-10-13 暂无排课\n\n请求暂未完成,请检查网络后重试;已填写内容会保留。\n重新加载\n上一页\n共 0 条 · 1 / 1\n下一页\n主页\n陪练\n我的",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "admin",
+    "width": 1366,
+    "route": "/admin/dashboard",
+    "url": "/admin/dashboard",
+    "overflow": false,
+    "body": "小树陪练\n运营管理后台\n\n运营总览\n\n工作台\n\n核心运营\n\n会员管理\n门店管理\n排课中心\n学习报表\n陪练老师\n工资结算\n\n内容运营\n\n词库管理\n专项训练\n\n账号与安全\n\n运营账号管理\n\n个人设置\n\n修改密码\n退出登录\n工作台\nL\n本地审查管理员\n\nOPERATIONS OVERVIEW\n\n运营工作台\n聚焦今天需要处理的用户、预约和教学任务\n刷新数据\n暂时无法载入运营工作台\n本地审查:模拟服务暂不可用\n重新加载",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "admin",
+    "width": 1366,
+    "route": "/admin/schedule",
+    "url": "/admin/schedule",
+    "overflow": false,
+    "body": "小树陪练\n运营管理后台\n\n运营总览\n\n工作台\n\n核心运营\n\n会员管理\n门店管理\n排课中心\n学习报表\n陪练老师\n工资结算\n\n内容运营\n\n词库管理\n专项训练\n\n账号与安全\n\n运营账号管理\n\n个人设置\n\n修改密码\n退出登录\n返回\n排课中心\nL\n本地审查管理员\n\nSCHEDULING CENTER\n\n排课中心\n按陪练或时间快速排课,并在提交前发现冲突\n刷新\n新增排课\n日\n周\n列表\n日期\n2026年10月13日\n陪练筛选\n全部陪练\n全部陪练\n搜索排课\n业务状态\n全部状态\n待开始\n上课中\n陪练已结束\n学员已确认\n已完课\n已归档\n逾期未完成\n状态待核对\n已取消\n待分配 / 账号异常\n全部状态\n查找\n重置\n0\n条筛选结果\n当前范围共 0 条\n\n本地审查:模拟服务暂不可用\n\n预约到完课流程\n排课、上课、结课与评价\n老师一次结课,学生随后评价\n1\n运营创建预约\n选择学员已绑定课程、时间和陪练\n状态 0\n2\n陪练开始上课\n预约当天均可开始,记录实际上课时间\n上课中\n3\n老师结课并生成报告\n课堂结果、反馈与课时扣减一次完成\n已结课\n4\n学生评价\n学生查看报告后评分;未评价不阻塞结课\n评价独立\n当前范围课程\n0\n2026-10-13 至 2026-10-13\n待分配\n0\n未分配老师时不能开始\n在岗陪练\n0\n未配置可用时间会提示\n历史排课总量\n0\n— 至 —\n数据库更新\n新排课程、教学状态以新库为准\n陪练排课泳道\n点击排课查看明细与可用操作\n待开始\n上课中\n陪练已结束\n学员已确认\n已完课\n已归档\n逾期未完成\n状态待核对\n已取消\n当前范围暂无已排课的陪练",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "role": "admin",
+    "width": 1366,
+    "route": "/admin/payroll",
+    "url": "/admin/payroll",
+    "overflow": false,
+    "body": "小树陪练\n运营管理后台\n\n运营总览\n\n工作台\n\n核心运营\n\n会员管理\n门店管理\n排课中心\n学习报表\n陪练老师\n工资结算\n\n内容运营\n\n词库管理\n专项训练\n\n账号与安全\n\n运营账号管理\n\n个人设置\n\n修改密码\n退出登录\n返回\n工资结算\nL\n本地审查管理员\n\nPAYROLL CENTER\n\n工资结算\n按有效上课记录与抗遗忘工资核算工资,完成月度审核与发放登记\n刷新统计\n导出 Excel\n\n收益统计: 本地审查:模拟服务暂不可用;工资批次: 本地审查:模拟服务暂不可用;历史单价: 本地审查:模拟服务暂不可用;工资标准: 本地审查:模拟服务暂不可用;老师列表: 本地审查:模拟服务暂不可用。对应区域保留上次成功结果(如有),请重试。\n\n收益统计\n抗遗忘工资\n工资批次\n排课与工资标准\n统计月份\n2026年10月\n门店\n全部门店\n全部门店\n陪练搜索\n查询\n重置\n缓存数据 · 查询于",
+    "unnamedInputs": [],
+    "pageErrors": []
+  },
+  {
+    "case": "unauthenticated",
+    "requested": "/pages/shop/get_order?id=901",
+    "actual": "/pages/member/wxauth",
+    "overflow": false
+  },
+  {
+    "case": "unauthenticated",
+    "requested": "/admin/payroll",
+    "actual": "/admin/login",
+    "overflow": false
+  }
+]

+ 10 - 0
docs/qa/launch-audit-2026-10-13/probe-local-pages.cjs

@@ -0,0 +1,10 @@
+const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');const fs=require('fs');
+(async()=>{const b=await chromium.launch({headless:true,channel:'chrome'});const results=[];const root='docs/qa/launch-audit-2026-10-13';
+for(const viewport of [{width:375,height:812},{width:1366,height:900}]){
+ for(const [role,port,routes] of [['student',4200,['/pages/home/index','/pages/home/shop','/pages/home/ucenter','/pages/member/passwd']],['teacher',4200,['/pages/home/index','/pages/home/shop']],['admin',4300,['/admin/dashboard','/admin/schedule','/admin/payroll']]]){
+ const p=await b.newPage({viewport});const errors=[];p.on('pageerror',e=>errors.push(e.message));await p.addInitScript(({role})=>{localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:audit-fixture',user:{userId:role==='teacher'?30:20,groupId:role==='teacher'?3:1,honeyName:'本地审查账号'},student:null}));localStorage.setItem('xiaoshu_admin_session_v1',JSON.stringify({objectId:'audit',sessionToken:'r:audit-fixture',username:'local-audit',displayName:'本地审查管理员',roles:['ops-manager'],operationsRole:'ops-manager',company:{objectId:'fixture'}}));},{role});
+ await p.route('**/*',async r=>{const u=new URL(r.request().url());if(!['127.0.0.1','localhost'].includes(u.hostname))return r.abort();if(u.pathname.startsWith('/cloud-functions')||u.pathname.startsWith('/ncloud')||u.pathname.startsWith('/backend'))return r.fulfill({status:503,json:{retcode:-1,retmsg:'本地审查:模拟服务暂不可用',success:false,message:'本地审查:模拟服务暂不可用'}});return r.continue()});
+ for(const route of routes){await p.goto(`http://127.0.0.1:${port}${route}`);await p.waitForTimeout(2200);const record=await p.evaluate(()=>({url:location.pathname,overflow:document.documentElement.scrollWidth>innerWidth,body:document.body.innerText.slice(0,1700),unnamedInputs:[...document.querySelectorAll('input')].filter(e=>!e.labels?.length&&!e.getAttribute('aria-label')&&!e.getAttribute('aria-labelledby')&&e.type!=='hidden').map(e=>({name:e.name,type:e.type,placeholder:e.placeholder}))}));results.push({role,width:viewport.width,route,...record,pageErrors:[...errors]});if(viewport.width===375&&route==='/pages/home/index')await p.screenshot({path:`${root}/${role}-home-unavailable.png`,fullPage:true});}
+ await p.close();}
+}
+const p=await b.newPage({viewport:{width:375,height:812}});for(const [port,route] of [[4200,'/pages/shop/get_order?id=901'],[4300,'/admin/payroll']]){await p.goto(`http://127.0.0.1:${port}${route}`);await p.waitForTimeout(200);results.push({case:'unauthenticated',requested:route,actual:new URL(p.url()).pathname,overflow:await p.evaluate(()=>document.documentElement.scrollWidth>innerWidth)});}await b.close();fs.writeFileSync(`${root}/local-page-probes.json`,JSON.stringify(results,null,2));console.log(JSON.stringify(results.map(({body,...r})=>r)));})().catch(e=>{console.error(e);process.exit(1)});

+ 22 - 0
docs/qa/launch-audit-2026-10-13/reproduce-auth.cjs

@@ -0,0 +1,22 @@
+const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');const fs=require('fs');const vm=require('vm');
+(async()=>{
+ const source=fs.readFileSync('scripts/deploy-admin-functions.mjs','utf8');const fn=source.split('\n').find(l=>l.startsWith('async function migrateLegacyLogin('));
+ let resetCalls=0,saves=0,newPassword='new-password-fixture';const values={username:'fixture',passwordResetRequired:true,isDisabled:false,isDeleted:false};
+ const target={get:k=>values[k],set:(k,v)=>values[k]=v,setPassword:p=>{newPassword=p;resetCalls++},save:async()=>{saves++}};
+ const context={verifyLegacyPassword:async()=>({retcode:1}),migratedLoginTarget:async()=>target,fieldsOf:async()=>({passwordResetRequired:true}),Parse:{User:{logIn:async()=>({getSessionToken:()=> 'r:local-fixture'})}},fail:(s,m)=>{throw Error(m)}};
+ vm.createContext(context);vm.runInContext(fn+';globalThis.run=migrateLegacyLogin',context);await context.run('fixture','legacy-password-fixture');
+ const results={legacyPasswordFallback:{resetCalls,saves,newPasswordOverwritten:newPassword==='legacy-password-fixture',resetRequiredCleared:values.passwordResetRequired===false,mode:'local function with isolated in-memory adapters; no real account touched'}};
+ const browser=await chromium.launch({headless:true,channel:'chrome'});const page=await browser.newPage({viewport:{width:390,height:844}});let calls=0,held;
+ await page.route('**/cloud-functions/**',async route=>{calls++;if(route.request().postDataJSON()?.params?.action==='user_get'){held=route;return;}await route.fulfill({json:{retcode:0,result:{}}});});
+ await page.goto('http://127.0.0.1:4200/pages/member/wxauth');await page.locator('app-login-page').waitFor();
+ await page.evaluate(()=>{const c=ng.getComponent(document.querySelector('app-login-page'));c.account='local-fixture';c.password='a'.repeat(20);c.privacyChecked=true;c.submit()});
+ results.longPassword={requestCount:calls,message:await page.locator('body').innerText()};
+ await page.evaluate(()=>{const c=ng.getComponent(document.querySelector('app-login-page'));c.session.setLogin({userId:20,groupId:1,sessionToken:'r:old-fixture'});c.api.get('user_get').subscribe({error:()=>{}})});
+ await page.waitForTimeout(100);if(!held)throw Error('No delayed request');
+ await page.evaluate(()=>{const c=ng.getComponent(document.querySelector('app-login-page'));c.session.setLogin({userId:21,groupId:1,sessionToken:'r:new-fixture'})});
+ await held.fulfill({status:401,json:{retcode:-1,retmsg:'旧会话已过期'}});await page.waitForTimeout(150);
+ results.staleSession={stored:await page.evaluate(()=>JSON.parse(localStorage.getItem('xiaoshu.angular.session'))),url:page.url()};
+ await page.evaluate(()=>localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:local-fixture',user:{userId:20,groupId:1}})));
+ await page.goto('http://127.0.0.1:4200/pages/member/acc_destory');await page.getByText('此功能暂未开放,请联系管理员处理。').waitFor();results.accountDeletion={body:await page.locator('body').innerText()};
+ await browser.close();fs.writeFileSync('docs/qa/launch-audit-2026-10-13/auth-reproduction.json',JSON.stringify(results,null,2));console.log(JSON.stringify(results,null,2));
+})().catch(e=>{console.error(e);process.exit(1)});

BIN
docs/qa/launch-audit-2026-10-13/student-home-unavailable.png


+ 118 - 0
docs/qa/launch-audit-2026-10-13/sync-readonly.json

@@ -0,0 +1,118 @@
+{
+  "status": "completed-with-preserved-conflicts",
+  "businessDate": "2026-10-12",
+  "failed": 0,
+  "createdBusinessRecords": 53,
+  "refreshedBusinessRecords": 181,
+  "conflicts": 1330,
+  "allCapturedSourceIdsReconciled": true,
+  "settlementReady": false,
+  "datasets": [
+    {
+      "modelId": 2,
+      "label": "专项训练",
+      "source": 19,
+      "created": 0,
+      "updated": 0,
+      "conflicts": 19,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 52,
+      "label": "词库",
+      "source": 106520,
+      "created": 0,
+      "updated": 0,
+      "conflicts": 148,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 58,
+      "label": "课程绑定",
+      "source": 1519,
+      "created": 0,
+      "updated": 0,
+      "conflicts": 105,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 56,
+      "label": "每日学习",
+      "source": 19638,
+      "created": 2,
+      "updated": 0,
+      "conflicts": 0,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 53,
+      "label": "练习记录",
+      "source": 443601,
+      "created": 29,
+      "updated": 74,
+      "conflicts": 224,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 60,
+      "label": "抗遗忘",
+      "source": 200355,
+      "created": 20,
+      "updated": 98,
+      "conflicts": 370,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 54,
+      "label": "预约",
+      "source": 31254,
+      "created": 0,
+      "updated": 9,
+      "conflicts": 311,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 59,
+      "label": "上课",
+      "source": 19411,
+      "created": 2,
+      "updated": 0,
+      "conflicts": 0,
+      "failed": 0,
+      "reconciled": true
+    },
+    {
+      "modelId": 61,
+      "label": "测评",
+      "source": 206,
+      "created": 0,
+      "updated": 0,
+      "conflicts": 0,
+      "failed": 0,
+      "reconciled": true
+    }
+  ],
+  "runtimeMatches": {
+    "run-nightly-direct-sync.mjs": true,
+    "lib/legacy-direct-projection.mjs": true,
+    "lib/legacy-direct-specs.mjs": true
+  },
+  "schedule": {
+    "StartCalendarInterval": {
+      "Hour": 23,
+      "Minute": 0
+    },
+    "RunAtLoad": true,
+    "KeepAlive": {
+      "SuccessfulExit": false
+    },
+    "ThrottleInterval": 300
+  }
+}

BIN
docs/qa/launch-audit-2026-10-13/teacher-home-unavailable.png


+ 60 - 0
docs/qa/launch-audit-2026-10-13/verification-summary.json

@@ -0,0 +1,60 @@
+{
+  "auditedHead": "9224f7a",
+  "baseline": "working tree including two pre-existing modified files",
+  "build": {
+    "mobile": "passed",
+    "admin": "passed",
+    "initialRawKB": {
+      "mobile": 410.17,
+      "admin": 367.21
+    },
+    "estimatedTransferKB": {
+      "mobile": 112.89,
+      "admin": 102.76
+    }
+  },
+  "tests": {
+    "mobile": {
+      "total": 197,
+      "passed": 192,
+      "failed": 5
+    },
+    "admin": {
+      "total": 256,
+      "passed": 254,
+      "failed": 2
+    },
+    "server": {
+      "total": 163,
+      "passed": 163,
+      "failed": 0
+    }
+  },
+  "diagnosticOnlyTests": {
+    "mobileBatchTests": {
+      "passed": 3,
+      "change": "temporary copy uses current .901.unbound. draft prefix"
+    },
+    "adminCoachMonthTests": {
+      "passed": 4,
+      "change": "temporary copy corrects obsolete September expectations to current October; not a permanent date-independent fix"
+    }
+  },
+  "actionAudit": {
+    "requested": 116,
+    "implemented": 99,
+    "blocked": 21,
+    "missing": []
+  },
+  "reportBrowser": {
+    "viewports": [
+      375,
+      812
+    ],
+    "overflow": false,
+    "saves": 1,
+    "pageErrors": [],
+    "screenshots": "/tmp/xiaoshu-report-previews"
+  },
+  "productionMutations": false
+}