@things-factory/headless-twin 10.0.8 → 10.0.10

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 (381) hide show
  1. package/dist-server/engine/attention-digest.d.ts +52 -0
  2. package/dist-server/engine/attention-digest.js +76 -0
  3. package/dist-server/engine/attention-digest.js.map +1 -0
  4. package/dist-server/engine/canonical-ingest.d.ts +2 -2
  5. package/dist-server/engine/canonical-ingest.js +24 -2
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.d.ts +33 -0
  8. package/dist-server/engine/command-routing.js +53 -0
  9. package/dist-server/engine/command-routing.js.map +1 -0
  10. package/dist-server/engine/energy-topology.d.ts +86 -0
  11. package/dist-server/engine/energy-topology.js +144 -0
  12. package/dist-server/engine/energy-topology.js.map +1 -0
  13. package/dist-server/engine/index.d.ts +11 -0
  14. package/dist-server/engine/index.js +17 -0
  15. package/dist-server/engine/index.js.map +1 -1
  16. package/dist-server/engine/kpi-baseline.d.ts +78 -0
  17. package/dist-server/engine/kpi-baseline.js +123 -0
  18. package/dist-server/engine/kpi-baseline.js.map +1 -0
  19. package/dist-server/engine/kpi-fold.d.ts +137 -1
  20. package/dist-server/engine/kpi-fold.js +225 -0
  21. package/dist-server/engine/kpi-fold.js.map +1 -1
  22. package/dist-server/engine/kpi-query.d.ts +32 -1
  23. package/dist-server/engine/kpi-query.js +262 -20
  24. package/dist-server/engine/kpi-query.js.map +1 -1
  25. package/dist-server/engine/kpi-target.d.ts +17 -0
  26. package/dist-server/engine/kpi-target.js +24 -2
  27. package/dist-server/engine/kpi-target.js.map +1 -1
  28. package/dist-server/engine/live-attentions.d.ts +1 -0
  29. package/dist-server/engine/live-attentions.js +7 -1
  30. package/dist-server/engine/live-attentions.js.map +1 -1
  31. package/dist-server/engine/live-feed-registry.d.ts +25 -0
  32. package/dist-server/engine/live-feed-registry.js +51 -0
  33. package/dist-server/engine/live-feed-registry.js.map +1 -0
  34. package/dist-server/engine/load-meter.d.ts +181 -0
  35. package/dist-server/engine/load-meter.js +267 -0
  36. package/dist-server/engine/load-meter.js.map +1 -0
  37. package/dist-server/engine/local-declarations.d.ts +262 -0
  38. package/dist-server/engine/local-declarations.js +528 -0
  39. package/dist-server/engine/local-declarations.js.map +1 -0
  40. package/dist-server/engine/model-basis.d.ts +39 -6
  41. package/dist-server/engine/model-basis.js +69 -9
  42. package/dist-server/engine/model-basis.js.map +1 -1
  43. package/dist-server/engine/model-vocabulary.d.ts +17 -0
  44. package/dist-server/engine/model-vocabulary.js +81 -0
  45. package/dist-server/engine/model-vocabulary.js.map +1 -0
  46. package/dist-server/engine/oee-accumulator.d.ts +28 -0
  47. package/dist-server/engine/oee-accumulator.js +26 -1
  48. package/dist-server/engine/oee-accumulator.js.map +1 -1
  49. package/dist-server/engine/operation-basis.d.ts +32 -0
  50. package/dist-server/engine/operation-basis.js +87 -0
  51. package/dist-server/engine/operation-basis.js.map +1 -0
  52. package/dist-server/engine/property-effects.d.ts +30 -0
  53. package/dist-server/engine/property-effects.js +192 -0
  54. package/dist-server/engine/property-effects.js.map +1 -0
  55. package/dist-server/engine/runtime-key.d.ts +15 -0
  56. package/dist-server/engine/runtime-key.js +64 -0
  57. package/dist-server/engine/runtime-key.js.map +1 -0
  58. package/dist-server/engine/spec-coverage.js +3 -3
  59. package/dist-server/engine/spec-coverage.js.map +1 -1
  60. package/dist-server/engine/state-axes.d.ts +21 -0
  61. package/dist-server/engine/state-axes.js +59 -0
  62. package/dist-server/engine/state-axes.js.map +1 -0
  63. package/dist-server/engine/structure-diff.js +1 -1
  64. package/dist-server/engine/structure-diff.js.map +1 -1
  65. package/dist-server/engine/travel-estimator.d.ts +9 -2
  66. package/dist-server/engine/travel-estimator.js +11 -6
  67. package/dist-server/engine/travel-estimator.js.map +1 -1
  68. package/dist-server/engine/twin-engine.d.ts +418 -57
  69. package/dist-server/engine/twin-engine.js +1599 -306
  70. package/dist-server/engine/twin-engine.js.map +1 -1
  71. package/dist-server/engine/warm-start.d.ts +93 -13
  72. package/dist-server/engine/warm-start.js +114 -14
  73. package/dist-server/engine/warm-start.js.map +1 -1
  74. package/dist-server/index.d.ts +1 -0
  75. package/dist-server/index.js +17 -11
  76. package/dist-server/index.js.map +1 -1
  77. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
  78. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
  79. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
  80. package/dist-server/migrations/index.d.ts +2 -0
  81. package/dist-server/migrations/index.js +10 -0
  82. package/dist-server/migrations/index.js.map +1 -0
  83. package/dist-server/service/index.d.ts +3 -2
  84. package/dist-server/service/index.js +25 -17
  85. package/dist-server/service/index.js.map +1 -1
  86. package/dist-server/service/reference/discovery-result.d.ts +34 -0
  87. package/dist-server/service/reference/discovery-result.js +84 -0
  88. package/dist-server/service/reference/discovery-result.js.map +1 -0
  89. package/dist-server/service/reference/ingest-space.d.ts +53 -0
  90. package/dist-server/service/reference/ingest-space.js +79 -0
  91. package/dist-server/service/reference/ingest-space.js.map +1 -0
  92. package/dist-server/service/reference/knob-defaults.d.ts +20 -0
  93. package/dist-server/service/reference/knob-defaults.js +59 -0
  94. package/dist-server/service/reference/knob-defaults.js.map +1 -0
  95. package/dist-server/service/reference/reference-live.d.ts +23 -0
  96. package/dist-server/service/reference/reference-live.js +123 -6
  97. package/dist-server/service/reference/reference-live.js.map +1 -1
  98. package/dist-server/service/reference/reference-master.d.ts +227 -11
  99. package/dist-server/service/reference/reference-master.js +206 -25
  100. package/dist-server/service/reference/reference-master.js.map +1 -1
  101. package/dist-server/service/reference/reference-resolver.d.ts +33 -3
  102. package/dist-server/service/reference/reference-resolver.js +290 -23
  103. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  104. package/dist-server/service/reference/template-registry.d.ts +19 -3
  105. package/dist-server/service/reference/template-registry.js.map +1 -1
  106. package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
  107. package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
  108. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  109. package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
  110. package/dist-server/service/twin-audit/command-audit.js +53 -0
  111. package/dist-server/service/twin-audit/command-audit.js.map +1 -0
  112. package/dist-server/service/twin-audit/index.d.ts +4 -0
  113. package/dist-server/service/twin-audit/index.js +8 -0
  114. package/dist-server/service/twin-audit/index.js.map +1 -0
  115. package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
  116. package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
  117. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
  118. package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
  119. package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
  120. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
  121. package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
  122. package/dist-server/service/twin-control/twin-control-mutation.js +50 -11
  123. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  124. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  125. package/dist-server/service/twin-event/twin-event-keys.js +1 -1
  126. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  127. package/dist-server/service/twin-event/twin-event.d.ts +14 -5
  128. package/dist-server/service/twin-event/twin-event.js +46 -14
  129. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  130. package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
  131. package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
  132. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
  133. package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
  134. package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
  135. package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
  136. package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
  137. package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
  138. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  139. package/dist-server/service/twin-forecast/twin-forecast-query.js +219 -39
  140. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  141. package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
  142. package/dist-server/service/twin-instance/twin-instance.js +66 -18
  143. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  144. package/dist-server/service/twin-journal/twin-journal-query.d.ts +11 -4
  145. package/dist-server/service/twin-journal/twin-journal-query.js +95 -15
  146. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  147. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +11 -5
  148. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +46 -27
  149. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  150. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  151. package/dist-server/service/twin-metrics/twin-metrics-query.js +63 -1
  152. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  153. package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
  154. package/dist-server/service/twin-model/epcis-coverage.js +174 -0
  155. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
  156. package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
  157. package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
  158. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
  159. package/dist-server/service/twin-model/index.d.ts +13 -0
  160. package/dist-server/service/twin-model/index.js +29 -0
  161. package/dist-server/service/twin-model/index.js.map +1 -0
  162. package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
  163. package/dist-server/service/twin-model/isa95-coverage.js +155 -0
  164. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
  165. package/dist-server/service/twin-model/project-structure.d.ts +16 -0
  166. package/dist-server/service/twin-model/project-structure.js +173 -0
  167. package/dist-server/service/twin-model/project-structure.js.map +1 -0
  168. package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
  169. package/dist-server/service/twin-model/standard-coverage.js +37 -0
  170. package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
  171. package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
  172. package/dist-server/service/twin-model/twin-equipment.js +134 -0
  173. package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
  174. package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
  175. package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
  176. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
  177. package/dist-server/service/twin-model/twin-location.d.ts +58 -0
  178. package/dist-server/service/twin-model/twin-location.js +130 -0
  179. package/dist-server/service/twin-model/twin-location.js.map +1 -0
  180. package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
  181. package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
  182. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
  183. package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
  184. package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
  185. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
  186. package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
  187. package/dist-server/service/twin-model/twin-model-query.js +551 -0
  188. package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
  189. package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
  190. package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
  191. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
  192. package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
  193. package/dist-server/service/twin-model/twin-operation.js +137 -0
  194. package/dist-server/service/twin-model/twin-operation.js.map +1 -0
  195. package/dist-server/service/twin-space/move-space.d.ts +65 -0
  196. package/dist-server/service/twin-space/move-space.js +166 -0
  197. package/dist-server/service/twin-space/move-space.js.map +1 -0
  198. package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
  199. package/dist-server/service/twin-space/space-integrity.js +182 -0
  200. package/dist-server/service/twin-space/space-integrity.js.map +1 -0
  201. package/dist-server/service/twin-space/twin-space-area.js +4 -4
  202. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  203. package/dist-server/service/twin-space/twin-space-representation.js +6 -6
  204. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  205. package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
  206. package/dist-server/service/twin-space/twin-space-resolver.js +365 -18
  207. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  208. package/dist-server/service/twin-space/twin-space.d.ts +17 -1
  209. package/dist-server/service/twin-space/twin-space.js +15 -5
  210. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  211. package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
  212. package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
  213. package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
  214. package/dist-server/service/twin-structure/twin-structure.js +8 -7
  215. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  216. package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
  217. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  218. package/dist-shared/axis-read.d.ts +12 -0
  219. package/dist-shared/axis-read.js +32 -0
  220. package/dist-shared/axis-read.js.map +1 -0
  221. package/dist-shared/entity-delta.d.ts +29 -0
  222. package/dist-shared/entity-delta.js +287 -0
  223. package/dist-shared/entity-delta.js.map +1 -0
  224. package/dist-shared/kpi-broadcast.d.ts +4 -0
  225. package/dist-shared/kpi-broadcast.js +16 -0
  226. package/dist-shared/kpi-broadcast.js.map +1 -0
  227. package/dist-shared/twin-level.d.ts +23 -0
  228. package/dist-shared/twin-level.js +52 -0
  229. package/dist-shared/twin-level.js.map +1 -0
  230. package/package.json +14 -12
  231. package/server/engine/attention-digest.ts +102 -0
  232. package/server/engine/canonical-ingest.ts +36 -4
  233. package/server/engine/command-routing.ts +67 -0
  234. package/server/engine/energy-topology.ts +189 -0
  235. package/server/engine/index.ts +17 -0
  236. package/server/engine/kpi-baseline.ts +202 -0
  237. package/server/engine/kpi-fold.ts +389 -1
  238. package/server/engine/kpi-query.ts +295 -22
  239. package/server/engine/kpi-target.ts +24 -2
  240. package/server/engine/live-attentions.ts +7 -2
  241. package/server/engine/live-feed-registry.ts +58 -0
  242. package/server/engine/load-meter.ts +384 -0
  243. package/server/engine/local-declarations.ts +700 -0
  244. package/server/engine/model-basis.ts +78 -10
  245. package/server/engine/model-vocabulary.ts +82 -0
  246. package/server/engine/oee-accumulator.ts +34 -1
  247. package/server/engine/operation-basis.ts +100 -0
  248. package/server/engine/property-effects.ts +199 -0
  249. package/server/engine/runtime-key.ts +58 -0
  250. package/server/engine/spec-coverage.ts +3 -3
  251. package/server/engine/state-axes.ts +60 -0
  252. package/server/engine/structure-diff.ts +1 -1
  253. package/server/engine/travel-estimator.ts +11 -6
  254. package/server/engine/twin-engine.ts +1710 -297
  255. package/server/engine/warm-start.ts +207 -22
  256. package/server/index.ts +13 -10
  257. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
  258. package/server/migrations/index.ts +7 -0
  259. package/server/service/index.ts +9 -1
  260. package/server/service/reference/discovery-result.ts +95 -0
  261. package/server/service/reference/ingest-space.ts +104 -0
  262. package/server/service/reference/knob-defaults.ts +59 -0
  263. package/server/service/reference/reference-live.ts +121 -6
  264. package/server/service/reference/reference-master.ts +362 -35
  265. package/server/service/reference/reference-resolver.ts +307 -23
  266. package/server/service/reference/template-registry.ts +20 -5
  267. package/server/service/twin-attention/twin-attention-query.ts +43 -6
  268. package/server/service/twin-audit/command-audit.ts +81 -0
  269. package/server/service/twin-audit/index.ts +5 -0
  270. package/server/service/twin-audit/twin-audit-event.ts +112 -0
  271. package/server/service/twin-audit/twin-audit-query.ts +72 -0
  272. package/server/service/twin-control/twin-control-mutation.ts +53 -13
  273. package/server/service/twin-event/twin-event-keys.ts +1 -1
  274. package/server/service/twin-event/twin-event.ts +52 -15
  275. package/server/service/twin-forecast/forecast-metrics.ts +60 -0
  276. package/server/service/twin-forecast/forecast-tuning.ts +40 -0
  277. package/server/service/twin-forecast/gap-analytics.ts +48 -9
  278. package/server/service/twin-forecast/twin-forecast-query.ts +211 -38
  279. package/server/service/twin-instance/twin-instance.ts +127 -22
  280. package/server/service/twin-journal/twin-journal-query.ts +108 -14
  281. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +40 -23
  282. package/server/service/twin-metrics/twin-metrics-query.ts +60 -3
  283. package/server/service/twin-model/epcis-coverage.ts +195 -0
  284. package/server/service/twin-model/iec61850-coverage.ts +117 -0
  285. package/server/service/twin-model/index.ts +25 -0
  286. package/server/service/twin-model/isa95-coverage.ts +190 -0
  287. package/server/service/twin-model/project-structure.ts +199 -0
  288. package/server/service/twin-model/standard-coverage.ts +35 -0
  289. package/server/service/twin-model/twin-equipment.ts +150 -0
  290. package/server/service/twin-model/twin-lineage-query.ts +193 -0
  291. package/server/service/twin-model/twin-location.ts +140 -0
  292. package/server/service/twin-model/twin-model-item-query.ts +848 -0
  293. package/server/service/twin-model/twin-model-mutation.ts +503 -0
  294. package/server/service/twin-model/twin-model-query.ts +537 -0
  295. package/server/service/twin-model/twin-model-tree-query.ts +180 -0
  296. package/server/service/twin-model/twin-operation.ts +150 -0
  297. package/server/service/twin-space/move-space.ts +262 -0
  298. package/server/service/twin-space/space-integrity.ts +293 -0
  299. package/server/service/twin-space/twin-space-area.ts +4 -4
  300. package/server/service/twin-space/twin-space-representation.ts +6 -6
  301. package/server/service/twin-space/twin-space-resolver.ts +365 -22
  302. package/server/service/twin-space/twin-space.ts +40 -6
  303. package/server/service/twin-state/twin-state-subscription.ts +1 -1
  304. package/server/service/twin-structure/twin-structure.ts +10 -7
  305. package/server/service/twin-target/twin-target-resolver.ts +22 -4
  306. package/shared/axis-read.ts +28 -0
  307. package/shared/entity-delta.ts +286 -0
  308. package/shared/kpi-broadcast.ts +13 -0
  309. package/shared/twin-level.ts +48 -0
  310. package/test/adopt-structure-live.test.ts +133 -0
  311. package/test/attention-digest.test.ts +135 -0
  312. package/test/axis-read.test.ts +87 -0
  313. package/test/boot-resume.test.ts +205 -0
  314. package/test/canonical-ingest-vocabularies.test.ts +118 -0
  315. package/test/capability-mapping.test.ts +5 -5
  316. package/test/command-routing.test.ts +61 -0
  317. package/test/declaration-reaches-model.test.ts +178 -0
  318. package/test/discovery-result.test.ts +75 -0
  319. package/test/energy-topology.test.ts +113 -0
  320. package/test/entity-delta.test.ts +195 -25
  321. package/test/event-time-column.test.ts +67 -0
  322. package/test/forecast-metrics.test.ts +71 -0
  323. package/test/forecast-tuning.test.ts +42 -0
  324. package/test/gap-analytics.test.ts +34 -7
  325. package/test/ingest-bench.test.ts +9 -9
  326. package/test/ingest-running-guard.test.ts +136 -0
  327. package/test/ingest-space.test.ts +78 -0
  328. package/test/instance-cache-lifecycle.test.ts +110 -0
  329. package/test/kernel-kind-guard.test.ts +96 -0
  330. package/test/knob-defaults.test.ts +72 -0
  331. package/test/kpi-baseline-db.test.ts +216 -0
  332. package/test/kpi-baseline.test.ts +196 -0
  333. package/test/kpi-fold.test.ts +393 -2
  334. package/test/kpi-query-bench.test.ts +130 -0
  335. package/test/lineage-survives-restart.test.ts +203 -0
  336. package/test/live-feed-registry.test.ts +67 -0
  337. package/test/live-kernel-facts.test.ts +161 -0
  338. package/test/live-mirror-parity.test.ts +54 -14
  339. package/test/load-meter.test.ts +314 -0
  340. package/test/local-declarations.test.ts +554 -0
  341. package/test/master-to-twin.test.ts +128 -36
  342. package/test/model-basis.test.ts +56 -1
  343. package/test/model-vocabulary.test.ts +114 -0
  344. package/test/move-space.test.ts +149 -0
  345. package/test/mutation-gate.test.ts +122 -0
  346. package/test/oee-accumulator.test.ts +90 -7
  347. package/test/operation-basis.test.ts +100 -0
  348. package/test/operations-capability-db.test.ts +165 -0
  349. package/test/project-structure-db.test.ts +226 -0
  350. package/test/projection-reaches-screen.test.ts +272 -0
  351. package/test/property-effects.test.ts +96 -0
  352. package/test/registry-key-guard.test.ts +80 -0
  353. package/test/resync-origin-site.test.ts +77 -0
  354. package/test/runtime-key.test.ts +66 -0
  355. package/test/scale-twin-bench.test.ts +7 -7
  356. package/test/snapshot-freshness.test.ts +60 -0
  357. package/test/space-integrity.test.ts +223 -0
  358. package/test/standard-coverage.test.ts +94 -0
  359. package/test/state-axes.test.ts +74 -0
  360. package/test/streamline-e2e.test.ts +16 -16
  361. package/test/structure-revision-db.test.ts +16 -15
  362. package/test/tenant-registry-db.test.ts +149 -0
  363. package/test/twin-audit.test.ts +83 -0
  364. package/test/twin-model-item-db.test.ts +275 -0
  365. package/test/twin-model-tree-db.test.ts +176 -0
  366. package/test/twin-origin-resync.test.ts +114 -0
  367. package/test/warm-start-seam.test.ts +140 -0
  368. package/test/warm-start.test.ts +223 -4
  369. package/tsconfig.json +6 -1
  370. package/tsconfig.shared.json +23 -0
  371. package/tsconfig.shared.tsbuildinfo +1 -0
  372. package/tsconfig.tsbuildinfo +1 -0
  373. package/dist-server/engine/entity-delta.d.ts +0 -19
  374. package/dist-server/engine/entity-delta.js +0 -161
  375. package/dist-server/engine/entity-delta.js.map +0 -1
  376. package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
  377. package/dist-server/service/twin-event/backfill-keys.js +0 -63
  378. package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
  379. package/dist-server/tsconfig.tsbuildinfo +0 -1
  380. package/server/engine/entity-delta.ts +0 -169
  381. package/server/service/twin-event/backfill-keys.ts +0 -72
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ /*
3
+ * 이미 있는 트윈을 다른 현장으로 옮긴다 — **판정만**. 순수.
4
+ *
5
+ * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────
6
+ * 트윈↔현장 연결은 **만들 때만** 정할 수 있었다(템플릿 위자드·import 의 `spaceId`). 잘못 넣었거나,
7
+ * 나중에 "이 창고는 사실 저 공장과 같은 부지다" 를 알게 되면 지우고 다시 만드는 것이 유일한 길이었다 —
8
+ * 다시 만들면 그 트윈의 저널(일어난 일)이 사라진다. 고칠 수 있는 실수를 데이터 손실로 갚게 하는 설계다.
9
+ *
10
+ * ── 옮기는 것이 무엇을 옮기나 (엔티티 키를 보고 정했다) ──────────────────────
11
+ * 무엇이 트윈에 매여 있고 무엇이 공간에 매여 있는지가 답을 정한다:
12
+ *
13
+ * · `TwinEvent` `(domain, instanceId, …)` → **따라온다.** 저널을 다시 쓰지 않는다.
14
+ * · `TwinLocation/Equipment/Operation` `(domain, instance, …)` → **따라온다.**
15
+ * · `TwinArea` `(domain, space, areaId)` → **남는다.** 구역은 공간의 것이다.
16
+ * · `TwinSpaceRepresentation`(지도·이미지·보드) → **남는다.** 표현도 공간의 것이다.
17
+ *
18
+ * 그래서 옮기기는 이력 이전이 아니라 **소속 갱신**이다(`instance.spaceId` + `model.spaceId`).
19
+ * 대신 공간에 매인 것들과의 연결이 끊길 수 있고, 그것을 **조용히 넘기지 않는 것**이 이 모듈의 일이다.
20
+ *
21
+ * ── 거절과 경고를 가른다 ────────────────────────────────────────────────────
22
+ * 거절: 되돌릴 수 없거나 뜻이 성립하지 않는 것(없는 공간·같은 공간·벤치 사본).
23
+ * 경고: 사용자가 알고 나면 정당할 수 있는 것(구역 소속이 끊긴다·시간대가 다르다·옛 표현이 남는다).
24
+ * 경고를 거절로 올리면 정당한 이전을 막고, 거절을 경고로 낮추면 조용한 사고가 된다.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.judgeMoveTwinSpace = judgeMoveTwinSpace;
28
+ exports.judgeDetachTwinSpace = judgeDetachTwinSpace;
29
+ /** 옮겨도 되나 — 되면 무엇을 알려야 하나. */
30
+ function judgeMoveTwinSpace(input) {
31
+ const to = (input.toSpaceId ?? '').trim();
32
+ const from = (input.fromSpaceId ?? '').trim();
33
+ if (!input.instanceId) {
34
+ return {
35
+ refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' },
36
+ warnings: []
37
+ };
38
+ }
39
+ if (!to || !input.toSpaceExists) {
40
+ return {
41
+ refusal: {
42
+ code: 'space-not-found',
43
+ params: { spaceId: to },
44
+ message: `space "${to}" not found — cannot move a twin into a space that does not exist`
45
+ },
46
+ warnings: []
47
+ };
48
+ }
49
+ if (from && from === to) {
50
+ return {
51
+ refusal: {
52
+ code: 'same-space',
53
+ params: { spaceId: to },
54
+ message: `twin "${input.instanceId}" is already in space "${to}"`
55
+ },
56
+ warnings: []
57
+ };
58
+ }
59
+ /* 벤치 사본은 "이 공장이 이만큼 커지면" 을 재는 사본이라 원본의 현장에 매여 있다. 옮기면 그 측정이
60
+ 무엇의 측정인지 말할 수 없게 된다 — 사본을 옮기는 대신 원본을 옮기고 사본을 다시 뜬다. */
61
+ if (input.purpose === 'bench') {
62
+ return {
63
+ refusal: {
64
+ code: 'bench-copy',
65
+ params: { instanceId: input.instanceId },
66
+ message: `twin "${input.instanceId}" is a bench copy — move its source twin instead`
67
+ },
68
+ warnings: []
69
+ };
70
+ }
71
+ const warnings = [];
72
+ /* 로케이션의 구역 소속 — 구역은 공간의 것이라 새 공간에 같은 id 가 없으면 소속이 끊긴다.
73
+ 막지는 않는다(새 공간에서 구역을 다시 그리는 것이 정상 경로다). 다만 몇 개가 끊기는지 말한다. */
74
+ const refs = [...new Set((input.areaRefs ?? []).filter((x) => !!x && !!x.trim()))];
75
+ const have = new Set(input.toAreaIds ?? []);
76
+ const orphaned = refs.filter(r => !have.has(r)).sort();
77
+ if (orphaned.length) {
78
+ warnings.push({
79
+ code: 'areas-left-behind',
80
+ params: { count: orphaned.length, areas: orphaned.slice(0, 5).join(', ') },
81
+ message: `${orphaned.length} area reference(s) do not exist in the target space — those locations lose their grouping: ${orphaned
82
+ .slice(0, 5)
83
+ .join(', ')}`
84
+ });
85
+ }
86
+ /* 시간대 — 공간이 정하고 커널이 분 오프셋으로 받는다. 다르면 같은 저널이 다른 시각으로 읽힌다. */
87
+ const tzFrom = (input.fromTimezone ?? '').trim();
88
+ const tzTo = (input.toTimezone ?? '').trim();
89
+ if (tzFrom !== tzTo) {
90
+ warnings.push({
91
+ code: 'timezone-differs',
92
+ params: { from: tzFrom || '—', to: tzTo || '—' },
93
+ message: `time zone changes from "${tzFrom || 'unset'}" to "${tzTo || 'unset'}" — shift boundaries and day buckets will read differently`
94
+ });
95
+ }
96
+ /* 옛 공간의 표현 — 폴리곤 바인딩이 이 트윈의 로케이션·그룹을 가리키고 있었을 수 있다. 표현은
97
+ 공간에 남으므로, 그 화면에서 이 트윈이 사라진다. 몇 개가 남는지 말해 두면 사람이 찾아갈 수 있다. */
98
+ if ((input.fromRepresentationCount ?? 0) > 0) {
99
+ warnings.push({
100
+ code: 'representations-left-behind',
101
+ params: { count: input.fromRepresentationCount, spaceId: from || '—' },
102
+ message: `${input.fromRepresentationCount} representation(s) stay with space "${from}" — bindings that pointed at this twin will no longer resolve there`
103
+ });
104
+ }
105
+ /* 실행 중 — 소속은 즉시 바뀌지만 시간대는 커널을 세울 때 실린다. 시간대가 같으면 말할 것이 없다. */
106
+ if (input.running && tzFrom !== tzTo) {
107
+ warnings.push({
108
+ code: 'running',
109
+ params: { instanceId: input.instanceId },
110
+ message: `twin "${input.instanceId}" is running — the new time zone applies after it is restarted`
111
+ });
112
+ }
113
+ return { warnings };
114
+ }
115
+ /** 떼도 되나 — 되면 무엇을 알려야 하나. */
116
+ function judgeDetachTwinSpace(input) {
117
+ const from = (input.fromSpaceId ?? '').trim();
118
+ if (!input.instanceId) {
119
+ return { refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' }, warnings: [] };
120
+ }
121
+ if (!from) {
122
+ return {
123
+ refusal: {
124
+ code: 'no-space',
125
+ params: { instanceId: input.instanceId },
126
+ message: `twin "${input.instanceId}" is not on any site — nothing to detach`
127
+ },
128
+ warnings: []
129
+ };
130
+ }
131
+ /* 벤치 사본은 원본의 현장을 따라야 측정이 무엇의 측정인지 말할 수 있다(옮기기와 같은 이유). */
132
+ if (input.purpose === 'bench') {
133
+ return {
134
+ refusal: {
135
+ code: 'bench-copy',
136
+ params: { instanceId: input.instanceId },
137
+ message: `twin "${input.instanceId}" is a bench copy — it must stay on the site it measures`
138
+ },
139
+ warnings: []
140
+ };
141
+ }
142
+ const warnings = [
143
+ {
144
+ code: 'no-stage',
145
+ params: { instanceId: input.instanceId },
146
+ message: `twin "${input.instanceId}" will have no site to be drawn on — it stays visible in lists and inspection only`
147
+ }
148
+ ];
149
+ if ((input.fromRepresentationCount ?? 0) > 0) {
150
+ warnings.push({
151
+ code: 'representations-left-behind',
152
+ params: { count: input.fromRepresentationCount, spaceId: from },
153
+ message: `${input.fromRepresentationCount} representation(s) stay with space "${from}" — bindings that pointed at this twin will no longer resolve there`
154
+ });
155
+ }
156
+ const tz = (input.fromTimezone ?? '').trim();
157
+ if (tz) {
158
+ warnings.push({
159
+ code: 'timebase-kept',
160
+ params: { timezone: tz },
161
+ message: `the time base stays as "${tz}" (the site it came from) — clearing it would silently read times as UTC`
162
+ });
163
+ }
164
+ return { warnings };
165
+ }
166
+ //# sourceMappingURL=move-space.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"move-space.js","sourceRoot":"","sources":["../../../server/service/twin-space/move-space.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이미 있는 트윈을 다른 현장으로 옮긴다 — **판정만**. 순수.\n *\n * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────\n * 트윈↔현장 연결은 **만들 때만** 정할 수 있었다(템플릿 위자드·import 의 `spaceId`). 잘못 넣었거나,\n * 나중에 \"이 창고는 사실 저 공장과 같은 부지다\" 를 알게 되면 지우고 다시 만드는 것이 유일한 길이었다 —\n * 다시 만들면 그 트윈의 저널(일어난 일)이 사라진다. 고칠 수 있는 실수를 데이터 손실로 갚게 하는 설계다.\n *\n * ── 옮기는 것이 무엇을 옮기나 (엔티티 키를 보고 정했다) ──────────────────────\n * 무엇이 트윈에 매여 있고 무엇이 공간에 매여 있는지가 답을 정한다:\n *\n * · `TwinEvent` `(domain, instanceId, …)` → **따라온다.** 저널을 다시 쓰지 않는다.\n * · `TwinLocation/Equipment/Operation` `(domain, instance, …)` → **따라온다.**\n * · `TwinArea` `(domain, space, areaId)` → **남는다.** 구역은 공간의 것이다.\n * · `TwinSpaceRepresentation`(지도·이미지·보드) → **남는다.** 표현도 공간의 것이다.\n *\n * 그래서 옮기기는 이력 이전이 아니라 **소속 갱신**이다(`instance.spaceId` + `model.spaceId`).\n * 대신 공간에 매인 것들과의 연결이 끊길 수 있고, 그것을 **조용히 넘기지 않는 것**이 이 모듈의 일이다.\n *\n * ── 거절과 경고를 가른다 ────────────────────────────────────────────────────\n * 거절: 되돌릴 수 없거나 뜻이 성립하지 않는 것(없는 공간·같은 공간·벤치 사본).\n * 경고: 사용자가 알고 나면 정당할 수 있는 것(구역 소속이 끊긴다·시간대가 다르다·옛 표현이 남는다).\n * 경고를 거절로 올리면 정당한 이전을 막고, 거절을 경고로 낮추면 조용한 사고가 된다.\n */\n\n/** 옮길 수 없는 이유 — 언어 중립 코드(발견 오류·인제스트 거절과 같은 규약). */\nexport type MoveSpaceRefusalCode = 'twin-not-found' | 'space-not-found' | 'same-space' | 'bench-copy'\n\n/** 옮겨도 되지만 알아야 하는 것 — 같은 규약. */\nexport type MoveSpaceWarningCode = 'areas-left-behind' | 'timezone-differs' | 'representations-left-behind' | 'running'\n\nexport interface MoveSpaceNote<C extends string> {\n code: C\n params?: Record<string, string | number>\n /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다. */\n message: string\n}\n\nexport type MoveSpaceRefusal = MoveSpaceNote<MoveSpaceRefusalCode>\nexport type MoveSpaceWarning = MoveSpaceNote<MoveSpaceWarningCode>\n\nexport interface MoveSpaceInput {\n /** 옮길 트윈. 없으면 거절한다. */\n instanceId?: string\n /** 지금 속한 공간(없을 수 있다 — 옛 트윈). */\n fromSpaceId?: string\n /** 옮겨 갈 공간. */\n toSpaceId: string\n /** 그 공간이 이 테넌트에 실제로 있나(호출측이 조회해 넘긴다). */\n toSpaceExists: boolean\n /** 운영 트윈인가 벤치 사본인가 — 사본은 원본의 공간을 따라야 하므로 옮기지 않는다. */\n purpose?: string\n /** 지금 실행 중인가 — 시간대 변화는 재기동 뒤에 커널에 실린다. */\n running?: boolean\n /** 두 공간의 시간대(IANA). 다르면 앞으로의 시각 해석이 달라진다. */\n fromTimezone?: string\n toTimezone?: string\n /** 이 트윈의 로케이션들이 소속으로 가리키는 구역 id 들(중복 포함 가능). */\n areaRefs?: readonly (string | undefined | null)[]\n /** 옮겨 갈 공간에 있는 구역 id 들. */\n toAreaIds?: readonly string[]\n /** 옛 공간의 표현 수 — 그 표현의 폴리곤이 이 트윈을 가리키고 있었을 수 있다. */\n fromRepresentationCount?: number\n}\n\nexport interface MoveSpaceJudgement {\n /** 있으면 옮기지 않는다. */\n refusal?: MoveSpaceRefusal\n /** 옮기되 함께 알린다. 비어 있으면 잃는 것이 없다. */\n warnings: MoveSpaceWarning[]\n}\n\n/** 옮겨도 되나 — 되면 무엇을 알려야 하나. */\nexport function judgeMoveTwinSpace(input: MoveSpaceInput): MoveSpaceJudgement {\n const to = (input.toSpaceId ?? '').trim()\n const from = (input.fromSpaceId ?? '').trim()\n\n if (!input.instanceId) {\n return {\n refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' },\n warnings: []\n }\n }\n if (!to || !input.toSpaceExists) {\n return {\n refusal: {\n code: 'space-not-found',\n params: { spaceId: to },\n message: `space \"${to}\" not found — cannot move a twin into a space that does not exist`\n },\n warnings: []\n }\n }\n if (from && from === to) {\n return {\n refusal: {\n code: 'same-space',\n params: { spaceId: to },\n message: `twin \"${input.instanceId}\" is already in space \"${to}\"`\n },\n warnings: []\n }\n }\n /* 벤치 사본은 \"이 공장이 이만큼 커지면\" 을 재는 사본이라 원본의 현장에 매여 있다. 옮기면 그 측정이\n 무엇의 측정인지 말할 수 없게 된다 — 사본을 옮기는 대신 원본을 옮기고 사본을 다시 뜬다. */\n if (input.purpose === 'bench') {\n return {\n refusal: {\n code: 'bench-copy',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is a bench copy — move its source twin instead`\n },\n warnings: []\n }\n }\n\n const warnings: MoveSpaceWarning[] = []\n\n /* 로케이션의 구역 소속 — 구역은 공간의 것이라 새 공간에 같은 id 가 없으면 소속이 끊긴다.\n 막지는 않는다(새 공간에서 구역을 다시 그리는 것이 정상 경로다). 다만 몇 개가 끊기는지 말한다. */\n const refs = [...new Set((input.areaRefs ?? []).filter((x): x is string => !!x && !!x.trim()))]\n const have = new Set(input.toAreaIds ?? [])\n const orphaned = refs.filter(r => !have.has(r)).sort()\n if (orphaned.length) {\n warnings.push({\n code: 'areas-left-behind',\n params: { count: orphaned.length, areas: orphaned.slice(0, 5).join(', ') },\n message: `${orphaned.length} area reference(s) do not exist in the target space — those locations lose their grouping: ${orphaned\n .slice(0, 5)\n .join(', ')}`\n })\n }\n\n /* 시간대 — 공간이 정하고 커널이 분 오프셋으로 받는다. 다르면 같은 저널이 다른 시각으로 읽힌다. */\n const tzFrom = (input.fromTimezone ?? '').trim()\n const tzTo = (input.toTimezone ?? '').trim()\n if (tzFrom !== tzTo) {\n warnings.push({\n code: 'timezone-differs',\n params: { from: tzFrom || '—', to: tzTo || '—' },\n message: `time zone changes from \"${tzFrom || 'unset'}\" to \"${tzTo || 'unset'}\" — shift boundaries and day buckets will read differently`\n })\n }\n\n /* 옛 공간의 표현 — 폴리곤 바인딩이 이 트윈의 로케이션·그룹을 가리키고 있었을 수 있다. 표현은\n 공간에 남으므로, 그 화면에서 이 트윈이 사라진다. 몇 개가 남는지 말해 두면 사람이 찾아갈 수 있다. */\n if ((input.fromRepresentationCount ?? 0) > 0) {\n warnings.push({\n code: 'representations-left-behind',\n params: { count: input.fromRepresentationCount as number, spaceId: from || '—' },\n message: `${input.fromRepresentationCount} representation(s) stay with space \"${from}\" — bindings that pointed at this twin will no longer resolve there`\n })\n }\n\n /* 실행 중 — 소속은 즉시 바뀌지만 시간대는 커널을 세울 때 실린다. 시간대가 같으면 말할 것이 없다. */\n if (input.running && tzFrom !== tzTo) {\n warnings.push({\n code: 'running',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is running — the new time zone applies after it is restarted`\n })\n }\n\n return { warnings }\n}\n\n/*\n * ── 현장에서 떼기 (2026-08-13) ──────────────────────────────────────────────\n *\n * 더하기·옮기기만 있으면 잘못 넣은 것을 되돌릴 수 없다. 떼기는 \"어느 현장에도 속하지 않음\" 으로\n * 되돌리는 일이고, 데이터 모델이 그 상태를 이미 허용한다(`TwinInstance.spaceId` 는 선택 컬럼).\n *\n * 떼면 무엇이 달라지나 — 옮기기와 같은 규칙이지만 **갈 곳이 없다**는 점이 다르다:\n * · 저널·로케이션·설비는 그대로 트윈에 남는다(잃는 것 없음).\n * · 구역·표현은 옛 현장에 남는다 → 그 화면에서 이 트윈이 사라진다.\n * · **볼 무대가 없어진다** — 현장 현황(지도·보드)은 현장의 표현으로 그리므로, 뗀 트윈은 목록과\n * 조회 화면에서만 보인다. 이것을 말하지 않으면 사용자는 트윈이 고장 났다고 읽는다.\n * · 시간 기준(`utcOffsetMinutes`)은 **옛 현장의 것으로 남는다.** 지우면 조용히 UTC 로 읽히므로\n * 그쪽이 더 위험하다 — 남기고 그 사실을 말한다.\n */\n\n/** 뗄 수 없는 이유 — 언어 중립 코드. */\nexport type DetachSpaceRefusalCode = 'twin-not-found' | 'no-space' | 'bench-copy'\n\n/** 떼도 되지만 알아야 하는 것. */\nexport type DetachSpaceWarningCode = 'no-stage' | 'representations-left-behind' | 'timebase-kept'\n\nexport type DetachSpaceRefusal = MoveSpaceNote<DetachSpaceRefusalCode>\nexport type DetachSpaceWarning = MoveSpaceNote<DetachSpaceWarningCode>\n\nexport interface DetachSpaceInput {\n instanceId?: string\n /** 지금 속한 공간 — 없으면 뗄 것이 없다. */\n fromSpaceId?: string\n purpose?: string\n /** 옛 공간의 표현 수. */\n fromRepresentationCount?: number\n /** 옛 공간의 시간대 — 있으면 그 기준이 model 에 남는다. */\n fromTimezone?: string\n}\n\nexport interface DetachSpaceJudgement {\n refusal?: DetachSpaceRefusal\n warnings: DetachSpaceWarning[]\n}\n\n/** 떼도 되나 — 되면 무엇을 알려야 하나. */\nexport function judgeDetachTwinSpace(input: DetachSpaceInput): DetachSpaceJudgement {\n const from = (input.fromSpaceId ?? '').trim()\n\n if (!input.instanceId) {\n return { refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' }, warnings: [] }\n }\n if (!from) {\n return {\n refusal: {\n code: 'no-space',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is not on any site — nothing to detach`\n },\n warnings: []\n }\n }\n /* 벤치 사본은 원본의 현장을 따라야 측정이 무엇의 측정인지 말할 수 있다(옮기기와 같은 이유). */\n if (input.purpose === 'bench') {\n return {\n refusal: {\n code: 'bench-copy',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is a bench copy — it must stay on the site it measures`\n },\n warnings: []\n }\n }\n\n const warnings: DetachSpaceWarning[] = [\n {\n code: 'no-stage',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" will have no site to be drawn on — it stays visible in lists and inspection only`\n }\n ]\n\n if ((input.fromRepresentationCount ?? 0) > 0) {\n warnings.push({\n code: 'representations-left-behind',\n params: { count: input.fromRepresentationCount as number, spaceId: from },\n message: `${input.fromRepresentationCount} representation(s) stay with space \"${from}\" — bindings that pointed at this twin will no longer resolve there`\n })\n }\n\n const tz = (input.fromTimezone ?? '').trim()\n if (tz) {\n warnings.push({\n code: 'timebase-kept',\n params: { timezone: tz },\n message: `the time base stays as \"${tz}\" (the site it came from) — clearing it would silently read times as UTC`\n })\n }\n\n return { warnings }\n}\n"]}
@@ -0,0 +1,97 @@
1
+ export type SpaceIntegrityCode =
2
+ /** 트윈이 없는 공간을 가리킨다 — 그 트윈은 무대가 사라졌다. */
3
+ 'twin-space-missing'
4
+ /** 레지스트리와 model 의 공간이 다르다 — 어느 쪽이 사실인지 알 수 없다. */
5
+ | 'twin-space-mismatch'
6
+ /** 폴리곤이 이 공간에 없는 구역을 가리킨다. */
7
+ | 'polygon-area-missing'
8
+ /** 폴리곤이 이 공간에 사는 어느 트윈에도 없는 로케이션·설비를 가리킨다. */
9
+ | 'polygon-target-missing'
10
+ /** 로케이션이 이 공간에 없는 구역에 소속되어 있다. */
11
+ | 'location-area-missing'
12
+ /** 대표 표현이 이 공간의 표현이 아니다. */
13
+ | 'primary-representation-missing'
14
+ /** 보드 표현이 보드를 가리키지 않는다(또는 없는 보드를 가리킨다). */
15
+ | 'board-representation-unbound'
16
+ /** 표현이 하나도 없는 공간에 트윈이 산다 — 볼 화면이 없다. */
17
+ | 'space-without-representation'
18
+ /** 어느 공간에도 속하지 않은 트윈 — 뗀 결과일 수 있다(사실이면 정상). */
19
+ | 'twin-without-space'
20
+ /**
21
+ * 이 트윈이 **담을 줄 모르는 어휘**를 받고 있다 — 리비전은 오르는데 상태가 채워지지 않는다.
22
+ *
23
+ * 커널은 모르는 `eventType` 을 버리고(전방 호환) 종류별로 센다(`state.unhandled`). 그 수가 0이
24
+ * 아니면 둘 중 하나다: 커넥터가 엉뚱한 트윈에 붙었거나(예: `energy.*` 가 창고 트윈으로), 커널이
25
+ * 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고, 조용히 두면 「정상인데 빈」 트윈이 된다.
26
+ */
27
+ | 'twin-unhandled-vocabulary';
28
+ export type SpaceIntegritySeverity = 'broken' | 'warn';
29
+ export interface SpaceIntegrityFinding {
30
+ code: SpaceIntegrityCode;
31
+ severity: SpaceIntegritySeverity;
32
+ /** 어디의 문제인가 — 화면이 그 자리로 데려갈 수 있게. */
33
+ spaceId?: string;
34
+ instanceId?: string;
35
+ representationId?: string;
36
+ params?: Record<string, string | number>;
37
+ /** 영어 canonical — 로그·폴백. */
38
+ message: string;
39
+ }
40
+ export interface IntegritySpace {
41
+ spaceId: string;
42
+ primaryRepresentationId?: string | null;
43
+ }
44
+ export interface IntegrityInstance {
45
+ instanceId: string;
46
+ /** 레지스트리 컬럼. */
47
+ spaceId?: string | null;
48
+ /** 저장된 model 의 공간 — 레지스트리와 같아야 한다. */
49
+ modelSpaceId?: string | null;
50
+ purpose?: string | null;
51
+ /** 이 트윈의 로케이션 id 들. */
52
+ locationIds?: readonly string[];
53
+ /** 이 트윈의 설비 id 들. */
54
+ equipmentIds?: readonly string[];
55
+ /** 로케이션의 소속 구역 id 들(중복 가능). */
56
+ areaRefs?: readonly (string | undefined | null)[];
57
+ /**
58
+ * 커널이 담을 줄 몰라 버린 사건들 — 실행 중인 트윈의 상태에서 온다(`state.unhandled`).
59
+ *
60
+ * 정지한 트윈에서는 알 수 없으므로 `undefined` 다 — **없는 것과 모르는 것을 구별한다**(빈 배열을
61
+ * 넣으면 「받은 것이 없다」는 사실 주장이 되고, 그것은 우리가 확인한 바가 아니다).
62
+ */
63
+ unhandled?: readonly {
64
+ eventType: string;
65
+ count: number;
66
+ }[];
67
+ }
68
+ export interface IntegrityRepresentation {
69
+ id: string;
70
+ spaceId: string;
71
+ type: string;
72
+ /** 보드 표현이면 보드 id. */
73
+ boardId?: string | null;
74
+ /** 폴리곤 바인딩들. */
75
+ polygons?: readonly IntegrityPolygon[];
76
+ }
77
+ export interface IntegrityPolygon {
78
+ id?: string;
79
+ /** 논리 구역 바인딩(그룹). */
80
+ groupId?: string | null;
81
+ /** 개체 바인딩 — 로케이션 또는 설비. */
82
+ targetKind?: 'location' | 'equipment' | null;
83
+ targetId?: string | null;
84
+ }
85
+ export interface SpaceIntegrityInput {
86
+ spaces: readonly IntegritySpace[];
87
+ instances: readonly IntegrityInstance[];
88
+ representations: readonly IntegrityRepresentation[];
89
+ /** 공간별 구역 id 들. */
90
+ areasBySpace: Readonly<Record<string, readonly string[]>>;
91
+ /** 존재하는 보드 id 들 — 보드 표현이 실제 보드를 가리키는지 본다. */
92
+ boardIds?: readonly string[];
93
+ }
94
+ /** 세 축을 맞대어 어긋난 자리를 모두 낸다. 비어 있으면 정합하다. */
95
+ export declare function checkSpaceIntegrity(input: SpaceIntegrityInput): SpaceIntegrityFinding[];
96
+ /** 심각한 것부터 — 화면이 정렬을 다시 정하지 않게 한 곳에서 정한다. */
97
+ export declare function sortFindings(findings: readonly SpaceIntegrityFinding[]): SpaceIntegrityFinding[];
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ /*
3
+ * 공간 ↔ 트윈 ↔ 가시화 정합성 — **순수**. 세 축이 서로 어긋난 자리를 찾는다.
4
+ *
5
+ * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────
6
+ * 이 셋은 서로를 id 로만 가리킨다. 참조 무결성을 DB 가 지켜 주지 않는 지점이 많다:
7
+ * 공간·구역은 공간에 매이고, 로케이션·설비는 트윈에 매이고, 폴리곤은 **양쪽을 가리킨다.**
8
+ * 그래서 트윈을 옮기거나 떼거나(2026-08-13 추가) 원본을 다시 읽으면, 아무 오류 없이 **가리킬 것이
9
+ * 없는 도형**과 **소속 없는 로케이션**이 남는다. 증상은 "지도가 비었다" 뿐이고 원인은 화면에 없다.
10
+ *
11
+ * 그 어긋남을 사람이 눈으로 맞추고 있었다(spaceId 를 목록에서 대조). 기계가 대신 본다.
12
+ *
13
+ * ── 무엇을 사실로 보나 ─────────────────────────────────────────────────────
14
+ * 판정은 **조회 결과만** 받는다(DB 를 물지 않는다). 그래서 단위테스트로 전부 부를 수 있고, 호출측은
15
+ * 사실을 모으는 일만 한다. 각 발견은 언어 중립 코드 + 파라미터이고, 화면이 사람 말을 만든다.
16
+ *
17
+ * ── 심각도를 가른다 ────────────────────────────────────────────────────────
18
+ * `broken` 은 무언가 이미 보이지 않는 상태다(가리킬 대상이 없다). `warn` 은 아직 보이지만 뜻이
19
+ * 흐려진 상태다(소속 없음·대표 없음). 섞으면 사람이 무엇을 먼저 볼지 정할 수 없다.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.checkSpaceIntegrity = checkSpaceIntegrity;
23
+ exports.sortFindings = sortFindings;
24
+ /** 세 축을 맞대어 어긋난 자리를 모두 낸다. 비어 있으면 정합하다. */
25
+ function checkSpaceIntegrity(input) {
26
+ const out = [];
27
+ const spaceIds = new Set(input.spaces.map(s => s.spaceId));
28
+ const areasOf = (spaceId) => new Set(input.areasBySpace[spaceId] ?? []);
29
+ const repsOf = (spaceId) => input.representations.filter(r => r.spaceId === spaceId);
30
+ const boards = new Set(input.boardIds ?? []);
31
+ /* 벤치 사본은 측정용 사본이라 이 검사의 대상이 아니다 — 사본의 공간은 잠깐 살고 지워진다. */
32
+ const instances = input.instances.filter(i => i.purpose !== 'bench');
33
+ /* ── 트윈 → 공간 ─────────────────────────────────────────────────────── */
34
+ for (const t of instances) {
35
+ const sid = (t.spaceId ?? '').trim();
36
+ const msid = (t.modelSpaceId ?? '').trim();
37
+ if (!sid) {
38
+ out.push({
39
+ code: 'twin-without-space',
40
+ severity: 'warn',
41
+ instanceId: t.instanceId,
42
+ message: `twin "${t.instanceId}" is on no site — it has no map or board to be drawn on`
43
+ });
44
+ }
45
+ else if (!spaceIds.has(sid)) {
46
+ out.push({
47
+ code: 'twin-space-missing',
48
+ severity: 'broken',
49
+ instanceId: t.instanceId,
50
+ spaceId: sid,
51
+ message: `twin "${t.instanceId}" points at site "${sid}" which does not exist`
52
+ });
53
+ }
54
+ /* 두 곳이 어긋나면 어느 쪽이 사실인지 알 수 없다 — 보드는 model 을, 목록은 컬럼을 읽는다. */
55
+ if (sid !== msid) {
56
+ out.push({
57
+ code: 'twin-space-mismatch',
58
+ severity: 'broken',
59
+ instanceId: t.instanceId,
60
+ spaceId: sid || undefined,
61
+ params: { registry: sid || '—', model: msid || '—' },
62
+ message: `twin "${t.instanceId}" registry site "${sid || 'none'}" differs from its model site "${msid || 'none'}"`
63
+ });
64
+ }
65
+ /* 로케이션의 소속 구역은 **트윈이 속한 공간**의 것이어야 한다. */
66
+ if (sid && spaceIds.has(sid)) {
67
+ const have = areasOf(sid);
68
+ const refs = [...new Set((t.areaRefs ?? []).filter((x) => !!x && !!x.trim()))];
69
+ const missing = refs.filter(r => !have.has(r)).sort();
70
+ if (missing.length) {
71
+ out.push({
72
+ code: 'location-area-missing',
73
+ severity: 'warn',
74
+ instanceId: t.instanceId,
75
+ spaceId: sid,
76
+ params: { count: missing.length, areas: missing.slice(0, 5).join(', ') },
77
+ message: `${missing.length} area(s) referenced by locations of "${t.instanceId}" do not exist in site "${sid}": ${missing
78
+ .slice(0, 5)
79
+ .join(', ')}`
80
+ });
81
+ }
82
+ }
83
+ /*
84
+ * 어휘가 맞는가 — 저널이 들어오는데 상태가 채워지지 않는 경우를 잡는다.
85
+ *
86
+ * `undefined` 는 「모른다」다(정지한 트윈). 세지 않는다 — 확인하지 않은 것을 정합으로도, 결함으로도
87
+ * 세지 않는 것이 이 하네스의 규율이다.
88
+ */
89
+ if (t.unhandled?.length) {
90
+ const total = t.unhandled.reduce((n, u) => n + u.count, 0);
91
+ const kinds = [...t.unhandled].sort((a, b) => b.count - a.count);
92
+ out.push({
93
+ code: 'twin-unhandled-vocabulary',
94
+ severity: 'warn',
95
+ instanceId: t.instanceId,
96
+ spaceId: sid || undefined,
97
+ params: { events: total, kinds: kinds.length, types: kinds.slice(0, 3).map(u => u.eventType).join(', ') },
98
+ message: `twin "${t.instanceId}" received ${total} event(s) of ${kinds.length} type(s) its kernel does not handle: ${kinds
99
+ .slice(0, 3)
100
+ .map(u => u.eventType)
101
+ .join(', ')}`
102
+ });
103
+ }
104
+ }
105
+ /* ── 공간 → 가시화 ───────────────────────────────────────────────────── */
106
+ for (const s of input.spaces) {
107
+ const reps = repsOf(s.spaceId);
108
+ const livesHere = instances.filter(t => (t.spaceId ?? '') === s.spaceId);
109
+ /* 트윈이 사는데 표현이 없으면 볼 화면이 없다. 트윈이 없는 빈 현장은 정상이므로 세지 않는다. */
110
+ if (livesHere.length && !reps.length) {
111
+ out.push({
112
+ code: 'space-without-representation',
113
+ severity: 'warn',
114
+ spaceId: s.spaceId,
115
+ params: { twins: livesHere.length },
116
+ message: `site "${s.spaceId}" has ${livesHere.length} twin(s) but no representation — there is nothing to draw them on`
117
+ });
118
+ }
119
+ const primary = (s.primaryRepresentationId ?? '').trim();
120
+ if (primary && !reps.some(r => r.id === primary)) {
121
+ out.push({
122
+ code: 'primary-representation-missing',
123
+ severity: 'broken',
124
+ spaceId: s.spaceId,
125
+ params: { representationId: primary },
126
+ message: `site "${s.spaceId}" points at primary representation "${primary}" which is not one of its representations`
127
+ });
128
+ }
129
+ }
130
+ /* ── 가시화 → 공간·트윈 ─────────────────────────────────────────────── */
131
+ for (const rep of input.representations) {
132
+ const have = areasOf(rep.spaceId);
133
+ /* 이 공간에 사는 트윈들의 개체 id — 폴리곤이 가리킬 수 있는 전부. */
134
+ const here = instances.filter(t => (t.spaceId ?? '') === rep.spaceId);
135
+ const locationIds = new Set(here.flatMap(t => [...(t.locationIds ?? [])]));
136
+ const equipmentIds = new Set(here.flatMap(t => [...(t.equipmentIds ?? [])]));
137
+ if (rep.type === 'board' && !(rep.boardId && boards.has(rep.boardId))) {
138
+ out.push({
139
+ code: 'board-representation-unbound',
140
+ severity: 'broken',
141
+ spaceId: rep.spaceId,
142
+ representationId: rep.id,
143
+ params: { boardId: rep.boardId ?? '—' },
144
+ message: `board representation "${rep.id}" of site "${rep.spaceId}" does not point at an existing board (${rep.boardId ?? 'none'})`
145
+ });
146
+ }
147
+ for (const p of rep.polygons ?? []) {
148
+ const gid = (p.groupId ?? '').trim();
149
+ if (gid && !have.has(gid)) {
150
+ out.push({
151
+ code: 'polygon-area-missing',
152
+ severity: 'broken',
153
+ spaceId: rep.spaceId,
154
+ representationId: rep.id,
155
+ params: { areaId: gid, polygonId: p.id ?? '—' },
156
+ message: `a polygon of representation "${rep.id}" binds area "${gid}" which does not exist in site "${rep.spaceId}"`
157
+ });
158
+ }
159
+ const tid = (p.targetId ?? '').trim();
160
+ if (tid && p.targetKind) {
161
+ const pool = p.targetKind === 'equipment' ? equipmentIds : locationIds;
162
+ if (!pool.has(tid)) {
163
+ out.push({
164
+ code: 'polygon-target-missing',
165
+ severity: 'broken',
166
+ spaceId: rep.spaceId,
167
+ representationId: rep.id,
168
+ params: { kind: p.targetKind, targetId: tid, polygonId: p.id ?? '—' },
169
+ message: `a polygon of representation "${rep.id}" binds ${p.targetKind} "${tid}", which no twin on site "${rep.spaceId}" has`
170
+ });
171
+ }
172
+ }
173
+ }
174
+ }
175
+ return out;
176
+ }
177
+ /** 심각한 것부터 — 화면이 정렬을 다시 정하지 않게 한 곳에서 정한다. */
178
+ function sortFindings(findings) {
179
+ const rank = (s) => (s === 'broken' ? 0 : 1);
180
+ return [...findings].sort((a, b) => rank(a.severity) - rank(b.severity) || a.code.localeCompare(b.code) || (a.spaceId ?? '').localeCompare(b.spaceId ?? ''));
181
+ }
182
+ //# sourceMappingURL=space-integrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-integrity.js","sourceRoot":"","sources":["../../../server/service/twin-space/space-integrity.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 공간 ↔ 트윈 ↔ 가시화 정합성 — **순수**. 세 축이 서로 어긋난 자리를 찾는다.\n *\n * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────\n * 이 셋은 서로를 id 로만 가리킨다. 참조 무결성을 DB 가 지켜 주지 않는 지점이 많다:\n * 공간·구역은 공간에 매이고, 로케이션·설비는 트윈에 매이고, 폴리곤은 **양쪽을 가리킨다.**\n * 그래서 트윈을 옮기거나 떼거나(2026-08-13 추가) 원본을 다시 읽으면, 아무 오류 없이 **가리킬 것이\n * 없는 도형**과 **소속 없는 로케이션**이 남는다. 증상은 \"지도가 비었다\" 뿐이고 원인은 화면에 없다.\n *\n * 그 어긋남을 사람이 눈으로 맞추고 있었다(spaceId 를 목록에서 대조). 기계가 대신 본다.\n *\n * ── 무엇을 사실로 보나 ─────────────────────────────────────────────────────\n * 판정은 **조회 결과만** 받는다(DB 를 물지 않는다). 그래서 단위테스트로 전부 부를 수 있고, 호출측은\n * 사실을 모으는 일만 한다. 각 발견은 언어 중립 코드 + 파라미터이고, 화면이 사람 말을 만든다.\n *\n * ── 심각도를 가른다 ────────────────────────────────────────────────────────\n * `broken` 은 무언가 이미 보이지 않는 상태다(가리킬 대상이 없다). `warn` 은 아직 보이지만 뜻이\n * 흐려진 상태다(소속 없음·대표 없음). 섞으면 사람이 무엇을 먼저 볼지 정할 수 없다.\n */\n\nexport type SpaceIntegrityCode =\n /** 트윈이 없는 공간을 가리킨다 — 그 트윈은 무대가 사라졌다. */\n | 'twin-space-missing'\n /** 레지스트리와 model 의 공간이 다르다 — 어느 쪽이 사실인지 알 수 없다. */\n | 'twin-space-mismatch'\n /** 폴리곤이 이 공간에 없는 구역을 가리킨다. */\n | 'polygon-area-missing'\n /** 폴리곤이 이 공간에 사는 어느 트윈에도 없는 로케이션·설비를 가리킨다. */\n | 'polygon-target-missing'\n /** 로케이션이 이 공간에 없는 구역에 소속되어 있다. */\n | 'location-area-missing'\n /** 대표 표현이 이 공간의 표현이 아니다. */\n | 'primary-representation-missing'\n /** 보드 표현이 보드를 가리키지 않는다(또는 없는 보드를 가리킨다). */\n | 'board-representation-unbound'\n /** 표현이 하나도 없는 공간에 트윈이 산다 — 볼 화면이 없다. */\n | 'space-without-representation'\n /** 어느 공간에도 속하지 않은 트윈 — 뗀 결과일 수 있다(사실이면 정상). */\n | 'twin-without-space'\n /**\n * 이 트윈이 **담을 줄 모르는 어휘**를 받고 있다 — 리비전은 오르는데 상태가 채워지지 않는다.\n *\n * 커널은 모르는 `eventType` 을 버리고(전방 호환) 종류별로 센다(`state.unhandled`). 그 수가 0이\n * 아니면 둘 중 하나다: 커넥터가 엉뚱한 트윈에 붙었거나(예: `energy.*` 가 창고 트윈으로), 커널이\n * 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고, 조용히 두면 「정상인데 빈」 트윈이 된다.\n */\n | 'twin-unhandled-vocabulary'\n\nexport type SpaceIntegritySeverity = 'broken' | 'warn'\n\nexport interface SpaceIntegrityFinding {\n code: SpaceIntegrityCode\n severity: SpaceIntegritySeverity\n /** 어디의 문제인가 — 화면이 그 자리로 데려갈 수 있게. */\n spaceId?: string\n instanceId?: string\n representationId?: string\n params?: Record<string, string | number>\n /** 영어 canonical — 로그·폴백. */\n message: string\n}\n\n/* ── 입력(조회 결과) ──────────────────────────────────────────────────────── */\n\nexport interface IntegritySpace {\n spaceId: string\n primaryRepresentationId?: string | null\n}\n\nexport interface IntegrityInstance {\n instanceId: string\n /** 레지스트리 컬럼. */\n spaceId?: string | null\n /** 저장된 model 의 공간 — 레지스트리와 같아야 한다. */\n modelSpaceId?: string | null\n purpose?: string | null\n /** 이 트윈의 로케이션 id 들. */\n locationIds?: readonly string[]\n /** 이 트윈의 설비 id 들. */\n equipmentIds?: readonly string[]\n /** 로케이션의 소속 구역 id 들(중복 가능). */\n areaRefs?: readonly (string | undefined | null)[]\n /**\n * 커널이 담을 줄 몰라 버린 사건들 — 실행 중인 트윈의 상태에서 온다(`state.unhandled`).\n *\n * 정지한 트윈에서는 알 수 없으므로 `undefined` 다 — **없는 것과 모르는 것을 구별한다**(빈 배열을\n * 넣으면 「받은 것이 없다」는 사실 주장이 되고, 그것은 우리가 확인한 바가 아니다).\n */\n unhandled?: readonly { eventType: string; count: number }[]\n}\n\nexport interface IntegrityRepresentation {\n id: string\n spaceId: string\n type: string\n /** 보드 표현이면 보드 id. */\n boardId?: string | null\n /** 폴리곤 바인딩들. */\n polygons?: readonly IntegrityPolygon[]\n}\n\nexport interface IntegrityPolygon {\n id?: string\n /** 논리 구역 바인딩(그룹). */\n groupId?: string | null\n /** 개체 바인딩 — 로케이션 또는 설비. */\n targetKind?: 'location' | 'equipment' | null\n targetId?: string | null\n}\n\nexport interface SpaceIntegrityInput {\n spaces: readonly IntegritySpace[]\n instances: readonly IntegrityInstance[]\n representations: readonly IntegrityRepresentation[]\n /** 공간별 구역 id 들. */\n areasBySpace: Readonly<Record<string, readonly string[]>>\n /** 존재하는 보드 id 들 — 보드 표현이 실제 보드를 가리키는지 본다. */\n boardIds?: readonly string[]\n}\n\n/** 세 축을 맞대어 어긋난 자리를 모두 낸다. 비어 있으면 정합하다. */\nexport function checkSpaceIntegrity(input: SpaceIntegrityInput): SpaceIntegrityFinding[] {\n const out: SpaceIntegrityFinding[] = []\n const spaceIds = new Set(input.spaces.map(s => s.spaceId))\n const areasOf = (spaceId: string) => new Set(input.areasBySpace[spaceId] ?? [])\n const repsOf = (spaceId: string) => input.representations.filter(r => r.spaceId === spaceId)\n const boards = new Set(input.boardIds ?? [])\n\n /* 벤치 사본은 측정용 사본이라 이 검사의 대상이 아니다 — 사본의 공간은 잠깐 살고 지워진다. */\n const instances = input.instances.filter(i => i.purpose !== 'bench')\n\n /* ── 트윈 → 공간 ─────────────────────────────────────────────────────── */\n for (const t of instances) {\n const sid = (t.spaceId ?? '').trim()\n const msid = (t.modelSpaceId ?? '').trim()\n\n if (!sid) {\n out.push({\n code: 'twin-without-space',\n severity: 'warn',\n instanceId: t.instanceId,\n message: `twin \"${t.instanceId}\" is on no site — it has no map or board to be drawn on`\n })\n } else if (!spaceIds.has(sid)) {\n out.push({\n code: 'twin-space-missing',\n severity: 'broken',\n instanceId: t.instanceId,\n spaceId: sid,\n message: `twin \"${t.instanceId}\" points at site \"${sid}\" which does not exist`\n })\n }\n\n /* 두 곳이 어긋나면 어느 쪽이 사실인지 알 수 없다 — 보드는 model 을, 목록은 컬럼을 읽는다. */\n if (sid !== msid) {\n out.push({\n code: 'twin-space-mismatch',\n severity: 'broken',\n instanceId: t.instanceId,\n spaceId: sid || undefined,\n params: { registry: sid || '—', model: msid || '—' },\n message: `twin \"${t.instanceId}\" registry site \"${sid || 'none'}\" differs from its model site \"${msid || 'none'}\"`\n })\n }\n\n /* 로케이션의 소속 구역은 **트윈이 속한 공간**의 것이어야 한다. */\n if (sid && spaceIds.has(sid)) {\n const have = areasOf(sid)\n const refs = [...new Set((t.areaRefs ?? []).filter((x): x is string => !!x && !!x.trim()))]\n const missing = refs.filter(r => !have.has(r)).sort()\n if (missing.length) {\n out.push({\n code: 'location-area-missing',\n severity: 'warn',\n instanceId: t.instanceId,\n spaceId: sid,\n params: { count: missing.length, areas: missing.slice(0, 5).join(', ') },\n message: `${missing.length} area(s) referenced by locations of \"${t.instanceId}\" do not exist in site \"${sid}\": ${missing\n .slice(0, 5)\n .join(', ')}`\n })\n }\n }\n\n /*\n * 어휘가 맞는가 — 저널이 들어오는데 상태가 채워지지 않는 경우를 잡는다.\n *\n * `undefined` 는 「모른다」다(정지한 트윈). 세지 않는다 — 확인하지 않은 것을 정합으로도, 결함으로도\n * 세지 않는 것이 이 하네스의 규율이다.\n */\n if (t.unhandled?.length) {\n const total = t.unhandled.reduce((n, u) => n + u.count, 0)\n const kinds = [...t.unhandled].sort((a, b) => b.count - a.count)\n out.push({\n code: 'twin-unhandled-vocabulary',\n severity: 'warn',\n instanceId: t.instanceId,\n spaceId: sid || undefined,\n params: { events: total, kinds: kinds.length, types: kinds.slice(0, 3).map(u => u.eventType).join(', ') },\n message: `twin \"${t.instanceId}\" received ${total} event(s) of ${kinds.length} type(s) its kernel does not handle: ${kinds\n .slice(0, 3)\n .map(u => u.eventType)\n .join(', ')}`\n })\n }\n }\n\n /* ── 공간 → 가시화 ───────────────────────────────────────────────────── */\n for (const s of input.spaces) {\n const reps = repsOf(s.spaceId)\n const livesHere = instances.filter(t => (t.spaceId ?? '') === s.spaceId)\n\n /* 트윈이 사는데 표현이 없으면 볼 화면이 없다. 트윈이 없는 빈 현장은 정상이므로 세지 않는다. */\n if (livesHere.length && !reps.length) {\n out.push({\n code: 'space-without-representation',\n severity: 'warn',\n spaceId: s.spaceId,\n params: { twins: livesHere.length },\n message: `site \"${s.spaceId}\" has ${livesHere.length} twin(s) but no representation — there is nothing to draw them on`\n })\n }\n\n const primary = (s.primaryRepresentationId ?? '').trim()\n if (primary && !reps.some(r => r.id === primary)) {\n out.push({\n code: 'primary-representation-missing',\n severity: 'broken',\n spaceId: s.spaceId,\n params: { representationId: primary },\n message: `site \"${s.spaceId}\" points at primary representation \"${primary}\" which is not one of its representations`\n })\n }\n }\n\n /* ── 가시화 → 공간·트윈 ─────────────────────────────────────────────── */\n for (const rep of input.representations) {\n const have = areasOf(rep.spaceId)\n /* 이 공간에 사는 트윈들의 개체 id — 폴리곤이 가리킬 수 있는 전부. */\n const here = instances.filter(t => (t.spaceId ?? '') === rep.spaceId)\n const locationIds = new Set(here.flatMap(t => [...(t.locationIds ?? [])]))\n const equipmentIds = new Set(here.flatMap(t => [...(t.equipmentIds ?? [])]))\n\n if (rep.type === 'board' && !(rep.boardId && boards.has(rep.boardId))) {\n out.push({\n code: 'board-representation-unbound',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { boardId: rep.boardId ?? '—' },\n message: `board representation \"${rep.id}\" of site \"${rep.spaceId}\" does not point at an existing board (${rep.boardId ?? 'none'})`\n })\n }\n\n for (const p of rep.polygons ?? []) {\n const gid = (p.groupId ?? '').trim()\n if (gid && !have.has(gid)) {\n out.push({\n code: 'polygon-area-missing',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { areaId: gid, polygonId: p.id ?? '—' },\n message: `a polygon of representation \"${rep.id}\" binds area \"${gid}\" which does not exist in site \"${rep.spaceId}\"`\n })\n }\n const tid = (p.targetId ?? '').trim()\n if (tid && p.targetKind) {\n const pool = p.targetKind === 'equipment' ? equipmentIds : locationIds\n if (!pool.has(tid)) {\n out.push({\n code: 'polygon-target-missing',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { kind: p.targetKind, targetId: tid, polygonId: p.id ?? '—' },\n message: `a polygon of representation \"${rep.id}\" binds ${p.targetKind} \"${tid}\", which no twin on site \"${rep.spaceId}\" has`\n })\n }\n }\n }\n }\n\n return out\n}\n\n/** 심각한 것부터 — 화면이 정렬을 다시 정하지 않게 한 곳에서 정한다. */\nexport function sortFindings(findings: readonly SpaceIntegrityFinding[]): SpaceIntegrityFinding[] {\n const rank = (s: SpaceIntegritySeverity) => (s === 'broken' ? 0 : 1)\n return [...findings].sort(\n (a, b) => rank(a.severity) - rank(b.severity) || a.code.localeCompare(b.code) || (a.spaceId ?? '').localeCompare(b.spaceId ?? '')\n )\n}\n"]}
@@ -8,8 +8,8 @@ const shell_1 = require("@things-factory/shell");
8
8
  const twin_space_representation_js_1 = require("./twin-space-representation.js");
9
9
  /*
10
10
  * TwinSpaceArea — map/image 표현 위의 폴리곤 구역(design/ui/space-composition.md).
11
- * geometry=폴리곤(map=위경도, image=px). drillTo=클릭 시 이동할 board/표현(폴리곤→보드 모니터/시뮬 뷰).
12
- * binding=트윈 엔티티/데이터 태그(라이브 상태 결합). board 표현은 씬 컴포넌트가 이 역할을 대신함.
11
+ * geometry=폴리곤(map=위경도, image=px). drillTo=클릭 시 이동할 model/표현(폴리곤→보드 모니터/시뮬 뷰).
12
+ * binding=트윈 엔티티/데이터 태그(라이브 상태 결합). model 표현은 씬 컴포넌트가 이 역할을 대신함.
13
13
  */
14
14
  let TwinSpaceArea = class TwinSpaceArea {
15
15
  };
@@ -54,7 +54,7 @@ tslib_1.__decorate([
54
54
  ], TwinSpaceArea.prototype, "geometry", void 0);
55
55
  tslib_1.__decorate([
56
56
  (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
57
- (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Drill-down target { kind:"board"|"representation"|"space", id } — click navigates to a board monitor/sim view.' }),
57
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Drill-down target { kind:"board"|"representation"|"space", id } — click navigates to a model monitor/sim view.' }),
58
58
  tslib_1.__metadata("design:type", Object)
59
59
  ], TwinSpaceArea.prototype, "drillTo", void 0);
60
60
  tslib_1.__decorate([
@@ -80,6 +80,6 @@ tslib_1.__decorate([
80
80
  exports.TwinSpaceArea = TwinSpaceArea = tslib_1.__decorate([
81
81
  (0, typeorm_1.Entity)(),
82
82
  (0, typeorm_1.Index)('ix_twin_space_area_0', (e) => [e.domain, e.representation]),
83
- (0, type_graphql_1.ObjectType)({ description: 'A polygon region on a map/image representation, linked to a board (drill-down) and bound to a twin entity.' })
83
+ (0, type_graphql_1.ObjectType)({ description: 'A polygon region on a map/image representation, linked to a model (drill-down) and bound to a twin entity.' })
84
84
  ], TwinSpaceArea);
85
85
  //# sourceMappingURL=twin-space-area.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"twin-space-area.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space-area.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\nimport { TwinSpaceRepresentation } from './twin-space-representation.js'\n\n/*\n * TwinSpaceArea — map/image 표현 위의 폴리곤 구역(design/ui/space-composition.md).\n * geometry=폴리곤(map=위경도, image=px). drillTo=클릭 시 이동할 board/표현(폴리곤→보드 모니터/시뮬 뷰).\n * binding=트윈 엔티티/데이터 태그(라이브 상태 결합). board 표현은 씬 컴포넌트가 이 역할을 대신함.\n */\n@Entity()\n@Index('ix_twin_space_area_0', (e: TwinSpaceArea) => [e.domain, e.representation])\n@ObjectType({ description: 'A polygon region on a map/image representation, linked to a board (drill-down) and bound to a twin entity.' })\nexport class TwinSpaceArea {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinSpaceArea) => e.domain)\n domainId?: string\n\n @ManyToOne(type => TwinSpaceRepresentation)\n @Field(type => TwinSpaceRepresentation, { nullable: true, description: 'Owning representation.' })\n representation?: TwinSpaceRepresentation\n\n @RelationId((e: TwinSpaceArea) => e.representation)\n representationId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Area name.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Region type (building/floor/zone/yard/dock…).' })\n type?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Polygon geometry { kind:\"polygon\", points:[[x,y]…] } (map=lat/lon, image=px).' })\n geometry?: any\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Drill-down target { kind:\"board\"|\"representation\"|\"space\", id } — click navigates to a board monitor/sim view.' })\n drillTo?: any\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Twin binding { locationId?|entityId?|tag? } for live state.' })\n binding?: any\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Polygon decoration { stroke, fill, fillOpacity, strokeWidth } — line/fill color and opacity.' })\n style?: any\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n}\n"]}
1
+ {"version":3,"file":"twin-space-area.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space-area.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\nimport { TwinSpaceRepresentation } from './twin-space-representation.js'\n\n/*\n * TwinSpaceArea — map/image 표현 위의 폴리곤 구역(design/ui/space-composition.md).\n * geometry=폴리곤(map=위경도, image=px). drillTo=클릭 시 이동할 model/표현(폴리곤→보드 모니터/시뮬 뷰).\n * binding=트윈 엔티티/데이터 태그(라이브 상태 결합). model 표현은 씬 컴포넌트가 이 역할을 대신함.\n */\n@Entity()\n@Index('ix_twin_space_area_0', (e: TwinSpaceArea) => [e.domain, e.representation])\n@ObjectType({ description: 'A polygon region on a map/image representation, linked to a model (drill-down) and bound to a twin entity.' })\nexport class TwinSpaceArea {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinSpaceArea) => e.domain)\n domainId?: string\n\n @ManyToOne(type => TwinSpaceRepresentation)\n @Field(type => TwinSpaceRepresentation, { nullable: true, description: 'Owning representation.' })\n representation?: TwinSpaceRepresentation\n\n @RelationId((e: TwinSpaceArea) => e.representation)\n representationId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Area name.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Region type (building/floor/zone/yard/dock…).' })\n type?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Polygon geometry { kind:\"polygon\", points:[[x,y]…] } (map=lat/lon, image=px).' })\n geometry?: any\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Drill-down target { kind:\"board\"|\"representation\"|\"space\", id } — click navigates to a model monitor/sim view.' })\n drillTo?: any\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Twin binding { locationId?|entityId?|tag? } for live state.' })\n binding?: any\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Polygon decoration { stroke, fill, fillOpacity, strokeWidth } — line/fill color and opacity.' })\n style?: any\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n}\n"]}
@@ -7,9 +7,9 @@ const type_graphql_1 = require("type-graphql");
7
7
  const shell_1 = require("@things-factory/shell");
8
8
  const twin_space_js_1 = require("./twin-space.js");
9
9
  /*
10
- * TwinSpaceRepresentation — 공간을 보는 한 방식(map|image|board), 복수·1개 primary(공간구성 아키텍처, design/ui/space-composition.md).
11
- * config(simple-json): board={boardId}(board-service 참조) / image={attachmentId|boardId,width,height} / map={center,zoom,provider}.
12
- * 폴리곤 영역은 TwinSpaceArea(map/image), board 표현은 씬 컴포넌트가 대신.
10
+ * TwinSpaceRepresentation — 공간을 보는 한 방식(map|image|model), 복수·1개 primary(공간구성 아키텍처, design/ui/space-composition.md).
11
+ * config(simple-json): model={boardId}(board-service 참조) / image={attachmentId|boardId,width,height} / map={center,zoom,provider}.
12
+ * 폴리곤 영역은 TwinSpaceArea(map/image), model 표현은 씬 컴포넌트가 대신.
13
13
  */
14
14
  let TwinSpaceRepresentation = class TwinSpaceRepresentation {
15
15
  };
@@ -39,7 +39,7 @@ tslib_1.__decorate([
39
39
  ], TwinSpaceRepresentation.prototype, "spaceId", void 0);
40
40
  tslib_1.__decorate([
41
41
  (0, typeorm_1.Column)(),
42
- (0, type_graphql_1.Field)({ description: 'Representation type: map | image | board.' }),
42
+ (0, type_graphql_1.Field)({ description: 'Representation type: map | image | model.' }),
43
43
  tslib_1.__metadata("design:type", String)
44
44
  ], TwinSpaceRepresentation.prototype, "type", void 0);
45
45
  tslib_1.__decorate([
@@ -59,7 +59,7 @@ tslib_1.__decorate([
59
59
  ], TwinSpaceRepresentation.prototype, "isPrimary", void 0);
60
60
  tslib_1.__decorate([
61
61
  (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
62
- (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Type config: board {boardId} / image {attachmentId|boardId,width,height} / map {center,zoom,provider}.' }),
62
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Type config: model {boardId} / image {attachmentId|boardId,width,height} / map {center,zoom,provider}.' }),
63
63
  tslib_1.__metadata("design:type", Object)
64
64
  ], TwinSpaceRepresentation.prototype, "config", void 0);
65
65
  tslib_1.__decorate([
@@ -75,6 +75,6 @@ tslib_1.__decorate([
75
75
  exports.TwinSpaceRepresentation = TwinSpaceRepresentation = tslib_1.__decorate([
76
76
  (0, typeorm_1.Entity)(),
77
77
  (0, typeorm_1.Index)('ix_twin_space_rep_0', (e) => [e.domain, e.space]),
78
- (0, type_graphql_1.ObjectType)({ description: 'A way to view a Space (map | image | board). Multiple per space, one primary.' })
78
+ (0, type_graphql_1.ObjectType)({ description: 'A way to view a Space (map | image | model). Multiple per space, one primary.' })
79
79
  ], TwinSpaceRepresentation);
80
80
  //# sourceMappingURL=twin-space-representation.js.map