@carllee1983/dbcli 1.20.0 → 1.20.2

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 CHANGED
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+ ## [1.20.1] - 2026-05-18
10
+
11
+ ### Changed
12
+
13
+ - **Phase 23-04 follow-up closure — full DML/DDL audit coverage.** `insert / update / delete / export / q / schema` now invoke `writeAuditEntry` on every happy / failure / rejection branch (BlacklistError / PermissionError / ConnectionError / validation all flow through the wired catch block). This closes the v1.20.0 INTEGRATE-01 / INTEGRATE-04 partial gap noted in v1.20.0's Known limitation paragraph.
14
+ - **Bi-directional `audit_ref` ⇄ `recovery_ref` linkage on every `--recovery`-capable command.** When any of the 6 newly-wired commands fails with `--recovery`, the audit entry's `recovery_ref` and the recovery envelope's `audit_ref` carry matching UUIDs — identical in shape to the Phase 25 `query` / `inspect` round-trip wiring. Agents can pivot from `.dbcli/last-recovery.json` to the audit entry via `dbcli audit tail --recovery-ref <id>`.
15
+ - **AI-agent skill docs (`assets/SKILL.md`, `assets/SKILL.zh-TW.md`, `assets/reference.md`)** updated to advertise full 8-command bi-directional coverage; bilingual user docs (`docs/user/en/index.{md,html}`, `docs/user/zh-TW/index.{md,html}`) gained a `--recovery` row noting the cross-command linkage.
16
+
17
+ ### Tests
18
+
19
+ - `tests/integration/recovery-audit-link.test.ts` — the legacy "J1 asymmetry guard" `describe` block (which asserted `'audit_ref' in envelope === false` for the 6 deferred commands) is replaced with a consolidated **6-command positive bi-directional round-trip** block. For each of `schema / q / export / insert / update / delete`, the test asserts `envelope.audit_ref === audit.id` AND `audit.recovery_ref === envelope.id`, with both refs matching `/^[0-9a-f-]{36}$/`.
20
+
21
+ ### Internal
22
+
23
+ - `src/commands/{insert,update,delete,export,q,schema}.ts` — adopt the D-J catch-block template from Phase 25: pre-generate `envelopeId = crypto.randomUUID()` only when `options.recovery === true`, call `writeAuditEntry({ success: false, error, recovery_ref: envelopeId })` and capture the returned `auditId`, then call `emitRecoveryEnvelope(err, ctx, { envelopeId, auditRef: auditId ?? undefined })`. Success branches add `await writeAuditEntry(config, '<cmd>', options, { success: true, ... })`.
24
+ - `src/commands/q.ts` — `handleQError` refactored to accept `config` so audit + envelope can be written together inside the same try/catch.
25
+ - `src/adapters/capabilities.ts` — narrow `ExportOptions` / `QCommandOptions` shapes opened so the shared audit helper can read `--recovery` and `--config` without per-command type casts.
26
+ - `.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md` — coverage table refreshed; all 6 previously-deferred rows flipped to `YES (Phase 23-04 wired)`; Round-Trip Contract section replaces the old Asymmetry Guard section.
27
+
9
28
  ## [1.20.0] - 2026-05-17
10
29
 
11
30
  ### Added
@@ -26,7 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
26
45
 
27
46
  - **Default-on, upgrade impact:** `audit.enabled = true` by default. Existing projects will begin creating `.dbcli/audit/<connection>.jsonl` on first command after upgrading. Set `audit.enabled = false` in `.dbcli` to opt out. The audit directory is gitignored by default; entries are metadata-only (D3) — never raw SQL bodies, `--param` values, or result cell contents. (D1)
28
47
  - `inspect` / `guide` / `recover` / `recover --apply` agent JSON output adds an `audit_recent` field (additive; shape stable; not a breaking change). v1.19.x consumers ignore the field.
29
- - _Known limitation (Phase 23-04 follow-up):_ Audit log captures `query`, `inspect`, and diagnostic-surface commands in v1.20.0; coverage for `insert / update / delete / export / q / schema` failure paths is tracked as Phase 23-04 follow-up (see `.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md`). Recovery envelope linkage from the envelope side is unaffected — those commands continue to emit `.dbcli/last-recovery.json` envelopes; only the `audit_ref` back-pointer is missing in v1.20.0.
48
+ - _Known limitation (Phase 23-04 follow-up):_ Audit log captures `query`, `inspect`, and diagnostic-surface commands in v1.20.0; coverage for `insert / update / delete / export / q / schema` failure paths is tracked as Phase 23-04 follow-up (see `.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md`). Recovery envelope linkage from the envelope side is unaffected — those commands continue to emit `.dbcli/last-recovery.json` envelopes; only the `audit_ref` back-pointer is missing in v1.20.0. (Closed in v1.20.1 — see entry above.)
30
49
 
31
50
  ### Internal
32
51
 
package/README.md CHANGED
@@ -1283,12 +1283,17 @@ audit history and recovery envelopes from either direction. The
1283
1283
  `audit_recent: AuditEntryBrief[]` (last 5 entries) so a fresh session has
1284
1284
  immediate context.
1285
1285
 
1286
- **Known limitation (v1.20.0):** Bi-directional linkage is wired for `query`,
1287
- `inspect`, and diagnostic surfaces. The DML commands `insert / update / delete /
1288
- export / q / schema` emit single-direction recovery envelopes (no `audit_ref`)
1289
- in v1.20.0; full coverage is tracked as Phase 23-04 follow-up. See
1290
- [`.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md`](./.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md)
1291
- for the full matrix.
1286
+ **Full bi-directional coverage (v1.20.1+):** Recovery ↔ audit linkage is
1287
+ wired on every `--recovery`-capable command `query`, `inspect`, `insert`,
1288
+ `update`, `delete`, `export`, `q`, and `schema`. The audit entry's
1289
+ `recovery_ref` and the envelope's `audit_ref` carry matching UUIDs on every
1290
+ failure path, so agents can pivot from a saved envelope
1291
+ (`.dbcli/last-recovery.json`) to its audit entry via
1292
+ `dbcli audit tail --recovery-ref <id>` (and back via
1293
+ `dbcli audit show --recovery-ref <id>`). The v1.20.0 partial-coverage gap
1294
+ on the 6 DML/DDL commands was closed in v1.20.1. The coverage matrix lives
1295
+ in
1296
+ [`.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md`](./.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md).
1292
1297
 
