@things-factory/headless-twin 10.0.9 → 10.0.11

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 (354) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +2 -2
  2. package/dist-server/engine/canonical-ingest.js +24 -2
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/command-routing.js +1 -1
  5. package/dist-server/engine/command-routing.js.map +1 -1
  6. package/dist-server/engine/energy-topology.d.ts +86 -0
  7. package/dist-server/engine/energy-topology.js +144 -0
  8. package/dist-server/engine/energy-topology.js.map +1 -0
  9. package/dist-server/engine/index.d.ts +8 -3
  10. package/dist-server/engine/index.js +10 -3
  11. package/dist-server/engine/index.js.map +1 -1
  12. package/dist-server/engine/kpi-baseline.d.ts +1 -1
  13. package/dist-server/engine/kpi-baseline.js.map +1 -1
  14. package/dist-server/engine/kpi-fold.d.ts +137 -1
  15. package/dist-server/engine/kpi-fold.js +225 -0
  16. package/dist-server/engine/kpi-fold.js.map +1 -1
  17. package/dist-server/engine/kpi-query.d.ts +1 -1
  18. package/dist-server/engine/kpi-query.js +212 -18
  19. package/dist-server/engine/kpi-query.js.map +1 -1
  20. package/dist-server/engine/kpi-target.d.ts +17 -0
  21. package/dist-server/engine/kpi-target.js +24 -2
  22. package/dist-server/engine/kpi-target.js.map +1 -1
  23. package/dist-server/engine/live-feed-registry.d.ts +25 -0
  24. package/dist-server/engine/live-feed-registry.js +51 -0
  25. package/dist-server/engine/live-feed-registry.js.map +1 -0
  26. package/dist-server/engine/load-meter.d.ts +181 -0
  27. package/dist-server/engine/load-meter.js +267 -0
  28. package/dist-server/engine/load-meter.js.map +1 -0
  29. package/dist-server/engine/local-declarations.d.ts +262 -0
  30. package/dist-server/engine/local-declarations.js +528 -0
  31. package/dist-server/engine/local-declarations.js.map +1 -0
  32. package/dist-server/engine/model-basis.d.ts +39 -6
  33. package/dist-server/engine/model-basis.js +69 -9
  34. package/dist-server/engine/model-basis.js.map +1 -1
  35. package/dist-server/engine/{board-vocabulary.d.ts → model-vocabulary.d.ts} +4 -4
  36. package/dist-server/engine/{board-vocabulary.js → model-vocabulary.js} +37 -19
  37. package/dist-server/engine/model-vocabulary.js.map +1 -0
  38. package/dist-server/engine/oee-accumulator.d.ts +28 -0
  39. package/dist-server/engine/oee-accumulator.js +26 -1
  40. package/dist-server/engine/oee-accumulator.js.map +1 -1
  41. package/dist-server/engine/operation-basis.d.ts +32 -0
  42. package/dist-server/engine/operation-basis.js +87 -0
  43. package/dist-server/engine/operation-basis.js.map +1 -0
  44. package/dist-server/engine/property-effects.d.ts +30 -0
  45. package/dist-server/engine/property-effects.js +192 -0
  46. package/dist-server/engine/property-effects.js.map +1 -0
  47. package/dist-server/engine/runtime-key.d.ts +1 -1
  48. package/dist-server/engine/runtime-key.js +1 -1
  49. package/dist-server/engine/runtime-key.js.map +1 -1
  50. package/dist-server/engine/spec-coverage.js +3 -3
  51. package/dist-server/engine/spec-coverage.js.map +1 -1
  52. package/dist-server/engine/state-axes.d.ts +7 -2
  53. package/dist-server/engine/state-axes.js +8 -3
  54. package/dist-server/engine/state-axes.js.map +1 -1
  55. package/dist-server/engine/structure-diff.js +1 -1
  56. package/dist-server/engine/structure-diff.js.map +1 -1
  57. package/dist-server/engine/travel-estimator.d.ts +9 -2
  58. package/dist-server/engine/travel-estimator.js +11 -6
  59. package/dist-server/engine/travel-estimator.js.map +1 -1
  60. package/dist-server/engine/twin-engine.d.ts +332 -47
  61. package/dist-server/engine/twin-engine.js +1314 -279
  62. package/dist-server/engine/twin-engine.js.map +1 -1
  63. package/dist-server/engine/warm-start.d.ts +38 -4
  64. package/dist-server/engine/warm-start.js +38 -9
  65. package/dist-server/engine/warm-start.js.map +1 -1
  66. package/dist-server/index.d.ts +1 -0
  67. package/dist-server/index.js +17 -11
  68. package/dist-server/index.js.map +1 -1
  69. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
  70. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
  71. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
  72. package/dist-server/migrations/index.d.ts +2 -0
  73. package/dist-server/migrations/index.js +10 -0
  74. package/dist-server/migrations/index.js.map +1 -0
  75. package/dist-server/service/index.d.ts +3 -2
  76. package/dist-server/service/index.js +25 -17
  77. package/dist-server/service/index.js.map +1 -1
  78. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  79. package/dist-server/service/reference/discovery-result.js +2 -2
  80. package/dist-server/service/reference/discovery-result.js.map +1 -1
  81. package/dist-server/service/reference/ingest-space.d.ts +24 -1
  82. package/dist-server/service/reference/ingest-space.js +17 -1
  83. package/dist-server/service/reference/ingest-space.js.map +1 -1
  84. package/dist-server/service/reference/reference-live.d.ts +23 -0
  85. package/dist-server/service/reference/reference-live.js +121 -4
  86. package/dist-server/service/reference/reference-live.js.map +1 -1
  87. package/dist-server/service/reference/reference-master.d.ts +194 -13
  88. package/dist-server/service/reference/reference-master.js +159 -28
  89. package/dist-server/service/reference/reference-master.js.map +1 -1
  90. package/dist-server/service/reference/reference-resolver.d.ts +31 -1
  91. package/dist-server/service/reference/reference-resolver.js +223 -12
  92. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  93. package/dist-server/service/reference/template-registry.d.ts +19 -3
  94. package/dist-server/service/reference/template-registry.js.map +1 -1
  95. package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
  96. package/dist-server/service/twin-audit/command-audit.js +53 -0
  97. package/dist-server/service/twin-audit/command-audit.js.map +1 -0
  98. package/dist-server/service/twin-audit/index.d.ts +4 -0
  99. package/dist-server/service/twin-audit/index.js +8 -0
  100. package/dist-server/service/twin-audit/index.js.map +1 -0
  101. package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
  102. package/dist-server/service/twin-audit/twin-audit-event.js +141 -0
  103. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
  104. package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
  105. package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
  106. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
  107. package/dist-server/service/twin-control/twin-control-mutation.js +21 -1
  108. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  109. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  110. package/dist-server/service/twin-event/twin-event-keys.js +1 -1
  111. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  112. package/dist-server/service/twin-event/twin-event.d.ts +14 -5
  113. package/dist-server/service/twin-event/twin-event.js +45 -13
  114. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  115. package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
  116. package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
  117. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
  118. package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
  119. package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
  120. package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
  121. package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
  122. package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
  123. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  124. package/dist-server/service/twin-forecast/twin-forecast-query.js +217 -38
  125. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  126. package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
  127. package/dist-server/service/twin-instance/twin-instance.js +66 -20
  128. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  129. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -2
  130. package/dist-server/service/twin-journal/twin-journal-query.js +71 -9
  131. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +3 -5
  133. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +24 -27
  134. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  135. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  136. package/dist-server/service/twin-metrics/twin-metrics-query.js +62 -0
  137. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  138. package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
  139. package/dist-server/service/twin-model/epcis-coverage.js +174 -0
  140. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
  141. package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
  142. package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
  143. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
  144. package/dist-server/service/twin-model/index.d.ts +13 -0
  145. package/dist-server/service/twin-model/index.js +29 -0
  146. package/dist-server/service/twin-model/index.js.map +1 -0
  147. package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
  148. package/dist-server/service/twin-model/isa95-coverage.js +155 -0
  149. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
  150. package/dist-server/service/twin-model/project-structure.d.ts +16 -0
  151. package/dist-server/service/twin-model/project-structure.js +173 -0
  152. package/dist-server/service/twin-model/project-structure.js.map +1 -0
  153. package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
  154. package/dist-server/service/twin-model/standard-coverage.js +37 -0
  155. package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
  156. package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
  157. package/dist-server/service/twin-model/twin-equipment.js +134 -0
  158. package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
  160. package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
  161. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
  162. package/dist-server/service/twin-model/twin-location.d.ts +58 -0
  163. package/dist-server/service/twin-model/twin-location.js +130 -0
  164. package/dist-server/service/twin-model/twin-location.js.map +1 -0
  165. package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
  166. package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
  167. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
  168. package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
  169. package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
  170. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
  171. package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
  172. package/dist-server/service/twin-model/twin-model-query.js +551 -0
  173. package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
  174. package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
  175. package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
  176. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
  177. package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
  178. package/dist-server/service/twin-model/twin-operation.js +137 -0
  179. package/dist-server/service/twin-model/twin-operation.js.map +1 -0
  180. package/dist-server/service/twin-space/move-space.d.ts +65 -0
  181. package/dist-server/service/twin-space/move-space.js +166 -0
  182. package/dist-server/service/twin-space/move-space.js.map +1 -0
  183. package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
  184. package/dist-server/service/twin-space/space-integrity.js +182 -0
  185. package/dist-server/service/twin-space/space-integrity.js.map +1 -0
  186. package/dist-server/service/twin-space/twin-space-area.js +4 -4
  187. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  188. package/dist-server/service/twin-space/twin-space-representation.js +6 -6
  189. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  190. package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
  191. package/dist-server/service/twin-space/twin-space-resolver.js +356 -18
  192. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  193. package/dist-server/service/twin-space/twin-space.d.ts +17 -1
  194. package/dist-server/service/twin-space/twin-space.js +15 -5
  195. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  196. package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
  197. package/dist-server/service/twin-structure/twin-structure.js +8 -8
  198. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  199. package/dist-shared/axis-read.d.ts +12 -0
  200. package/dist-shared/axis-read.js +32 -0
  201. package/dist-shared/axis-read.js.map +1 -0
  202. package/{dist-server/engine → dist-shared}/entity-delta.d.ts +3 -0
  203. package/{dist-server/engine → dist-shared}/entity-delta.js +102 -2
  204. package/dist-shared/entity-delta.js.map +1 -0
  205. package/dist-shared/kpi-broadcast.js.map +1 -0
  206. package/dist-shared/twin-level.js.map +1 -0
  207. package/package.json +14 -12
  208. package/server/engine/canonical-ingest.ts +36 -4
  209. package/server/engine/command-routing.ts +1 -1
  210. package/server/engine/energy-topology.ts +189 -0
  211. package/server/engine/index.ts +10 -3
  212. package/server/engine/kpi-baseline.ts +1 -1
  213. package/server/engine/kpi-fold.ts +389 -1
  214. package/server/engine/kpi-query.ts +214 -19
  215. package/server/engine/kpi-target.ts +24 -2
  216. package/server/engine/live-feed-registry.ts +58 -0
  217. package/server/engine/load-meter.ts +384 -0
  218. package/server/engine/local-declarations.ts +700 -0
  219. package/server/engine/model-basis.ts +78 -10
  220. package/server/engine/{board-vocabulary.ts → model-vocabulary.ts} +36 -15
  221. package/server/engine/oee-accumulator.ts +34 -1
  222. package/server/engine/operation-basis.ts +100 -0
  223. package/server/engine/property-effects.ts +199 -0
  224. package/server/engine/runtime-key.ts +1 -1
  225. package/server/engine/spec-coverage.ts +3 -3
  226. package/server/engine/state-axes.ts +8 -3
  227. package/server/engine/structure-diff.ts +1 -1
  228. package/server/engine/travel-estimator.ts +11 -6
  229. package/server/engine/twin-engine.ts +1414 -274
  230. package/server/engine/warm-start.ts +83 -12
  231. package/server/index.ts +13 -10
  232. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
  233. package/server/migrations/index.ts +7 -0
  234. package/server/service/index.ts +9 -1
  235. package/server/service/reference/discovery-result.ts +2 -2
  236. package/server/service/reference/ingest-space.ts +35 -1
  237. package/server/service/reference/reference-live.ts +119 -4
  238. package/server/service/reference/reference-master.ts +276 -35
  239. package/server/service/reference/reference-resolver.ts +232 -12
  240. package/server/service/reference/template-registry.ts +20 -5
  241. package/server/service/twin-audit/command-audit.ts +81 -0
  242. package/server/service/twin-audit/index.ts +5 -0
  243. package/server/service/twin-audit/twin-audit-event.ts +138 -0
  244. package/server/service/twin-audit/twin-audit-query.ts +72 -0
  245. package/server/service/twin-control/twin-control-mutation.ts +22 -1
  246. package/server/service/twin-event/twin-event-keys.ts +1 -1
  247. package/server/service/twin-event/twin-event.ts +59 -14
  248. package/server/service/twin-forecast/forecast-metrics.ts +60 -0
  249. package/server/service/twin-forecast/forecast-tuning.ts +40 -0
  250. package/server/service/twin-forecast/gap-analytics.ts +48 -9
  251. package/server/service/twin-forecast/twin-forecast-query.ts +208 -36
  252. package/server/service/twin-instance/twin-instance.ts +126 -25
  253. package/server/service/twin-journal/twin-journal-query.ts +74 -10
  254. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +23 -22
  255. package/server/service/twin-metrics/twin-metrics-query.ts +59 -2
  256. package/server/service/twin-model/epcis-coverage.ts +195 -0
  257. package/server/service/twin-model/iec61850-coverage.ts +117 -0
  258. package/server/service/twin-model/index.ts +25 -0
  259. package/server/service/twin-model/isa95-coverage.ts +190 -0
  260. package/server/service/twin-model/project-structure.ts +199 -0
  261. package/server/service/twin-model/standard-coverage.ts +35 -0
  262. package/server/service/twin-model/twin-equipment.ts +157 -0
  263. package/server/service/twin-model/twin-lineage-query.ts +193 -0
  264. package/server/service/twin-model/twin-location.ts +147 -0
  265. package/server/service/twin-model/twin-model-item-query.ts +848 -0
  266. package/server/service/twin-model/twin-model-mutation.ts +503 -0
  267. package/server/service/twin-model/twin-model-query.ts +537 -0
  268. package/server/service/twin-model/twin-model-tree-query.ts +180 -0
  269. package/server/service/twin-model/twin-operation.ts +157 -0
  270. package/server/service/twin-space/move-space.ts +262 -0
  271. package/server/service/twin-space/space-integrity.ts +293 -0
  272. package/server/service/twin-space/twin-space-area.ts +4 -4
  273. package/server/service/twin-space/twin-space-representation.ts +6 -6
  274. package/server/service/twin-space/twin-space-resolver.ts +355 -21
  275. package/server/service/twin-space/twin-space.ts +40 -6
  276. package/server/service/twin-structure/twin-structure.ts +10 -10
  277. package/shared/axis-read.ts +28 -0
  278. package/{server/engine → shared}/entity-delta.ts +96 -1
  279. package/test/adopt-structure-live.test.ts +133 -0
  280. package/test/attention-digest.test.ts +3 -3
  281. package/test/axis-read.test.ts +87 -0
  282. package/test/boot-resume.test.ts +205 -0
  283. package/test/canonical-ingest-vocabularies.test.ts +118 -0
  284. package/test/capability-mapping.test.ts +1 -1
  285. package/test/column-type-portability.test.ts +122 -0
  286. package/test/command-routing.test.ts +1 -1
  287. package/test/declaration-reaches-model.test.ts +178 -0
  288. package/test/discovery-result.test.ts +1 -1
  289. package/test/energy-topology.test.ts +113 -0
  290. package/test/entity-delta.test.ts +171 -1
  291. package/test/event-time-column.test.ts +67 -0
  292. package/test/forecast-metrics.test.ts +71 -0
  293. package/test/forecast-tuning.test.ts +42 -0
  294. package/test/gap-analytics.test.ts +34 -7
  295. package/test/ingest-bench.test.ts +6 -6
  296. package/test/ingest-running-guard.test.ts +136 -0
  297. package/test/ingest-space.test.ts +29 -1
  298. package/test/instance-cache-lifecycle.test.ts +110 -0
  299. package/test/kernel-kind-guard.test.ts +96 -0
  300. package/test/kpi-baseline-db.test.ts +3 -1
  301. package/test/kpi-baseline.test.ts +3 -3
  302. package/test/kpi-fold.test.ts +393 -2
  303. package/test/kpi-query-bench.test.ts +5 -3
  304. package/test/lineage-survives-restart.test.ts +203 -0
  305. package/test/live-feed-registry.test.ts +67 -0
  306. package/test/live-kernel-facts.test.ts +161 -0
  307. package/test/live-mirror-parity.test.ts +22 -15
  308. package/test/load-meter.test.ts +314 -0
  309. package/test/local-declarations.test.ts +554 -0
  310. package/test/master-to-twin.test.ts +121 -33
  311. package/test/model-basis.test.ts +56 -1
  312. package/test/{board-vocabulary.test.ts → model-vocabulary.test.ts} +25 -25
  313. package/test/move-space.test.ts +149 -0
  314. package/test/mutation-gate.test.ts +19 -5
  315. package/test/oee-accumulator.test.ts +26 -7
  316. package/test/operation-basis.test.ts +100 -0
  317. package/test/operations-capability-db.test.ts +165 -0
  318. package/test/project-structure-db.test.ts +226 -0
  319. package/test/projection-reaches-screen.test.ts +272 -0
  320. package/test/property-effects.test.ts +96 -0
  321. package/test/resync-origin-site.test.ts +77 -0
  322. package/test/scale-twin-bench.test.ts +5 -5
  323. package/test/snapshot-freshness.test.ts +60 -0
  324. package/test/space-integrity.test.ts +223 -0
  325. package/test/standard-coverage.test.ts +94 -0
  326. package/test/state-axes.test.ts +1 -1
  327. package/test/streamline-e2e.test.ts +14 -14
  328. package/test/structure-revision-db.test.ts +11 -11
  329. package/test/tenant-registry-db.test.ts +5 -5
  330. package/test/twin-audit.test.ts +83 -0
  331. package/test/twin-model-item-db.test.ts +275 -0
  332. package/test/twin-model-tree-db.test.ts +176 -0
  333. package/test/twin-origin-resync.test.ts +114 -0
  334. package/test/warm-start-seam.test.ts +6 -6
  335. package/test/warm-start.test.ts +88 -3
  336. package/tsconfig.json +6 -1
  337. package/tsconfig.shared.json +23 -0
  338. package/tsconfig.shared.tsbuildinfo +1 -0
  339. package/tsconfig.tsbuildinfo +1 -0
  340. package/dist-server/engine/board-vocabulary.js.map +0 -1
  341. package/dist-server/engine/entity-delta.js.map +0 -1
  342. package/dist-server/engine/kpi-broadcast.js.map +0 -1
  343. package/dist-server/engine/twin-level.js.map +0 -1
  344. package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
  345. package/dist-server/service/twin-event/backfill-keys.js +0 -63
  346. package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
  347. package/dist-server/tsconfig.tsbuildinfo +0 -1
  348. package/server/service/twin-event/backfill-keys.ts +0 -72
  349. /package/{dist-server/engine → dist-shared}/kpi-broadcast.d.ts +0 -0
  350. /package/{dist-server/engine → dist-shared}/kpi-broadcast.js +0 -0
  351. /package/{dist-server/engine → dist-shared}/twin-level.d.ts +0 -0
  352. /package/{dist-server/engine → dist-shared}/twin-level.js +0 -0
  353. /package/{server/engine → shared}/kpi-broadcast.ts +0 -0
  354. /package/{server/engine → shared}/twin-level.ts +0 -0
