@things-factory/headless-twin 10.0.14 → 10.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist-server/engine/canonical-ingest.js +30 -0
  2. package/dist-server/engine/canonical-ingest.js.map +1 -1
  3. package/dist-server/engine/ingest-health.d.ts +35 -0
  4. package/dist-server/engine/ingest-health.js +45 -0
  5. package/dist-server/engine/ingest-health.js.map +1 -1
  6. package/dist-server/engine/twin-engine.d.ts +7 -0
  7. package/dist-server/engine/twin-engine.js +19 -0
  8. package/dist-server/engine/twin-engine.js.map +1 -1
  9. package/dist-server/service/reference/reference-adapter.d.ts +25 -0
  10. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  11. package/dist-server/service/reference/reference-live.js +12 -0
  12. package/dist-server/service/reference/reference-live.js.map +1 -1
  13. package/dist-server/service/twin-event/twin-event-keys.d.ts +21 -1
  14. package/dist-server/service/twin-event/twin-event-keys.js +35 -3
  15. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  16. package/dist-server/service/twin-model/epcis-coverage.js +5 -1
  17. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  18. package/dist-server/service/twin-model/isa95-coverage.d.ts +10 -0
  19. package/dist-server/service/twin-model/isa95-coverage.js +3 -3
  20. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  21. package/dist-server/service/twin-model/twin-model-query.js +71 -3
  22. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  23. package/package.json +3 -3
  24. package/server/engine/canonical-ingest.ts +30 -0
  25. package/server/engine/ingest-health.ts +65 -0
  26. package/server/engine/twin-engine.ts +20 -0
  27. package/server/service/reference/reference-adapter.ts +22 -0
  28. package/server/service/reference/reference-live.ts +12 -0
  29. package/server/service/twin-event/twin-event-keys.ts +35 -3
  30. package/server/service/twin-model/epcis-coverage.ts +5 -1
  31. package/server/service/twin-model/isa95-coverage.ts +13 -3
  32. package/server/service/twin-model/twin-model-query.ts +71 -3
  33. package/test/standard-coverage.test.ts +44 -0
  34. package/test/twin-event-keys.test.ts +48 -2
  35. package/test/withheld-door.test.ts +95 -0
  36. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"isa95-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/isa95-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * ISA-95 적합성 기준 — **표준이 요구하는 것의 목록.**\n *\n * ── 왜 이 파일이 필요한가 ──────────────────────────────────────────────────────\n * 지금까지 화면은 \"우리가 가진 축\" 만 보였다. 그러면 채운 것은 세어지지만 **빠진 것은 셀 수 없다** —\n * 없는 것은 화면에 없으므로 아무도 그것을 묻지 않는다. 표준 준수는 \"우리 축에 표준 이름을 붙였다\"\n * 가 아니라 **표준의 목록을 기준으로 빠진 것이 없다**는 말이어야 한다. 기준이 코드로 있어야 그\n * 문장을 검사할 수 있다.\n *\n * ── 세 층에서 본다 ────────────────────────────────────────────────────────────\n * 표준 준수는 한 층의 일이 아니다. 개념마다 셋을 따로 본다:\n *\n * · `structure` — **데이터구조**가 있나(커널 축·호스트 표에 이 개념의 자리가 있나)\n * · `behavior` — **기능**이 그것을 쓰나(커널이 계획·집행·집계에서 실제로 다루나)\n * · `surface` — **UI 노출**이 있나(사용자가 그것을 볼 수 있나)\n *\n * 셋을 합치면 \"구조는 있는데 아무도 안 쓰는 컬럼\" 과 \"커널은 쓰는데 화면에 없는 사실\" 이 같은\n * 값으로 보인다. 실제로 이 프로젝트에서 둘 다 나왔다(인력 명세는 구조·기능이 있었지만 화면에\n * 없었고, `properties` 는 구조만 있다).\n *\n * ── 값은 관측이 아니라 선언이다 ────────────────────────────────────────────────\n * 여기 적힌 것은 **사람이 판단해 적은 사실**이다(자동 판정이 아니다). 그래서 틀릴 수 있고, 틀리면\n * 여기를 고쳐야 한다 — 화면은 이 선언을 그대로 보이고 지어내지 않는다. 자동으로 알 수 있는 것\n * 하나는 자동으로 한다: `axis` 가 커널 축 목록에 없으면 그것은 **구조가 없는 것**이다(오타 방지).\n *\n * ── 이 표가 커널에 없는 이유 ──────────────────────────────────────────────────\n * 커널은 **런타임 모델의 SSOT** 다(무엇이 있는가). 이 표는 **참조 모델의 목록**이다(무엇이 있어야\n * 하는가). 둘은 다른 종류의 사실이고, 후자는 커널을 배포하지 않고도 늘어야 한다.\n */\n\n/** 한 층의 상태 — 있음 · 부분 · 없음. \"부분\" 을 두는 이유: 절반 있는 것을 있다고 하면 기준이 죽는다. */\nexport type CoverageLevel = 'full' | 'partial' | 'none'\n\nexport interface Isa95Concept {\n /** 표준이 부르는 이름 — 이 표의 키다(우리 어휘가 아니라 표준 어휘). */\n std: string\n /** 표준의 어느 부분인가 — Part 2(자원 모델) · Part 4(일정·실적) 등. */\n part: string\n /** 사람이 읽는 이름(i18n 키). 화면이 번역한다. */\n label: string\n /** 우리 커널 축 — 있으면 데이터구조가 있는 것이다. 없으면 `null`. */\n axis: string | null\n /** 세 층의 상태. `structure` 는 `axis` 로도 검증된다. */\n structure: CoverageLevel\n behavior: CoverageLevel\n surface: CoverageLevel\n /** 왜 그 수준인지 — **모르는 것을 감추지 않기 위해** 한 줄로 적는다(i18n 키). */\n note?: string\n /**\n * 자기 축이 없을 때 **어디서 보이나** — 그 사실을 들고 있는 축들.\n *\n * 「어디에」칸이 두 가지를 합쳐고 있었다: *아직 축이 없다(부채)* 와 *축일 수 없다(설계)*. 가용 능력은\n * 개체가 아니라 자원의 지금 상태이므로 축이 될 수 없는데, 세 층이 다 완전한 줄이 \"개념 축이 없습니다\"\n * 라고만 말하면 **표가 스스로 모순된 말을 한다**(다 됐다면서 없다고 한다).\n *\n * 그래서 축일 수 없는 것은 갈 곳을 가리킨다. 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다.\n */\n shownOn?: string[]\n}\n\n/*\n * 가용 능력(2026-08-12, 커널 0.6.8) — **판정과 이유가 상태에 실려 온다**(`capability {available, reason}`).\n *\n * 셋 다 완전인 근거: 상태가 판정을 들고 오고(시뮬·미러가 같은 함수로 파생한다), 배정이 같은 판정을\n * 쓰고, 두 화면이 같은 낱말로 그 이유를 보인다(자원 탭 배지 · 인스펙션 관측 열).\n *\n * 왜 축이 없는가 — 가용 능력은 개체가 아니라 **자원의 지금 상태**다. 개념 카드로 세우면 사람·설비와\n * 나란히 놓여 \"능력이 몇 개인가\" 를 세게 되는데, 그건 물을 일이 아니다.\n */\n\n/*\n * 생산 능력(2026-08-13, 커널 0.6.12) — **구간의 양을 표준 종류로 구분한다.**\n *\n * `structure` 완전: 계약이 보고서를 낸다(`operationsCapabilityOf`) — 구간(StartTime·EndTime) · 표준\n * 열거값(Committed·Available·Unattainable) · 공정별 분해 · 제약하는 축. 낱말은 B2MML 대조 후 표준 것을\n * 그대로 쓴다(우리 낱말을 만들지 않았다).\n * `behavior` 완전: 약정은 오더에서, 달성불가는 자원 판정(`capabilityOf`)에서 온다 — 그리고 그 판정은\n * 배정이 쓰는 것과 같은 한 벌이다. 표준의 `Unattainable` 정의가 그 판정과 겹친다.\n * `surface` 완전: 두 자리에서 보인다 — 성과 화면이 현장 전체의 \"앞으로 더 받을 수 있는 몫\" 을, 공정\n * 상세가 그 공정 하나의 분해(상한·약정·달성불가·가용)와 제약하는 축을 보인다. 두 화면은 **같은 낱말**을\n * 쓴다(같은 i18n 키) — 같은 사실을 다르게 부르면 어느 쪽이 맞는지 아무도 모른다.\n *\n * 남은 것 — 표준은 **게시된(약속된) 능력**도 담는다(계산이 아니라 선언). 값을 실을 소스가 아직\n * 침묵하므로 빈 축을 먼저 만들지 않는다(상태 쪽 `workCalendar` 를 지운 이유와 같다).\n *\n * 과거 분해(Used·Unused)는 이 보고서에 없다 — 그것은 실적이고 이미 두 곳에 있다(설비 OEE 의 가동/유휴,\n * 성과 화면의 구간 가동률). 표준이 그 둘을 다른 종류로 부르므로 **합치지 않는다.**\n */\n\n/*\n * Part 2 — 자원 모델. 네 자원 각각이 **등급/정의 ↔ 개체** 이중구조를 갖는 것이 이 부분의 핵심이고,\n * 그 이중성이 이 트윈의 뼈대이기도 하다.\n */\nconst PART2: Isa95Concept[] = [\n { std: 'PersonnelClass', part: '2', label: 'twin.isa95.PersonnelClass', axis: 'personnelClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'Person', part: '2', label: 'twin.isa95.Person', axis: 'persons', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'PersonnelCapability', part: '2', label: 'twin.isa95.PersonnelCapability', axis: null, shownOn: ['persons'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },\n { std: 'EquipmentClass', part: '2', label: 'twin.isa95.EquipmentClass', axis: 'equipmentClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'Equipment', part: '2', label: 'twin.isa95.Equipment', axis: 'equipment', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'EquipmentCapability', part: '2', label: 'twin.isa95.EquipmentCapability', axis: null, shownOn: ['equipment'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },\n { std: 'PhysicalAssetClass', part: '2', label: 'twin.isa95.PhysicalAssetClass', axis: 'assetClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'PhysicalAsset', part: '2', label: 'twin.isa95.PhysicalAsset', axis: 'assets', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'MaterialClass', part: '2', label: 'twin.isa95.MaterialClass', axis: 'materialClasses', structure: 'full', behavior: 'partial', surface: 'full' },\n { std: 'MaterialDefinition', part: '2', label: 'twin.isa95.MaterialDefinition', axis: 'materialDefinitions', structure: 'full', behavior: 'full', surface: 'full' },\n /*\n * 로트·서브로트는 개체 축이 아니라 **관측**으로 존재한다(EPCIS 개체) — 구조는 저널이 갖는다.\n *\n * ── 2026-08-24 로트가 시험 결과를 들게 됐다 (커널 0.7.57) ─────────────────\n * `ItemState.testResults` 로 「이 로트를 쓸 수 있나」의 **근거**가 로트에 실린다. 그전에는 판정\n * (`disposition`)만 있었고 「왜 그렇게 판정했나」를 되짚을 수 없었다.\n *\n * **구조를 `full` 로 올리지 않는다.** `MaterialLotType` 원문(B2MML-Material.xsd)이 드는 것 중\n * 아직 없는 것이 있다: `MaterialLotProperty`(시험이 아닌 로트 속성 — 원산지·포장 형태) ·\n * `TestSpecificationID`(로트별 기준 참조 — 품목 정의가 대신 답하므로 두지 않았다) · `Status` ·\n * `HierarchyScope` · `Version`. 채운 것만 세는 것이 이 표의 규율이다.\n */\n { std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: null, structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },\n /*\n * ── 2026-08-24 판정 정정: `none` 이 아니었다 ──────────────────────────────\n * 이 줄은 셋 다 `none` 이었는데 **표가 코드보다 뒤처져 있었다.** 커널은 서브로트를 든다:\n *\n * `ItemState.subLotId` 표준 `MaterialSubLot.ID` (계약이 그렇게 적고 있다)\n * `subLotIdOf(class, location)` 비직렬 로트가 자리마다 나뉠 때 그 부분의 이름을 만든다\n * `itemKeyOf(item)` **개체의 정체성이 이 값이다** — `subLotId ?? epc`\n *\n * 기능이 `full` 인 근거: 이 축이 없으면 같은 로트를 rack-1 에 100개·rack-2 에 60개 관측했을 때\n * **뒤에 온 관측이 앞을 덮어 100개가 조용히 사라진다**(합계 160 → 60). 실제로 그 결함을 이 축으로\n * 고쳤다. 즉 장식이 아니라 정체성을 지탱한다.\n *\n * 구조가 `partial` 인 이유: 서브로트는 로트와 같은 모양이고(위 참조) 그 로트가 `partial` 이다.\n * 화면이 `partial` 인 이유: 값이 물품의 **이름으로** 나온다(`twin-item` 의 line) — 「이 로트가 자리마다\n * 나뉘어 있다」를 말하는 자리는 아직 없다.\n *\n * 이 부류(코드에 있는데 표가 `none`)는 자동으로 잡히지 않는다 — 표의 가드는 반대 방향만 본다\n * (축이 사라지면 구조를 내린다). 그래서 축을 늘릴 때 이 표를 함께 보는 것이 규율이다.\n */\n { std: 'MaterialSublot', part: '2', label: 'twin.isa95.MaterialSublot', axis: null, structure: 'partial', behavior: 'full', surface: 'partial' },\n { std: 'ProcessSegment', part: '2', label: 'twin.isa95.ProcessSegment', axis: 'operations', structure: 'full', behavior: 'full', surface: 'full' },\n /*\n * 속성은 자원마다 붙는 확장이라 **자기 축이 아니다** — 그래서 `axis` 는 없지만 구조는 완전하다:\n * 계약(`ResourceProperty`: 값·데이터형·단위·하위·등급속성)이 표준을 그대로 담고, 인제스트·상태·\n * 화면이 그것을 통과시킨다.\n *\n * 기능은 **부분**이다: 호스트의 주행 추정기가 `speed` 를 읽어 이동 시간을 만드는 것이 유일한 소비처다\n * (다른 속성은 아직 아무도 읽지 않는다). 그 사실을 `full` 로 올리면 표가 거짓이 된다.\n */\n { std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },\n /*\n * **자리의 상시 관측** — 2026-08-24 신설(커널 0.7.56). 표에 이 개념이 아예 없었다.\n *\n * 왜 이 축이 필요한가: 트윈의 판정 대상은 물건의 상태이고, 물건의 상태는 **조건 없이 정해지지\n * 않는다.** 「이 로트가 냉장실에 있었다」까지만 아는 트윈은 그 로트가 괜찮았는지 말할 수 없다. 그리고\n * **이 조인은 트윈만 할 수 있다** — 계측 시스템은 물건의 자리 이력을 모르고, 물류 시스템은 조건\n * 이력을 모른다.\n *\n * 표준 앵커: `OperationsEventType`(B2MML-OperationsEvent.xsd, ISA-95.00.02-2018) +\n * `OperationsRecordTemplateType`. 우리가 든 것 — `EffectiveTimestamp` · `EffectiveEndDate` ·\n * `RecordTimestamp` · `HierarchyScope`(자리) · `Source` · 값은 `ValueType` 으로 **좁혔다**\n * (단위 없는 물리량은 판정의 재료가 못 된다).\n *\n * 붙는 자리가 `HierarchyScope.EquipmentID` 인 것이 중요하다 — ISA-95 에서 장소 계층이 곧 설비\n * 계층이고 냉장실은 `StorageZone` 수준의 설비다. `OperationalLocationType` 에는 이 축이 없다.\n *\n * 구조가 `partial`: 봉투 전체(`ID`·`Description`·사건 분류·`OperationsRecord` 구조)가 아니라 관측\n * 하나를 담는 데 필요한 칸만 든다.\n * 기능이 `full`: 사건 채널(`location.measured`)이 상태를 만들고, 선언된 기준으로 판정해\n * `observation-out-of-limit` 신호를 세운다.\n * 화면이 `none`: 클라이언트에 이 축을 그리는 곳이 없다(실측 0곳).\n */\n { std: 'OperationsEvent', part: '2', label: 'twin.isa95.OperationsEvent', axis: null, shownOn: ['locations'], structure: 'partial', behavior: 'full', surface: 'none' }\n]\n\n/*\n * Part 4 — 일정과 실적.\n *\n * **2026-08-11 이전에는 이 부분이 거의 비어 있었다.** 오더·작업은 런타임과 저널에 실재하는데 조회\n * 모델의 축이 아니었고, 그래서 \"이 트윈이 표준의 어디를 채우나\" 라는 질문에 절반만 답했다.\n *\n * 커널이 계약을 넓혀(`source: 'state'`) 두 축이 추가되었다 — 이제 요구(`orders`)와 응답(`tasks`)을 개념으로\n * 가리켜 걸어 들어갈 수 있다. 남은 것은 **과거로 다시 계산하는 길**(저널 커서 페이징)과 능력·검사 명세다.\n */\nconst PART4: Isa95Concept[] = [\n { std: 'OperationsDefinition', part: '4', label: 'twin.isa95.OperationsDefinition', axis: 'productionSpec', structure: 'full', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.opsdef' },\n /*\n * 2026-08-11 — **축이 추가되었다**(커널 0.6.3 · `source: 'state'`). 요구는 `orders`, 응답은 `tasks` 다.\n *\n * `structure` 를 `full` 로 올린다: 담을 자리가 계약에 있다(상태 계약 + 축 선언). **테이블 투영이\n * 없는 것은 결손이 아니다** — 이 축의 정본은 저널이고, 투영하지 않는 것이 결정이다(ADR-0032-D).\n *\n * `surface` 도 `full` 이다(같은 날 두 번째 단계): 목록·상세·관계에 더해 **과거로 다시 계산하는 길**이 화면에\n * 있다 — 시각 커서를 옮기면 그 시점의 사실이 나오고, 무엇을 근거로 답했는지도 화면이 말한다\n * (라이브=권위 · 저널=파생 · 접은 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.\n */\n { std: 'OperationsSchedule', part: '4', label: 'twin.isa95.OperationsSchedule', axis: 'orders', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.schedule' },\n { std: 'OperationsPerformance', part: '4', label: 'twin.isa95.OperationsPerformance', axis: 'tasks', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.performance' },\n { std: 'OperationsCapability', part: '4', label: 'twin.isa95.OperationsCapability', axis: null, shownOn: ['operations'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.opscapability' },\n /*\n * 2026-08-12 — **축이 추가되었다**(커널 0.6.5). 자원 아홉이 가리키던 참조에 대상이 생겼고, 없는 명세를\n * 가리키면 선 채움이 끊어진 참조로 센다.\n *\n * 기능이 **`partial` 로 살아났다**(커널 0.6.6): 결과(`TestResult`)가 계약에 들어오면서 커널이 배정에서\n * 그것을 본다 — 만료·불합격이면 그 자격은 성립하지 않는다(실측: 만료된 용접사는 용접 작업 5건 중 한\n * 번도 배정되지 않았다).\n *\n * ── 2026-08-24 위 문장 하나가 낡았다 ─────────────────────────────────────\n * 「**사람만 판정한다**」고 적혀 있었다. 지금은 아니다 — `capabilityOfResource` 가 설비·사람·자산\n * **셋 모두**에 `requiredTests` 를 걸고, 그 답이 배정의 가용성 필터로 들어간다. 그리고 판정 대상이\n * 자원 밖으로 넓어졌다: **자리의 상시 관측**(`OperationsEvent` 줄)과 **로트의 시험 결과**\n * (`TestResult` 줄)도 같은 기준으로 판정한다.\n *\n * 남은 규율은 하나다: **결과가 없으면 판정하지 않는다**(선언한 것만 제약이 된다 — 결손이 아니라 규율).\n *\n * 그리고 기준의 **숫자 한계는 우리가 더한 것**이다. 표준 `TestSpecificationCriteria.Expression` 은\n * 자유 문장(`TextType`)이고, B2MML 일곱 파일에 `Minimum`·`Maximum`·`Tolerance` 가 하나도 없다\n * (원문 대조). 그것 없이는 판정할 수 없으므로 `limit: {minimum, maximum, uom}` 을 더했고, 발명한\n * 자리임을 계약에 적었다. 커널은 `Expression` 을 **읽지 않는다** — 문법이 정의되지 않았으므로\n * 파싱하면 그 순간 방언이다.\n */\n { std: 'TestSpecification', part: '4', label: 'twin.isa95.TestSpecification', axis: 'testSpecifications', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.test' },\n /*\n * **시험 결과** — 2026-08-24 신설(커널 0.7.57). 기준만 있고 결과의 자리가 표에 없었다.\n *\n * 표준 `TestResultType`(B2MML-OperationsTest.xsd)에서 우리가 든 것:\n * `EvaluationDate`(at) · `Expiration`(expiresAt) · `TestableObjectID` · `PropertyMeasurement`\n * · `EvaluatedCriterionResult` → **`result: 'pass'|'fail'` 로 좁혔다**(표준은 열거하지 않는다)\n * 아직 없는 것: `ID` · `Description` · `HierarchyScope` · `OperationsTestRequirementID` ·\n * `TestResultChild`. 그래서 구조는 `partial` 이다.\n *\n * 기능이 `partial` 인 이유를 정확히 적는다 — **자원에서는 결정을 바꾸고 로트에서는 아직 바꾸지\n * 않는다.** 만료·불합격 자원에는 작업이 배정되지 않지만, 불합격 로트가 할당에서 빠지거나 주의 신호를\n * 세우지는 않는다(들고·판정하고·되짚을 수는 있다). 그 자리를 `full` 로 적으면 표가 거짓이 된다.\n *\n * 화면은 `none` 이다 — 클라이언트에 이 축을 그리는 곳이 없다(실측: `operato-twin/client` 에 0곳).\n */\n { std: 'TestResult', part: '4', label: 'twin.isa95.TestResult', axis: null, shownOn: ['items'], structure: 'partial', behavior: 'partial', surface: 'none' },\n { std: 'WorkMaster', part: '4', label: 'twin.isa95.WorkMaster', axis: 'recipes', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'WorkDirective', part: '4', label: 'twin.isa95.WorkDirective', axis: null, structure: 'none', behavior: 'none', surface: 'none' }\n]\n\n/** 표준이 요구하는 것 전부 — 이 배열이 곧 기준이다. */\nexport const ISA95_CONCEPTS: Isa95Concept[] = [...PART2, ...PART4]\n\n/**\n * 적합성 보고 — **채운 것과 빠진 것을 함께.**\n *\n * `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로\n * **구조 없음으로 낮추고 그 사실을 말한다**(조용히 통과시키면 이 표가 거짓이 된다).\n */\n/**\n * 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 조용히 지우지 않는다.**\n *\n * 두 자리가 어긋날 수 있다.\n * · `axis` — 그 개념 자신의 문. 커널에 없으면 구조를 「없음」으로 낮추고 그 사실을 말한다.\n * · `shownOn` — 그 사실을 들고 있는 다른 축. 예전에는 없는 축을 **말없이 걸러 냈다.** 그러면 그 줄은\n * 「개념 축이 없습니다」로 보이고, 사람은 그것을 **원래 그런 것**으로 읽는다. 실제로 그 일이 있었다:\n * EPCIS 표의 일곱 줄이 커널에 없는 축(`items`)에 기대고 있었는데, 경고가 뜬 것은 두 줄뿐이었다.\n *\n * 둘 중 하나라도 어긋나면 `axisMissing` 이다 — 표가 스스로 「최신이 아니다」라고 말하게 한다.\n */\nexport function reconcileAxes(c: Isa95Concept, known: Set<string>): Isa95Concept & { axisMissing?: boolean } {\n const ownMissing = !!c.axis && !known.has(c.axis)\n const shownOn = c.shownOn?.filter(a => known.has(a))\n const lostShown = !!c.shownOn && (shownOn?.length ?? 0) !== c.shownOn.length\n const fixed = lostShown ? { ...c, shownOn } : c\n if (!ownMissing && !lostShown) return fixed\n /* 구조를 낮추는 것은 **자기 문이 없어진 경우**다 — 남의 축에서 보이던 것은 구조가 아니라 표시의 문제다. */\n return ownMissing ? { ...fixed, structure: 'none' as CoverageLevel, axisMissing: true } : { ...fixed, axisMissing: true }\n}\n\nexport function isa95Coverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = ISA95_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n /* 채운 수는 `full` 만 센다 — 부분을 채운 것으로 세면 기준이 스스로를 속인다. */\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n model: 'ISA-95',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