1293
1298
  For deeper agent workflows (session handoff, forensics walk-through), see
1294
1299
  [`assets/SKILL.md`](./assets/SKILL.md) §Audit Log usage.
package/assets/SKILL.md CHANGED
@@ -43,6 +43,8 @@ Database CLI for AI agents with permission-based access control.
43
43
  ```
44
44
 
45
45
  `--result` accepts inline JSON `StepResultSummary` or `@<path>` to read from a file. `stdoutSummary` and `stderrSummary` are capped at 4 KB each — pre-truncate to the **last** 4 KB before passing. `--next` is mutually exclusive with `--apply`. Each call is independent (no persisted cursor) — the agent tracks `--after-step` itself.
46
+
47
+ **Connection branching.** For `CONN_*` codes, the envelope ships a `branches` map + `branchFork` descriptor. Step 1 (`dbcli doctor --format json`) is the fork point: pass the doctor JSON in `--result.stdoutSummary` and `--next` will pick one of four labeled branches (`doctor-clean` / `doctor-config-missing` / `doctor-auth-error` / `doctor-network-error`). NextResult then carries `branchId` and `branchDescription`; subsequent calls must echo `--branch <id>` to walk that branch. Parse failure / unmatched keywords fall back to linear `recovery`. `--apply` ignores branches entirely.
46
48
  5. `dbcli blacklist list` — sensitive data boundaries.
47
49
  6. `dbcli schema <table> --format json` — real column names (SQL/Mongo/ES) or `schema <key>` (Redis). **Never guess.**
48
50
  7. Run `query` / `insert` / `update` / `delete` / `export` within permission.
@@ -100,15 +102,17 @@ The `inspect` / `guide` / `recover` / `recover --apply` agent JSON output embeds
100
102
  history context. The envelope's `audit_ref` and the audit entry's `recovery_ref`
101
103
  point at each other; agents can pivot either direction.
102
104
 
103
- **Known limitation (v1.20.0):** Bi-directional linkage is wired for `query`,
104
- `inspect`, and diagnostic surfaces. The commands `insert / update / delete /
105
- export / q / schema` emit single-direction recovery envelopes (no `audit_ref`)
106
- in v1.20.0; full coverage is tracked as Phase 23-04 follow-up. Recovery envelope
107
- linkage from the envelope side is unaffected.
105
+ Bi-directional `recovery_ref` / `audit_ref` linkage is wired on every command
106
+ that accepts `--recovery`: `query`, `inspect`, `insert`, `update`, `delete`,
107
+ `export`, `q`, and `schema`. Agents can pivot from an envelope to its audit
108
+ entry via `audit tail --recovery-ref <id>`.
108
109
 
109
110
  Audit entries are written to `.dbcli/audit/<connection>.jsonl` with rotation at
110
111
  ~10 MB or 1000 entries. `audit.enabled = false` in `.dbcli` opts out (default ON
111
112
  since v1.20.0). For flag reference see [`reference.md`](./reference.md) §audit.
113
+ For end-to-end recovery walkthroughs (per-code scenarios, `--next` multi-turn,
114
+ envelope ⇄ audit pivot, risk-gate cheat sheet) see
115
+ [`reference.md`](./reference.md) §Recovery Cookbook.
112
116
 
113
117
  ## Quick start
114
118
 
@@ -271,9 +275,12 @@ Full flags and edge cases: see [reference.md](reference.md) `init` section.
271
275
  ## MongoDB
272
276
 
273
277
  - JSON filter object (`find`) or JSON array (`aggregate`); SQL is rejected. `--collection <name>` is required on `query`.
274
- - **Supported:** `init`, `list`, `schema` (sampled), `query`, `insert`, `update`, `delete`, `export`, `status`, `use`, `shell`, `doctor`, `upgrade`, `completion`.
275
- - **Not supported:** `q` (saved queries), `diff`, `migrate`, `check`.
276
- - Schema is **sampled** (default 50 docs); types are JS `typeof` strings.
278
+ - **Supported:** `init`, `list`, `schema` (sampled), `query`, `insert`, `update`, `delete`, `export`, `q` (saved queries), `status`, `use`, `shell`, `doctor`, `upgrade`, `completion`.
279
+ - **Not supported:** `diff`, `migrate`, `check`.
280
+ - Schema is **sampled** by `$sample` (default 100 docs, max 1000). Pass `--sample-method natural` to use `find().limit()` instead. Columns surface as dot-paths (e.g. `profile.tokens.access`) with `presence` (0..1) and `redacted: true` flags for blacklist hits.
281
+ - **Write planner tiers:** `$set`/`$unset` → `ALLOW`; `$rename` → `WARN` (informational); `$inc`/`$mul`/`$min`/`$max`/`$currentDate` → `WARN`; `$push`/`$pull`/`$pullAll`/`$pop`/`$addToSet` → `WARN`; `$bit` → `WARN`; `$where` and unknown operators → `BLOCK`.
282
+ - **Nested blacklist:** `blacklist.columns[<collection>]` accepts dotted paths (`profile.email`) and trailing-wildcard prefixes (`profile.tokens.*`); middle wildcards are rejected with a warning at `dbcli blacklist list`. Read paths replace matched values with the literal string `[REDACTED]`.
283
+ - **Saved queries:** snippet file ends in `.mongodb.sql`. Frontmatter requires `engine: mongodb` and `operation: find` or `operation: aggregate`. `target: <collection>` is the default collection (override with `--collection`). Body is JSON (object for `find`, array for `aggregate`); `{{param}}` placeholders are JSON-encoded.
277
284
  - See reference.md MongoDB section for full syntax and examples.
278
285
 
279
286
  ## Redis
@@ -45,6 +45,8 @@ description: 為 AI 代理設計、具權限控管的資料庫 CLI。可用於
45
45
  ```
