dsh-jev-guard 0.5.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.
- package/CHANGELOG.md +285 -0
- package/CHANGELOG.zh-CN.md +271 -0
- package/DEPLOY.md +202 -0
- package/DEPLOY.zh-CN.md +200 -0
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/README.zh-CN.md +315 -0
- package/START-HERE.md +97 -0
- package/START-HERE.zh-CN.md +97 -0
- package/adapters/README.md +37 -0
- package/adapters/README.zh-CN.md +37 -0
- package/adapters/dsh/index.js +502 -0
- package/bin/guard.mjs +634 -0
- package/config.example.json +52 -0
- package/cordis.patch.yml +120 -0
- package/docs/AGENT-TASK-dsh.md +134 -0
- package/docs/AGENT-TASK-dsh.zh-CN.md +131 -0
- package/docs/ARCHITECTURE.md +118 -0
- package/docs/ARCHITECTURE.zh-CN.md +117 -0
- package/docs/DECISIONS.md +469 -0
- package/docs/DECISIONS.zh-CN.md +449 -0
- package/docs/DSH-INTEGRATION.md +178 -0
- package/docs/DSH-INTEGRATION.zh-CN.md +171 -0
- package/docs/MEASUREMENTS.md +433 -0
- package/docs/MEASUREMENTS.zh-CN.md +450 -0
- package/docs/USER-INTERVENTION.md +141 -0
- package/docs/USER-INTERVENTION.zh-CN.md +143 -0
- package/docs/VERIFICATION.md +279 -0
- package/docs/VERIFICATION.zh-CN.md +278 -0
- package/lib/audit.js +228 -0
- package/lib/gate.js +720 -0
- package/lib/i18n.js +575 -0
- package/lib/quota.js +389 -0
- package/lib/rules.js +174 -0
- package/lib/token.js +154 -0
- package/lib/verdict.js +285 -0
- package/package.json +82 -0
- package/tools/check-doc-pairs.mjs +158 -0
- package/tools/extract-commands.mjs +156 -0
- package/tools/gate-cli.mjs +240 -0
- package/tools/probe-prompt-lang.mjs +238 -0
- package/tools/probe-scripts.mjs +143 -0
- package/tools/report-result.mjs +146 -0
- package/tools/selftest-audit.mjs +93 -0
- package/tools/selftest-entry.mjs +177 -0
- package/tools/selftest-i18n.mjs +177 -0
- package/tools/selftest-quota.mjs +260 -0
- package/tools/selftest-reason.mjs +266 -0
- package/tools/selftest-rules.mjs +107 -0
- package/tools/selftest-token.mjs +100 -0
- package/tools/smoke-dsh-adapter.mjs +295 -0
- package/tools/smoke-dsh-pipeline.mjs +146 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# 人怎么介入 — 三条通道,以及它们的证据
|
|
2
|
+
|
|
3
|
+
> [English](USER-INTERVENTION.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
阀门在一个"没人看着"的模式下工作(YOLO / 完全权限 / 自动批准),所以**"人怎么进来"必须是设计的一部分**,
|
|
6
|
+
不能靠"反正会弹窗"。这份文档把三条通道写清楚:各自需要什么、谁执行、拿什么证明它真的有效。
|
|
7
|
+
|
|
8
|
+
> 所有结论都带**实测证据**(2026-09-20,DSH)。没测过的写"未验证",不写成事实。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 0. 一句话
|
|
13
|
+
|
|
14
|
+
**人只有三种介入方式:投递一次执行权、自己动手、当场点头。** 三种都实现,才算真的"有人管"。
|
|
15
|
+
|
|
16
|
+
| 通道 | 谁执行 | 阀门角色 | 依赖宿主? |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| **① 一次性令牌** | **AI**(被拦后重试) | 校验令牌 → 放行**那一条命令**一次 | **不依赖** —— 纯本地哈希 + 文件 |
|
|
19
|
+
| **② 宿主审批** | **AI**(经人点头) | 只负责把命令标成"需要人看" | 依赖宿主的问人能力 |
|
|
20
|
+
| **③ 人工手动执行** | **人** | **完全不参与** | 不依赖 |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. 通道①:一次性令牌(宿主无关,任何时候都在)
|
|
25
|
+
|
|
26
|
+
**机制。** 被判定为 `revise` / `block` / `escalate` 的命令,理由里会附一个
|
|
27
|
+
`ALLOW-XXXXXXXXXX`(= `sha256(规范化命令文本)` 的前 10 位十六进制,大写)。
|
|
28
|
+
人把它写进 `~/.jev-guard/allow.txt` 后,AI **重试同一条命令**即被放行一次,令牌随即删除。
|
|
29
|
+
|
|
30
|
+
**六条性质**(每条都有测试):
|
|
31
|
+
|
|
32
|
+
| 性质 | 含义 | 证据 |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| **绑定命令原文** | 换一个字就是另一个令牌;换写法蹭不到授权 | 带尾斜杠的变体被拦,给的是**另一个**令牌 `ALLOW-ADCD5EA86D` |
|
|
35
|
+
| **一次性** | 用掉即删,无法重放 | 放行后 `allow --list` 为空 |
|
|
36
|
+
| **确定性** | 同一条命令永远同一个令牌 | 同一条命令三次出现,永远是 `ALLOW-5031AC2085` |
|
|
37
|
+
| **不越过 L0 deny** | `dd` 写盘 / 格式化 / 删库这类"永不允许"**故意不给令牌** | 理由里**不出现**授权行 |
|
|
38
|
+
| **只在交互终端授权** | `guard allow` 要求 stdin 是 TTY;AI 自己跑会被拒 | 非 TTY 下明确拒绝并打印绝对路径整行命令 |
|
|
39
|
+
| **审计留痕** | 放行记录保留原判定的危险度 | `{source:"token", p:0.83, token:"ALLOW-…", overridden:"block"}` |
|
|
40
|
+
|
|
41
|
+
**为什么这个通道重要:** 它是**唯一不依赖宿主**的人工通道。宿主没有问人能力(或问不了)时,
|
|
42
|
+
它是人仍然能"只放行这一次"的办法。代价是复制粘贴一次。
|
|
43
|
+
|
|
44
|
+
**理由里那一行是给人整行复制粘贴用的**:绝对路径、命令原文**不截断**、引号已转义
|
|
45
|
+
(回归测试会把它喂给 `bash -c 'printf %s …'` 要求逐字节还原)。
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. 通道②:宿主审批(有则更强,没有也不致命)
|
|
50
|
+
|
|
51
|
+
阀门把 `escalate` 交给宿主,由宿主决定用什么形式问人。**同一套四态、同一份理由**,形态不同:
|
|
52
|
+
|
|
53
|
+
| 会话策略 | 宿主动作 | 阀门记录 | 人怎么做 |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| `ask`(带审批) | **弹审批框** | `action=escalate, decision=ask, policy=ask` | 在弹窗里点 —— 不开终端、不复制命令 |
|
|
56
|
+
| `never`(完全权限) | 没有弹窗可用 → **直接拒绝** | `action=escalate, decision=deny` | 走通道①或在终端自己执行 |
|
|
57
|
+
|
|
58
|
+
**实测(DSH,2026-09-20):** 用户把会话切到 `ask` 后,同一条被拦命令触发了一次真实审批 ——
|
|
59
|
+
会话日志出现成对的 `approval/asked` + `approval/decided`,`asked.reason` **就是阀门理由的原文**
|
|
60
|
+
(含硬规则 id 与 why),用户点允许后 `outcome=allowed-once`(从弹出到点下间隔 2.2 秒),
|
|
61
|
+
命令随即执行、靶子文件 148 → 0 字节。这条通道**不需要人开终端**。
|
|
62
|
+
|
|
63
|
+
### 2.1 DSH 的答案集是**闭集**:没有"永久允许"
|
|
64
|
+
|
|
65
|
+
这一点被专门核实过(源码依据,`deepseek-harness` @ `ddefc45fbc`):
|
|
66
|
+
|
|
67
|
+
| 位置 | 内容 |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `packages/interaction/user-approval/src/types.ts:32` | `ApprovalOutcome = 'allowed-once' \| 'rejected' \| 'cancelled' \| 'unavailable'` |
|
|
70
|
+
| `packages/interaction/user-approval/src/index.ts:204` | 注释:`'allowed-once' is the only grant` |
|
|
71
|
+
| `packages/client/ui-approval/.../slots.ts:64` | `ApprovalDecision = 'allowed-once' \| 'rejected'` —— 界面只有两个按钮 |
|
|
72
|
+
| `packages/session/session-format-v0-to-v1/src/payload-validation.ts:40,43` | 校验 outcome 与 policy(`ask`/`never`)都是闭集 |
|
|
73
|
+
| `packages/interaction/user-approval/tests/invariant.spec.ts:103` | 反向断言 `policy: 'always'` **必须被拒** |
|
|
74
|
+
|
|
75
|
+
**设计含义(对阀门是好消息):** `ask` 模式下每一次 `escalate` 都是一次**独立的逐次决定**,
|
|
76
|
+
没有任何常驻授权可以把阀门的 ask 悄悄吞掉。所以阀门**不需要**维护"用户已永久同意"这类状态 ——
|
|
77
|
+
也就不存在那份状态被绕过或过期的风险。
|
|
78
|
+
|
|
79
|
+
### 2.2 只有 DSH 这一条被支持(其余已归档)
|
|
80
|
+
|
|
81
|
+
2026-09-20 起本包**只支持 DSH**(见 [`DECISIONS.md`](./DECISIONS.md) D11)。历史上试过的其它执行通道
|
|
82
|
+
都没有做成:各自宿主的审批/信任机制不同,把某一条做扎实是各自独立的一轮工作;
|
|
83
|
+
其中一次实测还暴露出一个结构性缺陷 —— **拿不到的信息被一个默认值假装成拿到了**,
|
|
84
|
+
于是"宿主能不能问人"这件事在里面等于永远是否,宿主审批通道不可达。
|
|
85
|
+
|
|
86
|
+
判断标准只有一条:**那个宿主有没有"执行前必须经过、且能说不许执行"的回调。**
|
|
87
|
+
没有就只能做建议层(模型可以不遵守),而**未验证的适配器比没有适配器更危险**——
|
|
88
|
+
它看起来装了阀门,实际不拦。那些实现已随范围收窄一并移除;可迁移的教训见
|
|
89
|
+
[`MEASUREMENTS.md`](./MEASUREMENTS.md) §12 与 [`DECISIONS.md`](./DECISIONS.md) D11。
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 3. 通道③:人直接执行(阀门不参与)
|
|
94
|
+
|
|
95
|
+
人可以在自己终端里直接把那条命令跑了 —— 那**不经过阀门**,也不会给 AI 任何权限。
|
|
96
|
+
|
|
97
|
+
**实测(2026-09-20):** 人在终端里把靶子文件截成 0 字节(153 → 0),然后:
|
|
98
|
+
|
|
99
|
+
- 审计日志**零新增** —— 精确过滤后,那条命令的判定记录仍是 3 条(2 次 escalate + 1 次令牌放行)。
|
|
100
|
+
阀门审计的是 **agent 的动作**,不是人的动作:它守的是你,不是监视你。
|
|
101
|
+
- AI 随后重试**一字不差的同一条命令**,仍然被拦,并且公示**同一个**令牌。
|
|
102
|
+
|
|
103
|
+
**结论:`人做了一次` ≠ `给 agent 开了口子`。** 后者必须显式投递(通道①或②)。
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 4. 为什么阀门不需要记住"人已经同意了"
|
|
108
|
+
|
|
109
|
+
因为三条通道里**没有一条需要阀门记忆**:
|
|
110
|
+
|
|
111
|
+
- 通道①:令牌文件即事实,读一次、消费一次、删掉;
|
|
112
|
+
- 通道②:宿主决定,阀门每次都重新问;
|
|
113
|
+
- 通道③:阀门根本不在路上。
|
|
114
|
+
|
|
115
|
+
这条设计的价值在于:阀门**没有**一张"已批准清单"可以被改坏、被过期策略坑、被并发写乱。
|
|
116
|
+
每次判定都是一次干净的、可重放的独立计算。
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 5. 怎么在**新宿主**上验证这三条
|
|
121
|
+
|
|
122
|
+
协议化步骤在 [`VERIFICATION.md`](./VERIFICATION.md) 的 **U1–U3**(任何宿主都适用,
|
|
123
|
+
结论用 `tools/report-result.mjs --host <宿主> --item U1` 写回 `verification-results/`)。
|
|
124
|
+
|
|
125
|
+
三个最小判据:
|
|
126
|
+
|
|
127
|
+
- **U1**:被拦命令的理由里**有**令牌;走一遍"授权 → 重试 → 放行 → 令牌消失",审计里出现 `source=token`。
|
|
128
|
+
- **U2**:宿主**有没有**问人通道?若有,弹窗里是否带着阀门理由原文?点允许后命令是否真的执行?
|
|
129
|
+
若没有,写明"本宿主只能走通道①"。
|
|
130
|
+
- **U3**:人在终端里手动执行同一条命令 —— 审计**零新增**,且 AI 重试**仍然被拦**。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 6. 记录在案的错误推测(保持诚实)
|
|
135
|
+
|
|
136
|
+
2026-09-20,本项目曾推测:*"若用户在弹窗里选『永久允许』,宿主可能替阀门自动应答后续 ask,
|
|
137
|
+
使逐次确认退化为常驻策略。"*
|
|
138
|
+
|
|
139
|
+
**该推测不成立** —— DSH 没有这个选项(依据见 §2.1)。提出它的是 AI,指出错误的是**用户**。
|
|
140
|
+
入库记录见仓库记忆里的 `Correction: DSH has no persistent/always approval`。
|
|
141
|
+
|
|
142
|
+
留着这一节不是自我批评,而是为了说明一件事:**这份文档里"未验证"的标注不是客套**,
|
|
143
|
+
写下来的猜测会被当真,所以要么测,要么标注。
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# DSH acceptance checklist
|
|
2
|
+
|
|
3
|
+
> **English** | [简体中文](VERIFICATION.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
This checklist is **the ledger of "what exactly this package has been verified for on DSH"**, and also **the steps for how to re-verify it when you move to another machine**.
|
|
6
|
+
Every item says: how to verify it, the criteria, and its record number in `verification-results/`.
|
|
7
|
+
|
|
8
|
+
**The iron laws of acceptance (bought with three accidents):**
|
|
9
|
+
|
|
10
|
+
1. **Look at side effects, not at "it reported no error".** Only "the command really was refused" + "the log really has that entry" counts as passing.
|
|
11
|
+
This package has had three layers of silent failure: the script exits 0 without a sound, the command runs all the same, the log is blank (see `MEASUREMENTS.md` §10).
|
|
12
|
+
2. **Run it once on each of the two platforms.** Windows and WSL differ in path/quoting/module-resolution rules; passing on one platform does not mean passing on the other.
|
|
13
|
+
3. **Records must carry a timestamp and the original text.** The `at` / `token` / `source` fields are the only thing that can separate "I say it blocked" from "it really did block".
|
|
14
|
+
|
|
15
|
+
How to record (what you write is summarised automatically into `SUMMARY.md`):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
node tools/report-result.mjs --host dsh --item <item number> --status <pass|fail|partial|blocked|skipped> \
|
|
19
|
+
--evidence "evidence (with timestamp/token/key output)" --notes "additional notes or questions"
|
|
20
|
+
# when stuck: --status blocked --question "your question"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## A. The judgment layer (verifiable without installing DSH; purely offline + one network call)
|
|
26
|
+
|
|
27
|
+
### 6-pre · Adapter smoke test + the real tool pipeline
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node tools/smoke-dsh-adapter.mjs # fake ctx: wiring/assertions/approval policy/audit fields
|
|
31
|
+
node tools/smoke-dsh-pipeline.mjs # the real ToolRuntime five-stage pipeline (must be run from inside a DSH checkout)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Judging:** the smoke tests all pass (including "`policy` and `preset` are recorded in the audit"); the pipeline test gives the expected `ToolExecutionResult`.
|
|
35
|
+
|
|
36
|
+
### 7 · The false-positive defence line (must be run whenever a rule changes)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
node tools/selftest-rules.mjs
|
|
40
|
+
# two probes: a command whose text mentions a dangerous phrase, and an ordinary command ending in 2>/dev/null — neither may be blocked
|
|
41
|
+
echo "git push --force origin main" > /tmp/jev-anchor-test.txt
|
|
42
|
+
node bin/guard.mjs judge 'ls -la /var/log 2>/dev/null'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Judging:** the probes are not blocked (prose is left to Jev to judge; measured p≈0.02–0.08).
|
|
46
|
+
|
|
47
|
+
**Expanded 2026-09-20 — anchoring must be tested in **both directions** (48 cases: 25 old + 22 matrix + 1 performance):
|
|
48
|
+
|
|
49
|
+
| Direction | The shape it must pin down | Expectation |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| Prevent false positives | arguments inside quotes, comments, variable assignments, strings inside python `-c`/heredoc, grep arguments, **code strings** like `c.startswith('mkfs.ext4 …')` | no hit (left to Jev) |
|
|
52
|
+
| Prevent missed detections | real commands inside a multi-line heredoc / a multi-line `bash -c "`; `\| xargs`, `timeout 30`, `nice -n 5`, `find … -exec`, multi-level wrapping | **a hit on the corresponding rule** |
|
|
53
|
+
| Prevent collateral damage to prose | `xargs 删除 mkfs.ext4 …` (what follows the wrapper is Chinese) | no hit |
|
|
54
|
+
| Prevent catastrophic backtracking | a 4KB pure-wrapper prefix (the worst-case input) | < 50ms (measured 0.6ms) |
|
|
55
|
+
|
|
56
|
+
> **Looking only at false positives misses half the problem.** The first round of fixes only tested
|
|
57
|
+
> "prose must not be a hit", so nobody noticed that the anchoring was missing the `m` flag, which made
|
|
58
|
+
> **every real command inside a multi-line script a missed detection** (see [`MEASUREMENTS.md`](./MEASUREMENTS.md) §7.5
|
|
59
|
+
> and [`DECISIONS.md`](./DECISIONS.md) D2). When you change a rule, **run both directions**.
|
|
60
|
+
> One more quantity that is always unchanging: the `RULE_STATS.anywhere` printed by `staticRule` must
|
|
61
|
+
> **always be 2** (`redirect-to-device`, `fork-bomb`); if it grows, that means another rule has fallen back to whole-text matching.
|
|
62
|
+
|
|
63
|
+
### 8 · Audit log (offline) + 8-fix (running instance)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
node tools/selftest-audit.mjs # masking/appending/rotation/summary/blank logPath
|
|
67
|
+
node tools/selftest-i18n.mjs # bilingual copy: catalogue completeness/placeholders/leftover English/promptLang does not follow the UI language
|
|
68
|
+
node bin/guard.mjs log --tail 5 # the running instance really has entries
|
|
69
|
+
node bin/guard.mjs log --stats
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Judging:** the offline suites all pass; **and** the running instance yields real records (there was once a case of "the valve was working and the log had not a single entry").
|
|
73
|
+
|
|
74
|
+
### 13 · Quota degradation (offline)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
node tools/selftest-quota.mjs # stand-in fetch: 402/401/403/two kinds of 429/5xx/timeout/network/bad state file
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Judging:** all pass. Three things to confirm specifically: a persistent failure **degrades**, a transient failure **does not degrade**,
|
|
81
|
+
and while degraded L0 still blocks and there are **zero HTTP requests**.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## B. After installing it into DSH
|
|
86
|
+
|
|
87
|
+
### 6 · The probe is blocked after installation
|
|
88
|
+
|
|
89
|
+
Run a command that **is certain to be blocked** (costs nothing):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# in a DSH session, have the AI execute: git push --force origin main
|
|
93
|
+
node bin/guard.mjs log --tail 1
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Judging:** the command really is refused, the reason contains `hard rule git-force-push hit`; that entry appears in `guard.log`.
|
|
97
|
+
**If it does not pass, read first** `DSH-INTEGRATION.md` §5 (three layers of silent failure).
|
|
98
|
+
|
|
99
|
+
### 9 · The one-shot token closed loop
|
|
100
|
+
|
|
101
|
+
1. Have the AI execute a real command that will be blocked (for example `rm -rf <a demo directory>`).
|
|
102
|
+
2. The reason should contain `ALLOW-XXXXXXXXXX` + one line of **absolute-path** authorisation command.
|
|
103
|
+
3. **The human** pastes that line into their own terminal (a non-TTY is refused — that is the correct behaviour).
|
|
104
|
+
4. Have the AI **retry the exact same command, character for character**.
|
|
105
|
+
|
|
106
|
+
**Judging:** the command really is executed, the token file goes empty, and `guard.log` shows `source: token` and `overridden: <original action>`.
|
|
107
|
+
Also verify the binding: change one character in the command → it is **still blocked**, and what is published is **a different** token.
|
|
108
|
+
|
|
109
|
+
### 10 · The authorisation entry point and the reason wording
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
echo | node bin/guard.mjs allow 'rm -rf /tmp/x' # non-TTY: should be refused and print the whole command line
|
|
113
|
+
node tools/selftest-reason.mjs # 28+ cases: absolute path/no truncation/quote escaping/policy branching
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Judging:** the non-TTY is refused and gives the whole line in a copyable form; `selftest-reason` all passes.
|
|
117
|
+
**Windows addition:** quotes in the reason must be in **PowerShell** form (`''` escaping); `--command-file` is available.
|
|
118
|
+
|
|
119
|
+
### 14 · Degradation is visible in a real session
|
|
120
|
+
|
|
121
|
+
Inject a degraded state (fault injection), then run two commands:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# write a kind=quota ~/.jev-guard/degraded.json (with until set in the future)
|
|
125
|
+
# then: mkfs.ext4 /dev/whatever → L0 refuses it; the tail of the reason should carry a ⚠️ degradation warning
|
|
126
|
+
# touch /tmp/whatever → source=degraded, ms=0 (zero requests)
|
|
127
|
+
node bin/guard.mjs status --clear # wrap up: clear the injected state
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Judging:** the warning appears in the **refusal reason**, the audit has one `level: warn` entry, a non-L0 command is `source=degraded` with `ms=0`;
|
|
131
|
+
after `status --clear` it goes back to "✅ healthy" (exit code 0).
|
|
132
|
+
|
|
133
|
+
### 16 · The cross-platform entry guard (run it once on WSL **and** Windows)
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
node tools/selftest-entry.mjs # WSL
|
|
137
|
+
# Windows (if DSH/Windows, or this machine, has node.exe):
|
|
138
|
+
"C:\Program Files\nodejs\node.exe" T:\dsh-jev-guard\tools\selftest-entry.mjs
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Judging:** both platforms pass everything. **Only Windows can expose** that class of problem — "a drive letter + backslash in argv[1]";
|
|
142
|
+
if you only ran WSL, mark it `partial` rather than `pass`.
|
|
143
|
+
|
|
144
|
+
### 17 · Platform-dependent shell quoting (Windows)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
node tools/selftest-reason.mjs # includes a real PowerShell round-trip + the negative case "the POSIX form fails to parse in PS"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Judging:** all pass. Check by hand: paste that line from the reason into **PowerShell**; `--list` should show the token that was published.
|
|
151
|
+
|
|
152
|
+
### 21 · Activation check after restarting once renamed to `dsh-jev-guard`
|
|
153
|
+
|
|
154
|
+
These changes — the rename, the new `preset` field in the audit, the degradation/approval copy fixes, the removal of `serve`/`mcp` — **all take effect only after DSH is restarted**.
|
|
155
|
+
After the restart, check three things in order, then do one more real block:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
node bin/guard.mjs status # ① exit code 0 and prints "✅ ... healthy"
|
|
159
|
+
dsh --profile <yours> --dump-config | grep -A2 jev-guard # ② the bundle's id and name are both dsh-jev-guard
|
|
160
|
+
tail -n 1 ~/.jev-guard/guard.log # ③ the new entry should carry policy and preset as well
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Judging:** ① and ② must pass (**with a `degraded.json` present, the `status` exit code is 3** — that is degradation, not a fault).
|
|
164
|
+
③ requires `preset` (such as `danger-full-access` / `workspace-write`) to appear in an entry written **after the restart** —
|
|
165
|
+
this is the hard evidence that "what is running is the renamed new adapter"; the old version has no such field; ditto `policy`.
|
|
166
|
+
|
|
167
|
+
Finally hand it one command that **was going to be blocked anyway** for an end-to-end re-verification (pick a harmless one, e.g. `truncate -s 0` on a /tmp probe file),
|
|
168
|
+
and confirm three things: the block reason is given as usual, the corresponding entry appears in the audit (`action=escalate` / `decision=deny` /
|
|
169
|
+
`source=static-rule` + the same token), and **the command really was not executed** (the probe file does not exist = the block happened before the fact, not a warning after it).
|
|
170
|
+
|
|
171
|
+
### 22 · The verdict action branches with the approval policy (`ask` hands it to a human / `never` blocks hard / L0 is an absolute gate)
|
|
172
|
+
|
|
173
|
+
This changes [`DECISIONS.md`](./DECISIONS.md) **D13**. First run the two offline suites; they cover the routing matrix itself:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
node tools/selftest-reason.mjs # 56 cases: includes the revise/block × ask/never × L0 routing matrix
|
|
177
|
+
node tools/smoke-dsh-adapter.mjs # 10 groups: includes "an L0 hard rule tried 4 times in a row is always deny (the retry budget does not upgrade it into a prompt)"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Then **both sides must be run on the real deployment** (the policy can be switched inside a session; no restart needed):
|
|
181
|
+
|
|
182
|
+
| Scenario | What command to hand it | Expectation |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `ask` + grey zone | a command landing at 50–70% (look at `p` in `guard.log`) | **the approval prompt pops up**; the reason header is "needs human confirmation" and it carries the three degradation templates; the token authorisation line **does not appear** |
|
|
185
|
+
| `never` + the same one | as above | **a plain refusal**, with the token authorisation line attached |
|
|
186
|
+
| `ask` + L0 hard rule | `git push --force origin main` (in a repo with no remote or a safe repo) | **a plain refusal, no prompt**; audit `decision=deny` |
|
|
187
|
+
| `ask` + L0 hard rule tried 4 times in a row | as above, submitted repeatedly | still **not a single prompt** (the budget does not upgrade hard rules); `attempts` in the audit increments up to 4 |
|
|
188
|
+
|
|
189
|
+
**Judging:** both offline suites pass + all four rows on the real deployment match. **Running only the `never` side does not count as passing** (the routing branch is exactly what this change introduced),
|
|
190
|
+
mark `partial`. After approving one, remember to confirm: the approved command **really was executed** (`kind: 'ask'` passes once the host has approved it),
|
|
191
|
+
which shows that handing it to a human is not "the block reworded".
|
|
192
|
+
|
|
193
|
+
### 23 · Bilingual copy and the language switch
|
|
194
|
+
|
|
195
|
+
Mechanics in [`DECISIONS.md`](./DECISIONS.md) **D14**, measurements in [`MEASUREMENTS.md`](./MEASUREMENTS.md) §14.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
node tools/selftest-i18n.mjs # 24 cases: same keys in both languages/identical placeholders/no leftover Chinese in English/the question is unaffected by the UI language
|
|
199
|
+
node tools/selftest-entry.mjs # 20 cases: includes --lang / JEV_GUARD_LANG / "the switch's value is not a positional argument"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Four rows on the real deployment (each one must be **looked at once in each language**):
|
|
203
|
+
|
|
204
|
+
| Scenario | Command | Expectation |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| Default language | `node bin/guard.mjs status` | when not set explicitly = `zh-CN` (the system locale is not consulted; see the lesson about the WSL `en-US` fallback value in D14) |
|
|
207
|
+
| Explicit switch | `node bin/guard.mjs status --lang en` | all English; `--lang zh-CN` all Chinese |
|
|
208
|
+
| Environment variable | `JEV_GUARD_LANG=en node bin/guard.mjs rules` | the rule-list reasons become English (the rule ids do not change) |
|
|
209
|
+
| The verdict does not change | run the same batch of commands once per language with `judge --json` | `action` / `p` / `source` are **identical field by field**; only the reason copy differs |
|
|
210
|
+
|
|
211
|
+
**Judging:** both offline suites pass + all four rows on the real deployment match. **Running only one language does not count as passing** — what this item verifies is exactly "the verdict is consistent in both languages and
|
|
212
|
+
the copy is correct in each". Also confirm: changing `lang` **must not** change the action/decision in `guard.log` (compare the same batch of commands before and after).
|
|
213
|
+
|
|
214
|
+
> `promptLang` is not among this item's pass conditions: it is not a copy switch but a judging parameter, and switching it amounts to re-calibration,
|
|
215
|
+
> see MEASUREMENTS §14 — it only counts once it has been re-measured with `tools/probe-prompt-lang.mjs --repeat 3`.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## C. The three human-intervention channels (must be run on any machine)
|
|
220
|
+
|
|
221
|
+
The mechanics and properties of the three channels are in [`USER-INTERVENTION.md`](./USER-INTERVENTION.md).
|
|
222
|
+
|
|
223
|
+
### U1 · The one-shot token channel (**the host-independent** one)
|
|
224
|
+
|
|
225
|
+
1. Produce a block and note down the token published in the reason.
|
|
226
|
+
2. Paste the authorisation line into the human's own terminal; `node bin/guard.mjs allow --list` should show that token.
|
|
227
|
+
3. Have the AI retry **the exact same command, character for character** → it is allowed, the token disappears, `guard.log` records `source=token`.
|
|
228
|
+
|
|
229
|
+
### U2 · The host approval channel (DSH has it, **must be tested**)
|
|
230
|
+
|
|
231
|
+
1. Switch the session to a mode with approval (`approval: ask`).
|
|
232
|
+
2. Trigger one `escalate`-class block (a command that hits an L0 `ask` rule, such as `truncate -s 0 <demo file>`).
|
|
233
|
+
3. **The human** should really see the approval prompt, and the reason in it **is the valve's own original text** (hard-rule id + why), and
|
|
234
|
+
the "copy into the terminal to authorise" line is **no longer attached** (the human is right in front of the window).
|
|
235
|
+
|
|
236
|
+
**Judging:** the prompt appears and carries the original text; after clicking allow the command executes (`outcome=allowed-once`).
|
|
237
|
+
The session log shows a matching pair of `approval/asked` + `approval/decided`.
|
|
238
|
+
|
|
239
|
+
### U3 · Running it by hand ≠ authorising the AI (counter-intuitive, but it must be verified)
|
|
240
|
+
|
|
241
|
+
1. Have the human execute that blocked command **directly** in the terminal (no token, no prompt).
|
|
242
|
+
2. Watch two things: the judgment record for that command in the audit gains **zero new entries**; have the AI retry the same command → it is **still blocked**.
|
|
243
|
+
|
|
244
|
+
**Judging:** "zero new entries + still blocked" = pass. Failure means there is an unnoticed authorisation leak.
|
|
245
|
+
|
|
246
|
+
> When counting the audit, mind one trap: `guard.log` records **the text of every command that went through judging**,
|
|
247
|
+
> so when you count a command with a substring `grep`, **your own checking command** (which quotes that text) gets counted too.
|
|
248
|
+
> Filter precisely with "the `command` field starts with that command".
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## D. Number quick reference
|
|
253
|
+
|
|
254
|
+
| Number | What it verifies | Record |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| 6-pre | adapter smoke test + the real tool pipeline | ✅ pass |
|
|
257
|
+
| 6 | probe blocked after installation | ✅ pass |
|
|
258
|
+
| 7 | the false-positive defence line | ✅ pass |
|
|
259
|
+
| 8 / 8-fix | audit log (offline / running instance) | ✅ pass |
|
|
260
|
+
| 9 | the token closed loop | ✅ pass |
|
|
261
|
+
| 10 | authorisation entry point and reason wording | ✅ pass |
|
|
262
|
+
| 11 | the three human channels (accepted by hand by the user) | ✅ pass |
|
|
263
|
+
| 12 | the host approval channel | ✅ pass |
|
|
264
|
+
| 13 | quota degradation (offline + CLI) | ✅ pass |
|
|
265
|
+
| 14 | degradation visible in a real session | ✅ pass |
|
|
266
|
+
| 15 | the `ask` branch copy branches | ✅ pass |
|
|
267
|
+
| 16 | the cross-platform entry guard (WSL + Windows) | see `SUMMARY.md` |
|
|
268
|
+
| 17 | platform-dependent shell quoting (Windows) | see `SUMMARY.md` |
|
|
269
|
+
| 18 | narrowed to DSH only | see `SUMMARY.md` |
|
|
270
|
+
| 19 | non-DSH traces cleared out of the package | see `SUMMARY.md` |
|
|
271
|
+
| 20 | checking the package's current state (describes DSH only) | see `SUMMARY.md` |
|
|
272
|
+
| 21 | activation check after restarting once renamed to `dsh-jev-guard` | see `SUMMARY.md` |
|
|
273
|
+
| 22 | the verdict action branches with the approval policy (`ask` hands it to a human / `never` blocks hard / L0 absolute gate) | see `SUMMARY.md` |
|
|
274
|
+
| 23 | bilingual copy and the language switch (consistent verdicts in both languages) | see `SUMMARY.md` |
|
|
275
|
+
| U1–U3 | the three human-intervention channels | recorded under 11 / 12 |
|
|
276
|
+
|
|
277
|
+
History: this checklist once had several preliminary verifications (numbers 1–5) of "can some other execution channel carry the block", which were voided by
|
|
278
|
+
the decision to "support DSH only" — those implementations **have been removed from this package**, and the transferable lessons are kept in
|
|
279
|
+
[`MEASUREMENTS.md`](./MEASUREMENTS.md) §12 and [`DECISIONS.md`](./DECISIONS.md) D11.
|