// 資料外流警示 — 同意憑證機制(data-exfil-warning SDD §7 法律憑證 + §1b API 層) // // 觸發策略(richblack):只在「資料變成可被外部呼叫」時要求同意(暴露面)。 // webhook 部署(workflow 變對外 endpoint)、recipe push 都算。 // // 同意 = 法律憑證:留 log(誰、何時、同意了什麼),真出事時有「用戶明示知情同意」證據, // 避免 arcrun 訴訟風險。「以後不要警示」(suppress_future)本身也 log。 // // 誠實限制:AI 能偽造 confirmed_by_human。本機制的價值是「法律歸責 + 可審」,不是技術防偽。 /** 暴露同意憑證(人類明示知情同意把某資源開放/送出) */ export interface ExposureConsent { confirmed_by_human: true; // 必須為 literal true understood: string; // 人類說明「我知道這會把什麼開放給誰」(非空) confirmed_at: string; // ISO timestamp suppress_future?: boolean; // 「以後不要對此資源警示」(本選擇也 log) } /** * 判斷一個暴露動作是否已取得有效同意。 * @param consent 本次請求帶的同意憑證 * @param priorConsent 既有 record 裡存的同意(首次問、記住:§3) * @returns null = 放行(已同意或已 suppress);string = 拒絕原因 */ export function checkExposureConsent( consent: ExposureConsent | undefined, priorConsent: ExposureConsent | undefined, ): string | null { // 既有同意且選了「以後不警示」→ 放行(首次問記住) if (priorConsent?.suppress_future) return null; // 既有有效同意(同資源已確認過)→ 放行 if (priorConsent?.confirmed_by_human === true) return null; // 本次請求帶了有效同意 → 放行 if ( consent?.confirmed_by_human === true && typeof consent.understood === 'string' && consent.understood.trim() !== '' ) { return null; } return ( '此動作會把資源變成可被外部呼叫(暴露/送出資料)。需人類明示同意。\n' + '請用 CLI 互動確認(acr 會說明風險並提供保護選項),或帶 exposure_consent。\n' + 'arcrun 可幫你保護:要求呼叫者帶 API Key / 設權限 / 限流。' ); } /** * 正規化要存進 record 的同意憑證(法律憑證,可審)。 * 優先用本次新同意,否則沿用既有。 */ export function resolveConsentForRecord( consent: ExposureConsent | undefined, priorConsent: ExposureConsent | undefined, ): ExposureConsent | undefined { if (consent?.confirmed_by_human === true) return consent; return priorConsent; }