46
46
 
47
47
  `--result` 接受 inline JSON `StepResultSummary`,或 `@<path>` 從檔案讀取。`stdoutSummary` 與 `stderrSummary` 各上限 4 KB — 請先把結果截到 **最後** 4 KB 再傳入。`--next` 與 `--apply` 互斥。每次呼叫獨立(不持久化 cursor)— 代理自行追蹤 `--after-step`。
48
+
49
+ **連線錯誤分支。** 針對 `CONN_*` 代碼,envelope 會額外帶 `branches` 與 `branchFork`。步驟 1(`dbcli doctor --format json`)即為分支點:把 doctor JSON 透過 `--result.stdoutSummary` 傳入,`--next` 會挑選四個分支之一(`doctor-clean` / `doctor-config-missing` / `doctor-auth-error` / `doctor-network-error`)。NextResult 會帶 `branchId` 與 `branchDescription`;後續呼叫必須以 `--branch <id>` 走訪該分支。解析失敗或關鍵字不匹配時回落為線性 `recovery`。`--apply` 不使用 `branches`。
48
50
  5. `dbcli blacklist list` — 敏感資料邊界。
49
51
  6. `dbcli schema <table> --format json` — 取得真實欄位名稱(SQL / Mongo / ES)或 `schema <key>`(Redis)。**禁止猜測。**
50
52
  7. 在允許的權限範圍內執行 `query` / `insert` / `update` / `delete` / `export`。
@@ -89,9 +91,9 @@ dbcli audit show --recovery-ref <envelope-id> # 反向找出觸發 envelope 的
89
91
 
90
92
  `inspect` / `guide` / `recover` / `recover --apply` 的 agent JSON 內嵌 `audit_recent: AuditEntryBrief[]`(最近 5 筆),無須額外呼叫 audit CLI 即可看到歷史脈絡。Envelope 的 `audit_ref` 與 audit entry 的 `recovery_ref` 互為雙向指標。
91
93
 
92
- **已知限制(v1.20.0):** Bi-directional 連結僅在 `query` / `inspect` / diagnostic 表面寫入;`insert` / `update` / `delete` / `export` / `q` / `schema` 失敗路徑暫未含 `audit_ref`,追蹤於 Phase 23-04 follow-up(見 `.planning/phases/25-recovery-envelope-bi-directional-linkage/25-J1-COVERAGE-MATRIX.md`)。Recovery envelope 既有 linkage 不受影響。
94
+ **完整雙向覆蓋(v1.20.1+):** `recovery_ref` / `audit_ref` 雙向連結已在所有支援 `--recovery` 的指令上佈線:`query`、`inspect`、`insert`、`update`、`delete`、`export`、`q`、`schema`。Agent 可透過 `dbcli audit tail --recovery-ref <id>` envelope 反查 audit entry(反方向用 `dbcli audit show --recovery-ref <id>`)。v1.20.0 6 DML/DDL 指令的部分覆蓋缺口已於 v1.20.1 關閉。
93
95
 
94
- 詳細指令參考:[`reference.md`](./reference.md) §audit(英文)。
96
+ 詳細指令參考:[`reference.md`](./reference.md) §audit(英文)。完整 agent 復原 walkthrough(各錯誤碼 end-to-end 情境、`--next` 多輪逐步、envelope ⇄ audit pivot、risk gate cheat sheet)見 [`reference.md`](./reference.md) §Recovery Cookbook(英文)
95
97
 
96
98
  完整旗標、每個指令的可貼上範例、`migrate` DDL、互動式 `shell` 與 MongoDB / Redis / ES 教學在 [reference.md](reference.md)(安裝時與本檔放在一起)。
97
99
 
@@ -239,9 +241,12 @@ dbcli init --use-env-refs \
239
241
  ## MongoDB
240
242
 
241
243
  - 用 JSON filter 物件(`find`)或 JSON 陣列(`aggregate`);SQL 會被拒絕。`query` 必填 `--collection <name>`。
242
- - **支援:** `init`、`list`、`schema`(sampled)、`query`、`insert`、`update`、`delete`、`export`、`status`、`use`、`shell`、`doctor`、`upgrade`、`completion`。
243
- - **不支援:** `q`(saved queries)、`diff`、`migrate`、`check`。
244
- - Schema 是**取樣**(預設 50 份文件);型別是 JS `typeof` 字串。
244
+ - **支援:** `init`、`list`、`schema`(sampled)、`query`、`insert`、`update`、`delete`、`export`、`q`(saved queries)、`status`、`use`、`shell`、`doctor`、`upgrade`、`completion`。
245
+ - **不支援:** `diff`、`migrate`、`check`。
246
+ - Schema `$sample` **採樣**(預設 100 份文件,上限 1000)。可加 `--sample-method natural` 改用 `find().limit()`。欄位以 dot-path 呈現(如 `profile.tokens.access`),附帶 `presence`(0..1)與命中黑名單時的 `redacted: true`。
247
+ - **寫入規劃器分層:** `$set`/`$unset` → `ALLOW`;`$rename` → `WARN`(資訊提示);`$inc`/`$mul`/`$min`/`$max`/`$currentDate` → `WARN`;`$push`/`$pull`/`$pullAll`/`$pop`/`$addToSet` → `WARN`;`$bit` → `WARN`;`$where` 與未知運算子 → `BLOCK`。
248
+ - **巢狀黑名單:** `blacklist.columns[<collection>]` 接受點分路徑(`profile.email`)與結尾萬用字元(`profile.tokens.*`);中間萬用字元會在 `dbcli blacklist list` 警告並略過。讀取路徑會將命中值取代為字串字面值 `[REDACTED]`。
249
+ - **儲存查詢:** snippet 檔名以 `.mongodb.sql` 結尾。Frontmatter 必填 `engine: mongodb` 與 `operation: find` 或 `operation: aggregate`。`target: <collection>` 為預設集合,可由 `--collection` 覆蓋。主體為 JSON(`find` 為物件、`aggregate` 為陣列);`{{param}}` 佔位符會 JSON 編碼。
245
250
  - 完整語法與範例見 reference.md MongoDB 段落。
