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:
@@ -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 Account,58309bb90fd93ad6d0fe0aae99170e9d)。
|
||||
// 沒有這顆帳號的 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_KV(prod 通道)' },
|
||||
staging: { id: '750c23faf0794d3db317c32f596df9a9', label: 'PEER_INSTALLER_KV(staging 通道,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_KV/PEER_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)) {
|
||||
|
||||
Reference in New Issue
Block a user