vetto 0.2.22

Daemon-less sandbox + security layer for AI coding agents (Landlock/Seatbelt, TUI statusline, post-session audit reports)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# Prompt 09 — Structured Audit + Session Forensics (architecture proposal)

Ветка: `arch/prompt-09`. Production-код не писался. Скоуп: `docs/` (этот файл),
анализ `src/audit/`, `src/report/`, `src/events/`, `src/history.rs`.
`src/verify_ng/` не трогался (чужой скоуп Prompt 01).

## 1. Current audit architecture (что есть в репо)

Источники истины сегодня:

- `src/events/types.rs``Event` enum, 12 вариантов: `SessionStarted`,
  `FileObserved{pid,comm,path,access}`, `ExecObserved{pid,argv}`,
  `BlockedAttempt{pid,comm,path,source}`, `NetRequest{host,port,allowed}`,
  `DnsResolved`, `NetEgress{host,ip,port,bytes}`, `NetQuotaExceeded`,
  `SecretMasked{path}`, `Notice{message}`, `SessionTimeout`, `SessionEnded`.
  Сериализация JSONL через `#[serde(tag="event")]`. Честная оговорка уже в коде:
  `FileObserved` — best-effort /proc-poller, пропускает sub-100ms opens;
  `BlockedAttempt` — только при опциональном канале (`--observe-seccomp` /
  kernel audit feed). Enforcement от событий НЕ зависит.
- `src/events/bus.rs``tokio::broadcast` 4096, `publish` синхронный,
  fail-open при отсутствии получателей; `Lagged` в потребителях молча
  пропускается (`continue`) — дыра для forensics (см. §20).
- Потребители шины (`src/main.rs:1024-1056`): `JsonlSink` (дефолт
  `~/.vetto/logs/session-<pid>.jsonl` + опциональный `--jsonl`),
  `OsLogSink`, `StatsCollector`, OTEL-subscriber, `DesktopNotifier`.
  Первым паблишится `SessionStarted{pid,tier,net_mode,profile}`
  (`src/main.rs:1057`), затем `SecretMasked` per `deny_resolved` (только Tier FULL)
  или `Notice` о деградации (SECCOMP / fs-only).
- `src/report/stats.rs` — in-memory агрегатор: counts, op_counts, blocked
  (cap 4096 distinct), suspicious через `classifier`, net/dns/egress (cap 500),
  notices (cap 100). `SessionStats` — то, что рендерится в отчёты.
- `src/report/json|html|md|sarif.rs + storage.rs + mod.rs` — отчёты
  `.vetto/reports/vetto-report-<ts>-<pid>-<id>.<ext>`, `O_CREAT|O_EXCL|O_NOFOLLOW`,
  `0600`, symlink/hardlink/fifo отказы; весь JSON прогоняется через
  `sanitizer::sanitize_line` (best-effort). `compare_reports` — CI-дифф двух JSON.
- `src/audit/history.rs``AuditRecord` (ts, session_id, agent, command?,
  profile, policy_path?, exit_code, duration, tier, net_mode, blocked_count,
  events_total, report_path?, log_path?) в `~/.vetto/history.jsonl` через
  `record_session_history` (`src/main.rs:1484`); `inspect_session` /
  `inspect_latest_session` ре-конструируют `SessionAuditDetail` парсингом JSONL
  или JSON-отчёта; `vetto audit` — list/filter/since/agent/query + `--recap`;
  `render_session_audit` — 5 секций (fs-denials, net, syscalls, suspicious, recs).
- `src/history.rs` (`.vetto/history`, durations) — только для `--timeout auto`
  (p95×2, floor 300s). К forensics отношения не имеет, не смешивать.
- `src/events/tail.rs` (`vetto events --filter deny|net|files|exec|notice`),
  `src/events/replay.rs` (`vetto replay --speed`), `src/audit/recap.rs`
  (3–5 строк exit-recap, чистые сессии молчат), `src/audit/digest.rs` (daily).
