@carllee1983/dbcli 2.1.0 → 4.0.0
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/skills/dbcli/reference.md +38 -6
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/skills/dbcli/reference.md +38 -6
- package/CHANGELOG.md +471 -0
- package/assets/reference.md +38 -6
- package/dist/cli-runtime.mjs +2536 -1332
- package/dist/cli.mjs +4 -1
- package/dist/core.d.ts +172 -420
- package/dist/core.mjs +354 -2697
- package/gemini-extension.json +1 -1
- package/package.json +4 -1
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +38 -6
- package/skills/dbcli/reference.md +38 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,210 @@ All notable changes to dbcli are documented here.
|
|
|
5
5
|
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
|
+
## [4.0.0] - 2026-08-30 - Elasticsearch 的 shell 從來沒有問過 permission,同樣的形狀在 Redis 與 MongoDB 也成立,以及三個沒人比對的版本契約
|
|
9
|
+
|
|
10
|
+
**建議所有把 Elasticsearch 連線交給 AI agent 操作的使用者升級。** `dbcli shell` 連到 Elasticsearch 時,完全沒有檢查連線設定的 permission 等級就把請求送到叢集:`shell.ts` 在到達 SQL 與 Redis 共用的那道閘門之前就分支到 `es-shell.ts`。因此 `permission: query-only` 的連線可以送出 `POST /<index>/_delete_by_query` 清空索引、`DELETE /<index>` 刪掉索引、`PUT /<index>/_mapping` 改寫 schema —— 同樣這些請求走 `dbcli query` 一律會被拒絕。這條路徑也不寫任何 audit 紀錄,所以受影響的人事後無從查證發生過什麼。
|
|
11
|
+
|
|
12
|
+
它可以腳本化:shell 用管線餵入的 stdin 驅動與互動輸入相同的迴圈,所以一個 agent 用單一非互動指令就能做到上述任何一項。
|
|
13
|
+
|
|
14
|
+
影響範圍是所有 Elasticsearch 連線,`1.22`(ES shell 首次出現)起至 `3.0.0` 止。SQL、Redis、MongoDB 的 shell 不受影響 —— 它們走的是有閘門的那一條分支。沒有任何生產事故的紀錄,但這是從缺席推論出來的,而這條路徑本來就不寫 audit,受影響的操作者本來就無從發現。
|
|
15
|
+
|
|
16
|
+
ES shell 那條分支第九輪把「一個比對函式,有沒有把它要比的兩樣東西都正規化?」問到其他引擎上,找到 Redis 四個、MongoDB 兩個 CRITICAL。設計決策記在 ADR-0015。
|
|
17
|
+
|
|
18
|
+
Three numbers in this repository disagreed with reality and nothing checked any of them. The evidence pack and receipt formats still said `version: 1` after v3.0.0 broke both of them, so two mutually incompatible layouts shipped under one version number and an old artifact was reported as a tampered one. Five plugin manifests said `1.51.2` while the package said `3.0.0`. `SECURITY.md` promised fixes for `1.x`. Each is now stated once, derived from one source, and guarded by a check that fails on drift.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **BREAKING(對 `query-only` 與 `read-write` 的 Elasticsearch shell 使用者而言):ES shell 現在套用連線的 permission 等級。** 請求由 `dbcli query` 使用的同一個分類器判定 —— shell 交給它的是真正的 method 與 path,而 `query` 只生得出一個合成的 `_search`。無法證明是文件層級的操作一律落到需要 `admin` 的那一級:`DELETE /<index>`、`DELETE /_all`、`_delete_by_query`、`PUT /_mapping`、`PUT /_settings`、`POST /_aliases`、`POST /_reindex`。拒絕訊息會指出可行的等級,而且請求不會送出。先前能在 `query-only` 下跑這些請求的人,現在會被擋。
|
|
23
|
+
|
|
24
|
+
- **ES shell 的每個請求都寫入 audit,執行或被拒都寫。** side-effect tier 取自該請求的分類結果而非發起它的命令 —— 用命令的能力表來標記是一個已知缺陷,同一個破壞性操作曾因為經由不同命令而被記成三種不同的 tier。
|
|
25
|
+
|
|
26
|
+
- **分類器改讀伺服器實際路由的路徑。** 先前它拿到的是原始文字(含 query string),而黑名單拿到的是去掉 query string 的路徑。分類器裡每個子字串比對因此都會命中攻擊者控制的參數值,而 `filter_path` 是每個端點都接受、且吃任意字串的通用參數:`POST /<index>/_delete_by_query?filter_path=_count` 判成搜尋、`DELETE /<index>?filter_path=_bulk` 判成搜尋、`PUT /<index>/_mapping?filter_path=_bulk` 判成搜尋,三者都在 `query-only` 下實測執行成功。現在 query string 被丟棄、百分比編碼與 dot segment 在分類前解析,欄位也從 `apiPath` 改名為 `rawPath` 並在模組內正規化,呼叫端不可能再傳錯一個。
|
|
27
|
+
|
|
28
|
+
- **比對改為位置感知的路徑區段,不再是子字串。** `_search`、`_count`、`_bulk` 都是合法的文件 id,所以精確區段比對本身也不夠:`POST /<index>/_doc/_search` 是「索引+id」的寫入請求,最後一段卻是 `_search`。`_search` 與 `_count` 只在一段或兩段路徑的端點位置才算數,文件 id 一律不透明、永不參與比對。
|
|
29
|
+
|
|
30
|
+
- **Elasticsearch 的讀取判定維持白名單,並補上讓它不堪用的那幾個形狀。** 新增 `_cat/*`(不含 `_cat/aliases` 與 `_cat/tasks`)、`_cluster/health`、`GET`/`HEAD` 裸索引名稱。其餘一律落到需要 `admin` 的預設 —— 包含任何沒被列上的端點。這個方向是刻意的:白名單漏一項,使用者多付一個不必要的 admin 要求;拒絕集漏一項,使用者拿到一個繞過。記在 ADR-0014。
|
|
31
|
+
|
|
32
|
+
- **`_bulk` 的 body 無法辨識或無法解析時判為 `DROP`。** 先前判 `SELECT`,而 bulk 分支由路徑單獨選中,所以那是一個通用的降級管道。
|
|
33
|
+
|
|
34
|
+
- **路徑必須與 URL parser 產出的字串逐位元組相同,否則一律拒絕**,而拒絕訊息會給出正確的寫法。dbcli 原本有一套自己的「路由後路徑」概念,它在*近似* `fetch` 的行為——而近似的價值等於它最糟的那個缺口。`#` 就是一個:`fetch` 會丟棄第一個 `#` 之後的一切,所以 `POST /_reindex#/_count` 在 dbcli 眼中是兩段的 count、伺服器收到的卻是 `POST /_reindex`,那是任意索引對拷,因此同時也是黑名單繞過——把受保護的索引拷進可讀的索引再正常讀。tab、換行與 `\` 是同一形狀的另外三個缺口。現在改問傳輸層用的同一個 parser,adapter 也改用它組 URL,所以驗證過的字串就是送出的字串。
|
|
35
|
+
|
|
36
|
+
- **`source` query 參數一律拒絕。** Elasticsearch 接受 `source=<json>&source_content_type=...` 取代 request body,而這條路徑上每個 body 側檢查都讀 `req.body` —— 被保護的欄位名稱寫在偷渡的 body 裡時,那個為此存在的檢查完全看不到。參數以精確鍵名比對,`_source`、`_source_includes`、`_source_excludes` 不受影響。
|
|
37
|
+
|
|
38
|
+
- **黑名單欄位名稱在 query string 裡也會被拒絕**,因為 URI search 形式直接在參數裡指名欄位(`?q=password:*`、`?sort=password:asc`、`?docvalue_fields=`),而值會以請求自選的 key 回傳。比對改為看**點分元件**而非整串相等:`password.keyword` 是標準動態 mapping 對每個 `text` 欄位預設產生的 multi-field,而 `params._source.password` 是 Painless script 讀欄位的寫法——受保護的名稱可能落在點分路徑的任一端。回應遮罩套用同一條規則。
|
|
39
|
+
|
|
40
|
+
- **path 與 query 都從同一個 `URL` 解析,不再用 `String.split('?')`。** `split` 會在每一個 `?` 切開,而解構只取第二個元素,所以第二個 `?` 之後的一切都從這些檢查讀到的 query 中消失,adapter 卻拿到完整路徑:`?filter_path=x?&source=<body>` 因此對每一個為了找出偷渡 body 而存在的檢查隱形。逐位元組相等也隨之擴及整個 request target,不再只有路徑。
|
|
41
|
+
|
|
42
|
+
- **引號字串形式的 request body 一律拒絕。** JSON 字串字面值是合法的 body,卻能挾帶 NDJSON 通過每一個只走物件與陣列的檢查 —— 一個 bulk delete 曾因此從無害的路徑名稱抵達黑名單索引。
|
|
43
|
+
|
|
44
|
+
- **`_ingest` 與 `_tasks` 移出 shell 的 unscoped metadata 白名單**:pipeline 定義常內嵌憑證,詳細 task 列表會帶出執行中查詢的 request source。
|
|
45
|
+
|
|
46
|
+
- **server-side script 的攔截點移到傳輸層,shell 不再繞過它。** `assertNoElasticsearchScript` 原本只掛在 `ElasticsearchAdapter.execute()`,而 shell 走的是另一個執行入口 `request()`。因此 `query-only` 可以用 `POST /<index>/_search` 帶 `script_fields` 在叢集上執行任意 Painless,並把黑名單欄位以請求自選的 key 讀回來(欄位遮罩看的是 key 名稱,所以遮不到);`read-write` 則可用 `POST /<index>/_update/<id>` 的 `ctx._source` 直接改文件——同樣的操作走 `dbcli update` 一律被擋。檢查現在放在 `request()`,兩個入口共用。這也推翻了 ADR-0014 原本記下的一個「上限」:`doc['pass' + 'word']` 確實不是任何字面掃描擋得住的,但一律拒絕 `script` 鍵的控制本來就存在於這個倉庫裡。
|
|
47
|
+
|
|
48
|
+
- **shell 在退出前把在飛的請求與 audit 排乾。** `readline` 不會 await `'line'` handler,所以 EOF 的 `'close'` 會在請求還沒回來、audit 還沒寫出時就 `process.exit(0)`:`printf 'DELETE /orders\n\n' | dbcli shell` 會把請求送到叢集而一列紀錄都不留。權限與黑名單檢查都在送出前同步完成,所以檢查會通過、封包會出去,唯一沒發生的就是稽核——也就是原始漏洞報告裡「不留 audit」的那一半。**SQL shell 有同一個缺陷**(它有序列化那一半,缺排乾那一半),一併修正。
|
|
49
|
+
|
|
50
|
+
- **audit 在送出之前先記一筆 attempt,回應之後再記 outcome,而且每一列都說得出操作。** 只在回應後寫的紀錄描述不了一個沒有回來的請求:`_delete_by_query` 在 client 端逾時會 abort socket 而叢集把刪除做完,執行中被 SIGTERM 則連一列都沒有。同時每列補上 `<METHOD> <routed path>`——先前 `DELETE /orders`、`POST /orders/_update_by_query`、`PUT /orders/_mapping`、`POST /orders/_close` 產生的是四列一模一樣的紀錄。
|
|
51
|
+
|
|
52
|
+
- **只設定 `blacklist.columns`、沒設定 `blacklist.tables` 時,指不出索引的路徑不再被放行。** 整段黑名單檢查原本以 `tables` 是否為空決定要不要跳過,連帶跳過了「路徑指不出索引就拒絕」那一道守門員——而那道守門員才是擋住 `_sql`、`_mget`、`_search/scroll` 的東西。`POST /_sql` 配 `SELECT * FROM users` 會把受保護欄位的值原文放在 `rows` 陣列裡回傳,而欄位名只出現在 `columns[].name` 的值裡、不是 key,所以回應遮罩結構上救不回來。
|
|
53
|
+
|
|
54
|
+
- **query string 的欄位比對補上 Lucene 語法字元。** 切詞器只切 `[\s,:()"'[\]{}]`,於是 `?q=+password:hunter2` 通過而 `?q=password:hunter2` 被拒。`+`、`-`、`*`、`!`、`^`、`~`、`|`、`/`、`\` 一併視為分隔字元。
|
|
55
|
+
|
|
56
|
+
- **搜尋的 size 上限改讀路由後的路徑。** 原本用 `path.includes('_search')` 判斷,所以 `PUT /<index>/_doc/_search`(id 剛好叫 `_search` 的寫入)與 `?routing=_search` 都被當成搜尋,把一個使用者從未輸入的 `size` 欄位寫進文件。這是檔案裡最後一處對原始路徑做子字串比對的地方。
|
|
57
|
+
|
|
58
|
+
- **`dbcli audit tail` 與 `audit show` 的輸出逃脫控制字元。** ES shell 的 audit target 來自路徑,`%0A` 解碼後是真正的換行,因此一列紀錄能在表格輸出裡長成兩列、其中一列是偽造的。JSONL 檔本身不受影響。
|
|
59
|
+
|
|
60
|
+
- **連線失敗訊息裡的 URL 帳密會被遮蔽。** `nodes` 常寫成 `https://elastic:hunter2@host:9243`,而該字串會整串進入 audit 的 error 欄;`redactSensitive` 原本只認 `keyword=value` 形式,一個字元都吃不到 URL 的 userinfo。
|
|
61
|
+
|
|
62
|
+
- **server-side script 的比對改看形狀,不是兩個字面名稱。** 上一項把檢查點搬到傳輸層,但它認得的名字只有 `script` 與 `script_fields`,而 `scripted_metric` 聚合把四個 script 槽拼成 `init_script`、`map_script`、`combine_script`、`reduce_script`。`query-only` 因此仍可跑任意 Painless,並把黑名單欄位以 `aggregations.<name>.value` 這個請求自選的 key 讀回來。現在任何等於 `script`/`script_fields` 或以 `_script` 結尾的鍵都算——**一份名字清單擋不住一個會自己組名字的 API**。掃描另外加上深度上限,`'['.repeat(100000)` 這種合法 JSON 先前會讓它以 `RangeError` 收場。
|
|
63
|
+
|
|
64
|
+
- **黑名單欄位名含 `-`、`*`、`|`、`/` 時重新擋得住。** 上一項把 Lucene 運算子加進 query string 的分隔字元集,卻讓 `user-password` 被切成 `user` 與 `password`——兩者都不在黑名單裡。ES 的欄位名本來就允許這些字元,而 `?sort=user-password:asc` 會把值原樣放在 `hits.hits[].sort`,遮罩摸不到。現在保守與加寬兩套切法都跑、取聯集:多切一次只多幾個不命中的 term,少切一次會漏掉一個受保護欄位。
|
|
65
|
+
|
|
66
|
+
- **shell 的 block 在排入佇列的當下取快照。** 上一項把 `rl.on('line', async ...)` 改成 `queue.enqueue(submit)`,而 `submit` 是在任務跑起來時才讀共用的 `blockLines`;readline 會把管線送來的行在同一個 tick 全部同步發完,於是兩個命令被合併成一個 block,`parseEsRequest` 解析失敗,**兩個命令一個都沒送出**,audit 零列,exit code 仍是 0。互動模式下則是還沒打空行提交的內容被當成前一筆的 body 送進叢集。
|
|
67
|
+
|
|
68
|
+
- **`attempt` 那一列不再宣稱成功。** 它原本硬寫 `success: true`,於是「還沒送出」與「送出並成功」在紀錄裡長得一樣——包含被傳輸層擋下、從未離開行程的請求——而且讓每個操作的成功計數翻倍。現在一律 `false`,真相由 `outcome` 那一列說。
|
|
69
|
+
|
|
70
|
+
- **`audit tail --for-agent` 與 `--brief` 保留 statement 與 phase。** brief 原本只留 `ts`/`command`/`target`/`success`,所以「一列 audit 要說得出對誰做了什麼」只在 `audit show --no-brief` 修好了,agent 讀到的仍是兩筆一模一樣的紀錄。`audit tail` 的表格另外新增 `statement` 欄位。
|
|
71
|
+
|
|
72
|
+
- **Elasticsearch 的 audit statement 不再套用 SQL 字面值遮罩。** `redactSql` 會把數字換成 `0`,於是 `DELETE /orders/_doc/12345` 記成 `DELETE /orders/_doc/0`、`POST /logs-2026.08.30/_delete_by_query` 記成 `POST /logs-0.0/...`——操作對象被遮罩吃掉。ES 的 statement 是路徑不是語句,改用一般的敏感字串遮罩。
|
|
73
|
+
|
|
74
|
+
- **audit 表格的 cell 逃脫涵蓋非 C0 控制字元。** U+202E(RTL override)會讓該 cell 之後整段以右到左顯示,tier 與 success 欄可被視覺調換;U+2028/U+0085 在許多終端機裡同樣算換行。這是零權限的日誌注入——被 blacklist 拒絕的請求照樣寫紀錄,而 target 是攻擊者選的字串。
|
|
75
|
+
|
|
76
|
+
- **URL 帳密的遮蔽貪婪到最後一個 `@`。** 密碼裡含字面 `@` 時(`https://elastic:p@ssw0rd@host`)先前只遮到第一個,尾巴留在紀錄裡。
|
|
77
|
+
|
|
78
|
+
- **搜尋的 size 上限與分類器讀同一個路徑函式。** 先前 cap 不看 method 且讀解碼後的路徑,分類器看 method 且讀原始路徑,於是 `PUT /orders/_search` 與 `POST /orders/%5Fsearch` 上兩者給出不同答案。都不可利用,但「同一個請求、兩個函式、兩種答案」是前幾輪 CRITICAL 的形狀。
|
|
79
|
+
|
|
80
|
+
- **`wrapper` query 一律拒絕。** 它帶的是 base64 編碼的 query,伺服器解碼後執行,而所有 body 側檢查都只走物件的鍵、碰不到字串內部。裡面可以放 `function_score.script_score`,於是黑名單欄位的數值原文會以每筆 hit 的 `_score` 回來——那不是受保護的鍵名,回應遮罩不會動它;黑名單詞比對也看不到 base64 裡的欄位名。這與已經拒絕的字串形式 body 和 `?source=` 是同一個原則:**dbcli 檢查不了的編碼 body 不放行**,解碼一種編碼只會邀請下一種。
|
|
81
|
+
|
|
82
|
+
- **含 `.` 的黑名單欄位名重新生效。** `namesProtectedField` 先比整串相等,再把 term 拆成單一元件比對——而拆出來的元件永遠不含 `.`,所以 `blacklist.columns` 寫成 `user.password` 對整個檢查毫無作用。同一個函式也是回應遮罩的判斷,於是請求端放行 `?docvalue_fields=user.password.keyword`、回應端原樣返回 `_source.user.password`。ES 的 object field 一律以點分名稱呈現,那是最自然的設定寫法。比對改為**連續的點分元件區段**,遮罩則帶著走過的鍵路徑,才比對得到巢狀呈現的回應。扁平欄位名的行為完全不變——這正是這個缺陷七輪沒被發現的原因,每個測試用的都是扁平名稱。
|
|
83
|
+
|
|
84
|
+
- **`_script` 後綴的比對縮回 `scripted_metric` 底下。** 無條件的後綴規則會讓 `deploy_script`、`build_script` 這種一般欄位名在 `query-only` 的唯讀查詢上被拒絕,訊息還說它「executes script code on the cluster」——`term`/`match`/`range`/`sort`/`exists` 都把欄位名放在鍵的位置。`scripted_metric` 是唯一內層沒有字面 `script` 鍵的聚合,其餘 script 載體都已被第一條規則接住。
|
|
85
|
+
|
|
86
|
+
- **編碼後才出現的 `..` 一律拒絕。** `%2F` 原封不動通過位元組同一性檢查,但 `normalizeEsPath` 會先解碼再讓 `..` 刪掉前一段,跨過一個伺服器根本不存在的段界。`GET /secrets%2F..%2Fpublic/_search` 因此讓 `secrets` 從路徑區段檢查、index 抽取與 audit 三處同時消失。ES 是否解析得出那個 index expression 未經驗證——那正是拒絕而非正規化的理由。
|
|
87
|
+
|
|
88
|
+
- **`exit` 之後排在佇列裡的命令不再執行。** `'line'` handler 在同一個 tick 把管線的所有行 enqueue 完,所以 `rl.close()` 執行時後面的 block 早已在鏈上,而 `'close'` 的排乾語意是「全部跑完」——`printf 'exit\n\nDELETE /orders\n\n'` 會把索引刪掉。兩個 shell 都補上關閉旗標。
|
|
89
|
+
|
|
90
|
+
- **只有空白的行屬於 block 的內容,不是它的結尾。** 提交的判斷原本是 `trim()` 後為空,於是編輯器留下的空白會把 block 截斷、前半段以一個**沒有 body** 的請求送出——而 `POST /_update_by_query` 沒有 body 是合法的、作用範圍是整個索引,且 audit 寫下的字串與使用者本來要送的那筆逐字相同。改成只在真正的空行提交:反方向的代價是分隔行帶空白時命令會黏成一塊而解析失敗,但那個失敗可見且什麼都不會送出。
|
|
91
|
+
|
|
92
|
+
- **shell 的錯誤訊息逃脫控制字元。** 訊息內嵌使用者寫的路徑,而 `ESC[2K ESC[1G` 會清掉整行並把游標移回行首,用後續字元蓋掉「Refused」,讓操作者看到一則自己寫的假成功訊息。audit 檔與 `audit tail` 早已處理這件事,唯獨 shell 自己的 stderr 沒有;逃脫邏輯抽成共用的 `escapeControlCharacters`。
|
|
93
|
+
|
|
94
|
+
- **BREAKING(對用管線或腳本驅動 ES shell 的呼叫端而言):ES shell 的退出碼反映失敗。** 先前一律 `exit(0)`,所以 `dbcli shell < script.txt` 的呼叫端分不出「全部成功」與「一條都沒跑」——權限拒絕、blacklist 拒絕、strict-audit 拒絕全部只印一行紅字。**這會改變既有腳本的行為**:一個 session 內只要有任何一個請求失敗,`dbcli shell` 就以 `1` 結束,先前依賴它一律成功的 CI 步驟會開始紅。記在 ADR-0014 Decision 10。
|
|
95
|
+
|
|
96
|
+
- **不含欄位名的 query 參數不再進入黑名單切詞器。** `?routing=abc-name-1` 在黑名單欄位叫 `name` 時被誤擋,而 `routing`/`scroll`/`preference`/`filter_path` 的值沒有任何欄位名語意。
|
|
97
|
+
|
|
98
|
+
- **i18n 插值不再讓值改寫訊息。** `MessageLoader.interpolate` 用的是 `String.replace(regex, value)`,而替換字串會展開 `$&`、`$'`、`` $` `` 與 `$1`。這條分支第一次把**操作者可控**的字串餵進去(ES shell 的路徑、命中的 index expression、被拒的欄位名),於是拒絕訊息以及由它組成的 audit `error` 欄位可以被被拒的那個人部分改寫:`GET /sec$&rets/_search` 記下來的索引名是 `sec{index}rets`,`$'` 則會把整句複製一份接在後面。改成單次掃描 `\{name\}` 加替換函式,順帶修掉「依序替換讓值裡的 `{other}` 被二次替換」。所有 `t_vars` 呼叫端一併受惠。
|
|
99
|
+
|
|
100
|
+
- **ES shell 的訊息改走 i18n。** `src/commands/es-shell.ts` 的 i18n 呼叫數是 0,而同一層的 `src/commands/shell.ts` 是 14——CONTRIBUTING.md 明文寫「All user-facing messages must be translatable」,所以那是違規不是偏好。19 則訊息移進 `shell.es.*`,`blacklist table add` 這次新增的萬用字元拒絕訊息一併移進 `blacklist.refuse_wildcard`。`BlacklistRejection: ` 這個前綴刻意留在程式碼裡不翻譯:recovery 路徑與數個測試比對的是它,翻譯過的前綴是壞掉的比對器,不是翻譯過的訊息。
|
|
101
|
+
|
|
102
|
+
- **`es-shell.ts` 拆成兩個檔案。** 894 行、`runEsRequest` 363 行,對照 CONTRIBUTING.md 的 800 與 50。成因寫在 ADR-0014 裡:九輪,每一輪都往同一個函式再塞一個檢查。切線沿著檢查本來就有的分界——`es-shell-guards.ts` 放「關於這個請求」的純函式(伺服器會路由到哪、指名了哪些索引與欄位、回應要遮掉什麼),不讀設定、不開連線、不寫 audit;session 檔留下讀取迴圈、tier gate 的呼叫、audit 接線與退出碼。行為沒有改變,5873 個測試全過。
|
|
103
|
+
|
|
104
|
+
- **`audit.strict` 的強制點改在「效果發生前」的稽核寫入。** 先前只有 ES shell 讀這個鍵,但它放在全域 `audit` 區塊、文件也寫得像全域開關:設了 `strict: true` 再把 audit 目錄設成不可寫,`dbcli delete` 照樣執行。現在 ES shell 送出請求前那一列與 SQL 寫入閘門的決定紀錄都走 `writeAuditEntryBeforeEffect`。事後才寫的紀錄不在範圍內——那時拒絕擋不回任何東西,只會把一次已完成的操作回報成失敗。同時:`enabled: false` 配 `strict: true` 在讀設定時就失敗(那組合的語意是「不記錄、也不擋」);`dbcli audit health` 印得出 strict 的狀態;沒有 sink 或 sink 回 `null`(舊版 `writeAuditEntry` 的失敗形狀)在 strict 下都算失敗。
|
|
105
|
+
|
|
106
|
+
- **`recover` 與 `inspect` 的 audit 摘要跟上 statement 與 phase。** 第六輪修好了 `audit tail` 的 brief,但沒動 `briefifyForRecent`,於是這兩條路徑上一次成功的請求仍呈現為兩列只差 `success` 的紀錄。`topQueriedTable` 另外不再把 attempt 列重複計數,也不再把 `/_cat/indices` 這種路由路徑當成「最常查詢的資料表」。
|
|
107
|
+
|
|
108
|
+
- **黑名單條目本身可以是萬用字元、逗號清單、`_all`、帶前後空白。** `indexExpressionReaches` 原本只展開**請求端**(逗號、萬用字元、date math、CCS、百分號編碼),把黑名單條目當純字面字串比對——於是 `blacklist.tables: ["secrets*"]` 對 Elasticsearch 完全無效,而 `["*"]` 這個讀起來像「全面封鎖」的寫法是零保護。加重因素是:**同一個 `blacklist.tables` 陣列在 Redis 連線上就是以 glob 執行的**,使用者文件也明文教 `dbcli blacklist table add 'secrets:*'`。依文件寫下的設定,在 Redis 擋、在 ES 靜默放行。`dbcli query --index` 走的 `checkIndexBlacklist` 與欄位遮罩的 `filterColumnsForIndexExpression` 有同樣的不對稱,一併修正。
|
|
109
|
+
|
|
110
|
+
- **`dbcli blacklist table add` 接受真實的 Elasticsearch index 名。** 驗證規則原本是 SQL 識別字的形狀(`^[a-zA-Z_][a-zA-Z0-9_]*$`),拒絕 `my-index`、`logs-2026.08.30`、`.kibana`,等於 ES 使用者只能手編設定檔——而手編正是最容易把條目寫成 glob 的路徑,直接餵養上面那個缺陷。
|
|
111
|
+
|
|
112
|
+
- **拼錯或用 ES 詞彙寫的黑名單不再被靜默忽略。** zod 預設剝掉未知鍵,所以 `blacklist.indices`、`blacklist.fields`,以及寫在**連線層級**的 `blacklist`(它只有頂層一份),解析後都是空黑名單且沒有任何警告——使用者看著設定檔以為有保護。這幾種形狀現在是解析錯誤。沒有改成全域 `.strict()`:那會拒絕無害的額外鍵。
|
|
113
|
+
|
|
114
|
+
- **`blacklist add/remove` 不再把 v2 多連線設定壓成 v1。** 它讀設定走的是 v1 路徑(對 v2 檔案回傳「選中那條連線」的扁平化結果),寫回時以 v1 schema 整包覆寫。加一條黑名單因此會讓 `connections`、`default`、`envFile`、`environment` 全部消失,**而預設 permission 變成當時選中那條連線的值**——ES shell 的 tier gate 讀的正是它。整個過程走 `writeConfigWithIntegrity`,完整性紀錄同步更新,事後沒有 tamper 訊號。
|
|
115
|
+
|
|
116
|
+
- **根層 `--config` 對 blacklist 指令生效。** 每個子指令自己宣告了一個**帶預設值**的 `--config`,所以 commander 永遠不會回落到根層那個:`dbcli --config /path blacklist table add x` 會改到 `.dbcli` 而不是 `/path`,並且回報成功。
|
|
117
|
+
|
|
118
|
+
- **百分號編碼的路徑段不再改變分級。** `new URL().pathname` 不解百分號編碼,而 Elasticsearch 對路徑參數會解,於是 `GET /%2A` 是 `query-only` 的讀取而 `GET /*` 需要 `admin`——同一個請求兩個 tier,正是 `isBareIndexSegment` 的註解明文禁止的情形。路徑段現在在 `routedSegments` 解碼一次,且**不重新切分**:解碼後的 `/` 留在原段內,重新切分會複製第七輪修掉的 `%2F..%2F` 缺陷。
|
|
119
|
+
|
|
120
|
+
- **無 index 的 metadata 白名單細到子資源。** 它原本只比對第一段,於是整個 `_cluster` 與 `_cat` 前綴放行——而 `_cluster/state` 回傳 `metadata.ingest.pipeline[]`(`_ingest` 被移出白名單的理由就是它常內嵌憑證)、`metadata.stored_scripts`、以及黑名單索引的完整 mapping;`_cat/tasks` 回傳執行中查詢的 source(`_tasks` 被移出的理由)。`_nodes/stats`、`_nodes/settings`、`_nodes/hot_threads` 同樣扣住。
|
|
121
|
+
|
|
122
|
+
- **search template 端點一律拒絕。** template 的 `source` 是一段在叢集上渲染成完整 search body 的字串,stored template 的內容根本不在請求裡——所以指向黑名單索引的 terms lookup 對每一個 body 側檢查都是隱形的。與 `wrapper` 同一個原則。
|
|
123
|
+
|
|
124
|
+
- **黑名單條目的前後空白不再讓它變成死設定。** ES 的 index 名與欄位名都不能帶空白,所以 `[" secrets "]` 保證無效,而先前沒有任何提示。
|
|
125
|
+
|
|
126
|
+
- **audit 的 `target` 不再能由 SQL 註解或字串字面值指定。** 它原本走一條不剝註解、不剝字串的 regex,只取第一個 `FROM|INTO|UPDATE` 之後的識別字,所以 `/* FROM audit_decoy */ DELETE FROM users` 會以 `audit_decoy` 入帳。`target` 正是稽核者事後篩選用的主要欄位——找不到的紀錄與不存在的紀錄,對追查是同一件事。改成掃描:剝掉註解(含 PostgreSQL 的巢狀區塊註解)與字串字面值,保留引號識別字,並只在括號深度 0 比對關鍵字。順帶修好兩個日常誤記:`DELETE FROM public.users` 記成 `public`(schema 名)、`SELECT EXTRACT(MONTH FROM created_at) FROM salaries` 記成 `created_at`(欄位名)。
|
|
127
|
+
|
|
128
|
+
- **經 `query` 執行的 DML 記成 `db-write` 而非 `readonly`。** `writeAuditEntry` 在沒拿到 `sideEffectTier` 時取命令的能力等級,而 `query` 的能力是 `readonly`。於是 `DELETE`/`UPDATE`/`INSERT`/`CREATE TABLE AS` 全部以 `readonly` 入帳,而被寫入閘門**拒絕**的 `DROP`/`TRUNCATE` 由 `recordGateDecision` 帶著 `db-write`——以 tier 篩選破壞性操作會找到被擋下的那些、漏掉真正發生的那些。「讀」的判定沿用既有的 tier 語意(在 `query-only` 下被允許的就是讀),而不是另外維護一份唯讀型別清單。
|
|
129
|
+
|
|
130
|
+
- **`blacklist table add` 對 SQL 與 MongoDB 連線拒絕萬用字元條目。** 上一版為了 Elasticsearch 與 Redis 的名稱放寬了字元集,但沒有問「這個寫法對這個引擎有沒有意義」:那兩個引擎的黑名單比對是字面相等,`secret*` 這種條目永遠不會命中,而 CLI 回報成功。錯誤訊息會說出原因,不只是「名稱非法」。
|
|
131
|
+
|
|
132
|
+
- **BREAKING(對 `blacklist.tables` 設了 glob 的 Redis 使用者而言):`insert`/`update`/`delete` 現在也套 glob 黑名單。** 這三條只經過 `BlacklistValidator.checkTableBlacklist`(字面相等,不懂 glob),而 `RedisAdapter` 的三個方法一次 `checkKeyArgs` 都沒有。於是 `dbcli blacklist table add 'secrets:*'` 之後 `dbcli delete secrets:api_key` 照刪——而 `'secrets:*'` 正是使用者文件教的寫法,只有寫成完整字面 key 才擋得住。檢查改掛在 adapter 邊界。先前能刪改這些 key 的腳本現在會被擋。
|
|
133
|
+
|
|
134
|
+
- **`redis.mask` 對 `query`/`list`/`schema`/`insert`/`update`/`delete` 完全無效。** mask rules 是 `createRedisAdapter` 的第三個選擇性參數,八個呼叫端裡六個沒傳。文件明寫 `dbcli query "GET secret:api_key"` 回 `{"value":"[REDACTED]"}`,實際回明文。`export` 與 `shell` 有傳,所以任何人想驗證這個功能時它都是好的。factory 改為接整個 config。
|
|
135
|
+
|
|
136
|
+
- **指令表與權限白名單的落差不再是黑名單繞過。** `checkKeyArgs` 拿不到 command spec 時 fail-open,而 32 個被權限放行的指令沒有 spec:`LPOP secrets:list` 在 `read-write` 下把黑名單 key 的值取出來兼銷毀,`XRANGE secrets:stream - +` 在 `query-only` 下讀得到。改為 fail-closed(只在有設黑名單時生效),補齊 32 個 spec,並加契約測試釘住兩張表的關係。`RedisCommandSpec.permissionTier` 直接移除:它是一份沒人強制、已在五個指令上分歧的副本。
|
|
137
|
+
|
|
138
|
+
- **`SCAN` 不再列舉得出黑名單 key 名。** `SCAN 0 MATCH secrets:*` 只要 `query-only` 且完全不被檢查,而 `KEYS secrets:*` 要 `admin` 且被擋——低權限那條路才是通的。MATCH 現在會被找出來(不限位置、大小寫不敏感)並比對重疊;而只修 MATCH 擋不住裸 `SCAN 0`,所以 `SCAN` 與 `KEYS` 的回應也會把受保護的 key 名濾掉。cursor 不動。
|
|
139
|
+
|
|
140
|
+
- **MongoDB 的整份文件轉移也擋得住。** 底下那則「`blacklist.columns` 擋得住換名了」只擋「請求裡指名了受保護欄位」,而 `[{"$project":{"all":"$$ROOT"}}]` 一個欄位名都沒提就把整份文件放到自選的鍵底下回傳,遮罩比對的是完整路徑 `all.password`、對不上錨定在 `password` 的規則。`{"$objectToArray":"$$ROOT"}` 更徹底:文件變成 `[{k:"password",v:"p1"}]`,受保護的名字成了**值**,任何看鍵名的遮罩結構上都追不到。`$replaceRoot`/`$replaceWith` 把子樹提到頂層,同樣讓 `user.password` 這種規則失去指涉。這些現在一律視為「一次指名了所有受保護欄位」;`$getField` 的 `field` 若是運算式而非字串常數也一併拒絕——dbcli 判斷不出它會解析成什麼名字,就不轉發。`$$ROOT` 原本**有**被處理,但是當成「要剝掉的前綴」(為了讓 `$$ROOT.password` 命中),單獨出現時被讀成一個叫 `ROOT` 的路徑。
|
|
141
|
+
|
|
142
|
+
- **`SCAN` 的 `MATCH` 改為檢查每一個出現位置。** Redis 解析選項是後者覆寫前者,所以 `SCAN 0 MATCH benign:* MATCH secrets:*` 真正送出去的是 `secrets:*`,而只讀第一個等於檢查了一個從未送出的 pattern。回應過濾本來就會把 key 濾掉所以沒有洩漏,但「指名就拒絕」這條規則在這裡沒有成立。
|
|
143
|
+
|
|
144
|
+
- **`filterReturnedKeyNames` 對認不得的回應形狀改為 fail-closed。** 原本原樣轉發,與同一批修補在 `checkKeyArgs` 選的預設相反。非字串的 key 同樣丟棄——無法與 glob 比對的東西,回答不了「這個受不受保護」。
|
|
145
|
+
|
|
146
|
+
- **`MongoDBAdapter.insert` 補上兩個攔截點。** 它既沒有 `assertNoMongoServerSideScript` 也沒有欄位檢查,而 #47 的註解宣稱所有路徑一致受檢。實際蓋住它的是 `insert.ts` 的 `checkColumnBlacklistOnWrite`——掛在呼叫端的控制,正是 ADR-0015 Decision 1 要移除的安排。
|
|
147
|
+
|
|
148
|
+
- **`$lookup` 帶進來的 collection 名改為大小寫不敏感比對。** 請求側是精確比對、遮罩側 (`findCaseInsensitive`) 不敏感,於是同一份設定在遮罩生效、在請求側拒絕不生效。
|
|
149
|
+
|
|
150
|
+
- **MongoDB 的 `blacklist.columns` 擋得住換名了。** 遮罩只看回傳文件的鍵名,而 aggregation 自己決定那些鍵名:`$project:{"leak":"$password"}`、`$addFields`、`$set` 都把值搬到別的鍵下原文回傳,`query-only` 即可。`$group:{"_id":"$password"}` 更是保證出口——`_id` 為了保住文件參照而被無條件豁免。回應側追不完,所以改成**請求側拒絕**:請求裡指名受保護欄位就拒絕,與 Elasticsearch 的 `namesProtectedField` 同一個形狀,包含同樣的過度拒絕(值剛好等於受保護欄位名也會被拒)。檢查掛在 `assertNoMongoServerSideScript` 旁邊,那是所有 MongoDB 路徑本來就共用的攔截點。
|
|
151
|
+
|
|
152
|
+
### Added
|
|
153
|
+
|
|
154
|
+
- **`audit.strict` 設定(預設 `false`)。** 開啟時,送出前那一列 audit 寫不出去就拒絕執行請求。audit 一直是 best-effort——磁碟滿、目錄不可寫、lock budget 耗盡(可被刻意耗盡)時操作照樣執行、零紀錄,只有一行 stderr 警告,而管線模式通常看不到。對多數指令這是對的取捨;但 ES shell 這條路徑上 audit 就是控制本身,而先前連相反的取捨都無法表達。只管送出前那一列:`outcome` 寫不出去時請求已經在叢集上了。
|
|
155
|
+
|
|
156
|
+
- **`bun run manifest:check` (and `manifest:sync`) makes manifest drift fail the build.** It checks every manifest's version against `package.json`, the plugin name, that declared skill directories and entry files exist, that the root and portable copies agree, that the marketplace listing names the plugin, and that `SECURITY.md`'s supported major matches the package. Blocking in CI's drift job and in `release:check`. `tests/unit/scripts/plugin-manifest-contract.test.ts` breaks one thing at a time in a scratch tree and asserts the script goes red, because a guard only ever run against a correct tree is how five manifests drifted through a green build.
|
|
157
|
+
|
|
158
|
+
- **Frozen legacy artifact fixtures under `tests/fixtures/evidence-legacy/`.** Six files produced by the code that actually shipped — v2.1.0's builder and v3.0.0's — never regenerated. A test that builds its own "legacy" input proves the builder agrees with itself, which is precisely the check that would have caught none of this.
|
|
159
|
+
|
|
160
|
+
- **CI runs a dependency audit and installs from a frozen lockfile.** `bun audit` ran only inside `release:check`, by hand, shortly before a release; it is now its own blocking job on every push. Every job installs with `--frozen-lockfile`, and the workflow declares `permissions: contents: read`.
|
|
161
|
+
|
|
162
|
+
### Changed
|
|
163
|
+
|
|
164
|
+
- **BREAKING:`@carllee1983/dbcli/core` 不再匯出 `AdapterFactory`。** 它回傳的 adapter 的 `request()` 是 public,所以任何函式庫使用者都能拿到一條不經 permission、不經 blacklist、不寫 audit 的路徑。`QueryExecutor` 與 `DataExecutor` 保留——它們自己帶著閘門。CLI 使用者不受影響。
|
|
165
|
+
|
|
166
|
+
- **`_update_by_query` 從 `read-write` 收緊為 `admin`**:它是獨立的區段,精確比對之下落到破壞性預設,而它確實會改寫索引裡的每一份文件。
|
|
167
|
+
|
|
168
|
+
- **BREAKING (artifact format): evidence packs and receipts are `version: 2`, and the format version is no longer tied to the package version.** v3.0.0 changed the pack digest input, the id derivation, the `coverage` field and the receipt `observation` while leaving both constants at `1` — ADR-0012 decided to amend the schema in place, which was right about the repairs and wrong about the number. A reader now classifies an artifact by version *and* structure before computing anything: `v1-coverage` (2.1.0 and earlier), `v1-untagged-v3` (3.0.0), current, or unsupported. Both legacy formats stay readable and integrity-checkable through frozen reimplementations of their own digests, and neither is ever `trust: "current-valid"`. There is no migration and cannot be one — a pack's id derives from its digest, so rewriting an old pack would mint a new artifact wearing an old one's provenance, and a v2.1.0 receipt's hashed `observation` can only be "recovered" by inverting it, which is a guess and not a record. Recorded in `docs/adr/0013-evidence-artifact-format-versions-are-independent-of-the-package-version.md`, amending ADR-0012.
|
|
169
|
+
|
|
170
|
+
- **`evidence validate --format json` distinguishes three answers where it used to give one.** `status` is `current-valid`, `current-references-expired`, `recognized-legacy`, or `unsupported`, with `trust` stating plainly whether the pack may be relied on; `recognized-legacy` also carries `legacyFormat`, `producedBy`, and that format's own `integrity`. An unknown version fails closed. Previously every one of these arrived as `evidence pack digest mismatch`, which tells the holder of a two-week-old file that someone tampered with it.
|
|
171
|
+
|
|
172
|
+
- **`SECURITY.md` documents the major line that actually exists.** It listed `1.x` as supported against a `3.0.0` package — a security document making a false promise about which releases get fixes. `3.x` is supported; `2.x` and `1.x` are explicitly not, with no backport commitment invented to soften it.
|
|
173
|
+
|
|
174
|
+
- **Plugin and extension manifests carry the package version.** `.claude-plugin`, `.codex-plugin`, `.cursor-plugin`, `gemini-extension.json` and the portable `plugins/dbcli-agent` copy all read `1.51.2` — two majors behind what a user installing from a marketplace would receive.
|
|
175
|
+
|
|
176
|
+
## [3.0.0] - 2026-08-16 - Evidence that could not reproduce itself, and a hash that hid nothing
|
|
177
|
+
|
|
178
|
+
The evidence subsystem shipped in v1.53.0 and, until this week, nobody had composed a pack outside its own tests. The first real use — a `verify safe-backfill --after-write` against a live PostgreSQL — came back `not_verified` on data that was correct, and the audit that followed found three more defects of the same kind: an evidence pack whose digest covered a random UUID, so the same claims never produced the same pack twice; a receipt "fingerprint" that was an unsalted SHA-256 over eight possible values; and a blacklist comparison with no identifier boundaries, so a protected column named `id` refused any claim containing the word "identifier". Fixing them changes both published formats, which is what makes this a major release: **packs written by 2.x will fail validation under 3.0.0, and `observation.fingerprint` no longer exists.** The reversal that authorized the repairs — known defects get fixed whether or not anyone is using the code — is recorded in `docs/adr/0012-known-defects-get-fixed-whether-or-not-anyone-is-using-the-code.md`, superseding ADR 0011.
|
|
179
|
+
|
|
180
|
+
### Changed
|
|
181
|
+
|
|
182
|
+
- **BREAKING: an evidence pack's digest now covers only its content, so equivalent input produces the same pack.** The digest was taken over the whole pack including `id`, which was `evp_${randomUUID()}`, and a millisecond `createdAt` — so composing the same claims twice yielded two unrelated digests and cross-run comparison was not merely hard but undefined. The digest now covers `version` / `subject` / `claims`; `id` is derived from its first 32 characters, so equivalent input yields an identical pack down to the identifier, and `parse` checks that the two agree. `createdAt` sits outside the digest and is documented, in the type and in `reference.md`, as the one field that can be restamped without breaking verification — leaving that unsaid would be selling tamper-evidence that isn't. `canonicalizeWithoutDigest` was `JSON.stringify`, so "canonical" rested on the build and parse paths hand-maintaining the same key insertion order; it is now a real canonicalization that sorts keys recursively. The format is changed in place with no v2 fallback and no compatibility layer: existing packs fail validation, which is correct, because their digests were computed under rules that no longer hold (#116).
|
|
183
|
+
|
|
184
|
+
- **BREAKING: a receipt states its observation instead of hashing it.** `observation.fingerprint` was an unsalted SHA-256 over a preimage space of eight values for `verify` (four `VerificationStatus` × an `artifactPersisted` boolean) and 2^(n+1) for an `assert` with n checks, in a fixed public serialization — a dictionary attack measured in milliseconds. Everything it covered already appeared in plaintext in `outcome`, so the only thing it protected was the per-check pass bit pattern, and the only reader it stopped was an honest one; being deterministic, it also let receipts be grouped by result across files, which is the property the old test suite was asserting. `verify` now records `{ kind: 'verify-outcome', status }` and `assert` records `{ kind: 'assert-verdict', checksPassed, checksTotal }` — counts, not positions, so *which* check failed still does not leave the receipt, and less leaks than before, since anyone willing to invert the old hash recovered the full bit pattern. `parseObservation` validates the field shape per operation and rejects `checksPassed > checksTotal` (#118).
|
|
185
|
+
|
|
186
|
+
- **`evidence` no longer means three incompatible things.** `VerificationStatus` (`verified` / `not_verified` / `indeterminate` / `blocked`), `EvidenceItem.status` (`ok` / `no-data` / `skipped` / `error` / `timeout`) and `WorkloadEvidence.state` (`available` / `absent` / `invalid` / `unavailable`) are a verdict on a subject, whether a diagnostic ran, and whether a source file is usable — three vocabularies that cannot be mapped onto each other, all filed under one word, all string unions the type system cannot keep apart. `EvidenceItem` is now `ReportFinding` and `WorkloadEvidence` is `WorkloadSource` (with `WorkloadEvidenceState`, `LoadWorkloadEvidenceOptions`, `loadWorkloadEvidence` and the command layer's `loadObservedWorkloadEvidence` renamed to match). The values are unchanged and `VerificationStatus` is untouched, being part of a published JSON contract. `CONTEXT.md` gains an "Outcome vocabularies" section stating that they do not map — but the rename is what stops the mistake at the call site, since nobody reads `CONTEXT.md` while writing a comparison (#114).
|
|
187
|
+
|
|
188
|
+
### Added
|
|
189
|
+
|
|
190
|
+
- **`scripts/check-plan-acceptance.ts` makes an acceptance criterion say whether anything proved it.** The 2026-08-08 backlog's eight tickets shipped with v1.53.0 and still read `Status: Proposed`, and two of their criteria described behavior the code structurally could not produce. Nothing caught either, because plan documents are prose and prose drifts silently. Every numbered acceptance criterion under `docs/plans/*.md` must now end in `— covered by:` (naming a test file that exists), `— unverified:` (admitting nothing proves it), or `— known deviation:` (deliberately unmet, with a reason). The gate forces disclosure, not coverage — marking all thirty criteria `unverified` would pass — because a gate demanding real tests gets nagged into a rubber stamp, while the count stays visible in review, and eighteen of thirty unverified is its own pressure. The one substantive check is that a cited test file exists, that being the half that is cheap to check and the failure mode the gate exists to stop; prose criteria with no numbered list count as a violation, so reformatting cannot route around it. `docs/plans/done/` is not scanned, since rewriting closed plans to suit a later convention destroys the record this protects. `PLAN_ACCEPTANCE_EXEMPTIONS` is a ratchet shaped after `check-core-no-stdout.ts`: it may only shrink, and a contract test fails when an entry stops being needed (#113).
|
|
191
|
+
|
|
192
|
+
### Removed
|
|
193
|
+
|
|
194
|
+
- **The `coverage` field is gone from the evidence pack rather than made writable.** Both writers hardcoded an empty gap list and the parser rejected a non-empty one, so the ticket's promise that an expired reference produces a coverage gap could never fire. A pack is immutable and a reference expires after composition, so the value could not be written back even in principle; `evidence validate` already reports staleness, and that is where a reader can act on it. A field that can never change says nothing (#116).
|
|
195
|
+
|
|
196
|
+
### Fixed
|
|
197
|
+
|
|
198
|
+
- **`value == count` was never true on PostgreSQL, so `verify --after-write` could not report `verified` there.** `firstScalar` returned the driver's value as-is and `compare` used `===`, but pg returns `bigint` / `numeric` / `int8` as strings, so `"0" === 0` was permanently false — and a read-back after a write is almost always a count. The asymmetry is what made it hard to see: `>` and `<` went through JS coercion and passed, so on the same column `value > 5` worked while `value == 6` did not, with the output cheerfully printing `expected: "value == 6"`, `actual: "6"`, `pass: false`. Found by dogfooding a real backfill on PostgreSQL 16, where six rows were correctly updated and the assertion still said no (#115).
|
|
199
|
+
|
|
200
|
+
- **Blacklist matching in evidence content is bounded to whole identifiers, and its refusal says where.** Blocked terms were compared with `includes()` — no boundary, no minimum length — so a protected column named `id` refused any claim containing "identifier", "considered" or "valid", and the message was one line reading `evidence content contains a blocked identifier`, naming neither the field nor what it hit. A term now matches only when neither neighbour is a letter, digit or underscore, so `id` still hits `orders.id` and a bare `id` but not `identifier`, and regex metacharacters in a term are escaped, so `a.c` no longer matches `abc`. The keys of `blacklist.columns` — table names — were never collected into the term list at all, a silent hole in the other direction, and are now included. The error names the offending field (`subject.kind`, `claim 2 text`) and still does not name the term, since printing a protected identifier into a message the author may paste elsewhere is the thing the blacklist exists to prevent; claims are located by ordinal because the id may itself be the blocked string. Two limits are documented rather than papered over: `secret_customer` written as "secret customer" still gets through, which identifier matching against free prose cannot honestly promise to catch, and reference fields (audit `command`, receipt `path`) are not checked at all yet (#117).
|
|
201
|
+
|
|
202
|
+
- **The Elasticsearch integration suite had never asserted anything, on any machine.** It reported "container not running on port 9201" against a healthy container answering `200`: Elasticsearch's `GET /` carries no CORS headers, happy-dom's `fetch` drops a header-less response under the same-origin policy, `beforeAll` swallowed the error and every test returned early. It is the only adapter over HTTP, so it was the only casualty. Removing the global preload was tried and reverted — `tests/integration/ui-render-smoke.test.tsx` depends on it, and a per-file import leaks across the files Bun runs in one process, making the outcome depend on file order. `setup-happy-dom.ts` now saves the runtime `fetch` before `GlobalRegistrator.register()` and restores it after, exporting `runtimeFetch` so a test can pin the contract. Only `fetch` is restored: happy-dom's `window` / `document` / `Element` have no runtime counterpart to shadow, and no adapter touches `Request` or `XMLHttpRequest` (#109, #110).
|
|
203
|
+
|
|
204
|
+
- **A negative test asserting that extra fields are rejected no longer breaks `typecheck:tests`.** The object deliberately carrying a field the validator must refuse was written against a type that does not admit it, which `bun test` never sees and CI's `tsc` step fails on across all ten matrix jobs (#118).
|
|
205
|
+
|
|
206
|
+
### Documentation
|
|
207
|
+
|
|
208
|
+
- **ADR 0005's `deferred` described the feature, not the decision.** Of ten ADRs it was the only one not `accepted`, so every inventory picked it back up as an open question — while its content is a settled fail-closed policy with a reopen checklist and a falsification condition already attached. What is deferred is SQD-05 / SQD-06. It is now `accepted`, with a paragraph separating "the policy is settled" from "provider generation is authorized", the latter having not happened — without it, a reader seeing `remain deferred` in the title over an `accepted` status would plausibly "fix" the status (#111).
|
|
209
|
+
|
|
210
|
+
- **The 2026-08-08 evidence backlog now matches what shipped.** Eight tickets read `Status: Proposed` under a spec header saying no implementation was authorized, a week after all of it went out in v1.53.0. Checking all thirty acceptance criteria against the test files found eleven genuinely asserted, eighteen unproven — mostly asserted halfway — and one structurally impossible. Tickets are marked Delivered with their known deviations and unverified criteria listed per line, and `CONTEXT.md` gains `Known deviation` (deliberately unmet) and `Unverified` (nobody proved it) as distinct terms, because merging them dilutes the first into "everything imperfect" (#112).
|
|
211
|
+
|
|
8
212
|
## [2.1.0] - 2026-08-16 - The gate asked the wrong question, and one route never reached it
|
|
9
213
|
|
|
10
214
|
2.0.0 put a two-tier gate in front of raw SQL. This release is what measuring that gate against real servers found: one entire route into the database bypassed it, its qualification criterion asked whether a `WHERE` existed rather than whether it narrowed anything, and its notion of "what kind of statement is this" was the leading keyword — so a full-table delete wearing an `INSERT` or `WITH` in front of it was treated as routine. Each of the three is a way a statement that empties a table reached the database unattended, and each is closed here. The measurement the gate's own ADR bets on is now a command rather than a `jq` invocation somebody has to remember.
|
|
@@ -179,16 +383,23 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
179
383
|
### Changed
|
|
180
384
|
|
|
181
385
|
- **The CLI loads what a command actually needs.** Subcommands register lazily, SQL drivers load at connection time rather than at import, and `node-sql-parser` is both deferred and externalized from the bundle — measured at roughly 8ms off startup for the lazy registration alone.
|
|
386
|
+
|
|
182
387
|
- **Full-schema scans cost less.** Per-table queries are merged, row estimates replace `COUNT(*)`, and remaining work runs with bounded parallelism. The query path no longer loads the layered schema in full — it fetches the single table it needs.
|
|
388
|
+
|
|
183
389
|
- **Repeated lookups are cached within a process.** Config binding files are read and validated once per process, and the skill update check keeps a TTL cache instead of re-checking on every invocation. Redis and Elasticsearch list operations were narrowed to what the caller asked for.
|
|
390
|
+
|
|
184
391
|
- **Identifier quoting and error classification each have one implementation.** Quote/encode helpers were consolidated into a shared utility, and driver errors are now classified by error code first rather than by matching message text.
|
|
392
|
+
|
|
185
393
|
- **Server-side script protection lives in the adapter layer**, so every caller is covered by the same guard rather than each command re-implementing it.
|
|
186
394
|
|
|
187
395
|
### Fixed
|
|
188
396
|
|
|
189
397
|
- **`bun run build` was non-deterministic.** Consecutive builds of identical sources alternated between two `dist/cli-runtime.mjs` outputs about 690KB apart, depending on whether the bundler pulled in 48 `@inquirer/*` modules. `@inquirer/prompts` is now external, which also removes a silent degradation path where the prompt implementation quietly changed between builds. `bun run build:determinism` checks this in CI.
|
|
398
|
+
|
|
190
399
|
- **One CLI query writes exactly one audit entry.** Some paths recorded the same query more than once.
|
|
400
|
+
|
|
191
401
|
- **Windows CI is green again.** Path separator assumptions, CRLF handling in test fixtures, and CRLF frontmatter stripping in skill sources were all Unix-only.
|
|
402
|
+
|
|
192
403
|
- **The startup benchmark measures the noise floor rather than the median**, which is what actually distinguishes a regression from scheduler jitter, and a guide test no longer flakes on a random UUID colliding with `'5432'`.
|
|
193
404
|
|
|
194
405
|
## [1.53.0] - 2026-08-09 - Offline evidence, semantic contracts, and impact assessment
|
|
@@ -196,13 +407,17 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
196
407
|
### Added
|
|
197
408
|
|
|
198
409
|
- **Offline evidence packs.** `dbcli evidence compose`, `validate`, and `render` create, verify, and render workspace-contained evidence packs from safe claim text plus existing verification artifacts, audit entries, and optional assert receipts. Packs omit SQL, rows, targets, credentials, audit metadata, and verification summaries; source retention loss remains visible without preventing historical rendering.
|
|
410
|
+
|
|
199
411
|
- **Evidence receipts for post-write assertions.** `assert --evidence-receipt <path>` atomically records safe provenance only after the verdict, audit attempt, and optional verification artifact are authoritative. Receipts are workspace-contained, contain no SQL or returned data, cannot be used as execution approval, and may be composed into an evidence pack.
|
|
412
|
+
|
|
200
413
|
- **Reviewable semantic contracts.** `dbcli contract validate|context|search|drift` governs optional `dbcli.contracts.json` evidence expectations for canonical semantic terms. The commands are offline and read-only; only valid approved contracts enter ordinary agent context.
|
|
414
|
+
|
|
201
415
|
- **Offline impact assessment.** `dbcli impact assess` creates a declared-coverage report for a design change against exactly one local schema-cache or ORM baseline, optionally incorporating reviewed data-access metadata and redaction-first proxy workload evidence. It never connects, executes SQL, or claims complete coverage.
|
|
202
416
|
|
|
203
417
|
### Changed
|
|
204
418
|
|
|
205
419
|
- **Shared execution and adapter boundaries.** Query execution now uses an injectable command runtime, SQL adapters share readiness and driver-error handling, and doctor accepts a non-SQL collector runtime. These internal changes keep the CLI behavior stable while making offline evidence and impact workflows testable.
|
|
420
|
+
|
|
206
421
|
- **Documentation and agent skills cover the new surfaces.** English and Traditional Chinese Markdown and HTML guides, installed skill copies, and reference material document evidence packs, assert receipts, semantic contracts, and impact assessment.
|
|
207
422
|
|
|
208
423
|
### Fixed
|
|
@@ -214,10 +429,15 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
214
429
|
### Fixed
|
|
215
430
|
|
|
216
431
|
- **The skill documented three flags that do not exist.** `q --use` (the global `dbcli --use <name> q` form is the real one), `q --collection` (a MongoDB snippet's `target:` is the only collection source), and `audit tail --recovery-ref` (it lives on `audit show`). The same wrong `audit tail --recovery-ref` is corrected in `docs/user` (en/zh, Markdown and HTML).
|
|
432
|
+
|
|
217
433
|
- **The `design` artifact example failed its own validator.** It is replaced with one that validates clean, and the naming rules it has to satisfy are now documented: lowercase kebab-case for design names versus SQL identifiers for tables and columns, endpoints referencing a model rather than a table, strict objects, descriptions that must not contain SQL keywords, and the size limits. Structural violations report `INVALID_ARTIFACT`, a code the severity table did not list.
|
|
434
|
+
|
|
218
435
|
- **Stale lists corrected against the CLI.** `--recovery` covers `lint` and `diff`; verification subject kinds include `table` (what `verify constraint` writes); the Redis permission table includes `XLEN` / `XREAD` / `XRANGE` / `XREVRANGE` / `XADD` / `XDEL` / `LREM`; `audit show` lists `--brief` and `--for-agent`; the snippet guard emits `LIMIT 1001`, fetching one extra row to detect truncation. Elasticsearch supports `q`, and the 10 000 bound belongs to `query` — `export --no-limit` streams via the scroll API. `schema --help` claimed `--sample-size` defaults to 50; it is 100.
|
|
436
|
+
|
|
219
437
|
- **`design` was unreachable from the skill entry point.** `SKILL.md` did not mention it at all, so an agent asked to design or review a schema would hand-write DDL and bypass the review-only `propose` contract. It now has a command row, both workflows, and a guardrail that a proposed plan is never executed.
|
|
438
|
+
|
|
220
439
|
- **Six drifts between the English and Traditional Chinese skills.** The most serious dropped the permission-tier semantics (multi-statement SQL rejected below `admin`, snippets free of write and DDL keywords, `$out` / `$merge` requiring `data-admin`). The MongoDB connection guidance also disagreed between languages — field-by-field is the recommendation, full URI the escape hatch — and the zh-TW side was missing the env-refs MongoDB exception, the `uri`-wins-silently gotcha, `--slow-ms` on `query` and `q`, and the `proxy analyze` action guidance.
|
|
440
|
+
|
|
221
441
|
- **Cursor and Windsurf installs were the Claude skill verbatim.** Windsurf does not parse frontmatter, so roughly 900 characters of `description:` were read as rule text; Cursor reads `description` / `globs` / `alwaysApply` and received none of them. Both platforms keep `reference.md` outside the primary file's directory, so every mention of it resolved to nothing. `dbcli skill --install` now shapes the file per platform — Cursor as an Agent Requested rule, Windsurf with the frontmatter stripped and the description kept as prose — and repoints the reference path. Recognizing an existing dbcli install no longer depends on the frontmatter, so a Windsurf reinstall stops backing up dbcli's own file. See ADR 0006.
|
|
222
442
|
|
|
223
443
|
### Added
|
|
@@ -227,6 +447,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
227
447
|
### Changed
|
|
228
448
|
|
|
229
449
|
- **The bilingual parity gate compares content, not just shape.** It checked heading levels, fence counts, table rows, and a curated token list for mere presence — every drift above kept that structure intact. It now compares per-section counts of every code token and list item; run against 1.52.0 it reports 48 problems it used to pass. Its success message no longer reads as "the docs are aligned" when it only checked the skeleton.
|
|
450
|
+
|
|
230
451
|
- **The skill entry point carries less that an agent cannot act on.** The always-loaded `description` drops from 990 to 626 characters with every trigger branch intact, release markers such as `(v1.23)` and an internal ticket id are gone (an agent has exactly one installed version), and the `proxy` row's flag wall becomes an anchor.
|
|
231
452
|
|
|
232
453
|
## [1.52.0] - 2026-08-07 - Offline database design assistant and slow-query hints
|
|
@@ -234,8 +455,11 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
234
455
|
### Added
|
|
235
456
|
|
|
236
457
|
- **Offline database design assistant.** `dbcli design init|validate|render|diff|propose` authors and reviews a version-controlled `dbcli.design.json` beside the code. Every subcommand is offline: none opens a connection, executes DDL, or calls a provider, and `design init` is the only writer — to the explicit `--output` path, refusing to overwrite. `validate` is fail-closed, so `render`, `diff`, and `propose` refuse to work while `error` findings remain; `render` emits `json`, `markdown`, or `mermaid`.
|
|
458
|
+
|
|
237
459
|
- **Design drift comparison and review-only proposals.** `design diff` and `design propose` compare the artifact against the local schema cache (`--against-cache`) or local ORM definitions (`--against-orm`, supporting Prisma, DDL, Drizzle, TypeORM, Sequelize, and JSON), with `--orm-format` and `--ignore` for control. `propose` turns drift into a plan a human reviews and never applies a write: each entry carries a `dry-run` or `migration-review` safety level plus `preflight`, `rollback`, and `verification` steps.
|
|
460
|
+
|
|
238
461
|
- **Two further design review rules.** `REVERSE_RELATIONSHIP` (error) fires when the same endpoints are declared again in the opposite direction, and `PREFIX_REDUNDANT_INDEX` (warn) fires when a non-unique index is a leading-column prefix of a longer index.
|
|
462
|
+
|
|
239
463
|
- **Passive slow-query hint on `query` and `q`.** At or above `--slow-ms` (default 1000, `0` disables), a finished query gains a Performance hint footer and `metadata.performanceAdvisory`. It reuses the execution time already measured — no `EXPLAIN`, no schema read, no second request. The recommendation is engine-aware: PostgreSQL, MySQL, MariaDB, and Redis are pointed at `guide slow-query`; MongoDB and Elasticsearch state the timing instead. `--recovery` suppresses the hint so that envelope keeps its contract.
|
|
240
464
|
|
|
241
465
|
### Changed
|
|
@@ -267,8 +491,11 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
267
491
|
### Added
|
|
268
492
|
|
|
269
493
|
- **Business semantic context commands.** Add the offline, read-only `dbcli semantic validate`, `context`, `search`, `drift`, and `migrate` commands for a reviewable `dbcli.semantic.json`. Semantic models, metrics, aliases, and v2 relationships are checked against the cached visible schema and saved-query names; v1 files remain supported and `migrate --to 2` writes only to stdout.
|
|
494
|
+
|
|
270
495
|
- **Deterministic governed semantic search and relationship drift checks.** `semantic search` returns only reviewed metadata, removes blacklist names from free-text results, and supports bounded result counts. `semantic drift` identifies stale, invalid, or unavailable local semantic evidence, including relationship references that no longer match declared visible fields.
|
|
496
|
+
|
|
271
497
|
- **Offline validation boundary for agent query drafts.** `dbcli semantic draft validate --input <file|->` accepts an explicit untrusted JSON draft and validates its references and read-only SQL without executing it, persisting it, or calling a provider. Reports contain hashes, canonical references, and safe violation codes rather than candidate SQL; a successful validation is explicitly not permission to execute.
|
|
498
|
+
|
|
272
499
|
- **Semantic context in agent-facing skill context.** `dbcli skill context` includes the validated semantic context when present, after blacklist filtering, so agents receive only governed schema and semantic metadata.
|
|
273
500
|
|
|
274
501
|
## [1.50.0] - 2026-08-06 - QueryLens proxy query analysis
|
|
@@ -316,32 +543,59 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
316
543
|
### Security
|
|
317
544
|
|
|
318
545
|
- **blacklist 只檢查 SQL 的第一張表。** 擋下與遮罩都以單次 regex match 取得的單一表名為準,因此只要敏感表是經由 `JOIN`、逗號、`UNION` 或子查詢進入查詢,它既不會被擋、欄位也不會被遮罩。在 `users` 已列入 blacklist 的設定下,`SELECT * FROM users` 會被擋,但 `SELECT o.id, u.password_hash FROM orders o JOIN users u ON u.id = o.user_id` 會照常回傳 `users` 的敏感欄位。繞過不需要任何特殊語法,一個 `JOIN` 就夠,且在 `query-only` 權限下即可利用。影響 `query` / `export` / `q` / REPL 等所有 SQL 路徑。
|
|
546
|
+
|
|
319
547
|
- **`export` 的 SQL 路徑完全沒有套用 blacklist。** 該路徑建立 `QueryExecutor` 時把 validator 傳成 `undefined`,因此連單表的情況都不擋、不遮罩:`dbcli export "SELECT * FROM users" --format json` 會把已宣告為敏感的欄位原樣寫進檔案。
|
|
548
|
+
|
|
320
549
|
- **`export` 的 Elasticsearch 路徑檢查 index 但不遮罩欄位。** 同一個 index 上 `dbcli query` 會遮蔽的欄位,`dbcli export` 會寫進檔案。
|
|
550
|
+
|
|
321
551
|
- **`dbcli report` 完全沒有套用 blacklist。** 它直接呼叫 adapter 執行 snippet,而 collector 會載入使用者可寫的 snippet 目錄(不只內建),回傳的 rows 會被嵌進報告。既不擋黑名單資料表,也不遮罩欄位。
|
|
552
|
+
|
|
322
553
|
- **`dbcli q --verify` 的第二段查詢未經檢查。** blacklist 只套用在 snippet 本體,frontmatter 的 `verify.query` 是另一段直接送到 adapter 的 SQL。
|
|
554
|
+
|
|
323
555
|
- **互動式 shell 從未套用 `blacklist.columns`。** REPL 不走 `QueryExecutor`,它把 adapter 回傳的 rows 直接格式化輸出,因此 `dbcli shell` 裡的 `SELECT * FROM users` 會完整回傳 `dbcli query` 會遮蔽的欄位。
|
|
556
|
+
|
|
324
557
|
- **MongoDB 的 `$lookup` / `$unionWith` 從未被檢查。** 這是 #23 的 MongoDB 寫法:指令指定一個 collection,pipeline 卻讀另一個。`query` / `export` / `q` 三條路徑都只檢查被指名的那個 collection,因此 `$lookup: { from: 'secrets' }` 既不被擋,嵌入的欄位也不被遮罩。現在會遞迴讀取 `$lookup.from`、`$unionWith.coll`、`$graphLookup.from`、`$out`、`$merge.into`(含 sub-pipeline),並把來源 collection 的欄位規則重新錨定到 `as` 指定的巢狀路徑。
|
|
558
|
+
|
|
325
559
|
- **字串常值的反斜線解讀會讓掃描器失步。** 表名列舉先前假定反斜線不轉義引號,理由是「提早結束字串只會讓更多文字可見」—— 這個推理是錯的:提早結束會翻轉引號奇偶性,於是下一個引號開啟一段直到輸入結尾的偽字串,把整個 `FROM` 子句藏起來。`SELECT E'\'' AS x, * FROM secrets` 在 `query-only` 下即可取回整張黑名單資料表。現在兩種解讀都掃描並取聯集。
|
|
560
|
+
|
|
326
561
|
- **PostgreSQL 的 `U&"\0073ecrets"` 未解碼。** 回報的是原始文字,而伺服器解析出的是 `secrets`,因此擋下與遮罩都被繞過。`UESCAPE` 允許以幾乎任何字元代替反斜線(只要不是十六進位數字、`+`、引號或空白),包含一般字母,因此 `U&"x0073ecrets" UESCAPE 'x'` 是純英數字串;現在會對每個合法的 escape 字元各解碼一次。
|
|
562
|
+
|
|
327
563
|
- **⚠️ 非 ASCII 的 dollar-quote 標籤造成權限繞過(不只 blacklist)。** dollar-quote 的標籤依「未加引號的識別字」規則,而 PostgreSQL 識別字接受高位元組,因此 `$é$ … $é$` 是真正的字串;但語句剖析器的標籤樣式只接受 ASCII,於是該區段被當成一般文字,裡面的 `'` 開啟一段直到輸入結尾的字串,把後面整批語句藏起來。**在 `permission: query-only` 下,`SELECT $é$ ' $é$ ; DROP TABLE users; -- ` 會通過權限判定並送到資料庫。** 這條在 1.47.0 以前就存在,與 1.47.1 修掉的 `a$q$` 是同一族 —— 當時修了識別字延續字元的判定,沒有修標籤本身。詞法邊界規則已抽到 `src/utils/sql-lexical.ts` 由兩個掃描器共用,因為同一條規則已經三次在一個檔案修、另一個沒修。
|
|
564
|
+
|
|
328
565
|
- **Elasticsearch 的 `--index` 是運算式而非名稱。** 它接受逗號清單、萬用字元、`_all`、百分比編碼(`%2A`)、date math(`<logs-{now/d}>`)與跨叢集限定(`cluster:index`),因此 `--index "secrets,orders"`、`"*"`、`"sec*"`、`"_all"`、`"<secrets>"`、`"*:secrets"`、`"%2A"` 全都能讀取黑名單 index 而不與任何黑名單項目相等。`query` / `export` / `q` / ES shell **四條**路徑皆受影響。現在會正規化運算式後逐一檢查具名 index,萬用字元則在「可能匹配到黑名單 index」時拒絕。
|
|
566
|
+
|
|
329
567
|
- **同一個問題也讓欄位遮罩整個失效。** 遮罩仍以原始運算式做等值查表,因此 `--index "us*"` 或 `"users,orders"` 匹配不到任何欄位規則 —— 在只設定欄位黑名單(資料表本身未列入)時,`checkIndexBlacklist` 會放行,然後所有受保護欄位原樣回傳,`export` 更會寫進檔案。現在改以「該運算式可能觸及的所有 index 的規則聯集」遮罩。
|
|
568
|
+
|
|
330
569
|
- **ES 目標的比對讀的是原始文字,不是伺服器實際路由的路徑。** `%5F` 是 `_`、`%2F` 是 `/`、`..` 會退一層,因此 `GET /%5Fsearch`(實為 `/_search`)、`/secrets%2F_search`(實為 `/secrets/_search`)、`/_cat/../secrets/_search` 全都通過檢查。另有 `_ALL` 大小寫、`%252A` 雙重編碼、`<<secrets>>`、`c:d:secrets`、`secrets:` 等拼法在 `--index` 上同樣繞過。現在路徑會先解碼並解析(重複至穩定)再檢查,且**任何一段**命中黑名單 index 就拒絕 —— `/_cat/indices/secrets` 不讀文件,但黑名單保護的是物件本身。正規化規則抽到 `src/utils/es-index-target.ts` 由 validator 與 shell 共用。
|
|
570
|
+
|
|
331
571
|
- **ES shell 只看路徑,request body 指名的 index 完全未檢查。** `_mget` 的 `docs[]._index`、`_bulk` action 的 `_index`、`terms` lookup 的 `index` 都能指向黑名單 index —— 把路徑指向無害的 index,正好讓這些端點重新打開。
|
|
572
|
+
|
|
332
573
|
- **ES shell 的「未指名 index 即拒絕」讀的是原始路徑,其餘檢查讀的是解析後路徑。** 因此 `GET /_cat/../_search`、`/_ingest/../_sql`、`/_license/../_msearch` 只要前綴在允許清單內就放行,而 HTTP 客戶端會把 `..` 解掉,實際送出的是未界定的 `_search`。現在檢查與送出的是同一個字串,且路徑的文字與路由結果不一致時直接拒絕。
|
|
574
|
+
|
|
333
575
|
- **ES shell 的欄位遮罩保護的是鍵名,不是值。** Elasticsearch 會把欄位值放在**請求指定**的鍵底下回傳:`{"sort":["password"]}` 一個請求就能依序取回整欄,`aggs.*.field`、`script_fields`、`docvalue_fields`、runtime field 同理,都不需要 scripting 權限。現在請求本體中只要出現受保護欄位名(含字串內以非識別字切出的片段)即拒絕,遮罩回應則作為第二道。
|
|
576
|
+
|
|
334
577
|
- **Elasticsearch data stream 與 rollover 的支撐 index 名稱不同,等值比對蓋不到。** `.ds-secrets-2026.08.05-000001`、`secrets-000001` 都能讀到 `secrets` 的資料。現在依命名慣例一併涵蓋。**alias 仍是天花板** —— alias 指向哪個 index 是伺服器端知識,且 `GET /_cat/aliases` 會揭露對應關係;已記入威脅模型。
|
|
578
|
+
|
|
335
579
|
- **request body 中陣列型的 `index` / `_index` 未被檢查**(`_msearch` 標頭、`_reindex` 的 `source.index` 都接受陣列)。
|
|
580
|
+
|
|
336
581
|
- **`globToRegex` 的字元類別掃描不理會轉義**,`[a\]b]` 被讀成字面字串而非「a、]、b 三選一」的類別。
|
|
582
|
+
|
|
337
583
|
- **ES shell 完全沒有欄位遮罩。** `dbcli query --index users` 會遮蔽的欄位,ES shell 原樣回傳。現在回應中任何名稱命中欄位黑名單的鍵一律移除(不論深度)—— ES 回應是任意文件結構,與其為 `hits.hits` 等各種外層建模,不如從嚴。
|
|
584
|
+
|
|
338
585
|
- **ES shell 對任何未指名 index 的路徑完全跳過檢查。** 路徑第一段以 `_` 開頭時取不到 index,於是 `GET /_all/_search`、`/_search`、`/_msearch`、`/_mget`、`/_sql` 全都放行 —— 它們都會讀到黑名單 index 的文件。現在:有設定黑名單時,無法界定 index 的請求一律拒絕,僅以**允許清單**放行純叢集中繼資料端點(`_cat`、`_cluster`、`_nodes`、`_tasks`、`_ingest`、`_license`),因為改用拒絕清單就得窮舉現在與未來所有會回傳文件的端點。
|
|
586
|
+
|
|
339
587
|
- **`export` 的 ES 路徑先取資料才檢查 blacklist。** 雖然不會寫出檔案,但黑名單 index 已被查詢、scroll context 已被開啟。檢查已移到抓取之前。
|
|
588
|
+
|
|
340
589
|
- **MongoDB 巢狀 `$lookup` 的遮罩前綴不含巢狀層級。** `$facet` 分支或 `$lookup.pipeline` 內的 `$lookup`,文件實際落在 `fb.sec.*` / `outer.sec.*`,規則卻被錨定在 `sec.*`,因此不會遮罩。
|
|
590
|
+
|
|
341
591
|
- **同一個 collection 被 join 兩次時只有第一次被遮罩。** 前綴是以「尚未見過的 collection」為單位記錄的,因此 `$lookup ... as: 'first'` 與 `$lookup ... as: 'second'` 只產生一組前綴,`second.token` 外洩。
|
|
592
|
+
|
|
342
593
|
- **⚠️ 修復本身引入的回歸(已修):dollar-quote 判定改為「必須以可起始識別字的字元開頭」之後,數字後接識別字的情況被誤判。** `1a$q$` 在 PostgreSQL 是數值常值 `1` 加上識別字 `a$q$`(`$` 被吸收、不開引號),但新規則只看第一個字元、把整串當成數字,於是**憑空造出一個 dollar-quote**,`SELECT 1a$q$ ; DELETE FROM secrets ; SELECT 1 AS z$q$` 在 `query-only` 下通過。判定改為:以識別字字元開頭則吸收;`$` 開頭是位置參數;數字開頭則先吃掉數值前綴,若其後仍有識別字字元就吸收。
|
|
594
|
+
|
|
343
595
|
- **PostgreSQL 中 dollar-quote 接在位置參數之後未被識別。** `$1$q$` 裡的 `$1` 是參數而非識別字,但判定只排除「以數字開頭」的字元串,`$` 開頭的被當成識別字,於是 `$q$` 未被識別、裡面的 `'` 再次讓掃描失步。判定改為「該字元串必須以可*起始*識別字的字元開頭」。目前不可利用(`query.ts` 一律傳空參數,`$1` 在伺服器端是語法錯誤),支援參數綁定後即會成為實洞。
|
|
596
|
+
|
|
344
597
|
- **`globToRegex` 對轉義的字面量比對過少。** `sec\*` 應保護鍵 `sec*`,卻編譯成可比對 `sec\x` 而比不到 `sec*` —— Redis key 黑名單的樣式在這個方向上是會洩漏的。
|
|
598
|
+
|
|
345
599
|
- **PostgreSQL 中 dollar-quote 接在數字之後未被識別。** 只有*識別字*會吸收後面的 `$`:`1$q$` 是數值常值加上真正的 dollar-quote,`a1$q$` 則是單一識別字。原本只看前一個字元,兩者分不開,於是該引號未被識別、裡面的 `'` 再次讓掃描失步。
|
|
346
600
|
|
|
347
601
|
除前兩條外,其餘皆是在修 #23 的過程中、經由列舉「哪些路徑直接呼叫 adapter」與七輪對抗性審查找出來的 —— 與 1.47.1 的教訓相同:這類缺陷表現為**未設防的路徑**,不是缺少機制。
|
|
@@ -349,19 +603,29 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
349
603
|
### Changed
|
|
350
604
|
|
|
351
605
|
- **⚠️ 行為收緊:語句只要參照到任何一張黑名單資料表就會被擋下。** 過去只有排在最前面的那張表算數。升級後,先前能執行的跨表查詢(JOIN / 逗號 / UNION / 子查詢帶進黑名單表)會開始被拒絕 —— 那正是原本應該被擋的行為。
|
|
606
|
+
|
|
352
607
|
- **⚠️ 遮罩改以「所有被參照的表」的欄位規則聯集計算。** JOIN 結果的欄位名不帶表限定(`u.password_hash` 回傳成 `password_hash`),無法從結果反推欄位屬於哪張表,因此只要**任何一張**被參照的表把該欄位列入黑名單就遮蔽。
|
|
608
|
+
|
|
353
609
|
- **⚠️ 表名列舉刻意過度回報,可能誤擋。** 新的 `src/utils/sql-tables.ts` 除了走訪 `FROM` / `JOIN` / `INTO` / `UPDATE` / `USING` / `TRUNCATE` / `COPY` / `STRAIGHT_JOIN` 等位置,**還會把語句中每一個非保留字的識別字都列為候選表名**。這表示:若某個欄位名、別名或函式名剛好等於一張黑名單資料表的名稱,該語句會被擋下。這是刻意的取捨 —— 歷次對抗性審查各自都在「精確走訪」版本裡找到新的文法角落(`{ oj … }`、`STRAIGHT_JOIN`、`FROM a USE INDEX (i), secrets`、`ANALYZE t`、`U&"t"`),與 1.47.1 記錄的天花板是同一個模式,因此保證不建立在走訪完整之上,而建立在「表名是識別字,而每個識別字都會被回報」。被擋時錯誤訊息會指出是哪個名稱命中 —— 實務上會遇到的情形是:黑名單有一張叫 `token` 的資料表時,`SELECT t.token FROM api_keys t` 會被拒絕。
|
|
610
|
+
|
|
354
611
|
- **保留字清單只留三種方言都保留的字**(38 個)。 這是列舉唯一會 fail-open 的地方:清單裡的字若在某個方言其實可當未加引號的表名,那張表就隱形。`FILTER` / `PARTITION` / `SET` / `CURRENT` / `UPDATE` / `DELETE` / `INSERT` / `NULLS` / `OVER` / `EXISTS`、`RLIKE` / `XOR` / `ILIKE` / `STRAIGHT_JOIN`,以及 `BETWEEN`(PostgreSQL 的 `col_name_keyword`,`ColId` 接受)與 `EXCEPT` / `INTERSECT`(MySQL 8.0.31、MariaDB 10.3 才成為保留字)皆已移除 —— 每一個都在某個支援的方言裡是合法表名。
|
|
612
|
+
|
|
355
613
|
- **語句在所有歧義解讀下各掃描一次並取聯集。** 反斜線是否轉義引號取決於伺服器模式,未宣告方言時註解規則也不同。挑一種解讀正是失步繞過的成因。
|
|
614
|
+
|
|
356
615
|
- **無法辨識出資料表時,遮罩套用全部欄位規則而非不套用。** 過去(以及本次修復的第一版)在表名解析不出來時直接跳過遮罩,等於把任何解析缺口變成洩漏。
|
|
616
|
+
|
|
357
617
|
- **`query` 的大小防護不再對 schema 限定名靜默失效。** 舊的單次比對對 `FROM public.users` 回傳 `public`,那不是 schema 快取的鍵,於是防護整段被跳過。
|
|
358
618
|
|
|
359
619
|
### Fixed
|
|
360
620
|
|
|
361
621
|
- **`snapshot` 記錄的 redacted 欄位清單過去只取第一張表**,與實際遮罩的範圍不一致。
|
|
622
|
+
|
|
362
623
|
- **表名列舉在長 dotted chain 上是二次成長**(16 KB 的 `a.a.a…` 要 320ms,125 KB 要 22 秒)。改為單趟走訪後同一輸入為 2ms。
|
|
624
|
+
|
|
363
625
|
- **`decodedVariants` 對「相異字元數」是二次成長**(40 KB 的識別字要 3.4 秒)。改為單趟同時解碼所有合法 escape 字元後,同一輸入為 4ms。
|
|
626
|
+
|
|
364
627
|
- **`globToRegex` 遇到無法解析的字元類別(`[\]*`)會拋 `SyntaxError`**,從安全檢查裡竄出去而不是回答它。
|
|
628
|
+
|
|
365
629
|
- **識別字中超出 Unicode 上限的 escape 會讓掃描整個拋例外。** `\+FFFFFF` 是 16777215,`String.fromCodePoint` 會丟 `RangeError`;而 `"` 在所有方言都被當識別字引號,因此任何含該樣式的 MySQL 字串(例如 Windows 路徑)都會讓指令中斷。
|
|
366
630
|
|
|
367
631
|
### Known limits
|
|
@@ -383,10 +647,15 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
383
647
|
修復六個「看起來是讀、實際會寫」的繞過,它們都能在設定為 `permission: query-only` 的連線上寫入資料。**建議所有把資料庫交給 AI agent 操作的使用者升級。**
|
|
384
648
|
|
|
385
649
|
- **MongoDB `$out` / `$merge` 未被擋下(`query`、`q`、`export`)。** 這兩個 aggregation stage 只在多連線 fan-out 路徑被檢查,單連線 `dbcli query`、saved snippet、以及 `dbcli export` 全都會執行它們,不論 permission 等級。`$out` 可覆寫任意 collection。`--dry-run` 會把這種 pipeline 預覽成安全操作。影響 MongoDB 連線。
|
|
650
|
+
|
|
386
651
|
- **PostgreSQL 多語句堆疊。** 權限分類只讀第一個關鍵字,而 PostgreSQL 的 simple query protocol 會執行字串裡每一個以分號分隔的語句,因此 `SELECT 1 LIMIT 1; DELETE FROM users` 會以 SELECT 的身分通過 `query-only`。影響 PostgreSQL;MySQL / MariaDB 走 prepared statement,不受影響。
|
|
652
|
+
|
|
387
653
|
- **snippet 的偽唯讀語句。** snippet 只要求開頭是 `SELECT` 或 `WITH`,因此 `WITH x AS (DELETE FROM users RETURNING *) SELECT * FROM x` 與 `SELECT … INTO` 都能通過。一個 commit 進 repo、看起來是唯讀報表的 `.sql` 檔可以寫入資料庫。影響 PostgreSQL / MariaDB。
|
|
654
|
+
|
|
388
655
|
- **snippet frontmatter 的 `verify.query` 未經驗證。** 過去只檢查它是非空字串,然後由 `dbcli q <name> --verify` 原封執行。
|
|
656
|
+
|
|
389
657
|
- **唯讀證明只接在多連線 fan-out 上,單連線 `query` / `export` / REPL 沒有。** 權限判定只看第一個關鍵字,因此 `query-only` 連線接受並執行下列語句:`WITH gone AS (DELETE FROM users RETURNING *) SELECT * FROM gone`(data-modifying CTE)、`SELECT * INTO evil_copy FROM users`(建表)、`EXPLAIN ANALYZE DELETE FROM users`(`EXPLAIN ANALYZE` 會真的執行該語句)。同一句 SQL 加上 `--use a,b` 會被擋,不加就執行。auto-limit 補的 `LIMIT 1000` 對 CTE 無效,整張表仍會被刪。**這條在 1.47.0 以前就存在**,且不需要任何特殊語法。影響 PostgreSQL 與 MariaDB。
|
|
658
|
+
|
|
390
659
|
- **PostgreSQL 識別字中的 `$` 被誤判為 dollar-quote 起點。** PostgreSQL 的識別字從第二個字元起允許 `$`,因此 `a$q$` 是**一個識別字**;但語句剖析器把它讀成字串起點,於是 `SELECT 1 AS a$q$ LIMIT 1; DELETE FROM users; SELECT 1 AS b$q$` 中間整段對所有安全檢查隱形,資料庫卻照常執行三段。**這條在 1.47.0 以前就存在**:多連線 fan-out 的唯讀斷言(`dbcli query --use a,b`)用的正是同一個剖析器,因此可被此手法繞過。影響 PostgreSQL。
|
|
391
660
|
|
|
392
661
|
利用這些繞過需要能下達指令的一方送出 payload,也就是 agent 本身 —— 而 dbcli 的威脅模型前提正是 agent 不完全可信,因此這些屬於權限繞過,不以「使用者自己下的指令」論。
|
|
@@ -394,10 +663,15 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
394
663
|
### Changed
|
|
395
664
|
|
|
396
665
|
- **⚠️ 行為收緊:開頭讀取但夾帶寫入的語句一律需要 `admin`。** 例如 `WITH x AS (INSERT … RETURNING *) SELECT …`、`SELECT … INTO`、`EXPLAIN ANALYZE <寫入>`、`DESCRIBE ANALYZE <寫入>`(`DESCRIBE` 在 MySQL/MariaDB 是 `EXPLAIN` 的同義字)。過去這些一律不檢查;中間曾嘗試「比照該寫入的等級」,但那需要為 `INSERT INTO` 的 `INTO` 加文字例外,而例外之間會互相作用出新的繞過,因此改為單一規則。**升級後需要改用 `admin` 的用法有三類**:`data-admin` 連線上的可寫 CTE;對寫入語句做 `EXPLAIN ANALYZE` / `DESCRIBE ANALYZE` 效能分析(它會真的執行該語句);以及 MySQL/MariaDB 的 `SELECT … INTO @variable`——後者其實是純讀取,只是與 PostgreSQL 會建表的 `SELECT … INTO <table>` 共用關鍵字,為它加例外正是本次反覆出問題的來源,因此選擇留下這個誤擋。被擋時的錯誤訊息會指出是哪一個關鍵字觸發的。 不含 `ANALYZE` 的 `EXPLAIN` 只做計畫、不執行,維持唯讀;`SHOW` / `DESCRIBE` 不接受子查詢,因此 `SHOW CREATE TABLE users` 仍是讀取;`replace()` / `TRUNCATE()` / `INSERT()` 是函式不是語句,維持唯讀。
|
|
666
|
+
|
|
397
667
|
- **admin 以下的權限等級拒絕多語句 SQL。** 因為只有第一個語句會決定權限判定。`admin` 不受影響(它本來就允許所有語句類型)。分隔符依**該連線實際的方言**判定:`$$…$$` 只在 PostgreSQL 是字串、反引號只在 MySQL/MariaDB 引號化識別字、`#` 只在 MySQL/MariaDB 起始註解(在 PostgreSQL 是運算子)。方言未知時從嚴。
|
|
668
|
+
|
|
398
669
|
- **snippet 的唯讀證明依 `engine` 宣告的方言判定。** 因此 `SELECT \`update\` FROM t`(MySQL 反引號識別字)、`# drop …` 註解、`a.create` 這類欄位名不再被誤判為寫入;`FOR UPDATE` / `FOR SHARE` 是取鎖的讀取,同樣不算寫入。
|
|
670
|
+
|
|
399
671
|
- **無法解析的 snippet 只跳過該檔並發出警告,不再讓整個 snippet 目錄失效。** `queries check` 仍會回報它們並以 exit 1 結束。
|
|
672
|
+
|
|
400
673
|
- **snippet 一律拒絕寫入關鍵字。** snippet 依合約唯讀,這條規則不看 permission 等級,`admin` 連線亦然。
|
|
674
|
+
|
|
401
675
|
- **MongoDB 寫入 stage 在單連線 `query` 需要 `data-admin` 以上;在 snippet 與 `export` 一律拒絕。**
|
|
402
676
|
|
|
403
677
|
### Added
|
|
@@ -411,11 +685,13 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
411
685
|
### Added
|
|
412
686
|
|
|
413
687
|
- **新的 root-level 全域旗標 `--timeout <ms>`。** 覆寫連線設定中的 `timeout`;兩者都沒有時沿用各 adapter 內建的 5000ms。合法值為 100~600000 的整數,須放在子指令之前(和 `--global` / `--use` 一樣是 root-level flag)。對所有引擎有效,典型用途是 MongoDB 跨 VPN 或連 Atlas 時,預設 5 秒的 server selection timeout 太緊:`dbcli --timeout 20000 --use <conn> list`。這個覆寫只在建立連線時套用,不會寫回設定檔;要永久生效請在連線設定裡寫 `timeout` 欄位。
|
|
688
|
+
|
|
414
689
|
- **連線設定檔新增 `timeout` 欄位。** 四種連線 schema 皆支援,毫秒、100~600000 整數、可省略。
|
|
415
690
|
|
|
416
691
|
### Changed
|
|
417
692
|
|
|
418
693
|
- **設定檔驗證失敗的錯誤訊息改為可讀格式。** 過去會吐出整包 Zod `unionErrors` 巢狀 JSON;現在只列出與該連線 `system` 相符的分支問題,逐欄列出欄位路徑。
|
|
694
|
+
|
|
419
695
|
- **文件明確禁止 `2>&1`。** 診斷訊息走 stderr、結果走 stdout,合併兩者會讓 `--format json` 的輸出無法解析;SKILL 與 reference 都補上導管寫法。
|
|
420
696
|
|
|
421
697
|
## [1.46.0] - 2026-08-04 - MongoDB 逐欄連線設定
|
|
@@ -425,20 +701,27 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
425
701
|
### Changed
|
|
426
702
|
|
|
427
703
|
- **⚠️ BREAKING(互動流程):`dbcli init` 對 MongoDB 改為先問「連線設定方式」。** 過去第一個提問是 MongoDB URI,留空才退回逐欄詢問 —— 於是逐欄路徑事實上沒人走,所有文件也只教「整條 URI 貼進去」。現在預設是「逐欄填寫」,貼 URI 降為明示的進階選項。**設定檔格式向下相容**,既有含 `uri` 的設定不需修改;`--uri`、`--no-interactive` 等非互動用法行為完全不變,只有互動提問的順序改變。
|
|
704
|
+
|
|
428
705
|
- **逐欄模式在有帳號時會明確寫出 `authSource`。** 過去只有帶 `--auth-source` 才會(而且寫了也會被 schema 丟掉),現在未指定時會寫入 `admin`。連線結果與過去等價(adapter 本來就以 `admin` 為預設),但設定檔內容會多這一行 —— 包含 `--no-interactive` 的既有腳本。
|
|
706
|
+
|
|
429
707
|
- **`uri` 與逐欄欄位仍是 `uri` 優先,但不再靜默。** 兩者同時存在時 `dbcli doctor` 會發出 warning 指出逐欄值被忽略;`srv: true` 又指定非預設 `port` 也會 warning。這兩種設定過去都是「改了欄位卻沒生效」而無從診斷。
|
|
430
708
|
|
|
431
709
|
### Added
|
|
432
710
|
|
|
433
711
|
- **MongoDB 連線設定新增 `authSource` / `replicaSet` / `tls` / `srv` 四個欄位。** 過去這些選項只能塞進 `uri` 的 query string —— 這正是逐欄路徑不堪用的根因。其中 `authSource` 更微妙:runtime 型別與 `init --auth-source` flag 都存在,但 zod schema 沒有此鍵,`z.object` 會 strip 掉未知欄位,於是它落盤即遺失,只有 init 當下那次連線測試吃得到,等同一個死 flag。`srv: true` 會組出 `mongodb+srv://` 並沿用既有的 DNS SRV 展開(含 DoH fallback),讓 Atlas 這類最常見的雲端場景也能逐欄設定。`authSource` 與 `replicaSet` 支援 `{"$env": "..."}` 參照。
|
|
712
|
+
|
|
434
713
|
- **MongoDB 逐欄分支支援 `--use-env-refs`。** 過去 mongo 在 init 的 early-return 發生在 env-ref 分支之前,想用環境變數參照只能手改 `config.json`。現在五個 `--env-*` 旗標對 mongo 全部生效,密碼不必明文落盤。與 SQL 路徑的差異:mongo 只要求 `--env-host`,其餘留空即寫入字面值而不產生 `$env` —— 因為未定義的 `$env` 會讓之後每一個指令 fail closed,對無認證連線而言那是壞掉的設定。env-ref 模式同樣跳過連線測試(參照此時還沒有值可連),與 SQL 路徑一致。
|
|
714
|
+
|
|
435
715
|
- **連線失敗訊息按成因分類。** 認證失敗提示檢查 `authSource`(並說明 Atlas 與多數自架環境為 `admin`)、DNS/SRV 解析失敗提示 `srv` 設定與網路 DNS、TLS 握手失敗提示 `tls` 欄位與自簽憑證情境。原本三種情況共用同兩條泛用訊息。
|
|
436
716
|
|
|
437
717
|
### Fixed
|
|
438
718
|
|
|
439
719
|
- **逐欄模式的連線字串跳脫不完整。** `buildUri()` 原本只對 `password` 做 `encodeURIComponent`,`user` 與 `database` 直接字串拼接 —— 帳號含 `@`、資料庫名含 `/` 都會讓 driver 把 authority 切在錯的位置。現在三者一致跳脫,`host` 則改為驗證不含 `/@?#` 並在違反時明確報錯。
|
|
720
|
+
|
|
440
721
|
- **`host` 為空字串或含埠號、空白時會產出壞掉的連線字串。** `mongodb://:27017/db` 與 `mongodb://h:1234:27017/db` 過去都會被送進 driver,換來一個難懂的錯誤。現在在組字串前就擋下並說明埠號該填在 `port` 欄位。IPv6 位址需加方括號(`[::1]`),與 driver 的要求一致 —— 未加方括號的 `::1` 過去會組出 `mongodb://::1:27017/db`。同理 `authSource` 為空字串時會退回 `admin`,不再送出 `authSource=`。
|
|
722
|
+
|
|
441
723
|
- **連線失敗分類會被連線字串本身誤導。** driver 的錯誤訊息經常回吐原始 URI,而 `mongodb+srv://` 與這次新增的 `?tls=true` 正好含有 `SRV` 與 `TLS` 字樣 —— 用裸字串比對會讓一個單純的連線被拒歸類成 DNS 或 TLS 問題。改為優先讀 driver 的結構化 error code,訊息比對則收斂成 driver 實際會產生的片語。
|
|
724
|
+
|
|
442
725
|
- **只填 `user` 沒填 `password` 會靜默降級成無認證連線。** 原本的 `if (user && password)` 在密碼缺漏時直接落到無認證分支,錯誤會延後到伺服器端才浮現、且看起來像是權限問題。現在直接拋 `ConnectionError`,訊息說明補上密碼或一併清空 `user`。
|
|
443
726
|
|
|
444
727
|
## [1.45.1] - 2026-08-04 - Windows 上的 agent mode 修復
|
|
@@ -450,6 +733,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
450
733
|
### Changed
|
|
451
734
|
|
|
452
735
|
- **移除 schema loader 的牆鐘時間斷言。** `initialize` 的 `loadTime < 200ms` 跑在阻擋性的 `bun test` 裡,但共用 CI runner 不是量測儀器(Windows 冷啟動 270ms 就紅,程式本身無異常)。改為斷言合約(有量到並回報 loadTime),時間預算歸 `tests/perf/*.bench.ts` —— CI 對該套件本來就設 `continue-on-error`,正因為 timing 依環境而定。
|
|
736
|
+
|
|
453
737
|
- **`docs/security-threat-model.md` 補上平台差異。** POSIX 用 `0o700`/`0o600` 保護設定,Windows 沒有等價 mode bits,機密性靠 profile ACL;竄改偵測兩邊一致。
|
|
454
738
|
- 這兩項修復讓 `windows-latest` CI job 自 v1.40.0 以來首次通過(6 個 matrix job + docs-parity 全綠)。
|
|
455
739
|
|
|
@@ -458,11 +742,13 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
458
742
|
### Added
|
|
459
743
|
|
|
460
744
|
- **root-level `--global` 旗標。** 原本每條連線都綁在專案上:`init` 會在 `./.dbcli/config.json` 寫 binding stub,真正的設定落在 `~/.config/dbcli/projects/<project-id>/`。要在多個專案共用同一條連線,只能在每個 repo 重跑一次 `init`,或手動複製設定。`--global` 讓 `~/.config/dbcli/config.json` 成為一個獨立的 v2 registry:`dbcli --global init --conn-name shared ...` 直接寫進去、不建立也不修改專案 binding,`dbcli --global use --list` / `--global query` 則在不依賴當前目錄的情況下操作它。scope 必須明確選取 —— 未帶 `--global` 時一切照舊走專案 binding,避免在不相關的專案裡誤用全域連線。全域檔案沿用與 home storage 專案設定相同的私有檔案權限與 integrity record。
|
|
745
|
+
|
|
461
746
|
- **`getDbcliConfigHome()` / `getGlobalConfigPath()` / `isGlobalConfigPath()` 加入 `public.ts`。** 前者把 per-user root 改為延遲解析並支援 `DBCLI_CONFIG_HOME` 覆寫,測試與 embedder 不必 reload module 就能隔離 config home。
|
|
462
747
|
|
|
463
748
|
### Changed
|
|
464
749
|
|
|
465
750
|
- **`migrate` 與 `queries` 子指令補上 Commander `command` 傳遞。** 這兩處原本以 `resolveConfigPath(undefined, opts)` 解析設定路徑,看不到 ancestor 的 root-level 旗標 —— 沒有 `--global` 時症狀被 `.dbcli` 預設值蓋掉,加上 `--global` 後就會靜默讀錯 registry。現在 36 個 `resolveConfigPath` 呼叫點全部傳入 command。
|
|
751
|
+
|
|
466
752
|
- **`resolveConfigPath` 的優先序明確化。** 顯式 `--config` 仍最優先(`--global --config <path>` 因此是確定的),其次是顯式 `--global`,最後才是 `.dbcli` 預設值。
|
|
467
753
|
|
|
468
754
|
## [1.44.1] - 2026-08-02 - `agent-core` 的 `loadEnvFile` 改用 node:fs,可在 Node 執行
|
|
@@ -490,31 +776,49 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
490
776
|
### Added
|
|
491
777
|
|
|
492
778
|
- **穩定的 `./agent-core` 子路徑匯出。** 以五個 runtime functions(env 載入、env reference、連線選取、名稱解析、lookahead 截斷)與三個型別形成 agent CLI 共用的 semver interface;`./core` 仍是 dbcli 專用介面。建置同時產出 ESM 與型別宣告,CI purity gate 禁止資料庫、adapter 或 CLI framework 相依滲入。
|
|
779
|
+
|
|
493
780
|
- **欄位投影 `--fields`。** SQL 與 MongoDB 通用;`--fields a,b` 取用、`--fields=-raw_response` 排除,兩種形式不可混用。MongoDB 會把 `projection`(find)或 `$project`(aggregate)下推給 driver,未明確指定時不回傳 `_id`。黑名單欄位不會因為被 `--fields` 點名而洩漏。
|
|
781
|
+
|
|
494
782
|
- **欄位值截斷 `--truncate`。** table 輸出預設在 120 個 Unicode code point 截斷並標記 `…(+N chars)`,以 code point 計數所以不會切壞中文與 emoji;`--no-truncate` 可關閉。`--format json` / `csv` 會拒絕此旗標而非靜默忽略。
|
|
783
|
+
|
|
495
784
|
- **從檔案或 stdin 讀查詢 `-f, --query-file`。** `-f -` 讀 stdin,可用 heredoc 傳含 `$regex`、巢狀日期物件的 MongoDB pipeline,完全避開 shell 引號問題。同時給檔案與位置參數會明確報錯。
|
|
785
|
+
|
|
496
786
|
- **單次連線指定。** 新增 `DBCLI_CONNECTION` 環境變數,`query` / `list` / `schema` / `export` / `check` 也接受子指令層級的 `--use`。優先序為 `--use` > `DBCLI_CONNECTION` > 儲存的預設值,兩者都不會改寫 `.dbcli/config.json`,因此平行執行不會互相污染。
|
|
787
|
+
|
|
497
788
|
- **唯讀多連線扇出 `--use a,b`。** 同一查詢對多個連線執行,JSON 回傳 `results` 陣列並逐一標示 `ok` / `error`,table 則分段標註連線名。單一連線失敗不會取消其他連線。彙總 exit code:全成功 `0`、部分失敗 `2`、全失敗或執行前拒絕 `1`。寫入語句、`--recovery`、`--ui` 與 CSV/HTML 輸出在扇出下一律拒絕。
|
|
498
789
|
|
|
499
790
|
### Changed
|
|
500
791
|
|
|
501
792
|
- **HTML dashboard 明示不完整與遮蔽結果。** `query`、`q` 與 HTML export 會把既有的截斷與 security metadata 傳入 dashboard;在 KPI、圖表與 raw table 之前顯示醒目提示,避免使用不完整資料得出結論。
|
|
793
|
+
|
|
502
794
|
- **截斷改為出現在結果本身。** dbcli 擁有的 row cap 會多取一列前瞻,因此能區分「剛好 N 筆」與「被砍到 N 筆」:table footer 顯示 `Rows: N (truncated; limit N)`、`--format json` 帶 `metadata.truncated` 與 `metadata.limit_applied`、CSV 附加 `# truncated; limit N` 註解行。`dbcli q` 的 snippet size guard 同樣依此回報,不再讓整數列數被誤讀為全集。
|
|
795
|
+
|
|
503
796
|
- **`dbcli export` 撞到 auto-limit 改為 fail closed。** 匯出檔沒有地方記錄資料被丟掉(jsonl 是一行一筆、MongoDB `--format json` 是裸陣列),stderr 警告又會在重導向後消失,因此改為 exit `1` 且不寫檔,要求以 `--no-limit` 或 `--limit N` 明確表態。Elasticsearch 匯出的 1000 筆上限同此處理。
|
|
797
|
+
|
|
504
798
|
- **CLI 錯誤輸出收斂。** 連線類錯誤在所有指令路徑都會被頂層 handler 攔截並格式化,stderr 首行即為人類可讀訊息,不再由 Bun 印出打包後的 code frame 與未解碼的中文跳脫序列。stack 改掛在 `-v` / `-vv` 之下,預設不輸出。
|
|
505
799
|
|
|
506
800
|
### Fixed
|
|
507
801
|
|
|
508
802
|
- **MySQL 8 schema introspection 相容預設 `ONLY_FULL_GROUP_BY`。** 外鍵查詢現在完整分組 referenced table,不再讓 `dbcli schema <table>` 在原廠預設設定下失敗。
|
|
803
|
+
|
|
509
804
|
- **已分類的連線錯誤不再被巢狀 adapter catch 重包。** `mapError` 直接保留既有 `ConnectionError` 的 identity、code、message 與 hints,消除 `Connection failed: Connection failed:` 重複前綴與分類退化。
|
|
805
|
+
|
|
510
806
|
- **stdout 管線與 Windows CI 修復。** redirected stdout 以完整同步寫入避免 64KB 截斷;測試 filesystem 與換行處理改為跨平台實作,Windows matrix 恢復全綠。
|
|
807
|
+
|
|
511
808
|
- **發布依賴安全更新。** 將 PostCSS 鎖定至 `8.5.25`、`brace-expansion` 鎖定至 `5.0.9`,清除 release gate 回報的 3 個 high-severity advisories;並統一 Prettier 格式,讓完整 9 階段發布檢查恢復全綠。
|
|
809
|
+
|
|
512
810
|
- **`--no-limit` 過去被靜默忽略。** Commander 會把 `--no-limit` 折進 `limit` 屬性(設為 `false`)而不會產生 `noLimit`,但 `query` / `q` / `export` 都讀 `options.noLimit`,導致這個旗標自始無效——`query` 仍套用 1000 筆上限,`q` 仍包 size guard。CLI 邊界現在會把 Commander 的否定形式轉回指令實際讀取的形狀。
|
|
811
|
+
|
|
513
812
|
- **`dbcli export` 的 SQL 路徑忽略 `--limit` 與 `--no-limit`。** 該分支未把選項傳給 QueryExecutor,任何 `--limit N` 都不生效。
|
|
813
|
+
|
|
514
814
|
- **`-v` / `-vv` 的 stack 開關過去對 `q` / `insert` / `update` / `delete` 無效。** 這四個指令自行輸出在地化訊息、繞過共用的錯誤呈現層,因此 verbose 對它們不會多印任何東西。改為共用同一個呈現函式:措辭維持不變,但 verbose 下會補上 stack。
|
|
815
|
+
|
|
515
816
|
- **Redis 的 size-guard warning 在 `query` 被丟棄。** adapter 早已算出 `REDIS_SIZE_TRUNCATE` / `REDIS_SIZE_REWRITE` / `REDIS_BLACKLIST_FILTERED`,但 `query` 分支完全沒讀 `result.warnings`——文件卻聲稱結果會帶 `warnings[]`。現在每則 warning 都會印到 stderr,且被裁切的回覆會回報 `truncated` / `limit_applied`,與其他引擎一致。
|
|
817
|
+
|
|
516
818
|
- **`--query-file -` 在互動式終端會無提示空等。** 改為立即拒絕並說明需要 piped input,與 repo 中其他 stdin 消費端(`insert`、`shell`、`audit`)既有的 TTY 檢查一致。
|
|
819
|
+
|
|
517
820
|
- **單一連線 (v1) 設定會靜默忽略 `--use` / `DBCLI_CONNECTION`。** v1 沒有具名連線可選,過去卻照樣執行那唯一的連線,讓使用者以為切換成功——正是 issue #7 要避免的情境。現在會明確報錯並指出升級為 v2 的方式。
|
|
821
|
+
|
|
518
822
|
- **skill assets 與 reference 補齊。** `assets/SKILL.md`、`SKILL.zh-TW.md` 與 `reference.md` 新增查詢工作流程旗標章節;`reference.md` 原本記載「MongoDB 不套用 auto-limit」與實際行為不符,已更正為套用於 filter 與未自帶 `$limit` 的 pipeline。
|
|
519
823
|
|
|
520
824
|
## [1.42.0] - 2026-07-20 - Drizzle Snapshot 與 ORM DDL 工作流擴充
|
|
@@ -522,16 +826,19 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
522
826
|
### Added
|
|
523
827
|
|
|
524
828
|
- **Drizzle Kit snapshot 可直接用於 ORM drift 比對。** `dbcli diff --against-orm` 新增 Drizzle snapshot 格式偵測與 `NormalizedSchema` adapter,支援 PostgreSQL v7 snapshot 的 table、column、primary key、unique constraint、index 與 foreign key metadata。
|
|
829
|
+
|
|
525
830
|
- **TypeORM/Sequelize DDL alias。** `--orm-format typeorm`、`typeorm-ddl`、`sequelize` 與 `sequelize-ddl` 可直接走既有 DDL adapter;自動忽略 `typeorm_metadata` 與 `SequelizeMeta` bookkeeping table,並補上 source-file 使用者的可執行匯出/比對指引。
|
|
526
831
|
|
|
527
832
|
### Changed
|
|
528
833
|
|
|
529
834
|
- **ORM drift 文件完整同步。** 英文/繁體中文的 Markdown 與 HTML 使用者文件、skill assets、各平台 plugin 副本及 reference 已補上 Drizzle snapshot、TypeORM/Sequelize DDL 的格式、限制與操作範例。
|
|
835
|
+
|
|
530
836
|
- **跨平台發版 metadata 對齊。** npm package、Codex/Claude/Cursor plugin、packaged Codex plugin 與 Gemini extension 統一為 `1.42.0`。
|
|
531
837
|
|
|
532
838
|
### Fixed
|
|
533
839
|
|
|
534
840
|
- **不支援的 ORM 輸入改為 fail closed。** Drizzle snapshot 會拒絕不支援的版本/dialect、generated/identity/enum/composite primary key 等結構,以及無法無損轉換的 column default;TypeORM/Sequelize source file 則回報完整的匯出 DDL recipe,不再被 JSON/DDL fallback 誤解析。
|
|
841
|
+
|
|
535
842
|
- **Qualified ignore identity 保留完整。** ORM drift 的 ignore 比對不再把 schema-qualified identity 降成 bare table name,避免同名 table 跨 schema 時被錯誤忽略;ORM DDL alias 也會正確沿用 DDL 輸入處理與 bookkeeping ignore。
|
|
536
843
|
|
|
537
844
|
## [1.41.0] - 2026-07-19 - ORM Drift 比對與無損 Schema Identity
|
|
@@ -539,18 +846,23 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
539
846
|
### Added
|
|
540
847
|
|
|
541
848
|
- **`dbcli diff --against-orm` ORM drift 比對。** 可將 Prisma schema、DDL/migration SQL 或 normalized JSON 與既有 SQL schema cache 比對;支援多檔 DDL、filesystem glob、格式自動偵測、大小寫敏感的 `--ignore` pattern,以及 JSON、table、Markdown 輸出。比對只讀本地 cache,不連線、不更新 cache,也不執行提案。
|
|
849
|
+
|
|
542
850
|
- **結構化 drift 分類與安全提案。** 報告區分 `missing_in_db`、`missing_in_orm`、`mismatch`、`unmanaged` 與 `unparsed`;只有計分後的 error 會使 drift exit code 為 `1`。可無損表達的缺漏欄位/index 會產生 shell-safe、預設 dry-run 的 `migrate` 提案,其餘情況升級至 `migration-review`。
|
|
851
|
+
|
|
543
852
|
- **`orm-drift-review` agent task pack。** 工作流依序執行 blacklist 檢查、schema cache 更新與 ORM drift JSON 比對,並要求將 dry-run DDL 與精確目標交給獨立 migration review。
|
|
544
853
|
|
|
545
854
|
### Changed
|
|
546
855
|
|
|
547
856
|
- **Schema identity 改為精確保存。** PostgreSQL schema/table 名稱不再正規化為小寫;quoted 與 unquoted identifier 依 SQL 規則解析,qualified name、ignore pattern、foreign key 與 drift output 都保留大小寫與 schema identity。
|
|
857
|
+
|
|
548
858
|
- **ORM drift 文件完整同步。** 英文/繁體中文的 Markdown 與 HTML 使用者文件、skill assets、各平台 plugin 副本及 reference 已補上格式、exit code、安全邊界與操作流程。
|
|
859
|
+
|
|
549
860
|
- **跨平台發版 metadata 對齊。** npm package、Codex/Claude/Cursor plugin、packaged Codex plugin 與 Gemini extension 統一為 `1.41.0`。
|
|
550
861
|
|
|
551
862
|
### Fixed
|
|
552
863
|
|
|
553
864
|
- **Lossy ORM drift proposal 改為 fail closed。** Schema-qualified target、dash-leading positional、無法無損表達的 index column、identity collision 與不支援語法不再輸出可能損壞的指令,而是阻擋或升級人工審查。
|
|
865
|
+
|
|
554
866
|
- **DDL/Prisma adapter identity 與語意硬化。** 多檔 DDL 共用 deterministic context,foreign key pairing、default schema resolution、table option/partition 阻擋、重複 index 去重與 Unicode code-point 穩定排序皆保留來源語意。
|
|
555
867
|
|
|
556
868
|
## [1.40.0] - 2026-07-19 - SQL Lint、安全強化與 Agent 工作流擴充
|
|
@@ -558,19 +870,25 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
558
870
|
### Added
|
|
559
871
|
|
|
560
872
|
- **新增唯讀 `dbcli lint` 靜態 SQL 顧問。** 支援 inline SQL、saved query、SQL 檔案與 glob/混合批次輸入,提供 text、JSON、Markdown 輸出、最低嚴重度篩選、`--no-schema` 與 `--recovery`;指令不連線、不執行 SQL,也不會自動套用 rewrite。
|
|
873
|
+
|
|
561
874
|
- **九條結構與 schema-aware lint 規則。** 涵蓋 `SELECT *`、未錨定 `LIKE`、深度 `OFFSET`、non-sargable predicate、`OR`/subquery 改寫機會、重複 `DISTINCT` + `GROUP BY`、implicit cast,以及 `NOT IN` 右側 NULL 風險;finding 可附 confidence 標籤的草稿與 shell-safe 驗證指令。
|
|
875
|
+
|
|
562
876
|
- **MongoDB agent task packs。** 新增 `mongo-safe-backfill` 與 `mongo-schema-drift-review`,補上 MongoDB 安全回填與 schema drift 檢視工作流。
|
|
563
877
|
|
|
564
878
|
### Changed
|
|
565
879
|
|
|
566
880
|
- **Slow-query guide 納入 lint。** `guide slow-query` 現在會先安排本機靜態分析,再銜接 explain 與診斷 snippets,brief plan 也保留執行 metadata。
|
|
881
|
+
|
|
567
882
|
- **Agent 與使用者文件完整同步。** `lint` 已寫入 skill assets、platform plugin 副本及英文/繁體中文 Markdown 與 HTML 文件;GitHub Pages 產品介紹頁同步完成雙語、可及性與行動裝置導覽重構。
|
|
883
|
+
|
|
568
884
|
- **跨平台發版 metadata 對齊。** npm package、Codex/Claude/Cursor plugin、packaged Codex plugin 與 Gemini extension 統一為 `1.40.0`。
|
|
569
885
|
|
|
570
886
|
### Fixed
|
|
571
887
|
|
|
572
888
|
- **Lint 採 fail-closed 安全邊界。** 解析失敗、schema binding 不明、identifier 大小寫碰撞、CTE/derived/qualified relation 與不安全 rewrite proof 會阻擋對應建議,不再借用不可靠的 cache facts。
|
|
889
|
+
|
|
573
890
|
- **`NOT IN` NULL 分析補齊 scope 與 provenance。** 遞迴處理巢狀 SELECT、CTE、derived statement、JOIN `ON`、`WHERE`、`HAVING`、outer-join null extension、nullable 投影與 CASE/cast/aggregate,並保留正確 traversal order。
|
|
891
|
+
|
|
574
892
|
- **Lint audit/recovery 遮蔽與驗證指令硬化。** positional、global、bulk 與 `--` 後的 SQL 都會遮蔽;只有結構上已證明唯讀的 SQL 才建議 `explain --analyze`,session assignment 與 function-bearing statement 會保守退回 plain explain。
|
|
575
893
|
|
|
576
894
|
## [1.39.2] - 2026-07-03 - Windows 跨平台、skill 安裝安全與 plugin 版本對齊
|
|
@@ -580,13 +898,17 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
580
898
|
### Fixed
|
|
581
899
|
|
|
582
900
|
- **Windows 跨平台修復(Windows CI 首次全綠)。** filesystem 操作與 path 檢查改為跨平台實作、修正 `emit` 子行程 import 與殘留的 path assertion,並以 portable `node:fs` 取代僅限 unix 的 coreutils spawns。此前 Windows job 從未通過(fail-fast 總是先取消它)。
|
|
901
|
+
|
|
583
902
|
- **Skill 安裝安全強化。** 修正 output / install 旗標衝突、強化安裝安全檢查與 task 過濾條件。
|
|
903
|
+
|
|
584
904
|
- **zh-TW skill 安裝不再被誤判為永遠過期。**
|
|
905
|
+
|
|
585
906
|
- **Skill 參考修正。** 移除文件中不存在的 `blacklist add`、補回缺漏的 reference flags。
|
|
586
907
|
|
|
587
908
|
### Changed
|
|
588
909
|
|
|
589
910
|
- **文件補齊。** 明示 `--where` 僅支援等值比較、補上 Redis / Elasticsearch 寫入模型說明、記錄 home-storage 綁定並重新同步 md/html parity、對齊 config-location-policy 與實作綁定模型。
|
|
911
|
+
|
|
590
912
|
- **Plugin manifest 版本對齊。** `.claude-plugin` / `.cursor-plugin` / `.codex-plugin` 及 `plugins/dbcli-agent` 的 `plugin.json` 版本更新為 1.39.2(先前漂移在 1.37.1 / 1.31.0,未跟上主版本;`plugin:sync` / `plugin:check` 只同步 skill 內容不同步版本)。
|
|
591
913
|
|
|
592
914
|
### Internal
|
|
@@ -640,6 +962,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
640
962
|
### Added
|
|
641
963
|
|
|
642
964
|
- **`dbcli verify rollback` 情境執行器(第三個內建 verify 情境)。** 透過已穩定的 scenario registry 註冊,以 preflight / after-write 兩種模式驗證「還原變更後資料庫是否回到預期的先前狀態」,且**永遠不執行**還原寫入 / DDL——只分析 `--statement` 並執行回讀斷言。以必填的 `--kind <ddl|dml>` 選擇還原語句文法:`ddl` 複用 `migration` 的單語句 `ALTER TABLE` 契約,`dml` 複用 `safe-backfill` 的 `UPDATE` plan 契約。安全邏輯完全複用兩個 sibling 情境的 classifier,無重複實作。artifact 沿用既有 subject kind(`ddl→migration`、`dml→backfill`)並以 `subject.command = 'verify rollback'` 記錄出處,因此 artifact schema 與版本不變。
|
|
965
|
+
|
|
643
966
|
- **巢狀 bash / zsh / fish shell 補全。** 以遞迴 command-tree metadata model 從指令樹生成巢狀子指令與旗標補全,並由共用 registry 驅動 REPL 的補全與分派;補全會排除 denylisted 指令。
|
|
644
967
|
|
|
645
968
|
### Changed
|
|
@@ -651,8 +974,11 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
651
974
|
### Added
|
|
652
975
|
|
|
653
976
|
- **`dbcli verify safe-backfill` 情境執行器。** 以 preflight / after-write 兩種模式驗證安全回填工作流,並**永遠不執行回填寫入**:preflight 依序跑黑名單、schema、目標表與唯讀 verify-query 防護後回傳 `ready` / `blocked` 並印出精確的 after-write 指令;after-write 重跑防護、執行回讀斷言,並寫入 v1 `VerificationArtifact`(狀態對應 `verified` / `not_verified` / `indeterminate`,防護失敗為 `blocked`)。
|
|
977
|
+
|
|
654
978
|
- **`dbcli verify migration` 情境執行器。** 對 schema migration 做 preflight / after-write 驗證,且**永遠不執行 DDL**:分析提案的 `ALTER TABLE`、跑唯讀防護、要求 DDL 目標與 `--table` 相符(schema-aware),after-write 後記錄 `migration` 主體的證據。MVP 僅接受單語句 `ALTER TABLE`,並阻擋 `CREATE TABLE` / `DROP TABLE` / `CREATE INDEX` 及多語句 DDL。
|
|
979
|
+
|
|
655
980
|
- **`ALTER TABLE` 目標識別字契約。** `verify migration` 的目標擷取改用 quote-aware tokenizer:支援 `table` / `schema.table` / `catalog.schema.table`,每區段可為未加引號名稱或雙引號 / 反引號 / 方括號識別字(含 `""`、`]]` 跳脫),因此 `"user accounts"`、`"tenant-1"."orders"` 等含空白或連字號的名稱皆可接受。無法完整解析的目標(未封閉引號、不支援的跳脫、超過三段)會 fail closed 並以「目標無法解析」為由阻擋,與 `must match --table` 的不符原因明確區分。
|
|
981
|
+
|
|
656
982
|
- **`verification summary --latest-only` 交接選項。** 於既有 summary 輸出之上額外回傳最新一筆有效 artifact,方便 agent 在交接時直接引用最新證據;無 artifact 時回傳 `latest: null` 並維持 exit 0,無效檔案不會被升入 `latest`。
|
|
657
983
|
|
|
658
984
|
### Changed
|
|
@@ -664,8 +990,11 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
664
990
|
### Added
|
|
665
991
|
|
|
666
992
|
- **`dbcli assert --write-verification-artifact` 橋接(opt-in)。** `assert` 的判定結果(verdict)現在可選擇性地寫成一份結果型 `VerificationArtifact`:透過 subject 解析器將斷言主體對應到 artifact 的 `subject`、依 pass/fail 對應驗證狀態,並以既有的原子寫入器落地於 `.dbcli/verification/`。省略旗標時行為完全不變、不寫入任何檔案;`safe-backfill-verify` 仍維持 plan-only。artifact 路徑一律相對於 cwd,與 `--config` 無關。
|
|
993
|
+
|
|
667
994
|
- **唯讀 `verification` 指令介面(inspect + 生命週期)。** 新增核心 artifact 讀取器(含 schema 驗證、filter / summarize / find 輔助函式),並以此建構出 `verification list`(表格輸出,支援 subject-kind 篩選)、`verification show`、`verification summary` 等唯讀檢視指令,讓 agent 能直接讀取與彙整既有驗證證據,而非自行解析檔案。
|
|
995
|
+
|
|
668
996
|
- **`verification prune` 保留期清理。** 依保留期(duration 解析)與全域 `--keep-latest` 規則挑選清理候選,全域 keep-latest 優先於各項篩選;具刪除安全防護(缺少 mtime 的檔案排除在外、預設 dry-run 預覽、`--execute` 才實際刪除),並在 execute 模式輸出 deleted / skipped 明細表。
|
|
997
|
+
|
|
669
998
|
- **完整 v1 證據驗證。** 對 `subject` / `evidence` / 選用欄位進行完整驗證,並加入執行期 evidence-kind 防護,確保讀取與寫入兩端對 schema v1 的解讀一致。
|
|
670
999
|
|
|
671
1000
|
## [1.34.0] - 2026-06-18 - Verification Artifact Writer
|
|
@@ -673,8 +1002,11 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
673
1002
|
### Added
|
|
674
1003
|
|
|
675
1004
|
- **驗證證據建構器(`buildVerificationArtifact`)。** 純函式,產生 schema v1 的 `VerificationArtifact`:可注入 `now` / `idFactory` 以利測試確定性、證據文字欄位上限 2000 字元(超過截斷並標註)、證據筆數上限 20(超過保留前 19 筆並補一筆 `manual` 截斷標記);拒絕非法狀態、空白 summary、空證據。集中化證據裁切,讓後續寫入器與指令介面不必各自重複截斷決策。
|
|
1005
|
+
|
|
676
1006
|
- **`safe-backfill-verify` 計畫的「已規劃」驗證中繼資料。** `dbcli skill tasks plan safe-backfill-verify --format json` 現在輸出一個 `verification` 區塊(`status: "planned"`,取計畫中最後一個 `assert` 步驟作為證據)。此為**已規劃**證據,**不代表**驗證已執行或通過,與結果型 `VerificationArtifact` 明確區隔。其他 task pack 不受影響。
|
|
1007
|
+
|
|
677
1008
|
- **驗證證據寫入器(`writeVerificationArtifact`)。** 將建構出的 artifact 以原子方式寫入 `.dbcli/verification/verification-<YYYYMMDD-HHMMSS>-<short-id>.json`:檔名完全由 artifact 內部產生(UTC 時間戳 + `[a-z0-9]` 淨化短 id,杜絕路徑穿越)、缺少目錄時自動建立、以 `link()` 獨佔建立確保不會靜默覆寫既有檔案、回傳寫入路徑。
|
|
1009
|
+
|
|
678
1010
|
- **`recover --apply --write-verification-artifact`(opt-in)。** 僅在 verify 步驟實際執行時,將 recovery 驗證結果寫成一份 `recovery-verify` artifact(狀態取合約 `verificationStatus`,附 `recoveryRef`)。省略旗標時行為完全不變、不寫入任何檔案;寫入失敗只記到 stderr,不影響結束碼。保留既有 `verifyStatus`、不嵌入任何指令輸出或機密。
|
|
679
1011
|
|
|
680
1012
|
## [1.33.0] - 2026-06-18 - Workflow Pack Expansion
|
|
@@ -682,6 +1014,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
682
1014
|
### Added
|
|
683
1015
|
|
|
684
1016
|
- **4 個新的 plan-only Agent Task Pack(皆唯讀)。** `pr-database-review`(PR 變更持久化路徑、查詢、migration 的資料庫風險審查)、`migration-review`(在套用 DDL 前擷取變更前 schema 證據並預覽 migration)、`safe-backfill-verify`(規劃安全 backfill 並產生 read-back `assert` 驗證指令)、`slow-endpoint-investigation`(串接 proxy / explain / missing-index 證據調查慢端點)。每個 pack 都以 `safety.mode: plan-only`、`risk: readonly` 步驟組成,只產生計畫、永不寫入;SQL 類 pack 先支援 `postgres` 與 `mysql`。
|
|
1017
|
+
|
|
685
1018
|
- **Skill 路由更新(en / zh-TW)。** 在 `SKILL.md` 與 `SKILL.zh-TW.md` 的 Agent Task Packs 段落各加入一段精簡導引,讓 agent 在自行組合手動的審查、migration、backfill、效能流程前,先選擇對應的 workflow pack;已重新同步所有 plugin / platform skill 副本。
|
|
686
1019
|
|
|
687
1020
|
## [1.32.0] - 2026-06-18 - Agent Task Packs Expansion & Skill Parity Guards
|
|
@@ -689,8 +1022,11 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
689
1022
|
### Added
|
|
690
1023
|
|
|
691
1024
|
- **4 個新的內建 Agent Task Pack(皆 `plan-only` 唯讀)。** `audit-permissions`(權限等級與 blacklist 覆蓋稽核)、`safe-backfill`(在寫入前做 blacklist + schema + 風險檢查的回填計畫)、`schema-drift-review`(快取/committed schema 與線上 schema 的漂移比對)、`connection-health`(連線可達性 / 設定 / 容量分級三步診斷)。皆走確定存在的唯讀指令;用 `dbcli skill tasks list` 瀏覽完整清單。
|
|
1025
|
+
|
|
692
1026
|
- **平台清單 parity 檢查(`scripts/check-platform-parity.ts`,`bun run platform:check`)。** 以 `SUPPORTED_PLATFORMS` 為單一真實來源,驗證 README、SKILL.md、SKILL.zh-TW.md、reference.md 與 CLI `--install` 選項描述的平台列舉完全一致(缺項或多項皆報錯),並掛進 `release-check.sh`。
|
|
1027
|
+
|
|
693
1028
|
- **語意 parity 守門。** `scripts/check-skill-parity.ts` 在結構比對外,新增 14 個語言不變的安全/命令 token(`query`/`insert`/`update`/`delete`/`export`/`schema`、`blacklist`、`--dry-run`/`--no-limit`/`--recovery`、`LIMIT 1000`、三個權限等級)在 EN 與 zh-TW 皆須對稱出現的檢查。
|
|
1029
|
+
|
|
694
1030
|
- **安裝與 context CLI 測試覆蓋。** 新增 `skill --install` 對 7 個平台寫入 temp HOME/cwd 的 smoke 測試(含 cursor/windsurf 的 root-rule + reference 雙檔結構),以及 `skill context` 的 xml/json/markdown、預設格式、無效格式與 blacklist 不外洩的 CLI 入口測試。
|
|
695
1031
|
|
|
696
1032
|
### Fixed
|
|
@@ -702,7 +1038,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
702
1038
|
### Added
|
|
703
1039
|
|
|
704
1040
|
- **`@carllee1983/dbcli/core` 公開匯出 `DataExecutor` 與資料執行型別。** 在 `./core` barrel 開出資料編輯介面(insert/update/delete 執行面),讓外部消費者(如 `dbcli-gui` sidecar)能重用與 CLI 同源的資料寫入能力,不必重寫 adapter 邏輯。CLI 行為不變。
|
|
1041
|
+
|
|
705
1042
|
- **Agent plugin 打包與 marketplace 安裝。** 將 dbcli 打包為 agent plugin(Ponytail 風格 marketplace install),新增 GitHub Copilot CLI plugin 支援與 Cursor plugin 安裝(add-plugin metadata、marketplace 提交路徑),並依各 agent 拆分安裝指令與文件。
|
|
1043
|
+
|
|
706
1044
|
- **開發者工作流 skill 指引(en/zh-TW)。** 在 dbcli skill 新增「Developer workflows」段落,把資料庫影響隱含於開發任務時的最小安全路徑(DB-backed 功能、資料錯誤排查、ORM/migration、PR 審查、慢查詢、回填、環境驗證)寫入 SKILL en/zh-TW 與各平台副本,並以可執行的指令錨點取代不可執行的 migrate 範例。
|
|
707
1045
|
|
|
708
1046
|
## [1.30.0] - 2026-06-09 - Connection Writer API
|
|
@@ -714,6 +1052,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
714
1052
|
### Fixed
|
|
715
1053
|
|
|
716
1054
|
- **`writeV2Config` 改為 atomic temp+rename 寫入**,避免寫入中斷時破壞設定庫。
|
|
1055
|
+
|
|
717
1056
|
- **`migrateV1ToV2` 對非 SQL 的 v1 連線 fail-loud 拒絕**,防止把不相容連線寫進 v2 設定庫。
|
|
718
1057
|
|
|
719
1058
|
## [1.29.0] - 2026-06-08 - Core Config-Read Entrypoint
|
|
@@ -753,6 +1092,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
753
1092
|
### Added
|
|
754
1093
|
|
|
755
1094
|
- **`dbcli snapshot <query>` — 結果指紋。** 將任一查詢結果轉成確定性、黑名單安全的 `ResultSnapshot`(`rowCount` + 每欄聚合:null/distinct 計數、min/max/sum、順序無關的 checksum)。預設落檔至 `.dbcli/snapshots/snap-<timestamp>.json`,亦支援 `--out`、`--stdout`、`--rows`(連同遮罩後的列一併存檔)、`--format`、`--no-limit`。
|
|
1095
|
+
|
|
756
1096
|
- **`dbcli assert <query>` — 行內不變量檢查。** 三種模式:`--expect`(`rows > 0`、`value == 5000`、`col:email not null`、`col:id unique`、`col:amount between 0 and 100`、`col:age >= 18`)、`--vs <query> --compare rows|value`(跨查詢對帳)、`--against <snapshot> --tolerance <pct>`(對既有快照基準比對)。預設失敗時 `exit 1`,可用 `--no-fail` 僅報告不改變 exit code。
|
|
757
1097
|
- 兩個指令均沿用既有 adapter / QueryExecutor / blacklist / audit 堆疊,黑名單欄位由 QueryExecutor 在源頭遮罩,指紋天生安全。目前支援 SQL 引擎(PostgreSQL / MySQL / MariaDB)。
|
|
758
1098
|
|
|
@@ -777,7 +1117,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
777
1117
|
### Added
|
|
778
1118
|
|
|
779
1119
|
- **`dbcli explain` 一級指令。** 把 `EXPLAIN` / `ANALYZE SELECT` / `EXPLAIN (ANALYZE, BUFFERS) SELECT` 包成統一介面,單條 query、`@saved-query`、`@file.sql`、`@glob/*` 通吃。輸出統一的 `ExplainRow` schema,附 5 條 actionable annotations(`full-scan` / `temp-table` / `filesort` / `cost-estimate-skew` / `nested-loop-large`)。輸出格式 markdown(預設)/ json / table。支援 `--bulk` 多筆批次。MariaDB + MySQL + PostgreSQL。(v1.23 P2)
|
|
1120
|
+
|
|
780
1121
|
- **`dbcli guide missing-index-for` 單條 query 複合索引顧問。** 解析一條 `SELECT`,結合真實 `EXPLAIN` 計畫與既有索引,輸出帶 `confidence`(high/medium/low)與 `reason` 的索引候選;偵測既有索引碰撞(single-col 可擴成 composite),並把函式/運算式欄位與無法解析的 SQL 列為 `warnings`。輸出格式 yaml(預設)/ json / markdown,支援 `--min-confidence` 過濾。唯讀(僅 EXPLAIN + 索引內省)。(v1.23 P3)
|
|
1122
|
+
|
|
781
1123
|
- **`dbcli inspect` 情境感知 `suggestedCommands` 與新的 `hints` 欄位。** `suggestedCommands` 改為三層加權(bootstrap / context-aware / discovery):collector 讀近 10 條 audit 找出最熱門資料表,有 task pack 時自動建議 `skill tasks plan analyze-table-perf --param table=<table>` 與 `skill tasks list`。新增與 `suggestedCommands` 平行的 `hints` 欄位(JSON 機器可讀 + markdown `## Hints`),提示最熱門資料表、可用 task pack 數量與 schema 快取概況。新增內建 task pack `analyze-table-perf`(唯讀 `plan-only`,吃必填 `table` 參數)。audit 讀取唯讀且永不 throw。(v1.23 P4)
|
|
782
1124
|
|
|
783
1125
|
### Fixed
|
|
@@ -797,7 +1139,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
797
1139
|
### Added
|
|
798
1140
|
|
|
799
1141
|
- **Elasticsearch interactive shell.** `dbcli shell` 對 ES 連線開啟 Kibana Dev Tools 風格 REPL:輸入請求行 `<METHOD> /<path>` 加上可選的多行 JSON body,以空白行送出整個區塊,回應以美化 JSON 呈現。以讀取為主 — index 層級黑名單於前端直接拒絕受保護 index;`_search` 若 body 未指定 `size` 自動上限 1000 筆。(P1)
|
|
1142
|
+
|
|
800
1143
|
- **Elasticsearch export.** `dbcli export` 對 ES 連線支援兩種形式:傳入 search DSL 並以 `--index` 指定索引以匯出命中結果,或直接以 index 名稱當作查詢、透過 `match_all` + scroll 匯出整個索引。輸出 JSON / JSONL / CSV,預設上限 1000 筆(`--no-limit` 匯出全索引,以 scroll 分批串流)。匯出前套用索引層級黑名單檢查,並寫入稽核紀錄。(P2)
|
|
1144
|
+
|
|
801
1145
|
- **Redis value / hash-field 遮罩。** 新增 `.dbcli` `redis.mask` 設定區塊:key 命中 `keyPattern` glob 者,其值(或指定的 hash `fields`)於讀取時(`GET`、`GETRANGE`、`HGETALL`、`HGET`、`HMGET`、`HVALS`)回傳 `[REDACTED]`。遮罩與既有 key-glob 拒絕黑名單並存,且**拒絕一律優先於遮罩**。(P3)
|
|
802
1146
|
|
|
803
1147
|
### Fixed
|
|
@@ -817,7 +1161,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
817
1161
|
### Added
|
|
818
1162
|
|
|
819
1163
|
- **Redis shell.** `dbcli shell` 現對 Redis 連線開啟互動式 REPL,具備歷史、readline、tab 補全(指令 + key 前綴)與 `.no-limit on/off` meta 指令。單行語意。
|
|
1164
|
+
|
|
820
1165
|
- **Redis size guard.** `SCAN` / `HSCAN` / `SSCAN` / `ZSCAN` 在缺少時補上 `COUNT 1000`;`LRANGE` / `ZRANGE` / `ZREVRANGE` 夾限 `stop`;`ZRANGEBYSCORE` 補上 `LIMIT 0 1000`。`HGETALL` / `HKEYS` / `HVALS` / `SMEMBERS` / `KEYS` 的無上限回覆在 client 端截斷至 1000 並帶 `REDIS_SIZE_TRUNCATE` 警告。`--no-limit` 略過所有防護。
|
|
1166
|
+
|
|
821
1167
|
- **Redis blacklist 強制。** `dbcli blacklist add 'pattern'` 現會封鎖 key 命中的 Redis 讀寫。採 Redis 原生 glob(`*`、`?`、`[abc]`、`[a-z]`)。與黑名單重疊的 `KEYS` / `SCAN MATCH` 會被拒絕;未重疊的掃描則濾掉黑名單 keys 並帶 `REDIS_BLACKLIST_FILTERED` 警告。稽核記錄含 `metadata.rejection_reason: 'blacklist'` 與 `matched_pattern`。
|
|
822
1168
|
|
|
823
1169
|
### Changed
|
|
@@ -834,14 +1180,19 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
834
1180
|
### Added
|
|
835
1181
|
|
|
836
1182
|
- **MongoDB MVP 全套支援。** `q` 指令現以 limited-supported 等級納入 MongoDB(`find` / `aggregate` 兩種 snippet body),路由經過專屬分支與 field-masker;`schema` 採 `$sample` + 遞迴 path 偵測(含 BSON 型別),新增 `--sample-method` 旗標;`query` / `export` 套用 `maskMongoRows` 對巢狀結構遞迴遮罩。
|
|
1183
|
+
|
|
837
1184
|
- **MongoDB blacklist 強化。** 新增 path-matcher(exact / dotted / suffix-wildcard)、field-masker 遞迴遮罩、insert / update 在寫入前強制套用 nested-path blacklist;`blacklist list` 對 collection 上的 middle-`*` pattern 發出警告。
|
|
1185
|
+
|
|
838
1186
|
- **MongoDB 安全模型升級。** update operator 從硬性 allowlist 改為分級安全(tiered operator safety);schema 對 blacklist 欄位直接 redact;`cache` / `doctor` 暴露 `sampleMethod`。
|
|
1187
|
+
|
|
839
1188
|
- **MongoDB snippets 一級公民化。** 內建 reference snippets(find + aggregate)、`queries list/search/suggest` 將 MongoDB snippets 與 SQL 引擎並列;`mongoStrategy` 驗證 body 與 params 並支援 map 形式插值。
|
|
1189
|
+
|
|
840
1190
|
- **Recovery — per-code branching for connection codes (MVP)。** `recover --next` 對 connection 類錯誤碼支援多 branch 派發:新增 `buildConnectionBranches` factory(4 個 connection branch)、`matchConnectionBranch` resolver、`classify` emit `branches` / `branchFork`,並提供 `--branch <id>` 旗標讓 agent 顯式選擇 branch。輸出 `NextResult.branchId` 與 markdown 中的 branchId/description 一併呈現。
|
|
841
1191
|
|
|
842
1192
|
### Changed
|
|
843
1193
|
|
|
844
1194
|
- **MongoDB `q` 文件升級。** `docs/feature-matrix.md` / 雙語 user docs 將 MongoDB `q` 從 unsupported 改為 limited supported(記載目前支援的 body 形式與限制)。
|
|
1195
|
+
|
|
845
1196
|
- **Recovery schema 新增 `branches` / `branchFork`。** 行為向下相容(無 branch 時與舊版一致);`GuideStep` / `NextResult` / `NextStepOutput` 全鏈打通 `branchId`;`shellQuote` 抽離為共用模組。
|
|
846
1197
|
|
|
847
1198
|
### Security
|
|
@@ -872,7 +1223,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
872
1223
|
### Changed
|
|
873
1224
|
|
|
874
1225
|
- **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.
|
|
1226
|
+
|
|
875
1227
|
- **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>`.
|
|
1228
|
+
|
|
876
1229
|
- **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.
|
|
877
1230
|
|
|
878
1231
|
### Tests
|
|
@@ -939,7 +1292,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
939
1292
|
### Added
|
|
940
1293
|
|
|
941
1294
|
- **Expanded Antigravity Protocol**: Added Phase 0 (Scout) for research and Phase 3 (Auditor) for validation to the core agentic workflow.
|
|
1295
|
+
|
|
942
1296
|
- **Enhanced Agent Support**: `dbcli skill --install` now supports **Codex (OMX)** and **Windsurf**.
|
|
1297
|
+
|
|
943
1298
|
- **Cursor Rules Update**: `dbcli skill --install cursor` now uses the modern `.cursor/rules/*.mdc` project-local format.
|
|
944
1299
|
- New `GEMINI.md` project-level instruction file with full Antigravity lifecycle guidance.
|
|
945
1300
|
|
|
@@ -1084,11 +1439,13 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1084
1439
|
### Fixed
|
|
1085
1440
|
|
|
1086
1441
|
- **Packaged `dist/cli.mjs` 找不到 assets**:1.10.0 bundle 在 `task-paths.ts` / `snippet-paths.ts` 用 `import.meta.dir + ../../../` 解析 builtin 目錄,bundle 後三層往上會跳出 package root,npm 全域安裝的使用者執行 `dbcli queries list` / `dbcli skill tasks list` 讀不到資源。抽出 `src/utils/package-root.ts` 以 `package.json` 走訪定位 root,dev 與 bundle 都正確;`skill.ts` 內既有的 `findPackageRoot` 也收斂到同一處。
|
|
1442
|
+
|
|
1087
1443
|
- **`dbcli q` 略過 blacklist 檢查(安全)**:`q.ts` 把空字串當作 `tableName` 傳給 `BlacklistValidator.filterColumns`,column-level redaction 永遠不命中;同時也沒呼叫 `checkTableBlacklist`,使用者可以透過 saved snippet 直接 SELECT 黑名單表/欄位繞開保護。改為從 `prepared.rewrittenSql` 抽出主表(SQL)或 `prepared.execHints.index`(ES),執行前先 `checkTableBlacklist('SELECT', target)`,並把真正的 `tableName` 餵給 `filterColumns`;Redis 維持原樣。
|
|
1088
1444
|
|
|
1089
1445
|
### Added
|
|
1090
1446
|
|
|
1091
1447
|
- **dist/ 整合 smoke 測試**:`tests/integration/dist-smoke.test.ts` 從 OS tmpdir 執行 `dist/cli.mjs`,覆蓋 `--version`、`skill --output`、`queries list`、`skill tasks list`,守住 packaged assets path 不再回退。
|
|
1448
|
+
|
|
1092
1449
|
- **`q` blacklist 迴歸測試**:`tests/unit/commands/q-blacklist.test.ts` 覆蓋黑名單表阻擋、欄位 redact、未受影響 snippet 三種情境。
|
|
1093
1450
|
|
|
1094
1451
|
### Changed
|
|
@@ -1118,7 +1475,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1118
1475
|
### Changed
|
|
1119
1476
|
|
|
1120
1477
|
- **Redis 驅動**:改用 Bun 內建 `RedisClient`,移除外部 `ioredis` 依賴。
|
|
1478
|
+
|
|
1121
1479
|
- **Elasticsearch adapter**:refactor 並收斂錯誤訊息與 ExecutionResult 形狀,與 SQL / Mongo / Redis 對齊。
|
|
1480
|
+
|
|
1122
1481
|
- **文件**:`assets/SKILL.md` 與 `assets/reference.md` 補上 ES / Redis snippet 工作流;`docs/feature-matrix.md` 更新 saved-queries 欄位。
|
|
1123
1482
|
|
|
1124
1483
|
### Fixed
|
|
@@ -1165,6 +1524,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1165
1524
|
### Fixed
|
|
1166
1525
|
|
|
1167
1526
|
- **`insert` / `update` / `delete` / `export` / `diff` 對 Redis / Elasticsearch 的早期錯誤訊息**:先前會落入 SQL DataExecutor 出現「Column ... not found in table」之類誤導訊息,現在直接回傳明確的「不支援」JSON,並指引正確替代路徑(Redis 改用 `query`、Elasticsearch 改用外部工具或 `query --index`)。
|
|
1527
|
+
|
|
1168
1528
|
- **TypeScript 嚴格度**:`bun run typecheck` 從 43 個錯誤降為 0。
|
|
1169
1529
|
- `ConnectionConfig` union 加入 `ElasticsearchConnectionConfig`。
|
|
1170
1530
|
- `ResolvedConnection.connection.system`、`ReplContext.system` 涵蓋 `'elasticsearch'`。
|
|
@@ -1193,6 +1553,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1193
1553
|
- Discovery: Implemented schema inspection for MongoDB collections.
|
|
1194
1554
|
- Diagnostics: Added comprehensive MongoDB environment and connection diagnostics to `dbcli doctor`.
|
|
1195
1555
|
- **Improved AI Skill Installation**: `dbcli skill --install` now deploys both `SKILL.md` (high-level workflow) and `reference.md` (full command syntax and examples) to target platforms (Claude Code, Gemini CLI, Copilot, Cursor).
|
|
1556
|
+
|
|
1196
1557
|
- **Security model enhancement**: `dbcli init` now defaults to a more secure storage model, placing sensitive connection details in `~/.config/dbcli/` rather than the local project workspace.
|
|
1197
1558
|
|
|
1198
1559
|
### Changed
|
|
@@ -1204,6 +1565,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1204
1565
|
### Fixed
|
|
1205
1566
|
|
|
1206
1567
|
- **Doctor diagnostics for MongoDB SRV**: `dbcli doctor` now reports whether the current execution environment can resolve `mongodb+srv://` connections directly or only through the DNS-over-HTTPS fallback used by the MongoDB adapter.
|
|
1568
|
+
|
|
1207
1569
|
- **Documentation**: Clarified the new MongoDB SRV environment diagnostic in README, README.zh-TW, and `assets/SKILL.md`.
|
|
1208
1570
|
|
|
1209
1571
|
## [1.5.1] - 2026-04-22
|
|
@@ -1211,6 +1573,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1211
1573
|
### Fixed
|
|
1212
1574
|
|
|
1213
1575
|
- **MongoDB SRV Connections**: `mongodb+srv://` URIs are now expanded and connected through the MongoDB adapter, and MongoDB operations consistently use the configured database.
|
|
1576
|
+
|
|
1214
1577
|
- **MongoDB Documentation**: Clarified SRV URI support and configured-database behavior in README, README.zh-TW, and `assets/SKILL.md`.
|
|
1215
1578
|
|
|
1216
1579
|
## [1.5.0] - 2026-04-21
|
|
@@ -1222,6 +1585,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1222
1585
|
- Layered schema loading (Hot/Cold) integrated into `configModule`.
|
|
1223
1586
|
- Per-connection isolation: Each connection now has its own schema directory (`.dbcli/schemas/<connection>/`).
|
|
1224
1587
|
- **Improved Migration UX**: Added proactive hints during schema migration to ensure data consistency.
|
|
1588
|
+
|
|
1225
1589
|
- **Documentation Update**: Added per-connection schema isolation details to `SKILL.md` for AI agents.
|
|
1226
1590
|
- Clarified schema storage layout in `.dbcli/schemas/`.
|
|
1227
1591
|
- Added usage examples for `--use <connection>` with schema commands.
|
|
@@ -1256,6 +1620,7 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1256
1620
|
- Intelligent SQL generation per database dialect.
|
|
1257
1621
|
- Default dry-run mode for safety.
|
|
1258
1622
|
- **Enhanced Data Health Checks**: Added `rowCount` and `size` checks to the `dbcli check` command.
|
|
1623
|
+
|
|
1259
1624
|
- **Comprehensive Documentation**: Updated README (en/zh-TW) with Internals & Strategy sections and new command references.
|
|
1260
1625
|
|
|
1261
1626
|
### Changed
|
|
@@ -1271,7 +1636,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1271
1636
|
### Changed
|
|
1272
1637
|
|
|
1273
1638
|
- **Adapter `execute()` 回傳型別重構**: 從 `T[]` 改為 `ExecutionResult<T>`,包含 `rows`、`affectedRows`、`lastInsertId` 欄位,DML 操作(INSERT/UPDATE/DELETE)現在回傳正確的 affected rows 計數
|
|
1639
|
+
|
|
1274
1640
|
- **Export 覆寫確認**: `export --output` 寫入已存在檔案時會提示確認,可用 `--force` 跳過
|
|
1641
|
+
|
|
1275
1642
|
- **`ExecutionResult<T>` 介面**: 新增統一的查詢結果型別定義於 `src/adapters/types.ts`
|
|
1276
1643
|
|
|
1277
1644
|
---
|
|
@@ -1283,7 +1650,9 @@ Automation that performs unqualified full-table writes stops working. Three shap
|
|
|
1283
1650
|
dbcli v1.0.0 is the first stable release. All three milestones are complete:
|
|
1284
1651
|
|
|
1285
1652
|
- **M1 (v0.6.0):** Smart REPL — interactive shell with SQL + dbcli commands
|
|
1653
|
+
|
|
1286
1654
|
- **M2 (v0.8.0):** Schema DDL — CREATE/DROP/ALTER TABLE, INDEX, CONSTRAINT, ENUM
|
|
1655
|
+
|
|
1287
1656
|
- **M3 (v1.0.0):** Stabilization — documentation, permission matrix, known limitations update
|
|
1288
1657
|
|
|
1289
1658
|
### Added
|
|
@@ -1299,22 +1668,31 @@ dbcli v1.0.0 is the first stable release. All three milestones are complete:
|
|
|
1299
1668
|
- PostgreSQL: SERIAL, native ENUM types, ALTER COLUMN TYPE, double-quote identifiers
|
|
1300
1669
|
- MySQL: AUTO_INCREMENT, inline ENUM, MODIFY COLUMN, backtick identifiers
|
|
1301
1670
|
- **DDLExecutor**: Unified execution pipeline — admin permission check → blacklist protection → SQL generation → dry-run/execute → schema cache auto-refresh
|
|
1671
|
+
|
|
1302
1672
|
- **Default dry-run for DDL**: All `migrate` commands preview SQL without `--execute`. Destructive operations also require `--force`
|
|
1673
|
+
|
|
1303
1674
|
- **142 new tests**: column-parser (17), PG DDL (35), MySQL DDL (25), factory (5), DDL executor (22), schema cache DDL (6), CLI migrate (26), live-db migrate lifecycle (6)
|
|
1304
1675
|
|
|
1305
1676
|
### Fixed
|
|
1306
1677
|
|
|
1307
1678
|
- **Schema comment encoding**: Fixed double-encoded UTF-8 comments from MySQL/MariaDB `information_schema` (e.g., `帳號` → `帳號`)
|
|
1679
|
+
|
|
1308
1680
|
- **MySQL connection charset**: Added `charset: utf8mb4` and `SET NAMES utf8mb4`
|
|
1681
|
+
|
|
1309
1682
|
- **DDL multi-line SQL execution**: Fixed statement splitting to use `;\n` instead of `\n`
|
|
1683
|
+
|
|
1310
1684
|
- **MySQL DROP INDEX**: Added `--table` option (MariaDB requires `ON <table>`)
|
|
1311
1685
|
|
|
1312
1686
|
### Changed
|
|
1313
1687
|
|
|
1314
1688
|
- **Permission model**: 4 levels — query-only, read-write, data-admin, admin (DDL requires admin)
|
|
1689
|
+
|
|
1315
1690
|
- **Known Limitations**: Removed "Read-only schema" and "CLI-only" (both resolved). Added "No migration version tracking" as post-v1.0 item
|
|
1691
|
+
|
|
1316
1692
|
- **Test infrastructure**: `docker-compose.test.yml` for MySQL 8 + PostgreSQL 16 integration testing
|
|
1693
|
+
|
|
1317
1694
|
- **Package scripts**: Added `test:unit`, `test:integration`, `test:docker`
|
|
1695
|
+
|
|
1318
1696
|
- **SKILL.md**: Updated with full `migrate` command reference and AI agent guidelines
|
|
1319
1697
|
|
|
1320
1698
|
### Test Results (v1.0.0)
|
|
@@ -1332,14 +1710,19 @@ dbcli v1.0.0 is the first stable release. All three milestones are complete:
|
|
|
1332
1710
|
### Fixed
|
|
1333
1711
|
|
|
1334
1712
|
- **Schema comment encoding**: Fixed double-encoded UTF-8 comments from MySQL/MariaDB `information_schema`. Comments stored through latin1 (cp1252) connections now correctly display CJK characters (e.g., `帳號` → `帳號`)
|
|
1713
|
+
|
|
1335
1714
|
- **MySQL connection charset**: Added `charset: utf8mb4` and `SET NAMES utf8mb4` to MySQL adapter connections
|
|
1336
1715
|
|
|
1337
1716
|
### Added
|
|
1338
1717
|
|
|
1339
1718
|
- **`fixDoubleEncodedUtf8()` utility** (`src/utils/encoding.ts`): Detects and reverses cp1252-to-UTF-8 double encoding with full cp1252 reverse mapping table. Applied to schema comments in both MySQL and PostgreSQL adapters
|
|
1719
|
+
|
|
1340
1720
|
- **`docker-compose.test.yml`**: MySQL 8.4 (port 3307) + PostgreSQL 16 (port 5433) for integration testing, with health checks and tmpfs for fast ephemeral storage
|
|
1721
|
+
|
|
1341
1722
|
- **Environment-driven adapter tests**: `mysql.test.ts` and `postgresql.test.ts` now read connection from `MYSQL_*` / `PG_*` env vars, falling back to docker-compose defaults. Auto-skip when DB is unreachable
|
|
1723
|
+
|
|
1342
1724
|
- **`live-db.test.ts`**: 55 comprehensive CLI-level integration tests covering all commands against live MariaDB — list, schema, query, blacklist CRUD, insert/update/delete lifecycle, export, check, diff, status, doctor, shell, format validation, SQL injection protection
|
|
1725
|
+
|
|
1343
1726
|
- **New test scripts**: `test:unit`, `test:integration`, `test:docker` in package.json
|
|
1344
1727
|
|
|
1345
1728
|
### Test Results
|
|
@@ -1357,16 +1740,27 @@ dbcli v1.0.0 is the first stable release. All three milestones are complete:
|
|
|
1357
1740
|
### Added
|
|
1358
1741
|
|
|
1359
1742
|
- **`dbcli shell` command:** Interactive database shell with SQL execution and dbcli command dispatch
|
|
1743
|
+
|
|
1360
1744
|
- **SQL-only mode:** `--sql` flag restricts to SQL statements only
|
|
1745
|
+
|
|
1361
1746
|
- **Auto-completion (Tab):** Context-aware completion for SQL keywords, table names, column names, and dbcli commands
|
|
1747
|
+
|
|
1362
1748
|
- **Multi-line SQL:** Accumulates input until `;` is found, with `...>` continuation prompt
|
|
1749
|
+
|
|
1363
1750
|
- **SQL syntax highlighting:** Real-time colorization of keywords, strings, and numbers in verbose mode
|
|
1751
|
+
|
|
1364
1752
|
- **Meta commands:** `.help`, `.quit`/`.exit`, `.clear`, `.format`, `.history`, `.timing`
|
|
1753
|
+
|
|
1365
1754
|
- **Persistent history:** Stored in `~/.dbcli_history` (max 1000 entries), with up/down navigation and Ctrl+R search
|
|
1755
|
+
|
|
1366
1756
|
- **Permission & blacklist integration:** Full enforcement within REPL session — SQL goes through PermissionGuard, query results go through blacklist filtering
|
|
1757
|
+
|
|
1367
1758
|
- **Auto-reconnect:** Attempts to reconnect once on connection errors, then displays error without crashing the session
|
|
1759
|
+
|
|
1368
1760
|
- **Error resilience:** SQL/permission/connection errors never crash the session
|
|
1761
|
+
|
|
1369
1762
|
- **i18n support:** All shell messages available in English and Traditional Chinese
|
|
1763
|
+
|
|
1370
1764
|
- **102 new tests:** input-classifier (25), multiline-buffer (10), meta-commands (15), completer (17), history-manager (8), command-dispatcher (12), repl-engine (12), shell-command (3)
|
|
1371
1765
|
|
|
1372
1766
|
---
|
|
@@ -1376,9 +1770,13 @@ dbcli v1.0.0 is the first stable release. All three milestones are complete:
|
|
|
1376
1770
|
### Fixed
|
|
1377
1771
|
|
|
1378
1772
|
- **`init --use-env-refs` permission bug**: Interactive env-ref mode now correctly offers all 4 permission levels (was missing `data-admin`)
|
|
1773
|
+
|
|
1379
1774
|
- **`init` i18n completeness**: All 10 hardcoded English messages replaced with i18n keys (supports en/zh-TW)
|
|
1775
|
+
|
|
1380
1776
|
- **`init` duplicate code**: Extracted shared `.dbcli exists` overwrite check into `checkOverwrite()` helper
|
|
1777
|
+
|
|
1381
1778
|
- **`--use-env-refs` help text**: Improved option description to clarify CI/CD and multi-env use case
|
|
1779
|
+
|
|
1382
1780
|
- **Documentation**: Added `--use-env-refs` to README (en/zh-TW), CHANGELOG, and SKILL.md with AI agent guidance
|
|
1383
1781
|
|
|
1384
1782
|
---
|
|
@@ -1388,13 +1786,17 @@ dbcli v1.0.0 is the first stable release. All three milestones are complete:
|
|
|
1388
1786
|
### Added
|
|
1389
1787
|
|
|
1390
1788
|
- **Database version check**: Warns on stderr when connected database version is below minimum supported (PostgreSQL 12+, MySQL 8.0+, MariaDB 10.5+). Non-blocking — connection proceeds normally.
|
|
1789
|
+
|
|
1391
1790
|
- **`dbcli doctor` DB version check**: New "Database version" item in Connection & Data group.
|
|
1791
|
+
|
|
1392
1792
|
- **`dbcli init --use-env-refs`**: Store environment variable references (`{"$env": "DB_HOST"}`) in config instead of actual values. Supports interactive and non-interactive modes with `--env-host`, `--env-port`, `--env-user`, `--env-password`, `--env-database` options. Suitable for CI/CD and multi-environment deployments.
|
|
1393
1793
|
|
|
1394
1794
|
### Fixed
|
|
1395
1795
|
|
|
1396
1796
|
- **`init` permission bug**: Interactive env-ref mode now correctly offers all 4 permission levels (was missing `data-admin`)
|
|
1797
|
+
|
|
1397
1798
|
- **`init` i18n**: All hardcoded English messages in init command replaced with i18n keys (10 messages)
|
|
1799
|
+
|
|
1398
1800
|
- **`init` duplicate code**: Extracted shared `.dbcli exists` overwrite check into `checkOverwrite()` helper
|
|
1399
1801
|
|
|
1400
1802
|
---
|
|
@@ -1406,16 +1808,27 @@ dbcli v1.0.0 is the first stable release. All three milestones are complete:
|
|
|
1406
1808
|
### Added
|
|
1407
1809
|
|
|
1408
1810
|
- **Color system** (`picocolors`): Semantic color helpers (`success`/`error`/`warn`/`info`/`dim`/`bold`) with automatic `NO_COLOR` support
|
|
1811
|
+
|
|
1409
1812
|
- **SQL syntax highlighting**: Keywords (blue bold), strings (green), numbers (yellow) — applied in verbose mode and dry-run preview
|
|
1813
|
+
|
|
1410
1814
|
- **Leveled logger**: Four levels — quiet (`-q`), normal (default), verbose (`-v`), debug (`-vv`) — all output to stderr to keep stdout clean for structured data
|
|
1815
|
+
|
|
1411
1816
|
- **`--no-color` global flag**: Disable colored output; also respects `NO_COLOR` environment variable (<https://no-color.org/>)
|
|
1817
|
+
|
|
1412
1818
|
- **`-v, --verbose` global flag**: Increase verbosity (`-v` = verbose, `-vv` = debug)
|
|
1819
|
+
|
|
1413
1820
|
- **`-q, --quiet` global flag**: Suppress non-essential output
|
|
1821
|
+
|
|
1414
1822
|
- **`dbcli doctor` command**: Full self-diagnostic — checks Bun version, dbcli version (npm registry), config validity, permission level, blacklist completeness (detects unprotected sensitive columns like `password`/`token`/`secret`), database connectivity, schema cache freshness, and large table warnings (> 1M rows). Supports `--format json` for AI agents. Exits with code 1 on errors.
|
|
1823
|
+
|
|
1415
1824
|
- **`dbcli completion` command**: Shell auto-completion script generation for bash, zsh, and fish. `--install` flag auto-writes to the shell rc file using idempotent marker blocks.
|
|
1825
|
+
|
|
1416
1826
|
- **`dbcli upgrade` command**: Self-update from npm registry. `--check` flag for check-only mode.
|
|
1827
|
+
|
|
1417
1828
|
- **Background version check**: Every command silently checks the npm registry (at most once per 24 hours, cached in `.dbcli/version-check.json`). Shows a one-line hint to stderr after the command completes if a newer version is available. Suppressed by `--quiet`.
|
|
1829
|
+
|
|
1418
1830
|
- **Table formatter colorization**: Table headers now display in bold
|
|
1831
|
+
|
|
1419
1832
|
- **62 new tests**: colors (7), sql-highlight (6), logger (10), doctor (12), completion (8), upgrade/version-check (19)
|
|
1420
1833
|
|
|
1421
1834
|
### Dependencies
|
|
@@ -1437,12 +1850,19 @@ Added table and column-level blacklisting to protect sensitive data from AI agen
|
|
|
1437
1850
|
- `blacklist table add/remove <table>` — manage table-level blacklist
|
|
1438
1851
|
- `blacklist column add/remove <table>.<column>` — manage column-level blacklist
|
|
1439
1852
|
- **Table-level blacklisting:** Reject all operations (query, insert, update, delete) on blacklisted tables
|
|
1853
|
+
|
|
1440
1854
|
- **Column-level blacklisting:** Automatically omit blacklisted columns from SELECT results
|
|
1855
|
+
|
|
1441
1856
|
- **Security notifications:** Footer in table/CSV/JSON output when columns are filtered (e.g., "Security: 2 column(s) were omitted based on your blacklist")
|
|
1857
|
+
|
|
1442
1858
|
- **Context-aware override:** `DBCLI_OVERRIDE_BLACKLIST=true` environment variable for temporary bypass with warning
|
|
1859
|
+
|
|
1443
1860
|
- **i18n support:** Blacklist messages in English and Traditional Chinese
|
|
1861
|
+
|
|
1444
1862
|
- **Performance:** < 1ms overhead per query (O(1) Set/Map lookups)
|
|
1863
|
+
|
|
1445
1864
|
- **103 new tests:** 83 core + 12 CLI wiring + 8 formatter security tests
|
|
1865
|
+
|
|
1446
1866
|
- **`dbcli schema --reset`:** Clear all existing schema data and re-fetch from database — solves stale schema after switching DB connections
|
|
1447
1867
|
|
|
1448
1868
|
### Configuration
|
|
@@ -1477,9 +1897,13 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1477
1897
|
### Phase 1: Project Scaffold
|
|
1478
1898
|
|
|
1479
1899
|
- **Foundation established:** CLI framework with Commander.js v13.0+
|
|
1900
|
+
|
|
1480
1901
|
- **Build process:** Bun bundler with native TypeScript support (1.1MB binary, <100ms startup)
|
|
1902
|
+
|
|
1481
1903
|
- **Test infrastructure:** Vitest with 80%+ coverage target
|
|
1904
|
+
|
|
1482
1905
|
- **Cross-platform CI:** GitHub Actions matrix testing (ubuntu, macos, windows)
|
|
1906
|
+
|
|
1483
1907
|
- **Code quality:** ESLint + Prettier configured
|
|
1484
1908
|
|
|
1485
1909
|
**Status:** ✅ Complete
|
|
@@ -1489,10 +1913,15 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1489
1913
|
### Phase 2: Init & Config
|
|
1490
1914
|
|
|
1491
1915
|
- **`dbcli init` command:** Interactive configuration with `.env` parsing
|
|
1916
|
+
|
|
1492
1917
|
- **Hybrid initialization:** Auto-fills from .env, prompts only for missing values
|
|
1918
|
+
|
|
1493
1919
|
- **Config management:** `.dbcli` JSON file with immutable copy-on-write semantics
|
|
1920
|
+
|
|
1494
1921
|
- **Database support preparation:** Multi-database adapter layer foundation
|
|
1922
|
+
|
|
1495
1923
|
- **RFC 3986 percent-decoding:** Handles special characters in DATABASE_URL passwords
|
|
1924
|
+
|
|
1496
1925
|
- **Validation:** Zod schemas for type-safe configuration
|
|
1497
1926
|
|
|
1498
1927
|
**Status:** ✅ Complete
|
|
@@ -1504,9 +1933,13 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1504
1933
|
### Phase 3: DB Connection
|
|
1505
1934
|
|
|
1506
1935
|
- **Multi-database support:** PostgreSQL, MySQL, MariaDB via unified adapter interface
|
|
1936
|
+
|
|
1507
1937
|
- **Bun.sql integration:** Native SQL API (zero npm dependencies for drivers)
|
|
1938
|
+
|
|
1508
1939
|
- **Connection testing:** Validates credentials before saving config
|
|
1940
|
+
|
|
1509
1941
|
- **Error mapping:** Categorized error messages with troubleshooting hints (5 categories: ECONNREFUSED, ETIMEDOUT, AUTH_FAILED, ENOTFOUND, UNKNOWN)
|
|
1942
|
+
|
|
1510
1943
|
- **Adapter pattern:** Clean abstraction enabling driver swaps without CLI changes
|
|
1511
1944
|
|
|
1512
1945
|
**Status:** ✅ Complete
|
|
@@ -1518,9 +1951,13 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1518
1951
|
### Phase 4: Permission Model
|
|
1519
1952
|
|
|
1520
1953
|
- **Three-tier permission system:** Query-only, Read-Write, Admin
|
|
1954
|
+
|
|
1521
1955
|
- **SQL classification:** Character state machine for robust SQL analysis (handles comments, strings, CTEs, subqueries)
|
|
1956
|
+
|
|
1522
1957
|
- **Permission enforcement:** Coarse-grained checks (no per-table/column fine-grained control in V1)
|
|
1958
|
+
|
|
1523
1959
|
- **Default-deny approach:** Uncertain operations require Admin mode
|
|
1960
|
+
|
|
1524
1961
|
- **Zero external dependencies:** Pure TypeScript string processing
|
|
1525
1962
|
|
|
1526
1963
|
**Status:** ✅ Complete
|
|
@@ -1532,10 +1969,15 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1532
1969
|
### Phase 5: Schema Discovery
|
|
1533
1970
|
|
|
1534
1971
|
- **`dbcli list` command:** Display all tables with metadata
|
|
1972
|
+
|
|
1535
1973
|
- **`dbcli schema [table]` command:** Show single table structure or scan entire database
|
|
1974
|
+
|
|
1536
1975
|
- **Foreign key extraction:** PostgreSQL FK metadata from pg_stat_user_tables; MySQL from REFERENTIAL_CONSTRAINTS
|
|
1976
|
+
|
|
1537
1977
|
- **Output formatters:** Table (ASCII) and JSON (AI-parseable)
|
|
1978
|
+
|
|
1538
1979
|
- **Schema storage:** Complete metadata in `.dbcli` for offline AI reference
|
|
1980
|
+
|
|
1539
1981
|
- **Column details:** Type, constraints, nullable, defaults, primary keys, foreign keys
|
|
1540
1982
|
|
|
1541
1983
|
**Status:** ✅ Complete
|
|
@@ -1549,10 +1991,15 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1549
1991
|
### Phase 6: Query Operations
|
|
1550
1992
|
|
|
1551
1993
|
- **`dbcli query "SQL"` command:** Direct SQL execution with permission enforcement
|
|
1994
|
+
|
|
1552
1995
|
- **Output formatters:** Table (human-readable), JSON (AI-parseable), CSV (RFC 4180 compliant)
|
|
1996
|
+
|
|
1553
1997
|
- **Auto-limiting:** Query-only mode limits to 1000 rows (with user notification)
|
|
1998
|
+
|
|
1554
1999
|
- **Helpful errors:** Levenshtein distance table suggestions for typos
|
|
2000
|
+
|
|
1555
2001
|
- **Structured results:** Metadata including row count, execution time, columns
|
|
2002
|
+
|
|
1556
2003
|
- **Permission guarding:** Blocks write operations in Query-only/Read-Write modes
|
|
1557
2004
|
|
|
1558
2005
|
**Status:** ✅ Complete
|
|
@@ -1568,11 +2015,17 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1568
2015
|
### Phase 7: Data Modification
|
|
1569
2016
|
|
|
1570
2017
|
- **`dbcli insert [table]` command:** Insert rows with parameterized queries
|
|
2018
|
+
|
|
1571
2019
|
- **`dbcli update [table]` command:** Update existing rows with WHERE clause and SET columns
|
|
2020
|
+
|
|
1572
2021
|
- **`dbcli delete [table]` command:** Delete rows (Admin-only for safety)
|
|
2022
|
+
|
|
1573
2023
|
- **Parameterized SQL:** Prevents SQL injection across all modification commands
|
|
2024
|
+
|
|
1574
2025
|
- **Confirmation flows:** --force flag for bypass; default prompts user
|
|
2026
|
+
|
|
1575
2027
|
- **Dry-run mode:** `--dry-run` shows SQL without executing
|
|
2028
|
+
|
|
1576
2029
|
- **Permission enforcement:** Insert/Update require Read-Write+; Delete requires Admin
|
|
1577
2030
|
|
|
1578
2031
|
**Status:** ✅ Complete
|
|
@@ -1586,11 +2039,17 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1586
2039
|
### Phase 8: Schema Refresh & Export
|
|
1587
2040
|
|
|
1588
2041
|
- **`dbcli schema --refresh` command:** Detect and apply schema changes incrementally
|
|
2042
|
+
|
|
1589
2043
|
- **`dbcli export "SQL"` command:** Export query results as JSON or CSV
|
|
2044
|
+
|
|
1590
2045
|
- **SchemaDiffEngine:** Two-phase diff algorithm (table-level, column-level)
|
|
2046
|
+
|
|
1591
2047
|
- **Type normalization:** Case-insensitive comparison for column types
|
|
2048
|
+
|
|
1592
2049
|
- **Immutable merge:** Preserves metadata.createdAt, updates schemaLastUpdated
|
|
2050
|
+
|
|
1593
2051
|
- **Streaming output:** CSV generated line-by-line; JSON buffered for validity
|
|
2052
|
+
|
|
1594
2053
|
- **File output:** `--output file` support for both export and schema refresh
|
|
1595
2054
|
|
|
1596
2055
|
**Status:** ✅ Complete
|
|
@@ -1604,11 +2063,17 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1604
2063
|
### Phase 9: AI Integration
|
|
1605
2064
|
|
|
1606
2065
|
- **`dbcli skill` command:** Generate AI-consumable skill documentation
|
|
2066
|
+
|
|
1607
2067
|
- **SkillGenerator class:** Runtime CLI introspection (collects commands dynamically)
|
|
2068
|
+
|
|
1608
2069
|
- **Permission-based filtering:** Query-only hides insert/update/delete; Read-Write hides delete
|
|
2070
|
+
|
|
1609
2071
|
- **SKILL.md format:** YAML frontmatter + markdown (compatible with Claude Code, Gemini, Copilot, Cursor)
|
|
2072
|
+
|
|
1610
2073
|
- **Platform installation:** `dbcli skill --install {claude|gemini|copilot|cursor}`
|
|
2074
|
+
|
|
1611
2075
|
- **Cross-platform paths:** Installs to correct location per platform (.claude/, .local/share/gemini/, etc.)
|
|
2076
|
+
|
|
1612
2077
|
- **Dynamic updates:** Skill regenerates as CLI evolves; no manual documentation maintenance
|
|
1613
2078
|
|
|
1614
2079
|
**Status:** ✅ Complete
|
|
@@ -1622,9 +2087,13 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1622
2087
|
### Phase 10: Polish & Distribution
|
|
1623
2088
|
|
|
1624
2089
|
- **npm publication:** `files` whitelist, `engines` constraints, `prepublishOnly` hook
|
|
2090
|
+
|
|
1625
2091
|
- **Cross-platform validation:** Windows CI matrix with .cmd wrapper verification
|
|
2092
|
+
|
|
1626
2093
|
- **Comprehensive documentation:** API reference, permission model, AI guide, troubleshooting
|
|
2094
|
+
|
|
1627
2095
|
- **Performance benchmarking:** CLI startup < 200ms, query overhead < 50ms
|
|
2096
|
+
|
|
1628
2097
|
- **Release readiness:** v1.0.0 quality gates met, all requirements satisfied
|
|
1629
2098
|
|
|
1630
2099
|
**Status:** ✅ Complete
|
|
@@ -1634,7 +2103,9 @@ dbcli v0.1.0-beta is a complete, production-ready CLI tool enabling AI agents an
|
|
|
1634
2103
|
## Known Limitations
|
|
1635
2104
|
|
|
1636
2105
|
- **Single database per project:** Each directory uses one `.dbcli` config. For multi-database setups, use separate directories or `--config` flag. This is by design, not a technical limitation.
|
|
2106
|
+
|
|
1637
2107
|
- **No audit logging:** WHO/WHAT/WHEN tracking deferred to post-v1.0
|
|
2108
|
+
|
|
1638
2109
|
- **No migration version tracking:** `migrate` commands execute DDL directly without version history or rollback. The `migrate` namespace is reserved for future migration tracking support.
|
|
1639
2110
|
|
|
1640
2111
|
---
|