@argszero/cordis-plugin-sandbox-grant-advisor 0.5.0 → 0.7.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.
@@ -2,7 +2,7 @@
2
2
  * `sandbox-grant-advisor`: turn an environment failure that has no path forward
3
3
  * into a diagnosis the model — and the user reading the transcript — can act on.
4
4
  *
5
- * ## The two failures it recognizes
5
+ * ## The three failures it recognizes
6
6
  *
7
7
  * **Workspace provisioning (Windows ACL).** Four reports of one signature
8
8
  * (`#7538`, `#7622`, `#7646`, `#7720`) describe the same shape: the host-side write grant
@@ -40,13 +40,42 @@
40
40
  * `danger-full-access` succeeds, `standard` (one-shot shell) × confining
41
41
  * succeeds.
42
42
  *
43
+ * **A confined child that never started (native init, `#7876` + `#7877`).** The
44
+ * third family is not a message at all: two reports of one exit code —
45
+ * `0xC0000142` `STATUS_DLL_INIT_FAILED` — describing a child that died while the
46
+ * loader was initializing its native images, before its entry point. In `#7876`
47
+ * the packaged desktop's sandbox runner never runs, because `sandbox-local`
48
+ * starts it as `[process.execPath, entry]` and in that build `process.execPath`
49
+ * is the Electron executable, which starts as an *application* unless the child
50
+ * carries `ELECTRON_RUN_AS_NODE=1` — so every confined command reports this code
51
+ * with no output at all, while the unpacked `node apps/cli/lib/bin.js web` host
52
+ * is unaffected. In `#7877` it is an MSYS2/Git-Bash program: under the restricted
53
+ * token bash cannot create its own signal pipe (`couldn't create signal pipe,
54
+ * Win32 error 5`), so it dies in the same phase, while `cmd.exe` and `pwsh` run
55
+ * fine under the identical mode. Retrying is the one thing that cannot work, and
56
+ * the code tells the model nothing on its own.
57
+ *
58
+ * This family is read from the **canonical value of a successful result**, which
59
+ * is why the seam below now inspects both outcomes. The producer never marks it
60
+ * an error: upstream's runner-failure rules admit only exit `127` with the
61
+ * `windows-acl-run: ` signature (`packages/sandbox/sandbox-local/src/index.ts`),
62
+ * `classifyRunnerFailure` skips every other code before it looks at stderr
63
+ * (`packages/sandbox/sandbox/src/diagnostics.ts`), and the renderer reports a
64
+ * nonzero exit as `[exit code: N]` rather than as `isError`
65
+ * (`packages/shell/tool-pwsh/src/render.ts`). Every version of this plugin
66
+ * before `0.6.0` read error results only and was structurally blind to it. See
67
+ * `src/signature.ts` for why the read is `ToolExecutionSuccess.value` — the
68
+ * tool's own canonical output, never a line of rendered text — and which three
69
+ * narrowings keep the recognition from firing on something else.
70
+ *
43
71
  * ## Where it acts, and why there
44
72
  *
45
73
  * One listener on the public `tools/post-execute` waterfall
46
74
  * (`@deepseek-ai/dsh-tools`). Admissibility was decided by which half of the
47
75
  * defect this seam can reach: the failure text (the provider propagates its
48
- * error unchanged, and the tool pipeline turns it into an `isError` result), an
49
- * agent identity to attribute it to (`exec.agent`), and a channel that speaks
76
+ * error unchanged, and the tool pipeline turns it into an `isError` result) or,
77
+ * for the native-init family, the canonical value a successful result carries,
78
+ * an agent identity to attribute it to (`exec.agent`), and a channel that speaks
50
79
  * to the model in the same step (`PostToolDecision`'s `additionalContexts`,
51
80
  * a durable user-role message).
52
81
  *
@@ -71,7 +100,14 @@
71
100
  * right and are not (`takeown`, `icacls /reset`), each with its reason. For the PTY family it names the