- `docs/schema/session-stats.schema.json` — JSON-контракт отчёта.
- `docs/telemetry.md` — OTEL/opt-in, агрегированные счётчики, не forensics.
- `src/verify_ng/` (Prompt 01): `evidence.rs` (HostFact/Constrained/SelfReport,
  PASS только с host fact), `model.rs` (Verdict×ClaimStrength), `caps.rs`
  (probe-доказательства для NOT_APPLICABLE), `redact.rs` (MAX_DETAIL 2000,
  control-bytes strip, truncation) — переиспользовать как образец, не дублировать.

## 2. Threat model (для чего audit нужен)

Полезен при: incident, policy violation, unexpected egress, forbidden FS attempt,
child escape attempt, secret access attempt, backend failure, degraded fallback,
cleanup failure, споре «что реально было применено». Противник: (a) скомпрометированный/
любопытный агент внутри песочницы (шум, секреты в argv/stdout, fork-бомбы, ключи
в путях, попытки переполнить bus/stats); (b) post-hoc атакующий с доступом к хосту
(правка history.jsonl/отчётов — integrity §9); (c) невнимательный оператор
(шеринг отчёта с секретами — privacy §10). Audit — детекция и доказательство
post-hoc, НЕ enforcement: лог не останавливает атаку, он фиксирует её след.

## 3. Event taxonomy — сопоставление с prompt (Strong/Partial/Unsupported)

Шкала: Strong — событие уже публикуется из доверенного хоста;
Partial — публикуется, но best-effort/опционально/агрегировано с потерями;
Unsupported — нет вообще.

| Prompt event | Статус | Факт в репо |
|---|---|---|
| session created | Strong | `SessionStarted{pid,tier,net_mode,profile}`, `main.rs:1057` |
| policy requested / normalized | Unsupported | нет событий; requested vs effective неразличимы |
| backend selected | Partial | только строки `tier`/`net_mode` внутри `SessionStarted`; `Backend::describe()` никуда не пишется |
| policy compiled / enforcement constructed | Unsupported | `Backend::spawn` молчит; Landlock ABI/ruleset, seccomp-профиль, mount-overlay список не фиксируются (кроме `SecretMasked` per-path) |
| pre-launch verification | Partial | `Notice` о tier-деградации; `verify_status` в recap всегда `"off"` post-hoc; связи с `vetto verify` нет |
| process started / child started | Partial | `ExecObserved{pid,argv}` без ppid/pgid/tree; /proc-поллер best-effort |
| filesystem denial | Partial | `BlockedAttempt` только при опциональном канале; без него — тишина, неотличимая от «чисто» |
| filesystem allow | Partial | `FileObserved` best-effort, sub-100ms пропуски |
| network denial / allow | Strong (broker), Partial (прочее) | `NetRequest{allowed}` из `net_relay.rs`, `DnsResolved`, `NetEgress`, `NetQuotaExceeded` — Strong там, где трафик идёт через брокер; прямой egress вне брокера не виден |
| secret access denial | Partial | `SecretMasked` = факт маскирования (FULL), не попытка доступа; попытка видна только если дошла до `BlockedAttempt` |
| capability downgrade | Partial | `Notice` свободной формы, без структуры ` Capability{name,present,evidence}` (образец — `verify_ng/caps.rs`) |
| verification result | Unsupported | нет события; `SessionStats` не несёт verdict/strength |
| process termination | Partial | `SessionEnded{exit_code,duration}`; сигнал vs exit смешаны (`decode_status` в handle.rs, отрицательные коды) |
| cleanup verification | Unsupported | `sweep_reparented` (proctrack.rs) и `terminate()` ничего не паблишат: число убитых/уцелевших орфанов, бюджет, timeout — теряются |
| backend error | Partial | только `Notice{message}` свободной формы |
| session completed | Strong | `SessionEnded` + `record_session_history` |

Вывод: ядро deny/allow-сетки есть, но нет policy-snapshot, backend-plan,
process-tree, cleanup и verification событий. Главная дыра — отсутствие
негативных доказательств: «чистый» лог неотличим от «наблюдение было выключено».

## 4. Event schema (предлагаемый контракт)

Обёртка поверх существующего `Event` (расширение, не ломка JSONL):

