/* * **층이 이름만 남지 않게 한다** — 계약은 계약 패키지에서 온다. * * ── 왜 있나 (2026-08-30) ────────────────────────────────────────────────── * 어휘와 판별 함수를 `@operato/ops-contract` 로 갈라냈다. 사실을 만드는 쪽(MES · WMS · 커넥터)이 * 엔진을 의존하지 않게 하기 위해서다. * * ── 처음에는 커널이 계약을 다시 내보냈다 ────────────────────────────────── * 이유는 「이 저장소를 한 줄도 안 고쳐도 되게」였다. 그것은 **관리 편의**이지 구조의 근거가 아니다. * 그렇게 두면 같은 이름을 두 경로로 가져올 수 있고, 새 코드가 편한 쪽을 쓰면 계약이 커널 뒤에 숨는다. * * 그때는 이 파일에 **세는 시험**이 있었다 — 커널 경로로 가져오는 자리가 늘지 않는지 보는 것이다. * 세는 시험은 층이 깨졌다는 것을 인정하고 빚을 재는 도구다. 재export 를 걷으면서 그 시험도 없앴다. * * 지금 남은 것은 **사실을 확인하는 것**뿐이다. 커널이 계약을 내보내지 않으므로 잘못된 경로는 아예 * 컴파일되지 않는다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import * as contract from '@operato/ops-contract' import * as kernel from '@operato/twin-kernel' test('★ 계약 패키지가 어휘를 내보낸다', () => { const names = Object.keys(contract) assert.ok(names.length > 100, `계약이 내보내는 이름이 ${names.length}개뿐이다 — 설치가 잘못됐다`) for (const must of ['isEnergyRecord', 'ENERGY_EVENT', 'effectivityAt', 'ingestMasterData', 'procedureViolations']) { assert.ok(names.includes(must), `${must} 가 계약에 없다`) } }) test('★ 커널은 계약을 다시 내보내지 않는다 — 내보내면 어휘가 엔진 뒤에 숨는다', () => { const contractNames = new Set(Object.keys(contract)) const leaked = Object.keys(kernel).filter(n => contractNames.has(n)) assert.deepEqual( leaked, [], `커널이 계약 이름을 다시 내보낸다:\n${leaked.slice(0, 20).join(', ')}\n` + '두 경로로 가져올 수 있게 되면 새 코드가 편한 쪽을 쓰고, 사실을 만들기만 하는 쪽이 엔진을 의존하게 된다.' ) }) test('커널은 엔진을 내보낸다 — 걷어내다가 필요한 것까지 걷지 않았는지 본다', () => { const names = Object.keys(kernel) for (const must of ['EmsKernel', 'MesKernel', 'WmsKernel', 'YmsKernel', 'StateProjector']) { assert.ok(names.includes(must), `${must} 가 커널에 없다`) } })