本文件是 Claude Code 在本仓库工作的操作规范,是 docs/项目计划书.md(唯一权威来源)的执行摘要。两者冲突时以计划书为准,并回过头更新本文件。
StackMaster 是一个可回放、可解释的 Pwn 概念实验室:后端权威 VM + 浏览器公开投影,以与 API 同源的独立页面形态交付(服务端签发一次性启动地址并下发,学习者直接打开该地址),让初学者通过直接操作内存学习 pwn 基础。MVP 聚焦"栈帧、缓冲区与返回地址"闭环(计划书 11.1)。
分发改版状态(2026-09-28 订正):页面分发已是磁盘上的实现,不再是「目标态」—— apps/page-app(与 API 同源)+ 启动票据签发 / 换票链 + 会话动作协议 v2 + 整页布局 + 终端单主题均已落地(WP-90 ~ WP-95 + WP-95a;事实段见下文「分发改版与 UI 重设计落地事实」)。退役面的物理删除已执行完毕(WP-96;2026-09-28 复核:apps/plugin-dev、packages/embed-runtime|web-component|react-wrapper、docs/contracts/嵌入协议.md 均不在磁盘上,源码面 .tab-area 零命中)。WP-99 阶段收口评审已于 2026-09-28 完成 ⇒ 本阶段 WP-90 ~ WP-99 全部落地(判定 / 门禁读数 / 遗留总账见 docs/phases/中期验收评审.md §八;总账 43 项、仍开放 23、P0 = #21 / #27 / #30)。
非目标:不做完整 CPU/OS 模拟器、完整 x86-64 指令集、ELF 加载器、真实 shellcode、完整 glibc、复杂堆分配器、在线编辑器、AI 自动 exploit、多人实时协作;浏览器端永远不存在本地权威执行或本地判题。
- VM Core、完整状态和隐藏判题信息完全隔离在后端。
- 浏览器只接收可公开的脱敏投影,不负责最终计分或权威状态保存。
- 服务端会话串行执行所有动作;单步、回退、checkpoint、回放和最终裁决均以服务端为准。
- 题目 DSL 不执行任意宿主代码,以与 API 同源的独立页面分发(不提供与第三方页面共享 DOM 或执行上下文的嵌入形态),并保持 Core、UI、协议和平台适配层解耦。
- 浏览器完全不可信(9.2)。任何发送到浏览器的数据——投影、错误、事件、长度、时序——都视为可被选手完整读取。
VmState、私有题目包、隐藏测试、seed、权威快照、完整 IR 只存在于 vm-worker 进程内(信任域 3,5.2)。- 投影的白名单过滤与脱敏在执行域内完成(ADR-7);编排器、网络通道与浏览器上只允许出现公开投影。
- 禁止发送到浏览器:隐藏 flag、隐藏测试、私有目标条件、私有 capability 返回值、完整 IR、原始快照、完整事件日志、内部堆栈与文件路径(9.2)。
- 禁止 FFI / N-API 内嵌;TS 与 Rust 只通过进程边界(spawn + JSON 协议)通信(ADR-3)。
- TS 构建图不得引用 vm-engine 任何产物;浏览器不得加载 VM Core、可执行 IR 或私有题目包(5.5,CI 产物扫描强制)。
- 服务端从认证上下文派生用户/租户/题目,不接受请求体或 postMessage 自报身份(6.2)。
- 侧信道约束:错误精度按 ProjectionPolicy 粗化;投影、错误与回放不得通过长度、时序或分类差异间接泄露秘密(9.2)。
- 前端混淆、禁用右键、检测 DevTools 不是反作弊手段;有效控制只在服务端串行执行、独立重放、隐藏测试、短期凭证、限流与审计(9.2)。
- 公开描述包与私有判题包字段严格分离,禁止通过默认值、引用 ID 或 Schema 元数据从公开包推导私有字段(7.1、13.2)。
stackmaster/
├── apps/ # TypeScript 应用(pnpm workspaces)
│ ├── session-api/ # 会话编排器(Fastify;管理 vm-worker 进程池)——信任域 2
│ ├── verifier/ # 独立裁决服务(复用 vm-engine 回放实现)——信任域 4
│ ├── admin/ # 最小管理面(只读:题目登记 / 裁决查询 / 成绩导出;独立凭证与网络域,
│ │ # 自有只读库角色 admin_ro;不入页面分发链路)——信任域 4
│ ├── page-app/ # **新增(2026-09-18 分发改版)**:与 API 同源的独立页面应用(替代 plugin-dev 的插件壳职能)
│ └── ~~plugin-dev/~~ # **退役(2026-09-18 分发改版)**:插件 iframe 开发壳与接入 Demo(替代 = page-app + 启动票据签发端点)
├── packages/ # TypeScript 包
│ ├── protocol/ # @stackmaster/protocol:Zod 契约 → JSON Schema(跨语言契约唯一来源)
│ ├── challenge-schema/ # 公开/私有题目包 JSON Schema 与字段分类校验器
│ ├── challenge-compiler/ # DSL → 受限 IR(仅后端)
│ ├── ~~embed-runtime/~~ # **退役(2026-09-18 分发改版)**:postMessage 嵌入协议(宿主侧 SDK);替代 = 启动票据 / 启动地址
│ ├── ~~web-component/~~ # **退役(2026-09-18 分发改版)**:<pwn-memory-vm>(Lit 3);替代 = page-app 内 Lit 组件
│ ├── vm-ui/ # 投影渲染:字节视图、寄存器、调用栈、时间线、Payload 构造器
│ └── ~~react-wrapper/~~ # **退役(2026-09-18 分发改版)**:可选 React 薄包装
├── vm-engine/ # Rust workspace(cargo)——信任域 3,仅后端
│ ├── vm-worker/ # 二进制:单会话进程入口,stdio / 本地 socket JSON 协议
│ ├── vm-core/ # 纯 VM 语义
│ ├── vm-runtime/ # COW 快照、规范化动作日志、回放、私有题目包加载
│ └── projection/ # ProjectionPolicy 白名单与脱敏
├── tooling/ # eslint、dependency-cruiser、clippy 配置、CI 脚本与隔离扫描
└── docs/ # 文档(索引:docs/README.md;contracts 契约与规则 / adr 决策 / phases 阶段分解与验收 / develop 设计文档)
依赖方向由 CI 强制(dependency-cruiser / cargo workspace 声明,5.5):
- TS:
protocol可被所有 TS 包依赖(唯一跨域共享面);challenge-schema只被 challenge-compiler、session-api、verifier、admin 依赖(WP-79 起,admin 按公开包 Schema 展示题目登记元数据);vm-ui只依赖protocol(—— 2026-09-18 分发改版:vm-ui/web-component/embed-runtime/react-wrapper只依赖protocolweb-component/embed-runtime/react-wrapper三包退役,不再计依赖面)。admin对工作区包的依赖面 =protocol+challenge-schema(数据面是自有只读库角色直连 PG,禁 app→app 依赖;dependency-cruiseradmin-workspace-deps-allowlist强制)。 - Rust:
vm-core← vm-runtime、projection、vm-worker;vm-runtime与projection← vm-worker。 - 跨语言规则:IR 与题目包是版本化序列化格式,不是共享代码;VM Core 不知道自己运行在 Lit、React、iframe 还是 Node.js 里。
| 层 | 选型 |
|---|---|
| 浏览器 UI | Lit 3 + TypeScript;Vite |
| 契约 | @stackmaster/protocol(Zod → JSON Schema);题目包 JSON Schema 2020-12 + Ajv |
| 传输 | HTTPS + Fastify(REST);认证 WSS(@fastify/websocket)+ JSON 消息 |
| 服务运行时 | Node.js LTS(≥22) |
| VM 引擎 | Rust(safe Rust,无 async、无直接 IO),进程边界交付 vm-worker |
| 数据 | PostgreSQL 16+(唯一权威存储);Redis 7+(只存可重建、带 TTL 状态);MinIO / S3(私有判题包、trace) |
| 工程 | pnpm workspaces + Turborepo + Cargo workspace;Vitest + fast-check、cargo test + proptest、Playwright、axe-core、k6 |
| 边界强制 | dependency-cruiser + ESLint + cargo clippy |
| 可观测 | Pino / tracing + OpenTelemetry + Prometheus |
| CI/CD | GitHub Actions + Changesets(npm 发布) |
- 每个 crate 顶部
#![forbid(unsafe_code)],纯 safe Rust 同步状态机; - 无 async、无直接 IO;禁用
std::time、rand、std::io、std::net与文件系统;时钟与随机源由 trait 注入(6.3); - release 构建
overflow-checks = true;溢出触发时按engine_error安全终止,不得静默回绕; - 地址与 archBits 位宽架构值(G1/D1:出题人指定 32/64,以 64 位容器承载、高位按位宽掩蔽)在 VM Core 内是一等公民(端序内建);题目 DSL 与公开 API 用明确的十六进制字符串(6.2);
- verifier 与交互执行复用同一份回放引擎代码(ADR-8),禁止写第二套实现;
- Rust 收益来自安全与语义保真,不是速度——不要为性能引入 unsafe、async 或低层优化。
protocol/challenge-schema包是契约单一来源;Zod schema 同时产出 TS 类型与 JSON Schema;JSON Schema 是 TS 与 Rust 的共同权威,Rust 以 serde + schemars 消费;- 服务端对一切入站数据(HTTP、WSS;
postMessage 转发的动作—— 2026-09-18 分发改版:嵌入协议整体退役)按同一契约重新校验,不信任客户端类型标注; - 每类契约(
嵌入协议⇒ 启动票据 / 启动地址、会话动作协议、题目包 Schema、引擎进程协议)携带独立版本号;破坏性变更递增版本并保留 N-1 兼容窗口(例外:嵌入协议整体退役,不适用 N-1 演进窗口,走硬切 + 迁移指引;启动票据属新契约面,须先契约后实现); - 错误类型也是契约:
PublicError枚举保持稳定,前端不得解析非契约字段; - 修改协议必须同步更新 golden fixture:同一组样例必须被 TS 与 Rust 校验器同时接受或拒绝,且规范化 JSON 序列化一致。
- 回放不得依赖当前时间、浏览器随机数、网络响应顺序、宿主线程调度或浏览器未定义行为;
- 题目需要随机化时只用可复现的服务端 seed,并把 seed 策略与环境版本写入回放信息;
- 动作日志 append-only,每次操作记录:类型与参数、前后状态哈希、内存/寄存器变化、事件序号、题目版本、VM Profile 与引擎版本;
- 内存按固定大小分页,COW 快照,不逐步复制完整状态;
- 生产环境锁定执行环境版本,不得自动使用"最新引擎"(7.4)。
所有状态变化走统一动作链路:ActionRequest → 认证/授权/revision/幂等校验 → 服务端 VM 状态转换 → 私有目标条件检查 → ProjectionPolicy 脱敏 → ActionResponse / ProjectionDelta。
服务端必须:校验会话 token 绑定;校验 baseRevision;以 idempotencyKey 防重;单会话内串行;对动作参数、地址、区域权限、操作数与资源预算重新校验。浏览器不得提交自制快照、状态哈希或成功标志覆盖服务端状态。稳定结果类型:success、wrong_answer、invalid_action、program_crash、memory_fault、resource_limit、timeout、engine_error、challenge_invalid、replay_mismatch、cancelled(9.1)。
- 浏览器任何位置(主线程、Worker、IndexedDB)只保存公开投影与 UI 状态;
- 按"教学动作或关键事件"粒度返回
ProjectionDelta,不逐条指令发完整投影;服务端维护 dirty range 合并连续写入; - 用
requestAnimationFrame合帧,只更新变化的内存 cell,不重绘整张内存表; - 断线时只展示最近一次收到的公开投影;重连走
sync-projection;禁止降级为本地 VM 执行; - Web Worker(可选)仅做公开投影合并与渲染辅助,不得运行 VM;
- 动画只用 transform / opacity 等 compositor 友好属性;字节视图用语义化 DOM + 虚拟列表,控制流关系用 SVG;屏幕阅读器信息不得只存在 Canvas 中;
- 传输层二进制编码、压缩等优化必须有 benchmark 证据后才引入(10.1)。
当前处于 T0 最小闭环(TS 服务层 + Rust VM Engine、单实例编排器、每会话独立 Worker 进程、Docker Compose),目标是 MVP 验收。K8s、Kafka/NATS 等消息中间件、微服务拆分、二进制投影编码、VM Core WASM 化均属 T2/T3 选项——进入条件(benchmark 或教学数据证据)满足前不得引入。工程精力优先投给正确性、可解释性和测试覆盖。
- TS:tsc(project references)+ ESLint;Rust:cargo clippy(
-D warnings)+ cargo fmt 检查; - dependency-cruiser 依赖边界 + 引擎确定性 lint +
#![forbid(unsafe_code)]检查; - 单元与属性测试:Vitest + fast-check / cargo test + proptest;覆盖率整体 ≥ 80%,
vm-core、vm-runtime、projection、challenge-compiler≥ 90%; - cargo miri(vm-core UB 敏感面:掩蔽算术 / 译码边界 / 页与 COW / 快照克隆 / 条件求值;本地全量入口
pnpm test:miri)+ cargo-fuzz(题目包、动作与 IR 解析器);⚠ miri 是受限命令(2026-09-28 主控裁定;起因 = agent 在监控项目进度时未经同意自行启动pnpm test:miri,已中止) —— 任何test:miri/cargo +nightly miri的启动(本机 / CI / 由 agent 代跑一律适用)必须先取得用户(主控)明确同意,并说明理由;理由至少含「为何此刻必须跑 / 预期收益 / 预计耗时」(VM 敏感面实测量级 = 十至数十分钟,且长时间独占机器);未获同意不得启动,也不得以「顺手补一条证据」「既然 nightly 在场」为由自行开跑;未获同意期间该门禁在证据行里保持「未实测」登记 —— 既不得记为通过,也不构成阻塞项; - golden fixture 跨语言往返一致;
- 浏览器产物隔离扫描(不得含引擎代码、私有题目包内容、vm-worker 二进制);
- Compose 集成测试:会话创建 → 动作 → 投影 → 断线重连 → 提交裁决全链路;
- Playwright E2E:
iframe 嵌入⇒ 页面分发(启动地址进入的同源页面;2026-09-18 分发改版)、Chrome/Firefox/Safari、断线恢复; - k6 benchmark 归档(T2 触发判据)。
新功能先写测试(TDD);测试用行为描述命名;错误反馈断言要覆盖"可解释性"而不只是状态码。
- 复杂功能先出实现计划再写代码;涉及协议、投影、题目包 Schema 的改动,必须先更新
protocol/challenge-schema契约与 golden fixture,再改实现; - 涉及认证、投影生成、协议、题目包校验、判题的代码,提交前必须做安全审查;
- 受限命令:miri ——
test:miri/cargo +nightly miri的运行须先经用户(主控)同意并说明理由(规范条文与判据见上文「测试与质量门禁」第 4 条);agent 不得自行启动、不得代为补跑、不得以「顺手补证据」为由开跑; - 文档、提交信息与面向人的注释用中文;代码标识符用英文;
- Conventional commits:
feat|fix|refactor|docs|test|chore|perf|ci: <描述>;对外发包用 Changesets —— 仅契约包(protocol、challenge-schema等);对外发包(2026-09-18 退役(分发改版):前端三包随嵌入协议整体退役,不再对外发包;web-component、react-wrapper、embed-runtime)用 Changesets - 不提交
.env、私有题目包样本、真实隐藏 flag;private-bundles类内容永不进入 git。
- Compose 一键拓扑:
pnpm --filter @stackmaster/session-api compose:app:up / compose:app:down(PostgreSQL + Redis + MinIO + session-api + vm-worker 冒烟;session-api 发布 13000,依赖 15432 / 16379 / 19000 / 19001);拓扑集成入口test:compose(container / host 双形态,CI 为完整 linux 拓扑);开发上手文档apps/session-api/README.md; - 可观测端点:
GET /healthz(liveness)、GET /readyz(readiness)、GET /metrics(Prometheus 文本;五指标族:动作 RTT / 并发会话 / 队列深度 / Worker 占用 / 投影增量字节;标签零秘密零标识符,机检assertMetricsTextDiscipline);OpenTelemetry 为 T0 可选增量(仅登记 D-API-72); - k6 基线:
pnpm --filter @stackmaster/session-api k6:baseline(docker grafana/k6,场景在apps/session-api/k6/;不设通过阈值,结果归档apps/session-api/k6/results/); - 覆盖率入口:仓库根
pnpm test:coverage(vitest projects 聚合 apps + packages,整体门槛 ≥ 80%;完整门禁形态SESSION_API_IT=1 pnpm test:coverage,纳入容器门控集成测试;配置文件为根vitest.coverage.config.ts——不得改名vitest.config.ts,包级 vitest 会误读); - TS 测试规模(阶段三末):session-api 345 passed / 30 skipped(容器门控);集成 75 passed(
test:integration+ compose 套件);Rust 基线 318 × debug / release 双 profile 全绿。
- 工作区模型 = 固定窗口集(D-MP-1):窗口集合 = 注册表登记的全部类型(10 类)各恰一实例、常驻;窗口没有开 / 关状态,只有「视口内 / 暂离(条带滚出视野)」。模型层
WorkspaceLayoutModel.bindWindows(bindings, columns?)一次性绑窗(窗口 id ≡ 类型键 ⇒ 单实例结构性保证),focusWindow(type)聚焦(不创建实例),isWindowSetComplete不变量机检;openTab/closeTab/formatTabTitle/ 类型内序号 / 关闭入口 / 空态引导全部退场。绑定点 = 工作区接入(首帧前)+tabTypes换绑,与「接入会话」解耦 ⇒ 模式切换只换绑数据源、布局零副作用(layoutSnapshot深度相等有断言)。菜单「打开」组 → 「窗口」聚焦组(锚点data-window-type+ class.focus-window+aria-pressed,无禁用态,不留data-tab-type兼容别名)。 - 布局 = Niri 式,预设单一来源:
src/workspace/layout-presets.ts三张常量表(P0 宽屏 5 列 / P1 中宽 3 列 / P2 窄条单列)为默认列排布唯一来源,经bindWindows(entries, columns)单点注入(工作区层不得持有第二份默认布局);selectLayoutPreset(viewportWidth)纯函数选档。三表「10 类型各恰一次、无重无漏」机检固定。阈值推导:字符宽 7.8px(13px × 0.6em)× 行 58ch ⇒MIN_COLUMN_WIDTH452.4px、NARROW_MAX_PX468.4px、WIDE_MIN_PX932.8px(含真实列间空隙 = 列间距 ×2 + 分隔条宽 12;此处按实现侧推导登记,已由主控采纳为定案口径)。布局状态进快照面:columns[].widthRatio/columns[].rowHeights(和恒 1、单窗列恒[1])/viewportWidth;「重置布局」= 清空尺寸调整 + 应用当前视口宽对应预设。分隔条(role=separator+tabindex=0,pointer + 方向键)、焦点列相机纯函数cameraScrollLeft(按prefers-reduced-motion降级)、三类拖拽落点(stack/cross-column/new-column)、列宽五档 + 重置入口仅在菜单「布局」组(标题栏保持零控件)。视口外降级渲染 =content-visibility: auto+contain-intrinsic-size: auto 9rem(择一登记;零 JS)。 - 主题 = 21 token × 3 预设:
SM_THEME_PRESET_VALUES = ["light","dark","terminal"](light / dark 新增值取系统色关键字 ⇒ 零视觉变化;effect 面在 light / dark 取0/0s)。嵌入协议EMBED_THEMES三值零改动(冻结面);terminal由data-sm-theme="terminal"承载 + 宿主 appearance 映射为 dark(#applyAppearanceToHost保留外部锚 +#observeAnchor()锚变更观察)。决策登记 D-API-110。2026-09-17 M3 WP-80 增补第 21 枚:--sm-canvas-sprite-filter(深底画布垃圾桶 / 缩放光栅精灵处理;lightnone/ dark·terminalbrightness(1.6))—— 该增补取代 D-API-110「定案 1:零新增 token 硬约束」的绝对表述(见 D-API-130;授权来源 = M1 遗留项「深色画布光栅精灵可见性」,2026-09-17 移交 M3 并由 WP-80 承接)。theme-terminal.test.ts的精确键集断言已由 20 同步为 21。 - 调试档投影接线(WP-70):工厂
createDebugDataSource的 options 扩为DebugDataSourceAssemblyOptions(运输面 & 数据源面,显式解构),工作区缺省装配传projectionProvider: () => session.store.snapshot ?? null;未提供 provider 时regions()/registers()恒空的行为保留(以反例固定)。装配路径集成测试已补(既有测试经debugDataSourceFactory测试接缝绕开真实装配路径 = 缺陷漏网原因,已登记在测试文件头)。 - 台账:中期决策登记于
docs/develop/权威API语义规约.md§三·二十一(D-API-110 ~ 116);WP 勾选与证据行在docs/phases/中期任务分解.md§二(含 WP-74 前置项两包的承接登记);用户面文档docs/user/界面帮助手册.html已同步固定窗口模型(§1.1 / §1.3 / §2.1;列宽窗高 / 预设 / 重置 / 响应式降级等待 WP-72 后由 WP-77 回填)。 - 测试规模(M1 收口):vm-ui 62 files / 766 passed(M1 前 54/624,零 skip / todo);E2E chromium 全量含新增
e2e/workspace-layout.spec.ts(8 例)与e2e/workspace-windows.spec.ts(3 例);axe 真机e2e/axe-contrast.spec.ts3/3 例、6 个可达面 violations = 0。 - 全窗口常驻暴露的 4 条 axe 违规已清零(判定依据 = 逐面比对 HEAD 归档:五面均为 0 违规且
dark-registers面color-contrast仅检查 1 个节点、零blockly命中 ⇒ 相关节点在 M1 前根本不在场;WP-72 关闭content-visibility复跑同例仍红,排除降级渲染嫌疑):①role="log"误用于<ol>(违aria-allowed-role且使<li>失 list 父级)⇒ 去掉 role、保留aria-live="polite";②窗口面板与内层视图地标重名 ⇒ 面板名不动(WP-71 同源承诺保留),内层 region 名带窗口维度(byte.viewAria/instr.viewAria/vma.ariaScoped,SmVmaList新增viewLabel);③暗色 Blockly 画布.blocklyToolboxCategoryLabel对比度 1.35:1 ⇒ 新增src/payload/blockly-theme.ts:官方主题 API 的componentStyles映射 6 项组件样式为var(--sm-*)(内联 +var()⇒ 三档即时生效、组件内零色值复制)+ 同根样式表覆盖两处 Blockly 字面值(标签自身color:#fff声明继承不可覆盖,是唯一必需改写);实测 1.35 → 17.66(暗)/ 18.87(亮)。遗留:深色画布上垃圾桶 / 缩放光栅精灵的可见性 —— 已由 WP-74 复核确认未承接,2026-09-17 移交 M3(补偿规则需按主题生效,但样式表在画布宿主 shadow 根内、主题锚在树外,树内样式表无法匹配树外祖先 ⇒ 无效规则已删除;修法 = 把主题锚镜像进 shadow 树,或新增「深底精灵处理」token,须先定 token 名与值;承接载体 = M3 WP-80,2026-09-17 订正:原登记「M3 WP-81 遗留移交清单」是失效指针 —— WP-81 实为 canary 契约收口(docs/phases/中期任务分解.md:342),不含「遗留移交清单」;按 M3 分派口径丙逐项核对WP-78 ~ WP-82后落定实现 WP = WP-80(M3 中唯一触及 Blockly 画布与src/payload/blockly-theme.ts主题接缝者))。 - 主 chunk 体积:
dist/sm-workspace-*.js实测 1,353.13 kB(gzip 328.65 kB 量级)。该体积在 M1 之前即已越过中期计划 §一.2.3 的 1.3MB 触发线(两次独立测量 1,327.59 / 1,328.12 kB,且中期零新增依赖、零新增入口)⇒ 判为既有体积,拆分评估为已触发的待决项(不属 M1 交付面;M1 净增约 25.5 kB)。 - 全窗口常驻的渲染负担观测(风险表「全窗口常驻」行证据,可复跑):1440×900 下窗口 10 /
sm-workspaceshadow 节点 251 / 降级面板 10 / 聚焦交互 RTT 68~84 ms。
- 终端主题全组件落地(WP-74):全部消费主题变量的组件(16 个)零硬编码色、逐处走
var(--sm-*, <字面量回退>);回退字面量 = 原字面量逐字 ⇒ light / dark 零视觉变化(唯一例外--sm-divider-faint暗色8% 黑 → 10% 白,判为修正并已登记;.client-step-pause保留--sm-accent,terminal 下呈青绿而非琥珀,语义张力已登记)。未新增任何 token(theme-terminal.test.ts精确锁定 20 键)。等宽字体栈 19 处逐字统一为SM_MONO_FONT_STACK;字号下限 13px(7 处0.75rem → 0.8125rem,几何值零改动)。 - 效果面三件(扫描线 / 光标闪烁 / 终端标题栏):全部动画声明包在
@media (prefers-reduced-motion: no-preference)内、reduce 下display: none;装饰一律aria-hidden="true"+ 零文本 + 零可聚焦后代 + 不承载信息;扫描线opacity ≤ 0.06且pointer-events: none落在基态规则(静态属性,reduce 侧同样断言)。light / dark 下强度取0⇒ 不可见。 - 主题护栏 = 自校验全组件机检(非手写清单):
THEME_COMPONENT_TAGS(16 消费)+THEME_EXEMPT_TAGS(2 豁免,各附理由)的并集必须等于源码@customElement("…")真实清单(递归扫描src/**/*.ts)⇒ 新增 / 改名组件忘记登记直接变红。另含字号下限机检(rem×16 折算)与字体栈回退值漂移机检(防 19 处副本各自漂移)。 - axe 真机门禁(chromium):三预设 × 既有扫描面,9 个可达面
violations = 0;归档e2e/reports/axe/<运行当日>/(按日期新增,无覆盖开关;WP-55 的2026-09-11/为只增不改的历史证据)。不可达格如实登记:shell dark、degraded dark、degraded terminal(降级形态不安装文档级 token 样式表 ⇒ 锚在场但无变量可级联;拒绝用 harness 注入产品路径不会产生的样式表制造假绿)。 - 真机门禁的价值 = 抓出测试接缝漏掉的缺陷:首跑即抓出
.pseudo-asm[data-pseudo-asm-source="solve"]对比度 4.47:1(graytext落在面板色上,正文门槛 4.5:1)。该节点此前不可达 —— 跳转链死绑定(D-API-117)修好前链恒空,故 M1 归档里此节点根本不在场(与 M1「暗色 Blockly」1.35:1 同机理)。教训:graytext在浅底与深底都不达标,「降低前景色」表达次要性与 13px 正文门槛结构性不相容 ⇒ 改用系统字体 + 标题文案区分。 - 三浏览器矩阵(E2E_MATRIX=1):firefox / webkit 实跑 58 passed / 28 failed / 22 skipped。webkit 为环境级阻断、非产品缺陷:本机 Windows headless 建不了认证 WSS 动作通道(
[client_error] 动作通道未连接),故全面失败 —— 这正是「WebKit linux 复跑义务」被限定的原因,清偿载体 =e2e-matrix的 ubuntu job。2026-09-17 订正:该归因已被推翻(勿沿用) —— ubuntu 的e2e-matrixjob 跑起来后,webkit 在 375/768/1024/1440/1920px 的嵌入面与壳面共 10 格以同一形态失败(connection-status恒reconnecting,断言Expected "connected";另 320px 壳面step-button恒disabled同因),而 chromium / firefox 同格全绿 ⇒ 不是环境级阻断、清偿载体已用尽,须按真实 webkit 兼容缺陷重开调查。2026-09-17 调查完成,根因已定案:webkit 的 WSS 升级被服务端以401拒(浏览器控制台Unexpected response code: 401,服务端日志同步记录六次GET /sessions/channel全 401、responseTime ≈ 0.3ms),而 chromium / firefox 同 URL 同页面open。机制 = 升级认证走凭证 preHandler(Cookie 呈递)(apps/session-api/src/wss/wss-plugin.ts:7-10),而客户端注释所称「同源升级自动携带会话 Cookie」在本拓扑下不成立(插件页:5174≠ API:13000,且位于宿主页 iframe 内)⇒ WebKit 最严的跨源 / 第三方 Cookie 策略使握手不带凭证。修法方向与两个未定分项、以及「属认证面改动,须走契约 + 安全审查」的处置纪律,见docs/phases/中期任务分解.md§二 WP-74 末段。逐用例证据同处。 - CI/门禁形态:新增
e2e-matrixjob(.github/workflows/ci.yml,chromium + firefox + webkit);不进 turbo 任务图(保持 CI 承载),本地复跑命令与 env 清单在apps/plugin-dev/README.md。e2e-matrix在 CI 实跑绿至少一次(E-3)未达成 —— job 已落地并通过静态校验,首跑证据待推送后由 ubuntu 产出。2026-09-17 进展:四个历史红的根因已修(engine 侧 rustfmt 漂移 / Node 22 与本地 24 的微任务跳数差 / MinIO 镜像不再可匿名拉取——compose 与 matrix 各一处)⇒ run 32 起 6 个 job 中 5 个绿,仅e2e-matrix仍红;矩阵红点经 Playwrightgithubreporter 转为公开注解后逐用例具名(14 failed / 10 skipped / 27 passed),其中 320px 三引擎横向溢出已定位并修复(真凶 = Blockly 挂到document.body的文本测量画布,见docs/phases/中期任务分解.md§二 E-3),webkit 连接面 10 格待处置。E-3「CI 实跑绿至少一次」仍未取得;另ts-gate转为间歇(vm-ui 包,本地 12/12 未复现)⇒ 其绿不可视为稳定绿。2026-09-17 收口后进展(run 37 / 38,两个独立 run 逐条一致):矩阵收敛为29 passed / 12 failed / 10 skipped,chromium 与 firefox 已全绿(320px 画布修复实证生效:webkit 320px 现红在连接断言而非溢出),12 个失败逐条为[webkit]连接面 ⇒ 红点可复现且单一根因,即上条定案的跨源 WS 认证面。ts-gate间歇坐实(红 run 34 / 37,绿 run 32 / 38);因同一失败零逐用例注解,已查明其为套件级 / 进程级失败而非逐用例失败(配置式github-actionsreporter 经本地实证确实生效),故补**「失败日志 → 公开注解」桥**(76170c7;job 日志 403 / artifacts 401,注解是唯一免凭证通道)待其下次红时定名。当前 CI = 6 个 job 中 5 个绿,唯一红点 = webkit 认证面(须契约 + 安全审查,不在收尾轮改);遗留汇总见docs/phases/中期任务分解.md§二 M3「收口 · 中期验收评审」遗留移交清单。 - 调试档缺陷两类(均属「测试接缝绕开生产装配」缺陷族,D-API-120):①生产入口
main()漏传debugChannel⇒/sessions/debug-channel404(修于a35bb01;测试接缝自己传了该参数故漏网);②修好接线后debug_attach恒internal_error,根因不在通道实现而在题目形态 —— E2E / 演示拓扑唯一驱动的种子题是 IR 模式,而调试变体契约要求字节模式(ADR-DC1)⇒ 真机恒失败(修于c295c87;Node 侧或走占位变体供给、或把 IR 拒绝当期望断言)。纪律启示:凡「测试接缝与生产装配路径不一致」的包,必须补一条装配路径集成测试。 - 调试通道推送模型(冻结口径):attach 回执
status:"running"+debug_function_table,指令流随debug_paused才下发(packages/protocol/docs/调试通道协议语义.md:189-193)。E2E 断言「不步进即见指令行」与之冲突 ⇒ 以契约为准、改用例(未改冻结面)。遗留:单ret种子下唯一可达断点即入口自身,形成「设断点需指令行 / 指令行需暂停」循环依赖 ⇒ 已按 UX 口径 (a1) 产品侧落地(attach 未携带对齐暂停时,前端以公开投影的 RIP 触发一次debug_run_to_breakpoint([rip]),落点 =debug-data-source.ts#pauseAtCurrentRip;冻结帧族 / 推送时机 / 错误码零改动),真机实测首个暂停breakpoint@0x401000+ 该地址指令流 16 条。(a1) 的用户可见性另暴露两处死选择器族缺陷并已修:指令视图 flex 高度链写在渲染输出中不存在的.layout上(渲染根实为section.instruction-view);<sm-window-list>的static styles在 light DOM 架构(createRenderRoot()返回宿主 ⇒ LitadoptStyles不执行)下不可达 ⇒ 结构样式改随模板<style>落到 light DOM(修前列表不是滚动容器、锚点行落在视口外 ≈2539px;修后scrollHeight 1814 > clientHeight 173、锚点行 y=731)。新遗留(第 7 步伪汇编 chip 未达成,唯一红点):调试实例 = attach 时按origin.revision重放权威动作日志得到的克隆,而动作日志只在submit时落库(session-manager.ts#persistActionLogDelta)⇒ 未提交会话的克隆恒为种子初始态(栈区全0x00;真机取证:请求targetRevision:1、实际对齐revision:0)⇒ 栈行 8 字节非地址 ⇒ 跳转链 / chip 结构性不成立;待定案修法 = 对齐源改用在途会话的权威动作日志(SubmitReference.actionLog)或种子题栈上预置指向代码区的返回地址。 - 测试规模(M2 收口):vm-ui 64 files / 806 passed(M1 收口 62 / 766;零 skip;末轮 +8 用例 = (a1) 首个暂停 4 + 列表结构 3 + 指令视图高度链 1);session-api 62 passed | 12 skipped(74 files)/ 607 passed | 94 skipped,真机 IT(真实 Rust worker)
test/debug+test/scan99 passed;pnpm build12/12。主 chunk 体积dist/sm-workspace-*.js实测 1,373.15 kB(M1 收口 1,353.13 kB ⇒ M2 净增约 20.0 kB;该体积在 M1 之前即已越过 §一.2.3 的 1.3MB 触发线,仍属已触发的待决项)。 - M2 末轮新登记的既有缺陷(未修,建议独立 WP):指令视图中每个指令行实测行高 113px(表头行 21px)——
.row-address/.row-bytes是white-space: pre单元格,而 Lit 模板内的换行被保留为幻影空行。后果:一屏只显示 ≈1.5 行(而非 ≈8 行),锚点行的面板内可见比例仅 0.14(仍 > 0,故toBeInViewport是真绿但很薄);面板 231px 与视图固定24rem的几何张力属既有事实。修法小(收窄模板空白),但会改动表格视觉 ⇒ 不塞进 M2 收口,单独立项。
中期 M3「成绩面 / 管理面 / 声明面 / canary 收口 / 采集面 / 体积拆分」落地事实(2026-09-17,WP-78 ~ WP-83 + 四项遗留移交 + 收口;只登记事实,纪律仍以上文与计划书为准)
- WP-78 成绩同步只读接口:新增
GET /host/scores(宿主成绩同步),复用导出的hostBackendTokenMatches(单进程内单份实现,不另写第二套校验);查询参数白名单恰为cursor/limit/tenantId(多余参数即 400);游标 =verdicts.idkeyset 分页(并发插入下无零遗漏保证,已在契约与文档中明示);批量上限SESSION_API_HOST_SCORES_BATCH(默认 500 / 天花板 5000),超限返回 400 而非截断;限流键 =rate:{字典序最小租户}:host_scores;SQL 恰取七列;零 DDL / 零新角色 / 零新 GRANT。宿主→租户绑定经可选SESSION_API_HOST_TENANTS(逗号分隔白名单):空或缺省 ⇒ 成绩面返回 404 同形 fail-closed;查询参数只能在白名单内再选,永不从查询参数派生租户。存放apps/session-api/src/host-scores/。 - WP-79 分支 A 最小管理面(D-MP-5 定案 = 落地):新增
apps/admin(只读控制台;独立凭证ADMIN_CREDENTIAL_SHA256,sha256 +timingSafeEqual;独立网络域admin-net与独立部署,不入插件链路);自有只读库角色admin_ro(不得BYPASSRLS)+ RLS 迁移009_admin_row_level_security.sql+compose/admin-db-init.sql;三只读面(题目登记列表 / 裁决查询 / 成绩导出)全部 GET,成绩导出经HostScoresResponseSchema.parse()单出口复用 WP-78 契约(零第二套实现);闸序冻结 401 → 429 → 404 → 400 → 查询 → 审计 → 派发;零 app→app 依赖(admin只依赖protocol+challenge-schema,由 dependency-cruiseradmin-workspace-deps-allowlist强制)。审计口径(主控裁决):管理面只读查询不进audit_log(audit_log十值封闭集 = 安全事件账,只读查询属数据事件),零新增审计 kind;可审计性由受控日志 + 指标 + 披露点 fail-closed 承载,若将来合规要求读操作入库须复开 D-API-59。compose 三处阻断性片段(缺任一则起不来):db-roles-init.sql建admin_ro角、deps.yaml健康探针grep -qx 2 → 3且只读不变量断言rolname IN (…, 'admin_ro')(不加则被提权的admin_ro会静默绕过 RLS)、app.yaml的admin/admin-db-init/admin-net。 - WP-80 M10 出题者积木最小声明面:公开描述包新增顶层可选字段
authorBlocks(顶层字段 16 → 17,不进required);形状 = 1–16 ×{id, displayText, interfaceId, kind ∈ {address,immediate,length}, slots(≤4), actions(≤8)};actions只能是 12 个公开动作的冻结枚举;args值 = 字面量(≤64)或{slot: <本模板槽键>}结构引用 ⇒ 非图灵完备,不构成任意宿主执行;新增四条检查器规则XS-BLOCK-IFACE-REF/XS-BLOCK-SLOT-FORM/XS-BLOCK-ARG-ALLOW/XS-BLOCK-NO-EFFECT(递归扫描效果原语与私有键名,含展示文本)+authorBlocks[].id纳入既有XS-ID-UNIQUE;CHALLENGE_PACKAGE_SCHEMA_VERSION维持 1(additive)。字段名必须避开interfaces(FORBIDDEN_PUBLIC_PROPERTIES硬约束,公开包任意嵌套深度不得出现该键名)。客户端校验能力边界(结构性):XS-BLOCK-IFACE-REF的「接口是否存在」在客户端不可实现(vm-ui不依赖challenge-schema,依赖纪律 5.5)⇒ 权威面 = 服务端 + 编译链,客户端只做形状闸。同时清偿 M1 遗留的深色画布光栅精灵可见性:新增第 21 枚主题 token--sm-canvas-sprite-filter(lightnone/ dark·terminalbrightness(1.6);真机对比度 dark 1.84 → 3.07、terminal 1.82 → 3.04)。 - WP-81 canary 契约收口(XS-CANARY-CORR):定案 = (c) 双层语义分离(哨兵槽模式 T / 参考值模式 R);(a) 否决 = 安全剧场(隐藏区字节玩家永远写不到 ⇒ canary 恒不触发),(b) 否决 = 字面形态不可实现且只有放宽
project.rsfail-closed 才成立(正面触碰红线第 1 / 4 条与 I-3 / I-9);决定性取证 = 引擎自身 canary happy-path 测试即用visibility:"public"+containsSecret:false(session_lifecycle.rs:848-870)⇒ 冲突实质 = 规则面缺一种模式,不是可见性语义有争议;canary 值是否为判题秘密 = 分模式(模式 T 的期望值不是另存秘密,而是装载期从初始内存截取,exec.rs:288-301⇒ 槽在可见区时结构性不是判题秘密;模式 R 才 SERVER_ONLY);CH-05 = 不切换(无试用数据 + 会改一道已发布题的裁决语义)。本包只结定案面;实现与 P1~P6 待派单(建议 WP-81a),其中「跨语言互证机检」已定为 WP-81a 的完成标准(现状vm-engine/**对XS-CANARY-CORR零测试引用,仅assemble.rs:661-662一句注释 —— 这正是冲突能活到 M3 的原因)。 - WP-82 试用环境与指标采集最小面:
docs/user/试用环境部署指南.md(D-API-138;纪律 = 未实测不给配置片段,运维端点暴露面定案 = D-API-104 选项② 反代准入)+docs/phases/中期试用报告模板.md(24 观察点可得性分类,与采集面逐数一致)。采集载体重建:新增teaching_events表(apps/session-api/migrations/008_teaching_events.sql)——ENABLE+FORCEROW LEVEL SECURITY + 租户绑定政策(谓词逐字同 007,GUC 缺失 ⇒ 谓词恒假 ⇒ 零行 fail-closed)+ 两段式保留期(跨租户仅放行租户枚举读,删除逐租户经租户绑定政策 ⇒ 零跨域 DELETE 政策行、零USING (true))+BEFORE UPDATE/BEFORE TRUNCATE双触发器(DELETE 不设触发器 = 与audit_log的唯一一处分野,因本表带保留期)+kind三值库层 CHECK +UNIQUE(tenant_id, kind, source_ref)幂等锚;授权随表同批落迁移(理由:compose/session-api-db-init.sql首启执行、早于迁移,其ON ALL SEQUENCES覆盖不到新建序列)。三条硬约束:审计 kind 零新增(十值封闭集不变,机检语句面零audit_log+ 全文零审计 kind 字面);采集面不经/metrics(机检零prom-client导入);采集失败不影响会话主链路(采集器永不抛错,折叠为旁路账failed=true+ 可选onFailure回调,审计账 fail-closed 不动)。三类服务端可派生:会话创建 /verdicts成绩方向(冻结 11 值之success,SQL 层 + 派生层双层过滤)/action_log已接受undo计数;口径字面单源(kinds.ts常量以绑定参数注入,SQL 文本零字面)。零学习者标识(六重结构性断言):DDL 面无user_id/ 无原始session_id;端口面没有任何返回行级数据的方法;聚合 SQL 只在GROUP BY消费subject_digest且 SELECT 不含该列;真库扫描落库文本;聚合输出键集恒等冻结九键;编译期@ts-expect-error断言派生入口只收权威行集合。subjectDigest = SHA-256(tenantId ‖ 0x00 ‖ sessionId),只在库内用于幂等与分组,任何端口都不返回。「提示使用」= v1 暂不可采集(not_collectible_v1;库层 kind 封闭集不含hit_used⇒ 伪造计数值结构性不可写入;CLIENT_REPORTED_TEACHING_KINDS = []⇒ 零客户端自报)。聚合 = 受控查询(冻结九键 + 单点组装 +assertTeachingAggregateDiscipline八类机检且每类有红灯反例)。⚠ 未达成:runtime.ts生产接线未做 ⇒ 采集面当前仅经buildTeachingCollection(pool)单点装配交付(单测与 IT 都经该单点装配,不存在「测试接缝绕开生产装配」)。 - WP-83 主 chunk 体积拆分:主 chunk 1,379.02 → 428.65 kB(−68.9%),gzip 336.36 → 107.97 kB;首屏静态图 1,396.71 → 452.51 kB;惰性 chunk 945.82 kB;Blockly 占拆分前主 chunk 的 65.5% raw / 64.1% gzip。两处改动必需(积木注册表 + 入口桶),只改一处 = 假绿;
packages/web-component的codeSplitting: false是结构性上限(其单产物形态无法再分);零新增依赖(含 devDependency)。义务 CLOSED。 - 调试克隆对齐源(遗留第 6 项):对齐源改为在途权威动作日志(
SubmitReference.actionLog,经getSessionSummary.acceptedActionLog)⊕ 已落库基线前缀;origin.revision收紧为精确对齐点;该 revision 不可得时在 spawn 前冻结拒绝(invalid_input_format/"revision is not available"),不静默退回种子态(静默退回会把「对齐失败」伪装成正常克隆,是更坏的失败模式)。 - M3 收口轮真机 axe 修复:真机 axe 抓出
plugin-dev-shell/ light 面scrollable-region-focusable(serious) ——sm-payload-tab的<ol class="output-log">是滚动容器却无tabindex。修法 = 给该<ol>及同渲染点同成因的.program-list加tabindex="0"(axe 标准修法;不动aria-live、不加可聚焦后代、不动 CSS 几何、零新增依赖)。归因:缺tabindex属既有缺陷;把 needle 推过 axe「真实溢出 ≥13px」判定线的触发条件是 WP-83 惰性宿主(<sm-payload-tab-host>无样式插入 payload 窗口 DOM 链;节点集恒定 146 ⇒ 变的是几何)。修复后 9/9 面 violations = 0。遗留:该宿主的高度 / 宽度链未复核(不得因 axe 转绿即判定无几何回归);3 个 terminal 面的「0」主要建立在 axe 无法自动判定之上(非正向通过)。 - 台账与文档:M3 决策登记于
docs/develop/权威API语义规约.md§三·二十三(D-API-122 ~ 151,共 30 条,两批并入并各自逐块机检 19/19 与 11/11 等同);编号有意非连续,分派固定为 122126 WP-78 / 127130 WP-80 / 131133 WP-81 / 134136 WP-79 / 137140 WP-82(文档面)/ 141 遗留-e2e-descriptor / 142144 遗留-5 / 145 遗留第 6 项 / 146148 WP-83 / 149151 WP-82(实现子集)。中期验收评审成文于docs/phases/中期验收评审.md(E-1~E-8 + 门禁证据 + 遗留 32 项);WP 勾选与证据行在docs/phases/中期任务分解.md§二。 - 测试规模(M3 收口):
pnpm test26/26 tasks 全绿 ——session-api72 files / 757 passed | 13 skipped;vm-ui70 / 866 passed(M2 收口 806 ⇒ 净增 60);challenge-schema8 / 268 passed;challenge-compiler9 / 127;admin9 files / 87 passed(2 skipped);web-component9 / 82;embed-runtime4 / 62;plugin-dev2 / 28;session-core28;react-wrapper1 / 8。pnpm build13/13、typecheck18/18、lint:deps1357 模块 / 4300 依赖零违规、lint:deps:self-test20 组边、scan:public0 违规 / 3 条既有豁免、fixtures:manifest --check254 一致、smoke:contract21 Schema / 67 接受 / 143 拒绝 / 254 摘要比对、test:rust13 target 全ok、真机 axe 9/9 面 0 违规;全部 exit 0。未实测 / 环境阻断(明文登记,不得视为通过):test:coverage(完整形态)、test:miri、fuzz:smoke、全量 E2E、test:compose(本机镜像不可重建)、E2E_MATRIX/ CIe2e-matrix(webkit 12 格红 +ts-gate间歇红)。
- 缺陷 = 窗高下限无依据(工作区窗口被压扁):宽度侧下限是严格推导的(
58ch × 7.8px⇒MIN_COLUMN_WIDTH= 452.4px = 十六进制行不折行),高度侧却是一个无推导的裸9rem(= 144px)。真机实测(中宽 P1、嵌入形态、列高 656px):4 窗列每窗 146px(贴着 144 + 上下边框),而面板 chrome(标题栏 26 + 字节视图边框 1 + 工具区 133.3 + 列头行 21.8)实测 182.1px ⇒ 该下限连一行字节都装不下。因下限存在(并非缺失),它一路躲过了 axe 9/9 面与全套 E2E —— 「下限有无依据」比「有无护栏」隐蔽得多。 - 修法:
MIN_ROW_HEIGHT_PX = ceil(182.1 + N(=4) × 20.8) =266px(packages/vm-ui/src/workspace/layout-divider.ts:53,由常量插值、无魔数);推导输入与 N = 4 的理由(= 一次read的 32 字节跨度,MVP 栈帧教学闭环)登记在layout-presets.ts的「块轴(高度)阈值推导」段,与宽度侧同法。columnMinHeightPx(heights) = max_i(266 / ratio_i) + columnChromePx(n)(是比例的函数,等分时逐值退化为原算式 268/560/852/1144/2896)。 - 溢出列拖拽改像素语义:统一基准
F = 列高 − columnChromePx(n);rowHeightsAfterDrag按像素夹取 —— 富余列仍像素和守恒(既有不变量不变),被压窗贴住下限后多余位移转为列盒长高(列盒 ≡ 内容高、由滚动承载),被压窗永不低于下限。拖拽像素基准在pointerdown冻结:旧实现每步重读clientHeight,而列高在拖拽中会被下限顶高 ⇒ 同一手势把列盒顶高 137px、非相邻窗跟着变高(真机才暴露)。 - 伴生缺陷(同批修复):字节视图「特殊显示」列的 Lit 模板把字面换行写在声明了
white-space: pre的标签内部 ⇒ 每个 cell 渲染 2 个行盒 ⇒ 数据行实测 187.17px(应为 20.8px)。这与 M2 登记的「指令视图每行 113px」是同族缺陷,而字节视图这一处当时被漏掉(M3 只修了指令视图)。修后真机:普通数据行 20.8px ✔;挂跳转链的行 93.19px(= 20.8 + 72.39 跳转链块)、挂寄存器标注的行 43.59px —— 两者余高归因到别的组件,非本族产物。同族第二实例 =src/views/instruction/sm-instruction-view.ts:681的.jump-target按钮(继承.row-text的pre-wrap),一并修复。机检 =test/render/pre-whitespace-family.test.ts(9 例;源码文本扫描 + CSS 继承语义 + 跨文件作用域「双向一跳」;修复前临时回退实测 3 条真实违规必红:两个 span + 该 button)。 - 门禁读数:
e2e/workspace-layout.spec.ts9 例 +e2e/workspace-windows.spec.ts3 例 = 12 passed / 0 failed(原 8 例逐字未改、断言零放宽,新增第 9 例 = 溢出列 regime:被压窗≥ MIN_ROW_HEIGHT_PX、非相邻窗不动、列盒增量 ≡ 被增大窗增量);vm-ui 71 files / 895 passed(M3 收口 70 / 866);typecheck /scan:public/ eslint /lint:deps(1371 模块 / 4329 依赖)全部 exit 0。 - 诊断订正(留档,勿沿用错判):①「高度下限只在拖拽路径使用、未落到布局」错 —— 下限一直生效(
.tab-panel { min-block-size }),缺陷是取值无依据;②「flex-basis: 0+ 比例和恒 1 ⇒ 自由空间恒为 0 ⇒ 拖拽数学上不可能」错 —— 真机实测列 0 富余 824px、拖拽确实生效(只是只兑现 7.5px)。两条 E2E 失效的真成因 = 266px 下限把 3 窗列顶到 852px ⇒ 工作区 1163.3px、页面高 1322px ⇒ 交互几何落到 900px 视口之外,浏览器对视口外坐标不命中页面内容(event.target === html)⇒ 组件收不到事件。产品侧无解(不等式:需条带顶 < 399px,实测菜单 + 简介 + 状态行 = 294px ⇒ 条带顶 453px;旧下限 144px 时该点只剩 11px 余量 ⇒ 旧用例是靠 11px 侥幸绿的),修法落在 E2E 夹具(拖拽前把起点与终点一起滚进视口,真实用户同样先滚动;零断言变化)。 - dev 壳陷阱(已加固,勿回退):
apps/plugin-dev/vite.config.ts的server.watch.ignored含**/*.tmpdir/**/.tmp/**—— 写文件工具与编辑器的原子写会留下.<name>.<pid>.<guid>.tmpdir/,Windows 对该目录内文件的watch返回 EBUSY 会让 Vite 的 FSWatcher 整个进程退出(实测三次)。按前缀匹配会漏(见过._…tmpdir与.…tmpdir两种前缀),必须按后缀.tmpdir匹配。dev-only,零生产影响。 - 本次新登记的遗留 5 项(D-API-152 修复面之外):见
docs/phases/中期遗留清单.md§十 与docs/phases/中期验收评审.md§六 #33 ~ #37(编号双射)—— 几何 / 可读性无门禁(#33);拖拽落点出视口即不可达、缺拖拽中自动滚动(#34);开发壳.tab-area无高度上限(#35);MIN_COLUMN_WIDTH的「十六进制行不折行」前提在现有三档列宽下不成立 —— 字节视图自带14rem侧栏,列内实际只有 ≈220px,免折行需列宽 ≈1000px(#36,建议优先处置:该推导表会让依据它的人得出错误结论);宿主未给工作区定高 ⇒ 条带自身纵向滚动在两种已发布形态下都不会出现(#37)。 - 未实测(明文登记,不得视为通过):
test:coverage完整形态、test:miri、fuzz:smoke、全量 E2E、E2E_MATRIX(webkit 12 格)、test:compose(本机镜像不可重建)。
本节是「已落地」的事实段。其下 §在途改版 一节保留为需求与裁决的留档**(其「实现未落地」的表述已失效,仅作历史依据)。 权威入口:
docs/phases/分发改版与UI重设计任务分解与接手入口.md(WP-90~99 排期 + 铁律 + 门禁 + 陷阱);决策单一来源 =docs/develop/decisions-分发改版与UI重设计.md(D-UI-17 / D-LT-15)。
- 已落地范围 = WP-90 ~ WP-99(全部完成,本阶段无未开工项)。退役面的物理删除已执行完毕(2026-09-28 复核:
packages/embed-runtime|web-component|react-wrapper、apps/plugin-dev、docs/contracts/嵌入协议.md均不在磁盘上)。 - 收口三件(2026-09-28,WP-97 + WP-96/98 复核):① WP-96 退役面物理删除已落地(提交
9fbb78b);② WP-97 真机复跑 =E2E_MATRIX=1 pnpm --filter @stackmaster/page-app test:e2e51 passed / 24 skipped / 0 failed(三引擎逐格同形:chromium 23/2/0、firefox 14/11/0、webkit 14/11/0;axe 九面归档apps/page-app/e2e/reports/axe/2026-09-28/run-1/),并出 #34 / #35 / #37 结案判定(依据 =docs/phases/中期验收评审.md§六·三 与清单 §三 / §十);③ WP-98 用户文档已回填(f247b48/7867232)。 - ⚠ 口径(不得误读):WP-97 的这三引擎绿不覆盖遗留 #1 的握手面 —— page-app 的 E2E 用
page.route("/sessions")桩,WSS 升级与 Cookie 呈递未发生;该面唯一载体 =e2e/launch-chain.spec.ts(真拓扑),本机 Docker 引擎不可达 ⇒ 带E2E_LAUNCH_CHAIN=1实跑 = 2 skipped(非通过)。⇒ #1 仍为「失去载体」,不得记为「已修复」;新增未实测登记 = 「同源形态的 webkit 认证 WSS 握手」。 - 契约面(WP-90):启动票据族 ——
LaunchTicketRequest(恰两键challengeId/version)与LaunchTicketResponse(恰两键launchUrl/expiresAt),LAUNCH_TICKET_PROTOCOL_VERSION = 1+ 独立$id命名空间…/schemas/launch-ticket/v1;票据是不透明持有证明(≥128 bit CSPRNG,base64url 22 字符),不签发、不解析 claims ⇒ 无 claims Schema。会话动作协议递增为 v2(N-1 窗口[2, 1]):create_sessionpayload 恰两键{challengeId, challengeVersion},embedToken/embedSessionId退场;新增启动授权凭证 claimsLaunchGrantClaims(恰六字段,无sessionId/embedSessionId;server-only,$id归会话动作协议族)。读数:fixtures:manifest289 条(254 → 289);smoke:contract24 Schema / 70 接受 / 162 拒绝 / 289 摘要比对。 - 换票授权链(D-LT-5,WP-91):
POST /auth/launch-tickets(复用hostBackendTokenMatches,单份实现;租户只由凭证 ×SESSION_API_HOST_TENANTS白名单派生;面未启用 / 题目不存在均 404 同形;限流维度launch_ticket_rate,键rate:{锚租户}:launch_tickets)→201 {launchUrl, expiresAt}(票据只在launchUrl内);GET /app/c/:challengeId/:version?t=<ticket>(换票)=Sec-Fetch-Mode: navigate校验 → Redis Lua CAS 原子消费(键域launch:{jti},fail-closed;Redis 不可用 ⇒ 503store_unavailable,不降级)→ 绑定逐字比对 → 签发起动授权凭证(Set-Cookie: sm_launch_grant,Path=/sessions,HttpOnly + SameSite=Strict)→ 302 到不含票据的干净路径 +Cache-Control: no-store+Referrer-Policy: no-referrer;换票不建会话、不调issueSessionCredential;失败 = 401 统一形态(三态逐字节同形)。create_sessionv2 以该凭证 Cookie 为授权来源(身份只由「签名 claims ×launchGrant:{jti}记录」派生;payload 仅导航信息且必须逐字一致;凭证 GETDEL 单次消费,重放 = 401);通过后才建会话。userId= 配置键SESSION_API_LAUNCH_USER_ID(缺省launch-anon) ⇒ 同一部署下 userId 相同,userId维度的统计与配额不可分(显式接受的代价;sessionId仍逐次唯一,成绩可分)。/auth/embed-tokens端点已退役(v1 分支函数保留,物理删除归 WP-96)。新增配置键:SESSION_API_LAUNCH_TICKET_TTL_SECONDS(缺省 300 / 天花板 3600)、SESSION_API_LAUNCH_TICKET_ISSUANCE_PER_MINUTE、SESSION_API_LAUNCH_USER_ID、SESSION_API_PUBLIC_ORIGIN、SESSION_API_PAGE_APP_DIR。新增 Redis 键域:launch:{jti}、launchGrant:{jti}(均已登记)。 - 日志脱敏(WP-91):
redactRequestUrl剥离整个查询串(参数名漂移免疫)⇒?t=不落日志;有运行时断言机检(捕获真实日志输出断言零票据)。 - UI 改版 A/B(WP-93/94):整页布局(
100dvh,不依赖宿主给高度,溢出不上浮到文档层);左右两分(repeat(2, minmax(452.4px, 1fr)),固定 1:1、无 gap / 无 border / 无 divider、无分界拖拽);右半侧 = payload 搭建窗口(固定,同一元素实例);左半侧 = 视图管理窗口(上下两个可见视图位、纵向堆叠、overflow-y: auto+scroll-behavior: smooth);类型名移入视图内左上角(.tab-bar已删);列表按钮 =<details>+ 原生 checkbox 勾选 + 列表内拖拽 /Alt+↑↓重排;Ctrl+↑↓在左半侧捕获 +preventDefault+ 不环绕;主题单值化 = 21 枚 token 名保留、收敛为终端一套、锚:root级缺省(未设锚即终端),178 处回退值中 162 处删除浅色回退 + 无豁免的严格回退值机检。布局模型已重构 = 「一条有序视图列表 +visible勾选 + 焦点」;不变量 = 每登记类型恰一次无重无漏 / id ≡ 类型键 / 焦点唯一 / 无「隐藏即消失」语义(D-MP-1 不修订)。已删除:layout-camera.ts/layout-divider.ts、P0/P1/P2 预设与阈值表、列宽五档、分隔条与其方向键、三类拖拽落点、重置布局、窗高 / 列高下限(D-API-152 的几何面已废止,条目本身留档不删)。 - 页面应用与托管(WP-92):新增
apps/page-app(与 API 同源的独立页面应用,application build;scripts/sync-vm-ui-dist.mjsprebuild 同步 vm-ui 产物到public/vm-ui/,运行期按 URL 动态 import,不静态依赖 vm-ui);session-api 以@fastify/static+wildcard: false托管dist/(只为真实文件注册精确路由 ⇒ 结构上不存在能吞掉/app/c/:id/:version的通配处理器;注册序另有源码面断言)。新增依赖@fastify/static@^10.1.4。 - 门禁改造(WP-95):+ 几何护栏(
apps/page-app/e2e/geometry-guard.spec.ts,四视口 × 三引擎 27 例,逐条断言 D-UI-2 四条,量真实渲染结果、不断言任何已废止常量);+ E2E 改页面分发(launch-chain夹具:签发 → 顶层导航 → 302 后地址栏无?t=→connected;E2E_LAUNCH_CHAIN=1开启);+ axe 面矩阵重定义为 page-app 九面(1440×900 default/list-open/instruction/payload、1024×768 default/list-open、768×900 default、375×667 default/payload)+ 归档确定性 / 同日 run-N 不覆盖 / 未扫描面清单 / 两栏分列(遗留 #13 / #14 的修法);+E2E_MATRIX挂到 page-app。apps/plugin-dev/e2e/RETIRED-SURFACE.md逐文件登记退役面处置。 - 门禁读数(主控本机实测,2026-09-18/19):
pnpm build14/14;pnpm typecheck19/19;pnpm lintexit 0;lint:deps1603 模块 / 4738 依赖零违规;lint:deps:self-test20 组边;fixtures:manifest --check289 一致;smoke:contract24 / 70 / 162 / 289;scan:public0 违规 / 3 豁免;page-app test23/23、page-app test:e2e23 passed / 2 skipped、E2E_MATRIX=151 passed / 24 skipped / 0 failed。pnpm test --continue= 27/28,唯一红 =@stackmaster/web-component#test1 例(该例断言本包自己发出的 v1create_session,结构性,随 WP-96 物理删除该包消解;不得为它变绿而把该包移植到新链)。 - ⚠ 落地期新发现的 P0 产品缺陷(遗留 #39)—— 已于 WP-95a 修复:几何护栏首跑即抓出真实缺口 —— 字节视图的「完整可见数据行」在窄档为 0(chromium/firefox
1024→0 / 768→0;webkit1024/768/375全0),D-UI-2 的N ≥ 1红线在 9 / 12 个「引擎 × 视口」格上不成立。根因(逐层):①byte-tab.ts的 VMA 侧栏折叠用的是视口媒体查询@media (max-width:40rem),而字节视图位于半页槽位 ⇒ 1024/768 档视口 > 640px 故不折叠,视图列仅 280 / 220px = 媒体查询 vs 容器查询的类别错误;②.byte-row第三轨1fr的最小值是auto⇒ 轨宽溢出时无法收缩(列头折 4 行、21.8 → 84.2px 的直接机制);③<input size=20>默认 ≈177px 把工具区顶到多行 ⇒ chrome 反超视图位高。 - WP-95a 修法(已落地,不动工作区几何、不改任何常量数值):① 折叠判定改为容器查询
@container (max-width:40rem)(:host加contain/container-type: inline-size);② 第三轨1fr → minmax(0,1fr)+ 表头行nowrap+ 工具区 input 收口14ch/ 两行nowrap/@container紧凑(工具区 166.3 → 92.3px);③ 降级形态 = 折叠 VMA 侧栏(理由:它是可折叠辅助面,区域入口在.region-select上有等价副本;实测该选择下四档全部 ≥ 5 行,未选横向滚动)。 - 修复后真机读数(完整可见数据行,12 / 12 格) —— chromium
4→**8** / 0→**5** / 0→**7** / 1→**5**;firefox7→**9** / 0→**5** / 0→**9** / 1→**5**;webkit6→**8** / 0→**5** / 0→**8** / 0→**4**(视口序 1440×900 / 1024×768 / 768×900 / 375×667)。⇒N ≥ 1红线 12/12 满足,且 12/12 达N = 4目标;chrome 降至 chromium 114.09 / firefox 116.1 / webkit 128.09(修前 157~410);KNOWN_GEOMETRY_GAPS收敛为空表(登记机制保留在场)。主控独立复跑geometry-guard.spec.ts= 9 passed(逐格读数与上表一致)。 - 规格随之改述(不改数值):
N分档 ——N ≥ 1红线全档无条件,N = 4仅免折行宽度档(1440 类)要求;D-UI-5 的452.4px维持数值但改述含义 = 「十六进制行本身不折行」,不含 字节视图自带的14rem侧栏;左半侧 < ≈766px 时字节视图必有某种降级 = 已接受的代价;「抬到 ≈766px」被否决(会把表达力问题换成准入问题,并使 1024 / 1366 笔记本不可用)。遗留 #36 随之结案(其实测 ≈766px 升格为N = 4的阈值依据)。决策 = D-API-162 ~ D-API-166;权威文本 =decisions-分发改版与UI重设计.md§四·补。 - 订正留档(勿沿用错判):WP-93/94 曾报「字节列表可视高 83px ⇒ ≥ 4 行达标」——它测的是容器高而非真实行盒(首行实测行高 43.59px)⇒ 该结论从未成立;教训 = 测「容器」不等于测「内容」(与遗留 #33 同源)。
- 未实测(明文登记,不得视为通过):
test:integration/test:compose(本机 Docker 引擎不可达,dockerDesktopLinuxEngine管道缺失)/test:coverage完整形态 /test:miri/fuzz:smoke/ 启动地址链真机 E2E(E2E_LAUNCH_CHAIN=1,实测 2 skipped 非通过)/ 同源形态的 webkit 认证 WSS 握手(WP-97 新增登记)/ CIe2e-matrix(job 已切到 page-app,本机只复核 artifact 面)。 - 遗留台账(2026-09-28 WP-99 收口):总数 41 → 43;仍开放 23(P0 = #21 / #27 / #30);已闭环 15(含 #38 结案、#39 已修复、#40 已闭环 —— 处置提交
0966160);已定案并实施 1 / 载体退役 3(#1 / #15 / #16)/ 环境事实 1;新增 #42(文档一致性无机检)/ #43(列表内重排无真机门禁);#30 的「由改版消解」改判已被推翻(惰性宿主仍在 DOM 链)。编号双射{1..43},判定与订正清单见评审 §八。
⚠ 本节已降级为「历史留档」(需求与裁决正文见
docs/develop/decisions-分发改版与UI重设计.mdD-UI-17 / D-LT-15 与任务分解文件)。不要据本节判断产品现状。仍有效的纪律:
- 可读性纪律不得误读:废止窗高 / 列高下限 ≠ 放松可读性 —— 本版把「压缩以适配」整条移除,改由「左半侧纵向滚动 + 每个视图位有确定高度」满足;判据 = 若实现出现「视图被压到装不下一行字节」即违反本条(历史反例:4 窗列每窗 146px vs 面板 chrome 182.1px;2026-09-19 新反例 = 遗留 #39:窄档 chrome 反超视图位高 ⇒ 完整可见数据行 0)。
- 退役 ≠ 现在可删(历史纪律;WP-96 已执行完毕):退役面已随 WP-96 物理删除(2026-09-28 复核五处均不在磁盘),原「在此之前不得据退役决定删代码 / 删契约 / 改 E2E 断言」的冻结期已由主控 2026-09-19 解除。
- 「宿主」三义项(不得混改):宿主页面(嵌 iframe 的第三方页面)退役,涉及「页面」处不得再用「宿主」二字;宿主 / 宿主平台(服务端集成方)保留(只与 API 交互,不与页面共享运行环境);宿主代码 / 宿主环境(底线第 4 条前半句)一个字都不许改。
| 主题 | 章节 |
|---|---|
| 产品定位、目标用户、边界与非目标 | 一、二、三 |
| 四信任域架构与运行时拓扑 | 5.2、5.3 |
| 技术选型与 ADR | 5.4 |
| Monorepo 结构与依赖方向 | 5.5 |
| 契约与版本化 | 5.6 |
| 数据持久化 | 5.7 |
| 部署、可观测、质量门禁 | 5.8 |
| VM 语义、状态模型、确定性 | 六 |
| 题目 DSL 与双包模型 | 七 |
| 页面分发、协议与部署(原「插件嵌入与协议」,2026-09-18 分发改版) | 八 |
| 权威判题与威胁模型 | 九 |
| 前端性能约束 | 十 |
| MVP 定义 | 十一 |
| 开发阶段 | 十二 |
| 测试与验收 | 十三 |