```json
{"v":1,"event_id":"uuid7","seq":1234,"ts":"...","session_id":"session-<pid>",
 "proc":{"pid":1,"ppid":0,"pgid":0,"comm":"..."},"type":"...","policy_hash":"sha256:…",
 "backend":"linux:full","severity":"info|warning|high","decision":"allow|deny|n/a",
 "corr":"…","prev_hash":"…","meta":{…}}
```

- `event_id` uuid7 (сортируемый), `seq` — монотонный счётчик sink-а на хосте;
  разрыв seq = доказательство потерь (`Lagged`), пишется маркером
  `sink-lagged{missed}` (уже есть в `jsonl.rs:62`, но парсеры его игнорируют —
  чинить, не удалять).
- `session_id` обязателен везде (сейчас выводится из pid/имени файла —
  хрупко, коллизии при reuse pid).
- `policy_hash` — sha256 канонического effective-policy (см. §6), одинаковый
  во всех событиях сессии; `requested_hash` отдельно в snapshot-событии.
- `decision` + `severity`: deny без severity=high запрещён схемой.
- `meta` — типизированный per-type, секреты запрещены схемой (см. §10).
- `prev_hash` — hash-chain (см. §9).
- Fail-closed правило схемы: событие `*_denial` без `policy_hash+backend+decision`
  считается malformed и помечается `Notice{m alformed}` — не молча глотается.

## 5. Session schema

Одна запись `session completed` должна содержать: session_id, started/ended_at,
duration, exit (код + `signaled:bool`), requested/effective policy_hashes,
backend{platform,tier,abi/ruleset-summary}, capability set, policy snapshot
ссылку, счётчики (events_total, lost_events, denials, egress allow/deny),
cleanup report, verification block, integrity footer (seq_max, chain_head),
`vetto_version`+build. Сегодня это размазано по трём местам
(JSONL-лог + JSON-отчёт + `AuditRecord`); предложение: JSON-отчёт = session record,
JSONL = event stream, `history.jsonl` = индекс (указатели, не копия).

## 6. Policy snapshot model

Persisted (в отчёт + первым событием `policy.snapshot` после `SessionStarted`):
requested policy (источники layers + их `PolicySourceKind`, CLI-overrides как факт),
effective policy (allow_write/read, deny_resolved, secret_proxies как *паттерны*,
не значения; net allowlist; seccomp-профиль; limits), `Backend::describe()` строкой
+ структурой probe (landlock_abi, userns, full_tier, seccomp_notify, audit_feed),
capability report (`verify_ng::caps::CapabilitySet` — переиспользовать тип),
security level (tier label + `SeccompProfile`), `policy_hash` (sha256 по каноническому
JSON effective), `vetto_version` (`CARGO_PKG_VERSION`, уже пробрасывается в
`VETTO_VERSION` env, `main.rs:836`). In-memory only: raw secret values, host env
значения, полные argv с потенциальными секретами (в snapshot — только `argv0`+
redacted rest). `policy.show --effective` (`policy/explain.rs`) — тот же загрузчик,
что и сессия: использовать его как конструктор snapshot, чтобы requested vs
effective не расходились.

## 7. Process tree model

Сегодня `ExecObserved{pid,argv}` без ppid — дерево невосстановимо.
Предложение (Linux-first, честные оговорки): расширить до
`{pid,ppid,pgid,argv0,argv_redacted,comm}`; источник — /proc-скан в момент exec
(ppid/pgid из `/proc/<pid>/stat`, best-effort, помечать `evidence_tier:
constrained` по шкале `verify_ng/evidence.rs`); FULL-tier дополнительно —
post-mortem сверка через PID-namespace init (host fact). FS-ONLY — только
poll + `sweep_reparented` отчёт. Реконструкция `reconstruct process tree` —
чистая функция над событиями (pid→ppid рёбра), несвязанные pid — `orphan:true`,
не додумывать. macOS: kqueue/fsevents только advisory (Unsupported для дерева);
Windows: Job Object даёт kill-tree, но не exec-tree без ETW (Partial — ETW
опционален, `sandbox/windows/etw.rs`); VM: hypervisor-events out of scope,
только guest-agent маркеры как SelfReport.

## 8. Evidence model (что доказывает, что нет)

