import fs from 'node:fs' const read=p=>fs.readFileSync(p,'utf8') const failures=[] const check=(ok,msg)=>{if(!ok)failures.push(msg)} const feedback=read('packages/kbx-ui/src/components/KbxProblemFeedback.vue') for(const token of ["'business-rule': '업무 규칙'","permission: '권한'","integration: '외부 연계'","system: '시스템'","allowedActionIds","allowed.value.has(action.id)","retryable","correlationId","role=\"alert\""]) check(feedback.includes(token),`KbxProblemFeedback missing: ${token}`) check(!feedback.includes('v-html'),'Problem feedback must not render server problem text as raw HTML') for(const [file,name] of [ ['packages/kbx-ui/src/components/KbxListPage.vue','T01'], ['packages/kbx-ui/src/components/KbxMasterPage.vue','T02'], ['packages/kbx-ui/src/components/KbxTransactionPage.vue','T03'], ]){ const source=read(file) for(const token of ['KbxProblemFeedback','problem?:','problemActionAllowlist','retryProblem','dismissProblem']) check(source.includes(token),`${name} template problem contract missing: ${token}`) } const listVm=read('apps/web/src/modules/oms/orders/search/useOrderSearch.ts') for(const token of ['type KbxProblem','const problem = ref(null)',"failedCommand = ref<'ship' | null>","if(isKbxProblem(error))","failedCommand.value='ship'","async function retryProblem","function dismissProblem"]) check(listVm.includes(token),`OMS-ORD-001 problem recovery missing: ${token}`) const listPage=read('apps/web/src/modules/oms/orders/search/OrderListPage.vue') check(listPage.includes(':problem="vm.problem.value"')&&listPage.includes('@retry-problem="vm.retryProblem"')&&listPage.includes('@dismiss-problem="vm.dismissProblem"'),'OMS-ORD-001 must bind actionable command problem feedback') check(!listPage.includes('@problem-action="executeCommand"'),'Server-provided problem action ids must not be wired directly to arbitrary screen commands') const txVm=read('apps/web/src/modules/oms/orders/register/useOrderRegistration.ts') for(const token of ['const problem=ref(null)',"failedAction=ref<'save'|'confirm'|null>","problem.value=issue","failedAction.value='save'","failedAction.value='confirm'","async function retryProblem","function dismissProblem"]) check(txVm.includes(token),`OMS-ORD-002 problem recovery missing: ${token}`) const txPage=read('apps/web/src/modules/oms/orders/register/OrderRegisterPage.vue') check(txPage.includes(':problem="vm.problem.value"')&&txPage.includes('@retry-problem="vm.retryProblem"')&&txPage.includes('@dismiss-problem="vm.dismissProblem"'),'OMS-ORD-002 must bind actionable command problem feedback') const master=read('apps/web/src/modules/erp/items/ItemMasterPage.vue') for(const token of ['const problem = ref(null)',"failedCommand = ref<'load'|'save'|'deactivate'|null>","failedCommand.value = 'load'","failedCommand.value = 'save'","failedCommand.value = 'deactivate'","async function retryProblem","function dismissProblem",':problem="problem"']) check(master.includes(token),`ERP-MST-ITEM-001 problem recovery missing: ${token}`) const ref=read('apps/web/fe-reference/kbx-fe.js') for(const token of ['commandProblem:null','function problemSurface(problem)','data-problem-type','show-blocked','출고할 수 없는 주문이 포함되어 있습니다.']) check(ref.includes(token),`FE Reference actionable problem parity missing: ${token}`) const css=read('apps/web/fe-reference/kbx-fe.css') for(const token of ['.problem-feedback{','.problem-feedback.warning','.problem-feedback.danger','.problem-actions']) check(css.includes(token),`FE Reference problem theme CSS missing: ${token}`) if(failures.length){console.error('FE problem/recovery v47 validation failed:');for(const f of failures)console.error(`- ${f}`);process.exit(1)} console.log('FE problem/recovery v47 PASS: T01/T02/T03 command problems, retry/dismiss, action allow-list security, themed HTML/JS parity.')