dsh-permission-rules 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.es.md +9 -4
- package/README.hi.md +9 -4
- package/README.md +9 -4
- package/README.pt.md +9 -4
- package/README.zh.md +9 -4
- package/cordis.patch.yml +3 -0
- package/lib/index.js +99 -21
- package/lib/types/config.d.ts +12 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +3 -0
- package/lib/types/config.js.map +1 -1
- package/lib/types/events.d.ts +25 -6
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js +22 -4
- package/lib/types/events.js.map +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -1
- package/lib/types/index.js.map +1 -1
- package/lib/types/prose.d.ts +2 -0
- package/lib/types/prose.d.ts.map +1 -1
- package/lib/types/prose.js +5 -0
- package/lib/types/prose.js.map +1 -1
- package/lib/types/runtime.d.ts +30 -7
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +83 -17
- package/lib/types/runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +15 -0
- package/src/events.ts +28 -6
- package/src/index.ts +1 -1
- package/src/prose.ts +7 -0
- package/src/runtime.ts +84 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to dsh-permission-rules are recorded here, newest first.
|
|
4
4
|
|
|
5
|
+
## v0.4.1 — 2026-08-15
|
|
6
|
+
|
|
7
|
+
### Audit safety on every host (fixes [#2](https://github.com/PerryLink/dsh-permission-rules/issues/2))
|
|
8
|
+
|
|
9
|
+
- Hosts whose `Session.append` predates the `ignorable` envelope marker (the `0.1.0-rc.6` line) silently drop the marker, writing audit events that make sessions unresumable on stricter harness builds (`SessionFormatUnsupportedError`). The runtime now detects such hosts BEFORE the first append (peer-version pre-check) and re-checks after the first append (probe of the returned envelope), then degrades gracefully: session-log audit is disabled with a one-time warning so session logs stay loadable everywhere.
|
|
10
|
+
- New `allowUnmarkedAudit` config (default `false`): set `true` to opt back into the in-session audit trail on pre-marker hosts (accepting that those sessions may need `scripts/repair-session-logs.mjs` before loading on a newer harness). `/rules decisions` explains the disabled audit on degraded hosts.
|
|
11
|
+
- `events.ts` no longer claims rc.6 hosts are harmless ("no failure either way") — the failure mode is documented accurately, and `isMarkedAuditEvent`/`isUnmarkedHostVersion` ship as exported capability helpers.
|
|
12
|
+
- Thanks to [@22xuan](https://github.com/22xuan) for the detailed report and the upstream harness discussion; credited in the README Acknowledgments.
|
|
13
|
+
|
|
14
|
+
### Engineering
|
|
15
|
+
|
|
16
|
+
- 139 tests across 9 suites: new `audit-support.spec.ts` covers the version-line classification, the envelope probe, the pre-append degradation, the opt-in, and the `/rules decisions` notice. Five-language READMEs (config table, Known limitations, Acknowledgments), AGENTS.md, CHANGELOG, and VERIFICATION updated.
|
|
17
|
+
|
|
5
18
|
## v0.4.0 — 2026-08-15
|
|
6
19
|
|
|
7
20
|
### Hot reload and workspace identity
|
|
@@ -18,6 +31,7 @@ All notable changes to dsh-permission-rules are recorded here, newest first.
|
|
|
18
31
|
### Engineering
|
|
19
32
|
|
|
20
33
|
- 133 tests across 8 suites (new coverage: creation-adoption watch paths, fallback recreation, cache-key dedup, `--platform`, `list`, source attribution); coverage gate held. Five-language READMEs, both rules-format references, AGENTS.md, and the `/rules` command hint updated.
|
|
34
|
+
- The test harness now mounts with `watch: false` by default (only the chokidar-mocked watch suite opts in): real watchers on temp workspaces tripped a Node 24 + Windows libuv assertion (`src\win\fs-event.c`) when dirs were removed mid-test — the pre-existing cause of the red windows-latest/Node 24 matrix cells since v0.3.0.
|
|
21
35
|
|
|
22
36
|
## v0.3.0 — 2026-08-14
|
|
23
37
|
|
package/README.es.md
CHANGED
|
@@ -53,7 +53,7 @@ Un segundo modelo responde *"¿es segura ESTA llamada?"* con criterio, pero cues
|
|
|
53
53
|
- ✅ **Metadatos de reglas** — `enabled: false`, `description`, `tags`; `/rules` advierte sobre reglas eclipsadas por un catch-all anterior
|
|
54
54
|
- ✅ **Seguro para el waterfall** — `allow`/paso siempre llaman a `next()`; solo `deny`/`ask` cortocircuitan
|
|
55
55
|
- ✅ **Seam de aprobación oficial** — `ask` fluye por `ctx.approval`; nunca reimplementado, nunca eludido
|
|
56
|
-
- ✅ **Auditoría completa** — los eventos `permissionRules/decision` llevan la acción de la regla, el cwd del workspace Y el resultado final de cada llamada; `/rules decisions` reproduce el rastro en la sesión
|
|
56
|
+
- ✅ **Auditoría completa** — los eventos `permissionRules/decision` llevan la acción de la regla, el cwd del workspace Y el resultado final de cada llamada; `/rules decisions` reproduce el rastro en la sesión; los hosts anteriores al marcador de envoltura de auditoría degradan a auditoría desactivada con un aviso único en lugar de escribir registros irrecuperables (`allowUnmarkedAudit` la reactiva)
|
|
57
57
|
- ✅ **Despliegue en simulación** — `enforce: false` audita lo que la política *haría* (acción hipotética + resultado real posterior, marcado `dryRun`) mientras deja pasar todas las llamadas; prueba segura de políticas en producción
|
|
58
58
|
- ✅ **Prueba en seco** — `/rules test <tool> <json-args>` evalúa las reglas activas sin ejecutar nada, con sobrescrituras `--cwd`, `--env`, `--agent` y `--platform` para cada dimensión
|
|
59
59
|
- ✅ **Recarga en caliente** — vigilancia Chokidar con debounce; una edición rota conserva las reglas previas, nunca falla; un archivo de reglas creado a mitad de sesión (el del proyecto o el fallback) se adopta automáticamente, sin recarga manual
|
|
@@ -68,7 +68,7 @@ dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"
|
|
|
68
68
|
|
|
69
69
|
# o desde un tarball empaquetado (artefactos compilados, sin permiso de build)
|
|
70
70
|
pnpm pack
|
|
71
|
-
dsh plugin --profile web add ./dsh-permission-rules-0.4.
|
|
71
|
+
dsh plugin --profile web add ./dsh-permission-rules-0.4.1.tgz
|
|
72
72
|
|
|
73
73
|
# 2. reinicia
|
|
74
74
|
dsh --profile web
|
|
@@ -114,6 +114,7 @@ Todos los ajustes son campos `Config` de Schemastery (modificables desde cordis.
|
|
|
114
114
|
| `searchUp` | `false` | Recorre los directorios padre desde el cwd de la sesión y combina cada archivo de reglas encontrado, el más cercano primero |
|
|
115
115
|
| `maxGlobStars` | `2` | Límite duro de cuantificadores `*`/`**` sin límite por patrón glob (cota del grado de backtracking) |
|
|
116
116
|
| `enforce` | `true` | `false` = modo simulación: los aciertos deny/ask solo se registran en auditoría con un marcador `dryRun` (acción hipotética + resultado real posterior) y todas las llamadas pasan — prueba una política antes de aplicarla |
|
|
117
|
+
| `allowUnmarkedAudit` | `false` | Los hosts cuyo `Session.append` es anterior al marcador `ignorable` (la línea `0.1.0-rc.6`) escriben eventos de auditoría sin marcar, haciendo las sesiones irrecuperables en builds más estrictos: el plugin los detecta y desactiva la auditoría del registro de sesión con un aviso único. Ponlo en `true` para reactivar el rastro en la sesión (repara los registros existentes con `scripts/repair-session-logs.mjs`) |
|
|
117
118
|
|
|
118
119
|
### Comandos de sesión
|
|
119
120
|
|
|
@@ -151,7 +152,7 @@ La salida de los comandos es solo UI: el modelo aprende las reglas únicamente a
|
|
|
151
152
|
|
|
152
153
|
## Limitaciones conocidas
|
|
153
154
|
|
|
154
|
-
- `permissionRules/decision` se añade con el marcador de envoltura `ignorable: true`, de modo que cualquier build del harness carga el registro: los lectores que no conocen el tipo fuera del repositorio simplemente omiten el registro de auditoría en lugar de rechazar la sesión.
|
|
155
|
+
- `permissionRules/decision` se añade con el marcador de envoltura `ignorable: true`, de modo que cualquier build del harness carga el registro: los lectores que no conocen el tipo fuera del repositorio simplemente omiten el registro de auditoría en lugar de rechazar la sesión. Los hosts cuyo `Session.append` es anterior al marcador (la línea `0.1.0-rc.6`) lo DESCARTAN silenciosamente: el plugin los detecta en tiempo de ejecución (verificación previa de la versión del peer + una sonda del sobre añadido) y desactiva la auditoría del registro de sesión con un aviso único, para que los registros sigan siendo cargables en cualquier parte. Pon `allowUnmarkedAudit: true` para reactivar el rastro en la sesión; los registros ya escritos sin marcador pueden repararse con `scripts/repair-session-logs.mjs` antes de cargarlos en hosts con semántica required-on-read.
|
|
155
156
|
- Los candidatos de `paths` son heurísticos: solo las claves de argumentos documentadas alimentan la coincidencia de rutas, y la coincidencia relativa al workspace ignora las mayúsculas ASCII solo cuando `caseInsensitivePaths` está activo.
|
|
156
157
|
- Los globs son un subconjunto conservador (sin expansión de llaves): escribe dos patrones o usa el modo regex.
|
|
157
158
|
- El guard de backtracking de regex es estructural, no exhaustivo: los casos de ambigüedad por alternancia sin prefijos literales (p. ej. lookarounds elaborados) son responsabilidad del autor; prefiere el modo glob para archivos no confiables.
|
|
@@ -173,7 +174,7 @@ node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
|
|
|
173
174
|
pnpm install # node ^22.19 || >=24
|
|
174
175
|
pnpm run typecheck # tsc, src + tests
|
|
175
176
|
pnpm run lint # eslint, src + tests + scripts
|
|
176
|
-
pnpm test # vitest:
|
|
177
|
+
pnpm test # vitest: 139 tests, 9 suites
|
|
177
178
|
pnpm run test:coverage # puerta de cobertura (90/80/90/90)
|
|
178
179
|
pnpm run build # declaraciones tsc + bundles tsdown (lib/)
|
|
179
180
|
pnpm run pack:check # build + pack (el artefacto publicado)
|
|
@@ -182,6 +183,10 @@ node scripts/check-readme-sync.mjs # puerta de sincronización de READMEs en ci
|
|
|
182
183
|
|
|
183
184
|
Consulta [VERIFICATION.md](VERIFICATION.md) para el registro de verificación headless de extremo a extremo (deny bloqueando una herramienta de shell, ask enrutado por el seam de aprobación, `--dump-config`).
|
|
184
185
|
|
|
186
|
+
## Agradecimientos
|
|
187
|
+
|
|
188
|
+
- Gracias a [@22xuan](https://github.com/22xuan) por el detallado informe sobre los hosts rc.6 que descartan silenciosamente el marcador `ignorable` de los eventos de auditoría ([#2](https://github.com/PerryLink/dsh-permission-rules/issues/2)) y por abrir la discusión en el harness upstream — la detección de capacidades del host en tiempo de ejecución y la corrección de la documentación derivan directamente de ese análisis.
|
|
189
|
+
|
|
185
190
|
## Licencia
|
|
186
191
|
|
|
187
192
|
[Apache License 2.0](LICENSE) © 2026 dsh-permission-rules contributors
|
package/README.hi.md
CHANGED
|
@@ -53,7 +53,7 @@ tools/pre-execute waterfall approval/request waterfall (answ
|
|
|
53
53
|
- ✅ **नियम मेटाडेटा** — `enabled: false`, `description`, `tags`; `/rules` पहले किसी catch-all से छिपे नियमों की चेतावनी देता है
|
|
54
54
|
- ✅ **waterfall-सुरक्षित** — `allow`/पास-थ्रू हमेशा `next()` बुलाते हैं; केवल `deny`/`ask` शॉर्ट-सर्किट करते हैं
|
|
55
55
|
- ✅ **आधिकारिक approval seam** — `ask` `ctx.approval` से होकर जाता है; कभी पुनः-कार्यान्वित नहीं, कभी बायपास नहीं
|
|
56
|
-
- ✅ **पूर्ण ऑडिट** — `permissionRules/decision` इवेंट हर कॉल के लिए नियम क्रिया, workspace cwd और अंतिम परिणाम रखते हैं; `/rules decisions` सत्र में रास्ता फिर चलाता है
|
|
56
|
+
- ✅ **पूर्ण ऑडिट** — `permissionRules/decision` इवेंट हर कॉल के लिए नियम क्रिया, workspace cwd और अंतिम परिणाम रखते हैं; `/rules decisions` सत्र में रास्ता फिर चलाता है; ऑडिट एन्वेलप मार्कर से पुराने होस्ट अप्राप्य लॉग लिखने के बजाय एक-बार चेतावनी के साथ ऑडिट-बंद हो जाते हैं (`allowUnmarkedAudit` पुनः चालू करता है)
|
|
57
57
|
- ✅ **dry-run रोलआउट** — `enforce: false` केवल ऑडिट करता है कि नीति *क्या करती* (काल्पनिक क्रिया + वास्तविक डाउनस्ट्रीम परिणाम, `dryRun` चिह्नित) और हर कॉल को पास करता है; उत्पादन में नई नीति का सुरक्षित परीक्षण
|
|
58
58
|
- ✅ **dry-run परीक्षण** — `/rules test <tool> <json-args>` कुछ भी निष्पादित किए बिना सक्रिय नियमों का मूल्यांकन करता है, हर मिलान आयाम के लिए `--cwd`, `--env`, `--agent` और `--platform` ओवरराइड के साथ
|
|
59
59
|
- ✅ **हॉट रीलोड** — debounce सहित Chokidar निगरानी; टूटा हुआ संपादन पिछले नियम रखता है, कभी क्रैश नहीं करता; सत्र के बीच बनाई गई नियम फ़ाइल (प्रोजेक्ट की या fallback) स्वतः अपनाई जाती है, मैनुअल रीलोड की आवश्यकता नहीं
|
|
@@ -68,7 +68,7 @@ dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"
|
|
|
68
68
|
|
|
69
69
|
# या पैक किए गए tarball से (बिल्ट आर्टिफ़ैक्ट, build अनुमति की ज़रूरत नहीं)
|
|
70
70
|
pnpm pack
|
|
71
|
-
dsh plugin --profile web add ./dsh-permission-rules-0.4.
|
|
71
|
+
dsh plugin --profile web add ./dsh-permission-rules-0.4.1.tgz
|
|
72
72
|
|
|
73
73
|
# 2. पुनः आरंभ करें
|
|
74
74
|
dsh --profile web
|
|
@@ -114,6 +114,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules' # पंक
|
|
|
114
114
|
| `searchUp` | `false` | सत्र cwd से पैरेंट निर्देशिकाओं पर चलकर हर मिली नियम फ़ाइल मिलाता है, निकटतम पहले |
|
|
115
115
|
| `maxGlobStars` | `2` | प्रति glob पैटर्न असीमित `*`/`**` क्वांटिफ़ायर की कठोर सीमा (बैकट्रैकिंग-डिग्री सीमा) |
|
|
116
116
|
| `enforce` | `true` | `false` = dry-run मोड: deny/ask मिलान केवल ऑडिट में `dryRun` चिह्न के साथ दर्ज होते हैं (काल्पनिक क्रिया + वास्तविक डाउनस्ट्रीम परिणाम) और हर कॉल पास होती है — लागू करने से पहले नीति का परीक्षण करें |
|
|
117
|
+
| `allowUnmarkedAudit` | `false` | जिन होस्ट का `Session.append` `ignorable` मार्कर से पुराना है (`0.1.0-rc.6` शृंखला) वे बिना-चिह्न ऑडिट इवेंट लिखते हैं, जिससे सत्र कठोर बिल्ड पर अप्राप्य हो जाते हैं: प्लगइन ऐसे होस्ट का पता लगाकर एक-बार चेतावनी के साथ सत्र-लॉग ऑडिट बंद कर देता है। सत्र में रास्ता वापस चाहिए तो `true` सेट करें (मौजूदा लॉग `scripts/repair-session-logs.mjs` से मरम्मत करें) |
|
|
117
118
|
|
|
118
119
|
### सेशन कमांड
|
|
119
120
|
|
|
@@ -151,7 +152,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules' # पंक
|
|
|
151
152
|
|
|
152
153
|
## ज्ञात सीमाएँ
|
|
153
154
|
|
|
154
|
-
- `permissionRules/decision` एन्वेलप के `ignorable: true` मार्कर के साथ जोड़ा जाता है, इसलिए कोई भी harness बिल्ड लॉग लोड कर लेता है — जो पाठक इस आउट-ऑफ-रेपो प्रकार को नहीं जानते वे सत्र अस्वीकार करने के बजाय केवल ऑडिट रिकॉर्ड छोड़ देते हैं। (rc.6
|
|
155
|
+
- `permissionRules/decision` एन्वेलप के `ignorable: true` मार्कर के साथ जोड़ा जाता है, इसलिए कोई भी harness बिल्ड लॉग लोड कर लेता है — जो पाठक इस आउट-ऑफ-रेपो प्रकार को नहीं जानते वे सत्र अस्वीकार करने के बजाय केवल ऑडिट रिकॉर्ड छोड़ देते हैं। मार्कर से पुराने होस्ट (`0.1.0-rc.6` शृंखला) इसे चुपचाप गिरा देते हैं: प्लगइन रनटाइम पर उन्हें पहचानता है (peer संस्करण पूर्व-जाँच + जोड़े गए एन्वेलप की जाँच) और एक-बार चेतावनी के साथ सत्र-लॉग ऑडिट बंद कर देता है, ताकि लॉग हर जगह लोड होते रहें। सत्र में रास्ता वापस चाहिए तो `allowUnmarkedAudit: true` सेट करें; बिना-चिह्न लिखे गए लॉग required-on-read शब्दार्थ वाले होस्ट पर लोड से पहले `scripts/repair-session-logs.mjs` से मरम्मत किए जा सकते हैं।
|
|
155
156
|
- `paths` उम्मीदवार ह्यूरिस्टिक हैं: केवल दस्तावेज़ित आर्गुमेंट-कुंजियाँ पथ मिलान में आती हैं, और workspace-सापेक्ष मिलान केवल तभी ASCII-केस-असंवेदनशील होता है जब `caseInsensitivePaths` चालू हो।
|
|
156
157
|
- globs एक रूढ़िवादी उपसमुच्चय हैं (brace विस्तार नहीं) — दो पैटर्न लिखें या regex मोड इस्तेमाल करें।
|
|
157
158
|
- regex बैकट्रैकिंग गार्ड संरचनात्मक है, संपूर्ण नहीं: शाब्दिक उपसर्ग के बिना विकल्प-अस्पष्टता के मामले (जैसे कृत्रिम lookarounds) लेखक की ज़िम्मेदारी हैं; अविश्वसनीय फ़ाइलों के लिए glob मोड पसंद करें।
|
|
@@ -173,7 +174,7 @@ node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
|
|
|
173
174
|
pnpm install # node ^22.19 || >=24
|
|
174
175
|
pnpm run typecheck # tsc, src + tests
|
|
175
176
|
pnpm run lint # eslint, src + tests + scripts
|
|
176
|
-
pnpm test # vitest:
|
|
177
|
+
pnpm test # vitest: 139 tests, 9 suites
|
|
177
178
|
pnpm run test:coverage # कवरेज द्वार (90/80/90/90)
|
|
178
179
|
pnpm run build # tsc डिक्लेरेशन + tsdown bundles (lib/)
|
|
179
180
|
pnpm run pack:check # build + pack (प्रकाशित आर्टिफ़ैक्ट)
|
|
@@ -182,6 +183,10 @@ node scripts/check-readme-sync.mjs # पाँच-भाषा README सिं
|
|
|
182
183
|
|
|
183
184
|
headless एंड-टू-एंड सत्यापन रिकॉर्ड (deny द्वारा shell टूल रोकना, ask का approval seam से गुज़रना, `--dump-config`) [VERIFICATION.md](VERIFICATION.md) में देखें।
|
|
184
185
|
|
|
186
|
+
## आभार
|
|
187
|
+
|
|
188
|
+
- [@22xuan](https://github.com/22xuan) को धन्यवाद — rc.6 होस्ट द्वारा ऑडिट इवेंट के `ignorable` मार्कर को चुपचाप गिराने की विस्तृत रिपोर्ट ([#2](https://github.com/PerryLink/dsh-permission-rules/issues/2)) और अपस्ट्रीम harness चर्चा खोलने के लिए — रनटाइम होस्ट-क्षमता पहचान और दस्तावेज़ सुधार सीधे उसी विश्लेषण से निकले हैं।
|
|
189
|
+
|
|
185
190
|
## लाइसेंस
|
|
186
191
|
|
|
187
192
|
[Apache License 2.0](LICENSE) © 2026 dsh-permission-rules contributors
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ A second model answers *"is THIS call okay?"* with judgment, but costs a round-t
|
|
|
53
53
|
- ✅ **Rule metadata** — `enabled: false`, `description`, `tags`; `/rules` warns about rules shadowed by an earlier catch-all
|
|
54
54
|
- ✅ **Waterfall-safe** — `allow`/passthrough always call `next()`; only `deny`/`ask` short-circuit
|
|
55
55
|
- ✅ **Official approval seam** — `ask` flows through `ctx.approval`; never re-implemented, never bypassed
|
|
56
|
-
- ✅ **Full audit** — `permissionRules/decision` events carry the rule action, the workspace cwd, AND the final outcome for every call; `/rules decisions` replays the trail in-session
|
|
56
|
+
- ✅ **Full audit** — `permissionRules/decision` events carry the rule action, the workspace cwd, AND the final outcome for every call; `/rules decisions` replays the trail in-session; hosts that predate the audit envelope marker degrade to audit-off with a one-time warning instead of writing unresumable logs (`allowUnmarkedAudit` opts back in)
|
|
57
57
|
- ✅ **Dry-run rollout** — `enforce: false` audits what the policy *would* do (would-be action + real downstream outcome, `dryRun`-marked) while passing every call through; safe policy trialing in production
|
|
58
58
|
- ✅ **Dry-run testing** — `/rules test <tool> <json-args>` evaluates the active rules without executing anything, with `--cwd`, `--env`, `--agent`, and `--platform` overrides for every match dimension
|
|
59
59
|
- ✅ **Hot reload** — Chokidar watch with debounce; a broken edit keeps the previous rules, never crashes; a rule file created mid-session (the project file or the fallback) is adopted automatically, no manual reload
|
|
@@ -68,7 +68,7 @@ dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"
|
|
|
68
68
|
|
|
69
69
|
# or from a packed tarball (built artifacts, no build permission needed)
|
|
70
70
|
pnpm pack
|
|
71
|
-
dsh plugin --profile web add ./dsh-permission-rules-0.4.
|
|
71
|
+
dsh plugin --profile web add ./dsh-permission-rules-0.4.1.tgz
|
|
72
72
|
|
|
73
73
|
# 2. restart
|
|
74
74
|
dsh --profile web
|
|
@@ -114,6 +114,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
|
|
|
114
114
|
| `searchUp` | `false` | Walk parent directories from the session cwd and merge every found rule file, nearest first |
|
|
115
115
|
| `maxGlobStars` | `2` | Hard cap on unbounded `*`/`**` quantifiers per glob pattern (backtracking-degree bound) |
|
|
116
116
|
| `enforce` | `true` | `false` = dry-run mode: deny/ask hits are audit-logged with a `dryRun` marker (would-be action + real downstream outcome) and every call passes through — trial a policy before enforcing it |
|
|
117
|
+
| `allowUnmarkedAudit` | `false` | Hosts whose `Session.append` predates the `ignorable` marker (the `0.1.0-rc.6` line) write audit events unmarked, making sessions unresumable on stricter builds: the plugin detects them and disables session-log audit with a one-time warning. Set `true` to opt back into the in-session trail (repair existing logs with `scripts/repair-session-logs.mjs`) |
|
|
117
118
|
|
|
118
119
|
### Session commands
|
|
119
120
|
|
|
@@ -151,7 +152,7 @@ Command output is UI-only — the model learns the rules only through the tool r
|
|
|
151
152
|
|
|
152
153
|
## Known limitations
|
|
153
154
|
|
|
154
|
-
- `permissionRules/decision` is appended with the envelope's `ignorable: true` marker, so any harness build loads the log — readers that do not know the out-of-repo type simply skip the audit record instead of refusing the session. (rc.6
|
|
155
|
+
- `permissionRules/decision` is appended with the envelope's `ignorable: true` marker, so any harness build loads the log — readers that do not know the out-of-repo type simply skip the audit record instead of refusing the session. Hosts whose `Session.append` predates the marker (the `0.1.0-rc.6` line) silently DROP it: the plugin detects them at runtime (peer-version pre-check + a probe of the appended envelope) and disables session-log audit with a one-time warning, so session logs stay loadable everywhere. Set `allowUnmarkedAudit: true` to opt back into the in-session trail; logs already written without the marker can be repaired with `scripts/repair-session-logs.mjs` before loading on hosts with required-on-read semantics.
|
|
155
156
|
- `paths` candidates are heuristic: only the documented argument keys feed path matching, and workspace-relative matching is ASCII-case-insensitive only when `caseInsensitivePaths` is on.
|
|
156
157
|
- Globs are a conservative subset (no brace expansion) — write two patterns, or use regex mode.
|
|
157
158
|
- The regex backtracking guard is structural, not exhaustive: alternation-ambiguity cases without literal prefixes (e.g. crafted lookarounds) are the author's responsibility; prefer glob mode for untrusted files.
|
|
@@ -173,7 +174,7 @@ node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
|
|
|
173
174
|
pnpm install # node ^22.19 || >=24
|
|
174
175
|
pnpm run typecheck # tsc, src + tests
|
|
175
176
|
pnpm run lint # eslint, src + tests + scripts
|
|
176
|
-
pnpm test # vitest:
|
|
177
|
+
pnpm test # vitest: 139 tests, 9 suites
|
|
177
178
|
pnpm run test:coverage # coverage gate (90/80/90/90)
|
|
178
179
|
pnpm run build # tsc declarations + tsdown bundles (lib/)
|
|
179
180
|
pnpm run pack:check # build + pack (the published artifact)
|
|
@@ -182,6 +183,10 @@ node scripts/check-readme-sync.mjs # five-language README sync gate (also in CI
|
|
|
182
183
|
|
|
183
184
|
See [VERIFICATION.md](VERIFICATION.md) for the headless end-to-end verification record (deny blocking a shell tool, ask routing through the approval seam, `--dump-config`).
|
|
184
185
|
|
|
186
|
+
## Acknowledgments
|
|
187
|
+
|
|
188
|
+
- Thanks to [@22xuan](https://github.com/22xuan) for the detailed report on rc.6 hosts silently dropping the audit event's `ignorable` marker ([#2](https://github.com/PerryLink/dsh-permission-rules/issues/2)) and for filing the upstream harness discussion — the runtime host-capability detection and the documentation correction drew directly from that analysis.
|
|
189
|
+
|
|
185
190
|
## License
|
|
186
191
|
|
|
187
192
|
[Apache License 2.0](LICENSE) © 2026 dsh-permission-rules contributors
|
package/README.pt.md
CHANGED
|
@@ -53,7 +53,7 @@ Um segundo modelo responde *"ESTA chamada é segura?"* com julgamento, mas custa
|
|
|
53
53
|
- ✅ **Metadados de regras** — `enabled: false`, `description`, `tags`; `/rules` avisa sobre regras encobertas por um catch-all anterior
|
|
54
54
|
- ✅ **Seguro para o waterfall** — `allow`/passagem sempre chamam `next()`; somente `deny`/`ask` curto-circuitam
|
|
55
55
|
- ✅ **Seam de aprovação oficial** — `ask` flui por `ctx.approval`; nunca reimplementado, nunca contornado
|
|
56
|
-
- ✅ **Auditoria completa** — os eventos `permissionRules/decision` carregam a ação da regra, o cwd do workspace E o resultado final de cada chamada; `/rules decisions` reproduz o rastro na sessão
|
|
56
|
+
- ✅ **Auditoria completa** — os eventos `permissionRules/decision` carregam a ação da regra, o cwd do workspace E o resultado final de cada chamada; `/rules decisions` reproduz o rastro na sessão; hosts anteriores ao marcador de envelope de auditoria degradam para auditoria desativada com um aviso único em vez de gravar registros irrecuperáveis (`allowUnmarkedAudit` reativa)
|
|
57
57
|
- ✅ **Implantação em simulação** — `enforce: false` audita o que a política *faria* (ação hipotética + resultado real posterior, marcado com `dryRun`) enquanto deixa todas as chamadas passarem; teste seguro de políticas em produção
|
|
58
58
|
- ✅ **Teste em seco** — `/rules test <tool> <json-args>` avalia as regras ativas sem executar nada, com sobrescritas `--cwd`, `--env`, `--agent` e `--platform` para cada dimensão
|
|
59
59
|
- ✅ **Recarga a quente** — vigilância Chokidar com debounce; uma edição quebrada mantém as regras anteriores, nunca quebra; um arquivo de regras criado no meio da sessão (o do projeto ou o fallback) é adotado automaticamente, sem recarga manual
|
|
@@ -68,7 +68,7 @@ dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"
|
|
|
68
68
|
|
|
69
69
|
# ou a partir de um tarball empacotado (artefatos compilados, sem permissão de build)
|
|
70
70
|
pnpm pack
|
|
71
|
-
dsh plugin --profile web add ./dsh-permission-rules-0.4.
|
|
71
|
+
dsh plugin --profile web add ./dsh-permission-rules-0.4.1.tgz
|
|
72
72
|
|
|
73
73
|
# 2. reinicie
|
|
74
74
|
dsh --profile web
|
|
@@ -114,6 +114,7 @@ Todos os ajustes são campos `Config` do Schemastery (alteráveis no cordis.yml)
|
|
|
114
114
|
| `searchUp` | `false` | Percorre os diretórios pais a partir do cwd da sessão e combina cada arquivo de regras encontrado, o mais próximo primeiro |
|
|
115
115
|
| `maxGlobStars` | `2` | Limite rígido de quantificadores `*`/`**` ilimitados por padrão glob (cota do grau de backtracking) |
|
|
116
116
|
| `enforce` | `true` | `false` = modo simulação: acertos deny/ask são apenas registrados na auditoria com um marcador `dryRun` (ação hipotética + resultado real posterior) e todas as chamadas passam — teste uma política antes de aplicá-la |
|
|
117
|
+
| `allowUnmarkedAudit` | `false` | Hosts cujo `Session.append` é anterior ao marcador `ignorable` (a linha `0.1.0-rc.6`) gravam eventos de auditoria sem marcação, tornando as sessões irrecuperáveis em builds mais rígidos: o plugin os detecta e desativa a auditoria do registro da sessão com um aviso único. Defina `true` para reativar o rastro na sessão (repare registros existentes com `scripts/repair-session-logs.mjs`) |
|
|
117
118
|
|
|
118
119
|
### Comandos de sessão
|
|
119
120
|
|
|
@@ -151,7 +152,7 @@ A saída dos comandos é somente UI — o modelo aprende as regras apenas pelos
|
|
|
151
152
|
|
|
152
153
|
## Limitações conhecidas
|
|
153
154
|
|
|
154
|
-
- `permissionRules/decision` é anexado com o marcador de envelope `ignorable: true`, então qualquer build do harness carrega o log — leitores que não conhecem o tipo fora do repositório simplesmente pulam o registro de auditoria em vez de recusar a sessão.
|
|
155
|
+
- `permissionRules/decision` é anexado com o marcador de envelope `ignorable: true`, então qualquer build do harness carrega o log — leitores que não conhecem o tipo fora do repositório simplesmente pulam o registro de auditoria em vez de recusar a sessão. Hosts cujo `Session.append` é anterior ao marcador (a linha `0.1.0-rc.6`) o DESCARTAM silenciosamente: o plugin os detecta em tempo de execução (verificação prévia da versão do peer + sonda do envelope anexado) e desativa a auditoria do registro da sessão com um aviso único, mantendo os registros carregáveis em qualquer lugar. Defina `allowUnmarkedAudit: true` para reativar o rastro na sessão; registros já gravados sem o marcador podem ser reparados com `scripts/repair-session-logs.mjs` antes de carregar em hosts com semântica required-on-read.
|
|
155
156
|
- Os candidatos de `paths` são heurísticos: apenas as chaves de argumentos documentadas alimentam a correspondência de caminhos, e a correspondência relativa ao workspace ignora maiúsculas ASCII somente quando `caseInsensitivePaths` está ativo.
|
|
156
157
|
- Os globs são um subconjunto conservador (sem expansão de chaves) — escreva dois padrões ou use o modo regex.
|
|
157
158
|
- O guard de backtracking de regex é estrutural, não exaustivo: casos de ambiguidade por alternância sem prefixos literais (ex.: lookarounds elaborados) são responsabilidade do autor; prefira o modo glob para arquivos não confiáveis.
|
|
@@ -173,7 +174,7 @@ node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
|
|
|
173
174
|
pnpm install # node ^22.19 || >=24
|
|
174
175
|
pnpm run typecheck # tsc, src + tests
|
|
175
176
|
pnpm run lint # eslint, src + tests + scripts
|
|
176
|
-
pnpm test # vitest:
|
|
177
|
+
pnpm test # vitest: 139 tests, 9 suites
|
|
177
178
|
pnpm run test:coverage # portão de cobertura (90/80/90/90)
|
|
178
179
|
pnpm run build # declarações tsc + bundles tsdown (lib/)
|
|
179
180
|
pnpm run pack:check # build + pack (o artefato publicado)
|
|
@@ -182,6 +183,10 @@ node scripts/check-readme-sync.mjs # portão de sincronização dos READMEs em
|
|
|
182
183
|
|
|
183
184
|
Veja [VERIFICATION.md](VERIFICATION.md) para o registro de verificação headless de ponta a ponta (deny bloqueando uma ferramenta de shell, ask roteado pelo seam de aprovação, `--dump-config`).
|
|
184
185
|
|
|
186
|
+
## Agradecimentos
|
|
187
|
+
|
|
188
|
+
- Agradecemos a [@22xuan](https://github.com/22xuan) pelo relatório detalhado sobre hosts rc.6 descartando silenciosamente o marcador `ignorable` dos eventos de auditoria ([#2](https://github.com/PerryLink/dsh-permission-rules/issues/2)) e por abrir a discussão no harness upstream — a detecção de capacidade do host em tempo de execução e a correção da documentação derivam diretamente dessa análise.
|
|
189
|
+
|
|
185
190
|
## Licença
|
|
186
191
|
|
|
187
192
|
[Apache License 2.0](LICENSE) © 2026 dsh-permission-rules contributors
|
package/README.zh.md
CHANGED
|
@@ -53,7 +53,7 @@ tools/pre-execute waterfall approval/request waterfall(ans
|
|
|
53
53
|
- ✅ **规则元数据** — `enabled: false`、`description`、`tags`;`/rules` 会告警被前面通配规则遮蔽的规则
|
|
54
54
|
- ✅ **waterfall 安全** — `allow`/透传一律 `next()`;只有 `deny`/`ask` 短路
|
|
55
55
|
- ✅ **官方审批 seam** — `ask` 走 `ctx.approval`;不重复实现、绝不绕过
|
|
56
|
-
- ✅ **完整审计** — `permissionRules/decision` 事件记录规则动作、工作区 cwd 与最终裁决;`/rules decisions`
|
|
56
|
+
- ✅ **完整审计** — `permissionRules/decision` 事件记录规则动作、工作区 cwd 与最终裁决;`/rules decisions` 可在会话内回放审计轨迹;早于审计信封标记的宿主自动降级为关闭审计并一次性警告,绝不写出无法恢复的日志(`allowUnmarkedAudit` 可重新开启)
|
|
57
57
|
- ✅ **干跑上线** — `enforce: false` 只审计策略「本会做什么」(记录本会动作 + 下游真实结果,带 `dryRun` 标记),所有调用照常透传;可在生产环境安全试跑新策略
|
|
58
58
|
- ✅ **干跑测试** — `/rules test <工具> <json-参数>` 不执行任何东西地评估规则命中,支持 `--cwd`、`--env`、`--agent`、`--platform` 覆盖每个匹配维度
|
|
59
59
|
- ✅ **热更新** — Chokidar 监视 + 去抖;改坏了保留旧规则,绝不崩溃;会话中途创建的规则文件(项目文件或 fallback)自动生效,无需手动重载
|
|
@@ -68,7 +68,7 @@ dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"
|
|
|
68
68
|
|
|
69
69
|
# 或从打包好的 tarball 安装(预构建产物,无需构建许可)
|
|
70
70
|
pnpm pack
|
|
71
|
-
dsh plugin --profile web add ./dsh-permission-rules-0.4.
|
|
71
|
+
dsh plugin --profile web add ./dsh-permission-rules-0.4.1.tgz
|
|
72
72
|
|
|
73
73
|
# 2. 重启
|
|
74
74
|
dsh --profile web
|
|
@@ -114,6 +114,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules' # 验证挂
|
|
|
114
114
|
| `searchUp` | `false` | 从会话 cwd 向上逐级查找并合并规则文件,最近的优先 |
|
|
115
115
|
| `maxGlobStars` | `2` | 每个 glob 模式的无界 `*`/`**` 量词数硬上界(回溯度封顶) |
|
|
116
116
|
| `enforce` | `true` | `false` = 干跑模式:deny/ask 命中只写审计(带 `dryRun` 标记,记录本会动作与下游真实结果),所有调用照常透传——先试跑策略再强制执行 |
|
|
117
|
+
| `allowUnmarkedAudit` | `false` | 早于 `ignorable` 信封标记的宿主(`0.1.0-rc.6` 系列)会写出无标记审计事件,使会话在更严格构建上无法恢复:插件会探测到此类宿主并停用会话日志审计(一次性警告)。设 `true` 重新开启会话内轨迹(已有日志用 `scripts/repair-session-logs.mjs` 修复) |
|
|
117
118
|
|
|
118
119
|
### 会话命令
|
|
119
120
|
|
|
@@ -151,7 +152,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules' # 验证挂
|
|
|
151
152
|
|
|
152
153
|
## 已知局限
|
|
153
154
|
|
|
154
|
-
- `permissionRules/decision` 以信封 `ignorable: true` 标记写入,任何 harness
|
|
155
|
+
- `permissionRules/decision` 以信封 `ignorable: true` 标记写入,任何 harness 构建都能加载日志——不认识该仓库外类型的第一方读取器会跳过这条审计记录而不是拒绝整个会话。早于该标记的宿主(`0.1.0-rc.6` 系列)会静默丢弃它:插件在运行时探测(peer 版本预检 + 已写事件信封探针),探测到即停用会话日志审计并一次性警告,确保会话日志在任何构建上都能加载。设 `allowUnmarkedAudit: true` 重新开启会话内轨迹;已写出且缺标记的日志可在 required-on-read 语义的宿主上加载前用 `scripts/repair-session-logs.mjs` 修复。
|
|
155
156
|
- `paths` 候选是启发式的:只有文档化的参数键进入路径匹配;工作区相对匹配仅在 `caseInsensitivePaths` 开启时忽略 ASCII 大小写。
|
|
156
157
|
- glob 是保守子集(不支持花括号展开)——写两条模式,或用 regex 模式。
|
|
157
158
|
- regex 回溯守卫是结构性的而非穷尽的:不含字面量前缀的交替歧义(如精心构造的环视)由作者自负其责;不可信文件请优先用 glob 模式。
|
|
@@ -173,7 +174,7 @@ node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
|
|
|
173
174
|
pnpm install # node ^22.19 || >=24
|
|
174
175
|
pnpm run typecheck # tsc,src + tests
|
|
175
176
|
pnpm run lint # eslint,src + tests + scripts
|
|
176
|
-
pnpm test # vitest:
|
|
177
|
+
pnpm test # vitest:139 测试 / 9 套件
|
|
177
178
|
pnpm run test:coverage # 覆盖率门禁(90/80/90/90)
|
|
178
179
|
pnpm run build # tsc 声明 + tsdown 打包(lib/)
|
|
179
180
|
pnpm run pack:check # 构建 + pack(发布产物)
|
|
@@ -182,6 +183,10 @@ node scripts/check-readme-sync.mjs # 五语 README 同步门禁(CI 同步执
|
|
|
182
183
|
|
|
183
184
|
headless 端到端验证记录(deny 阻止 shell 工具、ask 走审批 seam、`--dump-config`)见 [VERIFICATION.md](VERIFICATION.md)。
|
|
184
185
|
|
|
186
|
+
## 致谢
|
|
187
|
+
|
|
188
|
+
- 感谢 [@22xuan](https://github.com/22xuan) 关于 rc.6 宿主静默丢弃审计事件 `ignorable` 标记的详尽报告([#2](https://github.com/PerryLink/dsh-permission-rules/issues/2))以及其向上游 harness 提交的讨论——运行时宿主能力探测与文档修正均直接源自该分析。
|
|
189
|
+
|
|
185
190
|
## License
|
|
186
191
|
|
|
187
192
|
[Apache License 2.0](LICENSE) © 2026 dsh-permission-rules contributors
|
package/cordis.patch.yml
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
4
|
import chokidar from "chokidar";
|
|
@@ -32,7 +33,8 @@ const Config = z.object({
|
|
|
32
33
|
audit: z.union(["all", "hits"]).default("all"),
|
|
33
34
|
searchUp: z.boolean().default(false),
|
|
34
35
|
maxGlobStars: z.number().default(2),
|
|
35
|
-
enforce: z.boolean().default(true)
|
|
36
|
+
enforce: z.boolean().default(true),
|
|
37
|
+
allowUnmarkedAudit: z.boolean().default(false)
|
|
36
38
|
});
|
|
37
39
|
/**
|
|
38
40
|
* Validate raw values and fill explicit defaults. A `maxRules`,
|
|
@@ -70,6 +72,7 @@ function resolveConfig(config = {}) {
|
|
|
70
72
|
assertBoolean("searchUp", searchUp);
|
|
71
73
|
assertBoolean("caseInsensitivePaths", config.caseInsensitivePaths ?? process.platform === "win32");
|
|
72
74
|
assertBoolean("enforce", config.enforce ?? true);
|
|
75
|
+
assertBoolean("allowUnmarkedAudit", config.allowUnmarkedAudit ?? false);
|
|
73
76
|
return {
|
|
74
77
|
rulesFile,
|
|
75
78
|
fallbackPath: config.fallbackPath,
|
|
@@ -84,7 +87,8 @@ function resolveConfig(config = {}) {
|
|
|
84
87
|
audit: config.audit ?? "all",
|
|
85
88
|
searchUp,
|
|
86
89
|
maxGlobStars,
|
|
87
|
-
enforce: config.enforce ?? true
|
|
90
|
+
enforce: config.enforce ?? true,
|
|
91
|
+
allowUnmarkedAudit: config.allowUnmarkedAudit ?? false
|
|
88
92
|
};
|
|
89
93
|
}
|
|
90
94
|
/** Throw unless `key` is one of `allowed` (TypeScript's closed enums are not runtime checks). */
|
|
@@ -934,6 +938,7 @@ const EN = {
|
|
|
934
938
|
usage: "Usage: /rules [list | reload | decisions [n] | test <tool> <json-args>]",
|
|
935
939
|
decisionsHeader: (shown, total) => `Last ${shown} of ${total} permission decision(s) for this session:`,
|
|
936
940
|
noDecisions: "No permission decisions recorded in this session yet.",
|
|
941
|
+
auditDisabledNotice: "Session-log audit is disabled on this host: its Session.append predates the ignorable marker and would make sessions unresumable elsewhere (set allowUnmarkedAudit: true to opt back in).",
|
|
937
942
|
invalidDecisionsCount: (arg) => `Invalid decisions count "${arg}": give a positive integer (default 10).`,
|
|
938
943
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
939
944
|
return `- #${seq} ${action} ${toolName}${ruleIndex === void 0 ? "" : ` (rule ${ruleIndex + 1})`}${dryRun ? outcome === void 0 ? " (dry-run)" : ` (dry-run → ${outcome})` : ""}${reason === void 0 ? "" : `: ${short(reason)}`}`;
|
|
@@ -960,6 +965,7 @@ const ZH = {
|
|
|
960
965
|
usage: "用法:/rules [list | reload | decisions [n] | test <工具名> <json-参数>]",
|
|
961
966
|
decisionsHeader: (shown, total) => `本会话最近 ${shown}/${total} 条权限裁决:`,
|
|
962
967
|
noDecisions: "本会话还没有记录任何权限裁决。",
|
|
968
|
+
auditDisabledNotice: "本宿主上会话日志审计已停用:其 Session.append 早于 ignorable 标记支持,会令会话在其他宿主上无法恢复(设 allowUnmarkedAudit: true 可重新开启)。",
|
|
963
969
|
invalidDecisionsCount: (arg) => `无效的裁决条数 "${arg}":请给正整数(默认 10)。`,
|
|
964
970
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
965
971
|
return `- #${seq} ${action} ${toolName}${ruleIndex === void 0 ? "" : `(规则 ${ruleIndex + 1})`}${dryRun ? outcome === void 0 ? "(干跑)" : `(干跑 → ${outcome})` : ""}${reason === void 0 ? "" : `:${short(reason)}`}`;
|
|
@@ -986,6 +992,7 @@ const ES = {
|
|
|
986
992
|
usage: "Uso: /rules [list | reload | decisions [n] | test <herramienta> <json-args>]",
|
|
987
993
|
decisionsHeader: (shown, total) => `Últimas ${shown} de ${total} decisión(es) de permiso de esta sesión:`,
|
|
988
994
|
noDecisions: "Esta sesión aún no registra decisiones de permiso.",
|
|
995
|
+
auditDisabledNotice: "La auditoría del registro de sesión está desactivada en este host: su Session.append es anterior al marcador ignorable y haría las sesiones irrecuperables en otros hosts (establece allowUnmarkedAudit: true para reactivarla).",
|
|
989
996
|
invalidDecisionsCount: (arg) => `Cantidad de decisiones inválida "${arg}": indique un entero positivo (por defecto 10).`,
|
|
990
997
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
991
998
|
return `- #${seq} ${action} ${toolName}${ruleIndex === void 0 ? "" : ` (regla ${ruleIndex + 1})`}${dryRun ? outcome === void 0 ? " (simulación)" : ` (simulación → ${outcome})` : ""}${reason === void 0 ? "" : `: ${short(reason)}`}`;
|
|
@@ -1012,6 +1019,7 @@ const PT = {
|
|
|
1012
1019
|
usage: "Uso: /rules [list | reload | decisions [n] | test <ferramenta> <json-args>]",
|
|
1013
1020
|
decisionsHeader: (shown, total) => `Últimas ${shown} de ${total} decisão(ões) de permissão desta sessão:`,
|
|
1014
1021
|
noDecisions: "Esta sessão ainda não registrou decisões de permissão.",
|
|
1022
|
+
auditDisabledNotice: "A auditoria do registro da sessão está desativada neste host: seu Session.append é anterior ao marcador ignorable e tornaria as sessões irrecuperáveis em outros hosts (defina allowUnmarkedAudit: true para reativar).",
|
|
1015
1023
|
invalidDecisionsCount: (arg) => `Quantidade de decisões inválida "${arg}": informe um inteiro positivo (padrão 10).`,
|
|
1016
1024
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
1017
1025
|
return `- #${seq} ${action} ${toolName}${ruleIndex === void 0 ? "" : ` (regra ${ruleIndex + 1})`}${dryRun ? outcome === void 0 ? " (simulação)" : ` (simulação → ${outcome})` : ""}${reason === void 0 ? "" : `: ${short(reason)}`}`;
|
|
@@ -1038,6 +1046,7 @@ const HI = {
|
|
|
1038
1046
|
usage: "उपयोग: /rules [list | reload | decisions [n] | test <टूल> <json-args>]",
|
|
1039
1047
|
decisionsHeader: (shown, total) => `इस सत्र के अंतिम ${shown}/${total} अनुमति निर्णय:`,
|
|
1040
1048
|
noDecisions: "इस सत्र में अभी कोई अनुमति निर्णय दर्ज नहीं है।",
|
|
1049
|
+
auditDisabledNotice: "इस होस्ट पर सत्र-लॉग ऑडिट अक्षम है: इसका Session.append ignorable मार्कर से पुराना है और सत्र अन्य होस्ट पर अप्राप्य बन जाते (पुनः चालू करने के लिए allowUnmarkedAudit: true सेट करें)।",
|
|
1041
1050
|
invalidDecisionsCount: (arg) => `अमान्य निर्णय-संख्या "${arg}": एक धनात्मक पूर्णांक दें (डिफ़ॉल्ट 10)।`,
|
|
1042
1051
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
1043
1052
|
return `- #${seq} ${action} ${toolName}${ruleIndex === void 0 ? "" : ` (नियम ${ruleIndex + 1})`}${dryRun ? outcome === void 0 ? " (ड्राई-रन)" : ` (ड्राई-रन → ${outcome})` : ""}${reason === void 0 ? "" : `: ${short(reason)}`}`;
|
|
@@ -1129,6 +1138,20 @@ const DESCRIBE_TOKENS = {
|
|
|
1129
1138
|
}
|
|
1130
1139
|
};
|
|
1131
1140
|
//#endregion
|
|
1141
|
+
//#region src/events.ts
|
|
1142
|
+
/**
|
|
1143
|
+
* Whether an `append` call actually honored the `ignorable` marker: the
|
|
1144
|
+
* logged event returned by the host carries `ignorable === true` on
|
|
1145
|
+
* marker-aware builds and nothing on pre-marker builds. `false` (or any
|
|
1146
|
+
* non-event return) means the host dropped the marker and the event landed
|
|
1147
|
+
* unmarked — the runtime then degrades instead of polluting further logs.
|
|
1148
|
+
* @param result - the return value of the audit append.
|
|
1149
|
+
* @returns true only when the marker is present on the returned envelope.
|
|
1150
|
+
*/
|
|
1151
|
+
function isMarkedAuditEvent(result) {
|
|
1152
|
+
return typeof result === "object" && result !== null && result.ignorable === true;
|
|
1153
|
+
}
|
|
1154
|
+
//#endregion
|
|
1132
1155
|
//#region src/runtime.ts
|
|
1133
1156
|
/**
|
|
1134
1157
|
* Runtime of `dsh-permission-rules`: per-workspace rule loading (project
|
|
@@ -1155,6 +1178,8 @@ var PermissionRulesRuntime = class {
|
|
|
1155
1178
|
watchers = /* @__PURE__ */ new Map();
|
|
1156
1179
|
/** Debounce timers per rule-file path. */
|
|
1157
1180
|
timers = /* @__PURE__ */ new Map();
|
|
1181
|
+
/** Whether the host honors the audit envelope's `ignorable` marker: unknown until the first decision (or peer-version check). */
|
|
1182
|
+
auditSupport = "unknown";
|
|
1158
1183
|
constructor(ctx, config) {
|
|
1159
1184
|
this.ctx = ctx;
|
|
1160
1185
|
this.config = config;
|
|
@@ -1395,13 +1420,17 @@ var PermissionRulesRuntime = class {
|
|
|
1395
1420
|
* Append the log-only `permissionRules/decision` audit event for every
|
|
1396
1421
|
* decision (passthrough included unless `audit: 'hits'`), requesting the
|
|
1397
1422
|
* envelope's `ignorable: true` marker so any harness build can load the
|
|
1398
|
-
* log
|
|
1399
|
-
*
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1423
|
+
* log. Hosts whose `Session.append` predates the marker (the rc.6 line)
|
|
1424
|
+
* silently drop it, leaving sessions unresumable on stricter hosts — the
|
|
1425
|
+
* runtime therefore detects such hosts BEFORE the first append (peer
|
|
1426
|
+
* version) and re-checks after the first append (returned envelope), then
|
|
1427
|
+
* degrades: session-log audit is disabled with a one-time warning unless
|
|
1428
|
+
* `allowUnmarkedAudit: true` opts back in. `source` names the matched
|
|
1429
|
+
* rule's file, or the nearest effective file on a passthrough; `cwd`
|
|
1430
|
+
* names the workspace the rules were resolved for; `dryRun` marks
|
|
1431
|
+
* would-be deny/ask hits under `enforce: false`. Agentless calls have no
|
|
1432
|
+
* session to audit; append failures are contained so an audit hiccup can
|
|
1433
|
+
* never change a permission decision.
|
|
1405
1434
|
* @param exec - the pending call.
|
|
1406
1435
|
* @param loaded - the rules in effect.
|
|
1407
1436
|
* @param hit - the first matching rule, or undefined for passthrough.
|
|
@@ -1412,8 +1441,17 @@ var PermissionRulesRuntime = class {
|
|
|
1412
1441
|
if (this.config.audit === "hits" && hit === void 0) return;
|
|
1413
1442
|
const agent = exec.agent;
|
|
1414
1443
|
if (agent === void 0) return;
|
|
1444
|
+
if (this.auditSupport === "unsupported") return;
|
|
1445
|
+
if (this.auditSupport === "unknown" && !this.config.allowUnmarkedAudit) {
|
|
1446
|
+
const version = this.peerVersion();
|
|
1447
|
+
if (version !== null && isUnmarkedHostVersion(version)) {
|
|
1448
|
+
this.auditSupport = "unsupported";
|
|
1449
|
+
this.warnUnmarkedAuditHost();
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1415
1453
|
try {
|
|
1416
|
-
|
|
1454
|
+
const result = this.appendAudit(agent, {
|
|
1417
1455
|
toolName: exec.name,
|
|
1418
1456
|
callId: exec.callId,
|
|
1419
1457
|
source: hit === void 0 ? loaded.sources[0] ?? "" : loaded.sources[hit.rule.sourceIndex] ?? "",
|
|
@@ -1425,11 +1463,35 @@ var PermissionRulesRuntime = class {
|
|
|
1425
1463
|
reason: hit.rule.reason
|
|
1426
1464
|
} : {},
|
|
1427
1465
|
...dryRun ? { dryRun: true } : {}
|
|
1428
|
-
}
|
|
1466
|
+
});
|
|
1467
|
+
if (this.auditSupport === "unknown" && !this.config.allowUnmarkedAudit) {
|
|
1468
|
+
if (isMarkedAuditEvent(result)) this.auditSupport = "supported";
|
|
1469
|
+
else {
|
|
1470
|
+
this.auditSupport = "unsupported";
|
|
1471
|
+
this.warnUnmarkedAuditHost();
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1429
1474
|
} catch (error) {
|
|
1430
1475
|
this.ctx.logger.warn(`permission-rules: audit append failed: ${String(error)}`);
|
|
1431
1476
|
}
|
|
1432
1477
|
}
|
|
1478
|
+
/** Append one audit event through the session surface; the probe seam for host-capability detection. */
|
|
1479
|
+
appendAudit(agent, data) {
|
|
1480
|
+
return agent.session.append("permissionRules/decision", data, { ignorable: true });
|
|
1481
|
+
}
|
|
1482
|
+
/** The installed `@deepseek-ai/dsh-session` version, or `null` when unresolvable (falls back to the append probe). */
|
|
1483
|
+
peerVersion() {
|
|
1484
|
+
try {
|
|
1485
|
+
const pkg = createRequire(import.meta.url)("@deepseek-ai/dsh-session/package.json");
|
|
1486
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
1487
|
+
} catch {
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
/** One-time warning that session-log audit was disabled to keep session logs loadable. */
|
|
1492
|
+
warnUnmarkedAuditHost() {
|
|
1493
|
+
this.ctx.logger.warn("permission-rules: this host drops the ignorable marker on audit events (Session.append predates it), which would make sessions unresumable on stricter harness builds — session-log audit is disabled; set allowUnmarkedAudit: true to opt back in, and repair existing logs with scripts/repair-session-logs.mjs (see https://github.com/PerryLink/dsh-permission-rules/issues/2)");
|
|
1494
|
+
}
|
|
1433
1495
|
/**
|
|
1434
1496
|
* Execute the `/rules` command: bare `/rules` lists the active rules and
|
|
1435
1497
|
* their sources; `/rules reload` re-reads the chain; `/rules decisions
|
|
@@ -1615,16 +1677,17 @@ var PermissionRulesRuntime = class {
|
|
|
1615
1677
|
/** Render the session's `permissionRules/decision` audit trail, newest last. */
|
|
1616
1678
|
decisionsCommand(invocation, count, prose) {
|
|
1617
1679
|
const decisions = invocation.agent.session.events.filter((event) => event.type === "permissionRules/decision");
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1680
|
+
const lines = [];
|
|
1681
|
+
if (decisions.length === 0) lines.push(prose.noDecisions);
|
|
1682
|
+
else {
|
|
1683
|
+
const shown = decisions.slice(-count);
|
|
1684
|
+
lines.push(prose.decisionsHeader(shown.length, decisions.length));
|
|
1685
|
+
for (const event of shown) {
|
|
1686
|
+
const data = event.data;
|
|
1687
|
+
lines.push(prose.decisionLine(event.seq, data.action, data.toolName, data.ruleIndex, data.reason, data.dryRun === true, data.outcome));
|
|
1688
|
+
}
|
|
1627
1689
|
}
|
|
1690
|
+
if (this.auditSupport === "unsupported") lines.push(prose.auditDisabledNotice);
|
|
1628
1691
|
return {
|
|
1629
1692
|
kind: "success",
|
|
1630
1693
|
text: lines.join("\n")
|
|
@@ -1868,6 +1931,21 @@ function apply(ctx, config) {
|
|
|
1868
1931
|
});
|
|
1869
1932
|
}
|
|
1870
1933
|
/**
|
|
1934
|
+
* Whether a `@deepseek-ai/dsh-session` version line predates the
|
|
1935
|
+
* `ignorable` envelope-marker surface: the `0.1.0-rc.6` line and earlier
|
|
1936
|
+
* silently drop the marker from `Session.append` options, so audit events
|
|
1937
|
+
* written by those builds land unmarked and break resume on stricter
|
|
1938
|
+
* hosts. Non-matching (post-rc.6, stable, or unresolvable) versions are
|
|
1939
|
+
* treated as possibly-marker-aware and verified by the append probe.
|
|
1940
|
+
* @param version - the installed peer version string.
|
|
1941
|
+
* @returns true for the known-unmarked rc.1–rc.6 lines.
|
|
1942
|
+
*/
|
|
1943
|
+
function isUnmarkedHostVersion(version) {
|
|
1944
|
+
const match = /^0\.1\.0-rc\.(\d+)$/.exec(version.trim());
|
|
1945
|
+
if (match === null) return false;
|
|
1946
|
+
return Number(match[1]) <= 6;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1871
1949
|
* Identity candidates for the `agents` match dimension, derived from the
|
|
1872
1950
|
* caller agent's session header: `main` for top-level sessions, `subagent`
|
|
1873
1951
|
* for subagent children (`header.origin === 'subagent'`), and
|
|
@@ -1942,4 +2020,4 @@ function nextToken(text) {
|
|
|
1942
2020
|
const name = "permission-rules";
|
|
1943
2021
|
const inject = ["commands", "tools"];
|
|
1944
2022
|
//#endregion
|
|
1945
|
-
export { Config, GlobError, PATH_CANDIDATE_KEYS, PLATFORMS, PermissionRulesRuntime, RULE_ACTIONS, RuleError, apply, compileGlob, compilePatternRegex, compileRules, compileRulesChain, describeRule, extractPathCandidates, findUnreachableRules, inject, matchRules, name, normalizeWorkspacePath, parseRulesDocument, resolveConfig };
|
|
2023
|
+
export { Config, GlobError, PATH_CANDIDATE_KEYS, PLATFORMS, PermissionRulesRuntime, RULE_ACTIONS, RuleError, apply, compileGlob, compilePatternRegex, compileRules, compileRulesChain, describeRule, extractPathCandidates, findUnreachableRules, inject, isMarkedAuditEvent, isUnmarkedHostVersion, matchRules, name, normalizeWorkspacePath, parseRulesDocument, resolveConfig };
|
package/lib/types/config.d.ts
CHANGED
|
@@ -59,6 +59,17 @@ export interface Config {
|
|
|
59
59
|
* policy in production before enforcing it.
|
|
60
60
|
*/
|
|
61
61
|
enforce?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Whether to keep appending audit events to the session log on hosts
|
|
64
|
+
* whose `Session.append` predates the `ignorable` envelope marker
|
|
65
|
+
* (the `0.1.0-rc.6` line). Defaults to `false`: such hosts write the
|
|
66
|
+
* events UNMARKED, which makes the session unresumable on stricter
|
|
67
|
+
* hosts, so the runtime detects the host and disables session-log audit
|
|
68
|
+
* with a one-time warning instead. Set `true` to opt back into the
|
|
69
|
+
* in-session audit trail (and accept that those sessions may need
|
|
70
|
+
* `scripts/repair-session-logs.mjs` before loading on a newer harness).
|
|
71
|
+
*/
|
|
72
|
+
allowUnmarkedAudit?: boolean;
|
|
62
73
|
}
|
|
63
74
|
/** Config after {@link resolveConfig}: every optional field has its explicit default. */
|
|
64
75
|
export interface ResolvedConfig {
|
|
@@ -76,6 +87,7 @@ export interface ResolvedConfig {
|
|
|
76
87
|
readonly searchUp: boolean;
|
|
77
88
|
readonly maxGlobStars: number;
|
|
78
89
|
readonly enforce: boolean;
|
|
90
|
+
readonly allowUnmarkedAudit: boolean;
|
|
79
91
|
}
|
|
80
92
|
/** Schemastery schema: the loader validates and fills defaults before `apply`. */
|
|
81
93
|
export declare const Config: z<Config>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,wFAAwF;AACxF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,qBAAqB,CAAA;AAE1D,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,CAAA;AAE7C,sFAAsF;AACtF,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,wGAAwG;IACxG,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2GAA2G;IAC3G,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,uGAAuG;IACvG,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,sEAAsE;IACtE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,iEAAiE;IACjE,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,oGAAoG;IACpG,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,oGAAoG;IACpG,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+FAA+F;IAC/F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,wFAAwF;AACxF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,qBAAqB,CAAA;AAE1D,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,CAAA;AAE7C,sFAAsF;AACtF,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,wGAAwG;IACxG,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2GAA2G;IAC3G,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,uGAAuG;IACvG,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,sEAAsE;IACtE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,iEAAiE;IACjE,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,oGAAoG;IACpG,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,oGAAoG;IACpG,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+FAA+F;IAC/F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,yFAAyF;AACzF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAA;IAC1C,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAA;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAA;IACtC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAA;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAA;CACrC;AAED,kFAAkF;AAClF,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAgB3B,CAAA;AAEF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,MAAW,GAAG,cAAc,CAgDjE"}
|