Три тира из `verify_ng/evidence.rs` распространить на audit: HostFact
(post-mortem stat, wait status, sweep result, canary, Landlock-apply success —
только это поддерживает сильные утверждения), Constrained (errno-класс + nonce
через seccomp-notify, broker verdicts), SelfReport (stdout-маркеры, argv —
только хинты). Audit trail доказывает: какая политика запрошена/применена
(snapshot+hash), какой backend/tier работал, какие denials наблюдались,
как завершилась сессия и уборка. НЕ доказывает: отсутствие необнаруженных
доступов (наблюдение best-effort), непротиворечивость ядра, отсутствие
kernel-эксплойта. Формулировка в отчёте обязана разделять
«enforced (host fact)» vs «observed (constrained)» vs «no signal (not proof)».

## 9. Integrity / tamper model (без buzzword-криптографии)

Угроза: пост-сессионная правка `~/.vetto/history.jsonl`, JSONL-логов, отчётов
владельцем хоста или малварью с правами пользователя. Честная матрица:

- append-only + `O_APPEND|O_NOFOLLOW|O_EXCL` + fstat-валидация — уже есть
  (`logger/jsonl.rs`, `report/mod.rs`, `report/storage.rs`). Защищает от symlink/
  fifo/hardlink-подмен в момент записи. НЕ защищает от последующей правки файла.
- `seq` + `sink-lagged` маркеры — детект потерь в пределах одного файла.
  НЕ защищает от удаления суффикса.
- hash-chain (`prev_hash`, sha256, verify в `vetto audit --verify-chain`) —
  детект вставки/перестановки/правки середины; НЕ защищает от truncation
  (нужен out-of-band head: seq_max+chain_head в `history.jsonl` + в отчёт) и
  НЕ защищает от полной перезаписи атакующим, контролирующим хост целиком.
- Ed25519-подпись отчёта (`policy/crypto.rs` уже умеет sign/verify для policy —
  переиспользовать ключ `~/.vetto/signing.key`) — доказывает авторство хоста
  на момент подписи третьему лицу; НЕ доказывает правдивость содержимого
  (подписывается то, что собрал тот же хост) и бесполезна, если ключ на том же
  хосте скомпрометирован. Поэтому: подпись — опциональная (`--sign-reports`),
  по умолчанию выкл; chain — по умолчанию вкл (дешёвый, без ключей).
- trusted host writer: writer = хост-процесс vetto (не песочница) — уже так;
  зафиксировать инвариант: события изнутри песочницы никогда не принимаются
  напрямую, только host-наблюдения (broker, notify, post-mortem).
- `verify_ng` остаётся единственным security verifier; audit — свидетель,
  не судья (требование prompt §Verification coupling).

## 10. Secret redaction model (как не сделать forensics каналом утечки)

Текущее состояние: три разных редктора — `logger/sanitizer.rs` (JSONL+reports,
BEST-EFFORT, честно документирован), `pty/redact.rs` + `pty/ansi.rs`
(Aho-Corasick, live stdout), `verify_ng/redact.rs` (MAX_DETAIL+control-strip+
truncate). Дыры (adversarial): `logger/system_log.rs` и `logger/oslog.rs`
форматируют события БЕЗ санитайзера (`system_log.rs:44-57`,
`oslog.rs:27-43`, `_ => format!("{:?}", ev)` — полный Debug-дамп, включая
`ExecObserved.argv` целиком); `audit/history.rs` хранит `command` как
`argv.join(" ")` без редакции и ищет по нему (`filter_records`); TUI
`format_event_row`/`describe` показывают полные пути/argv; `NetEgress.ip`
и `DnsResolved.ips` — потенциальный exfil-сигнал через DNS в логах — хранить
агрегированно (counts per domain), не каждый IP.

Предложение — классификация полей, не «ещё один regex»:

- `public`: tier, backend, counts, exit_code, duration, hashes — без редакции.
- `operational`: пути deny, host allowlist-домены, comm — санитайзер + cap длины
  (borrow `MAX_DETAIL` 2000 + truncate-маркер из `verify_ng/redact.rs`).
- `sensitive`: argv (кроме argv0), env, Notice-тексты, stdout-производные —
  redact-by-default: хранить `argv0` + `argc` + `argv_hash`, полный текст только
  с `--include-argv` и явным `Notice{argv_redacted:true}`; ключи/значения —
  только имена ключей, значения `[REDACTED]`.