1
+ {"version":3,"file":"isa95-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/isa95-coverage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAiPH,sCAQC;AAED,sCAaC;AA9ND;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;GAGG;AACH,MAAM,KAAK,GAAmB;IAC5B,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,2BAA2B,EAAE,IAAI,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IACxJ,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IAC/H,EAAE,GAAG,EAAE,qBAAqB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,gCAAgC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,4BAA4B,EAAE;IAC9M,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,2BAA2B,EAAE,IAAI,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IACxJ,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IACvI,EAAE,GAAG,EAAE,qBAAqB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,gCAAgC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,4BAA4B,EAAE;IAChN,EAAE,GAAG,EAAE,oBAAoB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,+BAA+B,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IAC5J,EAAE,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,0BAA0B,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IAC5I,EAAE,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,0BAA0B,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE;IACxJ,EAAE,GAAG,EAAE,oBAAoB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,+BAA+B,EAAE,IAAI,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IACnK;;;;;;;;;;;OAWG;IACH,EAAE,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,wBAAwB,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,qBAAqB,EAAE;IACvK;;;;;;;;;;;;;;;;;;OAkBG;IACH,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,2BAA2B,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE;IACpK,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,2BAA2B,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IAClJ;;;;;;;OAOG;IACH,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE;IACnK;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,EAAE,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;CAChJ,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,KAAK,GAAmB;IAC5B,EAAE,GAAG,EAAE,sBAAsB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,iCAAiC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,wBAAwB,EAAE;IACrM;;;;;;;;;OASG;IACH,EAAE,GAAG,EAAE,oBAAoB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,+BAA+B,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE;IACxL,EAAE,GAAG,EAAE,uBAAuB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,kCAAkC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,6BAA6B,EAAE;IAChM,EAAE,GAAG,EAAE,sBAAsB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,iCAAiC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,+BAA+B,EAAE;IACtN;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,EAAE,GAAG,EAAE,mBAAmB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,8BAA8B,EAAE,IAAI,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,EAAE;IAC9L;;;;;;;;;;;;;;OAcG;IACH,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,uBAAuB,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE;IACxI,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,uBAAuB,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;IACvI,EAAE,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,0BAA0B,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;CACzI,CAAA;AAED,oCAAoC;AACvB,QAAA,cAAc,GAAmB,CAAC,GAAG,KAAK,EAAE,GAAG,KAAK,CAAC,CAAA;AAElE;;;;;GAKG;AACH;;;;;;;;;;GAUG;AACH,SAAgB,aAAa,CAAC,CAAe,EAAE,KAAkB;IAC/D,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACjD,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACpD,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAA;IAC5E,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAA;IAC3C,mEAAmE;IACnE,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,MAAuB,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;AAC3H,CAAC;AAED,SAAgB,aAAa,CAAC,IAAc;IAK1C,MAAM,QAAQ,GAAG,sBAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzE,qDAAqD;IACrD,MAAM,KAAK,GAAG,CAAC,CAAuC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,MAAM,CAAA;IACvG,OAAO;QACL,KAAK,EAAE,QAAQ;QACf,QAAQ;QACR,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE;KAC7H,CAAA;AACH,CAAC","sourcesContent":["/*\n * ISA-95 적합성 기준 — **표준이 요구하는 것의 목록.**\n *\n * ── 왜 이 파일이 필요한가 ──────────────────────────────────────────────────────\n * 지금까지 화면은 \"우리가 가진 축\" 만 보였다. 그러면 채운 것은 세어지지만 **빠진 것은 셀 수 없다** —\n * 없는 것은 화면에 없으므로 아무도 그것을 묻지 않는다. 표준 준수는 \"우리 축에 표준 이름을 붙였다\"\n * 가 아니라 **표준의 목록을 기준으로 빠진 것이 없다**는 말이어야 한다. 기준이 코드로 있어야 그\n * 문장을 검사할 수 있다.\n *\n * ── 세 층에서 본다 ────────────────────────────────────────────────────────────\n * 표준 준수는 한 층의 일이 아니다. 개념마다 셋을 따로 본다:\n *\n * · `structure` — **데이터구조**가 있나(커널 축·호스트 표에 이 개념의 자리가 있나)\n * · `behavior` — **기능**이 그것을 쓰나(커널이 계획·집행·집계에서 실제로 다루나)\n * · `surface` — **UI 노출**이 있나(사용자가 그것을 볼 수 있나)\n *\n * 셋을 합치면 \"구조는 있는데 아무도 안 쓰는 컬럼\" 과 \"커널은 쓰는데 화면에 없는 사실\" 이 같은\n * 값으로 보인다. 실제로 이 프로젝트에서 둘 다 나왔다(인력 명세는 구조·기능이 있었지만 화면에\n * 없었고, `properties` 는 구조만 있다).\n *\n * ── 값은 관측이 아니라 선언이다 ────────────────────────────────────────────────\n * 여기 적힌 것은 **사람이 판단해 적은 사실**이다(자동 판정이 아니다). 그래서 틀릴 수 있고, 틀리면\n * 여기를 고쳐야 한다 — 화면은 이 선언을 그대로 보이고 지어내지 않는다. 자동으로 알 수 있는 것\n * 하나는 자동으로 한다: `axis` 가 커널 축 목록에 없으면 그것은 **구조가 없는 것**이다(오타 방지).\n *\n * ── 이 표가 커널에 없는 이유 ──────────────────────────────────────────────────\n * 커널은 **런타임 모델의 SSOT** 다(무엇이 있는가). 이 표는 **참조 모델의 목록**이다(무엇이 있어야\n * 하는가). 둘은 다른 종류의 사실이고, 후자는 커널을 배포하지 않고도 늘어야 한다.\n */\n\n/** 한 층의 상태 — 있음 · 부분 · 없음. \"부분\" 을 두는 이유: 절반 있는 것을 있다고 하면 기준이 죽는다. */\nexport type CoverageLevel = 'full' | 'partial' | 'none'\n\nexport interface Isa95Concept {\n /** 표준이 부르는 이름 — 이 표의 키다(우리 어휘가 아니라 표준 어휘). */\n std: string\n /** 표준의 어느 부분인가 — Part 2(자원 모델) · Part 4(일정·실적) 등. */\n part: string\n /** 사람이 읽는 이름(i18n 키). 화면이 번역한다. */\n label: string\n /** 우리 커널 축 — 있으면 데이터구조가 있는 것이다. 없으면 `null`. */\n axis: string | null\n /** 세 층의 상태. `structure` 는 `axis` 로도 검증된다. */\n structure: CoverageLevel\n behavior: CoverageLevel\n surface: CoverageLevel\n /** 왜 그 수준인지 — **모르는 것을 감추지 않기 위해** 한 줄로 적는다(i18n 키). */\n note?: string\n /**\n * 자기 축이 없을 때 **어디서 보이나** — 그 사실을 들고 있는 축들.\n *\n * 「어디에」칸이 두 가지를 합쳐고 있었다: *아직 축이 없다(부채)* 와 *축일 수 없다(설계)*. 가용 능력은\n * 개체가 아니라 자원의 지금 상태이므로 축이 될 수 없는데, 세 층이 다 완전한 줄이 \"개념 축이 없습니다\"\n * 라고만 말하면 **표가 스스로 모순된 말을 한다**(다 됐다면서 없다고 한다).\n *\n * 그래서 축일 수 없는 것은 갈 곳을 가리킨다. 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다.\n *\n * ── ★ `surface` 와 짝을 맞춘다 (2026-08-24 실측으로 붙임) ─────────────────\n * 이 칸은 「그 사실이 어느 축에 **담겨 있나**」가 아니라 「**어디서 보이나**」다. 화면이 그것을\n * 「해당 축 항목에 표시됩니다」로 읽어 내므로, 둘이 어긋나면 표가 거짓을 말한다.\n *\n * `surface: 'none'` 인데 여기가 차 있다 → **거짓**. 없는 화면으로 사람을 보낸다\n * `surface` 가 있는데 여기가 비어 있다 → **과소**. 「진짜 빈칸」으로 읽힌다(위 규칙)\n *\n * 같은 날 셋을 그렇게 틀리게 넣었다: 새 개념 둘(`TestResult`·`OperationsEvent`)에 화면이 없는데\n * 갈 곳을 적었고, `MaterialSublot` 은 물품 이름으로 보이는데 갈 곳을 비워 두었다.\n */\n shownOn?: string[]\n}\n\n/*\n * 가용 능력(2026-08-12, 커널 0.6.8) — **판정과 이유가 상태에 실려 온다**(`capability {available, reason}`).\n *\n * 셋 다 완전인 근거: 상태가 판정을 들고 오고(시뮬·미러가 같은 함수로 파생한다), 배정이 같은 판정을\n * 쓰고, 두 화면이 같은 낱말로 그 이유를 보인다(자원 탭 배지 · 인스펙션 관측 열).\n *\n * 왜 축이 없는가 — 가용 능력은 개체가 아니라 **자원의 지금 상태**다. 개념 카드로 세우면 사람·설비와\n * 나란히 놓여 \"능력이 몇 개인가\" 를 세게 되는데, 그건 물을 일이 아니다.\n */\n\n/*\n * 생산 능력(2026-08-13, 커널 0.6.12) — **구간의 양을 표준 종류로 구분한다.**\n *\n * `structure` 완전: 계약이 보고서를 낸다(`operationsCapabilityOf`) — 구간(StartTime·EndTime) · 표준\n * 열거값(Committed·Available·Unattainable) · 공정별 분해 · 제약하는 축. 낱말은 B2MML 대조 후 표준 것을\n * 그대로 쓴다(우리 낱말을 만들지 않았다).\n * `behavior` 완전: 약정은 오더에서, 달성불가는 자원 판정(`capabilityOf`)에서 온다 — 그리고 그 판정은\n * 배정이 쓰는 것과 같은 한 벌이다. 표준의 `Unattainable` 정의가 그 판정과 겹친다.\n * `surface` 완전: 두 자리에서 보인다 — 성과 화면이 현장 전체의 \"앞으로 더 받을 수 있는 몫\" 을, 공정\n * 상세가 그 공정 하나의 분해(상한·약정·달성불가·가용)와 제약하는 축을 보인다. 두 화면은 **같은 낱말**을\n * 쓴다(같은 i18n 키) — 같은 사실을 다르게 부르면 어느 쪽이 맞는지 아무도 모른다.\n *\n * 남은 것 — 표준은 **게시된(약속된) 능력**도 담는다(계산이 아니라 선언). 값을 실을 소스가 아직\n * 침묵하므로 빈 축을 먼저 만들지 않는다(상태 쪽 `workCalendar` 를 지운 이유와 같다).\n *\n * 과거 분해(Used·Unused)는 이 보고서에 없다 — 그것은 실적이고 이미 두 곳에 있다(설비 OEE 의 가동/유휴,\n * 성과 화면의 구간 가동률). 표준이 그 둘을 다른 종류로 부르므로 **합치지 않는다.**\n */\n\n/*\n * Part 2 — 자원 모델. 네 자원 각각이 **등급/정의 ↔ 개체** 이중구조를 갖는 것이 이 부분의 핵심이고,\n * 그 이중성이 이 트윈의 뼈대이기도 하다.\n */\nconst PART2: Isa95Concept[] = [\n { std: 'PersonnelClass', part: '2', label: 'twin.isa95.PersonnelClass', axis: 'personnelClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'Person', part: '2', label: 'twin.isa95.Person', axis: 'persons', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'PersonnelCapability', part: '2', label: 'twin.isa95.PersonnelCapability', axis: null, shownOn: ['persons'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },\n { std: 'EquipmentClass', part: '2', label: 'twin.isa95.EquipmentClass', axis: 'equipmentClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'Equipment', part: '2', label: 'twin.isa95.Equipment', axis: 'equipment', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'EquipmentCapability', part: '2', label: 'twin.isa95.EquipmentCapability', axis: null, shownOn: ['equipment'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },\n { std: 'PhysicalAssetClass', part: '2', label: 'twin.isa95.PhysicalAssetClass', axis: 'assetClasses', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'PhysicalAsset', part: '2', label: 'twin.isa95.PhysicalAsset', axis: 'assets', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'MaterialClass', part: '2', label: 'twin.isa95.MaterialClass', axis: 'materialClasses', structure: 'full', behavior: 'partial', surface: 'full' },\n { std: 'MaterialDefinition', part: '2', label: 'twin.isa95.MaterialDefinition', axis: 'materialDefinitions', structure: 'full', behavior: 'full', surface: 'full' },\n /*\n * 로트·서브로트는 개체 축이 아니라 **관측**으로 존재한다(EPCIS 개체) — 구조는 저널이 갖는다.\n *\n * ── 2026-08-24 로트가 시험 결과를 들게 됐다 (커널 0.7.57) ─────────────────\n * `ItemState.testResults` 로 「이 로트를 쓸 수 있나」의 **근거**가 로트에 실린다. 그전에는 판정\n * (`disposition`)만 있었고 「왜 그렇게 판정했나」를 되짚을 수 없었다.\n *\n * **구조를 `full` 로 올리지 않는다.** `MaterialLotType` 원문(B2MML-Material.xsd)이 드는 것 중\n * 아직 없는 것이 있다: `MaterialLotProperty`(시험이 아닌 로트 속성 — 원산지·포장 형태) ·\n * `TestSpecificationID`(로트별 기준 참조 — 품목 정의가 대신 답하므로 두지 않았다) · `Status` ·\n * `HierarchyScope` · `Version`. 채운 것만 세는 것이 이 표의 규율이다.\n */\n { std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: null, structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },\n /*\n * ── 2026-08-24 판정 정정: `none` 이 아니었다 ──────────────────────────────\n * 이 줄은 셋 다 `none` 이었는데 **표가 코드보다 뒤처져 있었다.** 커널은 서브로트를 든다:\n *\n * `ItemState.subLotId` 표준 `MaterialSubLot.ID` (계약이 그렇게 적고 있다)\n * `subLotIdOf(class, location)` 비직렬 로트가 자리마다 나뉠 때 그 부분의 이름을 만든다\n * `itemKeyOf(item)` **개체의 정체성이 이 값이다** — `subLotId ?? epc`\n *\n * 기능이 `full` 인 근거: 이 축이 없으면 같은 로트를 rack-1 에 100개·rack-2 에 60개 관측했을 때\n * **뒤에 온 관측이 앞을 덮어 100개가 조용히 사라진다**(합계 160 → 60). 실제로 그 결함을 이 축으로\n * 고쳤다. 즉 장식이 아니라 정체성을 지탱한다.\n *\n * 구조가 `partial` 인 이유: 서브로트는 로트와 같은 모양이고(위 참조) 그 로트가 `partial` 이다.\n * 화면이 `partial` 인 이유: 값이 물품의 **이름으로** 나온다(`twin-item` 의 line) — 「이 로트가 자리마다\n * 나뉘어 있다」를 말하는 자리는 아직 없다.\n *\n * 이 부류(코드에 있는데 표가 `none`)는 자동으로 잡히지 않는다 — 표의 가드는 반대 방향만 본다\n * (축이 사라지면 구조를 내린다). 그래서 축을 늘릴 때 이 표를 함께 보는 것이 규율이다.\n */\n { std: 'MaterialSublot', part: '2', label: 'twin.isa95.MaterialSublot', axis: null, shownOn: ['items'], structure: 'partial', behavior: 'full', surface: 'partial' },\n { std: 'ProcessSegment', part: '2', label: 'twin.isa95.ProcessSegment', axis: 'operations', structure: 'full', behavior: 'full', surface: 'full' },\n /*\n * 속성은 자원마다 붙는 확장이라 **자기 축이 아니다** — 그래서 `axis` 는 없지만 구조는 완전하다:\n * 계약(`ResourceProperty`: 값·데이터형·단위·하위·등급속성)이 표준을 그대로 담고, 인제스트·상태·\n * 화면이 그것을 통과시킨다.\n *\n * 기능은 **부분**이다: 호스트의 주행 추정기가 `speed` 를 읽어 이동 시간을 만드는 것이 유일한 소비처다\n * (다른 속성은 아직 아무도 읽지 않는다). 그 사실을 `full` 로 올리면 표가 거짓이 된다.\n */\n { std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },\n /*\n * **자리의 상시 관측** — 2026-08-24 신설(커널 0.7.56). 표에 이 개념이 아예 없었다.\n *\n * 왜 이 축이 필요한가: 트윈의 판정 대상은 물건의 상태이고, 물건의 상태는 **조건 없이 정해지지\n * 않는다.** 「이 로트가 냉장실에 있었다」까지만 아는 트윈은 그 로트가 괜찮았는지 말할 수 없다. 그리고\n * **이 조인은 트윈만 할 수 있다** — 계측 시스템은 물건의 자리 이력을 모르고, 물류 시스템은 조건\n * 이력을 모른다.\n *\n * 표준 앵커: `OperationsEventType`(B2MML-OperationsEvent.xsd, ISA-95.00.02-2018) +\n * `OperationsRecordTemplateType`. 우리가 든 것 — `EffectiveTimestamp` · `EffectiveEndDate` ·\n * `RecordTimestamp` · `HierarchyScope`(자리) · `Source` · 값은 `ValueType` 으로 **좁혔다**\n * (단위 없는 물리량은 판정의 재료가 못 된다).\n *\n * 붙는 자리가 `HierarchyScope.EquipmentID` 인 것이 중요하다 — ISA-95 에서 장소 계층이 곧 설비\n * 계층이고 냉장실은 `StorageZone` 수준의 설비다. `OperationalLocationType` 에는 이 축이 없다.\n *\n * 구조가 `partial`: 봉투 전체(`ID`·`Description`·사건 분류·`OperationsRecord` 구조)가 아니라 관측\n * 하나를 담는 데 필요한 칸만 든다.\n * 기능이 `full`: 사건 채널(`location.measured`)이 상태를 만들고, 선언된 기준으로 판정해\n * `observation-out-of-limit` 신호를 세운다.\n * 화면이 `none`: 클라이언트에 이 축을 그리는 곳이 없다(실측 0곳).\n */\n { std: 'OperationsEvent', part: '2', label: 'twin.isa95.OperationsEvent', axis: null, structure: 'partial', behavior: 'full', surface: 'none' }\n]\n\n/*\n * Part 4 — 일정과 실적.\n *\n * **2026-08-11 이전에는 이 부분이 거의 비어 있었다.** 오더·작업은 런타임과 저널에 실재하는데 조회\n * 모델의 축이 아니었고, 그래서 \"이 트윈이 표준의 어디를 채우나\" 라는 질문에 절반만 답했다.\n *\n * 커널이 계약을 넓혀(`source: 'state'`) 두 축이 추가되었다 — 이제 요구(`orders`)와 응답(`tasks`)을 개념으로\n * 가리켜 걸어 들어갈 수 있다. 남은 것은 **과거로 다시 계산하는 길**(저널 커서 페이징)과 능력·검사 명세다.\n */\nconst PART4: Isa95Concept[] = [\n { std: 'OperationsDefinition', part: '4', label: 'twin.isa95.OperationsDefinition', axis: 'productionSpec', structure: 'full', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.opsdef' },\n /*\n * 2026-08-11 — **축이 추가되었다**(커널 0.6.3 · `source: 'state'`). 요구는 `orders`, 응답은 `tasks` 다.\n *\n * `structure` 를 `full` 로 올린다: 담을 자리가 계약에 있다(상태 계약 + 축 선언). **테이블 투영이\n * 없는 것은 결손이 아니다** — 이 축의 정본은 저널이고, 투영하지 않는 것이 결정이다(ADR-0032-D).\n *\n * `surface` 도 `full` 이다(같은 날 두 번째 단계): 목록·상세·관계에 더해 **과거로 다시 계산하는 길**이 화면에\n * 있다 — 시각 커서를 옮기면 그 시점의 사실이 나오고, 무엇을 근거로 답했는지도 화면이 말한다\n * (라이브=권위 · 저널=파생 · 접은 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.\n */\n { std: 'OperationsSchedule', part: '4', label: 'twin.isa95.OperationsSchedule', axis: 'orders', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.schedule' },\n { std: 'OperationsPerformance', part: '4', label: 'twin.isa95.OperationsPerformance', axis: 'tasks', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.performance' },\n { std: 'OperationsCapability', part: '4', label: 'twin.isa95.OperationsCapability', axis: null, shownOn: ['operations'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.opscapability' },\n /*\n * 2026-08-12 — **축이 추가되었다**(커널 0.6.5). 자원 아홉이 가리키던 참조에 대상이 생겼고, 없는 명세를\n * 가리키면 선 채움이 끊어진 참조로 센다.\n *\n * 기능이 **`partial` 로 살아났다**(커널 0.6.6): 결과(`TestResult`)가 계약에 들어오면서 커널이 배정에서\n * 그것을 본다 — 만료·불합격이면 그 자격은 성립하지 않는다(실측: 만료된 용접사는 용접 작업 5건 중 한\n * 번도 배정되지 않았다).\n *\n * ── 2026-08-24 위 문장 하나가 낡았다 ─────────────────────────────────────\n * 「**사람만 판정한다**」고 적혀 있었다. 지금은 아니다 — `capabilityOfResource` 가 설비·사람·자산\n * **셋 모두**에 `requiredTests` 를 걸고, 그 답이 배정의 가용성 필터로 들어간다. 그리고 판정 대상이\n * 자원 밖으로 넓어졌다: **자리의 상시 관측**(`OperationsEvent` 줄)과 **로트의 시험 결과**\n * (`TestResult` 줄)도 같은 기준으로 판정한다.\n *\n * 남은 규율은 하나다: **결과가 없으면 판정하지 않는다**(선언한 것만 제약이 된다 — 결손이 아니라 규율).\n *\n * 그리고 기준의 **숫자 한계는 우리가 더한 것**이다. 표준 `TestSpecificationCriteria.Expression` 은\n * 자유 문장(`TextType`)이고, B2MML 일곱 파일에 `Minimum`·`Maximum`·`Tolerance` 가 하나도 없다\n * (원문 대조). 그것 없이는 판정할 수 없으므로 `limit: {minimum, maximum, uom}` 을 더했고, 발명한\n * 자리임을 계약에 적었다. 커널은 `Expression` 을 **읽지 않는다** — 문법이 정의되지 않았으므로\n * 파싱하면 그 순간 방언이다.\n */\n { std: 'TestSpecification', part: '4', label: 'twin.isa95.TestSpecification', axis: 'testSpecifications', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.test' },\n /*\n * **시험 결과** — 2026-08-24 신설(커널 0.7.57). 기준만 있고 결과의 자리가 표에 없었다.\n *\n * 표준 `TestResultType`(B2MML-OperationsTest.xsd)에서 우리가 든 것:\n * `EvaluationDate`(at) · `Expiration`(expiresAt) · `TestableObjectID` · `PropertyMeasurement`\n * · `EvaluatedCriterionResult` → **`result: 'pass'|'fail'` 로 좁혔다**(표준은 열거하지 않는다)\n * 아직 없는 것: `ID` · `Description` · `HierarchyScope` · `OperationsTestRequirementID` ·\n * `TestResultChild`. 그래서 구조는 `partial` 이다.\n *\n * 기능이 `partial` 인 이유를 정확히 적는다 — **자원에서는 결정을 바꾸고 로트에서는 아직 바꾸지\n * 않는다.** 만료·불합격 자원에는 작업이 배정되지 않지만, 불합격 로트가 할당에서 빠지거나 주의 신호를\n * 세우지는 않는다(들고·판정하고·되짚을 수는 있다). 그 자리를 `full` 로 적으면 표가 거짓이 된다.\n *\n * 화면은 `none` 이다 — 클라이언트에 이 축을 그리는 곳이 없다(실측: `operato-twin/client` 에 0곳).\n */\n { std: 'TestResult', part: '4', label: 'twin.isa95.TestResult', axis: null, structure: 'partial', behavior: 'partial', surface: 'none' },\n { std: 'WorkMaster', part: '4', label: 'twin.isa95.WorkMaster', axis: 'recipes', structure: 'full', behavior: 'full', surface: 'full' },\n { std: 'WorkDirective', part: '4', label: 'twin.isa95.WorkDirective', axis: null, structure: 'none', behavior: 'none', surface: 'none' }\n]\n\n/** 표준이 요구하는 것 전부 — 이 배열이 곧 기준이다. */\nexport const ISA95_CONCEPTS: Isa95Concept[] = [...PART2, ...PART4]\n\n/**\n * 적합성 보고 — **채운 것과 빠진 것을 함께.**\n *\n * `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로\n * **구조 없음으로 낮추고 그 사실을 말한다**(조용히 통과시키면 이 표가 거짓이 된다).\n */\n/**\n * 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 조용히 지우지 않는다.**\n *\n * 두 자리가 어긋날 수 있다.\n * · `axis` — 그 개념 자신의 문. 커널에 없으면 구조를 「없음」으로 낮추고 그 사실을 말한다.\n * · `shownOn` — 그 사실을 들고 있는 다른 축. 예전에는 없는 축을 **말없이 걸러 냈다.** 그러면 그 줄은\n * 「개념 축이 없습니다」로 보이고, 사람은 그것을 **원래 그런 것**으로 읽는다. 실제로 그 일이 있었다:\n * EPCIS 표의 일곱 줄이 커널에 없는 축(`items`)에 기대고 있었는데, 경고가 뜬 것은 두 줄뿐이었다.\n *\n * 둘 중 하나라도 어긋나면 `axisMissing` 이다 — 표가 스스로 「최신이 아니다」라고 말하게 한다.\n */\nexport function reconcileAxes(c: Isa95Concept, known: Set<string>): Isa95Concept & { axisMissing?: boolean } {\n const ownMissing = !!c.axis && !known.has(c.axis)\n const shownOn = c.shownOn?.filter(a => known.has(a))\n const lostShown = !!c.shownOn && (shownOn?.length ?? 0) !== c.shownOn.length\n const fixed = lostShown ? { ...c, shownOn } : c\n if (!ownMissing && !lostShown) return fixed\n /* 구조를 낮추는 것은 **자기 문이 없어진 경우**다 — 남의 축에서 보이던 것은 구조가 아니라 표시의 문제다. */\n return ownMissing ? { ...fixed, structure: 'none' as CoverageLevel, axisMissing: true } : { ...fixed, axisMissing: true }\n}\n\nexport function isa95Coverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = ISA95_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n /* 채운 수는 `full` 만 센다 — 부분을 채운 것으로 세면 기준이 스스로를 속인다. */\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n model: 'ISA-95',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
@@ -177,9 +177,27 @@ let TwinModelQuery = class TwinModelQuery {
177
177
  const inst = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } });
178
178
  if (!inst)
179
179
  return null;
180
- /* 어느 원본을 반영하나 — 없을 수 있다(템플릿으로 세운 트윈). 그 사실도 사실이다. */
180
+ /*
181
+ * 어느 원본을 반영하나 — 없을 수 있다(템플릿으로 세운 트윈). 그 사실도 사실이다.
182
+ *
183
+ * ── **레퍼런스 열쇠는 인스턴스 이름이 아니다** (2026-08-24 실측) ──────────────
184
+ * 여기서 `source: instanceId` 로 찾고 있었다. 레퍼런스 하나가 인스턴스 하나를 낳던 시절에는
185
+ * 두 값이 같았지만, **레퍼런스 하나가 여러 트윈을 먹이는 순간 어긋난다.**
186
+ *
187
+ * 인스턴스 operato-chef-v1-pocheon
188
+ * origin { kind: 'reference', source: 'seunghwa' } ← 이것이 레퍼런스 열쇠다
189
+ * 레퍼런스 seunghwa
190
+ *
191
+ * 그래서 요약의 `source` 가 **언제나 `null`** 이었다. 레퍼런스로 세운 트윈인데 화면이 그 원본을
192
+ * 못 찾았고, 「마지막 동기화」도 흐름별 신선도도 그릴 자리가 아예 없었다 — 그런데 오류는 없었다
193
+ * (`ref ? … : null` 이 조용히 갈렸다).
194
+ *
195
+ * 인스턴스가 자기 원본을 말하고 있으므로 **그 말을 먼저 듣는다.** 옛 트윈은 두 값이 같으므로
196
+ * 뒤의 것으로 여전히 찾힌다 — 가산적이다.
197
+ */
198
+ const refKey = inst.origin?.source || instanceId;
181
199
  const ref = await (0, shell_1.getRepository)(twin_reference_js_1.TwinReference)
182
- .findOne({ where: { domain: { id: domainId }, source: instanceId } })
200
+ .findOne({ where: { domain: { id: domainId }, source: refKey } })
183
201
  .catch(() => null);
184
202
  const model = inst.model ?? {};
185
203
  /*
@@ -511,11 +529,61 @@ let TwinModelQuery = class TwinModelQuery {
511
529
  recipeLabel: material?.label ?? material?.name ?? null
512
530
  };
513
531
  })(),
532
+ /*
533
+ * ── **식별자가 무엇에 근거하나** (2026-08-24) ──────────────────────────────
534
+ * 요약이 `production.companyPrefix` 를 그대로 보이는데 **그것이 발급받은 번호인지 말할 근거가
535
+ * 없었다.** 그래서 사용자가 긴 URI 를 보고 「GS1 을 강제하는 것인가」를 물었다 — 강제는 **이미
536
+ * 없고**(GS1 프리픽스가 없는 현장은 자기 도메인 아래에서 식별자를 만든다, CBV 2.0 §8.3.4)
537
+ * 없다는 사실이 화면에 없었던 것이 문제였다.
538
+ *
539
+ * ★ **이 축만은 무표를 쓰지 않는다.** GS1 형태의 식별자를 본 사람은 그것이 진짜 발급받은
540
+ * 번호라고 가정하므로, 표식이 없으면 그 가정이 곧 거짓 안심이 된다. 계약도 그렇게 적고 있다 —
541
+ * 「없으면 소비처는 판정하지 않는다. 기본값으로 `issued` 를 가정하면 그 화면이 곧 거짓이 된다.」
542
+ * 그래서 화면이 네 갈래 모두에 낱말을 붙일 수 있게 **그대로** 낸다
543
+ * (`claimed-issued`·`declared-namespace`·`gs1-shaped`·`kernel-constant`).
544
+ *
545
+ * 커널이 내는 것을 그대로 넘긴다 — 여기서 다시 판정하면 스냅샷과 요약이 다른 말을 한다.
546
+ * 트윈이 돌지 않거나 커널이 말하지 않으면 `null` 이고, 그때 화면은 **재지 않는다.**
547
+ */
548
+ identityGrounding: (await stateOnce()).snap?.identityGrounding ?? null,
514
549
  description: inst.description ?? null,
515
550
  kind: inst.kind,
516
551
  restartPolicy: inst.restartPolicy,
517
552
  source: ref
518
- ? { referenceId: ref.source, adapterType: ref.adapterType, status: ref.status, lastSyncedAt: ref.lastSyncedAt }
553
+ ? {
554
+ referenceId: ref.source,
555
+ adapterType: ref.adapterType,
556
+ status: ref.status,
557
+ lastSyncedAt: ref.lastSyncedAt,
558
+ /*
559
+ * ── **원본의 어느 흐름이 어디까지 살아 있나** (2026-08-24) ──────────────
560
+ * 사용자가 「지금 데이터가 들어오는 것이 사실이냐」고 물었고, 화면이 답할 것이 「붙어 있다」
561
+ * 뿐이었다. 그런데 한 원본 안에서 흐름마다 신선도가 **크게 다르다**(실측: 재고 08-23 ·
562
+ * 계측 08-12 · 작업지시 05-10 · 공정·로트 04-15). 「붙어 있다」 하나로 뭉치면 넉 달 멈춘
563
+ * 흐름이 살아 있는 것으로 읽힌다.
564
+ *
565
+ * **계약에는 자리가 있다** — `LiveFeedCursor.since` 가 「그 흐름에서 마지막으로 본 시각
566
+ * (원본의 시각)」이고 `onCursor` 가 그것을 `TwinReference.liveCursor` 에 적는다. 없던 것은
567
+ * **화면으로 가는 문**이고, 그 문이 여기다.
568
+ *
569
+ * ★ **다만 지금은 비어 있다** — 실 저장값을 확인했다:
570
+ * `{"streams":{},"firstAttachedAt":"2026-03-25T…"}`. 즉 어댑터가 흐름별 커서를 적지 않는다.
571
+ * 그래서 이 문을 열어도 당장은 빈 목록이 나오고, **그것이 정직한 답이다**: 어댑터가 말하지
572
+ * 않는 것을 여기서 지어내지 않는다. 채우는 것은 어댑터의 몫이고 그쪽에 알렸다.
573
+ *
574
+ * 빈 목록과 「흐름이 없다」를 화면이 구별할 수 있어야 한다 — 이 값이 비면 그것은
575
+ * 「어댑터가 아직 말하지 않는다」이고 「원본에 흐름이 없다」가 아니다.
576
+ *
577
+ * `seen`(중복 걸러낸 행 id)은 내지 않는다 — 읽기 내부 상태이고 수천 개가 될 수 있다.
578
+ * 흐름 열쇠는 **어댑터가 정한다**(계약이 이름을 닫지 않는다). 그래서 이름을 번역하지 않고
579
+ * 그대로 낸다 — 화면이 모르는 이름을 만나면 그 이름을 보이는 것이 맞다.
580
+ */
581
+ streams: Object.entries(ref.liveCursor?.streams ?? {})
582
+ .map(([stream, c]) => ({ stream, since: c?.since ?? null }))
583
+ /* 시각을 말하지 않는 흐름도 낸다 — 「모른다」와 「없다」는 다르고, 빼면 그 흐름이 사라진다. */
584
+ .sort((a, b) => String(b.since ?? '').localeCompare(String(a.since ?? ''))),
585
+ firstAttachedAt: ref.liveCursor?.firstAttachedAt ?? null
586
+ }
519
587
  : null,
