From 0708f64b9f8d20c126e106f8a87b4e46449fa4c4 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:51:18 +0800 Subject: [PATCH] test(dsh): retain safe install admission diagnostics Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- .../add-dsh-native-plugin/verification.md | 6 ++++-- plugins/dsh/scripts/smoke-native-host.mjs | 11 ++++++++++- .../integration/skill-availability-fixture.mjs | 1 + plugins/dsh/test/probe-diagnostics.test.ts | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/openspec/changes/add-dsh-native-plugin/verification.md b/openspec/changes/add-dsh-native-plugin/verification.md index c25077b2c..cd54c3174 100644 --- a/openspec/changes/add-dsh-native-plugin/verification.md +++ b/openspec/changes/add-dsh-native-plugin/verification.md @@ -566,8 +566,10 @@ Windows Node 24.2.0、DSH 0.2.0-rc.2、Chrome 156 的新隔离 Profile 已通过 DSH 0.6.0 发布检查在 Windows / DSH 0.2.1-rc.2 的 `install-host` 阶段两次达到 600 秒上限,均未进入技能探针;不能将该失败解释为启停断言失败。公开下载缓存存在不证明依赖解析或解包已加速。诊断摘要新增有界、白名单 npm 阶段计时及缓存观察计数,原始日志仍私有;计数不是去重请求数或完整缓存命中率。 -本机诊断发现新版 Host 依赖树存在多份嵌套同版本 Zod。隔离 npm Registry 的最小相容范围实验中,SDK 范围 `^3.25.0 || ^4.0.0` 与十二个 exact `4.4.3` 消费方共存:默认解析为根 `4.6.7` 加十二份 `4.4.3`,`--prefer-dedupe` 则保留一份 `4.4.3`;两棵树均通过 npm 依赖约束检查。此前不相容 major 的实验不代表这个实际依赖形状。 +本机诊断发现新版 Host 依赖树存在多份嵌套同版本 Zod。隔离 npm Registry 的最小相容范围实验中,SDK 范围 `^3.25.0 || ^4.0.0` 与十二个 exact `4.4.3` 消费方共存:默认解析为根 `4.6.7` 加十二份 `4.4.3`,`--prefer-dedupe` 则保留一份 `4.4.3`;两棵树均通过 npm 依赖约束检查。 候选调整仅在官方 Host 准备命令增加 `--prefer-dedupe`,不添加 Zod override、直接依赖、忽略 peer 的选项,也不改变安装脚本禁用、空白 Profile、进程清理、600 秒准备上限或业务断言。它可能在原范围内选择较旧的兼容依赖,因此仍须验证实际 Host 依赖身份和完整探针。 -当前只确认最小图的重复减少;真实 Windows 冷安装耗时、两版 Host 启停/恢复、其余平台及最终发布包验收仍待完成,不宣称超时已解决。修改验收脚本不等于改变已发布 CLI 或 DSH 产品运行逻辑。 +真实 Windows Node 24.2.0 / npm 11.3.0 的新版 Host lock-only 对照确认:默认 687 个安装位置、51 份 Zod,prefer-dedupe 为 636 个安装位置、1 份 Zod。本机两次完整冷安装仍达到 600 秒上限;减少重复依赖并不证明冷安装超时已解决。 + +候选提交 `74e6d93b7` 的 [CI 40996963179](https://github.com/iflytek/skillhub/actions/runs/40996963179) 中,旧版 Host 的 Windows Node 24.2.0 完整探针通过,准备耗时 348647ms;Windows Node 24 的准备完成(459541ms),但随后技能可用性探针在 user-install 的 accepted 断言失败。Linux/macOS 基础检查通过,后续官方 Host 和启停矩阵因前置失败未执行,不能算通过。安装工具返回状态与固定失败码需要补充诊断;保留现有断言。最终发布包验收仍待完成。修改验收脚本不等于改变已发布 CLI 或 DSH 产品运行逻辑。 diff --git a/plugins/dsh/scripts/smoke-native-host.mjs b/plugins/dsh/scripts/smoke-native-host.mjs index b9b3816b0..117aa2171 100644 --- a/plugins/dsh/scripts/smoke-native-host.mjs +++ b/plugins/dsh/scripts/smoke-native-host.mjs @@ -125,7 +125,16 @@ export function safeAvailabilityCheckpoint(value) { const checkpoints = ['setup', 'install', 'install-poll', 'reference', 'before-digest', 'execute', 'assertion', 'after-digest', 'mutation']; const assertions = ['none', 'isError', 'kind', 'schemaVersion', 'association', 'scope', 'integrity', 'discovery', 'name', 'reason', 'invocation', 'installation', 'namespace', 'slug', 'version', 'fingerprint', 'render', 'privacy', 'unchanged', 'accepted', 'completed', 'reference']; if (!scenarios.includes(value.scenario) || !checkpoints.includes(value.checkpoint) || !assertions.includes(value.assertion)) return undefined; - return { scenario: value.scenario, checkpoint: value.checkpoint, assertion: value.assertion }; + const result = { scenario: value.scenario, checkpoint: value.checkpoint, assertion: value.assertion }; + const statuses = ['accepted', 'not-confirmed', 'awaiting-confirmation', 'cancelled']; + // Mirror the public installationFailureCodes contract; never forward error messages or IDs. + const codes = ['CONTEXT_STALE', 'REQUEST_CONFLICT', 'SOURCE_NOT_CONFIGURED', 'SOURCE_DENIED', + 'SKILL_UNAVAILABLE', 'RATE_LIMITED', 'SOURCE_UNAVAILABLE', 'TARGET_UNAVAILABLE', 'UNCONFIRMED']; + if (statuses.includes(value.install?.status)) { + result.install = { status: value.install.status }; + if (codes.includes(value.install.code)) result.install.code = value.install.code; + } + return result; } export function safeToggleCheckpoint(value) { diff --git a/plugins/dsh/test/integration/skill-availability-fixture.mjs b/plugins/dsh/test/integration/skill-availability-fixture.mjs index 803421514..6f455ad6c 100644 --- a/plugins/dsh/test/integration/skill-availability-fixture.mjs +++ b/plugins/dsh/test/integration/skill-availability-fixture.mjs @@ -97,6 +97,7 @@ async function run(ctx, root, signal, checkpoint) { checkpoint.operation = { deadlineReached: false }; mark('install'); const result = await execute('astron_skillhub_install', { namespace, slug: 'document-helper', scope }); + checkpoint.install = { status: result.value?.status, code: result.value?.code }; mark('assertion', 'isError'); assert.equal(result.isError, false); mark('assertion', 'accepted'); diff --git a/plugins/dsh/test/probe-diagnostics.test.ts b/plugins/dsh/test/probe-diagnostics.test.ts index 2585082a7..dcde01c28 100644 --- a/plugins/dsh/test/probe-diagnostics.test.ts +++ b/plugins/dsh/test/probe-diagnostics.test.ts @@ -1,4 +1,5 @@ import { expect, test } from 'bun:test'; +import { installationFailureCodes } from '../src/host/installation-failure'; // @ts-expect-error The development-only Node entry has no TypeScript declarations. import { safeProbeFailure } from '../scripts/smoke-native-host.mjs'; @@ -80,3 +81,20 @@ test('operation observations and Host codes whitelist every field without reveal expect(safeProbeFailure(JSON.stringify({ ...base, probe: 'runtime-baseline' }), 'runtime-baseline').operation).toBeUndefined(); } }); + +test('availability admission diagnostics expose only known install status and failure code', () => { + const checkpoint = { scenario: 'user-install', checkpoint: 'assertion', assertion: 'accepted', + install: { status: 'not-confirmed', code: 'TARGET_UNAVAILABLE', message: '/secret/token' } }; + const record = { passed: false, probe: 'skill-availability', stage: 'host-assertion', + failureCode: 'HOST_ASSERTION_FAILED', diagnostics: { availability: checkpoint } }; + const safe = safeProbeFailure(JSON.stringify(record), 'skill-availability'); + expect(safe.availability.install).toEqual({ status: 'not-confirmed', code: 'TARGET_UNAVAILABLE' }); + expect(JSON.stringify(safe)).not.toContain('secret'); + for (const code of installationFailureCodes) { + checkpoint.install.code = code; + expect(safeProbeFailure(JSON.stringify(record), 'skill-availability').availability.install.code).toBe(code); + } + checkpoint.install.status = '/private/status'; + checkpoint.install.code = '/private/code'; + expect(safeProbeFailure(JSON.stringify(record), 'skill-availability').availability.install).toBeUndefined(); +});