- `forbidden`: raw credentials, PEM-body, bearer-значения, полные env-дампы —
  запрещены схемой; продюсер обязан не эмитить (fail-closed: sink дропает
  событие с forbidden-паттерном и пишет `Notice{redaction_drop}` вместо тишины).
- Единый `redact_text` на выходе заимствовать из `verify_ng/redact.rs`
  (control-strip + cap), токенные паттерны — из `logger/sanitizer.rs`;
  journald/oslog/eventlog-sink прогнать через тот же pipeline (сейчас голые).
- Escape: все рендеры (TUI/replay/report-html) обязаны escape control-bytes
  и ANSI (TUI уже частично через `AnsiRedactor`; replay `describe_replay_event`
  печатает `argv.join` и `path` голыми — чинить).

## 11. Storage format (JSONL vs SQLite vs CBOR)

- JSON Lines — текущий, оставить как primary event stream: append-friendly,
  grep-able, CI-простой (`jq`), работает с O_APPEND+NOFOLLOW, переживает
  падение mid-session (каждая строка самодостаточна). Минусы: нет индекса,
  повторный парсинг для `audit <id>` O(n). Приемлемо для CLI-объёмов.
- SQLite — отклонить для event stream: WAL+fsync-сложности при SIGKILL-уборке,
  lock-контеншн с sink-thread, риск «база бита — всё потеряно», тяжелее
  `O_NOFOLLOW`-инварианты; рассмотреть позже только как опциональный индекс
  (`vetto audit --index`) поверх JSONL, не как источник истины.
- CBOR/binary — отклонить: нечитаемо в CI, не grep-абельно, ноль выигрыша
  при текущих объёмах (busy-сессия ~10^4 событий).
- Решение: JSONL (events) + JSON (session report, схема
  `docs/schema/session-stats.schema.json` расширена §4-5) + JSONL
  (`history.jsonl` как индекс с chain_head). Миграция — аддитивная,
  старые файлы читаются (версионирование `"v":1`, неизвестные поля игнорить).

## 12. CLI / reporting (предлагаемые запросы поверх существующего)

Существует: `vetto audit [--latest|<id>|--since|--agent|--limit|query|--json|--recap]`,
`vetto events --filter deny|net|files|exec|notice [--follow|--json]`,
`vetto replay [--speed]`, `compare_reports`, recap, digest. Добавить (новые
флаги/сабкоманды, без ломки старых): `--filter secret` (SecretMasked +
secret-classified denials), `--filter degraded` (Notices о tier-fallback +
capability.absent), `--filter cleanup` (sweep/orphan события),
`vetto audit --verify-chain <id>` (пересчёт hash-chain + сверка head),
`why-denied <path>` (= `policy explain` + релевантные BlockedAttempt из лога),
`reconstruct --tree` (дерево из §7), `reconstruct --policy` (snapshot+hash),
`--include-argv` (opt-in, с redaction-маркером). Все новые выводы — через
единый санитайзер; `--json` остаётся машиночитаемым контрактом.

## 13. TUI implications

`src/tui/app.rs` (LiveEventAggregator, ring, фильтры), `full.rs` (PTY +
AnsiRedactor), `statusline.rs` (live redact) — уже фильтруют
deny/net/files/exec/notices. Добавить: бейдж integrity (`seq gaps: N`,
`chain: ok/broken`), бейдж наблюдения (`observation: full/partial/off` —
устраняет «тишина = чисто»), фильтр `degraded`/`cleanup`, redacted-рендер
argv (только argv0 по умолчанию). TUI — потребитель той же схемы §4,
никакой отдельной семантики событий.

## 14. Linux events (evidence)

FULL: Landlock-apply (ruleset summary, ABI — host fact), mount-overlay per
deny_resolved (уже `SecretMasked`), netns/broker verdicts (уже), seccomp-notify
BlockedAttempt (уже, опционально), PIDns teardown + sweep count (НОВОЕ —
cleanup event), audit-feed denials (уже через `audit_reader.rs`, опционально).
FS-ONLY: sub-reaper + `sweep_reparented` отчёт (НОВОЕ, иначе escape-attempt
невидим); честный `Notice` о carve-out вместо overlay (уже). SECCOMP-only:
только syscall/net сигналы + громкий `Notice` (уже); FS-события помечать
`observation: off`, не «0 denials».

