import fs from 'node:fs' const read = file => fs.readFileSync(file, 'utf8') const expect = (condition, message) => { if (!condition) { console.error(`FAIL: ${message}`) process.exit(1) } console.log(`PASS: ${message}`) } const excel = read('packages/kbx-ui/src/components/KbxExcelImport.vue') for (const step of ["key: 'file'", "key: 'mapping'", "key: 'validation'", "key: 'preview'", "key: 'commit'", "key: 'result'"]) { expect(excel.includes(step), `T08 Vue import owns ${step.replace("key: '", '').replace("'", '')} stage`) } expect(excel.includes("case 'validated':\n return 'preview'") && excel.includes("case 'committing':\n return 'commit'"), 'T08 maps server status to explicit preview and commit work surfaces') expect(excel.includes('data-kbx-surface="preview"') && excel.includes('반영 미리보기'), 'T08 exposes a dedicated preview surface before mutation') expect(excel.includes('focusNextError') && excel.includes('data-import-error-index') && excel.includes('첫 오류'), 'T08 preview supports deterministic first/next validation-error focus') expect(excel.includes('신규/수정 구분은 서버 Preview가 제공하는 경우에만 표시합니다.'), 'T08 avoids inventing projected create/update counts when server preview does not supply them') expect(excel.includes('같은 파일을 다시 제출하지 마세요'), 'T08 commit surface warns against duplicate user submission during background mutation') const workQueue = read('packages/kbx-ui/src/components/KbxWorkQueuePage.vue') for (const prop of ['queueTitle?:string','queueDescription?:string','queueCount?:number','queueCountUnit?:string']) { expect(workQueue.includes(prop), `T06 compatibility wrapper preserves ${prop}`) } expect(workQueue.includes(':queue-title="queueTitle"') && workQueue.includes('#queue-actions'), 'T06 wrapper forwards work-surface title/count/actions instead of silently dropping them') const operations = read('apps/web/src/modules/common/operations/OperationsQueuePage.vue') expect(operations.includes("label:'표시 긴급'") && operations.includes("label:'표시 미지정'") && operations.includes("label:'표시 60분+'"), 'T06 distinguishes visible-page pressure metrics from authoritative total count') expect(operations.includes('queue-title="예외 업무 Queue"') && operations.includes(':queue-count="query.data.value?.totalCount ?? 0"'), 'real T06 queue exposes authoritative total volume in the standard heading') expect(operations.includes('function openNextUrgent') && operations.includes('label="다음 긴급건"'), 'real T06 lets operators jump directly to the next urgent exception without rescanning the grid') expect(operations.includes('function showMine') && operations.includes("owner: 'mine'"), 'real T06 provides an explicit mine-work shortcut using the existing server filter contract') const reconcile = read('apps/web/src/modules/common/reconcile/ReconcilePage.vue') expect(reconcile.includes('KbxQuickFilterBar') && reconcile.includes("{key:'mismatch',label:'불일치'") && reconcile.includes("{key:'pending',label:'확인중'") && reconcile.includes("{key:'resolved',label:'해결'"), 'T07 exposes explicit all/mismatch/pending/resolved quick-result navigation') expect(reconcile.includes('@row-double-clicked="detail = $event"') && reconcile.includes('KbxDrawer') && reconcile.includes('Expected') && reconcile.includes('Actual') && reconcile.includes('Difference'), 'T07 keeps row comparison context in a dedicated evidence drawer') expect(reconcile.includes("permissionHost?.has('common.operations.create')") && reconcile.includes(':disabled="!canCreateException"'), 'T07 custom detail action respects frontend permission expression while backend remains authoritative') expect(reconcile.includes('원천값을 화면에서 직접 덮어쓰지 않습니다.'), 'T07 resolution policy forbids client-side reconciliation truth mutation') const pickingVm = read('apps/web/src/modules/wms/picking/useWmsPicking.ts') expect(pickingVm.includes('lastConfirmedScan') && pickingVm.includes('command.barcode') && pickingVm.includes('item.command.barcode'), 'T09 records only server-confirmed scans, including idempotent retry confirmation') expect(pickingVm.includes("'작업을 시작하지 못했습니다.'") && pickingVm.includes("'예외를 등록하지 못했습니다.'"), 'T09 start/exception mutation failures now produce actionable operator feedback') const picking = read('apps/web/src/modules/wms/picking/WmsPickingPage.vue') expect(picking.includes('data-kbx-surface="scan-truth"') && picking.includes('최근 서버 확인') && picking.includes('중복 스캔 금지'), 'T09 explicitly separates scan readiness from last authoritative server confirmation') expect(picking.includes(':disabled="!vm.online.value || vm.pendingCommands.value > 0 || vm.scanning.value"'), 'T09 direct quantity action is fail-closed during offline/pending/server-confirmation states') const templateManifest = read('packages/kbx-ui/src/registry/templateManifest.ts') expect(templateManifest.includes('파일→매핑→검증→미리보기→반영→결과 6단계 고정'), 'T08 governance now matches the six-stage Business UX standard') expect(templateManifest.includes('Queue 제목·전체건수·표시건수 구분'), 'T06 governance distinguishes authoritative total and visible pressure') expect(templateManifest.includes('행 단위 근거 Detail로 비교 Context 유지'), 'T07 governance requires evidence-preserving row detail') expect(templateManifest.includes('최근 서버 확인 Scan과 로컬 입력 구분'), 'T09 governance requires authoritative scan truth visibility') const manifest = read('packages/kbx-ui/src/registry/componentManifest.ts') expect(manifest.includes("name: 'KbxExcelImport'") && manifest.includes("version: '1.2.0'"), 'six-stage T08 business component change is versioned') expect(manifest.includes("name: 'KbxWorkQueuePage'") && manifest.includes("version: '1.7.0'"), 'T06 wrapper contract fix is versioned') const referenceHtml = read('apps/web/fe-reference/index.html') const referenceJs = read('apps/web/fe-reference/kbx-fe.js') const referenceCss = read('apps/web/fe-reference/kbx-fe.css') expect(referenceHtml.includes('FE Reference Lab v55'), 'static HTML reference identifies the v55 recovery-workbench build') for (const directive of ["connect-src 'self'", "frame-src 'none'", "worker-src 'none'", "object-src 'none'", "frame-ancestors 'none'"]) { expect(referenceHtml.includes(directive), `static reference CSP preserves ${directive}`) } expect(referenceJs.includes("const steps=['파일','매핑','검증','미리보기','반영','결과']"), 'static HTML/JavaScript T08 retains the same six-stage import grammar') expect(referenceJs.includes('importErrorIndex') && referenceJs.includes('data-import-error-row') && referenceJs.includes('importNextError'), 'static T08 mirrors validation error focus navigation') expect(referenceJs.includes('lastConfirmedScanValue') && referenceJs.includes('최근 서버 확인') && referenceJs.includes('state.lastConfirmedScanValue=normalized'), 'static T09 distinguishes last confirmed scan from buffered/invalid input') expect(referenceCss.includes('.import-error-nav') && referenceCss.includes('.scan-truth'), 'static CSS provides concrete recovery/scan-truth work surfaces') expect(!referenceCss.includes('var(--focus)') && referenceCss.includes('@media(forced-colors:active)'), 'new static styles use defined theme variables and retain high-contrast support') console.log('PASS: FE recovery workbench hardening v55 contract')