diff --git a/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.i18n.yaml b/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.i18n.yaml new file mode 100644 index 000000000000..8bfa1d82d3ab --- /dev/null +++ b/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.i18n.yaml @@ -0,0 +1,22 @@ +# Bilingual-pair consistency record for 2026-08-31-assistive-technology-headless-output.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.md +/agent-note-stable-headless-output-for-assistive-technology: + en: 6e9bcae87ea6daee + zh: 9ccd6b7b2178bba4 +/agent-note-stable-headless-output-for-assistive-technology/problem: + en: 10fb4193f3d09a51 + zh: f11dfdc8b429ae35 +/agent-note-stable-headless-output-for-assistive-technology/decision: + en: 3dccdfe707f0d1b3 + zh: 511272ead669f001 +/agent-note-stable-headless-output-for-assistive-technology/alternatives-considered: + en: a31183851911049d + zh: c162bbb4b8adacb2 +/agent-note-stable-headless-output-for-assistive-technology/consequences: + en: bda10e92e1fbe9b8 + zh: 1ea2f1c85a17f519 +/agent-note-stable-headless-output-for-assistive-technology/testing: + en: e7889aaaf7a64ad4 + zh: cee6444f5efa4142 diff --git a/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.md b/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.md new file mode 100644 index 000000000000..e00307eb26a3 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.md @@ -0,0 +1,39 @@ +# Agent Note: Stable headless output for assistive technology + +Status: implemented + +English | [中文](2026-08-31-assistive-technology-headless-output.zh.md) + +## Problem + +The product one-shot command streamed every provider reasoning delta to stderr. A screen reader could therefore announce token-sized fragments for the duration of a task, while carriage returns, terminal escape sequences, BEL, or other controls in model text could trigger redraw behavior or unwanted terminal feedback. The command also exposed only an unversioned final-text projection, so accessibility conformance automation had no stable process result to inspect after the durable turn boundary. + +Assistive-technology use and versioned accessibility verification require a stable final result, not a second CLI application or a Session-event stream. + +## Decision + +`dsh --profile headless` owns two output flags through its existing app command-line provider. `--accessibility` selects a text presentation with no color, spinner, cursor movement, updating counter, or reasoning deltas. It writes `dsh: task started` and exactly one durable terminal-state line to stderr. The final assistant text remains on stdout after terminal escape sequences, C0/C1 controls, carriage-return redraws, and BEL are removed; newlines and tabs remain. Error diagnostics use the same sanitizer and collapse to one line. Default text mode retains its existing reasoning stream and final-text behavior. + +`--output-format json` writes exactly one newline-terminated `dsh-headless-result` object to stdout after the owned Session interval is flushed and writes no outcome diagnostics to stderr. Version `1.0.0` carries `type`, `schemaVersion`, `status`, `text`, and `reason`. `status` is `completed` only for a durable completed turn and `failed` otherwise. `reason` projects completed, structured error, aborted cause kind, blocked, max-tokens, interrupted, and missing-turn outcomes; merge-extensible reasons become `{ "kind": "other", "name": }`. A direct runner failure becomes an `INTERNAL` error result. JSON mode suppresses reasoning independently of `--accessibility`; when both flags are present, JSON remains the sole output presentation. + +The mode establishes process-output properties, not assistive-technology compatibility evidence. Evidence for a named screen reader, terminal, operating system, speech configuration, and disabled-user workflow remains a separate recorded artifact. + +## Alternatives considered + +**Make low-noise output the default.** Rejected because existing terminal users and diagnostics deliberately consume streamed reasoning, and changing stdout or stderr without an explicit flag would break the product command's current behavior. + +**Keep reasoning and add periodic accessible summaries.** Rejected because token fragments would still dominate a screen reader queue and could expose sensitive reasoning in logs. One start line and one durable terminal line are bounded and correspond to authoritative state. + +**Restore the former CLI demo or stream every Session event as JSON.** Rejected because that would recreate a second application or enlarge the public protocol beyond the current consumer. Accessibility automation needs one final result, while SDK and ACP already own persistent machine control. + +**Treat sanitized output as proof of screen-reader support.** Rejected because automated bytes cannot observe speech order, focus, terminal settings, user comprehension, or independent task completion. + +## Consequences + +Accessibility mode intentionally changes model text that contains terminal control bytes; users who need the exact original bytes use default text or JSON. JSON strings retain their content through JSON escaping and never become terminal controls on the output line. Scripts can parse one format without combining stdout and stderr, and incompatible future result changes require a new schema version. + +The product now has a keyless, versioned CLI output target for accessibility conformance checks, but real NVDA, JAWS, Narrator, VoiceOver, and Orca evidence remains outstanding until named testers record it. The default command continues to carry the privacy and verbosity cost of reasoning output by explicit compatibility choice. + +## Testing + +Package tests pin default compatibility, reasoning suppression, control sanitization, bounded status lines, every built-in non-completed outcome, direct failures, single-line JSON, schema version, command parsing, help, and invalid formats. The built `dsh` acceptance starts the shipped headless profile against the mock provider and verifies default, accessibility, and JSON presentations through the published entry. diff --git a/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.zh.md b/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.zh.md new file mode 100644 index 000000000000..1dc627e07a46 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-08-31-assistive-technology-headless-output.zh.md @@ -0,0 +1,39 @@ +# Agent Note: 面向辅助技术的稳定 headless 输出 + +Status: implemented + +[English](2026-08-31-assistive-technology-headless-output.md) | 中文 + +## Problem + +产品的一次性命令会把提供方的每个推理增量流式写入 stderr。读屏软件可能因此在整个任务期间播报 token 粒度的片段,而模型文本中的回车、终端转义序列、BEL 或其他控制字符可能触发重绘或非预期终端反馈。该命令还只暴露没有版本号的最终文本投影,因此无障碍符合性自动化无法在持久化轮次边界之后检查稳定的进程结果。 + +辅助技术使用和版本化无障碍验证需要稳定的最终结果,不需要第二个 CLI 应用或 Session 事件流。 + +## Decision + +`dsh --profile headless` 通过既有应用命令行提供方拥有两个输出 flag。`--accessibility` 选择一种没有颜色、spinner、光标移动、持续更新计数器或推理增量的文本展示。它向 stderr 写入 `dsh: task started`,再恰好写入一个持久化终态行。最终 assistant 文本仍在移除终端转义序列、C0/C1 控制字符、回车重绘与 BEL 后写入 stdout;换行与制表符保留。错误诊断使用同一清理器并折叠为一行。默认文本模式保留既有推理流与最终文本行为。 + +`--output-format json` 在所属 Session 区间 flush 后,向 stdout 恰好写入一个以换行结束的 `dsh-headless-result` 对象,且不向 stderr 写入结果诊断。`1.0.0` 版本包含 `type`、`schemaVersion`、`status`、`text` 与 `reason`。只有持久化完成的轮次将 `status` 设为 `completed`,其余均为 `failed`。`reason` 投影 completed、结构化 error、aborted 原因种类、blocked、max-tokens、interrupted 与缺少轮次的结果;merge-extensible 原因变成 `{ "kind": "other", "name": }`。runner 直接失败时得到一个 `INTERNAL` error 结果。JSON 模式独立于 `--accessibility` 抑制推理;两个 flag 同时出现时,JSON 仍是唯一输出展示。 + +该模式建立进程输出属性,而不是辅助技术兼容性证据。针对具名读屏软件、终端、操作系统、语音配置与残障用户工作流的证据,仍须作为独立制品记录。 + +## Alternatives considered + +**把低噪声输出设为默认值。**不采用,因为现有终端用户与诊断会刻意消费流式推理;在没有显式 flag 时改变 stdout 或 stderr 会破坏产品命令的当前行为。 + +**保留推理并添加定期无障碍摘要。**不采用,因为 token 片段仍会占满读屏队列,也可能在日志中暴露敏感推理。一个开始行与一个持久化终态行具有数量上界,并对应权威状态。 + +**恢复原 CLI demo 或把每个 Session 事件流式输出为 JSON。**不采用,因为这会重新创建第二个应用,或让公开协议超出当前消费方所需。无障碍自动化只需要一个最终结果,持久机器控制已由 SDK 与 ACP 负责。 + +**把经过清理的输出当作读屏支持证明。**不采用,因为自动化字节无法观察语音顺序、焦点、终端设置、用户理解或独立完成任务。 + +## Consequences + +无障碍模式会刻意改变包含终端控制字节的模型文本;需要精确原始字节的用户使用默认文本或 JSON。JSON 字符串通过 JSON 转义保留内容,不会在输出行上变成终端控制。脚本可以解析一种格式而不必拼接 stdout 与 stderr;未来不兼容的结果变更需要新的 schema 版本。 + +产品获得了一个可供无障碍符合性检查使用的无密钥、版本化 CLI 输出目标,但具名测试者记录之前,真实 NVDA、JAWS、Narrator、VoiceOver 与 Orca 证据仍然缺失。出于显式兼容选择,默认命令继续承担推理输出的隐私与冗长成本。 + +## Testing + +包级测试固定默认兼容性、推理抑制、控制字符清理、有界状态行、每种内建非完成结果、直接失败、单行 JSON、schema 版本、命令解析、帮助与无效格式。构建后的 `dsh` 验收通过发布入口,以 mock 提供方启动随附 headless profile,并验证默认、无障碍与 JSON 展示。 diff --git a/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.i18n.yaml b/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.i18n.yaml new file mode 100644 index 000000000000..13c93f91aaac --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-09-07-rc1-accessibility-core-migration.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.md +/agent-note-version-pinned-accessibility-core-migration: + en: 245b138de031c204 + zh: 8cc0f81b56584edb +/agent-note-version-pinned-accessibility-core-migration/problem: + en: 3901a4a2216b852f + zh: 870eb122d2d16f97 +/agent-note-version-pinned-accessibility-core-migration/decision: + en: 6e5efb0ce0793ac8 + zh: b0bd1abe0ffb61fb +/agent-note-version-pinned-accessibility-core-migration/alternatives-considered: + en: d47a133a52f654b5 + zh: a3c91dc01b443ef4 +/agent-note-version-pinned-accessibility-core-migration/consequences: + en: 35ba1d5174f6b780 + zh: b260f84540a103b2 diff --git a/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.md b/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.md new file mode 100644 index 000000000000..ea94c4067d9d --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.md @@ -0,0 +1,33 @@ +# Agent Note: Version-pinned accessibility core migration + +Status: implemented + +English | [中文](2026-09-07-rc1-accessibility-core-migration.zh.md) + +## Problem + +The released accessibility candidate and its automated evidence describe DSH `0.1.2-rc.1`. The official `0.1.5-rc.2` line changes the right sidebar, menu placement, Assistant stream events, persisted fixture generations, and dependency packaging. Reusing old evidence would claim coverage for different code; copying the old layout would discard current product behavior. + +## Decision + +The migration starts at official tag `dsh-v0.1.5-rc.2`. Current upstream owns Session and layout state. Component owners retain native semantics, keyboard composites, focus containment and restoration, quiet live announcements, and bounded headless text or JSON output. + +The new right sidebar owns expansion; the outer frame only reports its geometry. Its named splitter resizes with Arrow keys, selects its width limits with Home and End, and restores the default width with Enter. Explicit expand/collapse actions transfer focus between the same Session's controls without stealing a newer focus owner. A hidden right pane is inert. The left splitter remains keyboard-restorable while collapsed. Narrow frames remove width transitions so newly focused rail controls cannot scroll a clipped intermediate column. + +The model menu waits for portal placement before moving focus. File actions retain separate disclosure and preview controls; previews now open the current right sidebar, not the former native file-open path. Accessibility fixtures use the current recorded `session.v3.jsonl` without modifying older generations. + +## Alternatives considered + +**Change package version strings only.** Rejected because compilation cannot prove portal focus, keyboard navigation, hidden controls, or the assembled preview route. + +**Restore the old Details panel.** Rejected because it would replace upstream ownership and remove current sidebar behavior. + +**Move required semantics into the companion plugin.** Rejected because post-render diagnostics cannot reliably reconstruct component-owned focus and durable Session boundaries. + +**Reuse the earlier candidate's browser record.** Rejected because evidence identifies exact product revisions, not a reusable compatibility label. + +## Consequences + +The fork and companion must pass their own checks and a clean, exact-revision, three-engine assembled run before publication. The earlier candidate and its evidence remain historical artifacts. The versioned browser-evidence decision remains active; this migration changes its product baseline, not its protocol or the distinction between emulated and real assistive technology. + +Unit, browser, geometry, contrast, motion, and headless checks do not establish VoiceOver, NVDA, braille, switch input, real zoom, or independent disabled-developer completion. Human evidence remains open. New sidebar viewers and the separate Desktop surface require their own task evidence; the legacy P0 suite is not a claim of exhaustive accessibility. diff --git a/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.zh.md b/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.zh.md new file mode 100644 index 000000000000..5709e5bd5dd2 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-07-rc1-accessibility-core-migration.zh.md @@ -0,0 +1,33 @@ +# Agent Note: 固定版本的无障碍核心迁移 + +Status: implemented + +[English](2026-09-07-rc1-accessibility-core-migration.md) | 中文 + +## 问题 + +已发布的无障碍候选版本及其自动化证据描述的是 DSH `0.1.2-rc.1`。官方 `0.1.5-rc.2` 修改了右侧栏、菜单定位、Assistant 流事件、持久夹具代际和依赖打包。复用旧证据会把不同代码错误地声明为已覆盖;复制旧布局则会丢弃当前产品行为。 + +## 决策 + +迁移从官方标签 `dsh-v0.1.5-rc.2` 开始。Session 与布局状态以当前上游为准。组件所有者保留原生语义、键盘复合控件、焦点约束与恢复、低干扰实时播报,以及有界的 headless 文本或 JSON 输出。 + +新的右侧栏拥有展开状态;外层框架只报告其几何信息。具名分隔条通过方向键调整宽度,Home 与 End 选择宽度边界,Enter 恢复默认宽度。显式展开或收起会在同一 Session 的控件之间转移焦点,不抢占更新的焦点所有者。隐藏的右侧面板进入 inert 状态。左侧分隔条在收起时仍可通过键盘恢复。窄框架移除宽度过渡,避免新聚焦的轨道控件滚动尚被裁切的中间栏宽。 + +模型菜单等待 portal 定位完成后再移动焦点。文件操作继续区分展开详情与预览控件;预览改为打开当前右侧栏,而不是原来的系统文件打开路径。无障碍夹具使用当前已录制的 `session.v3.jsonl`,不修改较早代际。 + +## 考虑过的替代方案 + +**只修改包版本字符串。** 否决,因为编译不能证明 portal 焦点、键盘导航、隐藏控件或组装后的预览路径。 + +**恢复旧 Details 面板。** 否决,因为这会替换上游的所有权并删除当前侧栏行为。 + +**把必需语义移到配套插件。** 否决,因为渲染后的诊断无法可靠重建组件拥有的焦点和持久 Session 边界。 + +**复用此前候选版本的浏览器记录。** 否决,因为证据标识精确产品修订,不是可复用的兼容性标签。 + +## 后果 + +分叉与配套插件必须通过各自检查,以及干净、固定精确修订的三引擎组装测试,才可发布。此前候选版本及其证据继续作为历史产物保留。版本化浏览器证据决策继续有效;本次迁移改变其产品基线,不改变协议或模拟环境与真实辅助技术的区分。 + +单元、浏览器、几何、对比度、动效和 headless 检查不能证明 VoiceOver、NVDA、盲文、开关输入、真实缩放或残障开发者独立完成任务。真人证据继续开放。新增侧栏查看器与独立 Desktop 表层需要各自的任务证据;既有 P0 套件不是全面无障碍声明。 diff --git a/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.i18n.yaml b/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.i18n.yaml new file mode 100644 index 000000000000..bdf711a7d3c9 --- /dev/null +++ b/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-08-31-versioned-core-browser-accessibility-evidence.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.md +/agent-note-versioned-core-browser-accessibility-evidence: + en: 9903cfb9b69bbd4c + zh: 6780d850094147e9 +/agent-note-versioned-core-browser-accessibility-evidence/problem: + en: 0c7978e9e852bb26 + zh: 451e63e237196954 +/agent-note-versioned-core-browser-accessibility-evidence/decision: + en: 2da2894707a929a5 + zh: dd00bbe5f52588cc +/agent-note-versioned-core-browser-accessibility-evidence/alternatives-considered: + en: 02bd9c8a4c487ff4 + zh: 5fc71334f664cc3a +/agent-note-versioned-core-browser-accessibility-evidence/consequences: + en: 31e6d7176e050664 + zh: 3c19207be2c39b79 diff --git a/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.md b/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.md new file mode 100644 index 000000000000..26c0c623480c --- /dev/null +++ b/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.md @@ -0,0 +1,33 @@ +# Agent Note: Versioned core browser accessibility evidence + +Status: implemented + +English | [中文](2026-08-31-versioned-core-browser-accessibility-evidence.zh.md) + +## Problem + +The assembled Web accessibility suites exercised real DSH routes, but their terminal exit status did not identify the exact product revision, enumerate the required task checks, distinguish an unsupported engine check from a pass, or preserve the boundary between headless browser assertions and human assistive-technology evidence. The focus-environment test also sampled only the shell and Settings. Extending it across the P0 routes exposed a real narrow-layout regression: after collapsing an expanded sidebar, keyboard focus remained on the renamed Open sidebar toggle while the desktop rail-entry translation painted that control under the main column for one animation interval. + +## Decision + +The P0 browser lane now covers the named shell and separators, Sessions tree and search, Session views, conversation transcript and disclosure, Trajectory, composer draft editing, menus, Settings, and Full access risk admission. A fresh 320 CSS-pixel page samples focused controls across those routes for viewport intersection, `:focus-visible`, and topmost paint; 640 and 320 CSS-pixel runs retain the 200% and 400% equivalent reflow checks. Reduced-motion runs and Chromium forced-color emulation remain separate required checks. + +On viewports no wider than 600px, or whenever reduced motion is requested, sidebar collapse settles the 56px rail in a layout effect and suppresses the desktop rail-entry animation. The focused toggle therefore occupies the visible rail before paint. Wider motion-enabled layouts keep the existing crossfade and translation. + +`pnpm run test:web:accessibility` remains the dirty-worktree-friendly diagnostic command. A clean committed candidate uses `pnpm run test:web:accessibility:evidence`, which rebuilds and runs all three engines before emitting `.artifacts/accessibility/core-browser-evidence.json`. The report uses `dsh-non-at-browser/1.0.0-draft`, identifies the core consumer as `dsh-core-browser-non-at`, pins the root package version and full 40-character Git revision, records host and engine versions, maps nine cataloged P0 task IDs to stable check IDs, and carries fixed limitations. The runner rejects a dirty worktree, invalid revision, failed Vitest process, missing or duplicate required title, a required skip, or a forced-color check reported as passed by an unsupported engine. A subset record is `partial`; only Chromium, Firefox, and WebKit together are `pass`. + +Pull-request CI replaces the former diagnostic invocation inside the existing required `node-24-accessibility` job with the versioned evidence command. It uploads the exact-revision JSON for seven days after success. This adds neither another job nor another browser run; it makes the already-required lane produce a reviewable record. + +## Alternatives considered + +**Treat the existing console summary as evidence.** Rejected because a copied `14 passed` line has no enforceable product identity, task inventory, engine capability boundary, or evidence limitations. + +**Call every zero-failure browser subset a pass.** Rejected because it would allow a Chromium-only run to stand in for the cross-engine contract and would turn unsupported forced colors into an implied pass. + +**Wait one animation interval before sampling narrow focus.** Rejected because the focused control is obscured during that interval for a real keyboard user. The product removes the inaccessible frame instead of teaching the test to ignore it. + +**Represent the automated record as screen-reader or WCAG conformance evidence.** Rejected because browser semantics and geometry do not observe spoken or braille output, real zoom, Windows High Contrast, or independent disabled-user task completion. + +## Consequences + +Each clean candidate can now produce a reviewable, schema-addressed record whose exact checks and limitations are stable across runs. Renaming or removing a required assertion deliberately breaks the evidence runner until the protocol mapping is reviewed. The stronger focus route guards the narrow-sidebar defect across all three engines, and unit tests retain both narrow-viewport and reduced-motion settle behavior. Evidence generation costs one build plus three serial assembled-browser runs and writes only to the ignored `.artifacts/` directory; required pull-request CI retains that small record without duplicating the job or build. Passing this gate improves automated product evidence but does not close the real VoiceOver, NVDA, Windows High Contrast, zoom, low-vision, or disabled-developer evidence rows. diff --git a/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.zh.md b/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.zh.md new file mode 100644 index 000000000000..ba6bf4e49d61 --- /dev/null +++ b/.agents/notes/implemented/testing/2026-08-31-versioned-core-browser-accessibility-evidence.zh.md @@ -0,0 +1,33 @@ +# Agent Note:版本化核心浏览器无障碍证据 + +Status: implemented + +[English](2026-08-31-versioned-core-browser-accessibility-evidence.md) | 中文 + +## 问题 + +组装后的 Web 无障碍套件会执行真实 DSH 路由,但终端退出状态没有标识精确产品 revision、枚举必需任务检查、区分引擎不支持与通过,也没有保留无头浏览器断言和真人辅助技术证据之间的边界。焦点环境测试此前也只采样应用壳与 Settings。把检查扩展到 P0 路由后暴露了一个真实的窄布局回归:从展开状态收起侧栏后,键盘焦点仍保留在已改名为“打开侧栏”的切换按钮上,但桌面轨道入场位移会在一个动画时段内把该控件绘制到主栏下面。 + +## 决策 + +P0 浏览器车道现在覆盖具名应用壳和分隔条、Sessions 树与搜索、Session 视图、对话 transcript 与 disclosure、Trajectory、合成器草稿编辑、菜单、Settings 和 Full access 风险准入。新建的 320 CSS 像素页面会在这些路由中采样获得焦点的控件,检查视口交集、`:focus-visible` 和最上层绘制;640 与 320 CSS 像素运行继续保留 200% 与 400% 等效重排检查。减少动态效果运行和 Chromium 强制颜色模拟仍是独立的必需检查。 + +当视口不超过 600px,或请求减少动态效果时,侧栏收起会在 layout effect 中完成 56px 轨道布局,并抑制桌面轨道入场动画。因此,获得焦点的切换按钮会在绘制前位于可见轨道中。更宽且启用动态效果的布局保留原有淡变与位移。 + +`pnpm run test:web:accessibility` 继续作为允许脏工作树的诊断命令。干净且已提交的候选版本使用 `pnpm run test:web:accessibility:evidence`;该命令会重新构建并运行三个引擎,然后生成 `.artifacts/accessibility/core-browser-evidence.json`。报告使用 `dsh-non-at-browser/1.0.0-draft`,以 `dsh-core-browser-non-at` 标识核心使用方,固定根包版本与完整 40 位 Git revision,记录宿主与引擎版本,把九个已登记 P0 任务 ID 映射到稳定检查 ID,并携带固定局限。脏工作树、无效 revision、失败的 Vitest 进程、缺失或重复的必需标题、必需检查被跳过,以及不支持强制颜色的引擎却把它报告为通过,都会被 runner 拒绝。子集记录为 `partial`;只有 Chromium、Firefox 与 WebKit 完整组合才是 `pass`。 + +拉取请求 CI 会在现有必需 `node-24-accessibility` 作业中,用版本化证据命令替换原先的诊断调用。成功后,精确 revision 的 JSON 会保留七天。这里不会增加新作业或再运行一遍浏览器,而是让原本已必需的车道生成可评审记录。 + +## 考虑过的替代方案 + +**把现有控制台摘要当作证据。** 已拒绝,因为复制出来的 `14 passed` 行没有可强制执行的产品身份、任务清单、引擎能力边界或证据局限。 + +**把每个零失败的浏览器子集都称为通过。** 已拒绝,因为这样会允许仅 Chromium 的运行冒充跨引擎契约,并把不支持的强制颜色检查变成暗示性通过。 + +**等待一个动画时段后再采样窄屏焦点。** 已拒绝,因为真实键盘用户会在该时段看到焦点控件被遮挡。产品会消除不可访问的帧,而不是教测试忽略它。 + +**把自动化记录表示为读屏或 WCAG 符合性证据。** 已拒绝,因为浏览器语义和几何检查没有观察语音或盲文输出、真实缩放、Windows 高对比度或残障用户独立完成任务。 + +## 后果 + +每个干净候选版本现在都能生成可评审、带 Schema 地址的记录,其精确检查与局限可跨运行保持稳定。重命名或删除必需断言会有意使证据 runner 失败,直到规程映射经过评审。更强的焦点路由会在三个引擎中守住窄侧栏缺陷,单元测试也保留窄视口和减少动态效果两种布局完成行为。生成证据需要一次构建和三次串行组装浏览器运行,并且只写入被忽略的 `.artifacts/` 目录;必需的拉取请求 CI 会保留这份小记录,而不重复作业或构建。通过此门禁会增强自动化产品证据,但不会关闭真实 VoiceOver、NVDA、Windows 高对比度、缩放、低视力或残障开发者证据行。 diff --git a/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.i18n.yaml b/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.i18n.yaml new file mode 100644 index 000000000000..dea4b4e1a828 --- /dev/null +++ b/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.i18n.yaml @@ -0,0 +1,22 @@ +# Bilingual-pair consistency record for 2026-08-31-alpha2-accessibility-core-migration.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.md +/agent-note-alpha-2-accessibility-core-migration: + en: 7054557d9e1ff87f + zh: 71cfb2d57e3ef507 +/agent-note-alpha-2-accessibility-core-migration/problem: + en: 362791eb0e291136 + zh: 120458159772565b +/agent-note-alpha-2-accessibility-core-migration/proposal: + en: 135e435e6aea1140 + zh: c6dc2fa5ca93b971 +/agent-note-alpha-2-accessibility-core-migration/alternatives-considered: + en: 15920428a1cd23b8 + zh: 781a07c516051e58 +/agent-note-alpha-2-accessibility-core-migration/acceptance-criteria: + en: cea536659fa505b0 + zh: a87b12e47a1040c3 +/agent-note-alpha-2-accessibility-core-migration/risks: + en: 0456793e3f6f847a + zh: c636df017faff30f diff --git a/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.md b/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.md new file mode 100644 index 000000000000..23273470f133 --- /dev/null +++ b/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.md @@ -0,0 +1,65 @@ +# Agent Note: Alpha.2 accessibility core migration + +Status: proposed + +English | [中文](2026-08-31-alpha2-accessibility-core-migration.zh.md) + +## Problem + +The verified accessibility candidate is bound to the `dsh-v0.1.2-alpha.1` product shape, while `dsh-v0.1.2-alpha.2` changes 1,604 paths from that official tag. The candidate changes 302 paths from its development base, and 128 of those paths overlap the official alpha.2 delta. A direct merge produces conflicts across interaction owners, tests, generated browser expectations, coverage infrastructure, and files removed by alpha.2. Passing alpha.1 evidence therefore cannot be transferred to alpha.2, and mechanically retaining either side would hide regressions or discard current product behavior. + +Alpha.2 also retains shared controls that expose accessibility semantics without owning the corresponding interaction. In particular, the shared `Modal` declares a modal dialog but does not make the application inert, contain focus, restore focus, or arbitrate nested dialogs. A companion plugin cannot reconstruct those lifecycle guarantees after React renders the control. + +## Proposal + +Build the alpha.2 accessibility candidate from the exact `dsh-v0.1.2-alpha.2` tag and migrate behavior by interaction owner. For each alpha.1 candidate responsibility, compare the official alpha.1 base, the verified alpha.1 candidate, and alpha.2; classify it as already equivalent, additive, redesigned, obsolete, test/process-only, or regenerated evidence. Reimplement redesigned responsibilities against alpha.2 contracts instead of cherry-picking the old branch. + +Core components continue to own required names, states, keyboard operation, focus, live announcements, contrast behavior, reflow, and reduced-motion behavior. Optional accessibility plugins add diagnostics, preferences, authoring assistance, and evidence collection through declared extension points; they do not repair core semantics with DOM observation. + +The migrated owner slices now restore the shared dialog and menu contracts, their Settings and Workspace consumers, the Workspace adoption-error path, Workspace and Session tree navigation, the application shell structure, Session view switching, Trajectory event navigation and resizing, question and approval flows, and structured tool lifecycle semantics. `Button` exposes its native focus owner; `Modal` manages an open-dialog stack, application-root inertness, initial and contained focus, topmost dismissal, descriptions, and connected focus restoration; Settings consumes that shared owner instead of duplicating modal lifecycle behavior; and the Workspace picker associates its alert, initially focuses Cancel, and restores the durable picker trigger. `Menu` names inline and external-trigger popups, synchronizes their expanded and controlled relationships, enters enabled items, owns wrapped arrow, Home, End, typeahead, submenu, Escape, and Tab operation, and restores the trigger without overriding focus intentionally moved into a follow-on surface. It distinguishes action rows from checkable choices: single-choice and toggle rows expose `menuitemradio` or `menuitemcheckbox` with synchronized `aria-checked`, keyboard traversal owns all three item roles, and the visual check is hidden from the accessible name. The outer dialog respects keys a nested composite already consumed, so a portaled Settings menu can close or release focus without dismissing the dialog or losing its focus boundary. Grouped, flat, and search results use one roving tree entry with arrow, Home, End, disclosure, activation, typeahead, and row-action focus ownership. The shell publishes named sidebar-navigation, main, and details-complementary landmarks, removes its mounted zero-width details subtree from interaction and the accessibility tree, and exposes each column divider as a named value-bearing window splitter with Arrow, Home, End, and Enter operation that retains focus. A multi-view Session exposes a named horizontal tablist with one sequential stop, wrapped Left and Right Arrow operation, Home and End, automatic activation, and a distinct controlled and labelled panel for each View; a lone View remains a named region without redundant tab chrome. The Trajectory event ledger is a named table with one rendered row in the sequential focus order; Up and Down Arrow, Home, and End move across the logical record set, including records outside the current virtualized window, while explicit keyboard navigation suspends tail following so streaming cannot steal focus. Enter or Space opens the row, Right Arrow enters an associated request marker, and Left Arrow or Escape returns to its row. Event details use a single-stop tablist with wrapped Left and Right Arrow, Home, End, automatic activation, and a focusable labelled panel; their value-bearing splitter exposes its pixel range, supports Arrow, Home, End, and Enter operation, and tracks container resizing. Generic question flows name choice groups and custom fields, keep one radio option in the sequential focus order, select with wrapped Arrow, Home, and End operation without advancing, and move focus into a destination question after activation, paging, skipping, or validation recovery. Question, plan-review, and approval failures use assertive announcements, and decision cards expose their pending state before re-enabling controls after a failed settlement. Tool and skill rows expose running, completed, failed, and stopped text plus stable controlled disclosure panels. Chat owns one polite live region that deduplicates root-tool, response, approval, question, and plan-review transitions by stable identity, treats history load and mount as silent baselines, and ignores token chunks, timers, and nested dispatch internals. A response terminal announcement waits for a Turn boundary newer than the one visible when that response started, so an earlier visual running-state settlement cannot misreport a late durable failure as completion. A built assembled-app contract operates the shell, dialog, menu, tree, Session-view, and Trajectory paths in Chromium, Firefox, and WebKit, including the Trajectory splitter, while replayed browser paths pin the question, plan-review, approval, and live-announcement surfaces. A long-history browser contract crosses virtualized Trajectory windows after streaming. Later slices cover remaining alpha.2 interaction owners and real assistive-technology task evidence. + +The original-image lightbox now consumes the shared modal owner instead of declaring `aria-modal` around an independently managed portal. The assembled attachment path proves application inertness, initial close-control focus, forward and reverse Tab containment, Escape and button dismissal, and connected-opener restoration; image zoom and download remain separate product limitations. + +The composer model seat now owns a single-stop hierarchical menu with edge opening, roving Arrow/Home/End navigation, directional pane entry and return, selected-value focus, and trigger restoration. Command `popupSelect` exposes its focus-retaining search as a combobox with a stable listbox relationship and synchronized active descendant; its controller binds focus return to the exact rendered session composer instead of a document-global lookup. Loading, applying, empty, provider-warning, and error states use explicit live semantics. + +Expandable file-tool rows separate their two user intentions: a named leading button owns the stable details panel, while a second named native button opens the path. The remaining visual row stays a pointer expansion target without publishing a pseudo-button around the nested file action. Focused primitive and Tool-view tests cover both controls, and the built three-engine contract proves that Enter and Space toggle details without opening the file while file activation leaves the disclosure state unchanged. + +Structured JSON inspection now treats every visible row, including primitive leaves, as an operable tree item instead of placing only expansion glyphs in the keyboard sequence. One roving Tab stop follows focus; Up and Down traverse visible rows, Right expands or enters a child, Left collapses or returns to the parent, Home and End reach the visible boundaries, and Enter or Space toggles a container. The visual chevron remains a pointer target but is hidden from the accessibility tree. Focused primitive tests cover nested and leaf behavior, and the built Trajectory path proves that a real tool payload leaf is keyboard reachable inside event details. + +Safety dialogs now complete the same shared contract at their real owners. `RiskConfirmation` associates the risk text with the dialog, hides its warning glyph, and initially focuses the required acknowledgement. Current-session and future-session permission selectors move focus from transient menu rows to their durable triggers before opening the dialog, restore that trigger after cancellation, and defer restoration across asynchronous lock or save completion without targeting an unavailable or unmounted owner. `OnboardingModal` delegates application inertness and initial focus to the shared Modal instead of duplicating lifecycle effects; the welcome heading is its explicit initial target and forward or reverse Tab reaches the sole action without escaping the dialog. Focused component tests and assembled Chromium command/RPC paths cover both Full-access selectors; the three-engine core gate repeats the complete keyboard-only current-session admission path. + +The parent-header subagent catalog now owns a complete keyboard tree instead of leaving every row in the page Tab sequence. Its count trigger supports native activation, identifies the controlled tree, opens from either edge with ArrowDown or ArrowUp, and regains focus after Escape. One enabled row owns the roving Tab stop; ArrowRight expands and then enters a branch, ArrowLeft collapses or returns to the parent, and catalog refresh or collapse repairs a removed tab-stop owner. Focused component tests cover disabled-only descendants and refresh repair, while the assembled persisted-lineage path operates a nested grandchild without pointer activation and proves that browsing does not activate either subagent. + +The transcript's message structure now remains complete during admission transitions. Durable user and Assistant nodes retain the articles owned by their stable Chat seats, while flow-tail submission echoes and Host-pending steering own equivalent named user-message articles until the atomic durable handoff replaces them. Focused tests cover both transient forms, and built submission-echo plus browser steering paths prove that their accessible boundary is present before persistence without introducing duplicate messages. + +The composer Context Meter now reports the rounded occupancy in its trigger name, exposes synchronized expanded state, and owns a stable controlled relationship to its named breakdown region. Because this inline disclosure neither moves nor contains focus, it no longer falsely advertises a dialog; focus remains on the trigger while users inspect the provider-authoritative total and the explicitly heuristic composition. Focused component evidence covers the disclosure lifecycle, and the built assembled path verifies all three projected token categories together with the accessible relationship. + +The assembled alpha.2 shell also preserves the environmental accessibility contracts from the earlier candidate. It publishes a named application heading, keeps the transcript as a named quiet log with navigable user and Assistant articles while bounded live events remain in their separate announcer, reflows the application and every Settings section at 200% and 400% equivalent widths without page-level horizontal overflow, keeps sequential focus visible and unobscured in the narrow modal, uses system colors with visible focus in Chromium forced-colors mode, and eliminates running frame animations under reduced motion. One built-app suite now verifies those contracts in Chromium, Firefox, and WebKit, with forced-colors marked as a Chromium capability rather than falsely passing elsewhere. + +Expected browser output is regenerated from alpha.2 only after the owning behavior passes focused source tests. Evidence records the exact product commit, browser and operating-system capability, assistive technology and version, task, result, limitation, and reviewer. Automated DOM, accessibility-tree, browser, contrast, reflow, motion, and packaging checks remain necessary but never substitute for task-level assistive-technology sessions and disabled-developer evidence. + +The public evidence fork remains executable without upstream private infrastructure. Pull requests outside the upstream owner select standard GitHub-hosted Linux and Windows runners with four-core workload budgets; upstream-only Cloudflare deployment and Project mutation are explicit neutral skips, while read-only policy follows the current repository identity. This keeps public verification reproducible without requesting or billing upstream larger runners. + +PowerShell evidence counts only when the job resolves a real `pwsh` binary; a skip on a host without PowerShell is a recorded capability limitation, not passing evidence. Startup first observes PowerShell's native startup output with a no-input initialization, then submits the controlled-prompt bootstrap exactly once and waits for backend `stdin_read` plus its owned marker, including when marker output lands between startup operations. It returns only the concise controlled prompt rather than echoed initialization source, preserving useful, non-noisy terminal feedback for assistive technology. A no-input readiness probe may accept the current exact stdin wait. A persistent-shell send that writes input accepts the same controlled foreground's exact wait only after polling observes a leave-and-return transition or post-submission output proves that a fast command crossed the unsampled transition; the pre-write wait alone is never reused. Quiescent `inferred_idle` is limited to operation-owned output when no input was written, the foreground is not inspectable, or a non-shell child owns it; echoed output from the same controlled shell cannot settle a send by silence alone. The following send still proves state persistence and secret scrubbing. The PowerShell-owned session, system-prompt, and tool-schema snapshots are refreshed together from the current alpha.2 profile, normalized into the canonical packed layout, and replayed on a PowerShell-capable host. + +## Alternatives considered + +**Merge the complete alpha.1 candidate into alpha.2.** Rejected because the histories share only the official alpha.1 release base and the semantic overlap crosses redesigned source, generated expectations, and deleted files. A text merge cannot decide which interaction contract remains valid. + +**Ship a companion plugin that rewrites inaccessible DOM.** Rejected because post-render observation cannot authoritatively control component state, nested dismissal, focus restoration, virtualized navigation, or async error recovery. The plugin remains useful for diagnostics and authoring support. + +**Keep alpha.1 as the accessibility release until upstream stabilizes.** Rejected because users would lose alpha.2 product and security changes, and stale evidence would accumulate behind the current package line. + +## Acceptance criteria + +- Every migrated responsibility is reviewed against alpha.2 architecture and has focused unit or component evidence plus a built, assembled browser path when user-visible. +- Keyboard-only, VoiceOver on macOS, NVDA on Windows, and at least one additional platform screen-reader path complete versioned core-task protocols; braille and other input evidence is recorded as contributors become available. +- Disabled developers validate that the supported core tasks can be completed independently, effectively, and safely, with failures and workarounds retained in the public ledger. +- Release metadata identifies the exact DSH compatibility range and evidence status; no tag, package, or documentation claims complete accessibility while required task or assistive-technology evidence is missing. +- Generated snapshots, coverage ownership, and CI workflow changes are derived from alpha.2 and pass without erasing failed run history or rerunning failures into invisibility. +- PowerShell evidence records the resolved runtime version, observes native startup output without input before one bootstrap, returns only the controlled startup prompt even across an operation-boundary race, replays both canonically packed PowerShell header-owner snapshots with real PowerShell, rejects same-shell echo plus silence as readiness, and proves a second send after the controlled shell returns to stdin wait without weakening state-persistence or secret-scrubbing assertions. +- Fork pull-request jobs resolve to standard hosted runners, retain full required scenarios at bounded concurrency, and leave upstream-only integrations neutral rather than queued or falsely failing. + +## Risks + +Alpha.2 may continue changing while the migration is in progress, so evidence can become version-bound before every task is covered. Broad shared primitives can also change focus order for many consumers; each slice needs both primitive tests and assembled consumer checks. Automated browser semantics may pass while spoken output or real input workflows remain confusing, which is why public evidence must distinguish automated conformance from assistive-technology and disabled-user validation. diff --git a/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.zh.md b/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.zh.md new file mode 100644 index 000000000000..6f434ca3dd98 --- /dev/null +++ b/.agents/notes/proposed/feature/2026-08-31-alpha2-accessibility-core-migration.zh.md @@ -0,0 +1,65 @@ +# Agent Note: Alpha.2 无障碍核心迁移 + +Status: proposed + +[English](2026-08-31-alpha2-accessibility-core-migration.md) | 中文 + +## Problem + +经过验证的无障碍候选版本绑定于 `dsh-v0.1.2-alpha.1` 的产品形态,而 `dsh-v0.1.2-alpha.2` 相比该官方 tag 改动了 1,604 个路径。候选版本相对其开发基线改动了 302 个路径,其中 128 个与官方 alpha.2 增量重叠。直接合并会在交互 owner、测试、生成的浏览器预期、覆盖率基础设施以及被 alpha.2 删除的文件中产生冲突。因此,alpha.1 的通过证据不能转移到 alpha.2;机械保留任一侧都会隐藏回归或丢弃当前产品行为。 + +Alpha.2 还保留了一些虽然暴露无障碍语义、却没有持有相应交互的共享控件。尤其是,共享 `Modal` 声明了模态对话框,却没有让应用进入 inert 状态,也没有约束和恢复焦点或协调嵌套对话框。React 渲染控件后,companion 插件无法重建这些生命周期保证。 + +## Proposal + +从精确的 `dsh-v0.1.2-alpha.2` tag 构建 alpha.2 无障碍候选版本,并按交互 owner 迁移行为。对于 alpha.1 候选版本中的每项职责,对比官方 alpha.1 基线、经过验证的 alpha.1 候选版本和 alpha.2,将其归类为已经等效、可增量迁移、需要重新设计、已经过时、仅涉及测试/流程或需要重新生成的证据。需要重新设计的职责应按照 alpha.2 契约重新实现,而不是 cherry-pick 旧分支。 + +核心组件继续持有必要的名称、状态、键盘操作、焦点、实时播报、对比度行为、回流与减少动态效果。可选无障碍插件通过已声明的扩展点增加诊断、偏好、内容创作辅助和证据采集能力;它们不通过观察 DOM 修补核心语义。 + +已经迁移的 owner 切片现已恢复共享对话框与菜单契约、使用它们的 Settings 和 Workspace、Workspace 接纳错误路径、Workspace 与 Session 树导航、应用外壳结构、Session 视图切换、Trajectory 事件导航与尺寸调整、问题和审批流程,以及结构化工具生命周期语义。`Button` 暴露原生焦点 owner;`Modal` 管理打开对话框栈、应用根节点 inert 状态、初始和受约束焦点、最上层关闭、描述关联以及仍连接目标的焦点恢复;Settings 使用该共享 owner,而不再重复实现模态框生命周期;Workspace 选择器关联其警报,把初始焦点放在“取消”上,并恢复到持久的选择器触发控件。`Menu` 为行内和外置触发控件的弹出菜单提供名称,同步展开状态与控制关系,把焦点移入可用菜单项,持有循环方向键、Home、End、前缀输入、子菜单、Escape 与 Tab 操作,并在不覆盖有意移入后续界面的焦点的前提下恢复触发控件。它会区分动作项与可勾选选项:单选和开关行分别通过 `menuitemradio` 或 `menuitemcheckbox` 暴露同步的 `aria-checked`,键盘遍历会持有全部三种菜单项角色,视觉勾选标记则不会进入无障碍名称。外层对话框会尊重嵌套复合控件已经消费的按键,因此采用 portal 的 Settings 菜单可以自行关闭或释放焦点,而不会连带关闭对话框或破坏其焦点边界。分组、平铺和搜索结果共用一个 roving 树入口,并持有方向键、Home、End、展开与折叠、激活、前缀输入和行操作焦点。外壳发布具名的侧边栏 navigation、main 与详情 complementary 地标,让已挂载但宽度为零的详情子树不可交互并从无障碍树中移除,还把每个分栏边界暴露为具名且带值的 window splitter;它支持方向键、Home、End 和 Enter,并在操作期间保留焦点。多视图 Session 会暴露具名的水平 tablist,仅提供一个顺序焦点,支持循环的左右方向键、Home、End 和自动激活,且每个视图都有独立、受其控制并由它命名的 panel;只有一个视图时,则保留具名 region,不暴露多余的 Tab 界面。Trajectory 事件台账现在是具名表格,当前渲染窗口中只有一行进入顺序焦点;上下方向键、Home 与 End 会在完整逻辑记录集上移动,包括当前虚拟化窗口之外的记录,而显式键盘导航会暂停末尾跟随,避免流式追加抢走焦点。Enter 或空格打开当前行,右方向键进入关联的请求标记,左方向键或 Escape 返回所属行。事件详情采用单顺序焦点的 tablist,支持循环左右方向键、Home、End 与自动激活,并提供可聚焦且具有关联名称的 panel;其带值的分隔条会暴露像素范围,支持方向键、Home、End 与 Enter 操作,并跟踪容器尺寸变化。通用问题流程会为选项组与自定义字段提供名称,只让一个单选选项进入顺序焦点;循环方向键、Home 与 End 会完成选择但不前进,而激活、翻页、跳过或校验恢复后,焦点会进入目标问题。问题、计划评审与审批失败会使用即时播报,决定卡片会先暴露等待状态,再在结算失败后重新启用控件。工具行与 skill 行会暴露运行中、已完成、失败和已停止文本,并提供稳定的受控展开面板。Chat 持有一个 polite live region,按稳定 identity 去重根工具、回答、审批、问题与计划评审的状态跃迁,把历史加载与挂载视为静默基线,并忽略 token 分片、计时器与嵌套 dispatch 内部过程。回答终态播报会等待比回答开始时更新的 Turn 边界,因此较早结束的视觉运行状态不会把随后抵达的 durable 失败误报为完成。构建后的组装应用契约已在 Chromium、Firefox 与 WebKit 中操作外壳、对话框、菜单、树、Session 视图和 Trajectory 路径,包括 Trajectory 分隔条;回放浏览器路径则固定问题、计划评审、审批与实时播报界面。长历史浏览器契约还会在流式追加后跨越虚拟化 Trajectory 窗口。后续切片覆盖 alpha.2 中剩余的交互 owner 与真实辅助技术任务证据。 + +原图灯箱现在使用共享 Modal owner,不再在独立管理的 portal 外仅声明 `aria-modal`。组装附件路径会证明应用 inert、初始焦点落在关闭控件、正反向 Tab 都受约束、Escape 与按钮均可关闭,以及打开者仍连接时正确恢复焦点;图片缩放与下载仍是另外的产品限制。 + +composer 模型位现在持有单停靠点的分层菜单,支持从边缘打开、方向键/Home/End roving 导航、按方向进入与返回子层、聚焦已选值以及恢复触发器。命令 `popupSelect` 将保留焦点的搜索框暴露为 combobox,通过稳定关系关联 listbox 并同步 active descendant;其 controller 把焦点返回路径绑定到确切的已渲染会话 composer,而不做 document 全局查找。加载、应用、空结果、提供方警告和错误状态均使用明确的实时语义。 + +可展开文件工具行会分离两种用户意图:具名的前导按钮持有稳定的详情面板,第二个具名原生按钮则打开路径。其余视觉行仍是指针展开目标,但不会在嵌套文件操作外发布伪按钮。聚焦的 primitive 与工具视图测试覆盖两个控件,构建后的三引擎契约会证明 Enter 和空格只切换详情而不打开文件,激活文件则不改变展开状态。 + +结构化 JSON 检查现在把每个可见行(包括原始值叶子)都作为可操作 tree item,而不再只让展开图标进入键盘序列。单一 roving Tab 停靠点随焦点移动;上/下键遍历可见行,右键展开或进入子项,左键折叠或返回父项,Home/End 到达可见边界,Enter/空格切换容器。视觉箭头仍可供指针激活,但不会进入无障碍树。聚焦 primitive 测试覆盖嵌套与叶子行为,构建后的 Trajectory 路径证明真实工具 payload 叶子可在事件详情中通过键盘到达。 + +安全对话框现在也在真实 owner 处完整采用同一套共享契约。`RiskConfirmation` 会把风险文字关联为对话框描述、隐藏警告图形,并把初始焦点放在必需的确认复选框上。当前会话与后续会话的权限选择器会在打开对话框前把焦点从临时菜单行移到持久触发器,取消后恢复该触发器,并跨越异步锁定或保存完成延迟恢复,同时不指向不可用或已卸载的 owner。`OnboardingModal` 不再重复执行生命周期 effect,而是把应用 inert 与初始焦点交给共享 Modal;欢迎标题是显式初始目标,正向或反向 Tab 都会到达唯一操作且不会逃出对话框。聚焦组件测试和组装后的 Chromium 命令/RPC 路径覆盖两个完全权限选择器;三引擎核心门禁还会重复完整的纯键盘当前会话准入路径。 + +父级页头的 subagent 目录现在持有完整键盘树,不再让每一行都进入页面 Tab 序列。其数量触发器支持原生激活、标识受控树、用 ArrowDown 或 ArrowUp 从任一边界打开,并在 Escape 后重新获得焦点。只有一个可用行持有 roving Tab 停靠点;ArrowRight 展开并进入分支,ArrowLeft 折叠或返回父项,目录刷新或折叠移除原停靠点 owner 后还会自动修复。聚焦组件测试覆盖仅含禁用后代和刷新修复,组装后的持久化谱系路径则完全不用指针进入嵌套孙级,并证明浏览不会激活任一 subagent。 + +消息流的结构现在会在接纳状态转换期间保持完整。durable 用户与 Assistant 节点继续使用稳定 Chat seat 持有的 article,消息流尾部的提交回显和 Host-pending steering 则在原子替换为 durable 节点之前持有等价的具名用户消息 article。聚焦测试覆盖两种瞬态形式,构建后的提交回显与浏览器 steering 路径会证明持久化之前已经存在无障碍边界,同时不会引入重复消息。 + +composer 的 Context Meter 现在会在触发器名称中报告取整后的占用率、公开同步的展开状态,并与具名明细 region 建立稳定的受控关系。这个行内 disclosure 既不移动也不约束焦点,因此不再错误宣称对话框;用户检查提供方权威总量和明确属于启发式估算的组成时,焦点仍保留在触发器上。聚焦组件证据覆盖 disclosure 生命周期,构建后的组装路径则同时验证全部三类 token 投影与无障碍关系。 + +组装后的 alpha.2 外壳也保留了早期候选版本的环境无障碍契约。它发布具名的应用标题;把消息流保持为具名的静默 log,并提供可导航的用户与 Assistant article,同时仍由独立播报器负责有界的实时事件;应用和每个 Settings 分区在对应 200% 与 400% 缩放的宽度下回流,且不产生页面级水平溢出;窄模态框中的顺序焦点始终可见且不被遮挡;Chromium 强制颜色模式采用系统色并保留清晰焦点;减少动态效果时没有仍在运行的框架动画。现在有一套构建后应用测试在 Chromium、Firefox 与 WebKit 中验证这些契约,并把强制颜色明确记录为 Chromium 能力,而不会在其他引擎中伪报通过。 + +只有 owner 行为通过聚焦源码测试后,才基于 alpha.2 重新生成预期浏览器输出。证据记录精确产品 commit、浏览器和操作系统能力、辅助技术及版本、任务、结果、限制与评审者。自动化 DOM、无障碍树、浏览器、对比度、回流、动态效果和打包检查仍然必不可少,但绝不替代任务级辅助技术会话和残障开发者证据。 + +公开证据 fork 无需依赖上游私有基础设施也能执行。非上游 owner 中的 Pull Request 选择 GitHub 标准托管的 Linux 与 Windows runner,并使用适合 4 核主机的工作负载预算;仅属于上游的 Cloudflare 部署与 Project 写入会明确中性跳过,只读策略则跟随当前仓库身份。这样既能保持公开验证可复现,也不会请求或计费使用上游 larger runner。 + +只有任务解析到真实 `pwsh` 二进制文件时,PowerShell 结果才计入证据;主机缺少 PowerShell 时的跳过属于已记录的能力限制,而不是通过证据。启动过程会先通过无输入初始化观察 PowerShell 原生启动输出,再且仅再提交一次受控提示符 bootstrap,并等待后端 `stdin_read` 与自有标记相互印证,即使标记输出恰好落在两个启动 operation 之间也一样。返回内容只有简洁的受控提示符,不会暴露回显的初始化源码,从而为辅助技术保留有用且无噪声的终端反馈。无输入就绪探测可以接受当前精确的 stdin 等待。写入输入的持久 shell 发送只有在轮询观察到同一受控前台进程先离开再返回,或提交后的输出证明快速命令跨过未被采样的状态转换后,才接受它精确的 stdin 等待;仅有写入前等待绝不会被复用。静默期 `inferred_idle` 只限 operation 自有输出且没有写入输入、前台不可检查或由非 shell 子进程占用的情况;同一受控 shell 的回显输出不能只凭静默结束发送。随后的发送仍须证明状态持久化与机密清理。PowerShell 所有的 session、system prompt 与 tool schema 快照必须一起从当前 alpha.2 profile 刷新、规范化为 canonical packed layout,并在具备 PowerShell 能力的主机上 replay。 + +## Alternatives considered + +**把完整的 alpha.1 候选版本合并到 alpha.2。** 拒绝,因为两条历史只共享官方 alpha.1 发布基线,语义重叠横跨重新设计的源码、生成的预期与已删除文件。文本合并无法决定哪一份交互契约仍然有效。 + +**发布一个重写不可访问 DOM 的 companion 插件。** 拒绝,因为渲染后观察无法权威控制组件状态、嵌套关闭、焦点恢复、虚拟化导航或异步错误恢复。该插件仍可用于诊断与内容创作支持。 + +**在上游稳定之前继续把 alpha.1 作为无障碍发布版本。** 拒绝,因为用户会失去 alpha.2 的产品与安全变更,陈旧证据也会在当前包版本之后持续累积。 + +## Acceptance criteria + +- 每项迁移职责都按照 alpha.2 架构进行评审,并具备聚焦单元或组件证据;如果属于用户可见行为,还必须具备构建后组装浏览器路径证据。 +- 纯键盘、macOS VoiceOver、Windows NVDA 与至少一种其他平台读屏路径完成版本化核心任务协议;随着贡献者加入,持续记录盲文与其他输入方式的证据。 +- 残障开发者验证受支持核心任务可以独立、有效且安全地完成;失败与规避方式保留在公开台账中。 +- 发布元数据标明精确的 DSH 兼容范围与证据状态;只要仍缺少必需任务或辅助技术证据,任何 tag、包或文档都不得宣称完整无障碍。 +- 生成的快照、覆盖率职责与 CI 工作流变更均以 alpha.2 为基线,并且通过检查时不会擦除失败运行历史,也不会通过重跑让失败不可见。 +- PowerShell 证据记录解析到的运行时版本,在一次 bootstrap 前无输入观察原生启动输出,即使发生 operation 边界竞态也只返回受控启动提示符,使用真实 PowerShell replay 两个采用 canonical packed layout 的 PowerShell header owner 快照,拒绝把同一 shell 的回显加静默认作就绪,并证明受控 shell 返回 stdin 等待后仍能完成第二次发送,同时不削弱状态持久化与机密清理断言。 +- fork 的 Pull Request 任务会解析为标准托管 runner,在受限并发下保留全部必需场景,并让上游专属集成保持中性,而不是无限排队或误报失败。 + +## Risks + +迁移期间 alpha.2 可能继续变化,因此在覆盖全部任务之前,证据就可能仅适用于特定版本。影响范围广的共享原语还可能改变许多使用方的焦点顺序,因此每个切片都需要基础组件测试和组装使用方检查。自动化浏览器语义可能已经通过,但真实朗读或输入工作流仍然令人困惑;因此公开证据必须区分自动化符合性、辅助技术验证与残障用户验证。 diff --git a/.github/issue-management/policy.mjs b/.github/issue-management/policy.mjs index bd726e571b0d..a68aad5bd731 100644 --- a/.github/issue-management/policy.mjs +++ b/.github/issue-management/policy.mjs @@ -7,6 +7,16 @@ import { pathToFileURL } from 'node:url' /** Command-line dispatch for PR policy checks and Issue lifecycle events. */ import { runLifecycle } from './lifecycle.mjs' import { runPullRequestCheck, runPullRequestPreflight } from './pull-request.mjs' +import config from './config.json' with { type: 'json' } + +// Read-only PR policy should follow the repository running the workflow. This +// preserves the checked-in upstream defaults for local use while making forks +// validate their own pull requests instead of querying the upstream PR number. +const runtimeRepository = process.env.GITHUB_REPOSITORY?.split('/') +if (runtimeRepository?.length === 2 && runtimeRepository.every(Boolean)) { + config.organization = runtimeRepository[0] + config.repository = runtimeRepository[1] +} function readEvent() { if (!process.env.GITHUB_EVENT_PATH) throw new Error('GITHUB_EVENT_PATH 未设置') diff --git a/.github/issue-management/policy.test.mjs b/.github/issue-management/policy.test.mjs index 91939c029abf..c88bafb5c29c 100644 --- a/.github/issue-management/policy.test.mjs +++ b/.github/issue-management/policy.test.mjs @@ -891,11 +891,13 @@ test('performs no lifecycle requests for removed signals or title-only edits', a assert.deepEqual(fixture.requests, []) }) -test('keeps trusted preflight before token minting and required policy unconditional', () => { +test('keeps trusted preflight before token minting and policy required in the upstream repository', () => { const source = readFileSync(new URL('../workflows/issue-policy.yml', import.meta.url), 'utf8') const job = source.slice(source.indexOf(' policy:')) assert.ok(job.includes(' name: Issue policy')) - assert.ok(!job.slice(0, job.indexOf(' steps:')).includes(' if:')) + assert.deepEqual(job.slice(0, job.indexOf(' steps:')).split('\n').filter(line => line.includes(' if:')), [ + " if: github.repository == 'deepseek-ai/deepseek-harness'", + ]) assert.ok(source.includes('types: [opened, edited, synchronize, reopened, labeled, unlabeled, ready_for_review, review_requested]')) const steps = job.split(' - name: ').slice(1) assert.equal(steps.length, 4) diff --git a/.github/workflows/build-preview-cloudflare.yml b/.github/workflows/build-preview-cloudflare.yml index 80caebdb7642..fc5632ea01ed 100644 --- a/.github/workflows/build-preview-cloudflare.yml +++ b/.github/workflows/build-preview-cloudflare.yml @@ -32,6 +32,8 @@ env: jobs: preview: + # Public forks do not inherit the upstream deployment credentials. + if: github.repository == 'deepseek-ai/deepseek-harness' runs-on: ubuntu-24.04 name: cloudflare pages preview steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c8d80186494..4389e235bcaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,24 +12,24 @@ env: DSH_TELEMETRY_DISABLED: '1' # Cancel a superseded pull-request run on a new push so a fresh head does not -# queue a second full 9-job run behind a stale one (paid enterprise runners -# would otherwise stack with no auto-cancellation). +# queue a second full required run behind a stale one (resource-intensive +# required runners would otherwise stack with no auto-cancellation). concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - # Three enterprise jobs isolate coverage, static analysis, and the - # build-backed consumer tail. The consumer job owns the only Linux build so - # all three jobs enter runner allocation independently. + # Four required Linux jobs isolate coverage, static analysis, the build-backed + # consumer tail, and focused assembled-app accessibility contracts. The + # latter two build independently so browser failures stay attributable. # # FAILOVER (Linux): each Linux enterprise job resolves its pool through the # DSH_CI_FAILOVER_LINUX repository variable. Unset (normal), the expressions # pick the hosted enterprise pools below. Setting the variable to # 'selfhosted' (repo Settings → Actions → Variables; writer-manageable # repository state — not PR-editable, no merge required) retargets all - # three onto the in-house + # four onto the in-house # vm-backup pool and re-running the failed jobs is the entire switch — # see .agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md. # Setting the variable to 'blacksmith' instead routes the same jobs onto @@ -42,7 +42,9 @@ jobs: node-24: if: github.event_name == 'pull_request' runs-on: >- - ${{ vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-8vcpu-ubuntu-2404' + ${{ github.repository != 'deepseek-ai/deepseek-harness' + && 'ubuntu-24.04' + || vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-8vcpu-ubuntu-2404' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]') @@ -110,7 +112,9 @@ jobs: node-24-coverage: if: github.event_name == 'pull_request' runs-on: >- - ${{ vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-16vcpu-ubuntu-2404' + ${{ github.repository != 'deepseek-ai/deepseek-harness' + && 'ubuntu-24.04' + || vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-16vcpu-ubuntu-2404' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]') @@ -118,9 +122,9 @@ jobs: name: node 24 / coverage env: # Shared hosts expose host CPUs, not an isolated allocation. - DSH_COVERAGE_MAX_WORKERS: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '6' || '' }} - DSH_COVERAGE_PARTITIONS: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '4' || '' }} - DSH_GATE_CONCURRENCY: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '3' || '' }} + DSH_COVERAGE_MAX_WORKERS: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '6' || '' }} + DSH_COVERAGE_PARTITIONS: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '4' || '' }} + DSH_GATE_CONCURRENCY: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '1' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '3' || '' }} # Managed-scope teardown cases run past the default per-test budget when # this lane's partitions, workers, and sibling gates share one host: the # disposal cases in subprocess-local and bash-sandbox exceeded 5000ms on @@ -239,7 +243,9 @@ jobs: node-24-consumers: if: github.event_name == 'pull_request' runs-on: >- - ${{ vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-16vcpu-ubuntu-2404' + ${{ github.repository != 'deepseek-ai/deepseek-harness' + && 'ubuntu-24.04' + || vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-16vcpu-ubuntu-2404' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]') @@ -247,11 +253,11 @@ jobs: name: node 24 / snapshots and artifacts env: # Shared hosts expose host CPUs, not an isolated allocation. - DSH_GATE_CONCURRENCY: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '10' || '' }} - DSH_OXLINT_THREADS: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '8' || '' }} - DSH_PUBLINT_CONCURRENCY: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '8' || '' }} - DSH_WEB_SNAPSHOT_WORKERS: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '6' || '' }} - DSH_SNAPSHOT_MAX_CONCURRENCY: ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '12' || '' }} + DSH_GATE_CONCURRENCY: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '10' || '' }} + DSH_OXLINT_THREADS: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '8' || '' }} + DSH_PUBLINT_CONCURRENCY: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '8' || '' }} + DSH_WEB_SNAPSHOT_WORKERS: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '6' || '' }} + DSH_SNAPSHOT_MAX_CONCURRENCY: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '12' || '' }} DSH_NODE_COMPAT_SKIP_TYPECHECK: '1' # A failing gate aborts its running siblings: a failing build stops the # independent Node compatibility smoke, and a failing reader (e.g. @@ -363,6 +369,82 @@ jobs: - name: Run compatibility, snapshot, and artifact gates run: pnpm run check:ci:consumers + node-24-accessibility: + if: github.event_name == 'pull_request' + runs-on: >- + ${{ github.repository != 'deepseek-ai/deepseek-harness' + && 'ubuntu-24.04' + || vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-16vcpu-ubuntu-2404' + || vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' + && github.event.pull_request.user.login != 'dependabot[bot]' + && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]') + || 'dsh-ubuntu-24-04-16core' }} + name: node 24 / accessibility browser contracts + timeout-minutes: 30 + env: + TZ: Asia/Shanghai + DSH_SNAPSHOT: replay + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: pnpm/action-setup@v4 + with: + dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} + + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Configure pnpm store path + id: pnpm-store + run: | + store_root="$HOME/.local/share/pnpm/store" + echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV" + store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) + echo "path=$store_path" >> "$GITHUB_OUTPUT" + + - uses: actions/cache/restore@v4 + if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' + with: + path: ${{ steps.pnpm-store.outputs.path }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- + + - uses: actions/cache/restore@v4 + if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Install (immutable) + run: pnpm install --frozen-lockfile + + - name: Install Playwright browser matrix and hosted dependencies + if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' + run: pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install --with-deps chromium firefox webkit + + # The persistent VM image owns Playwright's Linux system packages; do + # not mutate the shared host with apt on every failover run. + - name: Install Playwright browser matrix on the failover VM + if: vars.DSH_CI_FAILOVER_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' + run: pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install chromium firefox webkit + + - name: Run versioned assembled-app accessibility evidence + run: pnpm run test:web:accessibility:evidence + + - name: Retain exact-revision accessibility evidence + uses: actions/upload-artifact@v4 + with: + name: dsh-core-browser-accessibility-${{ github.run_id }}-${{ github.run_attempt }} + path: .artifacts/accessibility/core-browser-evidence.json + if-no-files-found: error + retention-days: 7 + node-compat: if: github.event_name == 'pull_request' @@ -503,7 +585,9 @@ jobs: windows-build: if: github.event_name == 'pull_request' runs-on: >- - ${{ vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-16vcpu-windows-2025' + ${{ github.repository != 'deepseek-ai/deepseek-harness' + && 'windows-2025' + || vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-16vcpu-windows-2025' || vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "dsh-win-ci", "windows"]') @@ -565,7 +649,9 @@ jobs: windows-coverage: if: github.event_name == 'pull_request' runs-on: >- - ${{ vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-16vcpu-windows-2025' + ${{ github.repository != 'deepseek-ai/deepseek-harness' + && 'windows-2025' + || vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-16vcpu-windows-2025' || vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "dsh-win-ci", "windows"]') @@ -574,9 +660,9 @@ jobs: timeout-minutes: 120 env: # Shared hosts expose host CPUs, not an isolated allocation. - DSH_COVERAGE_MAX_WORKERS: ${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '6' || '' }} - DSH_COVERAGE_PARTITIONS: ${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '4' || '' }} - DSH_GATE_CONCURRENCY: ${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '3' || '' }} + DSH_COVERAGE_MAX_WORKERS: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '6' || '' }} + DSH_COVERAGE_PARTITIONS: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '2' || vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '4' || '' }} + DSH_GATE_CONCURRENCY: ${{ github.repository != 'deepseek-ai/deepseek-harness' && '1' || vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '3' || '' }} DSH_COVERAGE_TEST_TIMEOUT_MS: '90000' # A gate failure aborts the sibling gate instead of waiting out its # multi-minute instrumented run. @@ -643,7 +729,9 @@ jobs: windows-native-tests: if: github.event_name == 'pull_request' runs-on: >- - ${{ vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-2vcpu-windows-2025' + ${{ github.repository != 'deepseek-ai/deepseek-harness' + && 'windows-2025' + || vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-2vcpu-windows-2025' || vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "dsh-win-ci", "windows"]') @@ -688,7 +776,6 @@ jobs: packages/workflow/workflow-ptc/tests/workflow-ptc.spec.ts packages/workflow/tool-ralph/tests/integration.spec.ts packages/subprocess/subprocess-local/tests/process-exit.spec.ts - # Single stable required check for branch protection: require "all checks # passed" instead of enumerating matrix legs whose names change as lanes and # node versions evolve. Every blocking job in THIS workflow must be listed in @@ -714,7 +801,7 @@ jobs: && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]') || 'ubuntu-latest' }} - needs: [node-24, node-24-coverage, node-24-bench, node-24-consumers, node-compat, python-sdk, python-runtime, windows-build, windows-native-tests] + needs: [node-24, node-24-coverage, node-24-bench, node-24-consumers, node-24-accessibility, node-compat, python-sdk, python-runtime, windows-build, windows-native-tests] if: ${{ !cancelled() && github.event_name == 'pull_request' }} steps: - name: Fail if any needed job did not succeed diff --git a/.github/workflows/issue-lifecycle.yml b/.github/workflows/issue-lifecycle.yml index 519fe1c7dfea..d7bac2fbfd48 100644 --- a/.github/workflows/issue-lifecycle.yml +++ b/.github/workflows/issue-lifecycle.yml @@ -33,7 +33,9 @@ concurrency: jobs: lifecycle: name: Issue lifecycle + # Upstream board integrations are unavailable to forks. if: >- + github.repository == 'deepseek-ai/deepseek-harness' && (github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested') && (github.event_name != 'pull_request' || github.event.action != 'edited' || github.event.changes.body != null) runs-on: ubuntu-latest diff --git a/.github/workflows/issue-policy.yml b/.github/workflows/issue-policy.yml index 6e07adb502a7..c31306fc2c85 100644 --- a/.github/workflows/issue-policy.yml +++ b/.github/workflows/issue-policy.yml @@ -14,6 +14,7 @@ permissions: jobs: policy: name: Issue policy + if: github.repository == 'deepseek-ai/deepseek-harness' runs-on: ubuntu-latest steps: - name: Check out trusted policy diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 3f325012a0ea..d7d1a1966172 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -244,6 +244,7 @@ External packages **directly declared** for development, tests, types, or toolin | [`tar`](https://github.com/isaacs/node-tar) | BlueOak-1.0.0 | | [`tsdown`](https://github.com/rolldown/tsdown) | MIT | | [`typescript-language-server`](https://github.com/typescript-language-server/typescript-language-server) | Apache-2.0 | +| [`unrun`](https://github.com/Gugustinette/unrun) | MIT | | [`vite`](https://github.com/vitejs/vite) | MIT | | [`vite-tsconfig-paths`](https://github.com/aleclarson/vite-tsconfig-paths) | MIT | | [`vitepress`](https://github.com/vuejs/vitepress) | MIT | diff --git a/apps/cli/README.i18n.yaml b/apps/cli/README.i18n.yaml index b5afe5cdf1c8..a3a5d188e2f9 100644 --- a/apps/cli/README.i18n.yaml +++ b/apps/cli/README.i18n.yaml @@ -6,7 +6,7 @@ en: ebdc75fa0da18801 zh: 5c6cd2554b8f8a98 /deepseek-ai-dsh/entry-modes: - en: 093936b9e1a59bea + en: 2c4a706cf64cefa8 zh: 12be7d2d931d9671 /deepseek-ai-dsh/app-arguments: en: cc03e6b75206661b diff --git a/apps/cli/README.md b/apps/cli/README.md index 1ebada88aeb8..7aaacb7f2439 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -11,7 +11,7 @@ The `dsh` command is the sole supported Node application launcher: profiles are | `dsh ` / `dsh --profile ` | Boot the named profile under `$DSH_HOME/profiles/`. | | `dsh --profile --from-default-profile