pi-jev-guard 0.5.0 → 0.6.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 +79 -32
- package/config/jev-config.example.json +2 -1
- package/extensions/index.ts +52 -19
- package/package.json +1 -1
- package/src/automatic/overlay.ts +200 -64
- package/src/commands.ts +18 -11
- package/src/config.ts +21 -3
- package/src/verification-runner.ts +5 -5
- package/src/verification-tool.ts +1 -1
- package/src/verification.ts +378 -41
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Verifica le risposte con [TypeSafe Jev](https://docs.typesafe.ai) dentro [pi](ht
|
|
|
6
6
|
- **Domande tipate**: tool `jev_ask` per giudizi calibrati definiti dal modello (noul/choice/score) invece di prosa.
|
|
7
7
|
- **Automatica**: gemello guarded `<modello>__jev` dentro lo stesso provider, che trattiene la risposta, la verifica con Jev e pubblica solo l'output approvato (con rigenerazione privata su `block`). Stessa auth del provider: nessun login extra, funziona anche con Codex/OAuth.
|
|
8
8
|
- **Giudice output** (solo `automatic`, fail-open): dopo ogni `bash` cerca secret nel testo e classifica i fallimenti, allegando un consiglio al risultato.
|
|
9
|
-
- **Verifica progetto** (opt-in): footer deterministico da `edit`/`write` osservati e check
|
|
9
|
+
- **Verifica progetto** (opt-in): footer deterministico da `edit`/`write` osservati e check autorizzati da override locale, manifest globale utente o profilo comune integrato; il modello può eseguire soltanto quegli id, mai shell arbitraria.
|
|
10
10
|
|
|
11
11
|
## Installazione
|
|
12
12
|
|
|
@@ -183,54 +183,101 @@ grounding (`warned`/`repaired`/`held`).
|
|
|
183
183
|
|
|
184
184
|
### Verifica progetto deterministica (opt-in)
|
|
185
185
|
|
|
186
|
-
Il grounding è probabilistico; per **compilazione, lint e test**
|
|
187
|
-
prova deterministica. La verifica progetto
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
Il grounding è probabilistico; per **compilazione, lint e test** serve una
|
|
187
|
+
prova deterministica. La verifica progetto conserva, solo nella sessione,
|
|
188
|
+
`edit`/`write` riusciti e gli esiti osservati dei check. Il footer viene
|
|
189
|
+
generato dall'estensione, non dal modello: un test non eseguito non può
|
|
190
|
+
diventare “test passati”.
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
#### Nuovi progetti: zero manifest locale
|
|
193
|
+
|
|
194
|
+
Una volta, in una sessione col twin `automatic`, abilita la modalità scelta:
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
/jev verification repair
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Da quel momento ogni progetto usa questa precedenza:
|
|
201
|
+
|
|
202
|
+
1. `.pi/jev-verification.json` locale, se presente;
|
|
203
|
+
2. `verification.globalManifestPath`, se l'hai configurato nella config
|
|
204
|
+
utente;
|
|
205
|
+
3. profilo integrato `common` (default).
|
|
206
|
+
|
|
207
|
+
Il profilo `common` non legge comandi dal repository: abilita esclusivamente
|
|
208
|
+
comandi fissi dell'estensione quando trova segnali convenzionali:
|
|
209
|
+
|
|
210
|
+
| Rilevamento | Check disponibili |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `package.json` con script | `npm run typecheck`, `npm run lint`, uno tra `npm run test:unit`, `npm run test:ci`, `npm test` |
|
|
213
|
+
| `Cargo.toml` | `cargo check`, `cargo test` |
|
|
214
|
+
| `go.mod` | `go test ./...` |
|
|
215
|
+
| configurazione pytest | `python -m pytest` |
|
|
216
|
+
| repository Git | `git diff --check` |
|
|
217
|
+
|
|
218
|
+
Il profilo sceglie un solo ecosistema (Node → Rust → Go → Python) e aggiunge
|
|
219
|
+
il check Git quando applicabile. I comandi sono bounded da
|
|
220
|
+
`verification.maxCommands` (default `4`) e timeout. Se nessun segnale è
|
|
221
|
+
rilevato, non esegue nulla e il footer dichiara la limitazione.
|
|
222
|
+
|
|
223
|
+
Dopo un edit il modello può scegliere solo id già acquisiti:
|
|
224
|
+
|
|
225
|
+
```text
|
|
226
|
+
/jev verify recommended # check pertinenti non ancora freschi
|
|
227
|
+
/jev verify all # tutti, fino a verification.maxCommands
|
|
228
|
+
/jev verify node-typecheck # un id esplicito della policy corrente
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Il tool LLM `jev_verify` ha lo stesso vincolo, richiede un progetto trusted e
|
|
232
|
+
non accetta shell arbitraria. Footer/repair/hold operano nel twin
|
|
233
|
+
`automatic`; `/jev verify` è utilizzabile manualmente anche senza footer.
|
|
234
|
+
|
|
235
|
+
#### Override per progetti speciali
|
|
236
|
+
|
|
237
|
+
Per una pipeline specifica, aggiungi un manifest al progetto: prevale sempre
|
|
238
|
+
sul profilo globale.
|
|
193
239
|
|
|
194
240
|
```bash
|
|
195
241
|
mkdir -p .pi
|
|
196
242
|
cp config/jev-verification.example.json .pi/jev-verification.json
|
|
197
243
|
```
|
|
198
244
|
|
|
199
|
-
|
|
200
|
-
modifica manuale):
|
|
245
|
+
Controllalo tu, poi acquisiscilo (e ripeti dopo ogni modifica manuale):
|
|
201
246
|
|
|
202
247
|
```text
|
|
203
248
|
/jev verification reload
|
|
204
|
-
/jev verification warn # footer trasparente, nessuna rigenerazione
|
|
205
|
-
# oppure: /jev verification repair|hold
|
|
206
249
|
```
|
|
207
250
|
|
|
208
|
-
|
|
209
|
-
|
|
251
|
+
In alternativa puoi definire **una** policy comune per tutti i tuoi progetti
|
|
252
|
+
nella config utente, purché esista davvero e sia sotto il tuo controllo:
|
|
210
253
|
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
/jev
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"verification": {
|
|
257
|
+
"globalManifestPath": "~/.pi/agent/jev-verification.global.json",
|
|
258
|
+
"defaultProfile": "common"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
215
261
|
```
|
|
216
262
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
263
|
+
Un `globalManifestPath` configurato ma assente o malformato non degrada
|
|
264
|
+
silenziosamente al profilo integrato: la verifica segnala l'errore. Per
|
|
265
|
+
disattivare il fallback integrato imposta `"defaultProfile": "off"`.
|
|
266
|
+
`/jev verification` e `/jev status` mostrano l'origine effettiva della policy.
|
|
267
|
+
|
|
268
|
+
Manifest e profilo sono acquisiti a inizio sessione, all'attivazione esplicita
|
|
269
|
+
o con `/jev verification reload`. Se un manifest, un `package.json` o un file
|
|
270
|
+
di configurazione che abilita il profilo cambia dopo l'acquisizione, nessun
|
|
271
|
+
check parte fino al reload utente. In automatic, con tool policy e verifica
|
|
272
|
+
attive, `write`/`edit` del modello su tali file sono bloccati; una modifica via shell
|
|
273
|
+
viene comunque rilevata dal fingerprint. Questo evita che il modello trasformi
|
|
274
|
+
uno script o un manifest in un nuovo comando autorizzato.
|
|
226
275
|
|
|
227
276
|
Modalità: `warn` aggiunge il footer; `repair` rigenera una volta con feedback
|
|
228
|
-
che chiede
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
limitata a `edit`/`write` osservati: una modifica fatta con shell non viene
|
|
233
|
-
attribuita automaticamente al modello.
|
|
277
|
+
che chiede `jev_verify`; `hold` trattiene dopo una failure concreta, o dopo il
|
|
278
|
+
tentativo bounded se restano check mancanti. Nessun check pertinente non è un
|
|
279
|
+
fallimento del codice e non produce hold. Le modifiche rilevate restano solo
|
|
280
|
+
quelle di `edit`/`write`: una modifica shell non viene attribuita al modello.
|
|
234
281
|
|
|
235
282
|
## Configurazione
|
|
236
283
|
|
|
@@ -69,10 +69,11 @@
|
|
|
69
69
|
"enabled": false,
|
|
70
70
|
"mode": "warn",
|
|
71
71
|
"manifestPath": ".pi/jev-verification.json",
|
|
72
|
+
"defaultProfile": "common",
|
|
72
73
|
"maxRepairs": 1,
|
|
73
74
|
"commandTimeoutMs": 120000,
|
|
74
75
|
"outputChars": 6000,
|
|
75
|
-
"maxCommands":
|
|
76
|
+
"maxCommands": 4
|
|
76
77
|
},
|
|
77
78
|
"exfilCheck": {
|
|
78
79
|
"enabled": true,
|
package/extensions/index.ts
CHANGED
|
@@ -68,16 +68,16 @@ import type { PostPublishCheckFn, PostPublishCheckResult } from "../src/automati
|
|
|
68
68
|
import {
|
|
69
69
|
buildVerificationStatus,
|
|
70
70
|
createVerificationState,
|
|
71
|
-
|
|
71
|
+
isVerificationPolicyPath,
|
|
72
72
|
decideVerificationAction,
|
|
73
|
-
|
|
73
|
+
loadVerificationPolicy,
|
|
74
74
|
manifestCheckForCommand,
|
|
75
75
|
normalizeObservedPath,
|
|
76
76
|
recordObservedModification,
|
|
77
77
|
recordVerificationRun,
|
|
78
78
|
selectVerificationChecks,
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
trustVerificationPolicy,
|
|
80
|
+
trustedVerificationPolicy,
|
|
81
81
|
type VerificationManifestTrust,
|
|
82
82
|
type VerificationStatus,
|
|
83
83
|
} from "../src/verification.ts";
|
|
@@ -225,18 +225,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
225
225
|
|
|
226
226
|
function captureVerificationManifest(cwd: string): VerificationManifestTrust {
|
|
227
227
|
verificationCwd = cwd;
|
|
228
|
-
verificationManifestTrust =
|
|
228
|
+
verificationManifestTrust = trustVerificationPolicy(cwd, config.verification);
|
|
229
229
|
return verificationManifestTrust;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
function verificationSnapshot(cwd: string): {
|
|
233
|
-
loaded: ReturnType<typeof
|
|
233
|
+
loaded: ReturnType<typeof loadVerificationPolicy>;
|
|
234
234
|
status: VerificationStatus;
|
|
235
235
|
} {
|
|
236
|
-
const loaded =
|
|
236
|
+
const loaded = trustedVerificationPolicy(
|
|
237
237
|
verificationManifestTrust,
|
|
238
238
|
cwd,
|
|
239
|
-
config.verification
|
|
239
|
+
config.verification,
|
|
240
240
|
);
|
|
241
241
|
return { loaded, status: buildVerificationStatus(verificationState, loaded) };
|
|
242
242
|
}
|
|
@@ -269,8 +269,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
|
-
* Esegue SOLO check estratti
|
|
273
|
-
*
|
|
272
|
+
* Esegue SOLO check estratti dalla policy acquisita (locale, globale o
|
|
273
|
+
* profilo integrato). Serve sia al tool del modello sia a /jev verify.
|
|
274
274
|
*/
|
|
275
275
|
async function runProjectVerification(args: {
|
|
276
276
|
cwd: string;
|
|
@@ -405,7 +405,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
405
405
|
refs: TwinRef[],
|
|
406
406
|
registry: ModelRegistry,
|
|
407
407
|
persist: boolean,
|
|
408
|
-
): { ok: boolean; error?: string; twins?: string[] } {
|
|
408
|
+
): { ok: boolean; error?: string; stack?: string; twins?: string[] } {
|
|
409
409
|
const byProvider = new Map<string, string[]>();
|
|
410
410
|
for (const ref of refs) {
|
|
411
411
|
const list = byProvider.get(ref.provider) ?? [];
|
|
@@ -443,6 +443,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
443
443
|
return {
|
|
444
444
|
ok: false,
|
|
445
445
|
error: error instanceof Error ? error.message : String(error),
|
|
446
|
+
...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
|
|
446
447
|
};
|
|
447
448
|
}
|
|
448
449
|
overlays.set(providerId, { twinIds: [...twinIds], live });
|
|
@@ -466,6 +467,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
466
467
|
return {
|
|
467
468
|
ok: false,
|
|
468
469
|
error: error instanceof Error ? error.message : String(error),
|
|
470
|
+
...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
|
|
469
471
|
};
|
|
470
472
|
}
|
|
471
473
|
}
|
|
@@ -483,7 +485,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
483
485
|
);
|
|
484
486
|
refs.push({ provider: providerId, model: modelId });
|
|
485
487
|
const res = applyTwins(refs, registry, true);
|
|
486
|
-
if (!res.ok) return { ok: false, error: res.error };
|
|
488
|
+
if (!res.ok) return { ok: false, error: res.error, ...(res.stack ? { stack: res.stack } : {}) };
|
|
487
489
|
return { ok: true, twin: `${providerId}/${twinIdFor(modelId)}` };
|
|
488
490
|
}
|
|
489
491
|
|
|
@@ -616,7 +618,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
616
618
|
enabled: config.verification.enabled,
|
|
617
619
|
mode: config.verification.mode,
|
|
618
620
|
manifestPath: config.verification.manifestPath,
|
|
619
|
-
|
|
621
|
+
defaultProfile: config.verification.defaultProfile,
|
|
622
|
+
...(loaded.ok
|
|
623
|
+
? { manifestLoaded: true, manifestSource: loaded.source, manifestSourceLabel: loaded.sourceLabel }
|
|
624
|
+
: { manifestLoaded: false, manifestError: loaded.error }),
|
|
620
625
|
changedFiles: status.changedFiles,
|
|
621
626
|
checks: status.checks.map((item) => ({ id: item.check.id, state: item.state })),
|
|
622
627
|
};
|
|
@@ -630,12 +635,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
630
635
|
next.verification.mode = mode;
|
|
631
636
|
}
|
|
632
637
|
saveConfig(next);
|
|
633
|
-
|
|
638
|
+
const refreshed = refresh();
|
|
639
|
+
// L'attivazione è un'azione utente esplicita: acquisisce gli input
|
|
640
|
+
// correnti del fallback invece di riusare una snapshot presa quando era off.
|
|
641
|
+
if (mode !== "off" && verificationCwd) captureVerificationManifest(verificationCwd);
|
|
642
|
+
return refreshed;
|
|
634
643
|
},
|
|
635
644
|
reloadVerificationManifest: (cwd) => {
|
|
636
645
|
const trusted = captureVerificationManifest(cwd);
|
|
637
646
|
return trusted.ok
|
|
638
|
-
? { ok: true, path: trusted.path }
|
|
647
|
+
? { ok: true, path: trusted.path, sourceLabel: trusted.sourceLabel }
|
|
639
648
|
: { ok: false, error: trusted.error };
|
|
640
649
|
},
|
|
641
650
|
runVerification: ({ cwd, selection, signal, trusted }) =>
|
|
@@ -702,6 +711,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
702
711
|
if (overlays.size === 0) {
|
|
703
712
|
const restored = refreshUpstream(ctx.modelRegistry);
|
|
704
713
|
if (!restored.ok) {
|
|
714
|
+
try {
|
|
715
|
+
pi.appendEntry("jev-refresh-upstream-error", {
|
|
716
|
+
error: restored.error,
|
|
717
|
+
...(restored.stack ? { stack: String(restored.stack).slice(0, 4000) } : {}),
|
|
718
|
+
});
|
|
719
|
+
} catch {
|
|
720
|
+
// solo diagnostica
|
|
721
|
+
}
|
|
705
722
|
if (ctx.hasUI) {
|
|
706
723
|
ctx.ui.notify(`Jev automatic senza guarded: ${restored.error} (diagnostica sola).`, "warning");
|
|
707
724
|
}
|
|
@@ -982,18 +999,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
982
999
|
|
|
983
1000
|
if (event.toolName === "write" || event.toolName === "edit") {
|
|
984
1001
|
const input = event.input as { path?: unknown };
|
|
985
|
-
|
|
1002
|
+
const profileInput = verificationManifestTrust?.ok && verificationManifestTrust.source === "global-profile";
|
|
1003
|
+
if (
|
|
1004
|
+
isVerificationPolicyPath(input.path, ctx.cwd, verificationManifestTrust) &&
|
|
1005
|
+
// Il profilo comune è opt-in: non rendere package.json immutabile
|
|
1006
|
+
// nelle installazioni che non hanno attivato la verifica progetto.
|
|
1007
|
+
(!profileInput || config.verification.enabled)
|
|
1008
|
+
) {
|
|
1009
|
+
const ruleId = profileInput
|
|
1010
|
+
? "verification-profile-input-immutable"
|
|
1011
|
+
: "verification-manifest-immutable";
|
|
986
1012
|
pi.appendEntry("jev-tool-block", {
|
|
987
1013
|
tool: event.toolName,
|
|
988
|
-
ruleId
|
|
1014
|
+
ruleId,
|
|
989
1015
|
revision: TOOL_POLICY_REVISION,
|
|
990
1016
|
});
|
|
991
1017
|
if (ctx.hasUI) {
|
|
992
|
-
ctx.ui.notify(
|
|
1018
|
+
ctx.ui.notify(
|
|
1019
|
+
profileInput
|
|
1020
|
+
? "Jev tool policy: il modello non può modificare un file che decide i check del profilo globale acquisito."
|
|
1021
|
+
: "Jev tool policy: il modello non può modificare il manifest che autorizza i suoi check.",
|
|
1022
|
+
"warning",
|
|
1023
|
+
);
|
|
993
1024
|
}
|
|
994
1025
|
return {
|
|
995
1026
|
block: true,
|
|
996
|
-
reason:
|
|
1027
|
+
reason: profileInput
|
|
1028
|
+
? "Bloccato da jev tool policy (verification-profile-input-immutable): modifica manualmente la configurazione del progetto e poi usa /jev verification reload."
|
|
1029
|
+
: "Bloccato da jev tool policy (verification-manifest-immutable): modifica il manifest manualmente e poi usa /jev verification reload.",
|
|
997
1030
|
};
|
|
998
1031
|
}
|
|
999
1032
|
const verdict = checkFilePath(input.path);
|
package/package.json
CHANGED