@@ -16,6 +16,29 @@ export declare const INGEST_SPACE_NOT_FOUND = "space-not-found";
16
16
  * @throws 요청한 공간이 없으면 — 조용히 새로 만들지 않는다.
17
17
  */
18
18
  export declare function resolveIngestSpace(masterSpaceId: string, requested: string | undefined | null, requestedExists: boolean): IngestSpaceChoice;
19
+ /**
20
+ * 실 시스템 import 가 현장을 고를 때의 거절 판정 — **순수**.
21
+ *
22
+ * 레퍼런스 하나는 사이트를 여럿 발견할 수 있고(`discoverSites`), 그 사이트마다 트윈이 하나씩 생긴다.
23
+ * 그런데 "이 현장에 더한다" 는 **한 곳**을 가리키는 말이다. 사이트 여럿을 한 공간에 넣으면 서로 다른
24
+ * 현장의 구역·랜드마크가 id 로 합쳐지고(`unionById`), 그 사실을 알아차릴 때는 이미 이벤트가 양쪽에
25
+ * 쌓여 있다 — 손으로 되돌릴 수 없는 종류다. 그래서 **묻지 않고 합치지 않고, 거절한다.**
26
+ *
27
+ * 공간을 고르지 않으면 지금까지의 거동 그대로다: 사이트마다 마스터가 파생한 공간으로 들어간다.
28
+ */
29
+ export type ImportSpaceRefusalCode = 'space-with-multiple-sites';
30
+ export interface ImportSpaceRefusal {
31
+ code: ImportSpaceRefusalCode;
32
+ params?: Record<string, string | number>;
33
+ /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다(발견 오류와 같은 규약). */
34
+ message: string;
35
+ }
36
+ /**
37
+ * @param siteCount 실제로 만들 사이트 수(필터 적용 후).
38
+ * @param requested 사용자가 고른 기존 공간 id. 없으면 판정할 것이 없다.
39
+ * @returns 거절 사유. 없으면 진행해도 된다.
40
+ */
41
+ export declare function refuseImportSpace(siteCount: number, requested?: string | null): ImportSpaceRefusal | undefined;
19
42
  /** 구역·랜드마크처럼 id 로 합쳐지는 것들. */