## 15. Windows events

Источники есть (`etw.rs`, `eventlog.rs`, `job_object.rs`, `firewall.rs`,
`restricted_token.rs`, `appcontainer.rs`, `windows_sandbox.rs`,
`minifilter.rs`), но в `Event` не проброшены. Предложение: маппить в ту же
схему §4: Job Object kill-on-close → cleanup event (Strong — kernel-объект);
WFP verdicts → NetRequest (Partial — только при включённом провайдере);
AppContainer/restricted-token construction → enforcement-constructed
(Strong, host fact); minifilter — только при наличии сервиса + ImagePath-match
(иначе `capability.absent`, fail-closed, не silent); ETW — Constrained,
опционально. Windows Sandbox (VM-путь) — см. §16.

## 16. macOS / VM events

macOS: Seatbelt-profile apply факт (Strong, host), fsevents (`fsevents.rs`) —
advisory only (Unsupported для enforcement-доказательств), net_proxy verdicts
→ NetRequest (Partial), `oslog` sink — прогнать через санитайзер (§10).
VM (`--backend win-sandbox`, uniform VM path): hypervisor-guarantee вне зоны
доказательств vetto — только guest-agent SelfReport-маркеры + host-side
launch/teardown факты (VM started/stopped, image hash); e2e-изоляцию audit
НЕ заявляет (Unsupported, честно).

## 17. Verification integration (`vetto verify` + `verify_ng`)

Не дублировать: переиспользовать `verify_ng::{evidence::EvidenceTier,
caps::CapabilitySet, redact::{redact_text,MAX_DETAIL}, model::{Verdict,
ClaimStrength}}` как общие типы (вынести в `src/evidence/` при реализации —
этот переезд тоже часть плана). Audit-события получают поле
`evidence_tier`; session report получает `verification:{verdict, strength,
registry_hash}` блок, заполняемый ТОЛЬКО `verify_ng`-хarness-ом, никогда
self-reported сессией. Инвариант: audit без host facts не может поднять
strength выше Partial; `verify` читает audit как вход (что наблюдалось),
а вердикт строит своими пробами.

## 18. Repo / module changes (только файлы, без кода)

- `src/events/types.rs` — envelope §4 (event_id/seq/session/proc/policy_hash/
  backend/severity/decision/corr/prev_hash), новые варианты:
  `PolicySnapshot`, `BackendSelected`, `EnforcementConstructed`,
  `PrelaunchCheck`, `ChildStarted{ppid,pgid}`, `CapabilityDowngrade`,
  `VerificationResult`, `CleanupReport`, `BackendError`; `ExecObserved` +
  ppid/pgid/argv_redacted. Миграция аддитивная (`"v"` + `#[serde(default)]`).
- `src/events/bus.rs` — счётчик seq на publish-стороне; `Lagged` превратить
  в явное событие, не `continue`.
- `src/logger/jsonl.rs` — простановка seq/prev_hash/session_id в sink,
  маркеры потерь парсабельны; forbidden-паттерн → `redaction_drop`, не тишина.
- `src/logger/system_log.rs`, `src/logger/oslog.rs` — пропустить через общий
  санитайзер (сейчас голый формат — exfil/secret-дыра).
- `src/policy/explain.rs` — конструктор policy snapshot + `policy_hash`
  (sha256 канонического JSON); `why-denied` reuse.
- `src/sandbox/mod.rs``Backend::describe()` структурно + publish
  `BackendSelected`; fail-closed при unknown backend уже есть — сохранить.
- `src/sandbox/linux/mod.rs` + `landlock.rs` + `mounts.rs` — publish
  `EnforcementConstructed` (ABI/ruleset/mask-count); `proctrack.rs` +
  `handle.rs` — publish `CleanupReport` (killed/reaped/survivors/budget).