246
251
 
247
252
  ## Redis
@@ -100,13 +100,14 @@ dbcli schema --use staging # Scan staging DB; saves to .dbcli/schemas/s
100
100
  dbcli schema --use prod # Scan prod DB; saves to .dbcli/schemas/prod/
101
101
  ```
102
102
 
103
- **Options:** `--format <table|json>`, `--refresh`, `--reset`, `--force`, `--use <connection>`
103
+ **Options:** `--format <table|json>`, `--refresh`, `--reset`, `--force`, `--use <connection>`, `--sample-size <n>` (mongo only), `--sample-method <random|natural>` (mongo only)
104
104
  **Permission:** query-only+
105
105
 
106
106
  **Schema storage (v1.4+):** Schema is persisted as layered files under `.dbcli/schemas/`. With v2 multi-connection config each connection gets its own subdirectory (`.dbcli/schemas/<connection>/`). Run `dbcli schema --use <connection>` once per connection before querying it — otherwise `schema <table>` may return data from the wrong connection's cache.
107
107
 
108
108
  > **Redis:** `schema <key>` is required (no full scan). The output exposes `type`, `ttl`, `size`, and a small `sample` (e.g. first 5 hash keys). `--reset` / `--refresh` are rejected — Redis caches no schema.
109
109
  > **Elasticsearch:** `schema [index]` flattens the `_mapping` properties (nested `a.b.c`) and emits each `.fields` multi-field as a separate column (e.g. `text` + `text.keyword`). Full scan iterates all non-system indices and stores per-connection caches alongside SQL engines.
110
+ > **MongoDB:** schema is sampled via `$sample` (default 100, max 1000). `--sample-method natural` switches to `find().limit()`; `random` (default) falls back to natural order on driver error. Output columns surface nested dot-paths with `presence` (0..1) and `redacted: true` flags for blacklist-matched paths. The persisted cache records `sampleMethod` and `sampleSize`; `dbcli doctor` reports them via a `sampled: method=…, size=…` line.
110
111
 
111
112
  ### query
112
113
 
@@ -328,6 +329,57 @@ Substitution rules: pure raw text — `:name` becomes the value's `String()` for
328
329
 
329
330
  Size guard: `LRANGE` / `ZRANGE` stop overridden when `< 0` or `> 1000`; `SCAN` / `HSCAN` / `SSCAN` / `ZSCAN` get `COUNT 1000` injected if absent. `--no-limit` disables.
330
331
 
332
+ ##### MongoDB snippets
333
+
334
+ File extension: `.mongodb.sql`. Frontmatter must declare `engine: mongodb` and
335
+ `operation: find` or `operation: aggregate`. `target: <collection>` provides a default
336
+ collection that `dbcli q --collection <name>` can override. The body is JSON: an object
337
+ for `find` and an array for `aggregate`. Each `{{param}}` placeholder is JSON-encoded
338
+ at substitution time — strings are quoted and escaped, so an attacker-supplied string
339
+ cannot escape into operator position.
340
+
341
+ Find example (`active-users.mongodb.sql`):
342
+
343
+ -- ---
344
+ -- name: active-users
345
+ -- engine: mongodb
346
+ -- operation: find
347
+ -- target: users
348
+ -- description: Active users matching the given status
349
+ -- params:
350
+ -- status:
351
+ -- type: string
352
+ -- required: true
353
+ -- ---
354
+ {
355
+ "status": {{status}}
356
+ }
357
+
358
+ Aggregate example (`top-orders-by-city.mongodb.sql`):
359
+
360
+ -- ---
361
+ -- name: top-orders-by-city
362
+ -- engine: mongodb
363
+ -- operation: aggregate
364
+ -- target: orders
365
+ -- description: Top order counts per city for a given status
366
+ -- params:
367
+ -- status:
368
+ -- type: string
369
+ -- required: true
370
+ -- limit:
371
+ -- type: int
372
+ -- default: 10
373
+ -- ---
374
+ [
375
+ { "$match": { "status": {{status}} } },
376
+ { "$group": { "_id": "$city", "n": { "$sum": 1 } } },
377
+ { "$sort": { "n": -1 } },
378
+ { "$limit": {{limit}} }
379
+ ]
380
+
381
+ Run with `dbcli q @active-users -p status=active` or `dbcli q @top-orders-by-city -p status=open -p limit=5`. The `q` command applies the same nested-blacklist redaction to results that `query` and `export` do.
382
+
331
383
  ### queries
332
384
 
333
385
  Manage saved snippets — discover, inspect, scaffold, and edit local copies. Mutating
@@ -727,8 +779,9 @@ deterministically.
727
779
  | Flag | Required | Description |
728
780
  |---|---|---|
729
781
  | `--next` | yes | Activate the multi-turn lookup. |
730
- | `--after-step <n>` | yes | 1-based order of the step the agent just executed. Range: `[1, envelope.recovery.length]`. |
782
+ | `--after-step <n>` | yes | 1-based order of the step the agent just executed. Range: `[1, envelope.recovery.length]` (or `[1, branches[id].steps.length]` when `--branch` is set). |
731
783
  | `--result <value>` | yes | JSON `StepResultSummary` (inline) or `@<path>` to read from a file. |
784
+ | `--branch <id>` | no | Walk a specific branch by id (required on `--next` calls after a fork). See *Connection branching* below. |
732
785
  | `--from <path>` | no | Override the auto-saved envelope. |
733
786
  | `--format <fmt>` | no | `json` (default) or `markdown`. |
734
787
 
@@ -760,9 +813,24 @@ interface NextResult {
760
813
  cursor: number // step.order when kind='step'; totalSteps when 'done'
761
814
  totalSteps: number
762
815
  step?: GuideStep // present iff kind='step'
816
+ branchId?: string // set iff agent is currently traversing a branch
817
+ branchDescription?: string // mirror of branches[branchId].description
763
818
  }
764
819
  ```
