dsh-codex-community 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/CONTRIBUTING.en.md +54 -0
  3. package/CONTRIBUTING.md +54 -0
  4. package/LICENSE +201 -0
  5. package/NOTICE +12 -0
  6. package/README.en.md +98 -0
  7. package/README.md +98 -0
  8. package/SECURITY.md +41 -0
  9. package/SUPPORT.md +17 -0
  10. package/THIRD_PARTY_NOTICES.md +25 -0
  11. package/codex-community.patch.yml +13 -0
  12. package/dist/client/index.js +1037 -0
  13. package/dist/host/index.mjs +98 -0
  14. package/dist/internal/authorization-bridge.mjs +662 -0
  15. package/dist/internal/authorization-commit-tracker.mjs +49 -0
  16. package/dist/internal/codex-authorization.mjs +202 -0
  17. package/dist/internal/codex-credential-store.mjs +164 -0
  18. package/dist/internal/codex-identifiers.mjs +4 -0
  19. package/dist/internal/codex-pi-provider.mjs +137 -0
  20. package/dist/internal/codex-provider-runtime.mjs +256 -0
  21. package/dist/internal/codex-route-adapter.mjs +133 -0
  22. package/dist/internal/codex-session-resources.mjs +64 -0
  23. package/dist/internal/failure-normalizer.mjs +456 -0
  24. package/dist/internal/image-policy.mjs +45 -0
  25. package/dist/internal/quota-observer.mjs +142 -0
  26. package/dist/internal/reliability.mjs +12 -0
  27. package/dist/internal/remote-image-input.mjs +801 -0
  28. package/dist/internal/session-preference-command.mjs +52 -0
  29. package/dist/internal/session-preferences.mjs +93 -0
  30. package/dist/internal/stream-resilience.mjs +273 -0
  31. package/docs/README.en.md +15 -0
  32. package/docs/README.md +15 -0
  33. package/docs/architecture.en.md +106 -0
  34. package/docs/architecture.md +106 -0
  35. package/docs/compatibility.en.md +53 -0
  36. package/docs/compatibility.md +53 -0
  37. package/docs/configuration.en.md +61 -0
  38. package/docs/configuration.md +61 -0
  39. package/docs/contribution-sources.en.md +35 -0
  40. package/docs/contribution-sources.md +35 -0
  41. package/docs/github-about.md +15 -0
  42. package/docs/releases/v0.0.1.acceptance.json +160 -0
  43. package/docs/releases/v0.0.1.md +174 -0
  44. package/docs/releasing.en.md +290 -0
  45. package/docs/releasing.md +290 -0
  46. package/docs/testing.en.md +85 -0
  47. package/docs/testing.md +85 -0
  48. package/docs/troubleshooting.en.md +47 -0
  49. package/docs/troubleshooting.md +47 -0
  50. package/package.json +144 -0
  51. package/types/client.d.ts +160 -0
  52. package/types/index.d.ts +22 -0
  53. package/types/reliability.d.ts +78 -0