520
588
  /*
521
589
  * **다시 읽을 수 있는가** — 이 트윈이 자기 원본을 기억하고 있나(`TwinInstance.origin`).
@@ -1 +1 @@
1
- {"version":3,"file":"twin-model-query.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-model-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { axisSeenInJournal } from './axis-journal-evidence.js'\nimport { statusTally } from './status-tally.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { Arg, Ctx, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { modelGapOf } from '../../engine/model-gap.js'\nimport { readBoardEquipment } from '@operato/twin-kernel'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinSpace } from '../twin-space/twin-space.js'\nimport { TwinReference } from '../reference/twin-reference.js'\nimport { declarablePropertySpecs } from '../../engine/local-declarations.js'\nimport { operationBasis } from '../../engine/operation-basis.js'\nimport { TwinLocation } from './twin-location.js'\nimport { TwinEquipment } from './twin-equipment.js'\nimport { TwinOperation } from './twin-operation.js'\nimport { standardsForKind, type StandardId } from './standard-coverage.js'\nimport { isa95Coverage } from './isa95-coverage.js'\nimport { iec61850Coverage } from './iec61850-coverage.js'\nimport { epcisCoverage } from './epcis-coverage.js'\n\n/**\n * 이 공정에 선언된 시간을 **겹에서** 든다 — 좁은 겹이 이긴다(트윈 먼저, 없으면 현장).\n *\n * 모델에는 ISO 8601 로 구워지지만 화면에는 **사용자가 넣은 수와 단위**를 보인다: 「PT240S」를 보이면\n * 자기가 넣은 값인지 알아볼 수 없다. 선언이 없으면 빈 배열이다(빈 값을 지어내지 않는다).\n */\nfunction declaredDurationProperty(kind: string, instanceLayer?: any[], spaceLayer?: any[]): any[] {\n const find = (layer?: any[]) => {\n const d = (layer ?? []).find(x => x?.target === 'operation' && String(x?.id ?? '') === kind)\n return (d?.properties ?? []).find((p: any) => p?.id === 'operation.duration')\n }\n const p = find(instanceLayer) ?? find(spaceLayer)\n if (!p) return []\n return [{ id: 'operation.duration', value: String(p.value ?? ''), ...(p.uom ? { uom: String(p.uom) } : {}), source: 'local' }]\n}\n\n/** 표준 id → 그 표의 함수. 정책은 `standardsForKind` 가, 표는 각 파일이 가진다. */\nconst COVERAGE: Record<StandardId, (axes: string[]) => any> = { isa95: isa95Coverage, epcis: epcisCoverage, iec61850: iec61850Coverage }\nimport { unwrapState } from '../../engine/warm-start.js'\n/* 공유 모듈은 빌드 산출물로 참조한다 — 서버·클라이언트가 같은 규칙을 쓰는 자리다(같은 관행: entity-delta). */\nimport { axisValueOf } from '@things-factory/headless-twin/dist-shared/axis-read.js'\n\n/* 커널 런타임 로드 — CJS 번들(엔진과 같은 방식). 축·관계 선언의 SSOT 다. */\nconst { TWIN_AXES, TWIN_RELATIONS, axisInfo, axisSource, axisAppliesTo } = require('@operato/twin-kernel')\n\n/**\n * 축 → 그 축이 이미 **행으로 옮겨졌나**. 옮겨진 축은 DB 가 세고, 아직인 축은 문서를 세어야 한다.\n *\n * 이 표가 곧 `ADR-0032` 이주의 진척이다 — 여기 없는 축은 아직 `TwinModelDef` 문서 안에 산다.\n */\nconst ROW_BACKED: Record<string, any> = {\n locations: TwinLocation,\n equipment: TwinEquipment,\n operations: TwinOperation\n}\n\n/** `productionSpec.definition.recipes` 같은 경로를 문서에서 따라간다. */\nconst at = (o: any, path: string) => path.split('.').reduce((a: any, k: string) => (a == null ? undefined : a[k]), o)\n\nfunction countInDocument(model: any, path: string): number {\n const v = at(model, path)\n if (Array.isArray(v)) return v.length\n return v ? 1 : 0\n}\n\n/*\n * 트윈 모델 조회 — **이 트윈은 표준 모델의 어디를 채우고 있나**(`plans/twin-inspection.md` §2 ②).\n *\n * ── 답의 크기가 개념 수에 비례한다 ─────────────────────────────────────────\n * 엔티티를 **절대 싣지 않는다.** 실으면 계약이 곧 무너지고, 규모가 오는 날 이 질의가 그대로\n * 병목이 된다. 항목은 별도 질의(`twinModelEntities`)가 페이징으로 낸다.\n *\n * ── 개념 목록을 이 코드가 갖지 않는다 ───────────────────────────────────────\n * 축·관계는 **커널 카탈로그가 선언**한다(`TWIN_AXES`·`TWIN_RELATIONS`). 그래서 야드의 `dock-door`\n * 나 에너지의 계량기가 늘어도 여기 손댈 것이 없다 — 도메인마다 화면·질의가 하나씩 늘어나던\n * 구조를 끊는 것이 이 설계의 요점이다(`ADR-0031`).\n *\n * ── 어떻게 셌는지 밝힌다 ───────────────────────────────────────────────────\n * 지금은 축 셋만 행이고 나머지는 문서 안에 있다. 그 사실을 `basis` 로 **축마다** 말한다.\n * 밝히지 않으면 \"이 트윈은 3개 축뿐\" 으로 읽히고, 이주가 어디까지 왔는지도 아무도 모른다.\n */\n/** `inputs[].material` 처럼 배열을 지나는 선언 경로를 따라간다 — 배열 표기가 관계 선언의 문법이다. */\nfunction valuesAt(obj: any, field: string): any[] {\n let cur: any[] = [obj]\n for (const seg of field.split('.')) {\n const key = seg.endsWith('[]') ? seg.slice(0, -2) : seg\n const next: any[] = []\n for (const c of cur) {\n if (c == null) continue\n const v = c[key]\n if (v == null) continue\n if (Array.isArray(v)) next.push(...v)\n else next.push(v)\n }\n cur = next\n }\n return cur.filter(v => v != null)\n}\n\nconst idOf = (o: any): string | null => (typeof o === 'string' ? o : o?.id ?? o?.key ?? o?.gtin ?? null)\n\n/** 상태가 빈 축의 이유 — 저널에 그 사실이 있으면 그렇게 말한다. 판단할 수 없으면 예전 그대로. */\nasync function emptyStateReason(domainId: string, instanceId: string, axis: string): Promise<string> {\n try {\n const seen = await axisSeenInJournal(getRepository(TwinEvent), { domain: { id: domainId }, instanceId }, axis)\n /* 판단할 수 없는 축(`null`)은 예전 답을 그대로 쓴다 — 모르는 것을 새 사실로 만들지 않는다. */\n return seen ? 'observed-not-in-state' : 'none-yet'\n } catch {\n /* 저널을 못 읽었다고 카드가 없는 말을 하지는 않는다. */\n return 'none-yet'\n }\n}\n\n/**\n * 되세운 상태를 담아 둔다 — **열쇠에 저널 끝 리비전이 들어 있어** 새 사건이 오면 저절로 낡는다.\n *\n * 접기는 저널 전체를 훑는 일이라(리비전 12만에서 2~3초) 모델 화면을 열 때마다 그 값을 낼 수 없다.\n * 프로세스 안에만 두고 몇 개만 남긴다 — 다시 뜨면 다시 접는다(정확함이 캐시보다 앞선다).\n */\nconst RECALL_CACHE = new Map<string, { snap: any; heardAt: string | null }>()\nconst RECALL_CACHE_MAX = 8\n\n@Resolver()\nexport class TwinModelQuery {\n /**\n * 이 선이 **얼마나 이어져 있나** — 그리고 **얼마나 끊겨 있나.**\n *\n * 선이 \"선언되었고 양끝이 존재한다\" 만 말하면 지도는 그림일 뿐이다. 관계에서 가장 중요한 정보는\n * 대개 **부재**다 — 기준 자리가 비어 있는 설비 열두 대, 어느 라우트에도 안 실린 공정. 그것이\n * 조감도에서 보여야 사람이 끊긴 데를 찾아다니지 않는다.\n *\n * 셋을 센다:\n * · `linked` — 값이 있고 그 값이 상대편에 **실제로 있다**\n * · `dangling` — 값은 있는데 **가리키는 것이 없다**(끊어진 참조 — 가장 나쁜 부류)\n * · `missing` — 값 자체가 없다(선택 관계면 정상, 필수 관계면 결손)\n *\n * 문서 어휘로 푼다 — 관계 선언의 필드 이름이 원본 문서의 어휘이기 때문이다(행은 자기 컬럼\n * 이름을 쓴다). 그 사실을 `fillBasis` 로 함께 낸다.\n */\n private edgeFill(model: any, r: any): any {\n const fromInfo = axisInfo(r.from)\n const toInfo = axisInfo(r.target.axis)\n if (!fromInfo || !toInfo) return {}\n\n /*\n * **문서로 셀 수 없는 관계는 세지 않는다.** 선 채움(이어짐·끊어짐·빈 것)은 두 축을 문서에서\n * 살펴 센다. 그런데 일정·실적은 문서에 없다(관측 축) — 그 관계를 문서로 세면 언제나 0 이 나오고,\n * 화면은 그것을 \"전부 끊어졌다\" 로 읽는다. 셀 수 없으면 **수를 내지 않는 편이 정직하다.**\n */\n if (axisSource(r.from) !== 'document' || axisSource(r.target.axis) !== 'document') return {}\n\n const listOf = (info: any) => {\n const raw = at(model, info.path)\n return Array.isArray(raw) ? raw : raw ? [raw] : []\n }\n const targets = new Set(listOf(toInfo).map(idOf).filter(Boolean) as string[])\n let linked = 0\n let dangling = 0\n let missing = 0\n for (const item of listOf(fromInfo)) {\n const vals = valuesAt(item, r.field).map(v => (typeof v === 'object' ? idOf(v) : v)).filter(v => v != null).map(String)\n if (!vals.length) { missing++; continue }\n /* 한 항목이 여럿을 가리킬 수 있다(`steps[]`). 하나라도 끊겼으면 끊긴 쪽으로 센다 —\n \"대체로 이어져 있다\" 는 진단이 아니다. */\n if (vals.every(v => targets.has(v))) linked++\n else dangling++\n }\n return { linked, dangling, missing, fillBasis: 'document' }\n }\n\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'What this twin reflects and where it fills the standard model: per-concept counts, how each was counted, and the declared relations between concepts. Never carries entities — use twinModelEntities for items.'\n })\n async twinModelSummary(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n\n /* 어느 원본을 반영하나 — 없을 수 있다(템플릿으로 세운 트윈). 그 사실도 사실이다. */\n const ref = await getRepository(TwinReference)\n .findOne({ where: { domain: { id: domainId }, source: instanceId } })\n .catch(() => null)\n\n const model = inst.model ?? {}\n /*\n * **한 요청에 저널은 한 번만 접는다.**\n *\n * 관측 축(`orders`·`tasks`)은 상태에서 센다. 그 둘이 각자 `recover()` 를 부르고 있었고, 커널이\n * 멈춰 있으면 그것은 **저널 전체를 다시 접는 일**이다(23,731건짜리 트윈에서 4.6초, 두 번이면 곱절).\n * 트윈을 전부 멈춰도 이 화면이 5~7초 걸린 이유가 이것이다 — 부하가 아니라 같은 일을 두 번 한 것이다.\n *\n * 한 번만 접어 나눠 쓴다. 값이 같아지는 것은 덤이 아니라 **옳음**이다: 두 번 접으면 그 사이에\n * 들어온 이벤트 때문에 오더와 작업이 서로 다른 시점의 사실이 될 수 있다.\n */\n let statePromise: Promise<{ snap: any; folded: boolean; observing: boolean }> | null = null\n const stateOnce = () =>\n (statePromise ??= (async () => {\n const observing = TwinEngine.isRunning(domainId, instanceId)\n let snap = observing ? unwrapState(TwinEngine.snapshot(domainId, instanceId)) : null\n let folded = false\n if (!snap) {\n try {\n snap = unwrapState(await TwinEngine.recover(domainId, instanceId))\n folded = !!snap\n } catch {\n /* 다시 계산할 것이 없다 — 0 으로 메우지 않는다(카드는 \"모름\" 을 낸다). */\n }\n }\n return { snap, folded, observing }\n })())\n\n /*\n * ── **라이브가 잊은 것을 저널이 안다** (2026-08-23) ─────────────────────────\n *\n * 미러의 관측은 메모리에만 산다. 재기동하면 빈 채로 시작하고, 원본이 다시 말해 줄 때 채워진다 —\n * 그런데 읽기 커서가 정직해진 뒤로는 원본이 **지난 것을 다시 말하지 않는다.** 그래서 재고 741건이\n * 저널에 있는데 화면은 「0」을 보였다. 사용자가 「재고현황이 없다」고 물은 자리가 이것이다.\n *\n * 그 0 은 사실이 아니라 **우리가 잊은 것**이다. 저널을 접으면 그대로 되살아난다(실측: 리비전\n * 97,689 을 2.4초에 접어 물품 741 · 작업 9,145 · 지시 2,856).\n *\n * ── 그래서 접는다. 다만 **언제 들은 값인지 함께 낸다** ──────────────────────\n * 저널을 접는 것은 **원천이 우리에게 말한 것을 다시 세우는 일**이지, 없는 값을 심는 일이 아니다.\n * 그러나 물품·재고처럼 **절대값인 축**은 우리가 못 듣는 사이 떠났을 수 있다 — 그래서 값과 함께\n * `heardAt`(그 접기의 시계)을 낸다. 사용자가 신선도를 스스로 판정할 수 있어야 하고, 화면은 그것을\n * 값 옆에 적는다. 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.\n *\n * 접는 조건을 좁게 둔다: **관측 축이 비어 있고, 저널에 그 축의 사실이 있을 때만**(그 판정은 색인\n * 한 줄이다). 그리고 한 요청에 **한 번만** 접는다 — 위 `stateOnce` 와 같은 규율이다.\n */\n let journalPromise: Promise<{ snap: any; heardAt: string | null } | null> | null = null\n const journalOnce = () =>\n (journalPromise ??= (async () => {\n try {\n const tip = await getRepository(TwinEvent).findOne({\n where: { domain: { id: domainId } as any, instanceId } as any,\n order: { revision: 'DESC' } as any\n })\n if (!tip?.revision) return null\n /*\n * **한 번 접은 것을 다시 접지 않는다.** 접기는 저널 전체를 훑는 일이다(리비전 12만에서 2~3초).\n * 이 화면은 사람이 여러 번 들어오는 자리이므로 그대로 두면 매번 그 값을 낸다 — 그리고 저널이\n * 수백만 행이 되면 그 비용이 화면을 못 쓰게 만든다.\n *\n * 열쇠에 **저널 끝의 리비전**을 넣는다: 새 사건이 오면 열쇠가 달라지므로 캐시가 저절로 낡는다\n * (캐시가 사실을 이기지 않는다). 프로세스 안에만 두고 몇 개만 남긴다.\n */\n const key = `${domainId}:${instanceId}:${tip.revision}`\n const hit = RECALL_CACHE.get(key)\n if (hit) return hit\n const snap = unwrapState(await TwinEngine.recover(domainId, instanceId, tip.revision))\n if (!snap) return null\n /* 그 접기의 시계가 「언제 들은 값인가」다. 없으면 저널 끝의 사건 시각으로 답한다. */\n const out = { snap, heardAt: (snap as any).nowTime ?? tip.eventTime ?? null }\n RECALL_CACHE.set(key, out)\n /* 오래된 열쇠부터 버린다 — 트윈 몇 개를 번갈아 봐도 메모리가 자라지 않게. */\n while (RECALL_CACHE.size > RECALL_CACHE_MAX) RECALL_CACHE.delete(RECALL_CACHE.keys().next().value as string)\n return out\n } catch {\n /* 접지 못했으면 아무 말도 만들지 않는다 — 라이브가 말한 것을 그대로 둔다. */\n return null\n }\n })())\n\n const concepts = await Promise.all(\n TWIN_AXES.map(async (a: any) => {\n /*\n * **이 종류에 없는 개념인가** — 「아직 없음」과 「해당 없음」은 다른 말이다.\n *\n * 에너지 트윈에는 옮겨 다니는 물건도, 배정할 사람도, 공정도 없다(커널이 축마다 선언한다).\n * 그것을 「로트 0」으로 그리면 사용자는 채우러 갈 곳을 찾고, 적합성 표는 채우지 못한 것으로\n * 센다 — 둘 다 거짓이다. 수는 `null`(모름이 아니라 셀 대상이 아님)이고 이유를 코드로 낸다.\n *\n * 세지도 않는다: 없는 개념을 행에서 세는 것은 질의만 늘리고 답을 바꾸지 않는다.\n */\n if (!axisAppliesTo(a.axis, inst.kind)) {\n return {\n axis: a.axis,\n label: a.label,\n kind: a.kind,\n container: false,\n standardClass: a.standardClass,\n typeRole: a.typeRole ?? null,\n count: null,\n notApplicable: true,\n emptyBecause: 'not-applicable',\n /*\n * **그 개념의 성질** — 선언인가 관측인가 (2026-08-22).\n *\n * `basis` 와 다르다: `basis` 는 「이 값이 어디서 왔나」이고 트윈마다·시점마다 달라진다(같은\n * 축이 한 트윈에서 `journal`, 다른 트윈에서 `state` 로 온다). 그것으로 묶으면 같은 축이 화면\n * 마다 다른 묶음에 들어간다. `source` 는 트윈과 무관하게 고정이므로 묶는 축이 된다.\n *\n * 화면이 축 이름을 열거하지 않아도 되고, 축이 늘면 자동으로 맞는 묶음에 들어간다 —\n * 카탈로그가 정본이다.\n */\n source: axisSource(a.axis),\n basis: 'declaration'\n }\n }\n const entity = ROW_BACKED[a.axis]\n /* 문서 축만 문서로 센다 — 경로 없는 축을 세면 모델 루트를 훑는다(상태 축은 아래에서 센다). */\n const inDocument = axisSource(a.axis) === 'document' ? countInDocument(model, a.path) : 0\n const inRows = entity\n ? await getRepository(entity).count({ where: { domain: { id: domainId } as any, instance: { id: inst.id } as any } })\n : undefined\n /*\n * **둘을 함께 세고 어긋나면 말한다.**\n *\n * 행으로 옮긴 축이라도 그 트윈이 투영을 거치지 않았으면 행은 0이다(인제스트 없이\n * warm-start 된 트윈이 그렇다). 행만 보고 답하면 문서에 자리 6개가 있는데 화면은\n * **0 이라고 말한다** — 조용한 거짓이고, 이 프로젝트가 내내 잡아 온 바로 그 부류다.\n *\n * 그래서 어느 하나로 메우지 않는다. 행 수를 답하되 문서 수를 함께 내고, 다르면\n * `stale` 로 표시한다 — 그 차이 자체가 \"이 트윈은 투영 이후 인제스트되지 않았다\" 는 정보다.\n */\n const stale = entity !== undefined && inRows !== inDocument\n /*\n * **그릇인가** — 문서에 객체 하나로 있는 축(`productionSpec`).\n *\n * `kind` 로는 갈리지 않는다: 커널은 공정·레시피·라우트도 `spec` 이라 부른다(같은 선언 부류라는\n * 뜻이다). 그릇인지 아닌지는 **모양**이 말한다 — 목록이 되는 축은 배열이고, 한 벌뿐인 선언은\n * 객체 하나다. 개념 지도는 개체를 세는 면이므로 그릇은 자기 면에서 통째로 읽는다.\n */\n const raw = axisSource(a.axis) === 'document' ? at(model, a.path) : undefined\n /*\n * **관측 축은 상태가 센다**(일정·실적 — ISA-95 Part 4). 커널이 멈춰 있으면 `null` 이다:\n * 0 으로 내면 \"작업이 없는 공장\" 으로 읽히고, 그건 거짓이다(관측이 없는 것과 0 건은 다르다).\n * 이 화면이 관측 열에서 내내 지켜 온 구분을 카드에서도 지킨다.\n */\n /*\n * **「아직 없다」와 「왔는데 상태에 없다」는 다른 사실이다** (2026-08-22).\n *\n * 화면이 「작업 — 아직 하나도 없습니다」라고 말했는데 저널에는 `task.status` 5,786건이 있었고\n * 마지막 상태가 `in-progress` 였다(원인은 커널의 내보내기였고 그 레인에 넘겼다). 카드가 단정한\n * 탓에 사용자가 그 차이를 직접 조사해야 했다 — 화면이 아는 것을 말하지 않은 자리다.\n *\n * 그래서 상태가 빈 축은 저널을 **한 건만** 물어본다(색인 한 줄, `journal-count` 의 문).\n */\n if (axisSource(a.axis) === 'state') {\n /* 스냅샷은 `{revision, state}` 봉투다 — 벗기지 않으면 축이 한 겹 안에 있어 언제나 `null` 이\n 된다(커널은 도는데 카드가 \"모름\" 으로 보였다). 벗기는 규칙의 집은 `warm-start` 하나다. */\n /*\n * 관측 중인지는 **런타임이 있는지**로 본다 — 캐시된 스냅샷은 관측이 아니다.\n *\n * 동작하지 않으면 저널을 접어 답한다(라이브=권위 · 히스토리=파생). 카드는 그 차이를 `basis` 로\n * 말한다: 파생을 권위처럼 내면 다시 계산한 수를 \"지금 이만큼\" 으로 읽게 된다.\n */\n const { snap, folded, observing } = await stateOnce()\n /* 축이 사는 자리는 **커널 선언이 말한다**(`path`) — 축 이름으로만 읽으면 중첩된 축이\n 언제나 「관측 없음」이 된다(에너지의 수요 구간이 그렇다). 규칙은 `axisValueOf` 한 곳. */\n let bucket = snap ? axisValueOf(snap, a) ?? null : null\n let heardAt: string | null = null\n let fromJournal = false\n /*\n * 라이브가 잊은 축은 **저널에서 되세운다**(§`journalOnce`). 그 축의 사실이 저널에 있을 때만\n * 접고, 값과 함께 「언제 들은 값인가」를 낸다 — 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.\n */\n if (!folded && Array.isArray(bucket) && bucket.length === 0) {\n const seen = await axisSeenInJournal(getRepository(TwinEvent), { domain: { id: domainId }, instanceId }, a.axis).catch(() => null)\n if (seen) {\n const j = await journalOnce()\n const jb = j?.snap ? axisValueOf(j.snap, a) ?? null : null\n if (Array.isArray(jb) && jb.length) {\n bucket = jb\n heardAt = j?.heardAt ?? null\n fromJournal = true\n }\n }\n }\n return {\n axis: a.axis,\n label: a.label,\n kind: a.kind,\n container: false,\n standardClass: a.standardClass,\n typeRole: a.typeRole ?? null,\n count: Array.isArray(bucket) ? bucket.length : null,\n /*\n * **수 하나가 뜻을 바꾸는 자리** (2026-08-23).\n *\n * 미러가 완료된 작업까지 상태에 들고 있게 되면서 「작업 2,820」이 되었다. 이 카드는 상태의\n * 인구조사이므로 그 수가 맞다 — 그런데 사람은 그것을 「지금 할 일 2,820건」으로 읽는다.\n * 수를 줄여 거짓으로 만드는 대신 **무엇으로 이루어진 수인지** 함께 낸다.\n *\n * 낱말은 커널의 것을 그대로 쓴다(`created`·`assigned`·`in-progress`·`completed`). 화면이\n * 라벨을 입히고, 없으면 낱말이 그대로 보여 누락이 드러난다.\n */\n ...statusTally(bucket),\n /*\n * **비었으면 왜 비었는지 말한다.** 0 과 `null` 만 내면 화면은 \"없다\" 까지만 말하고, 사용자는\n * \"안 적어 놨나 · 아직 안 일어났나 · 못 세었나\" 를 구별할 수 없다. 이 화면이 관측 열에서\n * 내내 지켜 온 구분(§observedAbsence)을 카드에서도 지킨다.\n */\n ...(Array.isArray(bucket)\n ? bucket.length\n ? {}\n : { emptyBecause: await emptyStateReason(domainId, instanceId, a.axis) }\n : { emptyBecause: 'not-observed' }),\n source: axisSource(a.axis),\n basis: folded || fromJournal ? 'journal' : 'state',\n /*\n * **언제 들은 값인가** — 저널에서 되세운 축만 갖는다. 라이브가 답한 축은 지금이므로 없다.\n * 이 값이 없으면 화면이 낡은 수를 지금으로 읽는다(그 구별이 이 카드의 전부다).\n */\n ...(fromJournal && heardAt ? { heardAt, recalled: true } : {}),\n observing,\n /* 선언이 아니라 관측인 축 — 화면이 \"원본이 침묵했다\" 로 읽지 않게 사실로 말한다. */\n observedOnly: true,\n standardUndeclared: !a.standardClass?.isa95 && !a.standardClass?.epcis && !a.standardClass?.iso55000\n }\n }\n return {\n axis: a.axis,\n label: a.label,\n kind: a.kind,\n container: !Array.isArray(raw) && raw != null,\n standardClass: a.standardClass,\n typeRole: a.typeRole ?? null,\n count: entity ? inRows : inDocument,\n /*\n * 비었으면 왜 비었는지 — 선언 쪽은 세 갈래다.\n * · `not-declared` 이 트윈의 모델이 그 축을 아예 말하지 않는다(대개 템플릿이 안 적었다)\n * · `declared-empty` 선언은 있는데 목록이 비어 있다(적었다가 지운 것도 사실이다)\n * · `not-projected` 문서엔 있는데 행에 없다 — 투영을 거치지 않은 트윈이다(`stale` 과 같은 사실)\n *\n * 셋을 \"0\" 하나로 합치면 사용자가 손볼 곳을 찾지 못한다: 첫째는 템플릿, 둘째는 원본,\n * 셋째는 다시 읽기다. 셋 다 다른 일이다.\n */\n ...((entity ? inRows : inDocument)\n ? {}\n : {\n emptyBecause:\n entity && inDocument > 0 ? 'not-projected' : raw == null ? 'not-declared' : 'declared-empty'\n }),\n /* 'rows' = DB 가 셌다(질의 가능) · 'document' = 문서를 세었다(아직 이주 전). */\n source: axisSource(a.axis),\n basis: entity ? 'rows' : 'document',\n ...(entity ? { documentCount: inDocument, stale } : {}),\n /* 표준 투영이 없는 축은 **숨기지 않고** 그 사실을 낸다(ADR-0031). */\n standardUndeclared: !a.standardClass?.isa95 && !a.standardClass?.epcis && !a.standardClass?.iso55000\n }\n })\n )\n\n /*\n * **원본이 모르는데 실행 중인 것** — 커널은 마스터에 없던 자원도 이벤트로 알게 되고\n * `origin: 'observed'` 를 단다(`observed-reducer`). 그것을 표에 밀어 넣지 않는다 — 그러면\n * 틱 루프에서 DB 로 쓰는 **두 번째 쓰기 경로**가 생기고, 실패하면 조용히 어긋난다\n * (쓰기 경로는 인제스트 하나, ADR-0032-D).\n *\n * 대신 **여기서 비교해 낸다.** 이 차이는 숨겨야 할 불일치가 아니라 인스펙션이 답해야 할\n * 질문이다 — \"원본이 말한 적 없는 설비가 실제로 실행 중이다.\"\n *\n * 트윈이 안 실행 중이면 비교할 상대가 없다 — 그때는 `null` 이다(0 이 아니다. 없는 것과\n * 세어 보지 못한 것은 다르다).\n */\n /*\n * 도출은 **한 벌**이다(`engine/model-gap.ts`). 예전에는 이 자리에 인라인으로 있었는데, 같은 사실을\n * 「현장과 맞춰지고 있나」 화면도 물어야 해서 두 곳이 다른 수를 말할 위험이 생겼다.\n *\n * 이 화면은 **전체 목록**이 필요하다(인스펙션이니까). 그래서 상한 없는 원본을 여기서 만들고,\n * 상한이 걸린 요약은 `modelGapOf` 가 답한다 — 같은 판정(`origin === 'observed'`)을 쓴다.\n */\n const snap = TwinEngine.snapshot(domainId, instanceId)\n const gap = modelGapOf(snap)\n const observedOnly = snap\n ? {\n equipment: (snap.equipment ?? []).filter((e: any) => e?.origin === 'observed').map((e: any) => e.id),\n locations: (snap.locations ?? []).filter((l: any) => l?.origin === 'observed').map((l: any) => l.id),\n persons: (snap.persons ?? []).filter((p: any) => p?.origin === 'observed').map((p: any) => p.id),\n /* 커널이 담을 줄 몰라 세어 둔 사건·모르는 설비 보고 — 인스펙션이 이미 물을 자리다. */\n unhandled: gap?.unhandled ?? [],\n unknownEquipmentReports: gap?.unknownEquipmentReports ?? null,\n notCovered: gap?.notCovered ?? []\n }\n : null\n\n /* 지도의 선 — 선언된 관계 중 **양끝이 이 트윈에 실재하는 것만**. 없는 선을 그리지 않는다. */\n /* 선의 양끝 판정은 **문서든 행이든 있으면 있는 것**으로 본다 — 투영이 낡았다고 관계가 없어지진 않는다. */\n const nonEmpty = new Set(concepts.filter(c => (c.count ?? 0) > 0 || ((c as any).documentCount ?? 0) > 0).map(c => c.axis))\n /* 선은 **얼마나 이어졌고 얼마나 끊겼는지**까지 말한다 — 관계에서 가장 중요한 정보는 대개 부재다. */\n const edges = await Promise.all(\n TWIN_RELATIONS.filter((r: any) => r.target.kind === 'axis' && nonEmpty.has(r.from) && nonEmpty.has(r.target.axis)).map(\n async (r: any) => ({ from: r.from, to: r.target.axis, via: r.via, optional: !!r.optional, ...this.edgeFill(model, r) })\n )\n )\n\n return {\n instanceId,\n /* 사람이 부르는 이름 — 결선 줄이 업무키만 보이지 않게(업무키는 곁에 남는다). */\n name: inst.name ?? null,\n /*\n * **생산 선언에서 갈 곳이 없는 두 값** — GS1 발행자와 대표 산출물.\n *\n * 나머지 선언(자재·레시피·라우트·공정)은 각자 개념 축이 있고, 자재의 GS1 참조는 자재가 보인다.\n * 이 둘만 자기 축이 없다: 회사 접두사는 이 트윈의 **정체**(발행자)이고, 대표 레시피는 이 공장이\n * 무엇을 만드는 곳인가다. 둘 다 결선 줄(어느 원본을 반영하나)과 같은 성격이라 그 줄에 둔다.\n */\n /*\n * **표준 적합성** — 채운 것과 빠진 것을 함께.\n *\n * 이 화면은 \"이 트윈이 표준 모델의 어디를 채우고 있나\" 를 묻는다. 그런데 지금까지 답은 우리\n * 축만 세었다 — 없는 것은 화면에 없으므로 아무도 묻지 않았다. 기준(표준의 목록)을 함께 내면\n * 그 질문이 실제로 답을 갖는다(`isa95-coverage.ts` 의 머리말 참조).\n */\n /* 종류가 기대는 표준을 **모두** 낸다 — 하나만 말하면 그 트윈의 다른 기준이 보이지 않는다. */\n standards: standardsForKind(inst.kind).map(id => COVERAGE[id]((TWIN_AXES as any[]).map(a => a.axis))),\n production: (() => {\n const spec: any = (inst.model as any)?.productionSpec\n if (!spec) return null\n const key = spec.recipeKey ?? null\n const material = (spec.definition?.materials ?? []).find((m: any) => (m.key ?? m.id) === key)\n return {\n companyPrefix: spec.companyPrefix ?? null,\n recipeKey: key,\n /* 이름은 자재 선언이 말한다(i18n 키일 수 있으므로 번역은 화면의 일). */\n recipeLabel: material?.label ?? material?.name ?? null\n }\n })(),\n description: inst.description ?? null,\n kind: inst.kind,\n restartPolicy: inst.restartPolicy,\n source: ref\n ? { referenceId: ref.source, adapterType: ref.adapterType, status: ref.status, lastSyncedAt: ref.lastSyncedAt }\n : null,\n /*\n * **다시 읽을 수 있는가** — 이 트윈이 자기 원본을 기억하고 있나(`TwinInstance.origin`).\n *\n * 없는 트윈이 실제로 있다(이 자리가 생기기 전에 만들어진 것). 그때 화면이 버튼을 그대로\n * 내면 누를 때마다 실패하고, 사용자는 무엇이 잘못됐는지 알 수 없다. 그래서 **할 수 있는지를\n * 먼저 답한다** — 못 하는 것을 못 한다고 말하는 것도 이 화면의 일이다.\n */\n origin: inst.origin ?? null,\n canResync: !!(inst.origin as any)?.kind,\n concepts,\n edges,\n /* 원본이 모르는데 관측에는 있는 것. `null` = 트윈이 실행 중이 아니어서 비교하지 못했다. */\n observedOnly,\n /*\n * 축 셋만 행이고 나머지는 문서에서 셌다는 사실. 축마다 `basis` 가 있지만 여기서도 한 번 말한다 —\n * 화면이 \"이 숫자는 무엇으로 셌나\" 를 한눈에 보여야 규모 한계가 드러난다(ADR-0032 이주 압력).\n */\n basis: {\n rows: Object.keys(ROW_BACKED),\n document: TWIN_AXES.filter((a: any) => !ROW_BACKED[a.axis]).map((a: any) => a.axis),\n /* 투영이 낡은 축 — 있으면 이 트윈은 인제스트를 한 번 더 태워야 한다. */\n stale: concepts.filter((c: any) => c.stale).map((c: any) => c.axis)\n }\n }\n }\n /**\n * **이 트윈에 무엇을 선언할 수 있나** — 값을 넣는 화면이 도메인의 모양을 알지 않게.\n *\n * 커널이 정한 명세(속성 id·단위·범위·설명)를 그대로 낸다. 화면이 「에너지 트윈이면 계약전력·요금\n * 단가…」를 알고 있으면 도메인이 늘 때마다 화면을 고쳐야 하고 단위가 두 곳에 적힌다(ADR-0031).\n *\n * 지금 무엇이 선언돼 있는지도 함께 낸다(`declared`) — 화면이 두 번 묻지 않게, 그리고 「원천이 말한\n * 값」과 「우리가 정한 값」을 한자리에서 견줄 수 있게(`PropertySource`, ADR-0034).\n *\n * 어휘가 없는 종류는 `properties: []` 다 — 「아직 없다」와 「아무 값이나 받는다」는 다르다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n \"What can be declared on this twin and what already is: the kernel's property specification (id, unit, range, note) plus the current local declarations at both scopes — this twin and its facility — each with the value it overrode. Empty properties mean this kind has no declaration vocabulary yet — not that anything goes.\"\n })\n async twinDeclarations(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n const model = (inst.model ?? {}) as any\n /*\n * 겹이 둘이다 — **현장의 것과 이 트윈의 것**. 화면이 둘을 함께 봐야 「이 값이 어느 범위의 것인가」를\n * 말할 수 있고, 철회 버튼이 어느 겹을 겨눌지 고를 수 있다(요금을 현장에 넣었는데 트윈에서 철회하려\n * 하면 「겹에 없다」로 끝난다 — 사용자는 이유를 알 수 없다).\n */\n const space = inst.spaceId\n ? await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId: inst.spaceId } })\n : null\n /*\n * 돌고 있으면 커널이 「무엇으로 돌고 있나」를 스스로 답한다. 그 보고는 **작업이 새로 생길 때** 채워지\n * 므로 재기동 직후에는 비는데, 이력에서 배운 종류는 그때도 남아 있다(실측으로 이 빈칸에 부딪혔다).\n */\n const operations = operationBasis(\n model,\n TwinEngine.kernel(domainId, instanceId),\n await TwinEngine.measuredOperationKinds(domainId, instanceId)\n )\n return {\n instanceId,\n kind: inst.kind,\n spaceId: inst.spaceId,\n spaceName: space?.name,\n properties: declarablePropertySpecs(inst.kind),\n declared: (inst.localDeclarations ?? []) as any[],\n declaredAtSpace: (space?.localDeclarations ?? []) as any[],\n /* 어디에 얹을 수 있나 — 모델의 자리·설비·공정. 화면이 대상을 지어내지 않게 목록으로 준다. */\n targets: [\n ...(model.locations ?? []).map((l: any) => ({ target: 'location', id: l.id, name: l.name, type: l.type, properties: l.properties ?? [] })),\n ...(readBoardEquipment(model) ?? []).map((e: any) => ({ target: 'equipment', id: e.id, name: e.name, type: e.type ?? e.kind, properties: e.properties ?? [] })),\n /*\n * 공정은 **자원이 아니다** — 값은 종류에 붙고, 그 종류에 명세 행이 없어도 된다(창고·야드에는 행이\n * 0개다). 그래서 대상 목록을 커널의 자기보고까지 합쳐 만든다(`operationBasis`).\n *\n * 지금 값은 **겹에서** 든다(사용자가 넣은 수와 단위 그대로). 모델에는 ISO 8601 로 구워지지만\n * 화면에 「PT240S」를 보이면 자기가 넣은 값인지 알아볼 수 없다.\n */\n ...operations.map(op => ({\n target: 'operation',\n id: op.kind,\n name: op.label,\n properties: declaredDurationProperty(op.kind, inst.localDeclarations as any[], space?.localDeclarations as any[])\n }))\n ],\n /*\n * 이 트윈이 돌리는 공정과 **그 시간이 어디서 오나** — 선언 화면이 「무엇이 상수로 도나」를 보여야\n * 사람이 채울 곳을 안다. `basis` 가 비면 아직 돌지 않은 것이다(공정이 없는 것이 아니다).\n */\n operations\n }\n }\n /**\n * **이 현장에 무엇을 선언할 수 있나** — 현장 값은 트윈의 것이 아니므로 트윈 없이 묻는다.\n *\n * ── 왜 트윈으로 묻지 않나 (2026-08-18) ─────────────────────────────────────\n * 처음에는 트윈 화면에서 범위만 골라 현장 값을 넣게 했다. 그런데 **현장 값을 넣으려고 트윈을 골라야\n * 하는 것**은 개념이 거꾸로다 — 트윈은 그 값을 읽는 쪽이다(사용자가 그 어긋남을 지적했다).\n *\n * 어휘와 대상은 **그 현장 트윈들의 합집합**이다: 한 현장에 여러 종류가 살고(에너지·창고·야드), 요금은\n * 에너지 트윈만 읽는다. 합집합으로 내되 **어느 트윈이 그 대상을 가졌는지** 함께 낸다 — 아무 트윈도\n * 읽지 않는 값을 넣는 일을 화면이 미리 보여 줄 수 있어야 한다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'What can be declared for a whole facility (space) and what already is: the union of the property specifications of the twin kinds living there, the union of their declarable targets (with which twins carry each), and the current facility-scope declarations.'\n })\n async spaceDeclarations(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return null\n const twins = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, spaceId } })\n\n /* 어휘 합집합 — 같은 속성이 여러 종류에 있어도 한 번만 낸다(명세는 커널 하나에서 온다). */\n const byId = new Map<string, any>()\n for (const t of twins) for (const spec of declarablePropertySpecs(t.kind)) if (!byId.has(spec.id)) byId.set(spec.id, spec)\n\n /* 대상 합집합 — 같은 id 가 여러 트윈에 있으면 **그 트윈들을 함께** 든다(현장 값은 그 전부에 얹힌다). */\n const targets = new Map<string, { target: string; id: string; name?: string; type?: string; twins: string[] }>()\n for (const t of twins) {\n const model = (t.model ?? {}) as any\n const add = (target: 'location' | 'equipment' | 'operation', e: any) => {\n const key = `${target}:${e.id}`\n const cur = targets.get(key)\n if (cur) { cur.twins.push(t.instanceId); return }\n targets.set(key, { target, id: e.id, name: e.name, type: e.type ?? e.kind, twins: [t.instanceId] })\n }\n for (const l of model.locations ?? []) add('location', l)\n for (const e of readBoardEquipment(model) ?? []) add('equipment', e)\n /*\n * 공정도 대상이다 — 「우리 현장의 적재는 4분」은 그 현장 창고 전부가 읽어야 하는 수다. 목록은 각\n * 트윈의 것을 합집합으로 든다(돌고 있으면 커널이 스스로 답한다).\n */\n for (const op of operationBasis(\n model,\n TwinEngine.kernel(domainId, t.instanceId),\n await TwinEngine.measuredOperationKinds(domainId, t.instanceId)\n ))\n add('operation', { id: op.kind, name: op.label })\n }\n\n return {\n spaceId,\n spaceName: space.name,\n properties: [...byId.values()],\n declared: (space.localDeclarations ?? []) as any[],\n targets: [...targets.values()],\n /* 이 현장에 사는 트윈들 — 화면이 「누가 이 값을 읽나」를 말할 수 있게. */\n twins: twins.map(t => ({ instanceId: t.instanceId, kind: t.kind, name: t.name }))\n }\n }\n}\n"]}
1
+ {"version":3,"file":"twin-model-query.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-model-query.ts"],"names":[],"mappings":";;;;AAAA,yEAA8D;AAC9D,uDAA+C;AAC/C,+DAAuD;AACvD,+CAAwD;AAExD,iDAAmE;AAEnE,oDAAkD;AAClD,4DAAsD;AACtD,sDAAyD;AACzD,wEAAgE;AAChE,+DAAuD;AACvD,sEAA8D;AAC9D,8EAA4E;AAC5E,wEAAgE;AAChE,yDAAiD;AACjD,2DAAmD;AACnD,2DAAmD;AACnD,iEAA0E;AAC1E,2DAAmD;AACnD,iEAAyD;AACzD,2DAAmD;AAEnD;;;;;GAKG;AACH,SAAS,wBAAwB,CAAC,IAAY,EAAE,aAAqB,EAAE,UAAkB;IACvF,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE;QAC7B,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,CAAA;QAC5F,OAAO,CAAC,CAAC,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,oBAAoB,CAAC,CAAA;IAC/E,CAAC,CAAA;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAA;IACjD,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACjB,OAAO,CAAC,EAAE,EAAE,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;AAChI,CAAC;AAED,+DAA+D;AAC/D,MAAM,QAAQ,GAAgD,EAAE,KAAK,EAAE,iCAAa,EAAE,KAAK,EAAE,iCAAa,EAAE,QAAQ,EAAE,uCAAgB,EAAE,CAAA;AACxI,8DAAwD;AACxD,yEAAyE;AACzE,yFAAoF;AAEpF,qDAAqD;AACrD,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAA;AAE1G;;;;GAIG;AACH,MAAM,UAAU,GAAwB;IACtC,SAAS,EAAE,+BAAY;IACvB,SAAS,EAAE,iCAAa;IACxB,UAAU,EAAE,iCAAa;CAC1B,CAAA;AAED,4DAA4D;AAC5D,MAAM,EAAE,GAAG,CAAC,CAAM,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAErH,SAAS,eAAe,CAAC,KAAU,EAAE,IAAY;IAC/C,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzB,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAA;IACrC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAClB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,uEAAuE;AACvE,SAAS,QAAQ,CAAC,GAAQ,EAAE,KAAa;IACvC,IAAI,GAAG,GAAU,CAAC,GAAG,CAAC,CAAA;IACtB,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QACvD,MAAM,IAAI,GAAU,EAAE,CAAA;QACtB,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,IAAI;gBAAE,SAAQ;YACvB,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;YAChB,IAAI,CAAC,IAAI,IAAI;gBAAE,SAAQ;YACvB,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;;gBAChC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACnB,CAAC;QACD,GAAG,GAAG,IAAI,CAAA;IACZ,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAA;AACnC,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,CAAM,EAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,CAAA;AAExG,6DAA6D;AAC7D,KAAK,UAAU,gBAAgB,CAAC,QAAgB,EAAE,UAAkB,EAAE,IAAY;IAChF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAA,4CAAiB,EAAC,IAAA,qBAAa,EAAC,yBAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,CAAC,CAAA;QAC9G,8DAA8D;QAC9D,OAAO,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAA;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;QACnC,OAAO,UAAU,CAAA;IACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,EAAiD,CAAA;AAC7E,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAGnB,IAAM,cAAc,GAApB,MAAM,cAAc;IACzB;;;;;;;;;;;;;;OAcG;IACK,QAAQ,CAAC,KAAU,EAAE,CAAM;QACjC,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACtC,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAA;QAEnC;;;;WAIG;QACH,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,UAAU,IAAI,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,UAAU;YAAE,OAAO,EAAE,CAAA;QAE5F,MAAM,MAAM,GAAG,CAAC,IAAS,EAAE,EAAE;YAC3B,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;YAChC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACpD,CAAC,CAAA;QACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC,CAAA;QAC7E,IAAI,MAAM,GAAG,CAAC,CAAA;QACd,IAAI,QAAQ,GAAG,CAAC,CAAA;QAChB,IAAI,OAAO,GAAG,CAAC,CAAA;QACf,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACvH,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBAAC,OAAO,EAAE,CAAC;gBAAC,SAAQ;YAAC,CAAC;YACzC;yCAC6B;YAC7B,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAE,MAAM,EAAE,CAAA;;gBACxC,QAAQ,EAAE,CAAA;QACjB,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAAA;IAC7D,CAAC;IAOK,AAAN,KAAK,CAAC,gBAAgB,CAAoB,UAAkB,EAAS,OAAwB;QAC3F,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QAC3G,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QAEtB;;;;;;;;;;;;;;;;;WAiBG;QACH,MAAM,MAAM,GAAI,IAAI,CAAC,MAAc,EAAE,MAAM,IAAI,UAAU,CAAA;QACzD,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAa,EAAC,iCAAa,CAAC;aAC3C,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;aAChE,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QAEpB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAA;QAC9B;;;;;;;;;WASG;QACH,IAAI,YAAY,GAAuE,IAAI,CAAA;QAC3F,MAAM,SAAS,GAAG,GAAG,EAAE,CACrB,CAAC,YAAY,KAAK,CAAC,KAAK,IAAI,EAAE;YAC5B,MAAM,SAAS,GAAG,qBAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;YAC5D,IAAI,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAA,2BAAW,EAAC,qBAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YACpF,IAAI,MAAM,GAAG,KAAK,CAAA;YAClB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,CAAC;oBACH,IAAI,GAAG,IAAA,2BAAW,EAAC,MAAM,qBAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAA;oBAClE,MAAM,GAAG,CAAC,CAAC,IAAI,CAAA;gBACjB,CAAC;gBAAC,MAAM,CAAC;oBACP,iDAAiD;gBACnD,CAAC;YACH,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;QACpC,CAAC,CAAC,EAAE,CAAC,CAAA;QAEP;;;;;;;;;;;;;;;;;;WAkBG;QACH,IAAI,cAAc,GAAiE,IAAI,CAAA;QACvF,MAAM,WAAW,GAAG,GAAG,EAAE,CACvB,CAAC,cAAc,KAAK,CAAC,KAAK,IAAI,EAAE;YAC9B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAa,EAAC,yBAAS,CAAC,CAAC,OAAO,CAAC;oBACjD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAS,EAAE,UAAU,EAAS;oBAC7D,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAS;iBACnC,CAAC,CAAA;gBACF,IAAI,CAAC,GAAG,EAAE,QAAQ;oBAAE,OAAO,IAAI,CAAA;gBAC/B;;;;;;;mBAOG;gBACH,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,UAAU,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAA;gBACvD,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;gBACjC,IAAI,GAAG;oBAAE,OAAO,GAAG,CAAA;gBACnB,MAAM,IAAI,GAAG,IAAA,2BAAW,EAAC,MAAM,qBAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAA;gBACtF,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAA;gBACtB,oDAAoD;gBACpD,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,OAAO,EAAG,IAAY,CAAC,OAAO,IAAI,GAAG,CAAC,SAAS,IAAI,IAAI,EAAE,CAAA;gBAC7E,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBAC1B,gDAAgD;gBAChD,OAAO,YAAY,CAAC,IAAI,GAAG,gBAAgB;oBAAE,YAAY,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAe,CAAC,CAAA;gBAC5G,OAAO,GAAG,CAAA;YACZ,CAAC;YAAC,MAAM,CAAC;gBACP,gDAAgD;gBAChD,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC,CAAC,EAAE,CAAC,CAAA;QAEP,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,CAAM,EAAE,EAAE;YAC7B;;;;;;;;eAQG;YACH,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtC,OAAO;oBACL,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,SAAS,EAAE,KAAK;oBAChB,aAAa,EAAE,CAAC,CAAC,aAAa;oBAC9B,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;oBAC5B,KAAK,EAAE,IAAI;oBACX,aAAa,EAAE,IAAI;oBACnB,YAAY,EAAE,gBAAgB;oBAC9B;;;;;;;;;uBASG;oBACH,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;oBAC1B,KAAK,EAAE,aAAa;iBACrB,CAAA;YACH,CAAC;YACD,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;YACjC,2DAA2D;YAC3D,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACzF,MAAM,MAAM,GAAG,MAAM;gBACnB,CAAC,CAAC,MAAM,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAS,EAAE,EAAE,CAAC;gBACrH,CAAC,CAAC,SAAS,CAAA;YACb;;;;;;;;;eASG;YACH,MAAM,KAAK,GAAG,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,UAAU,CAAA;YAC3D;;;;;;eAMG;YACH,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YAC7E;;;;eAIG;YACH;;;;;;;;eAQG;YACH,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;gBACnC;+EAC+D;gBAC/D;;;;;mBAKG;gBACH,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,EAAE,CAAA;gBACrD;8EAC8D;gBAC9D,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAA,0BAAW,EAAC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;gBACvD,IAAI,OAAO,GAAkB,IAAI,CAAA;gBACjC,IAAI,WAAW,GAAG,KAAK,CAAA;gBACvB;;;mBAGG;gBACH,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5D,MAAM,IAAI,GAAG,MAAM,IAAA,4CAAiB,EAAC,IAAA,qBAAa,EAAC,yBAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;oBAClI,IAAI,IAAI,EAAE,CAAC;wBACT,MAAM,CAAC,GAAG,MAAM,WAAW,EAAE,CAAA;wBAC7B,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAA,0BAAW,EAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;wBAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;4BACnC,MAAM,GAAG,EAAE,CAAA;4BACX,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAA;4BAC5B,WAAW,GAAG,IAAI,CAAA;wBACpB,CAAC;oBACH,CAAC;gBACH,CAAC;gBACD,OAAO;oBACL,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,SAAS,EAAE,KAAK;oBAChB,aAAa,EAAE,CAAC,CAAC,aAAa;oBAC9B,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;oBAC5B,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;oBACnD;;;;;;;;;uBASG;oBACH,GAAG,IAAA,6BAAW,EAAC,MAAM,CAAC;oBACtB;;;;uBAIG;oBACH,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;wBACvB,CAAC,CAAC,MAAM,CAAC,MAAM;4BACb,CAAC,CAAC,EAAE;4BACJ,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE;wBAC1E,CAAC,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;oBACrC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;oBAC1B,KAAK,EAAE,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;oBAClD;;;uBAGG;oBACH,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9D,SAAS;oBACT,qDAAqD;oBACrD,YAAY,EAAE,IAAI;oBAClB,kBAAkB,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,QAAQ;iBACrG,CAAA;YACH,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,SAAS,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,IAAI;gBAC7C,aAAa,EAAE,CAAC,CAAC,aAAa;gBAC9B,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;gBAC5B,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU;gBACnC;;;;;;;;mBAQG;gBACH,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;oBAChC,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC;wBACE,YAAY,EACV,MAAM,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,gBAAgB;qBAC/F,CAAC;gBACN,8DAA8D;gBAC9D,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC1B,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU;gBACnC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvD,iDAAiD;gBACjD,kBAAkB,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,QAAQ;aACrG,CAAA;QACH,CAAC,CAAC,CACH,CAAA;QAED;;;;;;;;;;;WAWG;QACH;;;;;;WAMG;QACH,MAAM,IAAI,GAAG,qBAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QACtD,MAAM,GAAG,GAAG,IAAA,yBAAU,EAAC,IAAI,CAAC,CAAA;QAC5B,MAAM,YAAY,GAAG,IAAI;YACvB,CAAC,CAAC;gBACE,SAAS,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpG,SAAS,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpG,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChG,sDAAsD;gBACtD,SAAS,EAAE,GAAG,EAAE,SAAS,IAAI,EAAE;gBAC/B,uBAAuB,EAAE,GAAG,EAAE,uBAAuB,IAAI,IAAI;gBAC7D,UAAU,EAAE,GAAG,EAAE,UAAU,IAAI,EAAE;aAClC;YACH,CAAC,CAAC,IAAI,CAAA;QAER,4DAA4D;QAC5D,kEAAkE;QAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAE,CAAS,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QAC1H,8DAA8D;QAC9D,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,cAAc,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CACpH,KAAK,EAAE,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CACxH,CACF,CAAA;QAED,OAAO;YACL,UAAU;YACV,kDAAkD;YAClD,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;YACvB;;;;;;eAMG;YACH;;;;;;eAMG;YACH,2DAA2D;YAC3D,SAAS,EAAE,IAAA,uCAAgB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAE,SAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YACrG,UAAU,EAAE,CAAC,GAAG,EAAE;gBAChB,MAAM,IAAI,GAAS,IAAI,CAAC,KAAa,EAAE,cAAc,CAAA;gBACrD,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAA;gBACtB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAA;gBAClC,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAA;gBAC7F,OAAO;oBACL,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;oBACzC,SAAS,EAAE,GAAG;oBACd,+CAA+C;oBAC/C,WAAW,EAAE,QAAQ,EAAE,KAAK,IAAI,QAAQ,EAAE,IAAI,IAAI,IAAI;iBACvD,CAAA;YACH,CAAC,CAAC,EAAE;YACJ;;;;;;;;;;;;;;;eAeG;YACH,iBAAiB,EAAE,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,iBAAiB,IAAI,IAAI;YACtE,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;YACrC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,MAAM,EAAE,GAAG;gBACT,CAAC,CAAC;oBACE,WAAW,EAAE,GAAG,CAAC,MAAM;oBACvB,WAAW,EAAE,GAAG,CAAC,WAAW;oBAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,YAAY,EAAE,GAAG,CAAC,YAAY;oBAC9B;;;;;;;;;;;;;;;;;;;;;;uBAsBG;oBACH,OAAO,EAAE,MAAM,CAAC,OAAO,CAAE,GAAG,CAAC,UAAkB,EAAE,OAAO,IAAI,EAAE,CAAC;yBAC5D,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC;wBAC3E,0DAA0D;yBACzD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;oBAC7E,eAAe,EAAG,GAAG,CAAC,UAAkB,EAAE,eAAe,IAAI,IAAI;iBAClE;gBACH,CAAC,CAAC,IAAI;YACR;;;;;;eAMG;YACH,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;YAC3B,SAAS,EAAE,CAAC,CAAE,IAAI,CAAC,MAAc,EAAE,IAAI;YACvC,QAAQ;YACR,KAAK;YACL,2DAA2D;YAC3D,YAAY;YACZ;;;eAGG;YACH,KAAK,EAAE;gBACL,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;gBAC7B,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;gBACnF,8CAA8C;gBAC9C,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aACpE;SACF,CAAA;IACH,CAAC;IACD;;;;;;;;;;OAUG;IAMG,AAAN,KAAK,CAAC,gBAAgB,CAAoB,UAAkB,EAAS,OAAwB;QAC3F,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QAC3G,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAQ,CAAA;QACvC;;;;WAIG;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO;YACxB,CAAC,CAAC,MAAM,IAAA,qBAAa,EAAC,yBAAS,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACxG,CAAC,CAAC,IAAI,CAAA;QACR;;;WAGG;QACH,MAAM,UAAU,GAAG,IAAA,mCAAc,EAC/B,KAAK,EACL,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,EACvC,MAAM,qBAAU,CAAC,sBAAsB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAC9D,CAAA;QACD,OAAO;YACL,UAAU;YACV,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,KAAK,EAAE,IAAI;YACtB,UAAU,EAAE,IAAA,+CAAuB,EAAC,IAAI,CAAC,IAAI,CAAC;YAC9C,QAAQ,EAAE,CAAC,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAAU;YACjD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,IAAI,EAAE,CAAU;YAC1D,0DAA0D;YAC1D,OAAO,EAAE;gBACP,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1I,GAAG,CAAC,IAAA,gCAAkB,EAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC/J;;;;;;mBAMG;gBACH,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBACvB,MAAM,EAAE,WAAW;oBACnB,EAAE,EAAE,EAAE,CAAC,IAAI;oBACX,IAAI,EAAE,EAAE,CAAC,KAAK;oBACd,UAAU,EAAE,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,iBAA0B,EAAE,KAAK,EAAE,iBAA0B,CAAC;iBAClH,CAAC,CAAC;aACJ;YACD;;;eAGG;YACH,UAAU;SACX,CAAA;IACH,CAAC;IACD;;;;;;;;;;OAUG;IAMG,AAAN,KAAK,CAAC,iBAAiB,CAAiB,OAAe,EAAS,OAAwB;QACtF,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,yBAAS,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;QACtG,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;QAEtG,yDAAyD;QACzD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAe,CAAA;QACnC,KAAK,MAAM,CAAC,IAAI,KAAK;YAAE,KAAK,MAAM,IAAI,IAAI,IAAA,+CAAuB,EAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QAE1H,oEAAoE;QACpE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyF,CAAA;QAChH,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAQ,CAAA;YACpC,MAAM,GAAG,GAAG,CAAC,MAA8C,EAAE,CAAM,EAAE,EAAE;gBACrE,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,CAAC,CAAC,EAAE,EAAE,CAAA;gBAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;gBAC5B,IAAI,GAAG,EAAE,CAAC;oBAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;oBAAC,OAAM;gBAAC,CAAC;gBACjD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;YACrG,CAAC,CAAA;YACD,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,IAAI,EAAE;gBAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAA;YACzD,KAAK,MAAM,CAAC,IAAI,IAAA,gCAAkB,EAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;YACpE;;;eAGG;YACH,KAAK,MAAM,EAAE,IAAI,IAAA,mCAAc,EAC7B,KAAK,EACL,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,EACzC,MAAM,qBAAU,CAAC,sBAAsB,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAChE;gBACC,GAAG,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAA;QACrD,CAAC;QAED,OAAO;YACL,OAAO;YACP,SAAS,EAAE,KAAK,CAAC,IAAI;YACrB,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9B,QAAQ,EAAE,CAAC,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAU;YAClD,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAC9B,+CAA+C;YAC/C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;SAClF,CAAA;IACH,CAAC;CACF,CAAA;AArnBY,wCAAc;AAoDnB;IALL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,iNAAiN;KACpN,CAAC;IACsB,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;sDA6bnE;AAiBK;IALL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,mUAAmU;KACtU,CAAC;IACsB,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;sDAsDnE;AAiBK;IALL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,mQAAmQ;KACtQ,CAAC;IACuB,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;IAAmB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;uDA2C9D;yBApnBU,cAAc;IAD1B,IAAA,uBAAQ,GAAE;GACE,cAAc,CAqnB1B","sourcesContent":["import { axisSeenInJournal } from './axis-journal-evidence.js'\nimport { statusTally } from './status-tally.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { Arg, Ctx, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { modelGapOf } from '../../engine/model-gap.js'\nimport { readBoardEquipment } from '@operato/twin-kernel'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinSpace } from '../twin-space/twin-space.js'\nimport { TwinReference } from '../reference/twin-reference.js'\nimport { declarablePropertySpecs } from '../../engine/local-declarations.js'\nimport { operationBasis } from '../../engine/operation-basis.js'\nimport { TwinLocation } from './twin-location.js'\nimport { TwinEquipment } from './twin-equipment.js'\nimport { TwinOperation } from './twin-operation.js'\nimport { standardsForKind, type StandardId } from './standard-coverage.js'\nimport { isa95Coverage } from './isa95-coverage.js'\nimport { iec61850Coverage } from './iec61850-coverage.js'\nimport { epcisCoverage } from './epcis-coverage.js'\n\n/**\n * 이 공정에 선언된 시간을 **겹에서** 든다 — 좁은 겹이 이긴다(트윈 먼저, 없으면 현장).\n *\n * 모델에는 ISO 8601 로 구워지지만 화면에는 **사용자가 넣은 수와 단위**를 보인다: 「PT240S」를 보이면\n * 자기가 넣은 값인지 알아볼 수 없다. 선언이 없으면 빈 배열이다(빈 값을 지어내지 않는다).\n */\nfunction declaredDurationProperty(kind: string, instanceLayer?: any[], spaceLayer?: any[]): any[] {\n const find = (layer?: any[]) => {\n const d = (layer ?? []).find(x => x?.target === 'operation' && String(x?.id ?? '') === kind)\n return (d?.properties ?? []).find((p: any) => p?.id === 'operation.duration')\n }\n const p = find(instanceLayer) ?? find(spaceLayer)\n if (!p) return []\n return [{ id: 'operation.duration', value: String(p.value ?? ''), ...(p.uom ? { uom: String(p.uom) } : {}), source: 'local' }]\n}\n\n/** 표준 id → 그 표의 함수. 정책은 `standardsForKind` 가, 표는 각 파일이 가진다. */\nconst COVERAGE: Record<StandardId, (axes: string[]) => any> = { isa95: isa95Coverage, epcis: epcisCoverage, iec61850: iec61850Coverage }\nimport { unwrapState } from '../../engine/warm-start.js'\n/* 공유 모듈은 빌드 산출물로 참조한다 — 서버·클라이언트가 같은 규칙을 쓰는 자리다(같은 관행: entity-delta). */\nimport { axisValueOf } from '@things-factory/headless-twin/dist-shared/axis-read.js'\n\n/* 커널 런타임 로드 — CJS 번들(엔진과 같은 방식). 축·관계 선언의 SSOT 다. */\nconst { TWIN_AXES, TWIN_RELATIONS, axisInfo, axisSource, axisAppliesTo } = require('@operato/twin-kernel')\n\n/**\n * 축 → 그 축이 이미 **행으로 옮겨졌나**. 옮겨진 축은 DB 가 세고, 아직인 축은 문서를 세어야 한다.\n *\n * 이 표가 곧 `ADR-0032` 이주의 진척이다 — 여기 없는 축은 아직 `TwinModelDef` 문서 안에 산다.\n */\nconst ROW_BACKED: Record<string, any> = {\n locations: TwinLocation,\n equipment: TwinEquipment,\n operations: TwinOperation\n}\n\n/** `productionSpec.definition.recipes` 같은 경로를 문서에서 따라간다. */\nconst at = (o: any, path: string) => path.split('.').reduce((a: any, k: string) => (a == null ? undefined : a[k]), o)\n\nfunction countInDocument(model: any, path: string): number {\n const v = at(model, path)\n if (Array.isArray(v)) return v.length\n return v ? 1 : 0\n}\n\n/*\n * 트윈 모델 조회 — **이 트윈은 표준 모델의 어디를 채우고 있나**(`plans/twin-inspection.md` §2 ②).\n *\n * ── 답의 크기가 개념 수에 비례한다 ─────────────────────────────────────────\n * 엔티티를 **절대 싣지 않는다.** 실으면 계약이 곧 무너지고, 규모가 오는 날 이 질의가 그대로\n * 병목이 된다. 항목은 별도 질의(`twinModelEntities`)가 페이징으로 낸다.\n *\n * ── 개념 목록을 이 코드가 갖지 않는다 ───────────────────────────────────────\n * 축·관계는 **커널 카탈로그가 선언**한다(`TWIN_AXES`·`TWIN_RELATIONS`). 그래서 야드의 `dock-door`\n * 나 에너지의 계량기가 늘어도 여기 손댈 것이 없다 — 도메인마다 화면·질의가 하나씩 늘어나던\n * 구조를 끊는 것이 이 설계의 요점이다(`ADR-0031`).\n *\n * ── 어떻게 셌는지 밝힌다 ───────────────────────────────────────────────────\n * 지금은 축 셋만 행이고 나머지는 문서 안에 있다. 그 사실을 `basis` 로 **축마다** 말한다.\n * 밝히지 않으면 \"이 트윈은 3개 축뿐\" 으로 읽히고, 이주가 어디까지 왔는지도 아무도 모른다.\n */\n/** `inputs[].material` 처럼 배열을 지나는 선언 경로를 따라간다 — 배열 표기가 관계 선언의 문법이다. */\nfunction valuesAt(obj: any, field: string): any[] {\n let cur: any[] = [obj]\n for (const seg of field.split('.')) {\n const key = seg.endsWith('[]') ? seg.slice(0, -2) : seg\n const next: any[] = []\n for (const c of cur) {\n if (c == null) continue\n const v = c[key]\n if (v == null) continue\n if (Array.isArray(v)) next.push(...v)\n else next.push(v)\n }\n cur = next\n }\n return cur.filter(v => v != null)\n}\n\nconst idOf = (o: any): string | null => (typeof o === 'string' ? o : o?.id ?? o?.key ?? o?.gtin ?? null)\n\n/** 상태가 빈 축의 이유 — 저널에 그 사실이 있으면 그렇게 말한다. 판단할 수 없으면 예전 그대로. */\nasync function emptyStateReason(domainId: string, instanceId: string, axis: string): Promise<string> {\n try {\n const seen = await axisSeenInJournal(getRepository(TwinEvent), { domain: { id: domainId }, instanceId }, axis)\n /* 판단할 수 없는 축(`null`)은 예전 답을 그대로 쓴다 — 모르는 것을 새 사실로 만들지 않는다. */\n return seen ? 'observed-not-in-state' : 'none-yet'\n } catch {\n /* 저널을 못 읽었다고 카드가 없는 말을 하지는 않는다. */\n return 'none-yet'\n }\n}\n\n/**\n * 되세운 상태를 담아 둔다 — **열쇠에 저널 끝 리비전이 들어 있어** 새 사건이 오면 저절로 낡는다.\n *\n * 접기는 저널 전체를 훑는 일이라(리비전 12만에서 2~3초) 모델 화면을 열 때마다 그 값을 낼 수 없다.\n * 프로세스 안에만 두고 몇 개만 남긴다 — 다시 뜨면 다시 접는다(정확함이 캐시보다 앞선다).\n */\nconst RECALL_CACHE = new Map<string, { snap: any; heardAt: string | null }>()\nconst RECALL_CACHE_MAX = 8\n\n@Resolver()\nexport class TwinModelQuery {\n /**\n * 이 선이 **얼마나 이어져 있나** — 그리고 **얼마나 끊겨 있나.**\n *\n * 선이 \"선언되었고 양끝이 존재한다\" 만 말하면 지도는 그림일 뿐이다. 관계에서 가장 중요한 정보는\n * 대개 **부재**다 — 기준 자리가 비어 있는 설비 열두 대, 어느 라우트에도 안 실린 공정. 그것이\n * 조감도에서 보여야 사람이 끊긴 데를 찾아다니지 않는다.\n *\n * 셋을 센다:\n * · `linked` — 값이 있고 그 값이 상대편에 **실제로 있다**\n * · `dangling` — 값은 있는데 **가리키는 것이 없다**(끊어진 참조 — 가장 나쁜 부류)\n * · `missing` — 값 자체가 없다(선택 관계면 정상, 필수 관계면 결손)\n *\n * 문서 어휘로 푼다 — 관계 선언의 필드 이름이 원본 문서의 어휘이기 때문이다(행은 자기 컬럼\n * 이름을 쓴다). 그 사실을 `fillBasis` 로 함께 낸다.\n */\n private edgeFill(model: any, r: any): any {\n const fromInfo = axisInfo(r.from)\n const toInfo = axisInfo(r.target.axis)\n if (!fromInfo || !toInfo) return {}\n\n /*\n * **문서로 셀 수 없는 관계는 세지 않는다.** 선 채움(이어짐·끊어짐·빈 것)은 두 축을 문서에서\n * 살펴 센다. 그런데 일정·실적은 문서에 없다(관측 축) — 그 관계를 문서로 세면 언제나 0 이 나오고,\n * 화면은 그것을 \"전부 끊어졌다\" 로 읽는다. 셀 수 없으면 **수를 내지 않는 편이 정직하다.**\n */\n if (axisSource(r.from) !== 'document' || axisSource(r.target.axis) !== 'document') return {}\n\n const listOf = (info: any) => {\n const raw = at(model, info.path)\n return Array.isArray(raw) ? raw : raw ? [raw] : []\n }\n const targets = new Set(listOf(toInfo).map(idOf).filter(Boolean) as string[])\n let linked = 0\n let dangling = 0\n let missing = 0\n for (const item of listOf(fromInfo)) {\n const vals = valuesAt(item, r.field).map(v => (typeof v === 'object' ? idOf(v) : v)).filter(v => v != null).map(String)\n if (!vals.length) { missing++; continue }\n /* 한 항목이 여럿을 가리킬 수 있다(`steps[]`). 하나라도 끊겼으면 끊긴 쪽으로 센다 —\n \"대체로 이어져 있다\" 는 진단이 아니다. */\n if (vals.every(v => targets.has(v))) linked++\n else dangling++\n }\n return { linked, dangling, missing, fillBasis: 'document' }\n }\n\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'What this twin reflects and where it fills the standard model: per-concept counts, how each was counted, and the declared relations between concepts. Never carries entities — use twinModelEntities for items.'\n })\n async twinModelSummary(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n\n /*\n * 어느 원본을 반영하나 — 없을 수 있다(템플릿으로 세운 트윈). 그 사실도 사실이다.\n *\n * ── **레퍼런스 열쇠는 인스턴스 이름이 아니다** (2026-08-24 실측) ──────────────\n * 여기서 `source: instanceId` 로 찾고 있었다. 레퍼런스 하나가 인스턴스 하나를 낳던 시절에는\n * 두 값이 같았지만, **레퍼런스 하나가 여러 트윈을 먹이는 순간 어긋난다.**\n *\n * 인스턴스 operato-chef-v1-pocheon\n * origin { kind: 'reference', source: 'seunghwa' } ← 이것이 레퍼런스 열쇠다\n * 레퍼런스 seunghwa\n *\n * 그래서 요약의 `source` 가 **언제나 `null`** 이었다. 레퍼런스로 세운 트윈인데 화면이 그 원본을\n * 못 찾았고, 「마지막 동기화」도 흐름별 신선도도 그릴 자리가 아예 없었다 — 그런데 오류는 없었다\n * (`ref ? … : null` 이 조용히 갈렸다).\n *\n * 인스턴스가 자기 원본을 말하고 있으므로 **그 말을 먼저 듣는다.** 옛 트윈은 두 값이 같으므로\n * 뒤의 것으로 여전히 찾힌다 — 가산적이다.\n */\n const refKey = (inst.origin as any)?.source || instanceId\n const ref = await getRepository(TwinReference)\n .findOne({ where: { domain: { id: domainId }, source: refKey } })\n .catch(() => null)\n\n const model = inst.model ?? {}\n /*\n * **한 요청에 저널은 한 번만 접는다.**\n *\n * 관측 축(`orders`·`tasks`)은 상태에서 센다. 그 둘이 각자 `recover()` 를 부르고 있었고, 커널이\n * 멈춰 있으면 그것은 **저널 전체를 다시 접는 일**이다(23,731건짜리 트윈에서 4.6초, 두 번이면 곱절).\n * 트윈을 전부 멈춰도 이 화면이 5~7초 걸린 이유가 이것이다 — 부하가 아니라 같은 일을 두 번 한 것이다.\n *\n * 한 번만 접어 나눠 쓴다. 값이 같아지는 것은 덤이 아니라 **옳음**이다: 두 번 접으면 그 사이에\n * 들어온 이벤트 때문에 오더와 작업이 서로 다른 시점의 사실이 될 수 있다.\n */\n let statePromise: Promise<{ snap: any; folded: boolean; observing: boolean }> | null = null\n const stateOnce = () =>\n (statePromise ??= (async () => {\n const observing = TwinEngine.isRunning(domainId, instanceId)\n let snap = observing ? unwrapState(TwinEngine.snapshot(domainId, instanceId)) : null\n let folded = false\n if (!snap) {\n try {\n snap = unwrapState(await TwinEngine.recover(domainId, instanceId))\n folded = !!snap\n } catch {\n /* 다시 계산할 것이 없다 — 0 으로 메우지 않는다(카드는 \"모름\" 을 낸다). */\n }\n }\n return { snap, folded, observing }\n })())\n\n /*\n * ── **라이브가 잊은 것을 저널이 안다** (2026-08-23) ─────────────────────────\n *\n * 미러의 관측은 메모리에만 산다. 재기동하면 빈 채로 시작하고, 원본이 다시 말해 줄 때 채워진다 —\n * 그런데 읽기 커서가 정직해진 뒤로는 원본이 **지난 것을 다시 말하지 않는다.** 그래서 재고 741건이\n * 저널에 있는데 화면은 「0」을 보였다. 사용자가 「재고현황이 없다」고 물은 자리가 이것이다.\n *\n * 그 0 은 사실이 아니라 **우리가 잊은 것**이다. 저널을 접으면 그대로 되살아난다(실측: 리비전\n * 97,689 을 2.4초에 접어 물품 741 · 작업 9,145 · 지시 2,856).\n *\n * ── 그래서 접는다. 다만 **언제 들은 값인지 함께 낸다** ──────────────────────\n * 저널을 접는 것은 **원천이 우리에게 말한 것을 다시 세우는 일**이지, 없는 값을 심는 일이 아니다.\n * 그러나 물품·재고처럼 **절대값인 축**은 우리가 못 듣는 사이 떠났을 수 있다 — 그래서 값과 함께\n * `heardAt`(그 접기의 시계)을 낸다. 사용자가 신선도를 스스로 판정할 수 있어야 하고, 화면은 그것을\n * 값 옆에 적는다. 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.\n *\n * 접는 조건을 좁게 둔다: **관측 축이 비어 있고, 저널에 그 축의 사실이 있을 때만**(그 판정은 색인\n * 한 줄이다). 그리고 한 요청에 **한 번만** 접는다 — 위 `stateOnce` 와 같은 규율이다.\n */\n let journalPromise: Promise<{ snap: any; heardAt: string | null } | null> | null = null\n const journalOnce = () =>\n (journalPromise ??= (async () => {\n try {\n const tip = await getRepository(TwinEvent).findOne({\n where: { domain: { id: domainId } as any, instanceId } as any,\n order: { revision: 'DESC' } as any\n })\n if (!tip?.revision) return null\n /*\n * **한 번 접은 것을 다시 접지 않는다.** 접기는 저널 전체를 훑는 일이다(리비전 12만에서 2~3초).\n * 이 화면은 사람이 여러 번 들어오는 자리이므로 그대로 두면 매번 그 값을 낸다 — 그리고 저널이\n * 수백만 행이 되면 그 비용이 화면을 못 쓰게 만든다.\n *\n * 열쇠에 **저널 끝의 리비전**을 넣는다: 새 사건이 오면 열쇠가 달라지므로 캐시가 저절로 낡는다\n * (캐시가 사실을 이기지 않는다). 프로세스 안에만 두고 몇 개만 남긴다.\n */\n const key = `${domainId}:${instanceId}:${tip.revision}`\n const hit = RECALL_CACHE.get(key)\n if (hit) return hit\n const snap = unwrapState(await TwinEngine.recover(domainId, instanceId, tip.revision))\n if (!snap) return null\n /* 그 접기의 시계가 「언제 들은 값인가」다. 없으면 저널 끝의 사건 시각으로 답한다. */\n const out = { snap, heardAt: (snap as any).nowTime ?? tip.eventTime ?? null }\n RECALL_CACHE.set(key, out)\n /* 오래된 열쇠부터 버린다 — 트윈 몇 개를 번갈아 봐도 메모리가 자라지 않게. */\n while (RECALL_CACHE.size > RECALL_CACHE_MAX) RECALL_CACHE.delete(RECALL_CACHE.keys().next().value as string)\n return out\n } catch {\n /* 접지 못했으면 아무 말도 만들지 않는다 — 라이브가 말한 것을 그대로 둔다. */\n return null\n }\n })())\n\n const concepts = await Promise.all(\n TWIN_AXES.map(async (a: any) => {\n /*\n * **이 종류에 없는 개념인가** — 「아직 없음」과 「해당 없음」은 다른 말이다.\n *\n * 에너지 트윈에는 옮겨 다니는 물건도, 배정할 사람도, 공정도 없다(커널이 축마다 선언한다).\n * 그것을 「로트 0」으로 그리면 사용자는 채우러 갈 곳을 찾고, 적합성 표는 채우지 못한 것으로\n * 센다 — 둘 다 거짓이다. 수는 `null`(모름이 아니라 셀 대상이 아님)이고 이유를 코드로 낸다.\n *\n * 세지도 않는다: 없는 개념을 행에서 세는 것은 질의만 늘리고 답을 바꾸지 않는다.\n */\n if (!axisAppliesTo(a.axis, inst.kind)) {\n return {\n axis: a.axis,\n label: a.label,\n kind: a.kind,\n container: false,\n standardClass: a.standardClass,\n typeRole: a.typeRole ?? null,\n count: null,\n notApplicable: true,\n emptyBecause: 'not-applicable',\n /*\n * **그 개념의 성질** — 선언인가 관측인가 (2026-08-22).\n *\n * `basis` 와 다르다: `basis` 는 「이 값이 어디서 왔나」이고 트윈마다·시점마다 달라진다(같은\n * 축이 한 트윈에서 `journal`, 다른 트윈에서 `state` 로 온다). 그것으로 묶으면 같은 축이 화면\n * 마다 다른 묶음에 들어간다. `source` 는 트윈과 무관하게 고정이므로 묶는 축이 된다.\n *\n * 화면이 축 이름을 열거하지 않아도 되고, 축이 늘면 자동으로 맞는 묶음에 들어간다 —\n * 카탈로그가 정본이다.\n */\n source: axisSource(a.axis),\n basis: 'declaration'\n }\n }\n const entity = ROW_BACKED[a.axis]\n /* 문서 축만 문서로 센다 — 경로 없는 축을 세면 모델 루트를 훑는다(상태 축은 아래에서 센다). */\n const inDocument = axisSource(a.axis) === 'document' ? countInDocument(model, a.path) : 0\n const inRows = entity\n ? await getRepository(entity).count({ where: { domain: { id: domainId } as any, instance: { id: inst.id } as any } })\n : undefined\n /*\n * **둘을 함께 세고 어긋나면 말한다.**\n *\n * 행으로 옮긴 축이라도 그 트윈이 투영을 거치지 않았으면 행은 0이다(인제스트 없이\n * warm-start 된 트윈이 그렇다). 행만 보고 답하면 문서에 자리 6개가 있는데 화면은\n * **0 이라고 말한다** — 조용한 거짓이고, 이 프로젝트가 내내 잡아 온 바로 그 부류다.\n *\n * 그래서 어느 하나로 메우지 않는다. 행 수를 답하되 문서 수를 함께 내고, 다르면\n * `stale` 로 표시한다 — 그 차이 자체가 \"이 트윈은 투영 이후 인제스트되지 않았다\" 는 정보다.\n */\n const stale = entity !== undefined && inRows !== inDocument\n /*\n * **그릇인가** — 문서에 객체 하나로 있는 축(`productionSpec`).\n *\n * `kind` 로는 갈리지 않는다: 커널은 공정·레시피·라우트도 `spec` 이라 부른다(같은 선언 부류라는\n * 뜻이다). 그릇인지 아닌지는 **모양**이 말한다 — 목록이 되는 축은 배열이고, 한 벌뿐인 선언은\n * 객체 하나다. 개념 지도는 개체를 세는 면이므로 그릇은 자기 면에서 통째로 읽는다.\n */\n const raw = axisSource(a.axis) === 'document' ? at(model, a.path) : undefined\n /*\n * **관측 축은 상태가 센다**(일정·실적 — ISA-95 Part 4). 커널이 멈춰 있으면 `null` 이다:\n * 0 으로 내면 \"작업이 없는 공장\" 으로 읽히고, 그건 거짓이다(관측이 없는 것과 0 건은 다르다).\n * 이 화면이 관측 열에서 내내 지켜 온 구분을 카드에서도 지킨다.\n */\n /*\n * **「아직 없다」와 「왔는데 상태에 없다」는 다른 사실이다** (2026-08-22).\n *\n * 화면이 「작업 — 아직 하나도 없습니다」라고 말했는데 저널에는 `task.status` 5,786건이 있었고\n * 마지막 상태가 `in-progress` 였다(원인은 커널의 내보내기였고 그 레인에 넘겼다). 카드가 단정한\n * 탓에 사용자가 그 차이를 직접 조사해야 했다 — 화면이 아는 것을 말하지 않은 자리다.\n *\n * 그래서 상태가 빈 축은 저널을 **한 건만** 물어본다(색인 한 줄, `journal-count` 의 문).\n */\n if (axisSource(a.axis) === 'state') {\n /* 스냅샷은 `{revision, state}` 봉투다 — 벗기지 않으면 축이 한 겹 안에 있어 언제나 `null` 이\n 된다(커널은 도는데 카드가 \"모름\" 으로 보였다). 벗기는 규칙의 집은 `warm-start` 하나다. */\n /*\n * 관측 중인지는 **런타임이 있는지**로 본다 — 캐시된 스냅샷은 관측이 아니다.\n *\n * 동작하지 않으면 저널을 접어 답한다(라이브=권위 · 히스토리=파생). 카드는 그 차이를 `basis` 로\n * 말한다: 파생을 권위처럼 내면 다시 계산한 수를 \"지금 이만큼\" 으로 읽게 된다.\n */\n const { snap, folded, observing } = await stateOnce()\n /* 축이 사는 자리는 **커널 선언이 말한다**(`path`) — 축 이름으로만 읽으면 중첩된 축이\n 언제나 「관측 없음」이 된다(에너지의 수요 구간이 그렇다). 규칙은 `axisValueOf` 한 곳. */\n let bucket = snap ? axisValueOf(snap, a) ?? null : null\n let heardAt: string | null = null\n let fromJournal = false\n /*\n * 라이브가 잊은 축은 **저널에서 되세운다**(§`journalOnce`). 그 축의 사실이 저널에 있을 때만\n * 접고, 값과 함께 「언제 들은 값인가」를 낸다 — 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.\n */\n if (!folded && Array.isArray(bucket) && bucket.length === 0) {\n const seen = await axisSeenInJournal(getRepository(TwinEvent), { domain: { id: domainId }, instanceId }, a.axis).catch(() => null)\n if (seen) {\n const j = await journalOnce()\n const jb = j?.snap ? axisValueOf(j.snap, a) ?? null : null\n if (Array.isArray(jb) && jb.length) {\n bucket = jb\n heardAt = j?.heardAt ?? null\n fromJournal = true\n }\n }\n }\n return {\n axis: a.axis,\n label: a.label,\n kind: a.kind,\n container: false,\n standardClass: a.standardClass,\n typeRole: a.typeRole ?? null,\n count: Array.isArray(bucket) ? bucket.length : null,\n /*\n * **수 하나가 뜻을 바꾸는 자리** (2026-08-23).\n *\n * 미러가 완료된 작업까지 상태에 들고 있게 되면서 「작업 2,820」이 되었다. 이 카드는 상태의\n * 인구조사이므로 그 수가 맞다 — 그런데 사람은 그것을 「지금 할 일 2,820건」으로 읽는다.\n * 수를 줄여 거짓으로 만드는 대신 **무엇으로 이루어진 수인지** 함께 낸다.\n *\n * 낱말은 커널의 것을 그대로 쓴다(`created`·`assigned`·`in-progress`·`completed`). 화면이\n * 라벨을 입히고, 없으면 낱말이 그대로 보여 누락이 드러난다.\n */\n ...statusTally(bucket),\n /*\n * **비었으면 왜 비었는지 말한다.** 0 과 `null` 만 내면 화면은 \"없다\" 까지만 말하고, 사용자는\n * \"안 적어 놨나 · 아직 안 일어났나 · 못 세었나\" 를 구별할 수 없다. 이 화면이 관측 열에서\n * 내내 지켜 온 구분(§observedAbsence)을 카드에서도 지킨다.\n */\n ...(Array.isArray(bucket)\n ? bucket.length\n ? {}\n : { emptyBecause: await emptyStateReason(domainId, instanceId, a.axis) }\n : { emptyBecause: 'not-observed' }),\n source: axisSource(a.axis),\n basis: folded || fromJournal ? 'journal' : 'state',\n /*\n * **언제 들은 값인가** — 저널에서 되세운 축만 갖는다. 라이브가 답한 축은 지금이므로 없다.\n * 이 값이 없으면 화면이 낡은 수를 지금으로 읽는다(그 구별이 이 카드의 전부다).\n */\n ...(fromJournal && heardAt ? { heardAt, recalled: true } : {}),\n observing,\n /* 선언이 아니라 관측인 축 — 화면이 \"원본이 침묵했다\" 로 읽지 않게 사실로 말한다. */\n observedOnly: true,\n standardUndeclared: !a.standardClass?.isa95 && !a.standardClass?.epcis && !a.standardClass?.iso55000\n }\n }\n return {\n axis: a.axis,\n label: a.label,\n kind: a.kind,\n container: !Array.isArray(raw) && raw != null,\n standardClass: a.standardClass,\n typeRole: a.typeRole ?? null,\n count: entity ? inRows : inDocument,\n /*\n * 비었으면 왜 비었는지 — 선언 쪽은 세 갈래다.\n * · `not-declared` 이 트윈의 모델이 그 축을 아예 말하지 않는다(대개 템플릿이 안 적었다)\n * · `declared-empty` 선언은 있는데 목록이 비어 있다(적었다가 지운 것도 사실이다)\n * · `not-projected` 문서엔 있는데 행에 없다 — 투영을 거치지 않은 트윈이다(`stale` 과 같은 사실)\n *\n * 셋을 \"0\" 하나로 합치면 사용자가 손볼 곳을 찾지 못한다: 첫째는 템플릿, 둘째는 원본,\n * 셋째는 다시 읽기다. 셋 다 다른 일이다.\n */\n ...((entity ? inRows : inDocument)\n ? {}\n : {\n emptyBecause:\n entity && inDocument > 0 ? 'not-projected' : raw == null ? 'not-declared' : 'declared-empty'\n }),\n /* 'rows' = DB 가 셌다(질의 가능) · 'document' = 문서를 세었다(아직 이주 전). */\n source: axisSource(a.axis),\n basis: entity ? 'rows' : 'document',\n ...(entity ? { documentCount: inDocument, stale } : {}),\n /* 표준 투영이 없는 축은 **숨기지 않고** 그 사실을 낸다(ADR-0031). */\n standardUndeclared: !a.standardClass?.isa95 && !a.standardClass?.epcis && !a.standardClass?.iso55000\n }\n })\n )\n\n /*\n * **원본이 모르는데 실행 중인 것** — 커널은 마스터에 없던 자원도 이벤트로 알게 되고\n * `origin: 'observed'` 를 단다(`observed-reducer`). 그것을 표에 밀어 넣지 않는다 — 그러면\n * 틱 루프에서 DB 로 쓰는 **두 번째 쓰기 경로**가 생기고, 실패하면 조용히 어긋난다\n * (쓰기 경로는 인제스트 하나, ADR-0032-D).\n *\n * 대신 **여기서 비교해 낸다.** 이 차이는 숨겨야 할 불일치가 아니라 인스펙션이 답해야 할\n * 질문이다 — \"원본이 말한 적 없는 설비가 실제로 실행 중이다.\"\n *\n * 트윈이 안 실행 중이면 비교할 상대가 없다 — 그때는 `null` 이다(0 이 아니다. 없는 것과\n * 세어 보지 못한 것은 다르다).\n */\n /*\n * 도출은 **한 벌**이다(`engine/model-gap.ts`). 예전에는 이 자리에 인라인으로 있었는데, 같은 사실을\n * 「현장과 맞춰지고 있나」 화면도 물어야 해서 두 곳이 다른 수를 말할 위험이 생겼다.\n *\n * 이 화면은 **전체 목록**이 필요하다(인스펙션이니까). 그래서 상한 없는 원본을 여기서 만들고,\n * 상한이 걸린 요약은 `modelGapOf` 가 답한다 — 같은 판정(`origin === 'observed'`)을 쓴다.\n */\n const snap = TwinEngine.snapshot(domainId, instanceId)\n const gap = modelGapOf(snap)\n const observedOnly = snap\n ? {\n equipment: (snap.equipment ?? []).filter((e: any) => e?.origin === 'observed').map((e: any) => e.id),\n locations: (snap.locations ?? []).filter((l: any) => l?.origin === 'observed').map((l: any) => l.id),\n persons: (snap.persons ?? []).filter((p: any) => p?.origin === 'observed').map((p: any) => p.id),\n /* 커널이 담을 줄 몰라 세어 둔 사건·모르는 설비 보고 — 인스펙션이 이미 물을 자리다. */\n unhandled: gap?.unhandled ?? [],\n unknownEquipmentReports: gap?.unknownEquipmentReports ?? null,\n notCovered: gap?.notCovered ?? []\n }\n : null\n\n /* 지도의 선 — 선언된 관계 중 **양끝이 이 트윈에 실재하는 것만**. 없는 선을 그리지 않는다. */\n /* 선의 양끝 판정은 **문서든 행이든 있으면 있는 것**으로 본다 — 투영이 낡았다고 관계가 없어지진 않는다. */\n const nonEmpty = new Set(concepts.filter(c => (c.count ?? 0) > 0 || ((c as any).documentCount ?? 0) > 0).map(c => c.axis))\n /* 선은 **얼마나 이어졌고 얼마나 끊겼는지**까지 말한다 — 관계에서 가장 중요한 정보는 대개 부재다. */\n const edges = await Promise.all(\n TWIN_RELATIONS.filter((r: any) => r.target.kind === 'axis' && nonEmpty.has(r.from) && nonEmpty.has(r.target.axis)).map(\n async (r: any) => ({ from: r.from, to: r.target.axis, via: r.via, optional: !!r.optional, ...this.edgeFill(model, r) })\n )\n )\n\n return {\n instanceId,\n /* 사람이 부르는 이름 — 결선 줄이 업무키만 보이지 않게(업무키는 곁에 남는다). */\n name: inst.name ?? null,\n /*\n * **생산 선언에서 갈 곳이 없는 두 값** — GS1 발행자와 대표 산출물.\n *\n * 나머지 선언(자재·레시피·라우트·공정)은 각자 개념 축이 있고, 자재의 GS1 참조는 자재가 보인다.\n * 이 둘만 자기 축이 없다: 회사 접두사는 이 트윈의 **정체**(발행자)이고, 대표 레시피는 이 공장이\n * 무엇을 만드는 곳인가다. 둘 다 결선 줄(어느 원본을 반영하나)과 같은 성격이라 그 줄에 둔다.\n */\n /*\n * **표준 적합성** — 채운 것과 빠진 것을 함께.\n *\n * 이 화면은 \"이 트윈이 표준 모델의 어디를 채우고 있나\" 를 묻는다. 그런데 지금까지 답은 우리\n * 축만 세었다 — 없는 것은 화면에 없으므로 아무도 묻지 않았다. 기준(표준의 목록)을 함께 내면\n * 그 질문이 실제로 답을 갖는다(`isa95-coverage.ts` 의 머리말 참조).\n */\n /* 종류가 기대는 표준을 **모두** 낸다 — 하나만 말하면 그 트윈의 다른 기준이 보이지 않는다. */\n standards: standardsForKind(inst.kind).map(id => COVERAGE[id]((TWIN_AXES as any[]).map(a => a.axis))),\n production: (() => {\n const spec: any = (inst.model as any)?.productionSpec\n if (!spec) return null\n const key = spec.recipeKey ?? null\n const material = (spec.definition?.materials ?? []).find((m: any) => (m.key ?? m.id) === key)\n return {\n companyPrefix: spec.companyPrefix ?? null,\n recipeKey: key,\n /* 이름은 자재 선언이 말한다(i18n 키일 수 있으므로 번역은 화면의 일). */\n recipeLabel: material?.label ?? material?.name ?? null\n }\n })(),\n /*\n * ── **식별자가 무엇에 근거하나** (2026-08-24) ──────────────────────────────\n * 요약이 `production.companyPrefix` 를 그대로 보이는데 **그것이 발급받은 번호인지 말할 근거가\n * 없었다.** 그래서 사용자가 긴 URI 를 보고 「GS1 을 강제하는 것인가」를 물었다 — 강제는 **이미\n * 없고**(GS1 프리픽스가 없는 현장은 자기 도메인 아래에서 식별자를 만든다, CBV 2.0 §8.3.4)\n * 없다는 사실이 화면에 없었던 것이 문제였다.\n *\n * ★ **이 축만은 무표를 쓰지 않는다.** GS1 형태의 식별자를 본 사람은 그것이 진짜 발급받은\n * 번호라고 가정하므로, 표식이 없으면 그 가정이 곧 거짓 안심이 된다. 계약도 그렇게 적고 있다 —\n * 「없으면 소비처는 판정하지 않는다. 기본값으로 `issued` 를 가정하면 그 화면이 곧 거짓이 된다.」\n * 그래서 화면이 네 갈래 모두에 낱말을 붙일 수 있게 **그대로** 낸다\n * (`claimed-issued`·`declared-namespace`·`gs1-shaped`·`kernel-constant`).\n *\n * 커널이 내는 것을 그대로 넘긴다 — 여기서 다시 판정하면 스냅샷과 요약이 다른 말을 한다.\n * 트윈이 돌지 않거나 커널이 말하지 않으면 `null` 이고, 그때 화면은 **재지 않는다.**\n */\n identityGrounding: (await stateOnce()).snap?.identityGrounding ?? null,\n description: inst.description ?? null,\n kind: inst.kind,\n restartPolicy: inst.restartPolicy,\n source: ref\n ? {\n referenceId: ref.source,\n adapterType: ref.adapterType,\n status: ref.status,\n lastSyncedAt: ref.lastSyncedAt,\n /*\n * ── **원본의 어느 흐름이 어디까지 살아 있나** (2026-08-24) ──────────────\n * 사용자가 「지금 데이터가 들어오는 것이 사실이냐」고 물었고, 화면이 답할 것이 「붙어 있다」\n * 뿐이었다. 그런데 한 원본 안에서 흐름마다 신선도가 **크게 다르다**(실측: 재고 08-23 ·\n * 계측 08-12 · 작업지시 05-10 · 공정·로트 04-15). 「붙어 있다」 하나로 뭉치면 넉 달 멈춘\n * 흐름이 살아 있는 것으로 읽힌다.\n *\n * **계약에는 자리가 있다** — `LiveFeedCursor.since` 가 「그 흐름에서 마지막으로 본 시각\n * (원본의 시각)」이고 `onCursor` 가 그것을 `TwinReference.liveCursor` 에 적는다. 없던 것은\n * **화면으로 가는 문**이고, 그 문이 여기다.\n *\n * ★ **다만 지금은 비어 있다** — 실 저장값을 확인했다:\n * `{\"streams\":{},\"firstAttachedAt\":\"2026-03-25T…\"}`. 즉 어댑터가 흐름별 커서를 적지 않는다.\n * 그래서 이 문을 열어도 당장은 빈 목록이 나오고, **그것이 정직한 답이다**: 어댑터가 말하지\n * 않는 것을 여기서 지어내지 않는다. 채우는 것은 어댑터의 몫이고 그쪽에 알렸다.\n *\n * 빈 목록과 「흐름이 없다」를 화면이 구별할 수 있어야 한다 — 이 값이 비면 그것은\n * 「어댑터가 아직 말하지 않는다」이고 「원본에 흐름이 없다」가 아니다.\n *\n * `seen`(중복 걸러낸 행 id)은 내지 않는다 — 읽기 내부 상태이고 수천 개가 될 수 있다.\n * 흐름 열쇠는 **어댑터가 정한다**(계약이 이름을 닫지 않는다). 그래서 이름을 번역하지 않고\n * 그대로 낸다 — 화면이 모르는 이름을 만나면 그 이름을 보이는 것이 맞다.\n */\n streams: Object.entries((ref.liveCursor as any)?.streams ?? {})\n .map(([stream, c]: [string, any]) => ({ stream, since: c?.since ?? null }))\n /* 시각을 말하지 않는 흐름도 낸다 — 「모른다」와 「없다」는 다르고, 빼면 그 흐름이 사라진다. */\n .sort((a, b) => String(b.since ?? '').localeCompare(String(a.since ?? ''))),\n firstAttachedAt: (ref.liveCursor as any)?.firstAttachedAt ?? null\n }\n : null,\n /*\n * **다시 읽을 수 있는가** — 이 트윈이 자기 원본을 기억하고 있나(`TwinInstance.origin`).\n *\n * 없는 트윈이 실제로 있다(이 자리가 생기기 전에 만들어진 것). 그때 화면이 버튼을 그대로\n * 내면 누를 때마다 실패하고, 사용자는 무엇이 잘못됐는지 알 수 없다. 그래서 **할 수 있는지를\n * 먼저 답한다** — 못 하는 것을 못 한다고 말하는 것도 이 화면의 일이다.\n */\n origin: inst.origin ?? null,\n canResync: !!(inst.origin as any)?.kind,\n concepts,\n edges,\n /* 원본이 모르는데 관측에는 있는 것. `null` = 트윈이 실행 중이 아니어서 비교하지 못했다. */\n observedOnly,\n /*\n * 축 셋만 행이고 나머지는 문서에서 셌다는 사실. 축마다 `basis` 가 있지만 여기서도 한 번 말한다 —\n * 화면이 \"이 숫자는 무엇으로 셌나\" 를 한눈에 보여야 규모 한계가 드러난다(ADR-0032 이주 압력).\n */\n basis: {\n rows: Object.keys(ROW_BACKED),\n document: TWIN_AXES.filter((a: any) => !ROW_BACKED[a.axis]).map((a: any) => a.axis),\n /* 투영이 낡은 축 — 있으면 이 트윈은 인제스트를 한 번 더 태워야 한다. */\n stale: concepts.filter((c: any) => c.stale).map((c: any) => c.axis)\n }\n }\n }\n /**\n * **이 트윈에 무엇을 선언할 수 있나** — 값을 넣는 화면이 도메인의 모양을 알지 않게.\n *\n * 커널이 정한 명세(속성 id·단위·범위·설명)를 그대로 낸다. 화면이 「에너지 트윈이면 계약전력·요금\n * 단가…」를 알고 있으면 도메인이 늘 때마다 화면을 고쳐야 하고 단위가 두 곳에 적힌다(ADR-0031).\n *\n * 지금 무엇이 선언돼 있는지도 함께 낸다(`declared`) — 화면이 두 번 묻지 않게, 그리고 「원천이 말한\n * 값」과 「우리가 정한 값」을 한자리에서 견줄 수 있게(`PropertySource`, ADR-0034).\n *\n * 어휘가 없는 종류는 `properties: []` 다 — 「아직 없다」와 「아무 값이나 받는다」는 다르다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n \"What can be declared on this twin and what already is: the kernel's property specification (id, unit, range, note) plus the current local declarations at both scopes — this twin and its facility — each with the value it overrode. Empty properties mean this kind has no declaration vocabulary yet — not that anything goes.\"\n })\n async twinDeclarations(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n const model = (inst.model ?? {}) as any\n /*\n * 겹이 둘이다 — **현장의 것과 이 트윈의 것**. 화면이 둘을 함께 봐야 「이 값이 어느 범위의 것인가」를\n * 말할 수 있고, 철회 버튼이 어느 겹을 겨눌지 고를 수 있다(요금을 현장에 넣었는데 트윈에서 철회하려\n * 하면 「겹에 없다」로 끝난다 — 사용자는 이유를 알 수 없다).\n */\n const space = inst.spaceId\n ? await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId: inst.spaceId } })\n : null\n /*\n * 돌고 있으면 커널이 「무엇으로 돌고 있나」를 스스로 답한다. 그 보고는 **작업이 새로 생길 때** 채워지\n * 므로 재기동 직후에는 비는데, 이력에서 배운 종류는 그때도 남아 있다(실측으로 이 빈칸에 부딪혔다).\n */\n const operations = operationBasis(\n model,\n TwinEngine.kernel(domainId, instanceId),\n await TwinEngine.measuredOperationKinds(domainId, instanceId)\n )\n return {\n instanceId,\n kind: inst.kind,\n spaceId: inst.spaceId,\n spaceName: space?.name,\n properties: declarablePropertySpecs(inst.kind),\n declared: (inst.localDeclarations ?? []) as any[],\n declaredAtSpace: (space?.localDeclarations ?? []) as any[],\n /* 어디에 얹을 수 있나 — 모델의 자리·설비·공정. 화면이 대상을 지어내지 않게 목록으로 준다. */\n targets: [\n ...(model.locations ?? []).map((l: any) => ({ target: 'location', id: l.id, name: l.name, type: l.type, properties: l.properties ?? [] })),\n ...(readBoardEquipment(model) ?? []).map((e: any) => ({ target: 'equipment', id: e.id, name: e.name, type: e.type ?? e.kind, properties: e.properties ?? [] })),\n /*\n * 공정은 **자원이 아니다** — 값은 종류에 붙고, 그 종류에 명세 행이 없어도 된다(창고·야드에는 행이\n * 0개다). 그래서 대상 목록을 커널의 자기보고까지 합쳐 만든다(`operationBasis`).\n *\n * 지금 값은 **겹에서** 든다(사용자가 넣은 수와 단위 그대로). 모델에는 ISO 8601 로 구워지지만\n * 화면에 「PT240S」를 보이면 자기가 넣은 값인지 알아볼 수 없다.\n */\n ...operations.map(op => ({\n target: 'operation',\n id: op.kind,\n name: op.label,\n properties: declaredDurationProperty(op.kind, inst.localDeclarations as any[], space?.localDeclarations as any[])\n }))\n ],\n /*\n * 이 트윈이 돌리는 공정과 **그 시간이 어디서 오나** — 선언 화면이 「무엇이 상수로 도나」를 보여야\n * 사람이 채울 곳을 안다. `basis` 가 비면 아직 돌지 않은 것이다(공정이 없는 것이 아니다).\n */\n operations\n }\n }\n /**\n * **이 현장에 무엇을 선언할 수 있나** — 현장 값은 트윈의 것이 아니므로 트윈 없이 묻는다.\n *\n * ── 왜 트윈으로 묻지 않나 (2026-08-18) ─────────────────────────────────────\n * 처음에는 트윈 화면에서 범위만 골라 현장 값을 넣게 했다. 그런데 **현장 값을 넣으려고 트윈을 골라야\n * 하는 것**은 개념이 거꾸로다 — 트윈은 그 값을 읽는 쪽이다(사용자가 그 어긋남을 지적했다).\n *\n * 어휘와 대상은 **그 현장 트윈들의 합집합**이다: 한 현장에 여러 종류가 살고(에너지·창고·야드), 요금은\n * 에너지 트윈만 읽는다. 합집합으로 내되 **어느 트윈이 그 대상을 가졌는지** 함께 낸다 — 아무 트윈도\n * 읽지 않는 값을 넣는 일을 화면이 미리 보여 줄 수 있어야 한다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'What can be declared for a whole facility (space) and what already is: the union of the property specifications of the twin kinds living there, the union of their declarable targets (with which twins carry each), and the current facility-scope declarations.'\n })\n async spaceDeclarations(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return null\n const twins = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, spaceId } })\n\n /* 어휘 합집합 — 같은 속성이 여러 종류에 있어도 한 번만 낸다(명세는 커널 하나에서 온다). */\n const byId = new Map<string, any>()\n for (const t of twins) for (const spec of declarablePropertySpecs(t.kind)) if (!byId.has(spec.id)) byId.set(spec.id, spec)\n\n /* 대상 합집합 — 같은 id 가 여러 트윈에 있으면 **그 트윈들을 함께** 든다(현장 값은 그 전부에 얹힌다). */\n const targets = new Map<string, { target: string; id: string; name?: string; type?: string; twins: string[] }>()\n for (const t of twins) {\n const model = (t.model ?? {}) as any\n const add = (target: 'location' | 'equipment' | 'operation', e: any) => {\n const key = `${target}:${e.id}`\n const cur = targets.get(key)\n if (cur) { cur.twins.push(t.instanceId); return }\n targets.set(key, { target, id: e.id, name: e.name, type: e.type ?? e.kind, twins: [t.instanceId] })\n }\n for (const l of model.locations ?? []) add('location', l)\n for (const e of readBoardEquipment(model) ?? []) add('equipment', e)\n /*\n * 공정도 대상이다 — 「우리 현장의 적재는 4분」은 그 현장 창고 전부가 읽어야 하는 수다. 목록은 각\n * 트윈의 것을 합집합으로 든다(돌고 있으면 커널이 스스로 답한다).\n */\n for (const op of operationBasis(\n model,\n TwinEngine.kernel(domainId, t.instanceId),\n await TwinEngine.measuredOperationKinds(domainId, t.instanceId)\n ))\n add('operation', { id: op.kind, name: op.label })\n }\n\n return {\n spaceId,\n spaceName: space.name,\n properties: [...byId.values()],\n declared: (space.localDeclarations ?? []) as any[],\n targets: [...targets.values()],\n /* 이 현장에 사는 트윈들 — 화면이 「누가 이 값을 읽나」를 말할 수 있게. */\n twins: twins.map(t => ({ instanceId: t.instanceId, kind: t.kind, name: t.name }))\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/headless-twin",
3
- "version": "10.0.14",
3
+ "version": "10.0.15",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -27,11 +27,11 @@
27
27
  "clean:shared": "rm -rf dist-shared tsconfig.shared.tsbuildinfo"
28
28
  },
29
29
  "dependencies": {
30
- "@operato/twin-kernel": "^0.7.57",
30
+ "@operato/twin-kernel": "^0.7.61",
31
31
  "@things-factory/auth-base": "^10.0.13",
32
32
  "@things-factory/cache-service": "^10.0.13",
33
33
  "@things-factory/env": "^10.0.8",
34
34
  "@things-factory/shell": "^10.0.13"
35
35
  },
36
- "gitHead": "539b63810538d7ce5428c2a2c581f6dcac3bb0b3"
36
+ "gitHead": "18c7b66990821c6acda5d3c8e6149d518978e133"
37
37
  }