72
101
  * combination that fails (persistent PTY × a confining mode), states the
73
102
  * resolved mode, says plainly that no command can fix it, and hands the
74
- * user-side preset choice over. Both ride `additionalContexts`, so the model
103
+ * user-side preset choice over. For the native-init family it states the
104
+ * resolved mode, says the process never reached its entry point, enumerates
105
+ * the two producers measured under a confining mode with the check that
106
+ * separates them (what program the reader ran; whether this host is the
107
+ * packaged desktop binary, which the plugin **measures and reports** rather
108
+ * than assumes), and carries the one conversion a model can actually make —
109
+ * rewrite the work as PowerShell or `cmd` when the program that could not
110
+ * start was an MSYS2 one. All three ride `additionalContexts`, so the model
75
111
  * sees the diagnosis beside the failure rather than only in a log it never
76
112
  * reads.
77
113
  * 2. **A bounded fail-fast, ACL family only.** With `enforceAfter` set, a call
@@ -80,9 +116,10 @@
80
116
  * default: the useful signal here is the diagnosis, and a plugin that blocks
81
117
  * command execution for a reason it merely recognizes is a risk, not a
82
118
  * feature. See the README for why the blocking half is deliberately narrow
83
- * and why it does not cover the PTY family.
84
- * 3. **A disclosure when it withholds.** The PTY advisory is only sent when the
85
- * resolved mode actually confines; if the mode is not confining, or cannot be
119
+ * and why it does not cover the two mode-gated families.
120
+ * 3. **A disclosure when it withholds.** The PTY and native-init advisories are
121
+ * only sent when the resolved mode actually confines; if the mode is not
122
+ * confining, or cannot be
86
123
  * resolved at all, the failure is left exactly as it was **and the host log
87
124
  * says so once**. Silence alone would make "the sandbox is not the cause" and
88
125
  * "this plugin could not tell" indistinguishable from the outside.
@@ -96,20 +133,30 @@
96
133
  * `ToolRuntime` — against synthetic results carrying the producers' exact
97
134
  * error shapes, with the formats taken from
98
135
  * `packages/subprocess/win32-process/src/errors.ts` and
99
- * `packages/terminal/terminal-bash/src/{index,session}.ts`.
136
+ * `packages/terminal/terminal-bash/src/{index,session}.ts`. The native-init
137
+ * family is exercised the same way and needs no Windows to be faithful, because
138
+ * what it reads is a number in a JSON value: the test builds the shipped shell
139
+ * tools' own foreground projection with the reported codes, including the
140
+ * signed form the reporter saw (`-1073741502`) and the real MSYS2 stderr, so the
141
+ * recognition runs against the producer's data rather than against a message
142
+ * this plugin invented.
100
143
  * - **It does not repair anything.** No ACL is written, no privilege is
101
- * requested, nothing is elevated, no preset is installed and no mode is
102
- * changed: both remedies are the user's to apply.
144
+ * requested, nothing is elevated, no environment variable is set for another
145
+ * process, no preset is installed and no mode is changed: the remedies are the
146
+ * user's (or, for the one in-session conversion, the model's own rewrite).
103
147
  * - **It complements, rather than replaces, `repeat-guard-escalation`.** That
104
148
  * guard keys on *call identity* (identical arguments retried); this one keys
105
149
  * on the *environment signature*, which is how several different commands can
106
150
  * share one cause. They can be mounted together.
107
- * - **The real fix is upstream**, in both families: the ACL failure should name
151
+ * - **The real fix is upstream**, in all three families: the ACL failure should
152
+ * name
108
153
  * the outstanding condition at the site that knows it (`grantWrite` computes
109
154
  * `hasExactGrant`/`hasExactDeny`/`hasExactLabel` and discards which was
110
- * false), and the PTY startup path should either report "this sandbox mode is
111
- * incompatible with the PTY backend" or fall back to a one-shot shell. This
112
- * plugin is the stopgap.
155
+ * false), the PTY startup path should either report "this sandbox mode is
156
+ * incompatible with the PTY backend" or fall back to a one-shot shell, and the
157
+ * sandbox runner should be launched with the environment its own execution
158
+ * needs (`ELECTRON_RUN_AS_NODE` when argv[0] is an Electron binary) or with a
159
+ * documented, checkable refusal for MSYS2 programs. This plugin is the stopgap.
113
160
  *
