fix(teardown): central KV 操作前機械核對 wrangler 身分(D88,leo 審核裁示①,必修項)

leo 審核意見:「這條正好違反 D88——閘要長在機器上,不是長在誰的記性上。而且失敗模式
是最壞的那種:靜默打錯帳號的中心 KV,沒有任何東西會叫。帳號側有白名單擋、中心側完全
沒有——這個不對稱本身就是設計缺口。」

修法:assertWranglerIsCentralAccount() 在 planCentralCleanup()(central-plan/
central-apply 共用的入口)最前面跑 `wrangler whoami --json`,核對登入態的帳號清單裡
有 uncle6(58309bb90fd93ad6d0fe0aae99170e9d)才放行,沒有就丟錯拒絕整個操作。
已用模擬錯誤帳號 id 驗證比對邏輯正確拒絕;正常路徑(真的登入 uncle6)central-plan
照常運作,貼在同一次 commit 的測試輸出裡。

同時把 leo 裁示②(apply 的依賴序失敗不擋併、但要記實測輪數)補進「已知限制」段:
兩次實測收斂輪數分別是 2 輪(有外部依賴殘留時)與 3 輪(純內部 service binding 鏈時)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-08-14 14:29:03 +08:00
parent 49248b18c5
commit c543abaa8a
+59 -3
View File
@@ -31,7 +31,18 @@
* 「這個 worker 是不是正在被別人使用」——那是 main/coordinator 層級的協調,不是機器能判斷的)。
* - 中心 KV 清理走 `wrangler kv key`,用的是本機既有的 wrangler OAuth 登入態(不是 CF API
* token),因為 INSTALLER_KV 住在另一個帳號(uncle6,官方安裝器所在),本工具沒有那把帳號
* 的 API token。呼叫前務必 `npx wrangler whoami` 確認登入態是 uncle6,不是別的帳號。
* 的 API token。**2026-08-14 起機械強制**`assertWranglerIsCentralAccount()` 在任何
* `central-plan``central-apply` 動作前跑 `wrangler whoami --json`,核對登入態看得到
* uncle6 帳號才放行,看不到就丟錯拒絕(D88「閘要長在機器上,不是長在誰的記性上」——
* 原本這是操作者每次手動核對的紀律,不是結構保證,現在補成機械閘)。
* - `apply` 對「worker 被別的 worker service binding 卡住刪不掉」**不會自動重試/拓樸排序**。
* 這是 fail-safe(失敗就是刪不掉,不會刪錯東西),但需要操作者自己重跑 `apply` 到收斂。
* 2026-08-14 兩次實測的收斂輪數:第一次(帳號上有 kbdb-graph-plugin 這類外部依賴殘留)
* 跑了 2 輪(16 個失敗 → mcp 已刪,cypher-executor 解鎖 → 其餘 13 個跟著解鎖,僅
* arcrun-kbdb 卡在外部依賴,需另外處理);第二次(帳號上沒有外部依賴殘留,純內部
* service binding 鏈)跑了 3 輪(16 個失敗 → cypher-executor 解鎖、13 個一次過 →
* 只剩 array-ops 卡 cypher-executor 剛好晚一步刪掉 → 第 3 輪清空)。**下一個人看到
* 第一輪一半失敗不是工具壞掉,是預期行為,重跑 `apply` 即可,通常 2–3 輪收斂。**
* - 🔴 **偵測不到「不屬於這台實例的 worker 反過來依賴這台實例的 worker」這種形狀**
* 2026-08-14 實撞,兩層咬合):`kbdb-graph-plugin`(不認得,正確被排除、不動)身上有
* service binding 指著 `arcrun-kbdb`(要刪的)。CF 因此拒絕刪 `arcrun-kbdb`——
@@ -296,14 +307,58 @@ async function applyTeardown(plan, { accountId, apiToken }) {
// ── 三、中心 KV(安裝器 deployed 紀錄)清理 ────────────────────────────────────
// 這兩顆 KV 住在 uncle6 帳號(官方安裝器 arcrun-installer 所在),不是 youlin 的帳號。
// 沒有這顆帳號的 CF API token,走本機已登入的 wrangler OAuth`npx wrangler whoami` 需顯示
// uncle6.me@gmail.com's Account58309bb90fd93ad6d0fe0aae99170e9d)。
// 沒有這顆帳號的 CF API token,走本機已登入的 wrangler OAuth
//
// 🔴 2026-08-14 leo 審核裁示(D88「閘要長在機器上,不是長在誰的記性上」):這裡原本
// 靠操作者自己每次手動 `wrangler whoami` 核對——那個核對動作是對的,只是長錯地方
// (長在人身上)。失敗模式是最壞的一種:wrangler 的 OAuth 登入態如果哪天換成別的帳號,
// 會**靜默**對錯的帳號動中心 KV,沒有任何東西會叫。帳號側(youlin)有 ALLOWED_ACCOUNT_IDS
// 白名單擋,中心側原本完全沒有——這個不對稱本身就是設計缺口,現在補齊。
// assertWranglerIsCentralAccount() 在任何中心 KV 讀寫前跑,核對 wrangler 現在的 OAuth
// 登入態的帳號清單裡有沒有 CENTRAL_ACCOUNT_ID,沒有就直接丟錯、不放行。
const CENTRAL_KV = {
prod: { id: '2ff9a845e7f646dab7b1e3dd6707b77f', label: 'INSTALLER_KVprod 通道)' },
staging: { id: '750c23faf0794d3db317c32f596df9a9', label: 'PEER_INSTALLER_KVstaging 通道,t157 雙通道問題)' },
};
const CENTRAL_ACCOUNT_ID = '58309bb90fd93ad6d0fe0aae99170e9d'; // uncle6
const CENTRAL_ACCOUNT_LABEL = "uncle6.me@gmail.com's Account";
/** 核對本機 wrangler 的 OAuth 登入態確實看得到 uncle6 帳號,不對就丟錯、不放行任何中心 KV 操作。 */
function assertWranglerIsCentralAccount() {
let out;
try {
out = execFileSync('npx', ['wrangler', 'whoami', '--json'], {
cwd: REPO_ROOT,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
});
} catch (e) {
throw new Error(
`拒絕執行中心 KV 操作:無法確認 wrangler 登入身分(\`wrangler whoami --json\` 失敗:` +
`${e instanceof Error ? e.message : e})。這是刪除工具,身分核不對就不准動中心 KV。`
);
}
const jsonStart = out.indexOf('{');
let who;
try {
who = JSON.parse(jsonStart >= 0 ? out.slice(jsonStart) : out);
} catch {
throw new Error(`拒絕執行中心 KV 操作:解析 \`wrangler whoami --json\` 輸出失敗:${out.slice(0, 300)}`);
}
if (!who.loggedIn) {
throw new Error('拒絕執行中心 KV 操作:wrangler 目前未登入(loggedIn:false)。');
}
const ids = (who.accounts ?? []).map((a) => a.id);
if (!ids.includes(CENTRAL_ACCOUNT_ID)) {
throw new Error(
`拒絕執行中心 KV 操作:wrangler 目前的 OAuth 登入態(${who.email ?? '未知 email'})看得到的帳號是` +
` [${ids.join(', ') || '無'}],裡面沒有中心帳號 ${CENTRAL_ACCOUNT_ID}${CENTRAL_ACCOUNT_LABEL})。` +
` 這代表你現在的 wrangler 登入態不是 uncle6——INSTALLER_KVPEER_INSTALLER_KV 只住在那個帳號,` +
` 繼續執行極可能是對錯的帳號動手(或單純連不到)。請先 \`npx wrangler login\` 切回 uncle6 帳號再重跑。`
);
}
}
function wranglerKvList(namespaceId, prefix) {
const out = execFileSync(
@@ -329,6 +384,7 @@ function wranglerKvDelete(namespaceId, key) {
}
function planCentralCleanup(accountId) {
assertWranglerIsCentralAccount();
const prefix = `deployed:${accountId}:`;
const plan = {};
for (const [chan, kv] of Object.entries(CENTRAL_KV)) {