- `src/sandbox/linux/net_relay.rs`, `observe_seccomp.rs`, `audit_reader.rs`  добавить policy_hash/corr/backend к существующим publish (контекст сессии).
- `src/sandbox/windows/*`, `src/sandbox/macos/*` — маппинг §15-16 в общий `Event`.
- `src/report/stats.rs` — агрегация новых счётчиков (lost_events, cleanup,
  degraded) + caps preservation; сохранить caps 4096/500/100.
- `src/report/json.rs` + `docs/schema/session-stats.schema.json` — session
  schema §5 (snapshot-ссылка, hashes, backend, cleanup, verification,
  integrity footer); html/md/sarif — те же поля, escape control/ANSI.
- `src/audit/history.rs``AuditRecord` + chain_head/seq_max/policy_hash/
  backend; `append_record_to_file` — O_NOFOLLOW-аналог jsonl (сейчас голый
  `OpenOptions::append` — symlink-дыра); `command` — redacted argv0-only
  по умолчанию; `--verify-chain`, `why-denied`, `reconstruct`.
- `src/events/tail.rs` — фильтры secret/degraded/cleanup; `replay.rs`  escape argv/path; `audit/recap.rs` — бейджи observation/integrity.
- `src/tui/*` — те же фильтры/бейджи, redacted argv.
- `src/evidence/` (новое, вынос из `verify_ng/`) — общие
  EvidenceTier/Capability/redact типы для audit и verify.
- `docs/schema/``audit-event.schema.json` (новое), расширение
  `session-stats.schema.json`; `docs/telemetry.md` — границу audit vs telemetry
  зафиксировать (forensics никогда не уходит на внешний endpoint).
- Тесты: chain-verify, redaction-adversarial (argv/env/PEM/journald/oslog),
  seq-gap детект, snapshot-hash стабильность, cleanup-report наличие,
  TUI-escape. (Покрытие — при реализации, не здесь.)

## 19. Migration strategy

Фаза 0: схема + снапшот-хэш + envelope поверх текущего `Event`
(аддитивно, `"v":1`, старые логи читаются). Фаза 1: backend/cleanup/
capability события (Linux first), санитайзер в journald/oslog, O_NOFOLLOW
для history. Фаза 2: Windows/macOS маппинг, `why-denied`/`reconstruct`/
`--verify-chain`, TUI-бейджи. Фаза 3: опциональная Ed25519-подпись отчётов
(`--sign-reports`, ключ из `policy/crypto.rs`), SQLite-индекс опционально.
Каждая фаза — minor +0.0.1 по VERSIONS.md, major всегда 0. Инвариант фаз:
ни одна фаза не меняет enforcement; только наблюдение.

## 20. Security limitations (честные границы)

1. Лог не доказывает enforcement: отсутствие denials при выключенном
   наблюдении неотличимо от чистоты — лечится бейджем `observation:` (§13-14),
   а не большим логом. Silent downgrade детектится только связкой
   snapshot(teir/backend/caps) + `BackendSelected` + degraded-Notices;
   один `Notice` свободной формы недостаточен — поэтому структура §4.
2. Broadcast-losses (`Lagged→continue`, `stats` caps, net caps 500) —
   атакующий шумом может вытеснить события; seq-gaps делают это видимым,
   но не предотвращают. Stats-агрегаты — не доказательства, только хинты.
3. Hash-chain ловит правку середины, но не truncation и не полную перезапись
   атакующим с правами пользователя; подпись ловит авторство, но не правдивость
   и умирает вместе с ключом на том же хосте. Модель: детект opportunistic-
   правок, не защита от root/владельца хоста.
4. Forensics-стор сам exfil-канал: argv/env/пути/IP в логах, отчёты, journald,
   oslog, TUI-экспорт — лечится классификацией §10 (redact-by-default,
   argv0-only, forbidden-drop, единый pipeline везде включая system_log/oslog,
   caps на размеры). Любой новый sink обязан пройти redaction-review.
5. Escape через логи: control/ANSI bytes в путях/argv/hostnames рендерятся в
   TUI/replay/HTML — обязательный strip+escape (образец `verify_ng/redact.rs`).