20
43
  interface Identified {
21
44
  id?: string;
@@ -24,7 +47,7 @@ interface Identified {
24
47
  * 합치는 쪽과 기존 쪽이 같은 id 를 쓰는 것들을 찾아 경고 문장을 만든다.
25
48
  *
26
49
  * 정당한 경우(같은 현장의 같은 구역)와 사고(서로 다른 구역이 우연히 같은 이름)를 기계가 가릴 수는
27
- * 없다. 가릴 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**
50
+ * 없다. 구분할 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**
28
51
  */
29
52
  export declare function collidingIds(existing: readonly Identified[] | undefined | null, incoming: readonly Identified[] | undefined | null): string[];
30
53
  export {};
@@ -23,6 +23,7 @@
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.INGEST_SPACE_NOT_FOUND = void 0;
25
25
  exports.resolveIngestSpace = resolveIngestSpace;
26
+ exports.refuseImportSpace = refuseImportSpace;
26
27
  exports.collidingIds = collidingIds;
27
28
  /** 거절 사유 — 호출측이 문장을 만들 때 쓴다(언어 중립 코드). */
28
29
  exports.INGEST_SPACE_NOT_FOUND = 'space-not-found';
@@ -46,11 +47,26 @@ function resolveIngestSpace(masterSpaceId, requested, requestedExists) {
46
47
  /* 마스터가 이미 그 공간을 가리키면 합치기가 아니다 — 같은 공간의 재인제스트다. */
47
48
  return { spaceId: want, joined: want !== masterSpaceId };
48
49
  }
50
+ /**
51
+ * @param siteCount 실제로 만들 사이트 수(필터 적용 후).
52
+ * @param requested 사용자가 고른 기존 공간 id. 없으면 판정할 것이 없다.
53
+ * @returns 거절 사유. 없으면 진행해도 된다.
54
+ */
55
+ function refuseImportSpace(siteCount, requested) {
56
+ const want = (requested ?? '').trim();
57
+ if (!want || siteCount <= 1)
58
+ return undefined;
59
+ return {
60
+ code: 'space-with-multiple-sites',
61
+ params: { spaceId: want, count: siteCount },
62
+ message: `cannot co-locate ${siteCount} sites into space "${want}" — pick one site, or omit the space so each site gets its own`
63
+ };
64
+ }
49
65
  /**
50
66
  * 합치는 쪽과 기존 쪽이 같은 id 를 쓰는 것들을 찾아 경고 문장을 만든다.
51
67
  *
52
68
  * 정당한 경우(같은 현장의 같은 구역)와 사고(서로 다른 구역이 우연히 같은 이름)를 기계가 가릴 수는
53
- * 없다. 가릴 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**
69
+ * 없다. 구분할 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**
54
70
  */
55
71
  function collidingIds(existing, incoming) {
56
72
  const have = new Set((existing ?? []).map(x => x?.id).filter(Boolean));
@@ -1 +1 @@
1
- {"version":3,"file":"ingest-space.js","sourceRoot":"","sources":["../../../server/service/reference/ingest-space.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAqBH,gDAUC;AAaD,oCAKC;AAvCD,0CAA0C;AAC7B,QAAA,sBAAsB,GAAG,iBAAiB,CAAA;AAEvD;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAAC,aAAqB,EAAE,SAAoC,EAAE,eAAwB;IACtH,MAAM,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACrC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC3D,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,UAAU,IAAI,iEAAiE,CAAC,CACnG;QAAC,CAAS,CAAC,IAAI,GAAG,8BAAsB,CAAA;QACzC,MAAM,CAAC,CAAA;IACT,CAAC;IACD,kDAAkD;IAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,KAAK,aAAa,EAAE,CAAA;AAC1D,CAAC;AAOD;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,QAAkD,EAAE,QAAkD;IACjI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;AACzB,CAAC","sourcesContent":["/*\n * 인제스트가 어느 공간으로 들어갈지 정한다 — **순수**.\n *\n * ── 왜 있나 (2026-08-05) ────────────────────────────────────────────────────\n * 한 현실(공장·물류센터)을 여러 렌즈가 비춘다: 창고(WMS)·공정(MES)·야드(YMS) 가 같은 부지에 있다.\n * 엔진은 이미 그것을 지원한다 — 같은 `spaceId` 로 들어오면 공간 content 를 병합하고 기존 값을 이긴다.\n *\n * 그런데 **만드는 흐름에 그 선택이 없었다.** 템플릿은 `space.spaceId` 를 정하지 않으므로 항상\n * `<source>-site` 로 파생돼, 새 트윈은 늘 새 현장처럼 생겼다. 데이터로는 가능한 일이 저작으로는\n * 불가능했다.\n *\n * ── 없는 공간에 조용히 새 공간을 만들지 않는다 ────────────────────────────────\n * \"이 현장에 더한다\" 는 요청이 오타·낡은 목록으로 빗나갔을 때 새 공간을 만들어 주면, 사용자는\n * 합쳤다고 믿고 화면에서는 따로 놀게 된다 — 나중에 두 현장을 손으로 합쳐야 하고, 그때는 이미\n * 이벤트가 양쪽에 쌓여 있다. 그래서 **모르는 공간이면 거절한다.**\n *\n * ── 같은 id 를 쓰는 구역은 조용히 합쳐지므로 알린다 ──────────────────────────\n * 공간 content 의 구역·랜드마크는 id 로 합집합을 만든다(`unionById`). 두 렌즈가 우연히 같은 구역\n * id(`a-yard` 따위)를 쓰면 서로 다른 구역이 하나로 접힌다. 막을 일은 아니다(같은 현장의 같은 구역을\n * 가리키는 정당한 경우가 많다) — 다만 **모르고 지나가지 않게** 경고로 돌려준다.\n */\n\n/** 공간 판정 결과. */\nexport interface IngestSpaceChoice {\n /** 실제로 쓸 공간 id. */\n spaceId: string\n /** 사용자가 기존 현장에 합치기를 골랐나(마스터가 파생한 공간과 다른 곳으로 들어가나). */\n joined: boolean\n}\n\n/** 거절 사유 — 호출측이 문장을 만들 때 쓴다(언어 중립 코드). */\nexport const INGEST_SPACE_NOT_FOUND = 'space-not-found'\n\n/**\n * 요청받은 공간과 마스터가 파생한 공간 중 무엇으로 들어갈지 정한다.\n *\n * @param masterSpaceId 마스터에서 파생된 공간 id(요청이 없을 때의 기본).\n * @param requested 사용자가 고른 기존 공간 id. 없으면 빈 값/undefined.\n * @param requestedExists 그 공간이 이 테넌트에 실제로 있나(호출측이 조회해 넘긴다).\n * @throws 요청한 공간이 없으면 — 조용히 새로 만들지 않는다.\n */\nexport function resolveIngestSpace(masterSpaceId: string, requested: string | undefined | null, requestedExists: boolean): IngestSpaceChoice {\n const want = (requested ?? '').trim()\n if (!want) return { spaceId: masterSpaceId, joined: false }\n if (!requestedExists) {\n const e = new Error(`space \"${want}\" not found — cannot co-locate into a space that does not exist`)\n ;(e as any).code = INGEST_SPACE_NOT_FOUND\n throw e\n }\n /* 마스터가 이미 그 공간을 가리키면 합치기가 아니다 — 같은 공간의 재인제스트다. */\n return { spaceId: want, joined: want !== masterSpaceId }\n}\n\n/** 구역·랜드마크처럼 id 로 합쳐지는 것들. */\ninterface Identified {\n id?: string\n}\n\n/**\n * 합치는 쪽과 기존 쪽이 같은 id 를 쓰는 것들을 찾아 경고 문장을 만든다.\n *\n * 정당한 경우(같은 현장의 같은 구역)와 사고(서로 다른 구역이 우연히 같은 이름)를 기계가 가릴 수는\n * 없다. 가릴 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**\n */\nexport function collidingIds(existing: readonly Identified[] | undefined | null, incoming: readonly Identified[] | undefined | null): string[] {\n const have = new Set((existing ?? []).map(x => x?.id).filter(Boolean) as string[])\n const hits = new Set<string>()\n for (const x of incoming ?? []) if (x?.id && have.has(x.id)) hits.add(x.id)\n return [...hits].sort()\n}\n"]}
1
+ {"version":3,"file":"ingest-space.js","sourceRoot":"","sources":["../../../server/service/reference/ingest-space.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAqBH,gDAUC;AA0BD,8CAQC;AAaD,oCAKC;AAzED,0CAA0C;AAC7B,QAAA,sBAAsB,GAAG,iBAAiB,CAAA;AAEvD;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAAC,aAAqB,EAAE,SAAoC,EAAE,eAAwB;IACtH,MAAM,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACrC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC3D,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,UAAU,IAAI,iEAAiE,CAAC,CACnG;QAAC,CAAS,CAAC,IAAI,GAAG,8BAAsB,CAAA;QACzC,MAAM,CAAC,CAAA;IACT,CAAC;IACD,kDAAkD;IAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,KAAK,aAAa,EAAE,CAAA;AAC1D,CAAC;AAqBD;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,SAAiB,EAAE,SAAyB;IAC5E,MAAM,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACrC,IAAI,CAAC,IAAI,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IAC7C,OAAO;QACL,IAAI,EAAE,2BAA2B;QACjC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE;QAC3C,OAAO,EAAE,oBAAoB,SAAS,sBAAsB,IAAI,gEAAgE;KACjI,CAAA;AACH,CAAC;AAOD;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,QAAkD,EAAE,QAAkD;IACjI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;AACzB,CAAC","sourcesContent":["/*\n * 인제스트가 어느 공간으로 들어갈지 정한다 — **순수**.\n *\n * ── 왜 있나 (2026-08-05) ────────────────────────────────────────────────────\n * 한 현실(공장·물류센터)을 여러 렌즈가 비춘다: 창고(WMS)·공정(MES)·야드(YMS) 가 같은 부지에 있다.\n * 엔진은 이미 그것을 지원한다 — 같은 `spaceId` 로 들어오면 공간 content 를 병합하고 기존 값을 이긴다.\n *\n * 그런데 **만드는 흐름에 그 선택이 없었다.** 템플릿은 `space.spaceId` 를 정하지 않으므로 항상\n * `<source>-site` 로 파생돼, 새 트윈은 늘 새 현장처럼 생겼다. 데이터로는 가능한 일이 저작으로는\n * 불가능했다.\n *\n * ── 없는 공간에 조용히 새 공간을 만들지 않는다 ────────────────────────────────\n * \"이 현장에 더한다\" 는 요청이 오타·낡은 목록으로 빗나갔을 때 새 공간을 만들어 주면, 사용자는\n * 합쳤다고 믿고 화면에서는 따로 놀게 된다 — 나중에 두 현장을 손으로 합쳐야 하고, 그때는 이미\n * 이벤트가 양쪽에 쌓여 있다. 그래서 **모르는 공간이면 거절한다.**\n *\n * ── 같은 id 를 쓰는 구역은 조용히 합쳐지므로 알린다 ──────────────────────────\n * 공간 content 의 구역·랜드마크는 id 로 합집합을 만든다(`unionById`). 두 렌즈가 우연히 같은 구역\n * id(`a-yard` 따위)를 쓰면 서로 다른 구역이 하나로 접힌다. 막을 일은 아니다(같은 현장의 같은 구역을\n * 가리키는 정당한 경우가 많다) — 다만 **모르고 지나가지 않게** 경고로 돌려준다.\n */\n\n/** 공간 판정 결과. */\nexport interface IngestSpaceChoice {\n /** 실제로 쓸 공간 id. */\n spaceId: string\n /** 사용자가 기존 현장에 합치기를 골랐나(마스터가 파생한 공간과 다른 곳으로 들어가나). */\n joined: boolean\n}\n\n/** 거절 사유 — 호출측이 문장을 만들 때 쓴다(언어 중립 코드). */\nexport const INGEST_SPACE_NOT_FOUND = 'space-not-found'\n\n/**\n * 요청받은 공간과 마스터가 파생한 공간 중 무엇으로 들어갈지 정한다.\n *\n * @param masterSpaceId 마스터에서 파생된 공간 id(요청이 없을 때의 기본).\n * @param requested 사용자가 고른 기존 공간 id. 없으면 빈 값/undefined.\n * @param requestedExists 그 공간이 이 테넌트에 실제로 있나(호출측이 조회해 넘긴다).\n * @throws 요청한 공간이 없으면 — 조용히 새로 만들지 않는다.\n */\nexport function resolveIngestSpace(masterSpaceId: string, requested: string | undefined | null, requestedExists: boolean): IngestSpaceChoice {\n const want = (requested ?? '').trim()\n if (!want) return { spaceId: masterSpaceId, joined: false }\n if (!requestedExists) {\n const e = new Error(`space \"${want}\" not found — cannot co-locate into a space that does not exist`)\n ;(e as any).code = INGEST_SPACE_NOT_FOUND\n throw e\n }\n /* 마스터가 이미 그 공간을 가리키면 합치기가 아니다 — 같은 공간의 재인제스트다. */\n return { spaceId: want, joined: want !== masterSpaceId }\n}\n\n/**\n * 실 시스템 import 가 현장을 고를 때의 거절 판정 — **순수**.\n *\n * 레퍼런스 하나는 사이트를 여럿 발견할 수 있고(`discoverSites`), 그 사이트마다 트윈이 하나씩 생긴다.\n * 그런데 \"이 현장에 더한다\" 는 **한 곳**을 가리키는 말이다. 사이트 여럿을 한 공간에 넣으면 서로 다른\n * 현장의 구역·랜드마크가 id 로 합쳐지고(`unionById`), 그 사실을 알아차릴 때는 이미 이벤트가 양쪽에\n * 쌓여 있다 — 손으로 되돌릴 수 없는 종류다. 그래서 **묻지 않고 합치지 않고, 거절한다.**\n *\n * 공간을 고르지 않으면 지금까지의 거동 그대로다: 사이트마다 마스터가 파생한 공간으로 들어간다.\n */\nexport type ImportSpaceRefusalCode = 'space-with-multiple-sites'\n\nexport interface ImportSpaceRefusal {\n code: ImportSpaceRefusalCode\n params?: Record<string, string | number>\n /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다(발견 오류와 같은 규약). */\n message: string\n}\n\n/**\n * @param siteCount 실제로 만들 사이트 수(필터 적용 후).\n * @param requested 사용자가 고른 기존 공간 id. 없으면 판정할 것이 없다.\n * @returns 거절 사유. 없으면 진행해도 된다.\n */\nexport function refuseImportSpace(siteCount: number, requested?: string | null): ImportSpaceRefusal | undefined {\n const want = (requested ?? '').trim()\n if (!want || siteCount <= 1) return undefined\n return {\n code: 'space-with-multiple-sites',\n params: { spaceId: want, count: siteCount },\n message: `cannot co-locate ${siteCount} sites into space \"${want}\" — pick one site, or omit the space so each site gets its own`\n }\n}\n\n/** 구역·랜드마크처럼 id 로 합쳐지는 것들. */\ninterface Identified {\n id?: string\n}\n\n/**\n * 합치는 쪽과 기존 쪽이 같은 id 를 쓰는 것들을 찾아 경고 문장을 만든다.\n *\n * 정당한 경우(같은 현장의 같은 구역)와 사고(서로 다른 구역이 우연히 같은 이름)를 기계가 가릴 수는\n * 없다. 구분할 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**\n */\nexport function collidingIds(existing: readonly Identified[] | undefined | null, incoming: readonly Identified[] | undefined | null): string[] {\n const have = new Set((existing ?? []).map(x => x?.id).filter(Boolean) as string[])\n const hits = new Set<string>()\n for (const x of incoming ?? []) if (x?.id && have.has(x.id)) hits.add(x.id)\n return [...hits].sort()\n}\n"]}
@@ -8,3 +8,26 @@ export declare function startReferenceLiveFeed(domainId: string, adapterType: st
8
8
  export declare function stopReferenceLiveFeed(instanceId: string): boolean;
9
9
  /** 활성 피드 목록(관측). */
10
10
  export declare function listReferenceLiveFeeds(): string[];
11
+ /**
12
+ * 부팅 때 **끊어진 피드를 다시 붙인다** — 도는 미러 트윈에 계측이 계속 들어오게.
13
+ *
14
+ * ── 무엇이 조용히 끊겼나 (2026-08-14) ───────────────────────────────────────
15
+ * `startReferenceLiveFeed` 를 부르는 곳은 `importTwinReference` **하나뿐**이었다. 그래서 서버를 한 번
16
+ * 재기동하면 미러 트윈은 「도는 중」이라 말하면서 아무 계측도 받지 않았다 — 오류도 로그도 없이,
17
+ * 사람은 「값이 안 변하네」로 알게 된다. 실 배포에서 재기동 한 번에 계측이 멈추는 셈이다.
18
+ *
19
+ * ── 되살릴 재료는 이미 영속돼 있다 ─────────────────────────────────────────
20
+ * `TwinReference` 가 어댑터 종류·접속 설정·그 레퍼런스가 만든 인스턴스(`scopeSpec.produced`)를 들고 있다.
21
+ * 사이트 서술자만 없는데(`hint` 는 남기지 않았다) **다시 찾아서** 얻는다(`discoverSites`) — 인제스트가
22
+ * 걷는 것과 같은 길이므로 규칙이 갈라지지 않는다. 되살릴 인스턴스가 없으면 그 호출도 하지 않는다.
23
+ *
24
+ * 재개 대상은 **등록부가 도는 미러라고 말하는 것**뿐이다: 멈춘 트윈을 되살리면 사람이 멈춘 것을
25
+ * 우리가 다시 켜는 것이고, 시뮬 트윈에 실 계측을 부으면 두 현실이 섞인다.
26
+ */
27
+ export declare function resumeReferenceLiveFeeds(): Promise<{
28
+ resumed: string[];
29
+ failed: {
30
+ instanceId: string;
31
+ error: string;
32
+ }[];
33
+ }>;
@@ -3,8 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.startReferenceLiveFeed = startReferenceLiveFeed;
4
4
  exports.stopReferenceLiveFeed = stopReferenceLiveFeed;
5
5
  exports.listReferenceLiveFeeds = listReferenceLiveFeeds;
6
+ exports.resumeReferenceLiveFeeds = resumeReferenceLiveFeeds;
7
+ const shell_1 = require("@things-factory/shell");
6
8
  const reference_adapter_js_1 = require("./reference-adapter.js");
7
9
  const index_js_1 = require("../../engine/index.js");
10
+ const twin_instance_js_1 = require("../twin-instance/twin-instance.js");
11
+ const twin_reference_js_1 = require("./twin-reference.js");
8
12
  /*
9
13
  * 커넥터 기반 라이브 피드 결선(reference → live, §0 프레임 ① realityMode='mirror').
10
14
  * 흐름: adapter.openLiveFeed(cfg, site, records) → ingestCanonicalRecords(정규→봉투) → TwinEngine.ingestLive(projector 미러).
@@ -23,20 +27,31 @@ async function startReferenceLiveFeed(domainId, adapterType, cfg, site, instance
23
27
  if (!adapter?.openLiveFeed)
24
28
  return false;
25
29
  const det = await index_js_1.TwinEngine.detail(domainId, instanceId);
26
- if (!det?.board)
27
- throw new Error(`instance "${instanceId}" not provisioned (no board) — ingest master before live start`);
30
+ if (!det?.model)
31
+ throw new Error(`instance "${instanceId}" not provisioned (no model) — ingest master before live start`);
28
32
  // mirror 모드 기동 — 커널 tick 없이 projector 가 실 이벤트를 미러(startLive 가 realityMode='mirror' 설정).
29
33
  // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.
30
- index_js_1.TwinEngine.startLive(instanceId, domainId, det.kind ?? 'wms', await index_js_1.TwinEngine.withSpaceTimeBase(det.board, domainId));
34
+ index_js_1.TwinEngine.startLive(instanceId, domainId, det.kind, await index_js_1.TwinEngine.withSpaceTimeBase(det.model, domainId));
31
35
  stopReferenceLiveFeed(instanceId); // 재기동 시 이전 피드 정리
32
36
  const unsubscribe = adapter.openLiveFeed(cfg, site, (records) => {
33
37
  if (!index_js_1.TwinEngine.owns(domainId, instanceId)) {
34
38
  stopReferenceLiveFeed(instanceId); // 인스턴스 사라지면 자가 정지(안전망)
35
39
  return;
36
40
  }
37
- const { accepted } = (0, index_js_1.ingestCanonicalRecords)(records, domainId, new Date().toISOString());
41
+ const { accepted, rejected } = (0, index_js_1.ingestCanonicalRecords)(records, domainId, new Date().toISOString());
38
42
  if (accepted.length)
39
43
  index_js_1.TwinEngine.ingestLive(domainId, instanceId, accepted);
44
+ /*
45
+ * **거부한 것을 조용히 버리지 않는다.** 예전에는 `rejected` 를 받지도 않았다 — 커넥터의 매핑이
46
+ * 틀리면 트윈은 「아무 일도 일어나지 않는」 모습이 되고, 로그에도 아무 흔적이 없었다.
47
+ *
48
+ * 지금은 이유와 함께 남긴다. 화면까지 나르는 것은 다음 일이다(라이브 계측은 초당 여러 건이라
49
+ * 건마다 방송하면 그 자체가 부하가 된다 — 집계해 낼 자리가 필요하다).
50
+ */
51
+ if (rejected.length) {
52
+ const why = rejected[0]?.errors?.join(' · ') ?? 'unknown';
53
+ console.warn(`[twin-live] "${instanceId}": ${rejected.length} record(s) rejected — e.g. ${why}`);
54
+ }
40
55
  });
41
56
  feeds.set(instanceId, unsubscribe);
42
57
  return true;
@@ -57,6 +72,108 @@ function stopReferenceLiveFeed(instanceId) {
57
72
  function listReferenceLiveFeeds() {
58
73
  return [...feeds.keys()];
59
74
  }
75
+ /**
76
+ * 부팅 때 **끊어진 피드를 다시 붙인다** — 도는 미러 트윈에 계측이 계속 들어오게.
77
+ *
78
+ * ── 무엇이 조용히 끊겼나 (2026-08-14) ───────────────────────────────────────
79
+ * `startReferenceLiveFeed` 를 부르는 곳은 `importTwinReference` **하나뿐**이었다. 그래서 서버를 한 번
80
+ * 재기동하면 미러 트윈은 「도는 중」이라 말하면서 아무 계측도 받지 않았다 — 오류도 로그도 없이,
81
+ * 사람은 「값이 안 변하네」로 알게 된다. 실 배포에서 재기동 한 번에 계측이 멈추는 셈이다.
82
+ *
83
+ * ── 되살릴 재료는 이미 영속돼 있다 ─────────────────────────────────────────
84
+ * `TwinReference` 가 어댑터 종류·접속 설정·그 레퍼런스가 만든 인스턴스(`scopeSpec.produced`)를 들고 있다.
85
+ * 사이트 서술자만 없는데(`hint` 는 남기지 않았다) **다시 찾아서** 얻는다(`discoverSites`) — 인제스트가
86
+ * 걷는 것과 같은 길이므로 규칙이 갈라지지 않는다. 되살릴 인스턴스가 없으면 그 호출도 하지 않는다.
87
+ *
88
+ * 재개 대상은 **등록부가 도는 미러라고 말하는 것**뿐이다: 멈춘 트윈을 되살리면 사람이 멈춘 것을
89
+ * 우리가 다시 켜는 것이고, 시뮬 트윈에 실 계측을 부으면 두 현실이 섞인다.
90
+ */
91
+ async function resumeReferenceLiveFeeds() {
92
+ const resumed = [];
93
+ const failed = [];
94
+ /*
95
+ * **한 트윈에 피드는 하나다** (2026-08-18).
96
+ *
97
+ * 같은 사이트를 두 레퍼런스가 각각 인제스트하면 둘 다 그 인스턴스를 자기가 만든 것(`produced`)으로
98
+ * 들고 있다. 기동 때 그 목록을 그대로 돌면 같은 트윈에 피드를 두 번 붙이는데, 붙이는 함수가 미러를
99
+ * 다시 세우므로(`startLive`) **먼저 쌓인 수요 구간이 리셋된다** — 실제로 그렇게 났다
100
+ * (로그: `reattached 2 feed(s): ems-plant-1, ems-plant-1`).
101
+ *
102
+ * 그러니 한 번만 붙이고, **두 레퍼런스가 같은 트윈을 주장한다는 사실은 말한다** — 조용히 하나를
103
+ * 고르면 어느 접속이 그 트윈을 먹이고 있는지 아무도 모르게 된다.
104
+ */
105
+ const attached = new Map(); // instanceId → 먼저 붙인 레퍼런스
106
+ try {
107
+ const refs = await (0, shell_1.getRepository)(twin_reference_js_1.TwinReference).find({ where: { status: 'connected' }, relations: ['domain'] });
108
+ for (const ref of refs) {
109
+ const domainId = ref.domain?.id ?? ref.domainId;
110
+ const adapter = (0, reference_adapter_js_1.getAdapter)(ref.adapterType);
111
+ if (!domainId || !adapter?.openLiveFeed)
112
+ continue; // 관측만 하는 어댑터에는 되살릴 피드가 없다
113
+ const produced = (ref.scopeSpec?.produced ?? []).filter((p) => p?.instanceId && p?.siteId);
114
+ if (!produced.length)
115
+ continue;
116
+ /* 등록부가 「도는 미러」라고 말하는 것만 고른다. */
117
+ const rows = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance).find({
118
+ where: produced.map((p) => ({ domain: { id: domainId }, instanceId: p.instanceId }))
119
+ });
120
+ const live = produced.filter((p) => rows.some(r => r.instanceId === p.instanceId && r.status === 'running' && r.realityMode === 'mirror'));
121
+ if (!live.length)
122
+ continue;
123
+ let sites;
124
+ try {
125
+ sites = await adapter.discoverSites(ref.connectionConfig ?? {});
126
+ }
127
+ catch (e) {
128
+ /* 원 시스템이 아직 안 떴을 수 있다 — 지어내지 않고 그렇게 적는다(트윈은 계속 「도는 미러」다). */
129
+ for (const p of live)
130
+ failed.push({ instanceId: p.instanceId, error: `discover failed: ${e?.message ?? 'unknown'}` });
131
+ continue;
132
+ }
133
+ for (const p of live) {
134
+ const site = sites.find(s => s.siteId === p.siteId);
135
+ if (!site) {
136
+ failed.push({ instanceId: p.instanceId, error: `site "${p.siteId}" no longer discoverable` });
137
+ continue;
138
+ }
139
+ const owner = attached.get(p.instanceId);
140
+ if (owner) {
141
+ /* 두 접속이 같은 트윈을 주장한다 — 먼저 붙은 것을 지키고, 그 사실을 남긴다(둘 다 붙이면 미러가 리셋된다). */
142
+ failed.push({
143
+ instanceId: p.instanceId,
144
+ error: `also claimed by reference "${ref.source}" — kept the feed from "${owner}" (two connections feeding one twin would reset the mirror)`
145
+ });
146
+ continue;
147
+ }
148
+ attached.set(p.instanceId, ref.source);
149
+ try {
150
+ const ok = await startReferenceLiveFeed(domainId, ref.adapterType, ref.connectionConfig ?? {}, site, p.instanceId);
151
+ if (ok)
152
+ resumed.push(p.instanceId);
153
+ else
154
+ failed.push({ instanceId: p.instanceId, error: 'adapter has no openLiveFeed' });
155
+ }
156
+ catch (e) {
157
+ failed.push({ instanceId: p.instanceId, error: e?.message ?? 'reattach failed' });
158
+ }
159
+ }
160
+ }
161
+ }
162
+ catch (e) {
163
+ console.error('[twin-live] feed resume scan failed', e?.message);
164
+ }
165
+ if (resumed.length)
166
+ console.log(`[twin-live] reattached ${resumed.length} feed(s): ${resumed.join(', ')}`);
167
+ /* **조용히 넘기지 않는다** — 붙지 않은 미러는 「도는데 아무것도 오지 않는」 트윈이다. */
168
+ for (const f of failed)
169
+ console.warn(`[twin-live] "${f.instanceId}" feed NOT reattached — ${f.error}`);
170
+ return { resumed, failed };
171
+ }
172
+ /*
173
+ * 이 계층의 피드 장부를 등록한다 — 화면이 「끊김」을 말할 근거는 **묻는 자리 하나**여야 한다.
174
+ * (프리셋 기반 피드는 앱이 자기 장부를 따로 등록한다. 한쪽만 보면 흐르는 트윈을 끊겼다고 부른다.)
175
+ */
176
+ (0, index_js_1.registerLiveFeedProbe)(() => [...feeds.keys()]);
60
177
  /* 인스턴스 정지 시 피드 자동 정리(생명주기 대칭). */
61
178
  index_js_1.TwinEngine.onStop(instanceId => stopReferenceLiveFeed(instanceId));
62
179
  //# sourceMappingURL=reference-live.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reference-live.js","sourceRoot":"","sources":["../../../server/service/reference/reference-live.ts"],"names":[],"mappings":";;AAkBA,wDA4BC;AAGD,sDAMC;AAGD,wDAEC;AA5DD,iEAA+F;AAC/F,oDAAgG;AAEhG;;;;;;GAMG;AAEH,iEAAiE;AACjE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAA;AAE3C;;;GAGG;AACI,KAAK,UAAU,sBAAsB,CAC1C,QAAgB,EAChB,WAAmB,EACnB,GAAqB,EACrB,IAAoB,EACpB,UAAkB;IAElB,MAAM,OAAO,GAAG,IAAA,iCAAU,EAAC,WAAW,CAAC,CAAA;IACvC,IAAI,CAAC,OAAO,EAAE,YAAY;QAAE,OAAO,KAAK,CAAA;IAExC,MAAM,GAAG,GAAG,MAAM,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;IACzD,IAAI,CAAC,GAAG,EAAE,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,aAAa,UAAU,gEAAgE,CAAC,CAAA;IAEzH,wFAAwF;IACxF,uDAAuD;IACvD,qBAAU,CAAC,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,IAAI,KAAK,EAAE,MAAM,qBAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;IAEtH,qBAAqB,CAAC,UAAU,CAAC,CAAA,CAAC,iBAAiB;IACnD,MAAM,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,OAAkB,EAAE,EAAE;QACzE,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;YAC3C,qBAAqB,CAAC,UAAU,CAAC,CAAA,CAAC,uBAAuB;YACzD,OAAM;QACR,CAAC;QACD,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAA,iCAAsB,EAAC,OAA4B,EAAE,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAA;QAC7G,IAAI,QAAQ,CAAC,MAAM;YAAE,qBAAU,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IACF,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;IAClC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,8BAA8B;AAC9B,SAAgB,qBAAqB,CAAC,UAAkB;IACtD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IACxB,IAAI,CAAC;QAAC,KAAK,EAAE,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;IACpC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACxB,OAAO,IAAI,CAAA;AACb,CAAC;AAED,oBAAoB;AACpB,SAAgB,sBAAsB;IACpC,OAAO,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;AAC1B,CAAC;AAED,kCAAkC;AAClC,qBAAU,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAA","sourcesContent":["import { getAdapter, type ConnectionConfig, type SiteDescriptor } from './reference-adapter.js'\nimport { TwinEngine, ingestCanonicalRecords, type CanonicalRecord } from '../../engine/index.js'\n\n/*\n * 커넥터 기반 라이브 피드 결선(reference → live, §0 프레임 ① realityMode='mirror').\n * 흐름: adapter.openLiveFeed(cfg, site, records) → ingestCanonicalRecords(정규→봉투) → TwinEngine.ingestLive(projector 미러).\n * 커넥터가 \"벤더 이벤트 → 정규 레코드\" 매핑을 소유(코드)한다 — preset 기반 startLiveFeed(operato-twin, http-get+jsonata)와\n * 대칭이되, 매핑 위치만 다르다. 실 시스템 전환은 connectionConfig.baseUrl 교체뿐(코드 경로 동일).\n * 생명주기: 인스턴스 정지(TwinEngine.stop)에 훅으로 묶여 피드도 함께 unsubscribe.\n */\n\n/** 활성 라이브 피드 — instanceId → unsubscribe(어댑터 openLiveFeed 반환). */\nconst feeds = new Map<string, () => void>()\n\n/**\n * 커넥터 라이브 기동 — 어댑터 openLiveFeed 를 인스턴스에 결선.\n * 반환: 라이브 결선 성공 여부(어댑터가 openLiveFeed 미지원이면 false → 호출측이 sim 폴백).\n */\nexport async function startReferenceLiveFeed(\n domainId: string,\n adapterType: string,\n cfg: ConnectionConfig,\n site: SiteDescriptor,\n instanceId: string\n): Promise<boolean> {\n const adapter = getAdapter(adapterType)\n if (!adapter?.openLiveFeed) return false\n\n const det = await TwinEngine.detail(domainId, instanceId)\n if (!det?.board) throw new Error(`instance \"${instanceId}\" not provisioned (no board) — ingest master before live start`)\n\n // mirror 모드 기동 — 커널 tick 없이 projector 가 실 이벤트를 미러(startLive 가 realityMode='mirror' 설정).\n // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.\n TwinEngine.startLive(instanceId, domainId, det.kind ?? 'wms', await TwinEngine.withSpaceTimeBase(det.board, domainId))\n\n stopReferenceLiveFeed(instanceId) // 재기동 시 이전 피드 정리\n const unsubscribe = adapter.openLiveFeed(cfg, site, (records: unknown[]) => {\n if (!TwinEngine.owns(domainId, instanceId)) {\n stopReferenceLiveFeed(instanceId) // 인스턴스 사라지면 자가 정지(안전망)\n return\n }\n const { accepted } = ingestCanonicalRecords(records as CanonicalRecord[], domainId, new Date().toISOString())\n if (accepted.length) TwinEngine.ingestLive(domainId, instanceId, accepted)\n })\n feeds.set(instanceId, unsubscribe)\n return true\n}\n\n/** 라이브 피드 중지(unsubscribe). */\nexport function stopReferenceLiveFeed(instanceId: string): boolean {\n const unsub = feeds.get(instanceId)\n if (!unsub) return false\n try { unsub() } catch { /* noop */ }\n feeds.delete(instanceId)\n return true\n}\n\n/** 활성 피드 목록(관측). */\nexport function listReferenceLiveFeeds(): string[] {\n return [...feeds.keys()]\n}\n\n/* 인스턴스 정지 시 피드 자동 정리(생명주기 대칭). */\nTwinEngine.onStop(instanceId => stopReferenceLiveFeed(instanceId))\n"]}
1
+ {"version":3,"file":"reference-live.js","sourceRoot":"","sources":["../../../server/service/reference/reference-live.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { getAdapter, type ConnectionConfig, type SiteDescriptor } from './reference-adapter.js'\nimport { TwinEngine, ingestCanonicalRecords, registerLiveFeedProbe, type CanonicalRecord } from '../../engine/index.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinReference } from './twin-reference.js'\n\n/*\n * 커넥터 기반 라이브 피드 결선(reference → live, §0 프레임 ① realityMode='mirror').\n * 흐름: adapter.openLiveFeed(cfg, site, records) → ingestCanonicalRecords(정규→봉투) → TwinEngine.ingestLive(projector 미러).\n * 커넥터가 \"벤더 이벤트 → 정규 레코드\" 매핑을 소유(코드)한다 — preset 기반 startLiveFeed(operato-twin, http-get+jsonata)와\n * 대칭이되, 매핑 위치만 다르다. 실 시스템 전환은 connectionConfig.baseUrl 교체뿐(코드 경로 동일).\n * 생명주기: 인스턴스 정지(TwinEngine.stop)에 훅으로 묶여 피드도 함께 unsubscribe.\n */\n\n/** 활성 라이브 피드 — instanceId → unsubscribe(어댑터 openLiveFeed 반환). */\nconst feeds = new Map<string, () => void>()\n\n/**\n * 커넥터 라이브 기동 — 어댑터 openLiveFeed 를 인스턴스에 결선.\n * 반환: 라이브 결선 성공 여부(어댑터가 openLiveFeed 미지원이면 false → 호출측이 sim 폴백).\n */\nexport async function startReferenceLiveFeed(\n domainId: string,\n adapterType: string,\n cfg: ConnectionConfig,\n site: SiteDescriptor,\n instanceId: string\n): Promise<boolean> {\n const adapter = getAdapter(adapterType)\n if (!adapter?.openLiveFeed) return false\n\n const det = await TwinEngine.detail(domainId, instanceId)\n if (!det?.model) throw new Error(`instance \"${instanceId}\" not provisioned (no model) — ingest master before live start`)\n\n // mirror 모드 기동 — 커널 tick 없이 projector 가 실 이벤트를 미러(startLive 가 realityMode='mirror' 설정).\n // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.\n TwinEngine.startLive(instanceId, domainId, det.kind, await TwinEngine.withSpaceTimeBase(det.model, domainId))\n\n stopReferenceLiveFeed(instanceId) // 재기동 시 이전 피드 정리\n const unsubscribe = adapter.openLiveFeed(cfg, site, (records: unknown[]) => {\n if (!TwinEngine.owns(domainId, instanceId)) {\n stopReferenceLiveFeed(instanceId) // 인스턴스 사라지면 자가 정지(안전망)\n return\n }\n const { accepted, rejected } = ingestCanonicalRecords(records as CanonicalRecord[], domainId, new Date().toISOString())\n if (accepted.length) TwinEngine.ingestLive(domainId, instanceId, accepted)\n /*\n * **거부한 것을 조용히 버리지 않는다.** 예전에는 `rejected` 를 받지도 않았다 — 커넥터의 매핑이\n * 틀리면 트윈은 「아무 일도 일어나지 않는」 모습이 되고, 로그에도 아무 흔적이 없었다.\n *\n * 지금은 이유와 함께 남긴다. 화면까지 나르는 것은 다음 일이다(라이브 계측은 초당 여러 건이라\n * 건마다 방송하면 그 자체가 부하가 된다 — 집계해 낼 자리가 필요하다).\n */\n if (rejected.length) {\n const why = rejected[0]?.errors?.join(' · ') ?? 'unknown'\n console.warn(`[twin-live] \"${instanceId}\": ${rejected.length} record(s) rejected — e.g. ${why}`)\n }\n })\n feeds.set(instanceId, unsubscribe)\n return true\n}\n\n/** 라이브 피드 중지(unsubscribe). */\nexport function stopReferenceLiveFeed(instanceId: string): boolean {\n const unsub = feeds.get(instanceId)\n if (!unsub) return false\n try { unsub() } catch { /* noop */ }\n feeds.delete(instanceId)\n return true\n}\n\n/** 활성 피드 목록(관측). */\nexport function listReferenceLiveFeeds(): string[] {\n return [...feeds.keys()]\n}\n\n/**\n * 부팅 때 **끊어진 피드를 다시 붙인다** — 도는 미러 트윈에 계측이 계속 들어오게.\n *\n * ── 무엇이 조용히 끊겼나 (2026-08-14) ───────────────────────────────────────\n * `startReferenceLiveFeed` 를 부르는 곳은 `importTwinReference` **하나뿐**이었다. 그래서 서버를 한 번\n * 재기동하면 미러 트윈은 「도는 중」이라 말하면서 아무 계측도 받지 않았다 — 오류도 로그도 없이,\n * 사람은 「값이 안 변하네」로 알게 된다. 실 배포에서 재기동 한 번에 계측이 멈추는 셈이다.\n *\n * ── 되살릴 재료는 이미 영속돼 있다 ─────────────────────────────────────────\n * `TwinReference` 가 어댑터 종류·접속 설정·그 레퍼런스가 만든 인스턴스(`scopeSpec.produced`)를 들고 있다.\n * 사이트 서술자만 없는데(`hint` 는 남기지 않았다) **다시 찾아서** 얻는다(`discoverSites`) — 인제스트가\n * 걷는 것과 같은 길이므로 규칙이 갈라지지 않는다. 되살릴 인스턴스가 없으면 그 호출도 하지 않는다.\n *\n * 재개 대상은 **등록부가 도는 미러라고 말하는 것**뿐이다: 멈춘 트윈을 되살리면 사람이 멈춘 것을\n * 우리가 다시 켜는 것이고, 시뮬 트윈에 실 계측을 부으면 두 현실이 섞인다.\n */\nexport async function resumeReferenceLiveFeeds(): Promise<{ resumed: string[]; failed: { instanceId: string; error: string }[] }> {\n const resumed: string[] = []\n const failed: { instanceId: string; error: string }[] = []\n /*\n * **한 트윈에 피드는 하나다** (2026-08-18).\n *\n * 같은 사이트를 두 레퍼런스가 각각 인제스트하면 둘 다 그 인스턴스를 자기가 만든 것(`produced`)으로\n * 들고 있다. 기동 때 그 목록을 그대로 돌면 같은 트윈에 피드를 두 번 붙이는데, 붙이는 함수가 미러를\n * 다시 세우므로(`startLive`) **먼저 쌓인 수요 구간이 리셋된다** — 실제로 그렇게 났다\n * (로그: `reattached 2 feed(s): ems-plant-1, ems-plant-1`).\n *\n * 그러니 한 번만 붙이고, **두 레퍼런스가 같은 트윈을 주장한다는 사실은 말한다** — 조용히 하나를\n * 고르면 어느 접속이 그 트윈을 먹이고 있는지 아무도 모르게 된다.\n */\n const attached = new Map<string, string>() // instanceId → 먼저 붙인 레퍼런스\n try {\n const refs = await getRepository(TwinReference).find({ where: { status: 'connected' }, relations: ['domain'] })\n for (const ref of refs) {\n const domainId = (ref as any).domain?.id ?? (ref as any).domainId\n const adapter = getAdapter(ref.adapterType)\n if (!domainId || !adapter?.openLiveFeed) continue // 관측만 하는 어댑터에는 되살릴 피드가 없다\n\n const produced: any[] = ((ref.scopeSpec as any)?.produced ?? []).filter((p: any) => p?.instanceId && p?.siteId)\n if (!produced.length) continue\n\n /* 등록부가 「도는 미러」라고 말하는 것만 고른다. */\n const rows = await getRepository(TwinInstance).find({\n where: produced.map((p: any) => ({ domain: { id: domainId }, instanceId: p.instanceId })) as any\n })\n const live = produced.filter((p: any) =>\n rows.some(r => r.instanceId === p.instanceId && r.status === 'running' && r.realityMode === 'mirror')\n )\n if (!live.length) continue\n\n let sites: SiteDescriptor[]\n try {\n sites = await adapter.discoverSites(ref.connectionConfig ?? {})\n } catch (e: any) {\n /* 원 시스템이 아직 안 떴을 수 있다 — 지어내지 않고 그렇게 적는다(트윈은 계속 「도는 미러」다). */\n for (const p of live) failed.push({ instanceId: p.instanceId, error: `discover failed: ${e?.message ?? 'unknown'}` })\n continue\n }\n\n for (const p of live) {\n const site = sites.find(s => s.siteId === p.siteId)\n if (!site) {\n failed.push({ instanceId: p.instanceId, error: `site \"${p.siteId}\" no longer discoverable` })\n continue\n }\n const owner = attached.get(p.instanceId)\n if (owner) {\n /* 두 접속이 같은 트윈을 주장한다 — 먼저 붙은 것을 지키고, 그 사실을 남긴다(둘 다 붙이면 미러가 리셋된다). */\n failed.push({\n instanceId: p.instanceId,\n error: `also claimed by reference \"${ref.source}\" — kept the feed from \"${owner}\" (two connections feeding one twin would reset the mirror)`\n })\n continue\n }\n attached.set(p.instanceId, ref.source)\n try {\n const ok = await startReferenceLiveFeed(domainId, ref.adapterType, ref.connectionConfig ?? {}, site, p.instanceId)\n if (ok) resumed.push(p.instanceId)\n else failed.push({ instanceId: p.instanceId, error: 'adapter has no openLiveFeed' })\n } catch (e: any) {\n failed.push({ instanceId: p.instanceId, error: e?.message ?? 'reattach failed' })\n }\n }\n }\n } catch (e: any) {\n console.error('[twin-live] feed resume scan failed', e?.message)\n }\n\n if (resumed.length) console.log(`[twin-live] reattached ${resumed.length} feed(s): ${resumed.join(', ')}`)\n /* **조용히 넘기지 않는다** — 붙지 않은 미러는 「도는데 아무것도 오지 않는」 트윈이다. */\n for (const f of failed) console.warn(`[twin-live] \"${f.instanceId}\" feed NOT reattached — ${f.error}`)\n return { resumed, failed }\n}\n\n/*\n * 이 계층의 피드 장부를 등록한다 — 화면이 「끊김」을 말할 근거는 **묻는 자리 하나**여야 한다.\n * (프리셋 기반 피드는 앱이 자기 장부를 따로 등록한다. 한쪽만 보면 흐르는 트윈을 끊겼다고 부른다.)\n */\nregisterLiveFeedProbe(() => [...feeds.keys()])\n\n/* 인스턴스 정지 시 피드 자동 정리(생명주기 대칭). */\nTwinEngine.onStop(instanceId => stopReferenceLiveFeed(instanceId))\n"]}