765
820
 
821
+ **Connection branching**
822
+
823
+ For `CONN_*` recovery codes, the envelope ships an additional `branches` map and a `branchFork` descriptor. Step 1 (`dbcli doctor --format json`) is the fork point: pass the doctor JSON in `--result.stdoutSummary` and `--next` will pick one of four labeled branches:
824
+
825
+ | Branch id | When chosen |
826
+ |---|---|
827
+ | `doctor-clean` | Doctor reports no errors — likely transient; verify baseline state, then retry. |
828
+ | `doctor-config-missing` | Doctor flagged a config-level failure (missing / invalid config). Re-init before reconnecting. |
829
+ | `doctor-auth-error` | Doctor confirms credentials were rejected. Re-init with `--force` to overwrite credentials. |
830
+ | `doctor-network-error` | Doctor confirms a network-level failure (host / port / DNS / timeout). Inspect and re-init host/port. |
831
+
832
+ NextResult sets `branchId` and `branchDescription` after the fork; subsequent `--next` calls must echo `--branch <id>` to walk that branch. If the doctor JSON cannot be parsed or no keyword matches, `--next` falls back to the linear `recovery` plan — branching never causes `--next` to fail. `--apply` ignores `branches` entirely (linear walk unchanged).
833
+
766
834
  **Exit codes**
767
835
 
768
836
  | Exit | Condition |
@@ -859,7 +927,7 @@ Output reports: writer enabled/disabled, last write result, file-lock state, rot
859
927
  #### Boundaries
860
928
 
861
929
  - Entries are append-only JSONL; rotation triggers at `~10 MB` or `~1000` entries (whichever first). Previous segment is preserved as `.jsonl.1`.
862
- - Bi-directional `recovery_ref` / `audit_ref` linkage is wired on `query` / `inspect` / diagnostic surfaces (Phase 25 J1). The commands `insert / update / delete / export / q / schema` emit single-direction envelopes (no `audit_ref`) in v1.20.0 tracked as Phase 23-04 follow-up.
930
+ - Bi-directional `recovery_ref` / `audit_ref` linkage is wired on every command that accepts `--recovery`: `query`, `inspect`, `insert`, `update`, `delete`, `export`, `q`, and `schema`. Use `audit tail --recovery-ref <id>` to find the audit entry an envelope was emitted alongside.
863
931
  - Audit writer failures are non-fatal (D6): main command result and exit code are preserved; a stderr warning is emitted. `audit health` surfaces the failure reason.
864
932
  - Reader truncation tolerance: a crash-truncated last line is skipped with a stderr warn `[dbcli audit] skipping truncated last line in <file>`; a mid-file non-JSON line is treated as corruption, exits 1, and points at `dbcli audit clear`.
865
933
 
@@ -1042,6 +1110,270 @@ Task storage layers:
1042
1110
  Higher tiers override lower tiers by task name. Task name is derived from the
1043
1111
  file path under the tier root (e.g. `diag/inspect.md` → `diag/inspect`).
1044
1112
 