6. Session report пригоден для независимого review если: канонический JSON +
   JSON-схема, policy snapshot + hashes, backend/caps блок, разделение
   enforced/observed/no-signal, redaction-маркер (`redacted:true` + метод),
   chain-head для проверки целостности, версия vetto. Без этих шести —
   это маркетинг, не evidence.
7. `command` в истории сегодня — голый `argv.join` с поиском по нему:
   до миграции либо redact, либо не хранить; хранить секретосодержащую строку
   ради удобства поиска — неприемлемый trade-off.

## Critical review (ответы на 5 вопросов prompt)

1. Что audit доказывает / не доказывает: доказывает запрошенное vs применённое
   (при snapshot+hash), backend/tier-факт, наблюдаемые denials, исход и уборку
   (при cleanup-событиях). НЕ доказывает отсутствие пропущенного наблюдения,
   отсутствие kernel-обхода, правдивость при скомпрометированном хосте.
2. Обязательные события для IR: PolicySnapshot, BackendSelected,
   EnforcementConstructed, все denials (fs/net/secret/syscall) с decision,
   ChildStarted(process tree), CleanupReport, BackendError, SessionEnded +
   seq/integrity footer. Без любого из них картина неполна.
3. Silent downgrade: детект = `requested.tier != effective.backend` или
   `Capability.absent` без явного `degraded:true` + alert в recap/TUI;
   молчаливый фолбэк без события — запрещён схемой (fail-closed Notice).
4. Forensics как канал утечки: закрывается классификацией полей (§10) +
   единым pipeline + forbidden-drop + argv0-only + caps; самая острая дыра
   сегодня — голые system_log/oslog-sink и `command` в history.
5. Пригодность для независимого review: шесть условий из п.6 §20; текущий
   отчёт им не удовлетворяет (нет snapshot/hash/backend/caps/chain/
   enforced-vs-observed), после §4-6, §9 — удовлетворяет.

## Implementation plan (по фазам, без кода)

- P0 (схема): envelope §4 + `audit-event.schema.json`; session schema §5;
  policy snapshot + hash из `explain.rs`; seq в bus/sink; парсеры учат `"v"`.
- P1 (Linux evidence + hygiene): BackendSelected/EnforcementConstructed/
  CleanupReport/CapabilityDowngrade publish; policy_hash/corr во все publish;
  санитайзер в system_log/oslog; O_NOFOLLOW для history; command redact;
  фильтры secret/degraded/cleanup; `--verify-chain`.
- P2 (поритет + UX): Windows/macOS маппинг §15-16; why-denied/reconstruct;
  TUI-бейджи observation/integrity; вынос `src/evidence/` из verify_ng.
- P3 (опционально): `--sign-reports` (Ed25519 из `policy/crypto.rs`);
  SQLite-индекс поверх JSONL; старые артефакты — read-only совместимость.

## Acceptance criteria

- [ ] `vetto audit --verify-chain` проходит на свежих сессиях, детектит
      ручную правку середины лог-файла и сообщает о truncation через head.
- [ ] Чистая сессия с выключенным наблюдением показывает
      `observation: off`, а не «0 denials»; silent downgrade tier даёт
      явный degraded-сигнал в recap/TUI/audit.
- [ ] Каждая сессия содержит PolicySnapshot с совпадающим policy_hash во всех
      событиях; requested vs effective различимы; версия vetto зафиксирована.
- [ ] CleanupReport присутствует всегда (FULL: pidns-teardown факт; FS-ONLY:
      sweep killed/reaped/survivors/budget); escape-попытка видна в audit.
- [ ] Adversarial redaction-тесты зелёные: argv/env/PEM/bearer/jwt в событиях,
      отчётах, journald/oslog-выводе, `history.jsonl`, TUI-экспорте —
      отредактированы; caps на размеры держат (bus-flood не роняет хост).
- [ ] `why-denied`, `reconstruct --tree`, `reconstruct --policy`,
      secret/degraded/cleanup-фильтры работают на Linux; Windows/macOS —
      с честными Partial/Unsupported-бейджами, без завышенных заявлений.
- [ ] JSON-отчёт валидируется расширенной `session-stats.schema.json`;
      старые логи/отчёты читаются; enforcement не изменён (дифф — только
      наблюдение); версионирование +0.0.1, major 0.