@@ -0,0 +1,290 @@
1
+ # 发布流程
2
+
3
+ [简体中文](releasing.md) | [English](releasing.en.md)
4
+
5
+ 新版本从 `0.0.1` 开始。`0.0.x` 是技术预览,但 `0.0.1` 仍通过 npm `latest` 和正式 GitHub Release 发布;每次发布都必须使用中英双语 Release 正文。发布前硬门禁是三平台 `3/3`、完整供应链证据与维护者批准;真实账号验证可以从 `0/13` 开始,正式发布后继续补齐并如实公开状态。
6
+
7
+ ## 准备
8
+
9
+ 1. 从 `main` 创建名称清晰的发布分支;
10
+ 2. 更新 `package.json`、`CHANGELOG.md`、README 功能与支持边界、兼容日期和 `docs/releases/v<version>.md`;
11
+ 3. 确认 package name、repository URL、license、peer 范围和 `dsh.bundle`;
12
+ 4. 同步审查并更新根目录 `pnpm-lock.yaml` 与 `test/fixtures/dsh-runtime/pnpm-lock.yaml`,不能只更新其中一套依赖图;
13
+ 5. 合并前让 CI 在 Linux、macOS、Windows 通过;
14
+ 6. 明确真实账号验证计划和当前进度;`0.0.1` 允许以 `0/13` 正式发布,发布后使用受控测试账号逐项补齐,不能把 token、OAuth code、Cookie 或账号凭据写入记录。
15
+
16
+ 可以用离线准备脚本建立下一版本的发布骨架:
17
+
18
+ ```sh
19
+ pnpm run release:prepare -- 0.0.2
20
+ ```
21
+
22
+ 它只创建或补齐 `package.json` 版本、双语 `CHANGELOG.md` 草稿、双语 Release 草稿和全新的 schema v3 draft 验收记录。脚本幂等,不访问 Git 或网络,也不会伪造日期、提交或通过证据;已有有效人工内容不会被覆盖,任何冲突都会在写入前失败。README、兼容性文档、lockfile、发布分支、提交和标签仍需人工处理。
23
+
24
+ ### 本地或 CI 复演候选
25
+
26
+ 在已经提交且工作树干净的候选 checkout 中,使用 Node 24、pnpm `10.34.5` 和 npm `11.16.0` 一键复演 `0.0.1` 候选:
27
+
28
+ ```sh
29
+ pnpm run release:candidate -- 0.0.1
30
+ ```
31
+
32
+ 该命令可在本地开发机或独立 CI 中运行,不发布 npm 包或 GitHub Release,也不读取 npm token、OIDC、OAuth 或其他凭据。它精确执行根目录和冻结 DSH 夹具的 frozen install、完整 `check`、仅一次会生成候选 tarball 的 `npm pack`、本地 tarball publish dry-run、确定性 SBOM、精确候选的 DSH profile smoke、隔离安装与 Host 导入,以及生产依赖 audit,并把候选包、摘要和全部证据写入 `release/`。为避免把旧文件误当成当前证据,只要发现已有候选输出就会在任何写入前失败;清理或移走已确认不再需要的旧输出后才能重试。
33
+
34
+ 普通 CI 的独立只读 `candidate-replay` job 在 Ubuntu/Node 24 的干净 checkout 中安装固定 pnpm `10.34.5` 与 npm `11.16.0`,动态读取 `package.json` 版本后只调用一次该统一命令。它把完整 `release/` 上传为 `dsh-codex-community-${{ github.sha }}-ci-replay` artifact,并保留 14 天。
35
+
36
+ 这只是可重复的本地/CI 预检。权威发布候选仍必须由 `main` 上的 `release.yml` workflow 生成并上传;macOS 或 Windows 上的本地复演不能替代该 workflow 的 Linux x64 发布证据,也不能替代三平台 CI/profile smoke、供应链门禁或维护者审批。它也不执行发布后的真实账号验证。
37
+
38
+ ## 两级门禁
39
+
40
+ 普通 CI 使用草稿模式。它允许 `TBD` 和 `pending`,但会检查版本、双语 Release 结构及验收记录结构,因此未发布版本可以持续集成:
41
+
42
+ ```sh
43
+ pnpm install --frozen-lockfile
44
+ pnpm run check
45
+ pnpm run verify:release
46
+ ```
47
+
48
+ `npm publish` 使用严格模式。`0.0.1` 允许真实账号验证保持 `0/13`,因此 `pending` 和对应的 `TBD` 是 live 记录中合法且必须如实保留的状态。下面任一发布条件不满足都会在网络发布前失败:
49
+
50
+ - Release 日期、Accepted commit、当前版本 CHANGELOG 日期或其他发布关键字段仍是占位值;
51
+ - `docs/releases/v<version>.acceptance.json` 未标记为维护者已批准正式发布;
52
+ - Linux、macOS、Windows 的 CI/profile smoke 没有全部通过并以 `3/3` 证据绑定同一候选提交;
53
+ - 平台验收时间、运行环境、Node 版本、审批人或 HTTPS 证据链接缺失;
54
+ - 验收后的提交修改了允许的发布证据文件以外的文件;允许清理发布状态的文件仅包括双语 Release 正文、验收 JSON、双语 README、`CHANGELOG.md` 和双语兼容性文档;
55
+ - `.tgz`、SHA-256、SRI、锁定参考 SBOM、实际安装树、生产依赖审计或隔离导入证据缺失或不匹配。
56
+
57
+ 严格命令由发布工作流和 `prepublishOnly` 同时调用。`publish=true` 时,候选 job 会先把候选包、隔离导入证据和当前发布提交绑定到严格校验;只有该校验通过,工作流才请求受保护 environment 审批。审批后,发布 job 下载同一不可变候选并再次执行完全相同的校验;在仓库根目录复现时使用:
58
+
59
+ ```sh
60
+ PACKAGE_VERSION="$(node -p "require('./package.json').version")"
61
+ RELEASE_PACKAGE_FILE="release/dsh-codex-community-${PACKAGE_VERSION}.tgz" \
62
+ RELEASE_ISOLATED_IMPORT_EVIDENCE=release/isolated-import.json \
63
+ RELEASE_SOURCE_COMMIT="$(git rev-parse HEAD)" \
64
+ pnpm run verify:release:publish
65
+ # Registry 发布与 provenance/签名回读完成后,上传不可变 Registry 证据;无 OIDC 的 GitHub Release job 下载并复核后才写入 Release
66
+ ```
67
+
68
+ 不要通过设置伪造环境变量绕过门禁。缺少三平台、供应链或维护者批准证据时保持草稿状态;缺少的 live 证据则保持 `pending`,不得冒充已验证。
69
+ `prepublishOnly` 保护从仓库 checkout 发起的发布;npm 对现成 tarball 不执行该生命周期,因此工作流会在审批前和发布同一 `.tgz` 的前一步各运行一次严格门禁,并只在受保护 environment 审批后申请短期 OIDC identity token。
70
+
71
+ ## 验收记录
72
+
73
+ 1. 在已提交的 release candidate 上运行跨平台 CI/profile smoke 和完整供应链校验;
74
+ 2. 由第一次 `pass-platform` 把该提交的完整 40 位 commit 绑定到 `testedCommit`;
75
+ 3. 在三个系统分别使用精确 DSH `0.1.1-rc.2` 和已测试的 Node 22(至少 `22.19.0`)或 24 运行 profile smoke,由维护者核验证据后通过 `pass-platform` 记录 `testedAt`、环境信息和不含敏感参数的 HTTPS 证据链接;
76
+ 4. 三平台、供应链与候选边界全部通过后,由维护者填写审批信息,并把 `releaseStatus` 改为 `approved`;这表示批准正式发布,不表示真实账号已完成 `13/13`;
77
+ 5. `0.0.1` 可以保持 live `0/13` 并使用 npm `latest` 与正式 GitHub Release 发布,Release 和兼容性文档必须如实标明未验证项;
78
+ 6. 发布后在受控本地环境中使用测试账号逐项完成下表的真实验证;该过程会消耗账号额度,每项只能在对应断言均有脱敏证据后改为 `passed`,不能用自由文本概括代替;
79
+ 7. 发布前已绑定候选后,源码、配置、依赖、锁文件或工作流有任何变化,都必须先把 draft 记录重置到新提交,再重新运行 CI、profile smoke 和供应链校验;approved 记录不可重置。正式发布后发现的产品问题修复到 `0.0.2`,不覆盖 `0.0.1`。
80
+
81
+ | 验收项 | 必须证明 | 可复制的精确 `--assert` 参数 |
82
+ | --- | --- | --- |
83
+ | `oauthWebSignIn` | 从 DSH 设置页启动 OAuth、凭据显示已配置,并能完成登录后的请求 | `--assert=flowStartedFromSettings --assert=credentialConfigured --assert=postSignInRequestSucceeded` |
84
+ | `modelCatalog` | 设置页能看到目录,且会话模型选择器可选择 Codex 模型 | `--assert=settingsCatalogVisible --assert=conversationModelSelectable` |
85
+ | `textStream` | 收到非空文本 delta 和正常终止事件 | `--assert=nonEmptyTextDelta --assert=terminalStopObserved` |
86
+ | `reasoningStream` | reasoning block 可见且请求正常终止 | `--assert=reasoningBlockObserved --assert=terminalStopObserved` |
87
+ | `terminalUsage` | 终态同时提供 input/output token usage | `--assert=inputTokensObserved --assert=outputTokensObserved` |
88
+ | `toolRoundTrip` | 收到工具调用、执行安全测试工具、返回结果,并成功完成后续回复 | `--assert=toolCallObserved --assert=toolExecuted --assert=toolResultReturned --assert=followUpSucceeded` |
89
+ | `replayContinuity` | 连续两轮请求成功,第二轮正确使用第一轮上下文 | `--assert=firstTurnSucceeded --assert=secondTurnUsedPriorContext --assert=secondTurnSucceeded` |
90
+ | `imageMaxPixels` | 原生图片输入成功,且实际请求投影了 `maxPixels=4194304` | `--assert=nativeImageAccepted --assert=maxPixels4194304Projected --assert=requestSucceeded` |
91
+ | `transportAuto` | 通过 auto transport 至少完成一次真实请求 | `--assert=requestSucceeded` |
92
+ | `transportSse` | 通过 SSE transport 至少完成一次真实请求 | `--assert=requestSucceeded` |
93
+ | `transportWebsocket` | 通过 WebSocket transport 至少完成一次真实请求 | `--assert=requestSucceeded` |
94
+ | `transportWebsocketCached` | 同一会话连续两轮请求均成功 | `--assert=firstTurnSucceeded --assert=secondTurnSucceeded` |
95
+ | `fastPriority` | 请求了 priority、真实请求成功,并确认没有自动降级重放 | `--assert=priorityRequested --assert=requestSucceeded --assert=noAutomaticDowngrade` |
96
+
97
+ 每项记录只允许 `status`、`testedAt`、无查询参数的 HTTPS `evidenceUrl` 和门禁定义的布尔 `assertions`。证据不得包含 token、OAuth code、Cookie、账号标识或完整私人会话。
98
+
99
+ macOS/Linux:
100
+
101
+ ```sh
102
+ DSH_BIN=/path/to/dsh pnpm run smoke:dsh-profile
103
+ ```
104
+
105
+ Windows PowerShell:
106
+
107
+ ```powershell
108
+ $env:DSH_BIN = "C:\path\to\dsh.exe"
109
+ pnpm run smoke:dsh-profile
110
+ ```
111
+
112
+ ### 跨平台 CI/profile smoke 记录
113
+
114
+ 维护者核验某个平台的 CI 与 profile smoke 证据后,用以下离线命令记录结果:
115
+
116
+ ```sh
117
+ pnpm run release:acceptance -- pass-platform <linux|macos|windows> \
118
+ --tested-commit=<完整40位小写SHA> \
119
+ --tested-at=<带时区的RFC3339> \
120
+ --runner=<运行环境单行标识> \
121
+ --node-version=<22.x.y或24.x.y> \
122
+ --dsh-version=0.1.1-rc.2 \
123
+ --profile-smoke=passed \
124
+ --evidence-url=<脱敏HTTPS>
125
+ ```
126
+
127
+ 七个选项都必须且只能提供一次,并使用 `--name=value` 形式。`--runner` 必须是去除首尾空格、非占位、最长 128 字符且不含控制字符的单行标识;`--node-version` 接受可带 `v` 前缀的稳定三段 Node 22(至少 `22.19.0`)或 Node 24;`--dsh-version` 和 `--profile-smoke` 必须分别精确为 `0.1.1-rc.2` 与 `passed`;`--evidence-url` 必须是没有 userinfo、查询参数或片段的绝对 HTTPS URL。
128
+
129
+ `pass-platform` 只把维护者已经核验的 CI/profile smoke 结论写入验收记录;它不运行 smoke、不联网,也不检查提交是否存在。第一次 `pass-platform` 或 `pass` 会绑定完整候选 SHA,此后所有平台和真实网络证据都必须使用同一 SHA。已通过项目只允许完全相同的幂等重放;任一字段不同都会冲突,不能覆盖已有证据。记录批准后同样只接受完全相同的重放。
130
+
131
+ ### 正式发布后的本地受控账号验证
132
+
133
+ 真实账号操作必须由维护者在受控本地环境中人工执行。对 `0.0.1` 而言,这些检查在正式发布后进行,未完成项继续保持 `pending`。`release:acceptance` 只是离线证据记录器:它不访问网络、不读取凭据、不发起 provider 请求、不推断断言,也不替代人工判断。
134
+
135
+ 先查看当前状态:
136
+
137
+ ```sh
138
+ pnpm run release:acceptance -- status
139
+ ```
140
+
141
+ `status` 只显示通过/待验收计数和检查项名称,不显示证据值。人工完成一个检查项并审查脱敏证据后,重复 `--assert` 明确列出该检查项要求的全部固定断言:
142
+
143
+ ```sh
144
+ pnpm run release:acceptance -- pass <check> \
145
+ --tested-commit=<完整40位小写SHA> \
146
+ --tested-at=<RFC3339> \
147
+ --evidence-url=<脱敏HTTPS> \
148
+ --assert=<固定断言> \
149
+ --assert=<固定断言>
150
+ ```
151
+
152
+ 第一次 `pass-platform` 会在记录中的 `testedCommit` 仍为 `TBD` 时绑定该候选;后续 `pass-platform`、`pass` 和 `approve` 必须提供同一个完整 40 位小写 SHA,防止平台与真实网络证据跨候选混用。只有断言名称完整且与上表固定清单精确一致时,`pass` 才会记录 `passed`;它不会自动推断、补齐或通过断言。真实验证可以在维护者批准并发布 `0.0.1` 后继续从 `pending` 单向补录为 `passed`。发布批准在三平台和供应链证据完整后记录:
153
+
154
+ ```sh
155
+ pnpm run release:acceptance -- approve \
156
+ --tested-commit=<完整40位小写SHA> \
157
+ --approved-by=<公开维护者标识> \
158
+ --approved-at=<RFC3339> \
159
+ --evidence-url=<脱敏HTTPS>
160
+ ```
161
+
162
+ 命令只校验 SHA 的格式及其与验收记录的一致性,并记录已经作出的人工审批;它不会代表维护者作出审批。提交是否存在以及候选与发布提交的祖先关系仍由严格 publish gate 验证。参数、证据 URL、验收记录和终端日志中都不得出现 token、OAuth code、Cookie、账号标识或完整私人内容。
163
+
164
+ ### 重置候选提交
165
+
166
+ 已绑定的 draft 候选在源码或其他受验收内容变化后,先用新提交替换旧候选:
167
+
168
+ ```sh
169
+ pnpm run release:acceptance -- reset-candidate \
170
+ --from-commit=<旧完整40位小写SHA> \
171
+ --to-commit=<新完整40位小写SHA>
172
+ ```
173
+
174
+ 两个 SHA 都必须完整、小写且互不相同;正常重置要求当前 `testedCommit` 与 `--from-commit` 一致。`reset-candidate` 只接受 draft 记录,并会清空全部旧平台证据、真实网络证据和审批字段,把所有平台/live 状态恢复为 `pending`/`TBD`/`false`,再把 `testedCommit` 绑定到 `--to-commit`。approved 记录不可重置。
175
+
176
+ 命令离线运行,不访问网络或 Git,也不检查两个提交是否存在。受管路径边界检查、并发写锁和原子替换保护验收文件。相同的 `old → new` 命令在记录已经是仅绑定新 SHA 的 fresh draft 时返回 `unchanged`;只要仍残留任何平台、live 或审批证据,就会冲突且不写入。重置后先对新 SHA 运行跨平台 CI/profile smoke,再用 `pass-platform` 记录新证据;随后所有平台、live 和审批命令都必须继续使用该新 SHA。
177
+
178
+ 发布前填写 Release 日期和 `testedCommit` 对应的验收提交,并清除全部占位词。工作流实际构建所用的 release commit 会写入隔离导入证据并作为附件上传。
179
+
180
+ ## 产物门禁
181
+
182
+ 发布工作流按以下顺序执行:
183
+
184
+ ```sh
185
+ PACKAGE_VERSION="$(node -p "require('./package.json').version")"
186
+ PACKAGE_FILE="release/dsh-codex-community-${PACKAGE_VERSION}.tgz"
187
+ pnpm install --frozen-lockfile --ignore-scripts
188
+ pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts
189
+ pnpm run check
190
+ pnpm run verify:release
191
+ mkdir -p release
192
+ npm install --global --ignore-scripts --no-audit --no-fund npm@11.16.0
193
+ npm --version > release/npm-cli-version.txt
194
+ npm pack --ignore-scripts --json --pack-destination release > release/pack.json
195
+ # 以 ./$PACKAGE_FILE 做本地 tarball dry-run,再生成 SHA-256、SRI 与锁定参考 SBOM
196
+ npm publish "./$PACKAGE_FILE" --dry-run --force --ignore-scripts --json
197
+ DSH_CLI_ROOT="$PWD/test/fixtures/dsh-runtime" DSH_PLUGIN_PACKAGE="$PACKAGE_FILE" pnpm run smoke:dsh-profile
198
+ # 记录 DSH 环境、实际安装树、生产依赖审计和隔离导入证据,上传不可变候选并生成含双重 SHA-256 与下载链接的审批摘要
199
+ # publish=true 时先在 candidate job 执行以下严格门禁;通过后才请求 environment 审批,publish job 下载同一候选并再执行一次:
200
+ npm install --global --ignore-scripts --no-audit --no-fund npm@11.16.0
201
+ pnpm install --frozen-lockfile --ignore-scripts
202
+ RELEASE_PACKAGE_FILE="$PACKAGE_FILE" \
203
+ RELEASE_ISOLATED_IMPORT_EVIDENCE=release/isolated-import.json \
204
+ RELEASE_SOURCE_COMMIT="$(git rev-parse HEAD)" \
205
+ pnpm run verify:release:publish
206
+ ```
207
+
208
+ 只构建一次 `.tgz`,随后生成 SHA-256、SRI 和 CycloneDX SBOM。SRI 必须与 npm Registry 回读的 `dist.integrity` 完全相等,不接受只比较算法前缀或重新格式化后的值。
209
+
210
+ CycloneDX SBOM 是从已提交 `pnpm-lock.yaml` 与 package manifest 离线生成的**锁定参考依赖图**,用于证明候选源码所声明和锁定的依赖;它不冒充某次安装实际得到的完整依赖树。生成脚本只执行 `pnpm list --prod --json --depth Infinity --lockfile-only`,不会运行 `npm install`、`npm sbom` 或向 Registry 重新解析依赖。输出会去重并稳定排序,删除本机绝对路径和下载 URL,同时绑定 tarball SHA-256、lockfile SHA-256 与 package 依赖描述符 SHA-256,并校验 tarball 内 `package.json` 的包名、版本、许可、仓库和生产/peer 依赖描述符与仓库一致。
211
+
212
+ 根目录 `pnpm-lock.yaml` 锁定插件的构建与发布依赖;`test/fixtures/dsh-runtime/pnpm-lock.yaml` 则锁定兼容性 smoke 使用的完整 DSH runtime 与 peer 图。CI、兼容性监测和发布候选都使用 `pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts`,不会在运行时用 `npm install @deepseek-ai/dsh@...` 重新解算无界 peer 图。后者既可能造成内存失控,也会让相同源码随 Registry 状态得到不同运行时。
213
+
214
+ 同一包体随后由夹具中的精确 `0.1.1-rc.2` DSH runtime 安装到隔离 profile 并启动 Web smoke,同时在空目录用 `--ignore-scripts` 安装并导入 Host。两次实际安装分别保存 `dsh-runtime-dependency-tree.json` 和 `isolated-dependency-tree.json`,与参考 SBOM 并列取证。Web/profile smoke 成功后才生成 `dsh-runtime-environment.json`,记录 DSH 版本、夹具 lock SHA-256、Node 完整版本、pnpm 版本、平台、架构和生命周期脚本禁用状态;严格门禁会根据发布提交重新计算并核对这些值。隔离安装还执行 `npm audit --omit=dev --audit-level=high --json` 并保存 `npm-audit.json`;出现 high 或 critical 生产依赖漏洞会阻止发布。候选 job 只有 `contents: read` 权限,候选产物与证据保留 90 天;审批摘要会显示版本、源提交、包体 SHA-256、Actions 归档 SHA-256、验收进度和候选下载链接。只有 `publish=true`、`refs/heads/main` 且审批前严格门禁通过后,才会请求 `npm-release` environment 审批。Registry job 只有 `contents: read` 与 `id-token: write`,完成 npm 发布、逐字节回读、签名检查及 provenance 对包摘要、仓库、`release.yml`、`main` 和源提交的绑定后,上传不可变 Registry 证据。随后 GitHub Release job 只持有 `contents: write`、没有 OIDC;它下载并复核该证据后才写入 Release。Release 从非公开转为公开后会重新核对双语正文、标题、标签、精确附件集合及每个附件字节,再确认标签提交。
215
+
216
+ 冻结夹具使用 `--ignore-scripts`,因此该层只证明 DSH Web/profile 与本插件的兼容性,不声称验证 DSH 依赖中需要生命周期脚本的原生终端或本机构建能力。
217
+
218
+ 升级 DSH、pi-ai 或其他运行依赖时,必须更新并审查两套 lockfile,确认夹具仍只固定目标 DSH 版本,再在发布前重跑完整跨平台 CI、profile smoke 和供应链校验,发布后重新开始受控真实验证。定时 Registry 漂移报告不能替代这条升级流程。
219
+
220
+ ## 发布环境
221
+
222
+ 在 GitHub 仓库的 **Settings → Environments** 创建 `npm-release`:
223
+
224
+ 1. 配置至少一名 required reviewer;
225
+ 2. deployment branches 只允许 `main`;
226
+ 3. 不添加普通仓库级 npm token;只有首次发布引导期间,才临时添加 environment secret `NPM_BOOTSTRAP_TOKEN`;
227
+ 4. 发布人手工触发工作流;确认候选 job 的审批前严格门禁通过,并根据摘要核对版本、源提交、两个 SHA-256、验收进度和不可变候选下载内容后,再审批 environment。
228
+
229
+ 发布工作流固定使用 npm `11.16.0`,在任何网络写入前精确校验并记录版本。Trusted Publishing 最低需要 npm CLI `11.5.1`,`--include-attestations` 至少需要 `11.12.0`;升级 npm 时必须更新固定版本、测试和发布说明。
230
+
231
+ 先运行只读候选流程:
232
+
233
+ ```sh
234
+ gh workflow run release.yml --ref main \
235
+ -f version=0.0.1 \
236
+ -f publish=false
237
+ ```
238
+
239
+ `--ref main` 是硬门禁;从其他分支触发时 candidate 会明确失败,不会以全部 job 被跳过的方式显示成功。下载并核对候选附件,确认三平台 `3/3`、供应链证据和维护者批准后,更新允许的发布证据文件并合并到 `main`,再开始发布。live `0/13` 不阻止 `0.0.1` 正式发布,但必须如实展示。
240
+
241
+ ## 首次发布 `0.0.1`
242
+
243
+ npm 只允许给**已经存在**的包配置 Trusted Publisher,因此新包第一次发布需要一次性引导。该例外只适用于 `dsh-codex-community@0.0.1` 且 Registry 中尚不存在这个包;工作流会同时检查版本、包名状态和精确候选产物,条件不满足就失败。
244
+
245
+ 1. 在 npm 网页创建最短有效期的 Granular Access Token:Packages and scopes 设为 **Read and write**,选择 **All packages**,开启 **Bypass 2FA**。新包尚不存在,无法把 token 限定到该包;因此必须使用最短有效期,并在成功后立即撤销;
246
+ 2. 只把 token 粘贴到 GitHub `npm-release` environment 的 secret `NPM_BOOTSTRAP_TOKEN`。不要写入仓库、终端历史、Issue、Release 证据或聊天;
247
+ 3. 确认三平台、供应链和维护者批准门禁已通过后触发一次性正式发布;工作流使用 npm `latest` 并创建正式 GitHub Release:
248
+
249
+ ```sh
250
+ gh workflow run release.yml --ref main \
251
+ -f version=0.0.1 \
252
+ -f publish=true
253
+ ```
254
+
255
+ 4. 审批 `npm-release` environment。工作流只在实际需要写入不存在的 `0.0.1` 时把该 secret 注入 `npm publish`,并在 GitHub 托管 runner 上生成 provenance;
256
+ 5. npm 包出现后,在包的 **Settings → Trusted Publisher** 中填写:Provider `GitHub Actions`、owner `yoshino-xiao7`、repository `dsh-codex`、workflow filename `release.yml`、environment `npm-release`、allowed action `npm publish`;
257
+ 6. 撤销 npm token,并删除 GitHub environment secret:
258
+
259
+ ```sh
260
+ gh secret delete NPM_BOOTSTRAP_TOKEN --env npm-release
261
+ ```
262
+
263
+ 7. 在包的 **Settings → Publishing access** 选择 **Require two-factor authentication and disallow tokens**,再验证 Trusted Publisher 配置;
264
+ 8. 后续版本由工作流自动选择 Trusted Publisher。Registry 中包名不存在时,只有 `0.0.1` 可以进入一次性引导;包名已存在时不会读取 bootstrap secret;如果 `0.0.1` 已经存在且包体与候选逐字节一致,重跑只恢复后续 Release 步骤,不再需要 token。
265
+
266
+ 首次引导依据 npm 的 [Trusted Publisher 限制](https://docs.npmjs.com/trusted-publishers/)、[2FA 与 Granular Access Token 要求](https://docs.npmjs.com/requiring-2fa-for-package-publishing-and-settings-modification/)和[GitHub Actions provenance 要求](https://docs.npmjs.com/generating-provenance-statements/)设计。
267
+
268
+ ## 常规发布
269
+
270
+ Trusted Publisher 配置完成后,所有版本使用:
271
+
272
+ ```sh
273
+ gh workflow run release.yml --ref main \
274
+ -f version=0.0.2 \
275
+ -f publish=true
276
+ ```
277
+
278
+ - `publish=false` 只生成并验证候选产物;`publish=true` 才启用严格门禁、Registry 写入和双语 GitHub Release;
279
+ - `0.0.1` 发布到 npm `latest`,并创建正式 GitHub Release;真实账号进度可为 `0/13`,但必须在文档中如实公开;
280
+ - 常规发布只使用 GitHub OIDC,不读取 token;
281
+ - npm 发布成功后回读 `repository.url`、`dist.integrity` 和 provenance,要求 `dist.integrity` 与候选 `.sri` 逐字符一致;
282
+ - 在全新目录安装精确 Registry 版本并执行 `npm audit signatures --json --include-attestations`;签名或 attestation 校验失败会阻止公开 Release,原始 provenance bundle 与审计结果随 Release 保存;
283
+ - 从 Registry 重新下载 tarball,验证其与工作流产生的 `.tgz` 逐字节一致;
284
+ - 若 npm 已存在同版本,工作流会先下载并逐字节比较:完全一致则跳过 `npm publish` 并继续恢复 Release,不一致则立即失败;
285
+ - GitHub Release 先保持 draft;工作流用 `--clobber` 恢复上传全部附件,从 Release 回下载后逐字节比较,只有附件集合和内容全部匹配才公开;已公开版本重跑时只接受 tag commit 和全部附件与候选产物一致;
286
+ - 不从本地开发机直接创建 tag 或发布包。
287
+
288
+ 正式发布后按验收表继续补齐真实账号证据。若发现产品问题,修复后发布 `0.0.2`;不修改、覆盖或重新发布已存在的 `0.0.1`。
289
+
290
+ 任何一步失败都停止,绝不覆盖 npm 的不可变版本。若 Registry 中的包体与候选逐字节一致,可以安全恢复后续步骤;若包体不同,修复后必须递增版本。候选 job 已成功而 publish job 失败时,只在同一 workflow run 中重跑失败的 job,不重跑已经成功的候选 job:恢复过程继续使用原始 `GITHUB_SHA` 与保留 90 天的已上传候选,即使 `main` 后来前进也不会改用新源码;新 workflow run 则从当时的 `main` 重新构建。
@@ -0,0 +1,85 @@
1
+ # Testing strategy
2
+
3
+ [简体中文](testing.md) | [English](testing.en.md)
4
+
5
+ ## Local gate
6
+
7
+ ```sh
8
+ corepack enable
9
+ pnpm install --frozen-lockfile
10
+ pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts
11
+ pnpm run check
12
+ pnpm run verify:release
13
+ ```
14
+
15
+ `pnpm run check` runs syntax/source-boundary checks, strict TypeScript consumption of the public declarations, the complete test suite, build, language-pair validation, and npm-pack allowlist validation. The complete suite includes the real frozen DSH AgentLoop/Retry composition, so local development, CI, compatibility monitoring, and release candidates must install the committed DSH runtime fixture first.
16
+
17
+ Both the root `pnpm-lock.yaml` and the fixture's `test/fixtures/dsh-runtime/pnpm-lock.yaml` must remain reviewed. An upgrade to DSH, pi-ai, or another runtime dependency updates both lockfiles, repeats complete CI, profile smoke, and supply-chain verification before publication, and restarts controlled live validation after formal publication.
18
+
19
+ The fixture disables lifecycle scripts, so this level verifies DSH Web/profile integration with the plugin; it does not claim to validate native terminal or native-build capabilities in DSH dependencies that require install scripts.
20
+
21
+ ### Regular-CI candidate replay
22
+
23
+ Regular CI has a separate read-only `candidate-replay` Ubuntu/Node 24 job. From a clean checkout, it installs pinned pnpm `10.34.5` and npm `11.16.0`, reads the version dynamically from `package.json`, and then invokes `pnpm run release:candidate -- <version>` exactly once. It uploads the complete `release/` directory as the `dsh-codex-community-${{ github.sha }}-ci-replay` artifact with 14-day retention. The job neither publishes nor reads credentials, and it is not the authoritative release candidate generated by `release.yml` on `main`; it cannot replace three-platform CI/profile smoke, the complete supply-chain gate, or maintainer approval, and it does not perform post-release controlled-account validation.
24
+
25
+ ## Failure regressions
26
+
27
+ - `failure-normalizer.test.mjs`: exact `AccountQuotaExceeded`, canonical DSH `QUOTA`, five-hour reset, request ID, ordinary/ambiguous 429, narrow transport classification, and secret-field suppression; nested request metadata and other independent JSON envelopes cannot impersonate or inject facts into the matched provider `error` envelope, while canonical `QUOTA` may retain valid reset/request ID from one independently matching envelope;
28
+ - `image-policy.test.mjs`: complete defaults and every invalid numeric class;
29
+ - `stream-resilience.test.mjs`: partial text preservation, pre-output quota, transient/ambiguous 429, directly thrown quota/usage-limit/`STREAM_CLOSED`, incomplete tool calls, fail-closed tool-only streams, and non-Codex routes; an exception from the `onRecovery` observer cannot break the recovered text, recovery notice, or normal terminal event;
30
+ - `provider-reliability-public-api.test.mjs`: public pi-ai → PiAiAdapter → route → resilience chains for successful text/reasoning/usage, a tool call with two-turn replay, native image limits, text-only image rejection, 429, `STREAM_CLOSED`, and WebSocket failure;
31
+ - `llm-runtime-integration.test.mjs`: the real frozen DSH `LlmRuntime + AgentLoop + Retry` composition proves that a transient `RATE_LIMIT` retries once while normalized `QUOTA` and `QUOTA_OR_RATE_LIMIT` each call the provider only once; it also proves the waterfall publishes non-retryable `QUOTA` before recovery policy reads the result;
32
+ - `codex-session-resources.test.mjs`: namespaced sessions, reset/agent/runtime cleanup, and isolation from an external in-process pi-ai session;
33
+ - `sdk-contract.test.mjs`: real pi-ai OAuth/catalog contracts, DSH profile schema, and a PNG through the real attachment seam;
34
+ - `bundle-contract.test.mjs`: preservation of the general `llm-pi-ai` configuration, insertion of `dsh-codex`, and complete image limits;
35
+ - `codex-route-adapter.test.mjs`: mapping between the external `dsh-codex` route and the internal canonical provider, including replay and tool-call IDs;
36
+ - `codex-provider-runtime.test.mjs`: the scoped route, settings updates, model filtering, and same-profile coexistence;
37
+ - `authorization-commit-tracker.test.mjs`, `authorization-commit-integration.test.mjs`, `codex-credential-store.test.mjs`, and `codex-authorization.test.mjs`: scoped OAuth, serialized refresh, fail-closed invalid records, sanitized sign-in events, generation isolation, and the cancellation linearization point; real Cordis `AuthorizationService + Bridge` regressions also cover flow-owner disposal cancelling before commit selection, waiting for a selected commit's final write, releasing after commit failure, sign-out as the final delete, and proving that one process cancelling cannot delete another process's already queued sign-in;
38
+ - `oauth-refresh-logout-race.test.mjs`: refresh/delete lock ordering through the public PiAiAdapter request path, proving that credentials cannot reappear after sign-out;
39
+ - `codex-pi-provider.test.mjs`: default payload, per-session Fast, transport, and no automatic downgrade;
40
+ - `session-preferences.test.mjs` and `session-preference-command.test.mjs`: session isolation, capacity bounds, reset, and command-error boundaries;
41
+ - `authorization-bridge.test.mjs`: interaction bounds, no credentials across RPC, sign-out, and command output; an unsupported or malformed stored record has a distinct `invalid` state, is never presented as signed in, and never exposes its secret across RPC;
42
+ - `quota-observer.test.mjs`: three states, reset/stale expiry, out-of-order observations, strict inputs, and frozen sanitized snapshots;
43
+ - `client-bundle.test.mjs`: Web sign-in, quota presentation, model-enablement writes, mutually exclusive sign-in actions, unmount safety, multi-instance/hot-reload style-node lifecycle, narrow-screen layout, and the loopback RPC boundary; the model-settings regression starts from the real section component, sends the public settings mutation, and passes the resulting config to the real provider runtime to verify the route model directory;
44
+ - `remote-image-input.test.mjs`: URL, DNS, redirect, response-size, MIME, timeout, two active jobs, 32 queued jobs, full-queue rejection, queued cancellation, queue closure and active-job convergence during plugin disposal, and persistence-stage cancellation boundaries, plus end-to-end execution through the real ToolRuntime and published `read_image` renderer; model-capability preflight runs inside the same limiter, so a blocked resolver that ignores abort cannot bypass active or queued bounds;
45
+ - `generate-sbom.test.mjs`, `release-evidence.test.mjs`, and `release-workflow.test.mjs`: offline deterministic reference dependency graphs, artifact/both-lockfile hash binding, the DSH smoke runtime environment, actual-install-tree and production-audit evidence, exact SRI, signature/attestation audit, Action SHA pinning, least privilege, draft/tag target preflight, and recoverable publication;
46
+ - `release-maintainability.test.mjs`: direct runtime-dependency notice coverage, the Apache-2.0 contribution license, bilingual privacy templates, root/fixture DSH version alignment, and coordinated Dependabot configuration;
47
+ - `dsh-runtime-fixture.test.mjs`: the fixture is private, pins exact DSH `0.1.1-rc.2`, and commits its complete pnpm lock; CI, compatibility, and release workflows may install that graph only with `--frozen-lockfile --ignore-scripts`, and must not hand the unbounded DSH peer graph back to direct npm resolution;
48
+ - `types-consumer.ts`: imports public `.d.ts` files through npm package exports and compiles them under strict TypeScript settings;
49
+ - `host-load.test.mjs`: Host exports, Config, waterfall registration, and dynamic service registration; its quota vertical regression carries structured and ambiguous 429 observations from the real stream listener through the sanitized status RPC and `/codex-usage` command, then verifies a successful request restores recent-success state.
50
+
51
+ ## Acceptance levels
52
+
53
+ 1. Unit: pure functions and stream fixtures;
54
+ 2. SDK contract: public exports from locked published packages;
55
+ 3. Isolated install: install the `.tgz` into an empty directory and import Host;
56
+ 4. Harness smoke: first install the exact DSH runtime from the frozen `test/fixtures/dsh-runtime/pnpm-lock.yaml`, then install the local `.tgz` into an isolated profile, confirm the general pi-ai provider configuration is unchanged, start Web, and read the sign-in-status RPC plus client bundle; SDK-contract and Host-runtime tests cover model discovery;
57
+ 5. Formal release readback: exact equality between npm `dist.integrity` and the candidate SRI, a passing signature/attestation audit, byte equality between GitHub assets and the locally built tarball, plus confirmed `3/3` platforms and maintainer approval before publication;
58
+ 6. Post-release real network: `0.0.1` may be formally published at `0/13`. The repository owner then manually uses a test account in a controlled local environment to verify Web OAuth, the model catalog, text/reasoning streams, terminal usage, a safe tool round trip, two-turn replay, the `maxPixels=4194304` image path, all four transports, and Fast. This consumes account quota. A check becomes `passed` only after every fixed boolean assertion has sanitized evidence; incomplete checks remain truthfully disclosed, and product findings are iterated in `0.0.2`.
59
+
60
+ After a maintainer verifies a platform's CI and profile smoke, record that result offline with every parameter:
61
+
62
+ ```sh
63
+ pnpm run release:acceptance -- pass-platform <linux|macos|windows> \
64
+ --tested-commit=<full-40-character-lowercase-SHA> \
65
+ --tested-at=<RFC3339-with-timezone> \
66
+ --runner=<single-line-runner-id> \
67
+ --node-version=<22.x.y-or-24.x.y> \
68
+ --dsh-version=0.1.1-rc.2 \
69
+ --profile-smoke=passed \
70
+ --evidence-url=<sanitized-HTTPS>
71
+ ```
72
+
73
+ All seven options are required exactly once in `--name=value` form. The runner must be trimmed, non-placeholder, no more than 128 characters, and a single line without control characters. Node must be a stable three-part 22 release (at least `22.19.0`) or 24, optionally prefixed with `v`. Evidence must be an absolute HTTPS URL without userinfo, query data, or a fragment.
74
+
75
+ If source or other accepted content changes after a candidate has been bound, reset the draft record before accepting the new commit:
76
+
77
+ ```sh
78
+ pnpm run release:acceptance -- reset-candidate \
79
+ --from-commit=<old-full-40-character-lowercase-SHA> \
80
+ --to-commit=<new-full-40-character-lowercase-SHA>
81
+ ```
82
+
83
+ `--from-commit` must match the current `testedCommit`, and the two full lowercase SHAs must differ. The command clears all old platform/live evidence and approval fields, then binds the new SHA; an approved record cannot be reset. It runs offline without checking commit existence and writes safely through managed-path checks, a concurrent-write lock, and atomic replacement. Replaying the same `old → new` command returns `unchanged` when the record is already a fresh draft with only the new SHA bound; any residual evidence conflicts and produces no write. After resetting, run CI/profile smoke against the new SHA first, then record the new platform evidence with `pass-platform`.
84
+
85
+ Real OAuth, quota, and network smoke must not require contributors to place secrets in CI, and secrets must never be exposed to external pull requests. `release:acceptance` is only an offline evidence recorder: `pass-platform` records only CI/profile-smoke evidence already verified by a maintainer and does not run tests, access the network, or check that the commit exists; `pass` neither reads credentials nor infers or automatically passes assertions; neither replaces human approval. `status` shows only counts and check names, never evidence values. The first `pass-platform` binds the full candidate SHA, and all later platform/live records plus `approve` must use that same SHA. A passed item permits only an identical idempotent replay; differing evidence cannot overwrite it. The strict publish gate verifies `3/3` platforms, supply-chain evidence, maintainer approval, commit existence, and ancestry; live validation may remain at `0/13` for formal publication and continue afterward.
@@ -0,0 +1,85 @@
1
+ # 测试策略
2
+
3
+ [简体中文](testing.md) | [English](testing.en.md)
4
+
5
+ ## 本地门禁
6
+
7
+ ```sh
8
+ corepack enable
9
+ pnpm install --frozen-lockfile
10
+ pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts
11
+ pnpm run check
12
+ pnpm run verify:release
13
+ ```
14
+
15
+ `pnpm run check` 依次执行语法与导入边界扫描、公开声明的严格 TypeScript 消费检查、全部测试、构建、中英文配对检查和 npm pack 白名单检查。全部测试包含真实冻结 DSH AgentLoop/Retry 编排,因此本地、CI、兼容性监测和发布候选都必须先安装已提交的 DSH 运行时夹具。
16
+
17
+ 根目录 `pnpm-lock.yaml` 与夹具的 `test/fixtures/dsh-runtime/pnpm-lock.yaml` 必须同时保持受审查状态。升级 DSH、pi-ai 或其他运行依赖时要更新两套 lockfile,在发布前重新运行完整 CI、profile smoke 与供应链校验,正式发布后再重新开始受控真实验证。
18
+
19
+ 夹具禁用生命周期脚本,因此这一级只验证 DSH Web/profile 与插件集成,不代表已验证 DSH 依赖中需要安装脚本的原生终端或本机构建能力。
20
+
21
+ ### 普通 CI 候选复演
22
+
23
+ 普通 CI 另设只读的 `candidate-replay` Ubuntu/Node 24 job。它从干净 checkout 安装固定 pnpm `10.34.5` 和 npm `11.16.0`,动态读取 `package.json` 版本,随后仅调用一次 `pnpm run release:candidate -- <version>`;完整 `release/` 以 `dsh-codex-community-${{ github.sha }}-ci-replay` artifact 上传并保留 14 天。该 job 不发布、不读取凭据,也不是 `main` 上 `release.yml` 生成的权威发布候选;它不能替代三平台 CI/profile smoke、完整供应链门禁或维护者审批,也不执行发布后的真实账号验证。
24
+
25
+ ## 故障回归
26
+
27
+ - `failure-normalizer.test.mjs`:精确的 `AccountQuotaExceeded`、DSH canonical `QUOTA`、五小时 reset、request ID、普通/不确定 429、窄化 transport 与敏感字段不回显;嵌套 request metadata 和其他独立 JSON envelope 不得冒充或向命中的 provider `error` envelope 注入配额事实,canonical `QUOTA` 仍可从一个自身匹配的 envelope 保留合法 reset/request ID;
28
+ - `image-policy.test.mjs`:完整默认值与所有非法数字;
29
+ - `stream-resilience.test.mjs`:半段纯文本保存、pre-output quota、瞬时/不确定 429、直接抛出的 quota/usage-limit/`STREAM_CLOSED`、未完成工具调用、纯工具流失败关闭与非 Codex route;`onRecovery` 观察者自身抛错时也不能破坏已恢复的文本、恢复提示或正常终态;
30
+ - `provider-reliability-public-api.test.mjs`:公开 pi-ai → PiAiAdapter → route → resilience 的成功文本/reasoning/usage、工具调用与两轮 replay、原生图片预算、text-only 图片拒绝、429、`STREAM_CLOSED` 与 WebSocket failure 链路;
31
+ - `llm-runtime-integration.test.mjs`:真实冻结 DSH `LlmRuntime + AgentLoop + Retry` 编排证明瞬时 `RATE_LIMIT` 发生一次重试,而归一化后的 `QUOTA` 与 `QUOTA_OR_RATE_LIMIT` 都只调用 provider 一次;同时验证 waterfall 在恢复策略读取结果前发布非重试 `QUOTA`;
32
+ - `codex-session-resources.test.mjs`:命名空间 session、reset/agent/runtime 清理,以及同进程外部 pi-ai session 隔离;
33
+ - `sdk-contract.test.mjs`:真实 pi-ai OAuth/模型目录、DSH profile schema,以及 PNG 穿过真实 attachment seam;
34
+ - `bundle-contract.test.mjs`:bundle 保留通用 `llm-pi-ai` 配置、插入 `dsh-codex`,且图片预算完整;
35
+ - `codex-route-adapter.test.mjs`:外部 `dsh-codex` route 与内部 canonical provider 的映射、replay 和 tool-call ID;
36
+ - `codex-provider-runtime.test.mjs`:专用 route、设置热更新、模型筛选与同 profile 共存;
37
+ - `authorization-commit-tracker.test.mjs`、`authorization-commit-integration.test.mjs`、`codex-credential-store.test.mjs` 与 `codex-authorization.test.mjs`:专用 OAuth 范围、串行 refresh、非法记录失败关闭、登录事件脱敏、generation 隔离和取消线性化点;真实 Cordis `AuthorizationService + Bridge` 回归还验证 flow owner 卸载在提交选择前取消、提交选择后等待最终写入、提交失败后释放、退出最终删除,以及一个进程取消时不会删除另一进程已排队的新登录;
38
+ - `oauth-refresh-logout-race.test.mjs`:使用公开 PiAiAdapter 请求链验证 refresh/delete 锁顺序,保证退出登录后凭据不会复活;
39
+ - `codex-pi-provider.test.mjs`:默认 payload、当前会话 Fast、transport 和不自动降级;
40
+ - `session-preferences.test.mjs` 与 `session-preference-command.test.mjs`:当前会话隔离、容量限制、重置和命令错误边界;
41
+ - `authorization-bridge.test.mjs`:登录交互限额、凭据不出 RPC、退出登录和命令输出;不支持类型或结构损坏的已存记录必须返回独立的 `invalid` 状态,不能显示为已登录,也不能跨 RPC 暴露 secret;
42
+ - `quota-observer.test.mjs`:三态、reset/stale 到期、乱序观测、严格输入与冻结脱敏快照;
43
+ - `client-bundle.test.mjs`:Web 登录页、额度展示、模型启用写入、登录操作互斥、组件卸载安全、样式节点多实例/热重载生命周期、窄屏布局与 loopback RPC 调用边界;模型设置回归从真实 section 组件发出公开 settings mutation,并把返回配置交给真实 provider runtime 验证 route 模型目录;
44
+ - `remote-image-input.test.mjs`:URL、DNS、重定向、响应大小、MIME、超时、2 个活动任务、32 个排队任务、队列满拒绝、排队取消、插件卸载时的队列封口/活动任务收敛、保存阶段取消边界,以及真实 ToolRuntime 和正式 `read_image` renderer 的端到端执行;模型能力预检位于同一限流器内,阻塞或忽略 abort 的 resolver 也不能绕过活动与排队上限;
45
+ - `generate-sbom.test.mjs`、`release-evidence.test.mjs` 与 `release-workflow.test.mjs`:离线确定性参考依赖图、产物/两套锁文件哈希绑定、DSH smoke 运行环境、实际安装树和生产依赖审计取证、精确 SRI、签名/attestation 审计、Action SHA 固定、最小权限、draft/tag 目标预检和可恢复发布;
46
+ - `release-maintainability.test.mjs`:直接运行依赖声明覆盖、Apache-2.0 贡献许可、双语隐私模板、根/夹具 DSH 版本一致性与协调 Dependabot 配置;
47
+ - `dsh-runtime-fixture.test.mjs`:夹具为 private、精确固定 DSH `0.1.1-rc.2` 且提交完整 pnpm lock;CI、兼容性和发布工作流只能用 `--frozen-lockfile --ignore-scripts` 安装该图,并禁止直接交给 npm 重新解算无界 DSH peer 图;
48
+ - `types-consumer.ts`:从 npm package exports 消费公开 `.d.ts`,并以严格 TypeScript 配置编译;
49
+ - `host-load.test.mjs`:Host export、Config、waterfall 与动态服务注册;配额纵向回归把真实 stream listener 的结构化/模糊 429 观测贯通到脱敏状态 RPC 和 `/codex-usage` 命令,并验证成功请求恢复近期成功状态。
50
+
51
+ ## 验收层级
52
+
53
+ 1. 单元:纯函数与 stream fixture;
54
+ 2. SDK 合同:锁定发布包的 public exports;
55
+ 3. 隔离安装:从 `.tgz` 安装到空目录并导入 Host;
56
+ 4. Harness smoke:先从 `test/fixtures/dsh-runtime/pnpm-lock.yaml` 冻结安装精确 DSH runtime,再从本地 `.tgz` 安装到隔离 profile、确认通用 pi-ai provider 配置未改变、启动 Web,并读取登录状态 RPC 与 client bundle;模型发现由 SDK 合同和 Host runtime 测试覆盖;
57
+ 5. 正式发布读回:npm `dist.integrity` 与候选 SRI 精确一致,签名/attestation 审计通过,GitHub 资产与本地 tarball 逐字节一致;发布前还必须确认三平台 `3/3`和维护者批准;
58
+ 6. 发布后真实网络:`0.0.1` 允许以 `0/13` 正式发布。随后由仓库所有者在受控本地环境中使用测试账号人工逐项验证 Web OAuth、模型目录、文本/reasoning 流、终态 usage、安全工具闭环、两轮 replay、`maxPixels=4194304` 图片路径、四种 transport 与 Fast;该过程会消耗账号额度,只有固定布尔断言全部有脱敏证据时才能将对应项改为 `passed`,未通过项必须如实保留;发现的产品问题进入 `0.0.2` 迭代。
59
+
60
+ 维护者核验一个平台的 CI 与 profile smoke 后,用完整参数离线记录该结论:
61
+
62
+ ```sh
63
+ pnpm run release:acceptance -- pass-platform <linux|macos|windows> \
64
+ --tested-commit=<完整40位小写SHA> \
65
+ --tested-at=<带时区的RFC3339> \
66
+ --runner=<运行环境单行标识> \
67
+ --node-version=<22.x.y或24.x.y> \
68
+ --dsh-version=0.1.1-rc.2 \
69
+ --profile-smoke=passed \
70
+ --evidence-url=<脱敏HTTPS>
71
+ ```
72
+
73
+ 七个选项都必须且只能用 `--name=value` 提供一次。runner 必须去除首尾空格、不是占位词、最长 128 字符且不含控制字符;Node 必须是可带 `v` 前缀的稳定三段 22(至少 `22.19.0`)或 24;证据必须是没有 userinfo、查询参数或片段的绝对 HTTPS URL。
74
+
75
+ 已绑定候选后若源码或其他受验收内容变化,必须先重置 draft 记录,再验收新提交:
76
+
77
+ ```sh
78
+ pnpm run release:acceptance -- reset-candidate \
79
+ --from-commit=<旧完整40位小写SHA> \
80
+ --to-commit=<新完整40位小写SHA>
81
+ ```
82
+
83
+ `--from-commit` 必须匹配当前 `testedCommit`,两个完整小写 SHA 必须不同。命令会清空全部旧平台/live 证据和审批字段并绑定新 SHA;approved 记录不能重置。它离线运行且不检查提交存在性,并通过受管路径检查、并发写锁和原子替换安全写入。同一条 `old → new` 命令在记录已是只绑定新 SHA 的 fresh draft 时返回 `unchanged`;任何残留证据都会冲突且不写入。重置后先在新 SHA 上运行 CI/profile smoke,再通过 `pass-platform` 记录新平台证据。
84
+
85
+ 真实 OAuth、配额或网络 smoke 不应要求贡献者在 CI 提交秘密,也不能使用外部 PR 可访问的 secret。`release:acceptance` 只是离线证据记录器:`pass-platform` 只记录维护者已核验的 CI/profile smoke,既不运行测试、不联网,也不检查提交是否存在;`pass` 不读取凭据、不推断或自动通过断言;两者都不替代人工审批。`status` 只显示计数和检查项名称,不显示证据值。第一次 `pass-platform` 绑定完整候选 SHA,后续所有平台/live 记录和 `approve` 必须使用同一 SHA。已通过项目只允许完全相同的幂等重放,任何不同证据都不能覆盖。严格 publish gate 负责验证三平台 `3/3`、供应链证据、维护者批准、提交存在性和祖先关系;live 可在 `0/13` 状态正式发布并在发布后继续补录。
@@ -0,0 +1,47 @@
1
+ # Troubleshooting
2
+
3
+ [简体中文](troubleshooting.md) | [English](troubleshooting.en.md)
4
+
5
+ ## `Image request maxPixels must be a positive integer.`
6
+
7
+ The `0.0.1` bundle explicitly sets `requestImagePixelBudget: 4194304`, and the suite passes a real PNG through the DSH attachment seam. If the error remains:
8
+
9
+ 1. confirm the exact `dsh-codex-community@0.0.1` artifact is running;
10
+ 2. confirm that the profile's `dsh-codex` configuration contains all three image budgets;
11
+ 3. check settings for zero, negative, fractional, or another explicitly invalid image field; omitted and `null` values receive safe defaults;
12
+ 4. run `npm test -- --test-name-pattern=maxPixels` or the complete `pnpm test`;
13
+ 5. report only plugin, DSH, and Node versions plus a sanitized stack—never the source image or credentials.
14
+
15
+ If another bundle or user setting replaces the complete `dsh-codex` config row, merge back all three valid positive-integer budgets instead of retaining only one.
16
+
17
+ ## `AccountQuotaExceeded` / five-hour quota
18
+
19
+ This is an exhausted account usage window, not a transient request-rate limit. The plugin:
20
+
21
+ - maps it to `QUOTA` and stops automatic retry;
22
+ - displays the provider reset time and request ID only when they are format-valid and come from the failure's top level or the same independently quota-confirming `error` envelope;
23
+ - preserves already visible safe text when possible;
24
+ - does not claim that another transport or repeated requests can bypass account quota.
25
+
26
+ The “Codex quota observation” card and `/codex-usage` show only state produced by recent requests. They do not query the account proactively or represent a live balance. A reset must pass strict format and bounded-horizon checks; otherwise an exhausted observation returns to “unknown” after a bounded interval.
27
+
28
+ Wait for reset, use another model/plan available to the account, or follow the options shown by the ChatGPT account surface. Never attach account screenshots, tokens, or the complete raw response to an issue.
29
+
30
+ ## `QUOTA_OR_RATE_LIMIT`
31
+
32
+ pi-ai may return only a generalized ChatGPT usage-limit message after discarding the original 429 body/code. The plugin cannot reliably distinguish account quota from transient rate limiting in that case, so it does not label the failure `QUOTA`, update quota observations, or retry automatically when no output exists. If safe plain text already exists, only that partial text is preserved; if a tool call appeared, the stream fails closed. Check the account surface or retry manually later instead of cycling transports.
33
+
34
+ ## Fast is unavailable
35
+
36
+ Run `/codex status` to confirm whether Fast is enabled for the current session. Fast requests the priority service tier, whose availability depends on the account and service. The plugin does not replay a failed request automatically on a lower tier. Run `/codex set fast off` to disable Fast for this session, then decide whether to send the request again.
37
+
38
+ ## A response stops halfway
39
+
40
+ - Visible text already exists and no tool call appeared: whether the failure arrived as a terminal chunk or a direct throw, open text/reasoning blocks are closed, content is saved, and the notice asks you to send “continue.”
41
+ - A tool call appeared, including a tool-only stream with no text: the plugin fails closed without replay. The exact error code retains its reliable classification, avoiding repeated file writes, requests, or other tool side effects.
42
+ - No output yet: transient `RATE_LIMIT`, `SERVER`, `TIMEOUT`, or `TRANSPORT` failures may retry at most twice.
43
+ - Evidence-free `QUOTA_OR_RATE_LIMIT`: no retry without output, safe partial text only when present, and no quota observation.
44
+
45
+ ## Signed in but unavailable
46
+
47
+ First confirm the sign-in state under **Settings → Codex sign-in**, ensure the target model is enabled on that same page, and then choose it from the `dsh-codex` provider group in the conversation model selector. A ChatGPT OAuth bearer is not an OpenAI Platform API key. If refresh fails, sign out on the Codex sign-in page and sign in again. Removing the package does not automatically erase the grant.
@@ -0,0 +1,47 @@
1
+ # 故障排查
2
+
3
+ [简体中文](troubleshooting.md) | [English](troubleshooting.en.md)
4
+
5
+ ## `Image request maxPixels must be a positive integer.`
6
+
7
+ `0.0.1` 的 bundle 显式提供 `requestImagePixelBudget: 4194304`,并在测试中让真实 PNG 穿过 DSH attachment seam。若仍出现该错误:
8
+
9
+ 1. 确认运行的是 `dsh-codex-community@0.0.1` 的精确包体;
10
+ 2. 确认 profile 中的 `dsh-codex` 配置包含完整的三项图片预算;
11
+ 3. 检查 settings 中是否把图片字段写成 `0`、负数、浮点或其他显式非法值;缺省和 `null` 会回填安全默认值;
12
+ 4. 运行 `npm test -- --test-name-pattern=maxPixels` 或完整 `pnpm test`;
13
+ 5. 报告时只附插件、DSH、Node 版本和脱敏 stack,不附图片原件或凭据。
14
+
15
+ 如果另一个 bundle 或用户设置替换了 `dsh-codex` 的整行 config,请合并回三项有效正整数预算,而不是只保留其中一项。
16
+
17
+ ## `AccountQuotaExceeded` / 五小时配额
18
+
19
+ 这是账户使用窗口耗尽,不是瞬时请求速率限制。插件会:
20
+
21
+ - 映射为 `QUOTA`,停止自动重试;
22
+ - 只在 failure 顶层或同一个可独立确认配额的 `error` envelope 中存在且通过格式校验时,显示 provider 给出的 reset 时间和 request ID;
23
+ - 在安全情况下保存已经显示的纯文本;
24
+ - 不承诺通过换 transport 或重复请求绕过账户额度。
25
+
26
+ 设置页的“Codex 额度观测”和 `/codex-usage` 只显示最近请求产生的状态。它们不会主动查询账户,也不代表实时余额;reset 必须通过严格格式和有限时距校验,否则耗尽状态会在有限时间后回到“未知”。
27
+
28
+ 请等待重置、使用账户允许的其他模型/方案,或按 ChatGPT 账户页面提供的选项处理。不要在 Issue 中提交账户截图、token 或完整原始响应。
29
+
30
+ ## `QUOTA_OR_RATE_LIMIT`
31
+
32
+ pi-ai 可能只返回通用的 ChatGPT usage-limit 文案,而不再提供原始 429 body/code。此时插件无法可靠区分账户配额与瞬时限流,因此不会把它写成 `QUOTA`、不会更新额度观测,也不会在无输出时自动重试。若已经产生安全纯文本,插件只保存该 partial;若已经出现工具调用,则失败关闭。请由用户确认账户页面或稍后手动重试,不要循环切换 transport。
33
+
34
+ ## Fast 不可用
35
+
36
+ 先运行 `/codex status` 确认当前会话是否开启 Fast。Fast 请求 priority service tier,但可用性取决于账号与服务端;插件不会把失败请求自动降级重放。可以运行 `/codex set fast off` 关闭当前会话的 Fast,再由用户决定是否重新发送请求。
37
+
38
+ ## 回复到一半中断
39
+
40
+ - 已有可见文本且没有工具调用:无论 failure 来自终止 chunk 还是直接抛出,插件都会闭合 text/reasoning block、保存内容并提示发送“继续”;
41
+ - 已出现工具调用(包括没有文本的纯工具流):插件失败关闭且不自动重放;具体错误码保留可靠分类,避免写文件、发请求或其他工具副作用重复发生;
42
+ - 尚无任何输出的瞬时 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`:最多重试两次。
43
+ - 无结构证据的 `QUOTA_OR_RATE_LIMIT`:无输出时不重试,有安全纯文本时只保存 partial,且不写入额度观测。
44
+
45
+ ## 登录后仍不可用
46
+
47
+ 先在 **设置 → Codex 登录** 确认登录状态,并在同一页面确认目标模型已启用,再从会话模型选择器的 `dsh-codex` provider 组中选用。不要把 ChatGPT OAuth bearer 当成 OpenAI Platform API key。若 refresh 失败,先在 Codex 登录页退出再重新登录;卸载包不会自动删除 grant。