
Beads 存儲層 Schema 一致性守衛與連接池遷移修復實戰從 be-krza3 發布門禁看 CLI/Runtime 模式對齊工程【免費下載鏈接】beadsBeads - A memory upgrade for your coding agent項目地址: https://gitcode.com/GitHub_Trending/beads1/beadsBeads 為編碼 Agent 提供嵌入式 Dolt 數據庫存儲其 schema 通過 SQL 遷移流演進而 CLI 側則內置同一套 schema 的 DDL 模板。為了確保CLI 打包的 schema 與運行時實際提交的 schema 永遠一致項目引入了一個名為schema_cli_parity_integration_test.go的模式一致性 oracleparity oracle測試。本文以發布門禁 be-krza3-schema-parity-pool-heal-gate.md 為線索深入剖析該 oracle 的工作原理、它曾暴露的兩類假性不一致缺陷ignored-stream 列誤報、Go map 迭代順序導致的隨機空讀以及一個被逐字節 cherry-pick 進來的連接池遷移修復be-itm5 的rebuildPoolAfterMigration。讀完本文你將理解 Beads 如何用雙快照對比守護數據庫 schema 演進如何消除測試中的非確定性以及一個嚴謹的發布門禁release gate如何通過 8 項標準判定一個修復是否具備合入資格。一、背景Beads 的 Dolt 存儲層與 schema 治理Beads 的核心存儲位于 internal/storage/dolt其底層是嵌入式 Dolt一個類 MySQL 的版本化數據庫。所有持久化對象——issue、wisp、lease、metadata、repo 元數據等——都落在 Dolt 數據庫中schema 的演進完全由遷移migration驅動。schema 治理有兩個關鍵約束運行時 schema由store.initSchema在打開數據庫時執行遷移得到遷移 SQL 由schema.AllMigrationsSQL()統一提供見 internal/storage/schema。CLI 側 schemaCLI 內部預置了同一套 DDL 模板如 internal/storage/schema/cli_prepared_ddl.go供各種命令準備語句使用。兩套 schema 來源不同卻必須保持完全一致——如果 CLI 預置的 DDL 與運行時遷移出的真實 schema 出現漂移會導致命令執行 SQL 報錯、結果集字段不匹配等隱蔽故障。為此項目用集成測試TestCLIBundleMatchesRuntimeCommittedSchema位于 internal/storage/dolt/schema_cli_parity_integration_test.go充當oracle分別從 CLI 側和運行時側采集 schema 快照逐行對比。二、Parity Oracle 的工作原理雙快照對比oracle 的核心思想非常直觀對同一個 schema 分別從兩個源頭生成規范化文本快照然后逐行比較。2.1 快照查詢集committedSchemaSnapshotQueriescommittedSchemaSnapshotQueries 定義了一組針對information_schema的查詢覆蓋 5 個維度維度查詢內容關鍵輸出字段tables表清單表名 表類型columns列定義表名、序號、列名、類型、可空性、默認值、extra、生成表達式indexes索引定義索引名、列序號、非唯一標志、子分區、可空、索引類型constraints約束定義約束名、類型、鍵列序號、引用表/列、更新/刪除規則version遷移版本schema_migrations中的最大遷移號每行輸出都被拼成一條規范化文本例如列維度SELECT CONCAT(column|, c.table_name, |, LPAD(c.ordinal_position, 3, 0), |, c.column_name, |, c.column_type, |, c.is_nullable, |, COALESCE(c.column_default, NULL), |, c.extra, |, COALESCE(c.generation_expression, )) AS line FROM information_schema.columns c JOIN information_schema.tables t ON t.table_schema c.table_schema AND t.table_name c.table_name WHERE ...LPAD(..., 3, 0)與COALESCE(..., NULL)都是為了讓同一 schema 在兩邊產生逐字節一致的文本從而可直接用sort.Strings排序后用 diff 比較。2.2 兩條采集路徑測試TestCLIBundleMatchesRuntimeCommittedSchema在臨時目錄中構造兩個并行的 Dolt 環境CLI 路徑dolt init 執行schema.AllMigrationsSQL()然后用 dolt CLI 跑同樣的快照查詢得到cliCommittedSchemaSnapshot運行時路徑通過New()打開一個真實 DoltStoreCreateIfMissing: true觸發initSchema執行遷移直接對store.db跑快照查詢得到runtimeCommittedSchemaSnapshot。兩者都調用sortedSnapshotQueryNames按固定順序執行 5 類查詢最后sort.Strings后由firstSchemaSnapshotDiff找出第一處差異。若存在差異則t.Fatalf測試失敗——這就是oracle 報警。三、缺陷剖析oracle 為何產生假性不一致be-krza3 門禁文檔的 Scope 部分明確指出這個 oracle 存在兩個缺陷會誤報false parity mismatch而不是漏報3.1 缺陷一ignored-stream 列無法表達Beads 存在一個被忽略的遷移流ignored migration series。該流擁有的對象分為兩個層次整張表wisps以及wisp_前綴的表還有ignored_schema_migrations、local_metadata、repo_mtimes等元數據表掛在主線表上的單列例如leases.granted_node由migrations/ignored/0016_add_lease_granted_node.up.sql添加。而schema.AllMigrationsSQL()只遍歷主線遷移序列沒有對應的 ignored 系列 bundle 可與 CLI 側配對。如果 oracle 不過濾這些對象它們會在運行時側出現、而 CLI 側缺失被誤讀為only in runtime的假性漂移。oracle 的過濾謂詞如下以tables為例WHERE t.table_schema DATABASE() AND t.table_name NOT IN (ignored_schema_migrations, local_metadata, repo_mtimes, wisps) AND LEFT(t.table_name, 5) wisp_ AND LEFT(t.table_name, 5) dolt_columns維度額外增加了一行針對單列的排除AND NOT (c.table_name leases AND c.column_name granted_node)值得警惕的是這種排除是有代價的文檔記錄到遷移 0065 的wisp_comments.text寬度漂移曾持續 6 天而 oracle 無法發現ga-61ruw因為該列屬于 ignored 平面。因此文檔強調正確方向是給 CLI 側補一個 ignored 系列 bundle而不是刪除這些謂詞在實現之前cli_prepared_ddl.go中的源碼級守衛masking-proof guard負責覆蓋這類漏洞。3.2 缺陷二Go map 迭代順序導致的隨機空讀committedSchemaSnapshotQueries返回的是map[string]string。修復前兩個快照采集器直接for name : range queries迭代 map而Go 的 map 迭代順序是隨機的。文檔與代碼注釋sortedSnapshotQueryNames解釋了這個問題的微妙之處運行時側的快照查詢經由一個 Dolt 會話讀取而該會話的 root 只在查詢成功之后才前進be-itm5 行為。直接 range map 會讓每次調用中誰先執行隨機變化——先執行的那個類別可能讀取到尚未就緒的狀態被記錄為空。于是同一個類別的查詢在每次運行時可能隨機讀到空結果產生間歇性、不可復現的假性不一致。3.3 修復方案靜態排除 確定性排序修復包含兩處均為測試代碼改動靜態排除子句在 5 個快照查詢中加入上述NOT IN/LEFT(...)/NOT (...)過濾明確表達oracle 只對比主線遷移流sortedSnapshotQueryNames輔助函數把 map 的 key 先收集進 slice 再sort.Strings保證兩個采集器以固定順序執行查詢func sortedSnapshotQueryNames(queries map[string]string) []string { names : make([]string, 0, len(queries)) for name : range queries { names append(names, name) } sort.Strings(names) return names }兩個采集器cliCommittedSchemaSnapshot與runtimeCommittedSchemaSnapshot都改為遍歷sortedSnapshotQueryNames(queries)徹底消除了對 map 迭代順序的依賴。四、關聯修復be-itm5 的連接池遷移修復pool healbe-krza3 門禁的 Round-2 重做還包含了一個逐字節 cherry-pick的生產代碼修復be-itm5 的rebuildPoolAfterMigration位于 internal/storage/dolt/store.go。4.1 問題本質會話 root 未隨遷移前進bug 場景be-itm5 / be-jjv2 復現是一個執行了遷移的 store 打開流程讓連接池store.db中的連接停留在了遷移前的 Dolt 會話 root上。第一次語句通過該過期連接讀取時返回table not found而失敗的查詢不會推進會話 root所以錯誤不會在重試時自愈——只有某個無關的成功查詢比如information_schema探測才會推進 root。這讓故障表現為打開后第一讀必失敗且隨機自愈極難排查。4.2 修復實現遷移后重建連接池func (s *DoltStore) rebuildPoolAfterMigration(ctx context.Context, applied int) error { if applied 0 { return nil } newDB, err : sql.Open(mysql, s.connStr) if err ! nil { return fmt.Errorf(rebuild pool after migration: %w, err) } applyPoolLimits(newDB, s.cfg) if err : newDB.PingContext(ctx); err ! nil { _ newDB.Close() return fmt.Errorf(rebuild pool after migration: %w, err) } old : s.db s.db newDB return old.Close() }關鍵設計點applied 0快速返回非遷移打開重新打開一個已遷移完成的庫是最常見路徑不支付任何重建代價也不觸碰s.db——這是 be-itm5 的 Done-when 守衛遷移走獨立連接池initSchema通過openMigrationDB這個一次性連接執行遷移store.go而 store 主池在打開早期已被啟動 Ping 釘住一個連接無鎖的池交換是安全的文檔在 OWASP 審查一節專門論證了這一點——rebuildPoolAfterMigration只有單一調用點構造函數內部、發布前執行不存在并發讀者因此s.db newDB無需加鎖。4.3 為什么必須帶上 store.go門禁文檔解釋了一個重要的工程判斷Round-1 時有人主張 store.go 不屬于本 diff 的授權范圍scope但若把 store.go 還原到 be-itm5 之前的狀態本輪的測試文件根本無法編譯——post_migration_pool_heal_integration_test.go與connection_pool_test.go直接調用rebuildPoolAfterMigration。于是原始驗收標準中的#3 非確定性消除與#4 不觸碰生產文件機械矛盾。Round-1 審查者裁定包含 store.go 是正確解法Round-2 審查者則獨立復核了這條授權鏈authority chain并用 RED/GREEN 復證不含 store.go 即編譯失敗而非照單全收。五、驗證體系從純 Go 單元測試到真實容器集成測試5.1 連接池生命周期單元測試無需 Dolt 服務器connection_pool_test.go 用進程內 mock drivermockDriver統計 Open/Close 次數釘住連接池不變量可在go test -short下運行。它覆蓋 5 個維度恰好對應門禁中的 9 個 diff-owned 測試測試斷言的不變量TestApplyPoolLimits_Defaults默認池參數MaxOpenConns10、MaxIdleConns5、ConnMaxLifetime1hTestApplyPoolLimits_OverridesConfig 非零字段覆蓋默認值如 3/2/15minTestApplyPoolLimits_ClampsIdleToOpenMaxIdleConns不得超過MaxOpenConns否則database/sql會靜默鉗制TestPool_SequentialQueriesReuseSingleConnection兩次順序查詢只 Open 1 次、Close 0 次——池必須復用連接TestPool_ConcurrentQueriesRespectMaxOpen8 個并發查詢、池上限 2 時底層連接數不超過 2TestPool_CloseReleasesUnderlyingConnectionsClose()后所有已打開連接全部釋放opens closesTestRebuildPoolAfterMigration_NoopWhenNotMigratedapplied0時不得打開新連接、不得觸碰s.db這些測試的來源是一個真實的線上故障報告dolt-server.log 中出現無窮無盡的NewConnection/ConnectionClosed配對說明守護進程實際上每條查詢都在新建*sql.DB。單元測試把連接復用固化為可回歸的不變量。5.2 遷移后首讀集成測試需要真實 Doltpost_migration_pool_heal_integration_test.go 中的TestMigratingOpen_FirstReadSucceeds是 be-itm5 的復現測試設計極其克制在New()返回后只發出唯一一條查詢SELECT key FROM config因為第二條無關查詢會通過推進會話 root直接掩蓋 bug。測試斷言首次讀返回遷移后的 config 種子數據非 0 行。5.3 非確定性消除的復證門禁記錄到Round-2 審查者獨立運行TestCLIBundleMatchesRuntimeCommittedSchema -count5共 5 輪5/5 全 PASS證明 map 迭代順序的隨機性已被徹底消除。六、發布門禁Release Gate評估標準全解be-krza3 門禁文檔的核心是一張 8 行#0–#7的評估表這是 Beads 項目修復必須過門禁才能合入的工程制度。以下完整繼承并解釋每一項#標準判定證據要點0預檢是否已合入NOgh pr list --search無結果git merge-base --is-ancestor確認目標 commit 不在 main 上繼續評估1是否存在 Review PASSPASS審查 bead be-8xjjg第 2 輪 verdictpass以 reasonpass關閉2驗收標準是否達成PASSscope 授權鏈按文檔化路徑裁決而非人說了算uncovered_criteria: none-count5復證非確定性已消除3diff 自有測試是否全過SKIPFAIL 規則PASS9/9 PASS、0 FAIL、0 SKIP19.002s真實 Dolt 容器rootless podman執行非 SKIP 替代品rebase 后 deployer 獨立復核gofmt -l/go build ./.../go vet ./...全干凈3a非 diff 自有的既有失敗歸因PASS全包運行193 測試有 14 個 FAIL全部位于federation_test.go且非 diff 自有13 個是包級并行槽競爭下的既有超時約 45s 處第 14 個TestFederationDatabaseIsolation是已跟蹤的 P0 be-3c78sbase-ref 對照運行逐條復現13 PASS 1 FAIL4 條歸因子句全部滿足3b策略 / lint 通道PASSgolangci-lint run0 issues——這是該 diff 上第 3 次獨立的 gofmt/vet/lint 干凈驗證4無未決 HIGH 級發現PASS顯式 OWASP Top 10 走查注入、認證、訪問控制、XXE/SSRF/反序列化/XSS、錯誤配置、漏洞依賴、日志無 blocker/major/minorstore.go 無鎖池交換的安全性已驗證單調用點、發布前、構造函數內5分支是否干凈PASS恢復 rebase 后git status --short為空6是否與 main 干凈分叉PASSassert_deploy_ancestry_scoperc0無.claude/**路徑所有 commit 都引用已接受的 bead idattempt_bounded_self_rebase0 沖突完成 rebaseBEFORE_SHAe0c39aa25...→ AFTER_SHA7d294b4b5...7單一功能主題PASSbe-0v3loracle 修復 被授權的 be-itm5 cherry-pick編譯必需依賴范圍內無雜散 commit6.1 值得借鑒的三個工程細節not caused by this diff 必須實證而非假設14 個既有失敗通過 base-refmerge-base6ec78f3a2對照運行逐一復現且 4 條歸因子句非 diff 自有、已跟蹤、base-ref 已存在、無路徑重疊全部滿足——即使后來發現 be-3c78s 已由 PR #5836 修復也不追溯否定當時的快照判斷。SKIP 不被當作 PASSdiff 自有測試 0 SKIP且容器真實執行杜絕了用 SKIP 頂替驗證的造假路徑。分支恢復有據可依worktree 的pre_start在輪次間把本地分支指針硬重置回origin/main但 rebase 產物 commit 對象仍在本地對象庫git cat-file -e確認且 bead 筆記已記錄 SHA 為持久狀態因此用git reset --hard AFTER_SHA恢復而非重做隨后對恢復后狀態重新跑全部門禁檢查。七、推送策略、合并權限與最終裁定推送目標origingastownhall/beads被哨兵配置DISABLED-upstream-is-fetch-only-push-to-fork-and-PR禁止推送headforkquad341/beads-sec003-contrib.git按本 rig 既定先例be-r3ysh接受推送。PR 以跨倉庫方式對gastownhall/beads:main發起head 為quad341:deploy/be-krza3-gate。已知基建缺口 be-z3iuvattempt_bounded_self_rebase內部的 force-with-lease 推送仍硬編碼指向origin腳本 rebase-resolve-lib.sh 第 489 行故本輪改用直接手動推送到headforkrebase 本身干凈完成不受該推送 bug 影響。注意此路徑為本機環境信息僅作為流程記錄。合并權限gastownhall/beads對本 rig 是僅貢獻者倉庫rig 無合并權限deployer 的職責止于打開已核驗的 PR門禁結果通過郵件上報 mayor。這是 be-vc1m、be-gd3v、be-79jh、be-39ss、be-pp7e、be-r3ysh 等先例確立的制度。最終裁定PASS 7/7。分支經本地 ref 重置后恢復并重新核驗、對最新 main 的 rebase 復證干凈、build/vet/gofmt 獨立復跑已推送 headforkPR 狀態 OPEN/MERGEABLE。deployer 交棒后續以 PR CI 作為額外的真實環境確認但不是門禁通過的前提條件。八、從本門禁提煉的可復用經驗確定性是測試的第一性原理Go map 迭代順序、會話 root 推進時機、連接池回收時機這些隱式非確定性是間歇性 flaky 的溫床。sortedSnapshotQueryNames的教訓是凡是順序會影響結果的測試必須顯式固定順序并加注釋說明原因。oracle 的盲區要顯式記賬ignored 遷移流的排除謂詞讓 oracle 失去了對 wisp 列漂移的感知ga-61ruw 事件文檔沒有掩蓋這一點而是記錄了代價、實測了 ignored 平面擁有的對象清單并給出正確的根治方向補 ignored bundle。測試要能精確復現而非大概率復現TestMigratingOpen_FirstReadSucceeds刻意只發一條查詢來復現 be-itm5connection_pool_test.go用 mock driver 把連接生命周期變成可斷言的數值。好的復現測試應該讓 bug 每次必現而不是碰運氣。修復的生產代碼邊界要靠編譯約束強制be-krza3 的 scope 爭議最終由不含 store.go 就無法編譯這一機械事實裁決比任何評審意見都更有說服力——測試本身就是需求。九、繼續深入倉庫門禁全文release-gates/be-krza3-schema-parity-pool-heal-gate.mdParity oracle 實現internal/storage/dolt/schema_cli_parity_integration_test.go池重建與池參數實現internal/storage/dolt/store.goapplyPoolLimits、internal/storage/dolt/store.gorebuildPoolAfterMigration連接池單元測試internal/storage/dolt/connection_pool_test.go遷移后首讀復現測試internal/storage/dolt/post_migration_pool_heal_integration_test.goSchema 遷移與 CLI DDL 模板internal/storage/schema【免費下載鏈接】beadsBeads - A memory upgrade for your coding agent項目地址: https://gitcode.com/GitHub_Trending/beads1/beads創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考