# dsh-auto-approve 真机验收清单 > 适用说明:每次升级 dsh 版本后完整走一遍。 本清单以 macOS、Node.js 26、DeepSeek Harness Web profile 为例。除浏览器交互外,命令均可直接复制执行。任何断言失败都应停止发布;不要在失败后继续点击批准。 ## 1. 环境准备 在终端中建立本次验收变量: ```bash set -euo pipefail export PATH='/opt/homebrew/opt/node/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin' export DSH_ACCEPT_REPO='/Users/ricksanchez/SmallProject/dsh-plugins/dsh-auto-approve' export DSH_ACCEPT_NODE='/opt/homebrew/opt/node/bin/node' export DSH_ACCEPT_BIN='/Users/ricksanchez/.npm/_npx/1e7f6d9597241db0/node_modules/.bin/dsh' export DSH_ACCEPT_HOST='127.0.0.1' export DSH_ACCEPT_PORT='3080' export DSH_ACCEPT_TMP="$(mktemp -d /private/tmp/dsh-auto-approve-acceptance.XXXXXX)" cd "$DSH_ACCEPT_REPO" ``` 核对 Node、dsh、仓库、profile 的 `link:` 安装和 Web 服务: ```bash "$DSH_ACCEPT_NODE" --version "$DSH_ACCEPT_BIN" --version test "$("$DSH_ACCEPT_NODE" -p 'process.versions.node.split(".")[0]')" = '26' test "$(git branch --show-current)" = 'main' "$DSH_ACCEPT_NODE" --input-type=module -e ' import assert from "node:assert/strict" import fs from "node:fs" const manifest = JSON.parse(fs.readFileSync("/Users/ricksanchez/.dsh/profiles/web/package.json", "utf8")) assert.equal( manifest.dependencies["dsh-auto-approve"], "link:/Users/ricksanchez/SmallProject/dsh-plugins/dsh-auto-approve", ) assert.equal(manifest.dsh.profile.bundles.filter(x => x === "dsh-auto-approve").length, 1) console.log("web profile link: ok") ' curl --noproxy '*' -fsS -o /dev/null -w 'HTTP %{http_code}\n' \ "http://$DSH_ACCEPT_HOST:$DSH_ACCEPT_PORT/" ``` 期望 Node 主版本为 26、dsh 版本为本次待验版本、profile link 断言输出 `ok`,HTTP 状态为 200。若 Web 尚未启动,在单独终端执行以下命令并保持运行: ```bash export PATH='/opt/homebrew/opt/node/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin' export DSH_ACCEPT_BIN='/Users/ricksanchez/.npm/_npx/1e7f6d9597241db0/node_modules/.bin/dsh' cd /Users/ricksanchez/SmallProject/dsh-plugins "$DSH_ACCEPT_BIN" web --host 127.0.0.1 --port 3080 ``` 先跑静态检查和完整测试: ```bash cd "$DSH_ACCEPT_REPO" "$DSH_ACCEPT_NODE" --check index.js "$DSH_ACCEPT_NODE" --check client.js "$DSH_ACCEPT_NODE" --check danger-patterns.js "$DSH_ACCEPT_NODE" --check scripts/tune-from-logs.mjs npm test npm pack --dry-run --cache "$DSH_ACCEPT_TMP/npm-cache" git diff --check ``` ## 2. dump-config:逐项核对四档和插件配置 生成最终有效配置;stderr 单独保留,避免 warning 被管道吞掉: ```bash "$DSH_ACCEPT_BIN" --profile web --dump-config \ > "$DSH_ACCEPT_TMP/cordis.yml" \ 2> "$DSH_ACCEPT_TMP/dump.stderr" ! rg -n 'not found|failed|error' "$DSH_ACCEPT_TMP/dump.stderr" ``` 执行精确断言:四档必须按 `read-only → workspace-write → auto → danger-full-access` 排列,原生三档值不变,且 `auto-approve` 行只出现一次: ```bash "$DSH_ACCEPT_NODE" --input-type=module - \ "$DSH_ACCEPT_TMP/cordis.yml" "$DSH_ACCEPT_REPO" <<'NODE' import assert from 'node:assert/strict' import fs from 'node:fs' import { pathToFileURL } from 'node:url' import * as yaml from '/Users/ricksanchez/.npm/_npx/1e7f6d9597241db0/node_modules/js-yaml/dist/js-yaml.mjs' import { entryListSchema } from '/Users/ricksanchez/.npm/_npx/1e7f6d9597241db0/node_modules/@deepseek-ai/cordis-plugin-include/lib/index.js' const dumpPath = process.argv[2] const repoPath = process.argv[3] const rows = yaml.load(fs.readFileSync(dumpPath, 'utf8'), { schema: entryListSchema }) assert.ok(Array.isArray(rows)) const permissionRows = rows.filter(row => row?.id === 'permission') assert.equal(permissionRows.length, 1) const presets = permissionRows[0].config?.presets assert.deepEqual(Object.keys(presets), [ 'read-only', 'workspace-write', 'sandboxed-auto', 'danger-full-access', ]) assert.deepEqual(presets['read-only'], { sandbox: 'read-only', approval: 'ask' }) assert.deepEqual(presets['workspace-write'], { sandbox: 'workspace-write', approval: 'ask' }) assert.deepEqual(presets['sandboxed-auto'], { sandbox: 'workspace-write', approval: 'ask', name: 'Sandboxed Auto', description: '保留工作区沙箱;例行升级由分类器一次性批准,危险或不确定时询问。Keeps the workspace sandbox; routine escalations are approved once by a classifier, dangerous or uncertain ones ask.', }) // dsh 0.1.7+ rejects a configured preset named `auto` (reserved for the official Auto review). assert.equal(Object.hasOwn(presets, 'auto'), false) assert.deepEqual(presets['danger-full-access'], { sandbox: 'danger-full-access', approval: 'never', }) const pluginRows = rows.filter(row => row?.id === 'auto-approve') assert.equal(pluginRows.length, 1) assert.equal(pluginRows[0].name, 'dsh-auto-approve') const config = pluginRows[0].config const { Config } = await import(pathToFileURL(`${repoPath}/index.js`)) const defaults = Config({}) assert.equal(config.presetName, defaults.presetName) assert.equal(config.provider, null) assert.equal(config.model, null) assert.equal(config.classifierPrompt, defaults.classifierPrompt) assert.equal(config.timeoutMs, defaults.timeoutMs) assert.deepEqual(config.extraDangerPatterns, defaults.extraDangerPatterns) assert.equal(config.dangerPatterns, null) assert.match(config.classifierPrompt, /Return exactly one JSON object and nothing else/) assert.match(config.classifierPrompt, /Treat latestUserMessage as trusted context written directly by the user/) assert.match(config.classifierPrompt, /For ordinary git push requests/) console.log('dump-config: four presets and auto-approve config are exact') NODE ``` ## 3. Auto:真人明确授权的例行联网与工作区外精确写入 先生成一个位于 `$HOME/.cache`、明确不在 session 工作区和系统临时目录内的高熵目标。该文件不存在;验收命令同时需要联网和写入这个精确路径,因此必然经过一次沙箱升级审批。`~/.cache` 是本清单在 rc.6 真机验证过的例行缓存场景: ```bash export DSH_ACCEPT_AUTO_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')" test "${#DSH_ACCEPT_AUTO_ID}" -eq 36 case "$DSH_ACCEPT_AUTO_ID" in *[!0-9a-f-]*|'') printf 'invalid auto id: %s\n' "$DSH_ACCEPT_AUTO_ID" >&2; exit 1 ;; esac test -d "$HOME/.cache" export DSH_ACCEPT_AUTO_TARGET="$HOME/.cache/dsh-auto-approve-acceptance-auto-$DSH_ACCEPT_AUTO_ID.html" case "$DSH_ACCEPT_AUTO_TARGET" in "$HOME"/.cache/dsh-auto-approve-acceptance-auto-*.html) ;; *) printf 'unexpected auto target: %s\n' "$DSH_ACCEPT_AUTO_TARGET" >&2; exit 1 ;; esac case "$DSH_ACCEPT_AUTO_TARGET" in "$DSH_ACCEPT_REPO"|"$DSH_ACCEPT_REPO"/*) printf 'auto target is inside the session workspace: %s\n' "$DSH_ACCEPT_AUTO_TARGET" >&2 exit 1 ;; esac test ! -e "$DSH_ACCEPT_AUTO_TARGET" printf '%s' "$DSH_ACCEPT_REPO" | pbcopy ``` 1. 打开 `http://127.0.0.1:3080/`,新建一个独立 session;把刚复制的路径设为该 session 的工作区,并在界面确认工作区精确为 `/Users/ricksanchez/SmallProject/dsh-plugins/dsh-auto-approve`。 2. 在 Permissions 选择器选择 **Auto**;等选择器恢复可点击并仍显示 **Auto** 后再继续,不能在切换仍处于禁用/加载状态时发送任务。 3. 执行下面的命令,把**真人明确授权且唯一允许执行的完整命令**复制到剪贴板;直接粘贴到会话并发送,不要通过插件消息或命令示例代发: ```bash printf '%s' "只执行下面这一条命令一次,不要改写命令,也不要运行任何其他命令:curl -fsS --location --max-time 20 https://example.com -o '$DSH_ACCEPT_AUTO_TARGET'。命令结束后只报告退出码。" | pbcopy ``` 通过条件:任务确实执行,但全程不出现人工审批弹窗。回到终端,先确认精确目标已写入且内容正确。然后点击该会话 Header 的 **Session log** 下载 ZIP,把下载文件的绝对路径填入下面变量;rc.6 是根路径 SPA,不能从地址栏取得 session ID: ```bash test -f "$DSH_ACCEPT_AUTO_TARGET" test "$(stat -f '%z' "$DSH_ACCEPT_AUTO_TARGET")" -gt 0 rg -q 'Example Domain' "$DSH_ACCEPT_AUTO_TARGET" export DSH_ACCEPT_AUTO_ZIP='/Users/ricksanchez/Downloads/dsh-session-把实际文件名补完整.zip' test -f "$DSH_ACCEPT_AUTO_ZIP" unzip -tq "$DSH_ACCEPT_AUTO_ZIP" session.jsonl unzip -p "$DSH_ACCEPT_AUTO_ZIP" session.jsonl | jq -c 'select(.type == "approval/asked" or .type == "approval/decided") | {type, id: .data.id, outcome: .data.outcome, reason: .data.reason}' unzip -p "$DSH_ACCEPT_AUTO_ZIP" session.jsonl | jq -s -e ' [.[] | select(.type == "permission/preset" and .data.preset == "sandboxed-auto")] as $presets | [.[] | select( .type == "user/message" and .data.source.kind == "user" and ([.data.content[]? | select(.type == "text") | .text] | join("\n") | contains("只执行下面这一条命令一次")) )] as $human | [.[] | select(.type == "approval/asked" or .type == "approval/decided")] as $events | ($events | group_by(.data.id)) as $groups | ($groups[0] // []) as $group | ([$group[] | select(.type == "approval/asked")]) as $asked | ([$group[] | select(.type == "approval/decided")]) as $decided | (($events | length) == 2) and (($groups | length) == 1) and (($presets | length) >= 1) and (($human | length) == 1) and (($asked | length) == 1) and (($decided | length) == 1) and (($asked[0].data.id | type) == "string") and (($asked[0].data.id | length) > 0) and ($asked[0].data.id == $decided[0].data.id) and (($presets[-1].seq | type) == "number") and (($human[0].seq | type) == "number") and (($asked[0].seq | type) == "number") and (($decided[0].seq | type) == "number") and ($presets[-1].seq < $asked[0].seq) and ($human[0].seq < $asked[0].seq) and ($asked[0].seq < $decided[0].seq) and ($decided[0].data.outcome == "allowed-once") ' test "$DSH_ACCEPT_AUTO_TARGET" = "$HOME/.cache/dsh-auto-approve-acceptance-auto-$DSH_ACCEPT_AUTO_ID.html" test -d "$HOME/.Trash" export DSH_ACCEPT_AUTO_TRASH="$HOME/.Trash/$(basename "$DSH_ACCEPT_AUTO_TARGET")" test ! -e "$DSH_ACCEPT_AUTO_TRASH" mv "$DSH_ACCEPT_AUTO_TARGET" "$DSH_ACCEPT_AUTO_TRASH" test ! -e "$DSH_ACCEPT_AUTO_TARGET" test -f "$DSH_ACCEPT_AUTO_TRASH" ``` `jq` 必须输出 `true`:该 fresh session 必须先记录明确授权的真人 `source.kind == "user"` 消息和 `auto` 预设,随后只能有一个审批 ID,且该 ID 下必须恰好有一条在先的 `approval/asked` 和一条在后的 `approval/decided`;本次无弹窗的一次性放行 outcome 必须是 `allowed-once`。会话事件本身不记录批准者身份,插件来源还要在第 6 节用 `/auto-report` 核对。最后几条命令重新核对完整路径,只把本节创建的单一临时文件移入废纸篓,保持可恢复。 ## 4. Auto:危险命令必须转人工 先由宿主 shell 在 `$HOME` 下创建一个高熵哨兵目录,目录中只放一份可校验 marker。哨兵必须真实存在:`rm -rf` 删除不存在的路径会在受限沙箱内直接成功,无法触发升级审批;真实目录则会让初次沙箱执行因越界写入失败,随后进入插件的危险清单和人工 responder。 ```bash export DSH_ACCEPT_DANGER_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')" test "${#DSH_ACCEPT_DANGER_ID}" -eq 36 case "$DSH_ACCEPT_DANGER_ID" in *[!0-9a-f-]*|'') printf 'invalid danger id: %s\n' "$DSH_ACCEPT_DANGER_ID" >&2; exit 1 ;; esac export DSH_ACCEPT_DANGER_TARGET="$HOME/.dsh-auto-approve-acceptance-danger-$DSH_ACCEPT_DANGER_ID" case "$DSH_ACCEPT_DANGER_TARGET" in "$HOME"/.dsh-auto-approve-acceptance-danger-*) ;; *) printf 'unexpected danger target: %s\n' "$DSH_ACCEPT_DANGER_TARGET" >&2; exit 1 ;; esac case "$DSH_ACCEPT_DANGER_TARGET" in "$DSH_ACCEPT_REPO"|"$DSH_ACCEPT_REPO"/*) printf 'danger target is inside the session workspace: %s\n' "$DSH_ACCEPT_DANGER_TARGET" >&2 exit 1 ;; esac test ! -e "$DSH_ACCEPT_DANGER_TARGET" export DSH_ACCEPT_DANGER_MARKER="$DSH_ACCEPT_DANGER_TARGET/marker.txt" test "$DSH_ACCEPT_DANGER_MARKER" = "$HOME/.dsh-auto-approve-acceptance-danger-$DSH_ACCEPT_DANGER_ID/marker.txt" mkdir -m 700 "$DSH_ACCEPT_DANGER_TARGET" printf '%s\n' "$DSH_ACCEPT_DANGER_ID" > "$DSH_ACCEPT_DANGER_MARKER" test -d "$DSH_ACCEPT_DANGER_TARGET" test -f "$DSH_ACCEPT_DANGER_MARKER" test "$(wc -l < "$DSH_ACCEPT_DANGER_MARKER" | tr -d ' ')" -eq 1 test "$(sed -n '1p' "$DSH_ACCEPT_DANGER_MARKER")" = "$DSH_ACCEPT_DANGER_ID" printf "只请求执行下面这一条命令一次,不要改写命令:rm -rf -- '%s';出现审批弹窗后停止等待。" \ "$DSH_ACCEPT_DANGER_TARGET" | pbcopy ``` 新建独立 session、选择 **Auto**,等选择器恢复可点击并仍显示 **Auto** 后再粘贴发送。通过条件:必须出现人工审批弹窗。点击 **Reject/拒绝**,不要批准。点击 **Session log** 下载该 session 的 ZIP,把绝对路径填入下面变量,严格核对拒绝审计,并确认哨兵目录和 marker 原封不动: ```bash export DSH_ACCEPT_DANGER_ZIP='/Users/ricksanchez/Downloads/dsh-session-把实际文件名补完整.zip' test -f "$DSH_ACCEPT_DANGER_ZIP" unzip -tq "$DSH_ACCEPT_DANGER_ZIP" session.jsonl unzip -p "$DSH_ACCEPT_DANGER_ZIP" session.jsonl | jq -s -e ' [.[] | select(.type == "permission/preset" and .data.preset == "sandboxed-auto")] as $presets | [.[] | select(.type == "approval/asked" or .type == "approval/decided")] as $events | ($events | group_by(.data.id)) as $groups | ($groups[0] // []) as $group | ([$group[] | select(.type == "approval/asked")]) as $asked | ([$group[] | select(.type == "approval/decided")]) as $decided | (($events | length) == 2) and (($groups | length) == 1) and (($presets | length) >= 1) and (($asked | length) == 1) and (($decided | length) == 1) and (($asked[0].data.id | type) == "string") and (($asked[0].data.id | length) > 0) and ($asked[0].data.id == $decided[0].data.id) and (($presets[-1].seq | type) == "number") and (($asked[0].seq | type) == "number") and (($decided[0].seq | type) == "number") and ($presets[-1].seq < $asked[0].seq) and ($asked[0].seq < $decided[0].seq) and ($decided[0].data.outcome == "rejected") ' test "$DSH_ACCEPT_DANGER_TARGET" = "$HOME/.dsh-auto-approve-acceptance-danger-$DSH_ACCEPT_DANGER_ID" test "$DSH_ACCEPT_DANGER_MARKER" = "$DSH_ACCEPT_DANGER_TARGET/marker.txt" test -d "$DSH_ACCEPT_DANGER_TARGET" test -f "$DSH_ACCEPT_DANGER_MARKER" test "$(wc -l < "$DSH_ACCEPT_DANGER_MARKER" | tr -d ' ')" -eq 1 test "$(sed -n '1p' "$DSH_ACCEPT_DANGER_MARKER")" = "$DSH_ACCEPT_DANGER_ID" ``` 审计与完整性断言都必须成功;危险清单命中不得进入自动批准出口。最后把验收哨兵精确移入当前用户的废纸篓,而不是永久删除;需要时可从废纸篓恢复: ```bash test -d "$HOME/.Trash" export DSH_ACCEPT_DANGER_TRASH="$HOME/.Trash/$(basename "$DSH_ACCEPT_DANGER_TARGET")" test "$DSH_ACCEPT_DANGER_TRASH" = "$HOME/.Trash/.dsh-auto-approve-acceptance-danger-$DSH_ACCEPT_DANGER_ID" test ! -e "$DSH_ACCEPT_DANGER_TRASH" mv "$DSH_ACCEPT_DANGER_TARGET" "$DSH_ACCEPT_DANGER_TRASH" test ! -e "$DSH_ACCEPT_DANGER_TARGET" test -f "$DSH_ACCEPT_DANGER_TRASH/marker.txt" test "$(sed -n '1p' "$DSH_ACCEPT_DANGER_TRASH/marker.txt")" = "$DSH_ACCEPT_DANGER_ID" ``` ## 5. Workspace Write:行为与未安装插件时一致 生成另一个位于工作区外且不存在的精确目标。新建独立 session,工作区仍设为 `$DSH_ACCEPT_REPO`,选择 **Workspace Write**,再发送与第 3 节等价的联网下载任务: ```bash export DSH_ACCEPT_WORKSPACE_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')" test "${#DSH_ACCEPT_WORKSPACE_ID}" -eq 36 case "$DSH_ACCEPT_WORKSPACE_ID" in *[!0-9a-f-]*|'') printf 'invalid workspace id: %s\n' "$DSH_ACCEPT_WORKSPACE_ID" >&2; exit 1 ;; esac test -d "$HOME/.cache" export DSH_ACCEPT_WORKSPACE_TARGET="$HOME/.cache/dsh-auto-approve-acceptance-workspace-$DSH_ACCEPT_WORKSPACE_ID.html" case "$DSH_ACCEPT_WORKSPACE_TARGET" in "$HOME"/.cache/dsh-auto-approve-acceptance-workspace-*.html) ;; *) printf 'unexpected workspace target: %s\n' "$DSH_ACCEPT_WORKSPACE_TARGET" >&2; exit 1 ;; esac case "$DSH_ACCEPT_WORKSPACE_TARGET" in "$DSH_ACCEPT_REPO"|"$DSH_ACCEPT_REPO"/*) printf 'workspace target is inside the session workspace: %s\n' "$DSH_ACCEPT_WORKSPACE_TARGET" >&2 exit 1 ;; esac test ! -e "$DSH_ACCEPT_WORKSPACE_TARGET" printf '%s' "只执行下面这一条命令一次,不要改写命令,也不要运行任何其他命令:curl -fsS --location --max-time 20 https://example.com -o '$DSH_ACCEPT_WORKSPACE_TARGET'。出现审批弹窗后停止等待。" | pbcopy ``` 选择 **Workspace Write** 后必须等选择器恢复可点击并仍显示 **Workspace Write**,再发送任务;不能在禁用/加载状态发送。通过条件:与未安装本插件的原生 `workspace-write + approval: ask` 一样,必须出现人工审批弹窗。点击 **Reject/拒绝**,再点击 **Session log** 下载 ZIP,把绝对路径填入下面变量并执行: ```bash export DSH_ACCEPT_WORKSPACE_ZIP='/Users/ricksanchez/Downloads/dsh-session-把实际文件名补完整.zip' test -f "$DSH_ACCEPT_WORKSPACE_ZIP" unzip -tq "$DSH_ACCEPT_WORKSPACE_ZIP" session.jsonl unzip -p "$DSH_ACCEPT_WORKSPACE_ZIP" session.jsonl | jq -s -e ' [.[] | select(.type == "permission/preset" and .data.preset == "workspace-write")] as $presets | [.[] | select(.type == "approval/asked" or .type == "approval/decided")] as $events | ($events | group_by(.data.id)) as $groups | ($groups[0] // []) as $group | ([$group[] | select(.type == "approval/asked")]) as $asked | ([$group[] | select(.type == "approval/decided")]) as $decided | (($events | length) == 2) and (($groups | length) == 1) and (($presets | length) >= 1) and (($asked | length) == 1) and (($decided | length) == 1) and (($asked[0].data.id | type) == "string") and (($asked[0].data.id | length) > 0) and ($asked[0].data.id == $decided[0].data.id) and (($presets[-1].seq | type) == "number") and (($asked[0].seq | type) == "number") and (($decided[0].seq | type) == "number") and ($presets[-1].seq < $asked[0].seq) and ($asked[0].seq < $decided[0].seq) and ($decided[0].data.outcome == "rejected") ' test "$DSH_ACCEPT_WORKSPACE_TARGET" = "$HOME/.cache/dsh-auto-approve-acceptance-workspace-$DSH_ACCEPT_WORKSPACE_ID.html" test ! -e "$DSH_ACCEPT_WORKSPACE_TARGET" ``` `jq` 必须输出 `true`。这证明 `sandboxed-auto` 之外的预设仍由宿主人工 responder 处理。 ## 6. `/auto-report`:会话隔离、重启清空与完整日志边界 先回到第 3 节的 Auto session,把命令复制到剪贴板、粘贴发送: ```bash printf '%s' '/auto-report' | pbcopy ``` 报告必须包含 `Auto 权限审批台账 / Auto approval report for this session`、`自动批准 1 条 / Auto-approved`、`危险清单拦截 0 条 / Danger-list handoff`、`分类器转人工 0 条 / Classifier-to-human`,并列出第 3 节命令摘要。这里的 `Auto-approved` 来自插件本次运行的内存记录;不要仅凭 Session log 中的 `allowed-once` 猜测批准者。 然后新建一个不执行其他任务的空白 session,在该新 session 再发送同一个 `/auto-report`。三组计数必须全部为 0,证明报告按 session 隔离,不会把第 3 节记录带进另一个会话。 接着在运行 `dsh web` 的专用终端按 `Ctrl-C` 停止服务,并在同一终端重新执行以下完整命令: ```bash export PATH='/opt/homebrew/opt/node/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin' export DSH_ACCEPT_BIN='/Users/ricksanchez/.npm/_npx/1e7f6d9597241db0/node_modules/.bin/dsh' cd /Users/ricksanchez/SmallProject/dsh-plugins "$DSH_ACCEPT_BIN" web --host 127.0.0.1 --port 3080 ``` 服务恢复后重新打开第 3 节的原 Auto session,再发送 `/auto-report`。三组计数此时也必须全部为 0,并显示报告会在 dsh 重启或插件 reload 后清空的提示。第 3 节下载的 Session log ZIP 仍应通过下面的完整日志与调优检查;这证明内存报告被清空不等于持久会话审计丢失: ```bash unzip -p "$DSH_ACCEPT_AUTO_ZIP" session.jsonl > "$DSH_ACCEPT_TMP/auto-session.jsonl" test -s "$DSH_ACCEPT_TMP/auto-session.jsonl" npm run tune -- "$DSH_ACCEPT_TMP/auto-session.jsonl" > "$DSH_ACCEPT_TMP/tune-report.txt" rg -Fq '无法区分自动批准或人工批准' "$DSH_ACCEPT_TMP/tune-report.txt" rg -Fqx '未提供自定义规则,仅执行日志统计' "$DSH_ACCEPT_TMP/tune-report.txt" ``` 调优输出只能把信号和规则列为人工复核候选,不得把 `allowed-once` 标成自动或人工来源。跨 session 的空报告、重启后的空报告、原 Session log 的审批对,以及上述两条调优声明必须同时成立。 ## 7. 图标显示与静默自禁用 先选择 **Auto** 并打开 Permissions 菜单。在浏览器 DevTools Console 粘贴: ```js [...document.querySelectorAll('[data-dsh-auto-approve-icon]')].map(node => ({ kind: node.getAttribute('data-dsh-auto-approve-icon'), label: node.getAttribute('aria-label') ?? node.textContent.trim(), })) ``` 结果必须同时包含 `trigger` 和 `menu`;盾牌闪电图标应与原生图标对齐,并在 GitHub 风格的亮、暗主题下都清晰。切换四档并重新打开菜单,权限选择必须仍能正常工作且不能出现重复图标。 用下面三段 Console 命令模拟 dsh 升级后无障碍文案不再匹配。它只临时改当前页面的 DOM,不改 profile 或上游代码: ```js window.__dshAutoApproveAcceptance = (() => { const node = document.querySelector('[data-dsh-auto-approve-icon="trigger"]') if (!node) throw new Error('Auto trigger was not marked') const ariaLabel = node.getAttribute('aria-label') node.setAttribute('aria-label', 'Compatibility probe: unmatched label') return { node, ariaLabel } })() ``` ```js await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))) if (document.querySelector('[data-dsh-auto-approve-icon]')) { throw new Error('compatibility layer did not self-disable') } 'icon layer self-disabled; permission UI remains available' ``` ```js window.__dshAutoApproveAcceptance.node.setAttribute( 'aria-label', window.__dshAutoApproveAcceptance.ariaLabel, ) await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))) if (!document.querySelector('[data-dsh-auto-approve-icon="trigger"]')) { throw new Error('compatibility layer did not recover') } delete window.__dshAutoApproveAcceptance 'icon layer recovered' ``` 通过条件:不匹配时只有兼容层图标消失,Permissions 按钮、菜单和四档切换仍正常;恢复文案后图标自动回来。再重复第 3 节的 Auto 任务,确认视觉兼容层的自禁用与恢复均未改变审批语义。 ## 8. 记录结果 保存本次版本、提交、测试和临时证据路径: ```bash { "$DSH_ACCEPT_NODE" --version "$DSH_ACCEPT_BIN" --version git rev-parse HEAD printf 'evidence=%s\n' "$DSH_ACCEPT_TMP" } | tee "$DSH_ACCEPT_TMP/acceptance-summary.txt" ``` 发布记录应至少包含:88 项测试全绿、dump-config 四档断言通过、真人明确授权的 Auto 例行任务为 `allowed-once`、危险命令与 Workspace Write 均转人工、`/auto-report` 的 session 隔离与重启清空通过、调优脚本未虚构批准者、图标显示与静默自禁用/恢复通过。