114
161
  * @module @argszero/cordis-plugin-sandbox-grant-advisor
115
162
  */
@@ -135,8 +182,9 @@ export declare const DEFAULT_MAX_DENIALS = 2;
135
182
  * The family the optional blocking half applies to.
136
183
  *
137
184
  * The ACL remedy is a command the user can run while the session continues; the
138
- * PTY remedy is a preset swap between sessions. Refusing calls is only useful
139
- * in the first case — see `denialText` in `src/advice.ts`.
185
+ * PTY remedy is a preset swap between sessions and the native-init remedy is a
186
+ * user-side launch fix (or a rewrite the model makes itself), so refusing calls
187
+ * is only useful in the first case — see `denialText` in `src/advice.ts`.
140
188
  */
141
189
  export declare const ENFORCED_FAMILY = "acl-provisioning";
142
190
  /** Configures what is watched and whether the blocking half runs. */
@@ -144,8 +192,9 @@ export interface Config {
144
192
  /**
145
193
  * ACL provisioning failures after which an identical, already-failing call is
146
194
  * denied before dispatch. `0` (the default) disables the half entirely; the
147
- * advisory half is unaffected and always on. The blocking half does not apply
148
- * to the persistent-shell family.
195
+ * advisory half is unaffected and always on. The blocking half applies to the
196
+ * ACL family alone: it does not cover the persistent-shell or native-init
197
+ * families, whose remedies are not a command the session can wait out.
149
198
  */
150
199
  enforceAfter?: number;
151
200
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Recognize the two environment failures this plugin explains, and refuse
2
+ * Recognize the three environment failures this plugin explains, and refuse
3
3
  * everything else.
4
4
  *
5
5
  * ## The ACL provisioning failure (`acl-provisioning`)
@@ -82,6 +82,71 @@
82
82
  * different cause space (a slow or blocked shell) with a different remedy, and
83
83
  * a classifier that names a wrong cause is worse than one that stays silent.
84
84
  *
85
+ * ## The process that never started (`native-init`)
86
+ *
87
+ * The third family is not a message at all: it is a **structured exit code on a
88
+ * result the pipeline calls a success**. Two reports of one code —
89
+ * `STATUS_DLL_INIT_FAILED`, `0xC0000142`, seen as `-1073741502` in a tool result
90
+ * because Windows exit codes are 32-bit NTSTATUS values and Node reports them
91
+ * signed — describe a confined child that died while its native images were
92
+ * initializing, i.e. before its entry point. `#7876` is the packaged desktop app:
93
+ * `sandbox-local` starts the sandbox runner as `[process.execPath, entry]`, and
94
+ * in that build `process.execPath` is the Electron executable, which starts as an
95
+ * *app* unless `ELECTRON_RUN_AS_NODE=1` is in the child's environment — so the
96
+ * runner itself never runs and every confined command reports this code with no
97
+ * output at all. `#7877` is an MSYS2/Git-Bash program under the restricted
98
+ * token: bash cannot create its own signal pipe (`couldn't create signal pipe,
99
+ * Win32 error 5`) and aborts in the same place, while `cmd.exe` and `pwsh` run
100
+ * fine under the identical mode.
101
+ *
102
+ * **This family is the only one that is invisible from the error path**, and that
103
+ * is the whole reason it is classified from the canonical value instead of from
104
+ * text. Upstream's runner-failure rules admit exactly one code —
105
+ * `RUNNER_FAILURE_RULES['windows-acl'] = [{ allowedExitCodes: [127], fatalSignatures:
106
+ * ['windows-acl-run: '] }]` (`packages/sandbox/sandbox-local/src/index.ts`) — and
107
+ * `classifyRunnerFailure` skips any other code before it even looks at stderr
108
+ * (`packages/sandbox/sandbox/src/diagnostics.ts`), so `0xC0000142` is never a
109
+ * runner failure and `SandboxUnavailableError` is never thrown. The renderer then
110
+ * reports it the way it reports any finished command — *"Non-zero exits are
111
+ * reported, not errored … only infrastructure failures (spawn errors, aborts)
112
+ * surface as isError results"* (`packages/shell/tool-pwsh/src/render.ts`) — as
113
+ * `[exit code: …]`. A plugin reading only `isError` results (every version of
114
+ * this one before `0.6.0`) is structurally blind to it, which is exactly why the
115
+ * model retries a command that can never start.
116
+ *
117
+ * The read is `ToolExecutionSuccess.value` — the tool's own canonical output,
118
+ * documented as *"Execution-local canonical value; deliberately omitted from
119
+ * durable events"* (`packages/core/tools/src/index.ts`) — so the code arrives
120
+ * structurally and no line of rendered text can be mistaken for it. Reading it
121
+ * this way is what makes the recognition safe: a command that prints a line
122
+ * saying `0xC0000142` is not this failure, and a call whose arguments merely
123
+ * mention a Windows path is not either.
124
+ *
125
+ * Three narrowings, each of which is a thing that could otherwise make the
126
+ * diagnosis wrong:
127
+ *
128
+ * - **The `foreground` discriminator is required.** The shipped shell tools
129
+ * project a finished foreground run as `{ kind: 'foreground', exitCode, … }`
130
+ * and a still-running background handle as a different shape (`tool-pwsh` /
131
+ * `tool-bash`, mirrored by design), so requiring it keeps a value some other
132
+ * tool happens to build with an `exitCode` field out of this family. A value
133
+ * without it is left alone — the fail-closed direction, since the cost of
134
+ * silence is one missing diagnosis and the cost of a wrong match is a confident
135
+ * wrong cause.
136
+ * - **Only `STATUS_DLL_INIT_FAILED` is classified.** `0xC0000142` has producers
137
+ * this module does not know about (a program that simply cannot load its own
138
+ * DLLs, and the console-hiding that the sandbox backend's own source records as
139
+ * producing it), so the advisory enumerates the measured ones and says so
140
+ * rather than asserting one. The neighbouring statuses are deliberately **not**
141
+ * folded in: `0xC0000409` is the Cygwin/MSYS2 runtime's deliberate fast-fail
142
+ * (a different mechanism with a different story), and `0xC0000135` is a missing
143
+ * DLL (a packaging problem, not a sandbox one).
144
+ * - **No platform gate.** The code is a Windows NTSTATUS: a POSIX process cannot
145
+ * exit with a value above 255, so the number itself is the platform evidence. A
146
+ * `process.platform === 'win32'` check would add nothing a session could
147
+ * observe and would make this family untestable on the host this plugin is
148
+ * built on — which is how a family ships without ever having been run.
149
+ *
85
150
  * @module
86
151
  */
87
152
  /** Which provisioning operation failed, and which diagnosis follows from it. */
@@ -107,7 +172,9 @@ export type FailureFamily =
107
172
  /** The Windows sandbox could not provision its workspace (ACL / mandatory label). */
108
173
  'acl-provisioning'
109
174
  /** The persistent PTY shell could not start under a confining sandbox mode. */
110
- | 'pty-startup';
175
+ | 'pty-startup'
176
+ /** A confined Windows child died while its native images were initializing. */
177
+ | 'native-init';
111
178
  /** One recognized provisioning failure, with the producer's own fields kept. */
112
179
  export interface ProvisioningFailure {
113
180
  /** Which family this failure belongs to. */
@@ -139,8 +206,35 @@ export interface PtyStartupFailure {
139
206
  /** The producer's message, kept as the constant so nothing can drift. */
140
207
  readonly line: string;
141
208
  }
209
+ /**
210
+ * `STATUS_DLL_INIT_FAILED`, the code a Windows process is terminated with when
211
+ * the loader fails while initializing it — before its entry point runs.
212
+ *
213
+ * Written unsigned here, which is how the NTSTATUS is named; a tool result
214
+ * usually carries it as the 32-bit signed number (`-1073741502`), and
215
+ * {@link classifyNativeInitDeath} accepts either because it compares the
216
+ * normalized 32-bit pattern.
217
+ */
218
+ export declare const STATUS_DLL_INIT_FAILED = 3221225794;
219
+ /**
220
+ * The code a confined Windows child died with, and the forms the caller may have
221
+ * to quote it in.
222
+ *
223
+ * There is no `path` and no `api` here: the producer of this failure is the
224
+ * operating system's loader, which reports only the status. What the diagnosis
225
+ * needs beyond the code — the effective sandbox mode — comes from the policy
226
+ * resolver at the call site, exactly as it does for the PTY family.
227
+ */
228
+ export interface NativeInitFailure {
229
+ /** Which family this failure belongs to. */
230
+ readonly family: 'native-init';
231
+ /** The exit code exactly as the tool reported it, so it can be quoted back verbatim. */
232
+ readonly rawExitCode: number;
233
+ /** The same code normalized to its unsigned 32-bit form, for comparison and printing. */
234
+ readonly exitCode: number;
235
+ }
142
236
  /** Any failure this plugin recognizes, tagged by family. */
143
- export type RecognizedFailure = ProvisioningFailure | PtyStartupFailure;
237
+ export type RecognizedFailure = ProvisioningFailure | PtyStartupFailure | NativeInitFailure;
144
238
  /**
145
239
  * Classify one failure message against the ACL family.
146
240
  * @param message - the failure text, from the result's `error.message` or its rendered content.
@@ -161,6 +255,28 @@ export declare function classifyProvisioningFailure(message: string): Provisioni
161
255
  * @returns the recognized failure, or undefined when this is not one.
162
256
  */
163
257
  export declare function classifyPtyStartupFailure(message: string): PtyStartupFailure | undefined;
258
+ /**
259
+ * Classify one **successful** execution's canonical value as a Windows native-init
260
+ * death.
261
+ *
262
+ * This is the only family read from a result the pipeline calls a success, and
263
+ * that is a fact about the producer rather than a choice: the code reaches the
264
+ * tool result as an ordinary nonzero exit status (upstream's runner-failure rules
265
+ * admit only exit `127` with the `windows-acl-run: ` signature, so this one is
266
+ * never reclassified), and the renderer reports nonzero exits without erroring.
267
+ * `ToolExecutionFailure` carries no value at all, so there is nothing to read on
268
+ * the error path — a session sees this failure exactly when its shell tool
269
+ * reports a command that "ran".
270
+ *
271
+ * Recognized structurally, never from text: the value must be the foreground
272
+ * shell projection (`kind: 'foreground'`) with an integer `exitCode` whose 32-bit
273
+ * pattern is {@link STATUS_DLL_INIT_FAILED}. A command's own output claiming the
274
+ * code cannot reach this function, and neither can a value some other tool built
275
+ * with an `exitCode` field.
276
+ * @param value - the settled execution's canonical value (`ToolExecutionSuccess.value`).
277
+ * @returns the recognized failure, or undefined when this is not one.
278
+ */
279
+ export declare function classifyNativeInitDeath(value: unknown): NativeInitFailure | undefined;
164
280
  /**
165
281
  * The one-line failure the producer wrote, for quoting back verbatim.
166
282
  * @param failure - a recognized failure.
@@ -8,10 +8,11 @@
8
8
  *
9
9
  * ## One record per family
10
10
  *
11
- * This plugin now recognizes two unrelated environment failures — a workspace
12
- * that cannot be provisioned (`acl-provisioning`) and a persistent shell that
13
- * cannot start (`pty-startup`). They are different diagnoses with different
14
- * remedies, so their bookkeeping is kept apart under one agent
11
+ * This plugin recognizes three unrelated environment failures — a workspace
12
+ * that cannot be provisioned (`acl-provisioning`), a persistent shell that
13
+ * cannot start (`pty-startup`), and a confined Windows child that died during
14
+ * native initialization (`native-init`). They are different diagnoses with
15
+ * different remedies, so their bookkeeping is kept apart under one agent
15
16
  * ({@link AgentState.families}): an agent that hits both is told about both,
16
17
  * and an agent that has already been told about one is still told about the
17
18
  * other. Sharing one "already advised" flag would silently swallow the second
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@argszero/cordis-plugin-sandbox-grant-advisor",
3
- "description": "Turns two sandbox environment failures that name neither their cause nor a remedy into a diagnosis with a path forward. Family 1, the Windows workspace ACL: nine reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816) of one signature — every sandboxed command fails before it runs with `SetNamedSecurityInfoW failed (Win32 5): grantWrite(<workspace>)`, because the merged DACL + mandatory-label write needs WRITE_OWNER on the directory (an object right the caller can self-grant), not SeSecurityPrivilege and not elevation; the host grant is materialized lazily and caches nothing on the failure path, so the same failure repeats per command. Family 2, the persistent shell (#7638): with the `minimal` preset under a confining sandbox mode every shell call dies instantly with `PTY shell exited during startup` because the terminal backend cannot create the pseudo-console inside the sandbox, retrying never helps, and `minimal` mounts no fallback shell tool. The plugin observes the public `tools/post-execute` waterfall, classifies both signatures narrowly (only the two `...NamedSecurityInfoW` operations; the PTY message matched on a whole line, never as a substring, and only under a mode the policy resolver reports as confining), and attaches ONE durable user-role advisory per agent per family through `additionalContexts`. The ACL advisory names the missing right, both environments the identical text can describe (an inherited Modify-only entry, a data volume where no ACE names the caller at all, and a directory owned by another account), the version boundary that arrived with the mandatory label (0.1.7-alpha.1, flag 20, versus the DACL-only flag 4 up to 0.1.6-alpha.x) together with why downgrading is not the remedy, the discriminator, the remedy forked on an ownership check the user runs (`(Get-Acl \"<dir>\").Owner`), because one command cannot serve both rights situations: where the caller owns the directory, the unelevated `icacls ... :(OI)(CI)(WO)` is the whole of what is missing — the owner's implicit WRITE_DAC already covers the DACL half — and where the caller does not own it that same command is refused for want of WRITE_DAC, so the grant has to come from an elevated account, or by taking ownership first, or by moving the workspace under %USERPROFILE% — and the two remedies that look right and are not (`takeown`, `icacls /reset`), each with the reason it fails; the PTY advisory names the failing combination, states the resolved mode, tells the model to stop rather than retry, and hands the user-side preset choice over — it never names a shell tool the failing composition does not mount. An optional, off-by-default `enforceAfter` refuses an identical ACL call this plugin has watched fail, bounded by `maxDenials`; the blocking half is ACL-only by design. It never edits an ACL, never elevates, and never changes a preset or a mode, and it complements repeat-guard-escalation, which keys on call identity rather than on the environment signature.",
4
- "version": "0.5.0",
3
+ "description": "Turns three sandbox environment failures that name neither their cause nor a remedy into a diagnosis with a path forward. Family 1, the Windows workspace ACL: nine reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816) of one signature \u2014 every sandboxed command fails before it runs with `SetNamedSecurityInfoW failed (Win32 5): grantWrite(<workspace>)`, because the merged DACL + mandatory-label write needs WRITE_OWNER on the directory (an object right the caller can self-grant), not SeSecurityPrivilege and not elevation; the host grant is materialized lazily and caches nothing on the failure path, so the same failure repeats per command. Family 2, the persistent shell (#7638): with the `minimal` preset under a confining sandbox mode every shell call dies instantly with `PTY shell exited during startup` because the terminal backend cannot create the pseudo-console inside the sandbox, retrying never helps, and `minimal` mounts no fallback shell tool. Family 3, a confined Windows child that died during native initialization (#7876, #7877): every command spawned through the sandbox runner can report exit 0xC0000142 STATUS_DLL_INIT_FAILED with the process never reaching its entry point \u2014 the packaged desktop starts that runner as [process.execPath, entry], and that host has been measured twice with one indistinguishable appearance from inside a session \u2014 either nothing on the runner path ran because the Electron binary launches as an application unless the child's environment carries ELECTRON_RUN_AS_NODE=1 (#7876), or the desktop launcher does set that variable and the child still dies because the restricted token is derived from the Electron process image (#8193), and an MSYS2 / Git-Bash program cannot create its signal pipe under the restricted token while cmd.exe and pwsh run fine in the same workspace under the same mode (#7877) \u2014 and because upstream's runner-failure rules admit only exit 127 with the `windows-acl-run: ` signature, the code is never an error: it arrives as the canonical value of a result the pipeline calls a success. The plugin observes the public `tools/post-execute` waterfall, classifies all three signatures narrowly (only the two `...NamedSecurityInfoW` operations; the PTY message matched on a whole line, never as a substring; the loader status read as a 32-bit integer out of the shell tool's own canonical success value, never from rendered text; both mode-gated families advised only under a mode the policy resolver reports as confining), and attaches ONE durable user-role advisory per agent per family through `additionalContexts`. The ACL advisory names the missing right, both environments the identical text can describe (an inherited Modify-only entry, a data volume where no ACE names the caller at all, and a directory owned by another account), the version boundary that arrived with the mandatory label (0.1.7-alpha.1, flag 20, versus the DACL-only flag 4 up to 0.1.6-alpha.x) together with why downgrading is not the remedy, the discriminator, the remedy forked on an ownership check the user runs (`(Get-Acl \"<dir>\").Owner`), because one command cannot serve both rights situations: where the caller owns the directory, the unelevated `icacls ... :(OI)(CI)(WO)` is the whole of what is missing \u2014 the owner's implicit WRITE_DAC already covers the DACL half \u2014 and where the caller does not own it that same command is refused for want of WRITE_DAC, so the grant has to come from an elevated account, or by taking ownership first, or by moving the workspace under %USERPROFILE% \u2014 and the two remedies that look right and are not (`takeown`, `icacls /reset`), each with the reason it fails; the PTY advisory names the failing combination, states the resolved mode, tells the model to stop rather than retry, and hands the user-side preset choice over \u2014 it never names a shell tool the failing composition does not mount; the native-init advisory states the resolved mode, says the process died before its entry point, enumerates the two producers measured under a confining mode \u2014 naming both measured shapes of the desktop host rather than asserting the one that was measured first \u2014 with the check that separates them (what program the reader ran; whether this host is the packaged desktop binary, which the plugin measures and reports rather than assumes), carries the one conversion a model can make itself (rewrite the work as PowerShell or `cmd` when an MSYS2 program is what could not start), and says plainly which producers it does not know \u2014 it never claims the sandbox caused the failure and never offers a widened mode as a fix. An optional, off-by-default `enforceAfter` refuses an identical ACL call this plugin has watched fail, bounded by `maxDenials`; the blocking half is ACL-only by design. It never edits an ACL, never elevates, never sets another process's environment, and never changes a preset or a mode, and it complements repeat-guard-escalation, which keys on call identity rather than on the environment signature.",
4
+ "version": "0.7.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",