1113
+ ## Recovery Cookbook (agent walkthroughs)
1114
+
1115
+ End-to-end recovery sessions for the most common failure codes. All examples
1116
+ assume the agent invoked a `--recovery`-capable command and received a
1117
+ `RecoveryEnvelope` (or hit the same envelope via `dbcli recovery --code <CODE>`
1118
+ lookup). See [§recovery](#recovery) for the envelope shape, [§recover](#recover)
1119
+ for `--apply` / `--next` / risk-gate semantics, and [§audit](#audit) for the
1120
+ bi-directional `audit_ref` ⇄ `recovery_ref` pivot.
1121
+
1122
+ ### Scenario index
1123
+
1124
+ | Code | Trigger | Primary remediation | Risk tier |
1125
+ |------|---------|---------------------|-----------|
1126
+ | `CONN_REFUSED` | Database process down or wrong host/port. | `dbcli doctor` → fix host/port → retry. | `readonly` |
1127
+ | `CONN_AUTH_FAILED` | Credentials rejected. | Re-check `.dbcli`/env, rotate credentials, `dbcli init --force` only on explicit user nod. | `readonly` → `interactive` |
1128
+ | `PERMISSION_DENIED` | Active permission level forbids the verb. | `dbcli inspect` to confirm level → escalate via `dbcli init` (human) or run a `--dry-run` instead. | `readonly` + `dry-run` |
1129
+ | `BLACKLIST_TABLE` | Target table is blacklisted. | `dbcli blacklist list` → `blacklist table remove <name>` (local-write tier). | `readonly` + `local-write` |
1130
+ | `BLACKLIST_COLUMN_WRITE` | INSERT/UPDATE touches a blacklisted column. | Re-shape payload to drop the column, or `blacklist column remove`. Envelope prepends a `--dry-run` preview step. | `dry-run` + `local-write` |
1131
+ | `SCHEMA_CACHE_MISSING` | Fresh checkout / new v2 connection / cache wiped. | `dbcli schema --refresh --force` (or `--use <conn>` per-connection). | `readonly` |
1132
+ | `SNIPPET_NOT_FOUND` / `SNIPPET_AMBIGUOUS` | Typo or duplicate snippet name. | `dbcli queries list` → `queries search <kw>` → run correct `@name`. | `readonly` |
1133
+ | `SNIPPET_PARAM_MISSING` | `--param k=v` not supplied. | `dbcli queries show @name` lists required params → re-run with full set. | `readonly` |
1134
+ | `CONFIG_MISSING` | No `.dbcli` in cwd. | `dbcli init` (human-driven). | `interactive` |
1135
+
1136
+ `risk` enum: `readonly` / `dry-run` / `write` / `unknown` (see §recovery boundaries).
1137
+ Allowlist tier: `readonly` / `dry-run` / `local-write` / `db-write` / `interactive` (see [§recover Risk gate matrix](#risk-gate-matrix)).
1138
+
1139
+ ### S1 — CONN_REFUSED end-to-end
1140
+
1141
+ ```bash
1142
+ # 1. Failing call writes envelope to stdout AND .dbcli/last-recovery.json
1143
+ $ dbcli query "SELECT 1" --recovery --format json
1144
+ {
1145
+ "schemaVersion": 1,
1146
+ "error": { "code": "CONN_REFUSED", "message": "..." },
1147
+ "audit_ref": "1f8e...c4d2",
1148
+ "recovery": [
1149
+ { "order": 1, "command": "dbcli doctor --format json", "risk": "readonly", ... },
1150
+ { "order": 2, "command": "dbcli inspect --for-agent", "risk": "readonly", ... }
1151
+ ],
1152
+ "verify": { "command": "dbcli doctor --format json", "risk": "readonly", ... }
1153
+ }
1154
+
1155
+ # 2. One-shot apply (only readonly + dry-run run by default)
1156
+ $ dbcli recover --apply --format json
1157
+ { "finalStatus": "ok", "executed": [...], "verifyStatus": "passed" }
1158
+ # Exit 0 → root cause cleared (verify probe succeeded).
1159
+
1160
+ # 3. If verify reported `failed` / `indeterminate`, drop into --next for control
1161
+ $ dbcli recover --next --after-step 1 --result '{"status":"failed","exitCode":1}'
1162
+ # → returns a refined step 2 or `kind:"done"` based on the prevResult
1163
+ ```
1164
+
1165
+ ### S2 — PERMISSION_DENIED with implicit `--dry-run` preview
1166
+
1167
+ ```bash
1168
+ $ dbcli update orders --where "id=1" --set '{"status":"shipped"}' --recovery --format json
1169
+ {
1170
+ "error": { "code": "PERMISSION_DENIED", ... },
1171
+ "audit_ref": "9ab0...e711",
1172
+ "recovery": [
1173
+ { "order": 1, "command": "dbcli update orders --where 'id=1' --set '<redacted>' --dry-run", "risk": "dry-run" },
1174
+ { "order": 2, "command": "dbcli inspect --for-agent", "risk": "readonly" }
1175
+ ]
1176
+ }
1177
+
1178
+ # Default apply runs both steps (dry-run is in-tier).
1179
+ $ dbcli recover --apply
1180
+ ```
1181
+
1182
+ When the failing operation is INSERT/UPDATE/DELETE, the envelope prepends a
1183
+ `risk: 'dry-run'` step (the same write subcommand with `--dry-run`). Run it
1184
+ before any escalation — it both teaches the agent what the SQL looks like and
1185
+ proves the change is well-formed before raising the permission tier.
1186
+
1187
+ ### S3 — BLACKLIST_TABLE (local-write remediation)
1188
+
1189
+ ```bash
1190
+ $ dbcli query "SELECT * FROM audit_logs" --recovery --format json
1191
+ # error.code: BLACKLIST_TABLE
1192
+ # recovery[0]: dbcli blacklist list (risk: readonly)
1193
+ # recovery[1]: dbcli blacklist table remove audit_logs (risk: write — local-write tier)
1194
+
1195
+ # Default --apply: step 1 runs, step 2 skipped:risk → exit 3.
1196
+ $ dbcli recover --apply
1197
+ # To proceed: open the gate to local-write tier ONLY (does not touch DB).
1198
+ $ dbcli recover --apply --allow-write=readonly-cmd
1199
+ { "finalStatus": "ok", "executed": [step1, step2], "verifyStatus": "passed" }
1200
+ ```
1201
+
1202
+ ### S4 — BLACKLIST_COLUMN_WRITE (preview-then-drop)
1203
+
1204
+ ```bash
1205
+ $ dbcli insert users --data '{"name":"a","ssn":"123"}' --recovery
1206
+ # recovery[0]: dbcli insert users --data '<redacted>' --dry-run (risk: dry-run)
1207
+ # recovery[1]: dbcli blacklist list (risk: readonly)
1208
+ # recovery[2]: dbcli blacklist column remove users.ssn (risk: write — local-write)
1209
+
1210
+ # Preferred path: don't widen the blacklist — re-shape the agent's payload to drop ssn.
1211
+ # Apply only the diagnostic prefix (steps 1+2) to confirm what columns are masked:
1212
+ $ dbcli recover --apply
1213
+ # Then re-issue insert without `ssn`.
1214
+ ```
1215
+
1216
+ ### S5 — SCHEMA_CACHE_MISSING (fresh / multi-conn)
1217
+
1218
+ ```bash
1219
+ $ dbcli inspect --require-schema-cache --recovery --format json
1220
+ # error.code: SCHEMA_CACHE_MISSING
1221
+ # recovery[0]: dbcli schema --refresh --force (risk: readonly — populates .dbcli/schemas/)
1222
+ # verify: dbcli inspect --format json (schemaCache.available === true)
1223
+
1224
+ $ dbcli recover --apply
1225
+ # Per-connection cache lives at .dbcli/schemas/<connection>/. If the failure was on
1226
+ # a v2 named connection, the envelope's command already carries `--use <name>`.
1227
+ ```
1228
+
1229
+ ### S6 — SNIPPET_NOT_FOUND with disambiguation
1230
+
1231
+ ```bash
1232
+ $ dbcli q @anaytics/revenue --recovery
1233
+ # typo: anaytics → analytics
1234
+ # recovery[0]: dbcli queries list --format json
1235
+ # recovery[1]: dbcli queries search analytics (or whatever --hint suggests)
1236
+ $ dbcli recover --apply
1237
+ # Agent reads stdoutSummary, identifies the correct @name, then re-issues:
1238
+ $ dbcli q @analytics/revenue --param days=30
1239
+ ```
1240
+
1241
+ ### Multi-turn `--next` walkthrough (3-step plan)
1242
+
1243
+ Use `--next` instead of `--apply` when:
1244
+
1245
+ - `--apply` is too coarse-grained (the agent wants step-by-step inspection).
1246
+ - The plan contains an `interactive` step that `--apply` would skip.
1247
+ - The agent uses its own runner / sandbox and just wants dbcli to drive cursoring.
1248
+
1249
+ `--next` returns one step at a time, given which step the agent **just executed**
1250
+ and a `StepResultSummary` of how it went. dbcli does not persist the cursor —
1251
+ the agent owns `--after-step`.
1252
+
1253
+ ```bash
1254
+ # Envelope already saved at .dbcli/last-recovery.json (3-step plan, CONN_REFUSED).
1255
+
1256
+ # Round 1 — agent reads step 1 from the envelope, executes it itself, then asks
1257
+ # dbcli for the next step.
1258
+ $ dbcli recover --next --after-step 1 --result '{"status":"ok","exitCode":0}' --format json
1259
+ {
1260
+ "schemaVersion": 1,
1261
+ "kind": "step",
1262
+ "errorCode": "CONN_REFUSED",
1263
+ "cursor": 2,
1264
+ "totalSteps": 3,
1265
+ "step": { "order": 2, "command": "dbcli inspect --for-agent", "risk": "readonly", ... }
1266
+ }
1267
+
1268
+ # Round 2 — bigger stdout, save to file and reference it.
1269
+ $ ./run-step.sh > /tmp/r2.json # agent's own runner; result is StepResultSummary JSON
1270
+ $ dbcli recover --next --after-step 2 --result @/tmp/r2.json
1271
+ { "kind": "step", "cursor": 3, "step": { "order": 3, ... } }
1272
+
1273
+ # Round 3 — last step done.
1274
+ $ dbcli recover --next --after-step 3 --result '{"status":"ok"}'
1275
+ { "kind": "done", "cursor": 3, "totalSteps": 3 }
1276
+ ```
1277
+
1278
+ `StepResultSummary` contract (recap of [§recover Multi-turn](#multi-turn---next-p2)):
1279
+
1280
+ ```ts
1281
+ interface StepResultSummary {
1282
+ status: 'ok' | 'failed' | 'skipped'
1283
+ exitCode?: number
1284
+ stdoutSummary?: string // last 4 KB
1285
+ stderrSummary?: string // last 4 KB
1286
+ }
1287
+ ```
1288
+
1289
+ Truncate to the **last** 4 KB before passing — the head of a huge stdout is
1290
+ usually not what disambiguates next steps.
1291
+
1292
+ Verification is **not** automatic under `--next`. If the agent wants the same
1293
+ verify probe `--apply` runs, it must execute the envelope's `verify` step
1294
+ itself after the plan completes.
1295
+
1296
+ ### Bi-directional pivot (envelope ⇄ audit)
1297
+
1298
+ Every `--recovery`-capable failure (`query`, `inspect`, `insert`, `update`,
1299
+ `delete`, `export`, `q`, `schema`) writes **both** sides of a UUID link:
1300
+
1301
+ - `RecoveryEnvelope.audit_ref` → the `audit.id` for the same failure.
1302
+ - `AuditEntry.recovery_ref` → the envelope's id (also the auto-saved
1303
+ `.dbcli/last-recovery.json` filename trace).
1304
+
1305
+ ```bash
1306
+ # From envelope → audit (forensics on a saved failure)
1307
+ $ ENV_ID=$(jq -r '.id' .dbcli/last-recovery.json) # or read from stdout
1308
+ $ dbcli audit show --recovery-ref "$ENV_ID" --format json
1309
+ # Returns the matching audit entry (full, not brief).
1310
+
1311
+ # From audit → envelope (you have an audit hit, want the structured plan)
1312
+ $ AUDIT_ID=$(dbcli audit tail --for-agent --n 1 | jq -r '.[0].id')
1313
+ $ dbcli audit show "$AUDIT_ID" --format json
1314
+ # Read `recovery_ref` from the entry, then either re-run --recovery against
1315
+ # the original command or load the saved envelope:
1316
+ $ jq '.recovery_ref' .dbcli/last-recovery.json | grep -q "$RECOVERY_REF" \
1317
+ && dbcli recover --format markdown # human inspect
1318
+ || dbcli recover --from /path/to/archived.json --format markdown
1319
+ ```
1320
+
1321
+ Session handoff: a fresh agent that opens `dbcli inspect --for-agent`,
1322
+ `dbcli guide`, `dbcli recover`, or `dbcli recover --apply` gets an
1323
+ `audit_recent: AuditEntryBrief[]` field (last 5 entries) embedded in the JSON
1324
+ output — no extra round-trip to the audit CLI needed for immediate history
1325
+ context.
1326
+
1327
+ ### Risk gate cheat sheet
1328
+
1329
+ Quick reference for what `--apply` runs at each `--allow-write` level. The
1330
+ canonical matrix lives at [§recover Risk gate matrix](#risk-gate-matrix); this
1331
+ table maps it onto common agent intents.
1332
+
1333
+ | Agent intent | Recommended flag | What runs | What's skipped |
1334
+ |---|---|---|---|
1335
+ | Probe-only (read state, learn) | `--apply` (default) | `readonly` + `dry-run` steps | `local-write`, `db-write`, `interactive` |
1336
+ | Local config remediation (e.g. `blacklist remove`) | `--apply --allow-write=readonly-cmd` | + `local-write` | `db-write`, `interactive` |
1337
+ | Database write recovery (rare; trusted plan) | `--apply --allow-write=write-cmd` | + `db-write` | `interactive` |
1338
+ | Interactive step (e.g. `dbcli init`) | Drive manually OR use `--next` | n/a | All interactive steps always skip under `--apply` |
1339
+ | Walk plan step-by-step with own runner | `--next --after-step N --result …` | one step per call | n/a — agent owns cursor + execution |
1340
+
1341
+ Three rules that always apply regardless of `--allow-write`:
1342
+
1343
+ 1. **Tier is code-owned, not envelope-claimed.** The risk gate reads the
1344
+ per-error-code allowlist after parsing argv. An envelope cannot escalate
1345
+ itself by setting `risk: 'readonly'` on a write subcommand — argv decides.
1346
+ 2. **Placeholders block.** A step with unresolved `<token>` placeholders is
1347
+ skipped as `skipped:placeholder` even at `--allow-write=write-cmd`. Bind
1348
+ them at `recovery` lookup time with `--hint` / `--snippet` / `--table`, or
1349
+ ask the user.
1350
+ 3. **Verify is signal, not gate.** `verifyStatus` ∈ `{passed, failed,
1351
+ indeterminate}` reports whether the original failure looks resolved.
1352
+ `recover --apply` exit code is set by step execution, not verification.
1353
+
1354
+ ### Common pitfalls
1355
+
1356
+ - **Stale `.dbcli/last-recovery.json`.** `recover` (no `--apply`) shows the
1357
+ *saved* plan, which may be hours old. Re-run the original command with
1358
+ `--recovery` to refresh it, or pass `--from <file>` to load an archived one.
1359
+ - **`.dbcli/` is gitignored.** Do not check `last-recovery.json` into a repo
1360
+ for "reproducibility"; it contains sanitized command snapshots but the
1361
+ workspace `cwd` only makes sense locally. Use `recover --from <archived.json>`
1362
+ for cross-machine replay.
1363
+ - **`--apply` exit 3 means every step skipped.** Not a failure — it means the
1364
+ default gate was too tight. Either widen with `--allow-write`, fill
1365
+ placeholders, or fall back to `--next` and drive steps manually.
1366
+ - **`--next` does not run verify.** Re-run the original failing command with
1367
+ `--recovery` once the plan is done; if it now succeeds (no envelope on
1368
+ stdout), recovery is complete. Or invoke `envelope.verify.command` yourself.
1369
+ - **Audit writer failures are non-fatal.** If `audit health` reports
1370
+ `lastWriteOk: false`, the main command still completed — but `recovery_ref`
1371
+ ⇄ `audit_ref` linkage is broken for that one call. `audit health` surfaces
1372
+ the underlying error (disk full, EACCES, etc.).
1373
+ - **Cross-connection forensics.** `audit tail --all --for-agent` merges all
1374
+ connections; `audit show <id-prefix> --all` returns an envelope `{connection,
1375
+ entry}` so a fresh agent can tell which DB the failure was against.
1376
+
1045
1377
  ## Interactive HTML dashboard
1046
1378
 
1047
1379
  `query`, `q`, and `export` can render results as a single, fully self-contained
@@ -1150,19 +1482,21 @@ MongoDB connections use a JSON-based query model instead of SQL. Treat MongoDB s
1150
1482
 
1151
1483
  Atlas-style `mongodb+srv://` URIs are supported. `list` and `query` run against the database configured for the connection, and `query` always requires `--collection <name>`.
1152
1484
 
1153
- **Supported commands:** `init`, `use`, `list`, `schema`, `query`, `insert`, `update`, `delete`, `status`, `shell`, `doctor`, `upgrade`, `completion`
1485
+ **Supported commands:** `init`, `use`, `list`, `schema`, `query`, `q`, `insert`, `update`, `delete`, `export`, `status`, `shell`, `doctor`, `upgrade`, `completion`
1154
1486
 
1155
1487
  **Limited support:**
1156
1488
 
1157
1489
  - `schema` samples collection documents to infer field names/types. It does not provide relational constraints, primary keys, foreign keys, or reliable index metadata.
1158
1490
  - `query` accepts only JSON object filters or aggregation pipeline arrays and always requires `--collection <name>`.
1491
+ - `q` saved-query execution accepts JSON `find` / `aggregate` bodies, requires a `collection` frontmatter field (CLI `--collection` overrides), JSON-encodes every `{{param}}` substitution, and enforces table-level blacklist plus document field masking before rendering.
1159
1492
  - `insert` inserts one JSON document into the named collection.
1160
1493
  - `update` accepts a JSON filter in `--where` or simple `key=value` conditions. If `--set` does not use MongoDB update operators such as `$set`, dbcli wraps it in `$set`.
1161
1494
  - `delete` deletes all documents matching the JSON/simple filter.
1495
+ - `export` accepts the same JSON filter / aggregation syntax as `query`.
1162
1496
  - MongoDB write paths do not currently provide the same SQL dry-run, relational schema validation, or column-level blacklist filtering guarantees as SQL writes.
1163
1497
  - `shell` blocks raw SQL for MongoDB; use `query <json> --collection <name>` inside the shell.
1164
1498
 
1165
- **Not supported (exit with error):** `q` saved-query execution, `export`, `diff`, `migrate`
1499
+ **Not supported (exit with error):** `diff`, `migrate`
1166
1500
 
1167
1501
  **Not a supported MongoDB target:** `check` is designed for relational health checks and emits SQL-style checks.
1168
1502
 
@@ -0,0 +1,14 @@
1
+ -- ---
2
+ -- name: active-users
3
+ -- engine: mongodb
4
+ -- operation: find
5
+ -- target: users
6
+ -- description: Active users matching the given status
7
+ -- params:
8
+ -- status:
9
+ -- type: string
10
+ -- required: true
11
+ -- ---
12
+ {
13
+ "status": {{status}}
14
+ }
@@ -0,0 +1,20 @@
1
+ -- ---
2
+ -- name: top-orders-by-city
3
+ -- engine: mongodb
4
+ -- operation: aggregate
5
+ -- target: orders
6
+ -- description: Top order counts per city for a given status
7
+ -- params:
8
+ -- status:
9
+ -- type: string
10
+ -- required: true
11
+ -- limit:
12
+ -- type: int
13
+ -- default: 10
14
+ -- ---
15
+ [
16
+ { "$match": { "status": {{status}} } },
17
+ { "$group": { "_id": "$city", "n": { "$sum": 1 } } },
18
+ { "$sort": { "n": -1 } },
19
+ { "$limit": {{limit}} }
20
+ ]