deepseek-coordination 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +424 -2
- package/README.zh-CN.md +548 -0
- package/cordis.patch.yml +34 -0
- package/package.json +90 -4
- package/src/host/bin/dsh-os.bat +19 -0
- package/src/host/bin/dsh-os.ps1 +4935 -0
- package/src/host/bin/dsh-os.sh +3385 -0
- package/src/host/bin/dsh-os7.ps1 +5397 -0
- package/src/host/command.txt +136 -0
- package/src/host/docs/command-original.txt +26 -0
- package/src/host/docs/os-spec.md +528 -0
- package/src/host/docs/permission-model.md +206 -0
- package/src/host/spec/charsets.tsv +28 -0
- package/src/host/spec/verbs.tsv +120 -0
- package/src/permission/index.js +435 -0
- package/src/plugin/index.js +327 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eloy Kim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,425 @@
|
|
|
1
|
-
#
|
|
1
|
+
# deepseek-coordination
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cross-platform **atomic OS-interaction surface** for the DeepSeek Harness.
|
|
4
|
+
|
|
5
|
+
The problem this project solves is narrow and concrete: an agent that talks to the operating
|
|
6
|
+
system through *strings* gets the wrong answer whenever a path contains a space, a file carries a
|
|
7
|
+
UTF-8 BOM, a line ending is CRLF, a document is not UTF-8, or a command is assembled by
|
|
8
|
+
concatenation. This project replaces string assembly with an **argument-stable verb set** and makes
|
|
9
|
+
encoding, byte order marks and line endings explicit and byte-exact.
|
|
10
|
+
|
|
11
|
+
## What is here
|
|
12
|
+
|
|
13
|
+
| Path | What it is |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `src/host/lib/powershell5.1/*.ps1` | **The source of truth** for Windows PowerShell 5.1: the 16 modules listed in `tools/build.ps1`, plus the standalone `git.ps1` |
|
|
16
|
+
| `src/host/lib/powershell7.6/*.ps1` | **The source of truth** for PowerShell 7.6: the same 16 modules ported to .NET, plus the standalone `git.ps1` |
|
|
17
|
+
| `src/host/lib/*.sh` | **The source of truth** for POSIX: the 16 shell modules, plus the standalone `git.sh` |
|
|
18
|
+
| `src/host/bin/dsh-os.ps1` | **Generated** Windows PowerShell 5.1 implementation (218,514 bytes) |
|
|
19
|
+
| `src/host/bin/dsh-os7.ps1` | **Generated** PowerShell 7.6 implementation (255,565 bytes) |
|
|
20
|
+
| `src/host/bin/dsh-os.sh` | **Generated** POSIX implementation (142,744 bytes) |
|
|
21
|
+
| `src/host/bin/dsh-os.bat` | ASCII-only `cmd.exe` shim: sets code page 65001 and forwards arguments to the `.ps1`. Hand-written, not generated |
|
|
22
|
+
| `src/host/command.txt` | The authoritative verb contract, one line per verb |
|
|
23
|
+
| `src/host/spec/verbs.tsv` | Machine-readable verb table: positionals, flags, permission class, error codes. **86 verbs** |
|
|
24
|
+
| `src/host/spec/charsets.tsv` | Charset table: IANA name to .NET code page to `iconv` name, with BOM bytes. **15 charsets** |
|
|
25
|
+
| `src/host/docs/os-spec.md` | Full specification: contracts, error codes, platform constraints, golden matrix |
|
|
26
|
+
| `src/host/docs/permission-model.md` | Authorization model: the four decisions, the file schema, the two enforcement layers |
|
|
27
|
+
| `src/host/lib/powershell.md` | PowerShell 5.1 to 7.6 API-change reference. Not referenced by code, tests or `package.json`; not published |
|
|
28
|
+
| `tools/build.ps1` | Assembles the generated scripts from the modules; `-Check` fails if they drift |
|
|
29
|
+
| `tools/parse-check.ps1` | Parse-checks the PowerShell modules under the runtime that will execute them; also reports non-ASCII bytes and a BOM. Skips files that are not build inputs |
|
|
30
|
+
| `tools/json-field.mjs` | Reads one field of an envelope by dotted path. Used by the shell harnesses, because `sed` cannot see into a nested object |
|
|
31
|
+
| `tools/verify-bundle.mjs` | Verifies the loader's resolution contract without touching a live profile. **10 cases** |
|
|
32
|
+
| `tools/verify-plugin-load.mjs` | Runs the real `apply()` path against a stub registry. **13 cases** |
|
|
33
|
+
| `tools/verify-editions.mjs` | Proves both PowerShell artefacts are reachable and that the default is unchanged. **14 cases** |
|
|
34
|
+
| `tests/golden.ps1` | Windows golden cases driving the real script through argv. **49 cases** |
|
|
35
|
+
| `tests/golden-sh.sh` | POSIX golden cases, including that a failing call still emits an envelope. **116 cases** |
|
|
36
|
+
| `tests/smoke-desktop-sh.sh` | Asserts what the POSIX desktop verbs actually *inject*, using the fakes below. **52 cases** |
|
|
37
|
+
| `tests/fake-xdotool`, `tests/fake-wmctrl` | Recording stand-ins for the X tools, so the desktop verbs can be verified on a machine with no X server |
|
|
38
|
+
| `tests/encoding.ps1` | Encoding-contract guard (BOM, ASCII-only, line endings, BOM-free stdout). **32 build modules + the generated artefacts** |
|
|
39
|
+
| `tests/conformance.ps1` | Asserts `command.txt` and `spec/verbs.tsv` describe the same surface: same verbs both ways, same count, same R/W class, same positional names. **5 checks** |
|
|
40
|
+
| `tests/plugin.test.mjs` | Plugin execute path, argv handling, and bundle manifest shape. **13 cases** |
|
|
41
|
+
| `tests/permission.test.mjs` | Consent delegate: rule matching, deny-first, trust, audit bounds, atomic write. **18 cases** |
|
|
42
|
+
| `src/plugin/index.js` | Host plugin: registers the `dsh_os` tool |
|
|
43
|
+
| `src/permission/index.js` | Consent delegate: the only writer of `permission.json` |
|
|
44
|
+
| `src/client/` | Web client surface (Vite scaffold, not yet wired to the host) |
|
|
45
|
+
|
|
46
|
+
The two PowerShell runtimes are kept in separate directories because they differ in ways that are not
|
|
47
|
+
cosmetic: .NET Framework 4.8 has no `ProcessStartInfo.ArgumentList` and needs an ANSI-aware encoding
|
|
48
|
+
path, while .NET 10 has both. Neither file has to carry the other's workarounds.
|
|
49
|
+
|
|
50
|
+
## Modules
|
|
51
|
+
|
|
52
|
+
`src/host/lib/` holds one module pair per concern. `bin/dsh-os.ps1` and `bin/dsh-os.sh` are
|
|
53
|
+
generated from them by `tools/build.ps1`, so never edit the generated files.
|
|
54
|
+
|
|
55
|
+
| Module | Concern |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `preamble` | strict mode, encoding pins, layout and repository-root discovery |
|
|
58
|
+
| `json` | error model, JSON serialisation, the envelope writer |
|
|
59
|
+
| `command` | argument parsing (positional and `name="value"`), assertions, coercion |
|
|
60
|
+
| `charset` | charset table, BOM policy, byte-level text model |
|
|
61
|
+
| `native` | P/Invoke declarations and atomic replace (Windows only; empty on POSIX) |
|
|
62
|
+
| `paths` | path resolution and the protected-path boundary |
|
|
63
|
+
| `file` | `file.*`, `file verify.*`, `file search` |
|
|
64
|
+
| `system` | `system.*` queries |
|
|
65
|
+
| `execute` | `execute.command` / `.synchronous` / `.asynchronous` |
|
|
66
|
+
| `task` | `task.*` |
|
|
67
|
+
| `environment` | `environment.*` and `environment attribute.*` |
|
|
68
|
+
| `service` | `service.*` |
|
|
69
|
+
| `permission` | `permission.*` (three read-only verbs, three delegate-only writes) and `selfcheck` |
|
|
70
|
+
| `window` | `window.*` / `mouse.*` / `keyboard.*` / `clipboard.*` / `wait.*` / observe |
|
|
71
|
+
| `dispatch` | verb resolution and the dispatch table |
|
|
72
|
+
| `entry` | run, emit, exit |
|
|
73
|
+
|
|
74
|
+
A build step rather than dot-sourcing the modules at runtime, deliberately: the shipped script must
|
|
75
|
+
stay one self-contained file that the harness and a human can invoke directly, with no module
|
|
76
|
+
resolution and no relative-path assumptions. On Windows PowerShell 5.1 a sourced file without a BOM
|
|
77
|
+
is decoded in the ANSI code page, which is the exact encoding fault this project exists to remove.
|
|
78
|
+
|
|
79
|
+
## Design rules
|
|
80
|
+
|
|
81
|
+
1. **Values are separate argv elements.** Commands are never assembled into a shell string. This is
|
|
82
|
+
what removes the Windows space-in-path problem: `C:\Program Files\a b.exe` is one argument.
|
|
83
|
+
2. **Byte-exact text handling.** Reads strip the BOM; every write restores the file's original BOM
|
|
84
|
+
presence and line-ending convention. A BOM is never allowed to leak into text a caller sees.
|
|
85
|
+
3. **Assertions report, they do not throw.** `file verify charset` returns `match: false` alongside
|
|
86
|
+
the observed value, so a caller can tell "the file is wrong" from "I asked wrongly".
|
|
87
|
+
4. **Read-only verbs never prompt; mutating verbs require explicit authorization** (`--allow`).
|
|
88
|
+
5. **Unsupported is never silent.** An unimplemented verb or an unavailable platform capability
|
|
89
|
+
returns a typed error code, rather than succeeding with an empty result.
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
# Windows
|
|
95
|
+
src\host\bin\dsh-os.ps1 selfcheck
|
|
96
|
+
src\host\bin\dsh-os.ps1 file charset "C:\path with spaces\file.txt"
|
|
97
|
+
src\host\bin\dsh-os.ps1 file verify charset .\file.txt utf-8
|
|
98
|
+
src\host\bin\dsh-os.ps1 environment attr get PATH --scope process
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Every call writes exactly one JSON envelope to stdout, UTF-8 without a BOM:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{"ok":true,"verb":"file.charset","result":{"charset":"utf-8","bom":false,"eol":"crlf"},"meta":{}}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`error.code` is authoritative; exit codes are a coarse class (0 ok, 1 usage, 2 type, 3 authorization,
|
|
108
|
+
4 unsupported, 5 target/encoding, 6 timeout, 7 internal).
|
|
109
|
+
|
|
110
|
+
## Tests
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
npm run test:all # encoding guard, Windows golden, plugin, permission, the three verifiers, desktop smoke
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`test:all` chains `test:conformance`, `test:encoding`, `test` (the Windows golden suite), `test:plugin`,
|
|
117
|
+
`test:permission`, `test:bundle` (`tools/verify-bundle.mjs`), `test:load`
|
|
118
|
+
(`tools/verify-plugin-load.mjs` then `tools/verify-editions.mjs`) and `test:desktop`
|
|
119
|
+
(`bash tests/smoke-desktop-sh.sh`), in that order. The POSIX golden suite is run directly with
|
|
120
|
+
`bash tests/golden-sh.sh`, because it is a POSIX acceptance suite and not part of the Windows-side `npm`
|
|
121
|
+
chain.
|
|
122
|
+
|
|
123
|
+
Current state, measured on this checkout:
|
|
124
|
+
|
|
125
|
+
| Suite | Command | Cases | Result |
|
|
126
|
+
|---|---|---|---|
|
|
127
|
+
| Table conformance | `powershell -File tests/conformance.ps1` | 5 | **5 passed, 0 failed** |
|
|
128
|
+
| Encoding contracts | `powershell -File tests/encoding.ps1` | 32 build modules | **all contracts hold** |
|
|
129
|
+
| Windows golden | `powershell -File tests/golden.ps1` | 49 | **49 passed, 0 failed** |
|
|
130
|
+
| POSIX golden | `bash tests/golden-sh.sh` | 116 | **116 passed, 0 failed** |
|
|
131
|
+
| POSIX desktop smoke | `bash tests/smoke-desktop-sh.sh` | 52 | **52 passed, 0 failed** |
|
|
132
|
+
| Plugin | `node tests/plugin.test.mjs` | 13 | **13 passed, 0 failed** |
|
|
133
|
+
| Permission delegate | `node tests/permission.test.mjs` | 18 | **18 passed, 0 failed** |
|
|
134
|
+
| Bundle resolution | `node tools/verify-bundle.mjs` | 10 | **10 passed, 0 failed** |
|
|
135
|
+
| Plugin load | `node tools/verify-plugin-load.mjs` | 13 | **13 passed, 0 failed** |
|
|
136
|
+
| Edition selection | `node tools/verify-editions.mjs` | 14 | **14 passed, 0 failed** |
|
|
137
|
+
| Build drift | `powershell -File tools/build.ps1 -Check` | 3 artefacts | **no drift** |
|
|
138
|
+
|
|
139
|
+
86 verbs are declared in the table; **82 are implemented on Windows PowerShell 5.1 and on PowerShell
|
|
140
|
+
7.6 (identical sets), and 61 on POSIX**.
|
|
141
|
+
|
|
142
|
+
The POSIX desktop verbs need an X display, which this machine does not have. `tests/golden-sh.sh`
|
|
143
|
+
therefore asserts the **absence** path — with no `DISPLAY`, every one of them must return a typed
|
|
144
|
+
`E_NO_DISPLAY` rather than an empty success. `tests/smoke-desktop-sh.sh` covers the other half without a
|
|
145
|
+
display server: it puts the recording `tests/fake-xdotool` on `PATH` and sets `DISPLAY`, so the verbs run
|
|
146
|
+
their real code and the assertions are about the **exact argument vector** handed to the tool — that
|
|
147
|
+
`--button right` became button `3`, that `Enter` became the keysym `Return`, that a name outside the
|
|
148
|
+
table (`XF86AudioMute`) is not mangled, that `--clearmodifiers` and `--repeat` land in the right place,
|
|
149
|
+
and that an `xdotool` failure is reported as `E_IO` rather than a false success. That harness is what
|
|
150
|
+
found two real defects in the POSIX implementation (see below). Neither suite is evidence that the verbs
|
|
151
|
+
drive a real window manager.
|
|
152
|
+
|
|
153
|
+
`tests/encoding.ps1` requires ASCII-only, BOM-free files **of the 32 modules `tools/build.ps1`
|
|
154
|
+
concatenates**, because a non-ASCII byte there would be decoded in the ANSI code page and corrupt the
|
|
155
|
+
generated artefact. It deliberately does not require it of the two standalone `git.ps1` scripts, which
|
|
156
|
+
are never concatenated into anything and are UTF-8 **with** a BOM so their Chinese prose parses under
|
|
157
|
+
Windows PowerShell 5.1. An earlier version of the guard applied the rule to every `*.ps1` under `lib/`
|
|
158
|
+
and reported four violations that were a contract conflict rather than a defect; the guard now scopes
|
|
159
|
+
the rule to build inputs and prints which files it skipped, so the narrowing is visible rather than
|
|
160
|
+
silent.
|
|
161
|
+
|
|
162
|
+
`tests/conformance.ps1` exists because `spec/verbs.tsv` had claimed since it was written that
|
|
163
|
+
"tests/conformance.ps1 asserts the two agree" while no such file existed — a comment naming a guard that
|
|
164
|
+
is not there is worse than no comment, because it is read as a guarantee. It resolves each `command.txt`
|
|
165
|
+
line to a verb by the longest word-prefix that names a known verb (so `file verify charset path charset`
|
|
166
|
+
resolves to `file.verify.charset` rather than to a nonexistent `file.verify`), then checks that the two
|
|
167
|
+
tables list the same verbs, the same count, the same R/W class and the same positional names in order.
|
|
168
|
+
Writing it found **five real inconsistencies**, all of them in `command.txt` except one:
|
|
169
|
+
|
|
170
|
+
| Verb | Was declared | Actual, per the implementation |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `file.append` | `path content charset` | charset is an **optional** third positional, so the declaration implied it was required |
|
|
173
|
+
| `file.insert` | `path content charset offset` | charset is `--charset`, deliberately **not** a positional — an omitted positional there would silently shift `offset` |
|
|
174
|
+
| `wait.ms` | no positionals | takes `ms` |
|
|
175
|
+
| `keyboard.combination` | two lines, `... down a b c d` / `... up a b c d` | one verb with a **`phase`** positional; the two-line form read as two verbs |
|
|
176
|
+
| `execute.command` | tsv `command\|arguments` | the entire positional list is argv, so there is no separate `command` |
|
|
177
|
+
|
|
178
|
+
`command.txt` is the authoritative contract, so four were corrected there; `verbs.tsv`'s
|
|
179
|
+
`execute.command` row was corrected to `arguments...`. Nothing about the running surface changed — only
|
|
180
|
+
what the two documents say — which is exactly the class of drift a conformance guard is for.
|
|
181
|
+
|
|
182
|
+
## Using it from the harness
|
|
183
|
+
|
|
184
|
+
`package.json` declares `dsh.bundle.patch`, so the package installs into a DSH profile as a bundle and
|
|
185
|
+
registers one model-facing tool:
|
|
186
|
+
|
|
187
|
+
| Tool | Purpose |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `dsh_os` | Run one verb of the surface and return its JSON envelope verbatim |
|
|
190
|
+
|
|
191
|
+
One tool rather than one per verb: the verb table is the contract and lives in `spec/verbs.tsv`, so
|
|
192
|
+
the schema does not need regenerating when a verb is added or renamed; an agent discovers the whole
|
|
193
|
+
surface with a single `verb: "selfcheck"` call; and the envelope is already the stable result shape,
|
|
194
|
+
so there is no second contract to drift from the script's.
|
|
195
|
+
|
|
196
|
+
```jsonc
|
|
197
|
+
// call shape
|
|
198
|
+
{ "verb": "file charset", "arguments": ["path=\"./a.txt\""], "allow": false }
|
|
199
|
+
{ "verb": "environment attribute get", "arguments": ["PATH"], "flags": ["--scope", "process"] }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The implementation is spawned by absolute path with an argv array, never through a shell string, so a
|
|
203
|
+
value containing spaces stays one value. On Windows the `.ps1` is launched through `powershell.exe -File`
|
|
204
|
+
rather than the `.bat`: `cmd.exe` interprets a batch file, and spawning one with `shell: false` fails
|
|
205
|
+
with `EINVAL`. On POSIX the `.sh` is launched through `/bin/bash` so it need not be marked executable.
|
|
206
|
+
|
|
207
|
+
## Status
|
|
208
|
+
|
|
209
|
+
- Implemented and tested on Windows: `file`, `file verify`, `file search`, `system`, `task`
|
|
210
|
+
(list/query), `service` (list/query), `environment` including `environment attribute`,
|
|
211
|
+
`permission` (read-only) and `selfcheck`.
|
|
212
|
+
- Implemented and tested on POSIX: the same surface except `service`, `window`, `screenshot`,
|
|
213
|
+
`clipboard`, `wait`, `mouse` and `keyboard`, plus `file search`, `execute` (command / synchronous /
|
|
214
|
+
asynchronous), the process verbs `execute.query`, `execute.kill`, `task.query`, `task.show` and
|
|
215
|
+
`task.kill`, and the whole `permission.*` group including `permission acl init`.
|
|
216
|
+
- POSIX layer A is a mode change rather than a Windows deny ACE, and the equivalence is weaker: the
|
|
217
|
+
same user can restore write access. `permission acl init` says so in its own result, and its
|
|
218
|
+
`restore` field names the command that undoes it.
|
|
219
|
+
- `selfcheck` reports on the two platforms differently, and the difference is worth knowing before
|
|
220
|
+
reading its output as a parity check. On POSIX it derives the implemented list from its own dispatch
|
|
221
|
+
table and emits an `implementedCount`. On **Windows it does not**: `Invoke-Selfcheck` walks every row
|
|
222
|
+
of `spec/verbs.tsv` and emits only `verbCount`, with no implemented/unimplemented distinction at all.
|
|
223
|
+
So a Windows `selfcheck` naming a verb does not mean that verb is implemented — the dispatch tables
|
|
224
|
+
above are the authority.
|
|
225
|
+
- Implemented and tested on Windows for `window`, `screenshot`, `clipboard` and `wait`: enumeration,
|
|
226
|
+
matching, move, resize, minimize, maximize, restore, z-order, close, screen capture to png/jpeg/bmp,
|
|
227
|
+
clipboard text round-trip including non-ASCII, an explicit sleep, and waiting for a window to appear
|
|
228
|
+
or to go away.
|
|
229
|
+
- Both goldens include the failure path, which is the point: a failing call must still deliver exactly
|
|
230
|
+
one parseable envelope on stdout, because a bare exit code tells an agent nothing.
|
|
231
|
+
- Implemented and tested on Windows for `mouse` and `keyboard`: cursor read-back, move, click,
|
|
232
|
+
right-click, wheel, button down/up, key down/up, a self-releasing press, chords in both phases, and
|
|
233
|
+
text entry by Unicode key events (so it is layout-independent and accepts non-ASCII).
|
|
234
|
+
- Implemented and tested on Windows for `text` and `structure` through UI Automation: the element at a
|
|
235
|
+
point, its text via ValuePattern / TextPattern / LegacyIAccessiblePattern / Name in that order, and
|
|
236
|
+
the control tree around it. Both are honest about the environment - a missing UIA stack or a refusal
|
|
237
|
+
is a typed E_ACCESSIBILITY_UNAVAILABLE / E_ACCESSIBILITY_DENIED, never an empty result.
|
|
238
|
+
- Missing on Windows: `clipboard.paste` and the `service` lifecycle verbs (`start`, `stop`, `restart`)
|
|
239
|
+
— four verbs. Everything else in the table is dispatched on both PowerShell runtimes, with identical
|
|
240
|
+
verb sets. Every listed verb is declared in the table and returns a typed `E_NOT_SUPPORTED` rather
|
|
241
|
+
than a silent success.
|
|
242
|
+
- Missing on POSIX: 25 verbs — `file.encode`; all eleven `window.*`; `screenshot`, `text`, `structure`;
|
|
243
|
+
`clipboard.read`, `clipboard.write`, `clipboard.paste`; `wait.window`; `task.launch`; and all five
|
|
244
|
+
`service.*`. These fall through the dispatch table to a typed `E_NOT_SUPPORTED`; they are not silently
|
|
245
|
+
absent.
|
|
246
|
+
- Implemented on POSIX and needing **no display server**: the five `environment attribute` verbs
|
|
247
|
+
(`add`, `set`, `delete`, `list`, `get`) and `wait.ms`. The attribute mutations are `--scope process`
|
|
248
|
+
only, and refuse a persistent scope with `E_NOT_SUPPORTED` rather than half-applying it, because POSIX
|
|
249
|
+
has no kernel API for a list variable and rewriting `PATH` in a startup file can make a machine's
|
|
250
|
+
shells unusable.
|
|
251
|
+
- Implemented on POSIX and needing **an X display**: all seven `mouse.*` and all six `keyboard.*`, which
|
|
252
|
+
drive X11 through `xdotool`. With no `DISPLAY` each returns a typed `E_NO_DISPLAY`; with no `xdotool`
|
|
253
|
+
installed each returns `E_NOT_SUPPORTED` naming the missing tool. Key names are translated from the
|
|
254
|
+
Windows spelling to the X keysym spelling (`Enter`→`Return`, `PgUp`→`Prior`, `win`→`super`) and
|
|
255
|
+
anything outside the table is passed through unchanged, so any X keysym name works. A Windows virtual
|
|
256
|
+
key *number* is refused, because `13` means Return to a Windows caller and nothing to X.
|
|
257
|
+
- Both goldens include the failure path, which is the point: a failing call must still deliver exactly
|
|
258
|
+
one parseable envelope on stdout, because a bare exit code tells an agent nothing.
|
|
259
|
+
- Windows and PowerShell 7.6 are at exact parity: both dispatch the same 82 verbs, checked by
|
|
260
|
+
comparing the two dispatch tables rather than by eye.
|
|
261
|
+
- Every Windows verb that a caller can reach is also reachable on POSIX or reports a typed refusal. The
|
|
262
|
+
POSIX-only weak spot is that the desktop verbs are verified against recording fakes rather than a real
|
|
263
|
+
X server; see *Tests*.
|
|
264
|
+
- The four `execute` verbs that the README previously listed as unwritten are in fact implemented on
|
|
265
|
+
Windows — `execute.command`, `execute.query`, `execute.kill` and `task.launch` are dispatched to real
|
|
266
|
+
bodies. So are `permission.grant`, `permission.revoke` and `permission.acl.init` on **both**
|
|
267
|
+
platforms. `permission.json` therefore does have writing verbs; see the authorization section.
|
|
268
|
+
- The consent delegate (`src/permission/index.js`, row id `dsh-os-permission`) is written and tested:
|
|
269
|
+
it is the only component that writes `permission.json`, matching is deny-first regardless of rule
|
|
270
|
+
order, a file whose `updatedBy` is not the delegate is untrusted and falls back to defaults, a
|
|
271
|
+
wildcard target needs explicit acknowledgement, and writes go through a temp file plus rename so a
|
|
272
|
+
crash cannot leave a half-written file.
|
|
273
|
+
- The A layer (kernel ACL enforcement) is implemented on Windows: `permission acl init` appends an
|
|
274
|
+
explicit deny for Everyone to the authorization file DACL, is idempotent, requires elevation
|
|
275
|
+
(reporting `E_ELEVATION_REQUIRED` rather than failing silently), and `selfcheck` reports the strength
|
|
276
|
+
it MEASURED from the file security descriptor plus a reason, never a stored flag.
|
|
277
|
+
|
|
278
|
+
### Verified or not
|
|
279
|
+
|
|
280
|
+
Proven by tests and verifiers in this repository:
|
|
281
|
+
|
|
282
|
+
- the scripts on both platforms, including that a failing call still emits exactly one envelope;
|
|
283
|
+
- the build's determinism: `tools/build.ps1 -Check` reports the three generated artefacts matching
|
|
284
|
+
their modules byte for byte (218,514 + 255,565 + 142,744 bytes);
|
|
285
|
+
- the plugin's execute path: entry-point resolution, argv construction, a path containing spaces
|
|
286
|
+
reaching the script as one argument, flag forwarding, authorization, and error propagation;
|
|
287
|
+
- the loader's **resolution** contract, by `tools/verify-bundle.mjs`: the manifest header, the patch
|
|
288
|
+
file, each row resolving from a profile-style `node_modules`, each resolved module exporting the
|
|
289
|
+
Cordis shape the loader calls, the two rows resolving to **distinct** modules, and the version gate.
|
|
290
|
+
The package is linked into a throwaway profile sandbox by junction, so resolution happens as it
|
|
291
|
+
would in a profile and the live profile is never written to;
|
|
292
|
+
- the real `apply()` path, by `tools/verify-plugin-load.mjs`: the host plugin registers `dsh_os` with a
|
|
293
|
+
parameters schema whose root type is `object`, the permission plugin registers its five tools, the
|
|
294
|
+
**registered handler actually spawns the script** and returns a real envelope (`selfcheck` → ok,
|
|
295
|
+
`verbCount` 86), and a mutating verb without `allow` is refused with a typed `E_PERMISSION` rather
|
|
296
|
+
than throwing;
|
|
297
|
+
- edition selection, by `tools/verify-editions.mjs`: the default stays on PowerShell 5.1,
|
|
298
|
+
`powershellEdition: '7.6'` and `DSH_OS_POWERSHELL_EDITION` select the 7.6 artefact, `'auto'` uses
|
|
299
|
+
7.6 only when both the artefact and a working `pwsh` are present, config beats environment, and an
|
|
300
|
+
explicit `scriptPath` disables the choice. `-ExecutionPolicy` is passed only to the 5.1 executable,
|
|
301
|
+
being a Windows PowerShell option.
|
|
302
|
+
|
|
303
|
+
Measured on this checkout, stated exactly as observed:
|
|
304
|
+
|
|
305
|
+
| Suite | Cases | Result |
|
|
306
|
+
|---|---|---|
|
|
307
|
+
| Table conformance | 5 | **5 passed, 0 failed** |
|
|
308
|
+
| Windows golden | 49 | **49 passed, 0 failed** |
|
|
309
|
+
| POSIX golden | 116 | **116 passed, 0 failed** |
|
|
310
|
+
| POSIX desktop smoke | 52 | **52 passed, 0 failed** |
|
|
311
|
+
| Plugin | 13 | **13 passed, 0 failed** |
|
|
312
|
+
| Permission delegate | 18 | **18 passed, 0 failed** |
|
|
313
|
+
| Bundle resolution | 10 | **10 passed, 0 failed** |
|
|
314
|
+
| Plugin load | 13 | **13 passed, 0 failed** |
|
|
315
|
+
| Edition selection | 14 | **14 passed, 0 failed** |
|
|
316
|
+
| Encoding contracts | 32 modules | **all contracts hold** |
|
|
317
|
+
|
|
318
|
+
- Every suite above passes. The four encoding violations this section used to report are gone, because
|
|
319
|
+
the guard now scopes the ASCII-only rule to the files the assembler actually reads; the rule was never
|
|
320
|
+
relaxed, its scope was corrected, and the guard prints which files it skipped.
|
|
321
|
+
- The PowerShell 7.6 artefact builds, is reachable, and the version of PowerShell 7 on this machine is
|
|
322
|
+
**7.6.6**, which is what the edition verifier drove. Beyond that, the 7.6 modules are established as a
|
|
323
|
+
faithful port by comparing the two dispatch tables (identical 82-verb sets), by a per-module
|
|
324
|
+
function-name comparison, and by running the window group's golden cases against `dsh-os7.ps1` — rather
|
|
325
|
+
than by a per-verb run of the whole surface on 7.6. That is a real gap in the evidence, not a claim of
|
|
326
|
+
full parity.
|
|
327
|
+
- Two defects found by `tests/smoke-desktop-sh.sh` after the POSIX mouse and keyboard verbs were written,
|
|
328
|
+
both recorded here because they are the kind review does not catch:
|
|
329
|
+
1. **Validation ran after injection.** `mouse click 10 20 --count abc` returned an error *and had
|
|
330
|
+
already moved the pointer*. The Windows side validates every argument before injecting; the POSIX
|
|
331
|
+
side now does too.
|
|
332
|
+
2. **A rejection inside `$(...)` did not stop the verb.** The coordinate validator called `fail`, but
|
|
333
|
+
the caller invoked it as `x="$(window_int_arg ...)"`, so the `exit` ended only the subshell: the verb
|
|
334
|
+
carried on with an empty value and injected anyway — `mouse over abc 20` moved the pointer and
|
|
335
|
+
returned `ok`. The validators now assign a variable (`WINDOW_INT`, `WINDOW_BUTTON`, `WINDOW_KEY`)
|
|
336
|
+
and are called in the caller's own shell. This trap was already documented in the environment
|
|
337
|
+
module, and it was still repeated here.
|
|
338
|
+
|
|
339
|
+
Not proven, and not claimed: that the **harness's own loader** composes these bundles in a live
|
|
340
|
+
profile. That would mean installing this package into the user's active desktop profile and restarting
|
|
341
|
+
the harness, which has not been done.
|
|
342
|
+
|
|
343
|
+
## Authorization
|
|
344
|
+
|
|
345
|
+
Read-only verbs never prompt. Every mutating verb requires an explicit `--allow`; without it the call
|
|
346
|
+
returns `E_PERMISSION` and nothing happens.
|
|
347
|
+
|
|
348
|
+
`permission.json` is written by the consent delegate (`src/permission/index.js`, row id
|
|
349
|
+
`dsh-os-permission`) and by nothing else. The table declares three write verbs —
|
|
350
|
+
`permission.grant`, `permission.revoke` and `permission.acl.init`, all `W` — and three read-only ones
|
|
351
|
+
(`query`, `list`, `audit`). `grant` and `revoke` accept only the `delegate` flag besides `allow`, so
|
|
352
|
+
the script itself cannot create a persistent authorization: with no delegate present they return
|
|
353
|
+
`E_PERMISSION_DELEGATE_ONLY`.
|
|
354
|
+
|
|
355
|
+
Two enforcement layers protect "the files that decide what this layer may do":
|
|
356
|
+
|
|
357
|
+
- **Layer B, structural** — a hard protected-path boundary rejects any modification of
|
|
358
|
+
`permission.json` (and `.bak`), `src/host/command.txt`, the three `src/host/bin/dsh-os.*` entry
|
|
359
|
+
points, `src/host/spec/verbs.tsv` and `src/host/spec/charsets.tsv`, plus any same-named temporary
|
|
360
|
+
staged beside one of them. Violations report `E_PERMISSION_PROTECTED_PATH` with no side effect;
|
|
361
|
+
reads are still allowed so you can review the rules. The check runs on the **resolved, normalised**
|
|
362
|
+
path, compared segment by segment rather than by regex, because `..`, 8.3 short names or a trailing
|
|
363
|
+
dot all walk around a regex. `file.move` and `file.copy` check **both ends**, since moving a
|
|
364
|
+
protected file is a way of destroying it. The POSIX list has eight entries: the same list without
|
|
365
|
+
`dsh-os7.ps1`. `dsh-os7.ps1` was added to the Windows list only after the gap was found destructively —
|
|
366
|
+
the golden suite's protected-path case moved the artefact into a sandbox it then deleted, because the
|
|
367
|
+
list named the 5.1 and shell entry points but not the 7.6 one. Every entry point belongs to the same
|
|
368
|
+
boundary, and the two lists now protect both PowerShell scripts.
|
|
369
|
+
- **Layer A, kernel ACL** — Windows only, opt-in and requiring elevation. `permission acl init`
|
|
370
|
+
appends an explicit `DENY` for `Everyone` (`S-1-1-0`) to the DACL of `permission.json`, covering
|
|
371
|
+
`FILE_GENERIC_WRITE | DELETE | WRITE_DAC | WRITE_OWNER`. It is idempotent and reports
|
|
372
|
+
`E_ELEVATION_REQUIRED` rather than failing opaquely. `selfcheck` reports `aclEnforced` by reading
|
|
373
|
+
the file's real security descriptor and walking the DACL, never from a stored flag, and `aclReason`
|
|
374
|
+
explains a `false`. The POSIX equivalent is weaker by construction: it is a mode change, and the
|
|
375
|
+
same user can restore write access. `permission acl init` says so in its own result and its
|
|
376
|
+
`restore` field names the command that undoes it.
|
|
377
|
+
|
|
378
|
+
Four limitations, stated because they change what the guarantees above mean:
|
|
379
|
+
|
|
380
|
+
1. **Persistent rules are recorded but not enforced at the verb layer.** The rules in
|
|
381
|
+
`permission.json` are matched by the consent delegate and its `os_permission_query` tool. The
|
|
382
|
+
script reads the file for **display only** (`permission list/query/audit`); no verb consults a rule
|
|
383
|
+
before acting. The real gates are `--allow`, the protected-path check and (if enabled) layer A. A
|
|
384
|
+
`deny-always` rule does **not** currently stop a verb that arrives with `--allow`.
|
|
385
|
+
2. **`E_PERMISSION_DENIED_BY_RULE` is declared but never thrown.** It is in the error table and maps
|
|
386
|
+
to exit class 3, but no code path produces it — consistent with point 1.
|
|
387
|
+
3. **The in-script rule reader is not deny-first.** `permission query` takes the **first** matching
|
|
388
|
+
rule without regard to whether it allows or denies. The delegate implements deny-first correctly
|
|
389
|
+
and has test coverage; the two readers can therefore disagree, and the script side is the weaker
|
|
390
|
+
one.
|
|
391
|
+
4. **`execute` is a way around the protected-path boundary.** That boundary guards `file.*` and
|
|
392
|
+
`environment.*`. It cannot constrain a child process: `execute.synchronous` hands the command line
|
|
393
|
+
to `cmd.exe /d /s /c` on Windows and to `sh -c` on POSIX, so the program it starts can write
|
|
394
|
+
anything its own rights permit, `permission.json` included. Only layer A prevents that, and layer B
|
|
395
|
+
cannot when `aclEnforced` is false.
|
|
396
|
+
|
|
397
|
+
The consent delegate has a **design gap** worth stating plainly: it registers five tools
|
|
398
|
+
(`os_permission_query`, `os_permission_grant`, `os_permission_revoke`, `os_permission_list`,
|
|
399
|
+
`os_permission_audit`) but does **not** register an `approval/request` answerer, although its own
|
|
400
|
+
header explains that reaching the user is why it is a plugin rather than a script subcommand. As the
|
|
401
|
+
code stands, the enforcement is a **tool description** telling the model to call `os_permission_grant`
|
|
402
|
+
only after the user chooses; the code does not verify that a prompt ever happened. Treat the delegate
|
|
403
|
+
as trusted infrastructure rather than as a check on the agent. It also reports
|
|
404
|
+
`aclEnforced: false` unconditionally in its `query` result instead of measuring the ACL as the script
|
|
405
|
+
does, so it **under-reports** protection when layer A is on.
|
|
406
|
+
|
|
407
|
+
## Standalone tools
|
|
408
|
+
|
|
409
|
+
`src/host/lib/git.sh`, `src/host/lib/powershell5.1/git.ps1` and `src/host/lib/powershell7.6/git.ps1`
|
|
410
|
+
are interactive git runners. They are deliberately **not** part of the verb surface: they are absent
|
|
411
|
+
from `$modulesPs1` and `$modulesSh` in `tools/build.ps1`, so they are never concatenated into
|
|
412
|
+
`bin/dsh-os.ps1`, `bin/dsh-os7.ps1` or `bin/dsh-os.sh`, and they emit no JSON envelope. Each one is a
|
|
413
|
+
single self-contained file, byte-independent of the others, kept in step by review rather than by a
|
|
414
|
+
generator.
|
|
415
|
+
|
|
416
|
+
They locate git in three phases — `GIT_EXE`/`GIT_HOME`/`PATH`, then the known install locations
|
|
417
|
+
(including the Git-for-Windows registry keys), then a depth- and time-bounded walk of every mounted
|
|
418
|
+
file system — and exit 127 with the searched locations when nothing is found. Arguments reach git as
|
|
419
|
+
separate argv elements; no shell string is ever assembled. `git add` with an empty answer becomes
|
|
420
|
+
`git add .`. All three are ASCII-or-BOM per their own runtime's encoding contract, which is why the
|
|
421
|
+
shell module is English-only and the PowerShell ones are not.
|
|
422
|
+
|
|
423
|
+
## License
|
|
424
|
+
|
|
425
|
+
MIT
|