iOS CI
2026-11-07 核心回归并行化
PR 和 main / dev 推送的 core-regression 不再让两台设备串行承担同一套 App 单测和 StoreKit。App 单元测试只在 iPhone 执行;七个核心 UI 选择器按布局分工,iPhone 覆盖会员、默认目录和相机旋转,iPad 覆盖账号数据、两个饮食目标流程及宽屏输入。全部 UI 测试仍先完整编译,手动 full 仍在两端执行每个 UI 用例。
真实 StoreKit 契约从 ios-tests 拆到并行运行的 ios-storekit:日常 core 只执行一次 iPhone,会员专属改动和手动 full 继续覆盖 iPhone、iPad。ios 汇总门禁同时要求 App/UI 矩阵和 StoreKit 成功,任一失败都不能由另一项通过覆盖。结果包名称加入 run_attempt,重跑时不会与上一轮同名。这样减少的主要是串行等待和重复的设备无关测试,没有删除 UI 用例或降低金融断言上限。
2026-11-06 按输入范围验证
后续会员基础层为纯会员模型/服务及本地 StoreKit 目录选择 membership:App 单元测试只执行一次,两端保留账号 UI,另以独立、ad-hoc 签名的 CookMembershipStoreKit Scheme 在两端执行实际 SDK 契约;不将测试商品配置放入默认或 Release Scheme。混入工程、数据、协议、未知路径时继续 core。core 和手动 full 的 StoreKit 独立任务也执行真实契约,任一步骤失败都会令 ios 失败,不增加金融重试。编排测试实际执行对应 Bash 并验证构建/测试失败码保留。双设备本地覆盖和历史环境失败见 会员验证。
浏览器专属的页面、组件、适配器、组合函数、状态及已列出的对应测试只运行 Web 检查。scripts/ci-changes.mjs 使用白名单:共享协议(包括位于 Web 目录的 utils/userData.ts)、共享数据和资源、生成器、原生代码、依赖与构建配置、未知文件仍触发原生验证。重命名同时检查删除和新增路径,不能把原生文件移进 Web 目录来避开检查。说明文档包含 specs/**/*.md;打包的隐私政策仍是原生输入。
push 仍只在同分支的精确前一提交 CI 已成功时免除原生任务;前一提交失败、取消、查询失败时继续跑原生。既有 PR 结果复用继续核验源码树和工具链,不扩大复用范围;ios、swift、web-acceptance 名称和失败门禁保持。2026-11-06 只读查询 main 的 branch protection 返回未保护,适用 rules API 返回空列表,仓库当前没有服务端 required checks;本改动不修改仓库设置。
主线 CI 的 Web 汇总从首个任务启动到完成为 74 秒,iOS 汇总为 1,522 秒(25 分 22 秒);iPad 原生任务 1,423 秒,iPhone 1,485 秒。PR #200 的四个路径在新分类下只需 Web 检查,免除两个模拟器任务;约 95% 是按该历史运行计算的等待减少估计,需区分于新流程的实测时间。此次 CI 和原生测试自身变更仍执行 iPhone/iPad 核心矩阵,不能以 Web 分类跳过。
同轮失败位于旋转流程第二次点击番茄后的 isSelected == false 等待:234 项 App 单元测试和其他六项 UI 通过,旋转用例 483.953 秒后报告 Failed to get matching snapshot。此前菜单操作两次缺少动画完成通知,各等待约 60 秒;之后全局 First Match 对投影按钮重复获取快照失败。这是已定位的 XCTest 查询超时,日志不能证明产品的取消选中逻辑有误,也不能证明所有环境均不存在产品缺陷。投影按钮改为唯一完整匹配,旋转用例在每次动作和状态等待前重新查询;继续保留实际点击、选择/取消、3pt 移动、2pt 复位、禁止旋转、场景重建与前后台断言,正常动画和原有时限不变,没有新增重试。
本地复用 iPhone 16 Pro / iOS 26.5,显式禁用并行测试,旋转专项 49.570 秒通过,构建与测试共 93 秒;与远端硬件、系统不同,不能据此宣称远端问题已全部解决。结果摘要与完整命令日志在本机 ~/Library/Developer/ReleaseArtifacts/Cook/20261106-ci-scope/;临时 DerivedData 和结果包在导出后回收。完整系统审计仍通过手动 full 执行。当前发布前还须保留真实支付沙盒及账号切换验收,分类不替代这些门禁。
后续 PR #201 CI 在提交 35fa43388c0acecff19eaf870be18dc257e4c767 全部通过,两端各实际执行 234 项 App 单元与 7 项核心 UI,手机旋转 207.561 秒通过。两个原生任务分别约 23 分 35/36 秒;这轮改动原生查询与 CI 本身,仍运行完整核心矩阵,没有将其描述为 Web 专属变更的耗时。
swift 在 Linux 上运行 CookCore 和跨端往返检查;ios-tests 使用 Xcode 27.1 和现有 iPhone、iPad 模拟器。
PR 和向 main / dev 的推送默认执行 core-regression,iPhone、iPad 各一个 App/UI 任务,另有日常一次的 iPhone StoreKit 任务。App 单元测试只在 iPhone 运行;七个核心 UI 选择器覆盖会员、账号数据、最大字号便当与体重目标交集、结果页取消目标、宽屏忌口搜索、默认目录数量及相机旋转与复位,按窄屏和宽屏分工。所有 App 和 UI 测试代码仍先完整编译;核心用例的断言失败直接使检查失败,不自动重试。
合并后,push 可复用两台设备已经通过的 PR 核心测试。仅接受同仓库、同目标分支、与 push 提交对应的已合并 PR。GitHub 必须记录两组实际构建和测试均成功,且来自同一次 CI 运行和尝试。核验使用两端真正测试的合并 checkout:其父提交必须是当前 push 的前一提交和 PR head,Git 树必须等于合并后的树。仓库代码和本地工作流、脚本、配置、锁文件均在比较范围内。
匹配后分配一台 xcode-27 runner,使用与测试相同的准备步骤,比对执行的 CI 工作流、runner 镜像、macOS 构建、架构、Xcode、Swift、模拟器 SDK、XcodeGen 版本和可执行文件摘要、Node、pnpm、实际 SwiftPM 解析文件以及两台模拟器的机型和运行时构建。macOS 准备步骤的外部 actions 固定到具体提交。命中时在核验任务摘要给出来源运行,省去两台设备的构建和测试;合并别的提交、设备检查被跳过、版本变化、查询失败、旧 PR 没有记录等情况均执行正常矩阵。Web 和 Linux Swift 检查独立运行。手动运行(包括 full)不读取 PR 复用记录。
完整回归改为手动执行:在 Actions 的 CI → Run workflow 中将 ios_coverage 选为 full(默认值),或运行 gh workflow run ci.yml --ref main -f ios_coverage=full。选择 core 则只重跑核心组。完整模式继续在每端执行全部 56 项 UI 测试(含两项系统无障碍审计),分为以下三组,共六个任务;没有删除用例或审计类别。手动 full 与日常 core 分开排队,后续核心推送不会取消正在运行的完整审计。
| 分组 | 内容 |
|---|---|
regression-main | App 单元测试及其他 UI 测试;新增 UI 测试类默认归入此组 |
regression-discovery | 云厨房、找菜和随机挑选 UI 测试 |
dietary | 两项独立无障碍审计,然后执行其余饮食目标 UI 测试 |
每个任务先 build-for-testing,后续调用 test-without-building 复用编译结果。日常核心组最多并行两个原生任务,手动完整矩阵最多并行三个;其他仓库和运行仍可能占用共享配额。同一模拟器内仍禁用并行测试,避免产生克隆设备。ios 汇总门禁要求本次选择的全部分组成功;只涉及已列出的文档或独立工单工作流 .github/workflows/support-sync.yml 时,PR 可跳过原生测试。推送还要求同一分支、前一提交的 CI 已成功。无法核实前一提交时仍执行核心测试,手动 full 始终执行完整矩阵。
构建限时 15 分钟;核心测试步骤限时 30 分钟、任务限时 45 分钟;完整测试步骤限时 60 分钟、任务限时 90 分钟。2026-11-04 的 iPad 完整常规回归在冷启动、213 项单元测试和持续通过的 UI 用例上耗尽了原先的 45 分钟预算,因此为完整回归保留额外时间。普通用例默认允许 300 秒,独立审计允许 600 秒;取消空目标、结果页清除目标、目标保存、云厨房跨布局浏览、旋转/前后台恢复、云厨房目标浏览和最大字号便当流程也显式使用 600 秒上限。其中取消空目标、目标保存、云厨房跨布局浏览在慢速 CI 快照下曾分别耗时约 360 秒、264 秒和超过 300 秒,云厨房目标浏览曾在 297 秒完成主体后于清理中超过 300 秒。查询已缩小范围,各操作的原有等待期限与失败断言保留。禁用失败后的全局诊断收集,仍保留结果包中的测试操作、截图与错误。相同分支的新运行会取消旧运行。
只有 classify-ios-test-retry.mjs 已识别的单项审计服务超时或宿主启动故障可在同一个既有设备上重试一次。断言、真实审计发现、未知错误、混合失败或再次失败均不能被重试掩盖。
失败、取消或发生基础设施重试时,App/UI 结果上传为 ios-xcresult-<device>-<suite>-attempt-<run_attempt>,StoreKit 结果上传为 ios-storekit-xcresult-<device>-attempt-<run_attempt>,均保留 7 天。失败步骤会输出结果包中的错误;下载 Artifacts 后可用 Xcode 打开完整记录。
xcodebuild 的 stdout/stderr 先写入同目录的 <device>-<stage>.log,测试结束后再完整输出到 Actions,退出码继续决定成败。CI #698 的失败录像中目标选项已经显示,采样栈显示 App 在 XCTest 查询元素属性期间阻塞于 writev。日志管道回压是据此排查的一个方向;磁盘缓冲让 Actions 消费日志的速度不再直接限制测试进程输出。编排测试故意暂停 stdout 消费并输出超过管道容量的日志,验证测试工具仍能完成、完整失败日志保留且任务继续失败。没有重试或跳过相关业务用例。
Xcode 27.1 / iOS 27.0 上,CI #709 的 iPhone 在启动后重复选择食材视图时无法取得快照,iPad 在筛选、饮食目标两层弹窗间累计等待至 300 秒。采样显示 XCTest 在 App 内遍历无障碍属性时写入 stderr;外层 xcodebuild 日志缓冲无法避免这条路径。饮食测试通过启动参数重置到食材快选后直接等待 cook.chooser。工具栏控件、决定页操作与播报卡片、详情列表,以及唯一的弹层导航栏和列表用 element 检查匹配;重启或重新打开弹层后重新定位元素。确认按钮可点击后不重复读取其存在和点击点;实际点击、关闭以及返回后选中状态均继续验证。
饮食目标的滚动辅助方法也对结果列表和唯一菜谱行使用完整匹配。检查目标弹层中“记住目标”控件缺席时,先确认实际列表和导航栏存在,再分别断言按钮与开关的匹配数量为零;避免为不存在的元素创建惰性首匹配查询。最大字号的滚动、可点击检查、五类无障碍审计,以及取消目标后弹层持续显示的断言仍保留。失败采样与验证记录见 2026-11-05 CI 修复证据。
同日完整矩阵的第二次运行中,iPad 浏览流程在目标完成按钮和目标选项的 First Match 查询上耗时约两分钟,随后在仍继续操作时触及 300 秒上限;其余九个饮食目标用例通过。附件记录了这两个实际查询链,采样也包含 XCTest 的首匹配遍历。目标选项辅助方法及弹层打开、关闭辅助方法中的唯一完成按钮改用 element,保留容器范围、重复匹配检查、呈现等待、业务断言和原有时间上限。
iOS 27.1 Duo 的快照中,完成按钮与结果菜单位于导航栏之外的系统工具栏。按全局唯一标识定位实际控件,并单独验证弹层导航标题与关闭结果;滚动收起完成工具栏时,在对应列表中最多反向滚动八次以恢复控件,再检查可操作状态。“记住目标”的缺席检查覆盖原有列表、导航栏及包含当前完成按钮的投影工具栏,继续分别检查按钮与开关数量为零。
最大字号便当列表的行可能跨越多个屏幕;使用与现有最大字号审计相同的 25 次滚动上限,标准字号仍为五次。继续定位同一道西葫芦炒蛋、核对候选数 10 → 9、实际点击与原文用油量,保留原有执行时间上限。
无障碍审计可能重置目标列表的滚动位置并重建行。iOS 26.5 最大字号录屏确认,审计后“减少添加糖”仍有勾选,但回到列表顶部后已部分离屏,原行查询无法解析。审计前后都在实际 dietary.choices 列表中完整匹配同一目标,保留每次最多八次滚动及原有可点击等待,随后继续断言选中状态;审计后只恢复可见区域,不重新选择目标,全部审计类别仍执行。
UI 测试的布尔状态等待优先复用 CookUITests.swift 中的 waitForState,由 XCTest 原生 API 检查存在、选中、启用和可点击状态;复合条件共用调用方原有的等待期限。精确属性相等也可使用原生 KeyPath 等待,其他文本匹配、值和几何条件保留对应谓词。辅助方法失败时会保留当前无障碍树和截图。
已知控件所属列表时,将查询限定到 cook.chooser、dietary.results 等实际容器;找菜、云厨房和饮食目标流程通过 cookingToolbarButton 按全局唯一控件标识完整匹配,避免依赖切换布局时仍在更新的导航栏投影,目标选项限定到 dietary.choices。取消目标按钮的消失检查也使用唯一匹配,保留原有不存在断言。食材名称查询限定到实际食材列表;场景和目标入口限定到 filter.options;已选食材文案和候选行限定到对应结果列表;找菜详情的收藏按钮限定到导航栏,避免标签匹配扫描旁侧的完整菜谱目录。手机固定结果入口先确认已显示;有候选时还应可点击,无候选时保留可见但禁用的状态。滚动辅助方法在单次调用内复用窗口和固定底栏边界,每轮只读取一次列表边界,保留滚动后的实际可见区域检查。滚动前根据弹层导航栏与固定底栏确定可见区域,必要时先展开半屏弹层,再确认目标的完整边界进入可点击区域。
打开筛选或饮食目标弹层时,先确认该弹层的完成按钮存在且可点击,并验证对应导航标题,再确认列表已出现,随后读取选项。最大字号滚动后,在实际点击前通过原生状态等待确认目标选项可点击,允许列表完成布局。关闭饮食目标或筛选弹层后,复用 completeDietarySelection 等待实际父页的控件恢复存在且可点击,再继续验证目标值和结果。对已消失全局元素的查询可能遍历整个无障碍树;使用已知入口和当前呈现方式定位控件,避免通过探测不存在的全局控件推断页面状态。云厨房普通控件与原生渲染检查限定到 kitchen.content,滚动边界使用当前布局实际存在的底栏;投影虚拟按钮在相机更新时可能改变快照祖先,使用唯一标识的全局 firstMatch。缺席检查限定到相应列表、工具栏或文章内容,原生菜谱渲染检查限定到已确认存在的详情或文章容器,避免对全 App 做空匹配。决定页跳转则确认详情导航标题对应选中的菜名,并等待其实际收藏按钮可点击;导航栏是容器,部分布局没有自身点击点。确认选中菜谱的导航恢复可操作后,再确认 recipe.detail 列表及其中的工作台按钮,避免在旧、新详情和决定弹层切换时发起全局按钮查询。
发现流程和饮食目标测试为每个 App 实例注册异步 teardown,保留其代理直到清理结束,再终止仍在运行的测试 App,避免下一用例继承旧实例。
CI #695 的 iPad 在取消目标后的食材列表快照查询处超时,其余 229 项通过;相同用例本地单跑未复现。食材按钮提供稳定的 cook.ingredient.<食材名> 标识,该用例直接查找唯一标识并继续断言中文标签和选中状态。滚动辅助方法每轮只读取一次列表边界,导航栏和底栏的固定边界只读取一次,减少宽屏结果列表旁重复生成无障碍快照的开销;保留完整可见、可点击与食材不丢失断言。
CI #701 的 iPad 在关闭饮食目标弹层后,读取筛选页完成按钮的可点击状态时报告无效激活点;录像中按钮已可见,原用例本地连续三次未复现。关闭子弹层时先等待其导航栏消失,再实际点击父页完成按钮并确认筛选导航栏消失,最后验证原入口恢复可操作,避免在层级切换期间重复读取点击点。弹层导航栏负责验证关闭状态,完成按钮按稳定唯一标识定位,以兼容导航栏之外的系统投影;该取消目标用例继续在日常核心组两端执行。
CI #706 的 iPad 226 项全部通过,十项 UI 约 24 分钟、测试步骤约 30 分钟,低于 #701 的约 31、39 分钟。iPhone 225 项通过,最大字号便当流程在完成用油断言、保存截图时触及 300 秒上限。其重新打开筛选后的计数查询耗时约 110 秒,诊断快照仍指向上次弹层的“10 道菜”及无效边界。该流程现在每次从当前 filter.summary 底栏重新查询计数,复用弹层呈现就绪等待并确认关闭;保留 10 → 9 道、原文用油和最大字号断言。并行修复为该最大字号流程保留 600 秒单例上限,PR 测试步骤仍限时 35 分钟,不增加重试。CI 失败附件中的录像未完成导出,不能据此声称目视验证;最终以最新提交运行结果为准。
食材快选与云厨房切换会替换工具栏。选择后在导航栏中重新查询 cook.presentation,同时匹配当前视图的无障碍值或标签,再等待该元素出现;不要对切换前的按钮保留属性谓词等待。PR #189 的 CI #684 录像中厨房已显示,旧属性等待仍超时;此处继续核对实际播报的选择,失败时保留无障碍树与截图。
CI #687 的 iPhone 食材查询也曾超时,附件中的按钮描述和录像均确认「番茄」存在,采样栈停留在 XCTest 子元素遍历。云厨房切换用例的食材标签查询限定到 cook.chooser,厨房控件限定到 kitchen.content,继续检查同一按钮的标签、选择状态及切换后的食材保留。
三个反馈表单、厨房互切/真实菜谱、灵感卡片静态布局及最大字号视图菜单功能测试通过 -ui-testing-disable-animations 在 Debug 测试进程中关闭 UIKit 动画。CI #694 的厨房流程在原生菜单处两次等待动画完成通知,各耗时 60 秒,随后在流程末尾触及 5 分钟限时。保留所有交互、选中状态、真实菜谱、错误与草稿断言;最大字号菜单继续检查真实菜单选项、44pt 边缘命中与食材保留;其他菜单连续开关、旋转以及无障碍审计继续使用正常动画。
2026-11-05 主线完整矩阵中,iPhone 灵感卡片用例在菜单处两次缺失 UIKit 动画完成通知,各等待 60 秒,随后等待事件循环就绪并超过 300 秒限时。该用例使用既有 Debug 动画开关,仍通过真实菜单进入云厨房,检查食材命中、灵感卡片完整滚动到固定操作区上方及其实际可点击性;专门的正常菜单开关、相机旋转与审计流程继续保留正常动画。未对后续启动断言失败增加重试,最终以完整矩阵重新验收。
2026-11-05 后续完整矩阵的 iOS 27.2 旋转用例在关闭旋转后的锅标记查询处失败,录屏画面已恢复固定视角。相机移动会替换投影无障碍快照;每次测量都重新完整匹配唯一锅标记,并从同一帧读取横纵坐标。保留 3pt 移动、2pt 复位及禁止旋转时不移动的几何断言、原有等待期限、正常 UIKit 动画、场景重建和前后台恢复流程。
原生视图菜单由 Coordinator 保留当前菜单配置;只有呈现方式或辅助字号说明变化时才重建菜单,避免无关 SwiftUI 刷新替换 UIKit 正在呈现的菜单。单元测试同时检查菜单身份保持与实际选项状态更新,UI 测试继续验证连续开关、44pt 边缘命中和最大字号。
快选和云厨房共用 CookPresentationToolbarItem。iOS 26.5 验收曾发现系统共享胶囊将自定义内容父容器限制为 36pt;该自定义项使用独立 44pt 玻璃胶囊,保持真实的上下边缘命中区域,避免只扩大内部 UIButton。 修改编排后运行 node --test test/iosTestInfrastructure.test.mjs。该检查执行真实 Bash 脚本,只替换外部 Xcode / 模拟器边界,并验证完整矩阵中每个现有 UI 用例在每种设备上恰好分配一次、核心组保留全部单元测试与五个必要流程、失败不被另一台设备成功掩盖、重试分类及汇总门禁。
2026-11-03 排查记录
CI #667 的 iOS 任务 从 19:20:57 UTC 运行到 22:50:13 UTC,约 3 小时 29 分钟,结论为 failure。Web 和 Swift 检查通过,两台设备的 214 项 App 单元测试均通过。
长耗时由两台设备串行全量 UI 回归、失败用例反复查询与滚动、审计超时重试共同累积。iPad 的一个用例寻找仅在窄屏存在的 cook.showResults,耗时约 375 秒;取消目标后读取原生菜单 value 得到空字符串;标准字号审计在 dynamicType 类别报 Code=-56。本地复现还确认,失败后 simctl diagnose --timeout=600 会继续占用时间。
对应修复:宽屏改为核对实际结果栏的目录数量;菜单标签直接提供当前饮食目标;筛选点击前确认整个选项露在固定汇总栏上方,并等待选中状态;iPad 输入联想后通过系统按钮收起键盘;滚动辅助方法按内容方向、所在栏和键盘遮挡定位,依次检查顶部食材和下方厨具时分别选择方向;灵感用例滚动收集三道菜的 ID,兼容 iPad 的懒加载;随机结果使用确定的无障碍元素类型查询,避免遍历整个应用的所有角色。
本地验证
2026-11-03:17 项变更检测与 CI 编排检查通过,合并主线后的 32 项 AI 服务和契约检查通过,工作流静态检查与 lint 通过。使用 Xcode 27.1,复用 iPad Pro 13 英寸 M5(iPadOS 27.0)和 iPhone 16 Pro(iOS 26.5)串行复查相关 UI 路径;标准与最大字号审计、便当与体重目标交集、目标清除及持久化均有成功结果。
针对过程中仍失败的键盘与滚动方向分别补跑:iPad 联想选材用例 51.117 秒通过,iPhone 取消场景后保留食材与厨具用例 63.464 秒通过。原始失败日志、各轮结果摘要和最终专项成功摘要保留在本地 artifacts/ci-ios-20261103/。全部 UI 用例由远端矩阵继续执行,不能用本地专项结果替代最终 CI 状态。
2026-11-04 PR #189 后续检查
CI #671 的 Web、Swift、视频检查和其余五组 iOS 均通过;iPad regression-main 剩下忌口搜索查询超时和常备调料入口未完整进入可见区域两项失败。
PR 同步主线 7b3200cc 的原生状态等待、受限容器查询、筛选弹层展开与导航容器消失检查,保留「次日便当 10 → 叠加控制体重 9 → 原文用油」的标准及最大字号覆盖。本地四项专项复测全部通过。
后续一轮 PR 原生任务排队超过 20 分钟,而同仓库主线已在运行完整矩阵。为减少重复占用 macOS runner,PR 改为两台设备的关键回归,完整矩阵在主线和手动执行时运行。原来失败的忌口搜索和常备调料用例均保留在 PR 中;最终结果以 PR 最新提交的 CI 为准。
食材查询限定到实际列表后,共享 iPhone 16 Pro / iOS 26.5 的厨房互切标准流程,以及 iPad Pro 11 英寸 / iPadOS 27.0 的标准和横屏流程共三项通过。额外试跑的大字号菜单边缘触控在 iOS 26.5 仍有失败;iOS 27.1 的两项探测另遇到返回按钮无障碍类型不一致和菜单命中检查失败。保留这些失败记录及原有断言,不把额外探测计为通过;本地专项不能代替最新提交的远端门禁。
上述本地菜单失败来自同步 4609ff73 之前的版本;该主线修复保留菜单实例并修正工具栏父容器的真实命中区域,新增两项单元测试。历史失败记录保留用于对照,合并后状态以新提交的 CI 和对应平台复测为准。
2026-11-07 后续会员基础分支 CI 42249053236 在旋转按钮的存在查询再次遇到快照超时(所有 App 单元及其余核心 UI 通过),并非食材选中断言失败。旋转控件改为每次全局定位唯一 ID,避免复用 List 子树的 First Match 代理;拖动、点击、选中、关闭复位与重建场景验证保持完整。本机 iOS 27.1 定向用例 58.713 秒通过,远端结果仍须独立跟进,不以先前 #201 的一轮通过覆盖这次失败。
StoreKit 金融契约在独立的 ios-storekit 任务中与 App/UI 矩阵并行,即使其他 UI 失败也会继续执行并保留结果。记录 PR 复用凭据必须同时满足两台设备的 App/UI 核心任务和 iPhone StoreKit 成功;不能用金融通过覆盖其他失败,也不能让无关 UI 的快照错误阻止交易契约验收。
手动 workflow_dispatch 可显式选择 ios_coverage=membership,App 单元只执行一次,两端执行账号/会员 UI 和独立真实本地 StoreKit;后续会员页面分支还加入两项会员交互。full 仍是默认值;core、默认未知输入和 PR/push 的保守分类不改变。会员任务使用独立 concurrency 组,不取消显式完整审计。手动会员结果不表示完整核心回归已通过。