Repository navigation
v8.0.0 - agent 平台契約、證據鏈與安全邊界完成
ForgeFlow Story DBCLI-001 到 DBCLI-012 全數在 v7.0.1(224ee59d)之後交付;該 tag
早於 Story 的合併點 04a88a44,因此以下每一條都在 7.0.1 之外。DBCLI-013 補上
採用漂移的檢查與這份紀錄本身。
版本為 8.0.0。理由不是變更量,而是三處會拒絕先前被接受的輸入、以及一處會
改變 JSON 判決值——SemVer 對這四項的答案都是 MAJOR,詳見下方 Breaking。
Added
-
dbcli capabilities與dbcli capabilities check:外部 Skill 現在問得到「這個
工具能做什麼」。 Catalog 是靜態、有自己的schemaVersion、依 id 排序,同一個
build 兩次輸出 byte-identical;check --require <ids>只讀本機設定的 engine 與
permission。兩者都不建立資料庫連線,也不動檔案系統,這一點由 import graph 的
contract test 結構性地保證,不是靠註解宣稱。純型別與純函式從
@carllee1983/dbcli/core匯出。Capability 描述的是工具的原子能力(
schema.read、data.delete),不是職務或
方法——後者屬於組合 dbcli 的 Role Skill 與 Method Skill。每一項 engine 與 risk
宣稱都由既有的ENGINE_CAPABILITIES推導,不另立第二張表;contract test 雙向
檢查 catalog 與實際 Commander tree 不會漂移。Fail closed 是預設:不認得的 id 回
unknown而不會被解析成長得像的那一個,缺少
設定回unavailable+context-unavailable而不是拿DEFAULT_CONFIG的
localhost PostgreSQL 充數。Exit code0/1/2。DBCLI_AGENT_MODE=1是 context 的一部分,不是免責聲明能蓋過去的東西。它無條件
拒絕所有設定、權限與憑證變更,而且不連線就完全可知——差別在拒絕是何時決定的:
blacklist 與人類同意在執行當下決定,契約無從代言;agent mode 在這裡就決定了。
connection.select、connection.init、blacklist.manage因此在 agent mode 下
回unavailable+agent-mode,不管權限寫的是不是 admin。「沒有設定」與「有設定但解不開」是兩件事。
{"$env":...}密碼指向未設定變數的
設定檔是存在且可讀的,回報「找不到設定」是對使用者機器說假話——context-unresolvable
因此獨立成一個原因,且不外洩錯誤訊息裡的檔案路徑。v1 涵蓋 engine capability matrix 管轄的 34 個 command key;另外 16 個指令
(explain、plan、impact、assert、verify、evidence等)不在其中,
因為替它們寫 engine 支援度等於憑讀碼捏造未經稽核的宣稱。它們回unknown,
擴充 matrix 是後續 Story。(DBCLI-PLAT-001,ADR-0022) -
Agent output 成為可組合的穩定契約。
--agent-output提供嚴格、限界且遮蔽過的
Operation Envelope;--correlation-id能把跨命令結果與 audit metadata 串在一起,
同時保留原有 human 與 JSON 輸出。(DBCLI-PLAT-004、005、006) -
外部 agent workflow 能在執行前驗證能力並留下證據。
inspect、report、
schema、plan、lint、explain與impact assess可選擇寫出 bounded receipt;
Task Pack 的safety.requires會對 catalog 與本機 context 做 fail-closed preflight。
(DBCLI-PLAT-007、008) -
套件新增 Skill Author Integration Kit 與外部 consumer contracts。 可複製的
Bun/TypeScript fixture 只使用公開 package surface,示範 discovery、schema pin、
envelope、correlation 與 evidence;CRUD、CQRS、DBA contract tests 覆蓋成功 preflight
與 query-only permission refusal。(DBCLI-PLAT-009、010)
Added
-
Capability catalog 涵蓋所有公開指令,從 34 條增為 53 條。 PLAT-001 刻意把
十六個指令留在 catalog 之外——explain、plan、impact、assert、snapshot、
verify、verification、evidence、contract、semantic、design、proxy、
recovery、backfill、password,以及capabilities自己——理由是替沒有稽核過
的指令寫 engine 支援度,正是這份契約要防的那種捏造。這次每一條宣稱都是從實作讀出來的,證據(
file:line)逐條寫在 Story 裡。程式碼
會直接拒絕非 SQL 連線的六個指令標成unsupported,並指名那道 gate;只有某一個
模式 需要 SQL 的(impact assess與design的--against-cache、semantic
的draft validate)標成limited,因為指令其餘部分照跑;完全不碰資料庫的標成
not-applicable。判定不了的一律unsupported,不新增第三種狀態——一個「我們沒查」
的值,呼叫端拿到也不能做任何事,而且會永遠留在那裡沒人查。那三條
limited是第一版寫錯、被第二個讀者抓回來的:grep 到 engine check 就停手,
少讀了一層 caller——那道 check 擋的是模式,不是指令。寫成unsupported會對著
MongoDB 上的 agent 說一個它其實跑得動的指令不能用;fail-closed,所以永遠不會看起來
像錯的,只會讓契約悄悄比工具本身沒用。capabilities自己也在 catalog 裡(capability.discover、capability.check),
決定寫在 ADR-0023:live 讀的時候它確實是個常數,但 catalog 本來就是設計成可以被
pin 住的——一個帶著舊 catalog 的 Skill,對著沒有這個指令的 dbcli 問
--require capability.check會拿到unknown-capability,那正是它需要的訊號。
(DBCLI-PLAT-011) -
supportsEvidence不再全部是 false。 它原本是空集合,因為會寫 receipt 的
assert、verify、evidence都不在 v1 catalog 裡。現在由 contract test 從指令層
的實際 writer 呼叫雙向推導——「import graph 碰得到 evidence 模組」不等於「會寫」,
insert、query等十幾個指令只是遞移拉到型別而已。順帶補上recover:它一直在
catalog 裡,也一直能用--write-verification-artifact寫出 artifact。
(DBCLI-PLAT-011)
Changed
-
ORM artifact 的讀取搬進
src/core/orm-drift/input.ts。 這段程式碼的註解本來就
寫著「never opens a database connection or reads dbcli configuration」,但它住在
src/commands/diff.ts裡,於是impact assess與design只要 import 它,就會
把 diff 的 adapter import 拖進自己的 static graph——contract test 因此拒絕它們
requiresConnection: false的宣稱。行為一個字都沒改,diff原樣 re-export,
差別是那兩個指令的離線宣稱現在證明得出來,而不是被豁免。(DBCLI-PLAT-011) -
contract test 不再假設指令名等於檔名。
commands/<name>.ts對password
(credential.ts)與contract(contracts.ts)都找不到檔案,而找不到時它是
「跳過」——於是「這個指令不會寫設定」對著那個專門用來改憑證的指令回了 true。
對照表改從program-lazy.ts的 lazy loader 讀,找不到模組是失敗而不是跳過。
(DBCLI-PLAT-011)
Fixed
-
要求 receipt 的失敗命令現在也會留下
outcome: failed。inspect、report、
schema、plan、lint與explain原本會在 CommanderpostAction前 exit 或 throw,
因而跳過 receipt;schema的既有快取/未加--force成功路徑也會提早 exit。
這些路徑現在保留原本的輸出與 exit code,再完成 bounded receipt;任何 receipt 寫入
失敗仍只回報Failed to write evidence receipt。(DBCLI-PLAT-007、#157) -
Schema cache 的寫入不再是一次完整設定的重新發布。
dbcli schema原本透過
configModule.write(v1)與writeV2Config(v2)存快取,兩者都在
assertConfigMutationApproved()後面,所以DBCLI_AGENT_MODE=1下
capabilities check回報schema.read可用、指令本身卻 exit 1,訊息還說的是
「configuration, permission, and credential changes」——只讀 exit code 的 agent
只能推論成資料庫讀取失敗,而讀取其實成功了。移動時量到一個沒有人記錄過的缺陷:v1 路徑會把
connection.password從
config.json刪掉、並整份覆寫.env.local。這件事在 agent mode 之外也會發生,
也就是每一次dbcli schema都在搬憑證,而那裡根本沒有任何 guard 會攔。新的
src/core/schema-cache-persistence.ts只寫快取欄位,而且寫不出別的東西:
它的參數只有 schema、connection slot 與兩個時間戳,設定是它自己從磁碟讀的,
沒有任何參數能夠承載憑證或權限。窄化在簽章,不在旗標——一個「這次寫入沒問題」的
布林參數等於把邊界交給呼叫端的誠實。assertOnlyCacheFieldsChanged把同一句保證
講出來,任何擴大寫入範圍的修改會在單元測試指名欄位而失敗。assertConfigMutationApproved()一個字都沒改,六個 guarded writer 一個都沒少,
並由tests/contract/config-mutation-boundary.test.ts以名冊釘住。agent mode 下
init、use、blacklist add與憑證指令照樣被拒。(DBCLI-PLAT-012) -
快取寫入失敗現在說自己是快取寫入失敗。 訊息明講 schema 已成功讀出、失敗的是
存進本機快取那一步,並且是 bounded 的:只有SchemaCacheWriteError會原樣輸出
(它的訊息在建構上就不含路徑),其他成因一律分類而不引用,因為完整性拒絕會帶上
它檢查的設定檔路徑。(DBCLI-PLAT-012)
Breaking
-
query-only 的最後一道界線改由資料庫提供。 PostgreSQL / MySQL / MariaDB 上
每一句 caller-controlled SQL 都在該實體連線上開一個原生唯讀交易後才送出,涵蓋
query、export、saved query 及其 verification、report診斷、SQL shell、
analyzed explain,以及會把較高權限收斂成 query-only 的多連線 fan-out。語句分類
只看得到語句形狀,看不到SELECT mutate_accounts()背後的函式會不會寫資料——
這類先前成功的呼叫現在會被資料庫本身擋下。邊界建立不起來就在送出目標語句前失敗;
目標語句跑完但交易收尾失敗時回報結果不確定並丟棄該連線。(DBCLI-002) -
slow-endpoint-investigation task pack 新增必填的
table參數。 計畫順序變成
blacklist → proxy analyze → schema<table>→ explain → guide
missing-index-for。先前不帶 table 的呼叫現在會在產出任何計畫之前失敗。在沒確認
資料表形狀的情況下讀執行計畫或索引候選,那是猜測,不是佐證。(DBCLI-012) -
儀表板內嵌 payload 改為 allowlist。
SavedQueryMeta先前是整包序列化進獨立
HTML 的,把參數預設值與列舉、目標 index / collection、verification 的 query 與
expects 一起帶進一個從未渲染它們的檔案。現在只有已顯示的資料列、applied-limit
與安全通知、provenance,以及顯示用的 name / description 與引用已顯示欄位的圖表
/KPI 定義會被序列化。讀取那些欄位的下游消費者會發現它們不再存在。(DBCLI-006) -
語意契約中形式錯誤的 subject 判為
invalid而非stale。table:orders
這類形式本身就不合法的 subject 先前與「model 被改名」得到同一個判決,
contract drift --format json的消費者因此無法區分兩者。形式檢查移到 parse
階段。(DBCLI-011) -
init --no-interactive從無效變成有效。 它先前讀的是
options.noInteractive,而 Commander 存的是interactive,所以那個旗標從來沒
擋掉任何提問。依賴舊行為(帶著--no-interactive卻仍期待被提問)的腳本會改變
行為。(DBCLI-004) -
互動式
init的遮蔽輸入沒有純文字退路。 沒有 TTY 或載入不到遮蔽實作時,
直接在寫出任何設定之前失敗,並指出當下模式支援的替代輸入(--password、
.env/ 環境變數、--use-env-refs,MongoDB 則是--uri)。先前這些情境會以
可見提示收下密碼。(DBCLI-004) -
assert在 receipt 寫入失敗時的 stdout 形狀改變。 先前是一行 stderr 且沒有
envelope,現在--format json的呼叫端拿得到完整 envelope,exit code 仍為 1。
解析舊有失敗輸出的呼叫端需要跟著改。(DBCLI-009)
Added
-
skill context --context-version 2:給外部 agent 的穩定契約。 離線、不開
連線、不掃 Redis key、不讀文件、不讀專案原始碼。輸出引擎、設定權限、能力、可見
資源、snippet metadata、semantic 與 contracts、truncation 與 gaps;不輸出憑證、
實際值、預設值與計數、原始 ES mapping、Redis key/value 或專案路徑。Elasticsearch
只以快取的扁平欄位呈現,Redis 只接受 repository 自己宣告的
dbcli.redis-context.json。缺證據時明確回報 gap,不推測名稱與語意。不帶旗標時
維持 byte-compatible 的 version 1 輸出。(DBCLI-003) -
saved-query 儀表板帶上可攜、封閉的執行溯源。 version 1 的 provenance 物件與
對應的 Execution Traceability 區塊:邏輯連線名稱與引擎、snippet key 與來源分類、
實際生效的權限,以及實際生效的筆數上限——沒有套上限時明講not-applied,而不是
留白讓人分不出完整結果與被截斷的結果。applied-limit 與畫面上的截斷警示不一致就
在寫檔前拒絕。直接查詢的儀表板行為不變,也不帶溯源區塊。(DBCLI-006) -
impact assess的不完整性契約寫進兩份index.html。 coverage 永遠是
declared或partial、v1 不宣稱complete、--fail-on只在報告寫出後改變
exit code。(DBCLI-008)
Security
-
互動式
init的憑證改走遮蔽輸入。 PostgreSQL / MySQL / MariaDB / Redis /
Elasticsearch 的密碼、MongoDB 逐欄模式的密碼,以及貼上的 MongoDB 連線字串(它
本身就帶著帳密),先前都停在終端機捲動紀錄、session 錄影與螢幕分享畫面裡。連線
測試失敗時 driver 常把密碼或整條 URI 原樣寫回錯誤訊息,現在先經過共用的
redactSecretsForDisplay遮蔽並截斷再輸出。(DBCLI-004) -
safe-backfill 的佐證文物不再外洩敏感字串。 寫進佐證文物的失敗原因改用固定的
安全字串而非 driver 的錯誤訊息;使用者自訂的 subject / summary 與 SQL 佐證會先把
憑證、檔案系統路徑與 SQL 註解遮掉再落檔。(DBCLI-005) -
evidence pack 的失敗輸出不再帶出絕對路徑與原始錯誤。
evidence compose --receipt <不存在>先前會原樣印出
ENOENT: ... lstat '/private/var/.../x.json'。receipt 的 realpath 失敗改拋有界
訊息;safeMessage只放行兩個 validation error 類別;validateFormat換成自己的
有界檢查——它先前會把被拒的值原樣引用,而那個位置可以是路徑。(DBCLI-010) -
語意契約的診斷不再複述產物輸入。
rejectUnknownKeys先前把被拒絕的 JSON
property key 內插進診斷路徑,而那個 key 由產物作者控制:contract validate的
stderr 與contract drift --format json會原樣印出帶憑證與絕對路徑的 key。改為
列出允許的 property 名稱。fail()與collectContractEvidence的邊界一併收
緊。(DBCLI-011)
Fixed
-
safe-backfill 的四項防護全數執行。 先前一失敗就中斷,操作者只看得到第一個
擋下來的原因。blacklist、schema、plan、verify-query-readonly 現在都會執行並各自
回報結果;after-write 仍只在四項全過時才跑回讀斷言。(DBCLI-005) -
assert的 receipt 寫入失敗不再吞掉斷言判決。 先前直接process.exit(1),
--format json的呼叫端拿到的不是 envelope 而是一行 stderr,--no-fail的語意
也被推翻。改成錯誤記在結果旁邊、判決照常輸出、exit code 仍為 1——與同一個檔案裡
相鄰的 artifact 寫入分支一致。(DBCLI-009) -
JSON 形態的 ORM 產物改為指名檔案的 fail closed。 Drizzle snapshot 與
normalized JSON 先前會把JSON.parse與 Zod 的原始錯誤直接丟出去,都沒有指出是
哪一個檔案壞掉;agent 一次審多份產物時無從得知該修哪一個。(DBCLI-007) -
impact assess的輸出目標衝突看得出原因。impact output already exists
不在safeMessage的清單裡而被壓成泛用句子,審查者無從得知是報告檔已存在。
同時把 prefix 比對改成完全比對——這是雙向的:design dialect這類帶插值後綴的
訊息反而不再放行、收斂成泛用句。prefix 的寫法在某個字串日後長出插值後綴時會默默
漏資訊,而那正是這個函式存在的理由。(DBCLI-008)
Documentation
-
--context-version 2、儀表板溯源、遮蔽輸入、safe-backfill、slow-endpoint pack
的說明同步到四份使用者文件與相關 Pages guide,中英雙語、Markdown 與 HTML 兩種
格式皆同步。(DBCLI-002 到 DBCLI-012) -
三份長期落在檢查外的 guide 納入涵蓋範圍。
evidence-packs、
offline-impact-assessment、semantic-contracts與verification-evidence
併入guideSlugs;該清單本身改由讀目錄的檢查守住,因為手維護的清單擋不住第四
份。(DBCLI-008、DBCLI-010、DBCLI-011) -
receipt 旗標表格不再被一句話切成兩張,「Flag trio」/「旗標三件組」列了四個
旗標,四份文件一併改為「Flags」/「旗標」。(DBCLI-009) -
capabilities check --require的順序語意現在只有一種說法。 PLAT-001 的
Story 與 acceptance 宣告輸出「與--require參數順序無關」,實作從第一天起就在
required與results保留 first-seen 輸入順序,而且它自己的單元測試逐字斷言
這件事。正式語意改為三條分開的話:相同輸入 byte-identical、兩個清單保留
first-seen 輸入順序、換順序不改變任何 capability 的判決與整體ok。實作與輸出
一個位元都沒有動;tests/docs/capability-ordering-parity.test.ts把八個表面
綁在一起,並拒絕被撤回的那句話重新長出來。(DBCLI-PLAT-013)
Process
-
ForgeFlow 採用版本現在只有一個可檢查的答案。
specs/.forgeflow-adoption是
唯一權威,bun run forgeflow:check新增的
scripts/check-forgeflow-adoption.ts把specs/stories/README.md、
specs/handoff.md、Story template 與本地story-developmentSkill 全部對回它。
這條 gate 的起因是它已經失效過一次:marker 與 README 推進到 0.3.2 之後,handoff
跨兩個已合併的 PR 仍寫著 0.3.1,沒有任何東西比對過。(DBCLI-013) -
ForgeFlow delivery gate 不再認 Story ID 的形狀。
scripts/check-forgeflow-handoff.ts原本用/^(DBCLI-\d+).*$/從目錄名推 ID,
DBCLI-PLAT-001-capability-contract不匹配就被鍵在自己的完整目錄名底下——把
DBCLI-PLAT-001加進completed_stories會得到「找不到 Story directory」,
指著一個明明存在的目錄。現在 ID 從每份story.md的# Story: <ID>標題讀,
gate 不認任何形狀,新的 ID family 不需要改它;仍然比對宣告的 ID 是不是目錄名的
前綴。規則搬進scripts/lib/forgeflow-handoff.ts,對 fixture 測,而且不 import
任何東西——「這個 gate 不碰網路」因此是檔案的性質,不是註解裡的承諾。同時新增
一條規則:同一個 Story 不能同時是current_story與completed_stories。
(DBCLI-PLAT-013)