@things-factory/headless-twin 10.0.12 โ†’ 10.0.13

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 (312) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +57 -2
  2. package/dist-server/engine/canonical-ingest.js +43 -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/index.d.ts +3 -0
  7. package/dist-server/engine/index.js +4 -0
  8. package/dist-server/engine/index.js.map +1 -1
  9. package/dist-server/engine/ingest-health.d.ts +335 -0
  10. package/dist-server/engine/ingest-health.js +434 -0
  11. package/dist-server/engine/ingest-health.js.map +1 -0
  12. package/dist-server/engine/integration-coverage.d.ts +76 -0
  13. package/dist-server/engine/integration-coverage.js +73 -0
  14. package/dist-server/engine/integration-coverage.js.map +1 -0
  15. package/dist-server/engine/integration-probes.d.ts +65 -0
  16. package/dist-server/engine/integration-probes.js +100 -0
  17. package/dist-server/engine/integration-probes.js.map +1 -0
  18. package/dist-server/engine/integration-runner.d.ts +45 -0
  19. package/dist-server/engine/integration-runner.js +59 -0
  20. package/dist-server/engine/integration-runner.js.map +1 -0
  21. package/dist-server/engine/integration-target-profile.d.ts +57 -0
  22. package/dist-server/engine/integration-target-profile.js +79 -0
  23. package/dist-server/engine/integration-target-profile.js.map +1 -0
  24. package/dist-server/engine/kpi-fold.d.ts +27 -0
  25. package/dist-server/engine/kpi-fold.js +47 -4
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.js +128 -14
  28. package/dist-server/engine/kpi-query.js.map +1 -1
  29. package/dist-server/engine/live-feed-registry.js +2 -16
  30. package/dist-server/engine/live-feed-registry.js.map +1 -1
  31. package/dist-server/engine/load-meter.d.ts +1 -1
  32. package/dist-server/engine/load-meter.js +12 -3
  33. package/dist-server/engine/load-meter.js.map +1 -1
  34. package/dist-server/engine/log.d.ts +18 -0
  35. package/dist-server/engine/log.js +80 -0
  36. package/dist-server/engine/log.js.map +1 -0
  37. package/dist-server/engine/loop-lag.d.ts +54 -0
  38. package/dist-server/engine/loop-lag.js +87 -0
  39. package/dist-server/engine/loop-lag.js.map +1 -0
  40. package/dist-server/engine/model-gap.d.ts +108 -0
  41. package/dist-server/engine/model-gap.js +95 -0
  42. package/dist-server/engine/model-gap.js.map +1 -0
  43. package/dist-server/engine/restart-policy.d.ts +3 -3
  44. package/dist-server/engine/restart-policy.js +4 -4
  45. package/dist-server/engine/restart-policy.js.map +1 -1
  46. package/dist-server/engine/runtime-key.js +1 -1
  47. package/dist-server/engine/runtime-key.js.map +1 -1
  48. package/dist-server/engine/stage-path.d.ts +83 -0
  49. package/dist-server/engine/stage-path.js +118 -0
  50. package/dist-server/engine/stage-path.js.map +1 -0
  51. package/dist-server/engine/twin-engine.d.ts +352 -16
  52. package/dist-server/engine/twin-engine.js +1037 -166
  53. package/dist-server/engine/twin-engine.js.map +1 -1
  54. package/dist-server/index.js +34 -2
  55. package/dist-server/index.js.map +1 -1
  56. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
  57. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
  58. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
  59. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
  60. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
  61. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
  62. package/dist-server/migrations/index.js +7 -1
  63. package/dist-server/migrations/index.js.map +1 -1
  64. package/dist-server/service/index.d.ts +5 -2
  65. package/dist-server/service/index.js +18 -7
  66. package/dist-server/service/index.js.map +1 -1
  67. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  68. package/dist-server/service/reference/discovery-result.js +1 -1
  69. package/dist-server/service/reference/discovery-result.js.map +1 -1
  70. package/dist-server/service/reference/index.d.ts +4 -1
  71. package/dist-server/service/reference/index.js +6 -1
  72. package/dist-server/service/reference/index.js.map +1 -1
  73. package/dist-server/service/reference/reference-adapter.d.ts +184 -3
  74. package/dist-server/service/reference/reference-adapter.js +28 -4
  75. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  76. package/dist-server/service/reference/reference-assessment.d.ts +68 -0
  77. package/dist-server/service/reference/reference-assessment.js +136 -0
  78. package/dist-server/service/reference/reference-assessment.js.map +1 -0
  79. package/dist-server/service/reference/reference-live.d.ts +12 -1
  80. package/dist-server/service/reference/reference-live.js +116 -11
  81. package/dist-server/service/reference/reference-live.js.map +1 -1
  82. package/dist-server/service/reference/reference-master.d.ts +47 -1
  83. package/dist-server/service/reference/reference-master.js +34 -4
  84. package/dist-server/service/reference/reference-master.js.map +1 -1
  85. package/dist-server/service/reference/reference-probe.d.ts +92 -0
  86. package/dist-server/service/reference/reference-probe.js +186 -0
  87. package/dist-server/service/reference/reference-probe.js.map +1 -0
  88. package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
  89. package/dist-server/service/reference/reference-progress-subscription.js +94 -0
  90. package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
  91. package/dist-server/service/reference/reference-progress.d.ts +38 -0
  92. package/dist-server/service/reference/reference-progress.js +71 -0
  93. package/dist-server/service/reference/reference-progress.js.map +1 -0
  94. package/dist-server/service/reference/reference-resolver.js +210 -43
  95. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  96. package/dist-server/service/reference/twin-reference.d.ts +32 -0
  97. package/dist-server/service/reference/twin-reference.js +10 -0
  98. package/dist-server/service/reference/twin-reference.js.map +1 -1
  99. package/dist-server/service/twin-control/twin-control-mutation.js +4 -3
  100. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  101. package/dist-server/service/twin-event/journal-count.d.ts +21 -0
  102. package/dist-server/service/twin-event/journal-count.js +26 -0
  103. package/dist-server/service/twin-event/journal-count.js.map +1 -0
  104. package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
  105. package/dist-server/service/twin-event/twin-event-keys.js +15 -18
  106. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  107. package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
  108. package/dist-server/service/twin-event/twin-event-type.js +18 -1
  109. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  110. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  111. package/dist-server/service/twin-event/twin-event.js +9 -1
  112. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  113. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  114. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  115. package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
  116. package/dist-server/service/twin-ingest-window/index.js +10 -0
  117. package/dist-server/service/twin-ingest-window/index.js.map +1 -0
  118. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
  119. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
  120. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
  121. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
  122. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
  123. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
  124. package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
  125. package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
  126. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
  127. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  128. package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
  129. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  130. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
  131. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +72 -2
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  133. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  134. package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
  135. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  136. package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
  137. package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
  138. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
  139. package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
  140. package/dist-server/service/twin-model/epcis-coverage.js +19 -7
  141. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  142. package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
  143. package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
  144. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  145. package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
  146. package/dist-server/service/twin-model/isa95-coverage.js +23 -9
  147. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  148. package/dist-server/service/twin-model/item-ref.d.ts +24 -0
  149. package/dist-server/service/twin-model/item-ref.js +88 -0
  150. package/dist-server/service/twin-model/item-ref.js.map +1 -0
  151. package/dist-server/service/twin-model/name-index.d.ts +36 -0
  152. package/dist-server/service/twin-model/name-index.js +116 -0
  153. package/dist-server/service/twin-model/name-index.js.map +1 -0
  154. package/dist-server/service/twin-model/project-structure.js +1 -1
  155. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  156. package/dist-server/service/twin-model/status-tally.d.ts +9 -0
  157. package/dist-server/service/twin-model/status-tally.js +37 -0
  158. package/dist-server/service/twin-model/status-tally.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
  160. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  161. package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
  162. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  163. package/dist-server/service/twin-model/twin-model-query.js +156 -4
  164. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  165. package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
  166. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  167. package/dist-server/service/twin-readiness/index.d.ts +2 -0
  168. package/dist-server/service/twin-readiness/index.js +6 -0
  169. package/dist-server/service/twin-readiness/index.js.map +1 -0
  170. package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
  171. package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
  172. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
  173. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  174. package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
  175. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  176. package/dist-shared/entity-delta.d.ts +23 -3
  177. package/dist-shared/entity-delta.js +12 -8
  178. package/dist-shared/entity-delta.js.map +1 -1
  179. package/dist-shared/kpi-broadcast.js +1 -1
  180. package/dist-shared/kpi-broadcast.js.map +1 -1
  181. package/dist-shared/touched-items.d.ts +7 -0
  182. package/dist-shared/touched-items.js +80 -0
  183. package/dist-shared/touched-items.js.map +1 -0
  184. package/package.json +7 -7
  185. package/server/engine/canonical-ingest.ts +92 -7
  186. package/server/engine/command-routing.ts +1 -1
  187. package/server/engine/index.ts +4 -0
  188. package/server/engine/ingest-health.ts +704 -0
  189. package/server/engine/integration-coverage.ts +147 -0
  190. package/server/engine/integration-probes.ts +144 -0
  191. package/server/engine/integration-runner.ts +95 -0
  192. package/server/engine/integration-target-profile.ts +103 -0
  193. package/server/engine/kpi-fold.ts +72 -4
  194. package/server/engine/kpi-query.ts +129 -15
  195. package/server/engine/live-feed-registry.ts +2 -1
  196. package/server/engine/load-meter.ts +12 -3
  197. package/server/engine/log.ts +72 -0
  198. package/server/engine/loop-lag.ts +120 -0
  199. package/server/engine/model-gap.ts +168 -0
  200. package/server/engine/restart-policy.ts +4 -4
  201. package/server/engine/runtime-key.ts +1 -1
  202. package/server/engine/stage-path.ts +172 -0
  203. package/server/engine/twin-engine.ts +1135 -162
  204. package/server/index.ts +35 -2
  205. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
  206. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
  207. package/server/migrations/index.ts +7 -1
  208. package/server/service/index.ts +11 -0
  209. package/server/service/reference/discovery-result.ts +1 -1
  210. package/server/service/reference/index.ts +6 -1
  211. package/server/service/reference/reference-adapter.ts +197 -5
  212. package/server/service/reference/reference-assessment.ts +215 -0
  213. package/server/service/reference/reference-live.ts +123 -11
  214. package/server/service/reference/reference-master.ts +64 -4
  215. package/server/service/reference/reference-probe.ts +264 -0
  216. package/server/service/reference/reference-progress-subscription.ts +73 -0
  217. package/server/service/reference/reference-progress.ts +95 -0
  218. package/server/service/reference/reference-resolver.ts +200 -12
  219. package/server/service/reference/twin-reference.ts +39 -1
  220. package/server/service/twin-control/twin-control-mutation.ts +4 -3
  221. package/server/service/twin-event/journal-count.ts +53 -0
  222. package/server/service/twin-event/twin-event-keys.ts +16 -1
  223. package/server/service/twin-event/twin-event-type.ts +26 -2
  224. package/server/service/twin-event/twin-event.ts +7 -0
  225. package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
  226. package/server/service/twin-ingest-window/index.ts +7 -0
  227. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
  228. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
  229. package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
  230. package/server/service/twin-instance/twin-instance.ts +1 -1
  231. package/server/service/twin-journal/twin-journal-query.ts +59 -5
  232. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +77 -3
  233. package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
  234. package/server/service/twin-model/axis-journal-evidence.ts +57 -0
  235. package/server/service/twin-model/epcis-coverage.ts +2 -8
  236. package/server/service/twin-model/iec61850-coverage.ts +2 -8
  237. package/server/service/twin-model/isa95-coverage.ts +22 -9
  238. package/server/service/twin-model/item-ref.ts +80 -0
  239. package/server/service/twin-model/name-index.ts +94 -0
  240. package/server/service/twin-model/project-structure.ts +1 -1
  241. package/server/service/twin-model/status-tally.ts +37 -0
  242. package/server/service/twin-model/twin-lineage-query.ts +38 -9
  243. package/server/service/twin-model/twin-model-item-query.ts +26 -27
  244. package/server/service/twin-model/twin-model-query.ts +155 -4
  245. package/server/service/twin-model/twin-model-tree-query.ts +7 -0
  246. package/server/service/twin-readiness/index.ts +3 -0
  247. package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
  248. package/server/service/twin-space/twin-space-resolver.ts +5 -4
  249. package/shared/entity-delta.ts +31 -7
  250. package/shared/kpi-broadcast.ts +1 -1
  251. package/shared/touched-items.ts +73 -0
  252. package/test/axis-journal-evidence.test.ts +71 -0
  253. package/test/axis-read.test.ts +101 -1
  254. package/test/boot-resume.test.ts +92 -26
  255. package/test/broadcast-cost-baseline.test.ts +200 -0
  256. package/test/broadcast-period.test.ts +58 -0
  257. package/test/canonical-ingest-vocabularies.test.ts +36 -1
  258. package/test/canonical-quantity-door.test.ts +61 -0
  259. package/test/command-routing.test.ts +1 -1
  260. package/test/declared-location-types.test.ts +89 -0
  261. package/test/discovery-result.test.ts +1 -1
  262. package/test/duration-estimators.test.ts +1 -1
  263. package/test/entity-delta.test.ts +2 -2
  264. package/test/ingest-bench.test.ts +3 -3
  265. package/test/ingest-health-engine.test.ts +248 -0
  266. package/test/ingest-health-wiring.test.ts +119 -0
  267. package/test/ingest-health.test.ts +306 -0
  268. package/test/ingest-history.test.ts +247 -0
  269. package/test/integration-probes.test.ts +103 -0
  270. package/test/integration-runner.test.ts +95 -0
  271. package/test/item-ref.test.ts +78 -0
  272. package/test/journal-read-discipline.test.ts +177 -0
  273. package/test/journal-retention.test.ts +133 -0
  274. package/test/journal-sort-axis.test.ts +142 -0
  275. package/test/journal-write-door.test.ts +110 -0
  276. package/test/journal-write-trend.test.ts +115 -0
  277. package/test/kernel-kind-guard.test.ts +4 -4
  278. package/test/kpi-fold.test.ts +90 -3
  279. package/test/kpi-query-bench.test.ts +2 -2
  280. package/test/lineage-survives-restart.test.ts +17 -1
  281. package/test/live-cursor-wiring.test.ts +87 -0
  282. package/test/live-feed-registry.test.ts +9 -1
  283. package/test/live-kernel-facts.test.ts +7 -7
  284. package/test/live-mirror-parity.test.ts +6 -0
  285. package/test/load-meter.test.ts +29 -15
  286. package/test/local-declarations.test.ts +1 -1
  287. package/test/log-stamp.test.ts +59 -0
  288. package/test/loop-lag.test.ts +82 -0
  289. package/test/model-gap.test.ts +153 -0
  290. package/test/oee-accumulator.test.ts +4 -0
  291. package/test/operational-vocabulary.test.ts +4 -4
  292. package/test/read-failure-visible.test.ts +145 -0
  293. package/test/reference-grounding.test.ts +70 -0
  294. package/test/resolve-ts-siblings.mjs +52 -0
  295. package/test/restart-policy.test.ts +7 -7
  296. package/test/revision-axis.test.ts +93 -0
  297. package/test/runtime-key.test.ts +2 -2
  298. package/test/scale-twin-bench.test.ts +2 -2
  299. package/test/spec-coverage.test.ts +1 -1
  300. package/test/stage-path.test.ts +95 -0
  301. package/test/standard-coverage.test.ts +15 -3
  302. package/test/status-tally.test.ts +55 -0
  303. package/test/structure-revision-db.test.ts +4 -0
  304. package/test/tenant-registry-db.test.ts +1 -1
  305. package/test/time-range.test.ts +152 -0
  306. package/test/touched-items.test.ts +61 -0
  307. package/test/twin-event-keys.test.ts +10 -2
  308. package/test/twin-model-item-db.test.ts +2 -0
  309. package/test/warm-start-seam.test.ts +4 -0
  310. package/test/yield-loop.test.ts +57 -2
  311. package/tsconfig.shared.tsbuildinfo +1 -1
  312. package/tsconfig.tsbuildinfo +1 -1
@@ -4,6 +4,7 @@ exports.startReferenceLiveFeed = startReferenceLiveFeed;
4
4
  exports.stopReferenceLiveFeed = stopReferenceLiveFeed;
5
5
  exports.listReferenceLiveFeeds = listReferenceLiveFeeds;
6
6
  exports.resumeReferenceLiveFeeds = resumeReferenceLiveFeeds;
7
+ const log_js_1 = require("../../engine/log.js");
7
8
  const shell_1 = require("@things-factory/shell");
8
9
  const reference_adapter_js_1 = require("./reference-adapter.js");
9
10
  const index_js_1 = require("../../engine/index.js");
@@ -22,7 +23,18 @@ const feeds = new Map();
22
23
  * ์ปค๋„ฅํ„ฐ ๋ผ์ด๋ธŒ ๊ธฐ๋™ โ€” ์–ด๋Œ‘ํ„ฐ openLiveFeed ๋ฅผ ์ธ์Šคํ„ด์Šค์— ๊ฒฐ์„ .
23
24
  * ๋ฐ˜ํ™˜: ๋ผ์ด๋ธŒ ๊ฒฐ์„  ์„ฑ๊ณต ์—ฌ๋ถ€(์–ด๋Œ‘ํ„ฐ๊ฐ€ openLiveFeed ๋ฏธ์ง€์›์ด๋ฉด false โ†’ ํ˜ธ์ถœ์ธก์ด sim ํด๋ฐฑ).
24
25
  */
25
- async function startReferenceLiveFeed(domainId, adapterType, cfg, site, instanceId) {
26
+ async function startReferenceLiveFeed(domainId, adapterType, cfg, site, instanceId,
27
+ /**
28
+ * **์–ด๋А ๋ ˆํผ๋Ÿฐ์Šค์—์„œ ์ฝ๋Š” ํ”ผ๋“œ์ธ๊ฐ€** โ€” ์ฝ๊ธฐ ์ปค์„œ๋ฅผ ์ €์žฅํ•  ์ž๋ฆฌ๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค(ยง`loadLiveCursor`).
29
+ *
30
+ * ๋ถ€๋ฅด๋Š” ์ชฝ์ด `TwinReference` ๋ฅผ ์ด๋ฏธ ๋“ค๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๊ทธ ์ด๋ฆ„์„ ๋„˜๊ธด๋‹ค. ์ฒ˜์Œ์—๋Š” `cfg`ยท`site` ์—์„œ
31
+ * ์ฐพ์œผ๋ ค ํ–ˆ๋Š”๋ฐ **๋‘˜ ๋‹ค ๊ทธ ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์—†๋‹ค**: `cfg` ๋Š” ์‚ฌ๋žŒ์ด ํ™”๋ฉด์—์„œ ์ž…๋ ฅํ•œ ์ ‘์† ์„ค์ •์ด๊ณ 
32
+ * (`endpoint`ยท`token`โ€ฆ), `site` ๋Š” ์ปค๋„ฅํ„ฐ๊ฐ€ ์›๋ณธ์—์„œ ๋ฐœ๊ฒฌํ•œ ๊ฒƒ์ด๋‹ค โ€” **๋ ˆํผ๋Ÿฐ์Šค์˜ ์ด๋ฆ„์€ ํ˜ธ์ŠคํŠธ๊ฐ€
33
+ * ๋ถ™์ด๋Š” ๊ฒƒ์ด๊ณ  ์›๋ณธ์€ ๊ทธ๊ฒƒ์„ ๋ชจ๋ฅธ๋‹ค.** ์ปค๋„ฅํ„ฐ๊ฐ€ ์ฑ„์šธ ์ˆ˜ ์—†๋Š” ๊ฐ’์„ ์ปค๋„ฅํ„ฐ์—์„œ ์ฝ์œผ๋ ค ํ•œ ๊ฒƒ์ด ์ž˜๋ชป์ด์—ˆ๋‹ค.
34
+ *
35
+ * ์ฃผ์ง€ ์•Š์œผ๋ฉด ์ปค์„œ๋ฅผ ์ž‡์ง€ ์•Š๊ณ  **๊ทธ ์‚ฌ์‹ค์„ ๋งํ•œ๋‹ค**(์กฐ์šฉํžˆ ์ฐฝ์ด ๋ฏธ๋„๋Ÿฌ์ง€์ง€ ์•Š๊ฒŒ).
36
+ */
37
+ source) {
26
38
  const adapter = (0, reference_adapter_js_1.getAdapter)(adapterType);
27
39
  if (!adapter?.openLiveFeed)
28
40
  return false;
@@ -33,29 +45,122 @@ async function startReferenceLiveFeed(domainId, adapterType, cfg, site, instance
33
45
  // ์‹œ๊ฐ ๊ธฐ์ค€์€ **๊ณต๊ฐ„**์ด ๊ฐ–๋Š”๋‹ค(ํ…Œ๋„ŒํŠธ ์•„๋‹˜) โ€” ๊ต๋Œ€์˜ HH:MM ์„ ์–ด๋А ๊ธฐ์ค€์œผ๋กœ ์ฝ๋‚˜.
34
46
  index_js_1.TwinEngine.startLive(instanceId, domainId, det.kind, await index_js_1.TwinEngine.withSpaceTimeBase(det.model, domainId));
35
47
  stopReferenceLiveFeed(instanceId); // ์žฌ๊ธฐ๋™ ์‹œ ์ด์ „ ํ”ผ๋“œ ์ •๋ฆฌ
48
+ /*
49
+ * **์–ด๋””๊นŒ์ง€ ์ฝ์—ˆ๋‚˜๋ฅผ ์ด ์ธต์ด ๋“ค๊ณ  ๊ฑด๋„จ๋‹ค**(ยง`LiveFeedContinuity`, 2026-08-23).
50
+ *
51
+ * ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ด ๊ฐ’์„ ์ž๊ธฐ ์‚ฌ๋ฌผํ•จ์— ๋‘๋ฉด ์›๋ณธ์ด ๋Š˜ ๋•Œ๋งˆ๋‹ค ์ €์žฅ ๊ธฐ์ œ๊ฐ€ ๋Š˜๊ณ , ๊ทธ์ค‘ ํ•˜๋‚˜๊ฐ€ ์กฐ์šฉํžˆ ๋‹ค๋ฅด๊ฒŒ
52
+ * ๋™์ž‘ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ปค๋„์ด ์ด๋ฏธ ๊ฐˆ๋ผ ๋‘์—ˆ๋‹ค: ใ€Œ์›์ฒœ์ด ์• ์ดˆ์— ๋‹ค์‹œ ๋งํ•ด ์ฃผ์ง€ ์•Š๋Š” ์ถ•ใ€์€ ์žฌ๊ธฐ๋™
53
+ * ์—ฐ์†์„ฑ์œผ๋กœ ์ด์–ด๋ฐ›๋Š”๋‹ค(ยง`hydrateContinuity`) โ€” ์ฝ๊ธฐ ์ปค์„œ๊ฐ€ ์ •ํ™•ํžˆ ๊ทธ ์„ฑ์งˆ์ด๋‹ค.
54
+ *
55
+ * ์—†์œผ๋ฉด `undefined` ๋ฅผ ๊ฑด๋„จ๋‹ค โ€” ๊ทธ๊ฒƒ์ด ใ€Œ์ฒซ ๋ถ™์Œใ€์ด๋ผ๋Š” ์‚ฌ์‹ค์ด๊ณ , ๊ทธ๋•Œ๋งŒ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ๋˜๋Œ์•„๋ณผ ๋‚ ์ˆ˜๋กœ
56
+ * ์ฒซ ์ฐฝ์„ ๋งŒ๋“ ๋‹ค. ๋นˆ ๊ฐ์ฒด๋กœ ๋ฉ”์šฐ์ง€ ์•Š๋Š”๋‹ค(๊ทธ๋Ÿฌ๋ฉด ์ฒซ ๋ถ™์Œ๊ณผ ์ด์–ด ๋ถ™์Œ์ด ๊ฐ™์•„์ง„๋‹ค).
57
+ */
58
+ const continuity = await loadLiveCursor(domainId, instanceId, source);
36
59
  const unsubscribe = adapter.openLiveFeed(cfg, site, (records) => {
37
60
  if (!index_js_1.TwinEngine.owns(domainId, instanceId)) {
38
61
  stopReferenceLiveFeed(instanceId); // ์ธ์Šคํ„ด์Šค ์‚ฌ๋ผ์ง€๋ฉด ์ž๊ฐ€ ์ •์ง€(์•ˆ์ „๋ง)
39
62
  return;
40
63
  }
64
+ const offered = Array.isArray(records) ? records.length : records ? 1 : 0;
41
65
  const { accepted, rejected } = (0, index_js_1.ingestCanonicalRecords)(records, domainId, new Date().toISOString());
42
- if (accepted.length)
43
- index_js_1.TwinEngine.ingestLive(domainId, instanceId, accepted);
66
+ /*
67
+ * ์ปค๋„์ด **๋ช‡ ๊ฑด์„ ์‹ค์ œ๋กœ ๋ฐ›์•˜๋‚˜**๋ฅผ ํ•จ๊ป˜ ๋ณธ๋‹ค. ํŠธ์œˆ์ด ๋ผ์ด๋ธŒ๋กœ ๋Œ์ง€ ์•Š์œผ๋ฉด `ingestLive` ๊ฐ€ 0 ์„
68
+ * ๋Œ๋ ค์ฃผ๊ณ , ๊ทธ ์ฐจ์ด๊ฐ€ ๊ณง ใ€Œ๋ฐ”๋‹ฅ์— ๋ฒ„๋ ค์ง„ ์ˆ˜ใ€๋‹ค. ์ด๊ฒƒ์„ ํ†ต๊ณผ๋กœ ์„ธ๋ฉด ๋ฉˆ์ถ˜ ํŠธ์œˆ์ด 100% ๋กœ ๋ณด์ธ๋‹ค.
69
+ */
70
+ const applied = accepted.length ? index_js_1.TwinEngine.ingestLive(domainId, instanceId, accepted) : 0;
71
+ const undelivered = accepted.length - applied;
44
72
  /*
45
73
  * **๊ฑฐ๋ถ€ํ•œ ๊ฒƒ์„ ์กฐ์šฉํžˆ ๋ฒ„๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.** ์˜ˆ์ „์—๋Š” `rejected` ๋ฅผ ๋ฐ›์ง€๋„ ์•Š์•˜๋‹ค โ€” ์ปค๋„ฅํ„ฐ์˜ ๋งคํ•‘์ด
46
74
  * ํ‹€๋ฆฌ๋ฉด ํŠธ์œˆ์€ ใ€Œ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”ใ€ ๋ชจ์Šต์ด ๋˜๊ณ , ๋กœ๊ทธ์—๋„ ์•„๋ฌด ํ”์ ์ด ์—†์—ˆ๋‹ค.
47
75
  *
48
- * ์ง€๊ธˆ์€ ์ด์œ ์™€ ํ•จ๊ป˜ ๋‚จ๊ธด๋‹ค. ํ™”๋ฉด๊นŒ์ง€ ๋‚˜๋ฅด๋Š” ๊ฒƒ์€ ๋‹ค์Œ ์ผ์ด๋‹ค(๋ผ์ด๋ธŒ ๊ณ„์ธก์€ ์ดˆ๋‹น ์—ฌ๋Ÿฌ ๊ฑด์ด๋ผ
49
- * ๊ฑด๋งˆ๋‹ค ๋ฐฉ์†กํ•˜๋ฉด ๊ทธ ์ž์ฒด๊ฐ€ ๋ถ€ํ•˜๊ฐ€ ๋œ๋‹ค โ€” ์ง‘๊ณ„ํ•ด ๋‚ผ ์ž๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค).
76
+ * ๊ทธ๋‹ค์Œ ์ž๋ฆฌ์˜€๋˜ ใ€Œํ™”๋ฉด๊นŒ์ง€ ๋‚˜๋ฅด๊ธฐใ€๋ฅผ ์ด์ œ ์ฑ„์šด๋‹ค. ๊ฑด๋งˆ๋‹ค ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…ํ•˜์ง€ ์•Š๊ณ  **์ง‘๊ณ„**ํ•œ๋‹ค โ€” ํ†ต๊ณผ๋ถ„๊นŒ์ง€
77
+ * ํ•จ๊ป˜ ์„ธ๋Š” ์ด์œ ๋Š”, ๊ฑฐ๋ถ€๋งŒ ์„ธ๋ฉด ใ€Œ์œ ์ž… ์—†์Œใ€๊ณผ ใ€Œ์ •์ƒใ€์ด ๋‘˜ ๋‹ค ๊ฑฐ๋ถ€ 0 ์œผ๋กœ ๊ฐ™์•„ ๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
78
+ * ์กฐํšŒ๋Š” `twinIngestHealth`.
79
+ */
80
+ /*
81
+ * **์ฝ์—ˆ๋‹ค๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ์‚ฌ์‹ค์ด๋‹ค** โ€” ๋นˆ ์ฝ๊ธฐ๋„ ์„ฑ๊ณต์ด๋ฏ€๋กœ ๋‹จ์ ˆ ๊ธฐ๋ก์„ ์ง€์šด๋‹ค(ยง`onReadFailure`).
82
+ * ๊ทธ ๋‘˜์„ ์„ž์œผ๋ฉด ์กฐ์šฉํ•œ ์›๋ณธ์ด ๋Š๊ธด ์›๋ณธ์œผ๋กœ ๋ณด์ธ๋‹ค.
50
83
  */
84
+ index_js_1.TwinEngine.clearIngestReadFailure(domainId, instanceId);
85
+ index_js_1.TwinEngine.recordIngestResult(domainId, instanceId, offered, rejected, Date.now(), undelivered);
51
86
  if (rejected.length) {
52
87
  const why = rejected[0]?.errors?.join(' ยท ') ?? 'unknown';
53
- console.warn(`[twin-live] "${instanceId}": ${rejected.length} record(s) rejected โ€” e.g. ${why}`);
88
+ (0, log_js_1.twinWarn)(`[twin-live] "${instanceId}": ${rejected.length}/${offered} record(s) rejected โ€” e.g. ${why}`);
54
89
  }
55
- });
90
+ /* ๋ฒ„๋ ค์ง„ ๊ฒƒ์€ ๋กœ๊ทธ์—๋„ ๋‚จ๊ธด๋‹ค โ€” ํ™”๋ฉด์„ ๋ณด์ง€ ์•Š๋Š” ์‚ฌ๋žŒ๋„ ์•Œ์•„์•ผ ํ•œ๋‹ค(์‚ฌ์‹ค์ด ์‚ฌ๋ผ์ง€๋Š” ์ค‘์ด๋‹ค). */
91
+ if (undelivered > 0) {
92
+ (0, log_js_1.twinWarn)(`[twin-live] "${instanceId}": ${undelivered}/${offered} record(s) NOT delivered โ€” the twin is not running live; ` +
93
+ `records are being discarded. Start the twin, or disconnect the feed.`);
94
+ }
95
+ }, continuity);
56
96
  feeds.set(instanceId, unsubscribe);
57
97
  return true;
58
98
  }
99
+ /**
100
+ * ๊ทธ ์›๋ณธ์˜ ์ฝ๊ธฐ ์ปค์„œ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ , ์›€์ง์ด๋ฉด ์ €์žฅํ•˜๋Š” ํ†ต๋กœ๋ฅผ ๋งŒ๋“ ๋‹ค.
101
+ *
102
+ * ์ปค์„œ๋Š” **์›๋ณธ(`TwinReference`)์˜ ๊ฒƒ**์ด์ง€ ์ธ์Šคํ„ด์Šค์˜ ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค โ€” ํ•œ ์›๋ณธ์ด ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๋ฅผ ๋‚ณ๊ณ 
103
+ * (`scopeSpec.produced`) ์ฝ๋Š” ๊ฒƒ์€ ํ•œ ๋ฒˆ์ด๋‹ค. ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ์ปค์„œ๋ฅผ ๋‘๋ฉด ๊ฐ™์€ ํ‘œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ฝ๋Š”๋‹ค.
104
+ *
105
+ * ์ €์žฅ ์‹คํŒจ๋Š” **์‚ผํ‚ค์ง€ ์•Š๊ณ  ๋งํ•œ๋‹ค.** ์กฐ์šฉํžˆ ์‹คํŒจํ•˜๋ฉด ๋‹ค์Œ ์žฌ๊ธฐ๋™์—์„œ ์ฐฝ์ด ๋‹ค์‹œ ๋ฏธ๋„๋Ÿฌ์ง€๊ณ , ๊ทธ ์†์‹ค์€
106
+ * ์˜ค๋ฅ˜ ์—†์ด ์‚ฌ๋ผ์ง„๋‹ค โ€” ์ด ์ปฌ๋Ÿผ์„ ๋งŒ๋“  ์ด์œ ๊ฐ€ ๋ฐ”๋กœ ๊ทธ๊ฒƒ์ด๋‹ค.
107
+ */
108
+ async function loadLiveCursor(domainId,
109
+ /** ์ฝ๊ธฐ ์‹คํŒจ๋ฅผ ์ ์„ ํŠธ์œˆ โ€” ์ปค์„œ๋Š” ๋ ˆํผ๋Ÿฐ์Šค์˜ ๊ฒƒ์ด์ง€๋งŒ **๊ฑด๊ฐ•์€ ์ธ์Šคํ„ด์Šค์˜ ๊ฒƒ**์ด๋‹ค(ํ•œ ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ์—ฌ๋Ÿฟ์„ ๋‚ณ๋Š”๋‹ค). */
110
+ instanceId, source) {
111
+ if (!source) {
112
+ /* ์›๋ณธ์„ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์—†์œผ๋ฉด ์ €์žฅํ•  ์ž๋ฆฌ๋„ ์—†๋‹ค โ€” ๊ทธ ์‚ฌ์‹ค์„ ๋งํ•œ๋‹ค(์กฐ์šฉํžˆ ์ปค์„œ ์—†์ด ๋Œ์ง€ ์•Š๊ฒŒ). */
113
+ (0, log_js_1.twinWarn)('[twin-live] live cursor not carried โ€” the caller did not say which reference this feed reads from');
114
+ return undefined;
115
+ }
116
+ const repo = (0, shell_1.getRepository)(twin_reference_js_1.TwinReference);
117
+ const row = await repo.findOne({ where: { domain: { id: domainId }, source } }).catch(() => null);
118
+ if (!row) {
119
+ (0, log_js_1.twinWarn)(`[twin-live] live cursor not carried โ€” no reference row for source "${source}"`);
120
+ return undefined;
121
+ }
122
+ const stored = row.liveCursor;
123
+ if (stored?.firstAttachedAt) {
124
+ (0, log_js_1.twinLog)(`[twin-live] "${source}": resuming from stored cursor โ€” knowledge starts ${stored.firstAttachedAt}`);
125
+ }
126
+ return {
127
+ cursor: stored ?? undefined,
128
+ /*
129
+ * **์›๋ณธ์— ๋‹ฟ์ง€ ๋ชปํ–ˆ๋‹ค** โ€” ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์•Œ๋ฆฌ๋ฉด ์ƒํƒœ์— ์ ๋Š”๋‹ค(ยง`onReadFailure`).
130
+ *
131
+ * ๋กœ๊ทธ๋กœ๋Š” ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ด๋ฏธ ๋งํ•˜๊ณ  ์žˆ์—ˆ๋‹ค(์žฌ์‹œ๋„ ๊ฒฝ๊ณ ). ๊ทธ๋Ÿฌ๋‚˜ **๋กœ๊ทธ๋Š” ์‚ฌ๋žŒ์ด ๋ณผ ๋•Œ๋งŒ ๊ฐ’์ด ์žˆ๋‹ค** โ€”
132
+ * ํ™”๋ฉด์ด ๋งํ•˜๋ ค๋ฉด ์กฐํšŒ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค.
133
+ *
134
+ * โ”€โ”€ `status` ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค (2026-08-23, ๋˜๋Œ๋ฆฐ ์ž๋ฆฌ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
135
+ * ์ฒ˜์Œ์—๋Š” ๋ ˆํผ๋Ÿฐ์Šค ํ–‰์˜ `status` ๋ฅผ `'error'` ๋กœ๋„ ์ ์—ˆ๋‹ค โ€” ใ€Œ๋ชฉ๋ก์ด ใ€Œ์—ฐ๊ฒฐ๋จใ€์ด๋ผ ๋งํ•˜๋ฉด์„œ ์‹ค์ œ๋กœ๋Š”
136
+ * ๋ชป ์ฝ๋Š” ์ƒํƒœ๋ฅผ ์—†์• ๋ ค๊ณ ใ€. **๊ทธ๊ฒƒ์ด ํ”ผ๋“œ๋ฅผ ์˜๊ตฌํžˆ ๊ป๋‹ค.**
137
+ *
138
+ * ๋ถ€ํŒ… ์žฌ๋ถ€์ฐฉ์€ `status: 'connected'` ์ธ ๋ ˆํผ๋Ÿฐ์Šค๋งŒ ๋˜์‚ด๋ฆฐ๋‹ค(ยง`resumeReferenceLiveFeeds`). ๊ทธ๋ž˜์„œ
139
+ * ์›๋ณธ์ด **์ž ๊น** ๋Š๊ธด ๊ฒƒ์ด ๊ทธ ํ–‰์„ `error` ๋กœ ๋งŒ๋“ค๊ณ , ๋‹ค์Œ ์žฌ๊ธฐ๋™์—์„œ ๊ทธ ํ”ผ๋“œ๋Š” ์•„์˜ˆ ๋ถ™์ง€ ์•Š์•˜๋‹ค โ€”
140
+ * ์‹ค์ธก์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ๋‚ฌ๋‹ค(09:25 ํŒ์— `[twin-live]` ์ค„์ด ํ•œ ์ค„๋„ ์—†์—ˆ๋‹ค).
141
+ *
142
+ * ๋‘ ์ถ•์„ ํ•œ ์นธ์— ์„ž์€ ๊ฒƒ์ด ์›์ธ์ด๋‹ค:
143
+ * `status` **์—ฐ๊ฒฐ ์„ค์ •์ด ์„ฑ๋ฆฝํ•˜๋‚˜** โ€” ์ ‘์† ์‹œํ—˜ยท๋งˆ์Šคํ„ฐ ๋™๊ธฐ๊ฐ€ ์ •ํ•˜๋Š” ์ถ•(์žฌ๋ถ€์ฐฉ์˜ ์ž๊ฒฉ์ด๋‹ค)
144
+ * ์ฝ๊ธฐ ์‹คํŒจ **์ง€๊ธˆ ๋‹ฟ๋‚˜** โ€” ์˜ค๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์‚ฌ์‹ค์ด๊ณ , ์œ ์ž… ์žฅ๋ถ€๊ฐ€ ๊ทธ ์ถ•์„ ๊ฐ–๋Š”๋‹ค
145
+ *
146
+ * ๊ทธ๋Ÿฌ๋‹ˆ ์—ฌ๊ธฐ์„œ๋Š” `lastError` ์—๋งŒ ์ ๋Š”๋‹ค(์‚ฌ๋žŒ์ด ๋ชฉ๋ก์—์„œ ์ด์œ ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ). ใ€Œ์ง€๊ธˆ ๋‹ฟ๋‚˜ใ€๋Š”
147
+ * `ingestHealth().readFailure` ๊ฐ€ ๋‹ตํ•˜๊ณ , ์„ฑ๊ณตํ•œ ์ฃผ๊ธฐ๊ฐ€ ๊ทธ๊ฒƒ์„ ์ง€์šด๋‹ค.
148
+ */
149
+ onReadFailure: ({ reason, stream }) => {
150
+ index_js_1.TwinEngine.recordIngestReadFailure(domainId, instanceId, reason, Date.now(), stream);
151
+ (0, log_js_1.twinWarn)(`[twin-live] "${source}": cannot read from the source${stream ? ` (${stream})` : ''} โ€” ${reason}`);
152
+ repo
153
+ .update({ id: row.id }, { lastError: `live: ${reason}` })
154
+ .catch(err => (0, log_js_1.twinWarn)(`[twin-live] "${source}": read failure not recorded โ€” ${err?.message ?? err}`));
155
+ },
156
+ onCursor: next => {
157
+ /* ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ค€ ๋ชจ์–‘์„ ๊ทธ๋Œ€๋กœ ์ ๋Š”๋‹ค โ€” ์ด ์ธต์€ ํ๋ฆ„์˜ ๋œป์„ ๋ชจ๋ฅธ๋‹ค(์—ด์‡ ๋Š” ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ •ํ•œ๋‹ค). */
158
+ repo
159
+ .update({ id: row.id }, { liveCursor: next })
160
+ .catch(err => (0, log_js_1.twinWarn)(`[twin-live] "${source}": live cursor not saved โ€” ${err?.message ?? err}`));
161
+ }
162
+ };
163
+ }
59
164
  /** ๋ผ์ด๋ธŒ ํ”ผ๋“œ ์ค‘์ง€(unsubscribe). */
60
165
  function stopReferenceLiveFeed(instanceId) {
61
166
  const unsub = feeds.get(instanceId);
@@ -147,7 +252,7 @@ async function resumeReferenceLiveFeeds() {
147
252
  }
148
253
  attached.set(p.instanceId, ref.source);
149
254
  try {
150
- const ok = await startReferenceLiveFeed(domainId, ref.adapterType, ref.connectionConfig ?? {}, site, p.instanceId);
255
+ const ok = await startReferenceLiveFeed(domainId, ref.adapterType, ref.connectionConfig ?? {}, site, p.instanceId, ref.source);
151
256
  if (ok)
152
257
  resumed.push(p.instanceId);
153
258
  else
@@ -160,13 +265,13 @@ async function resumeReferenceLiveFeeds() {
160
265
  }
161
266
  }
162
267
  catch (e) {
163
- console.error('[twin-live] feed resume scan failed', e?.message);
268
+ (0, log_js_1.twinError)('[twin-live] feed resume scan failed', e?.message);
164
269
  }
165
270
  if (resumed.length)
166
- console.log(`[twin-live] reattached ${resumed.length} feed(s): ${resumed.join(', ')}`);
271
+ (0, log_js_1.twinLog)(`[twin-live] reattached ${resumed.length} feed(s): ${resumed.join(', ')}`);
167
272
  /* **์กฐ์šฉํžˆ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค** โ€” ๋ถ™์ง€ ์•Š์€ ๋ฏธ๋Ÿฌ๋Š” ใ€Œ๋„๋Š”๋ฐ ์•„๋ฌด๊ฒƒ๋„ ์˜ค์ง€ ์•Š๋Š”ใ€ ํŠธ์œˆ์ด๋‹ค. */
168
273
  for (const f of failed)
169
- console.warn(`[twin-live] "${f.instanceId}" feed NOT reattached โ€” ${f.error}`);
274
+ (0, log_js_1.twinWarn)(`[twin-live] "${f.instanceId}" feed NOT reattached โ€” ${f.error}`);
170
275
  return { resumed, failed };
171
276
  }
172
277
  /*
@@ -1 +1 @@
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 ํ”„๋ ˆ์ž„ โ‘  restartPolicy='resync').\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 // ๊ด€์ธก ๊ธฐ๋™ โ€” ์ปค๋„ tick ์—†์ด projector ๊ฐ€ ์‹ค ์ด๋ฒคํŠธ๋ฅผ ๋ฐ˜์˜ํ•œ๋‹ค(startLive ๊ฐ€ restartPolicy='resync' ๋กœ ์ ๋Š”๋‹ค).\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.restartPolicy === 'resync')\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"]}
1
+ {"version":3,"file":"reference-live.js","sourceRoot":"","sources":["../../../server/service/reference/reference-live.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinLog, twinWarn, twinError } from '../../engine/log.js'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getAdapter, type ConnectionConfig, type LiveFeedContinuity, 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 ํ”„๋ ˆ์ž„ โ‘  restartPolicy='resync').\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 /**\n * **์–ด๋А ๋ ˆํผ๋Ÿฐ์Šค์—์„œ ์ฝ๋Š” ํ”ผ๋“œ์ธ๊ฐ€** โ€” ์ฝ๊ธฐ ์ปค์„œ๋ฅผ ์ €์žฅํ•  ์ž๋ฆฌ๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค(ยง`loadLiveCursor`).\n *\n * ๋ถ€๋ฅด๋Š” ์ชฝ์ด `TwinReference` ๋ฅผ ์ด๋ฏธ ๋“ค๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๊ทธ ์ด๋ฆ„์„ ๋„˜๊ธด๋‹ค. ์ฒ˜์Œ์—๋Š” `cfg`ยท`site` ์—์„œ\n * ์ฐพ์œผ๋ ค ํ–ˆ๋Š”๋ฐ **๋‘˜ ๋‹ค ๊ทธ ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์—†๋‹ค**: `cfg` ๋Š” ์‚ฌ๋žŒ์ด ํ™”๋ฉด์—์„œ ์ž…๋ ฅํ•œ ์ ‘์† ์„ค์ •์ด๊ณ \n * (`endpoint`ยท`token`โ€ฆ), `site` ๋Š” ์ปค๋„ฅํ„ฐ๊ฐ€ ์›๋ณธ์—์„œ ๋ฐœ๊ฒฌํ•œ ๊ฒƒ์ด๋‹ค โ€” **๋ ˆํผ๋Ÿฐ์Šค์˜ ์ด๋ฆ„์€ ํ˜ธ์ŠคํŠธ๊ฐ€\n * ๋ถ™์ด๋Š” ๊ฒƒ์ด๊ณ  ์›๋ณธ์€ ๊ทธ๊ฒƒ์„ ๋ชจ๋ฅธ๋‹ค.** ์ปค๋„ฅํ„ฐ๊ฐ€ ์ฑ„์šธ ์ˆ˜ ์—†๋Š” ๊ฐ’์„ ์ปค๋„ฅํ„ฐ์—์„œ ์ฝ์œผ๋ ค ํ•œ ๊ฒƒ์ด ์ž˜๋ชป์ด์—ˆ๋‹ค.\n *\n * ์ฃผ์ง€ ์•Š์œผ๋ฉด ์ปค์„œ๋ฅผ ์ž‡์ง€ ์•Š๊ณ  **๊ทธ ์‚ฌ์‹ค์„ ๋งํ•œ๋‹ค**(์กฐ์šฉํžˆ ์ฐฝ์ด ๋ฏธ๋„๋Ÿฌ์ง€์ง€ ์•Š๊ฒŒ).\n */\n source?: 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 // ๊ด€์ธก ๊ธฐ๋™ โ€” ์ปค๋„ tick ์—†์ด projector ๊ฐ€ ์‹ค ์ด๋ฒคํŠธ๋ฅผ ๋ฐ˜์˜ํ•œ๋‹ค(startLive ๊ฐ€ restartPolicy='resync' ๋กœ ์ ๋Š”๋‹ค).\n // ์‹œ๊ฐ ๊ธฐ์ค€์€ **๊ณต๊ฐ„**์ด ๊ฐ–๋Š”๋‹ค(ํ…Œ๋„ŒํŠธ ์•„๋‹˜) โ€” ๊ต๋Œ€์˜ HH:MM ์„ ์–ด๋А ๊ธฐ์ค€์œผ๋กœ ์ฝ๋‚˜.\n TwinEngine.startLive(instanceId, domainId, det.kind, await TwinEngine.withSpaceTimeBase(det.model, domainId))\n\n stopReferenceLiveFeed(instanceId) // ์žฌ๊ธฐ๋™ ์‹œ ์ด์ „ ํ”ผ๋“œ ์ •๋ฆฌ\n /*\n * **์–ด๋””๊นŒ์ง€ ์ฝ์—ˆ๋‚˜๋ฅผ ์ด ์ธต์ด ๋“ค๊ณ  ๊ฑด๋„จ๋‹ค**(ยง`LiveFeedContinuity`, 2026-08-23).\n *\n * ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ด ๊ฐ’์„ ์ž๊ธฐ ์‚ฌ๋ฌผํ•จ์— ๋‘๋ฉด ์›๋ณธ์ด ๋Š˜ ๋•Œ๋งˆ๋‹ค ์ €์žฅ ๊ธฐ์ œ๊ฐ€ ๋Š˜๊ณ , ๊ทธ์ค‘ ํ•˜๋‚˜๊ฐ€ ์กฐ์šฉํžˆ ๋‹ค๋ฅด๊ฒŒ\n * ๋™์ž‘ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ปค๋„์ด ์ด๋ฏธ ๊ฐˆ๋ผ ๋‘์—ˆ๋‹ค: ใ€Œ์›์ฒœ์ด ์• ์ดˆ์— ๋‹ค์‹œ ๋งํ•ด ์ฃผ์ง€ ์•Š๋Š” ์ถ•ใ€์€ ์žฌ๊ธฐ๋™\n * ์—ฐ์†์„ฑ์œผ๋กœ ์ด์–ด๋ฐ›๋Š”๋‹ค(ยง`hydrateContinuity`) โ€” ์ฝ๊ธฐ ์ปค์„œ๊ฐ€ ์ •ํ™•ํžˆ ๊ทธ ์„ฑ์งˆ์ด๋‹ค.\n *\n * ์—†์œผ๋ฉด `undefined` ๋ฅผ ๊ฑด๋„จ๋‹ค โ€” ๊ทธ๊ฒƒ์ด ใ€Œ์ฒซ ๋ถ™์Œใ€์ด๋ผ๋Š” ์‚ฌ์‹ค์ด๊ณ , ๊ทธ๋•Œ๋งŒ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ๋˜๋Œ์•„๋ณผ ๋‚ ์ˆ˜๋กœ\n * ์ฒซ ์ฐฝ์„ ๋งŒ๋“ ๋‹ค. ๋นˆ ๊ฐ์ฒด๋กœ ๋ฉ”์šฐ์ง€ ์•Š๋Š”๋‹ค(๊ทธ๋Ÿฌ๋ฉด ์ฒซ ๋ถ™์Œ๊ณผ ์ด์–ด ๋ถ™์Œ์ด ๊ฐ™์•„์ง„๋‹ค).\n */\n const continuity = await loadLiveCursor(domainId, instanceId, source)\n const unsubscribe = adapter.openLiveFeed(cfg, site, (records: unknown[]) => {\n if (!TwinEngine.owns(domainId, instanceId)) {\n stopReferenceLiveFeed(instanceId) // ์ธ์Šคํ„ด์Šค ์‚ฌ๋ผ์ง€๋ฉด ์ž๊ฐ€ ์ •์ง€(์•ˆ์ „๋ง)\n return\n }\n const offered = Array.isArray(records) ? records.length : records ? 1 : 0\n const { accepted, rejected } = ingestCanonicalRecords(records as CanonicalRecord[], domainId, new Date().toISOString())\n /*\n * ์ปค๋„์ด **๋ช‡ ๊ฑด์„ ์‹ค์ œ๋กœ ๋ฐ›์•˜๋‚˜**๋ฅผ ํ•จ๊ป˜ ๋ณธ๋‹ค. ํŠธ์œˆ์ด ๋ผ์ด๋ธŒ๋กœ ๋Œ์ง€ ์•Š์œผ๋ฉด `ingestLive` ๊ฐ€ 0 ์„\n * ๋Œ๋ ค์ฃผ๊ณ , ๊ทธ ์ฐจ์ด๊ฐ€ ๊ณง ใ€Œ๋ฐ”๋‹ฅ์— ๋ฒ„๋ ค์ง„ ์ˆ˜ใ€๋‹ค. ์ด๊ฒƒ์„ ํ†ต๊ณผ๋กœ ์„ธ๋ฉด ๋ฉˆ์ถ˜ ํŠธ์œˆ์ด 100% ๋กœ ๋ณด์ธ๋‹ค.\n */\n const applied = accepted.length ? TwinEngine.ingestLive(domainId, instanceId, accepted) : 0\n const undelivered = accepted.length - applied\n /*\n * **๊ฑฐ๋ถ€ํ•œ ๊ฒƒ์„ ์กฐ์šฉํžˆ ๋ฒ„๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.** ์˜ˆ์ „์—๋Š” `rejected` ๋ฅผ ๋ฐ›์ง€๋„ ์•Š์•˜๋‹ค โ€” ์ปค๋„ฅํ„ฐ์˜ ๋งคํ•‘์ด\n * ํ‹€๋ฆฌ๋ฉด ํŠธ์œˆ์€ ใ€Œ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”ใ€ ๋ชจ์Šต์ด ๋˜๊ณ , ๋กœ๊ทธ์—๋„ ์•„๋ฌด ํ”์ ์ด ์—†์—ˆ๋‹ค.\n *\n * ๊ทธ๋‹ค์Œ ์ž๋ฆฌ์˜€๋˜ ใ€Œํ™”๋ฉด๊นŒ์ง€ ๋‚˜๋ฅด๊ธฐใ€๋ฅผ ์ด์ œ ์ฑ„์šด๋‹ค. ๊ฑด๋งˆ๋‹ค ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…ํ•˜์ง€ ์•Š๊ณ  **์ง‘๊ณ„**ํ•œ๋‹ค โ€” ํ†ต๊ณผ๋ถ„๊นŒ์ง€\n * ํ•จ๊ป˜ ์„ธ๋Š” ์ด์œ ๋Š”, ๊ฑฐ๋ถ€๋งŒ ์„ธ๋ฉด ใ€Œ์œ ์ž… ์—†์Œใ€๊ณผ ใ€Œ์ •์ƒใ€์ด ๋‘˜ ๋‹ค ๊ฑฐ๋ถ€ 0 ์œผ๋กœ ๊ฐ™์•„ ๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.\n * ์กฐํšŒ๋Š” `twinIngestHealth`.\n */\n /*\n * **์ฝ์—ˆ๋‹ค๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ์‚ฌ์‹ค์ด๋‹ค** โ€” ๋นˆ ์ฝ๊ธฐ๋„ ์„ฑ๊ณต์ด๋ฏ€๋กœ ๋‹จ์ ˆ ๊ธฐ๋ก์„ ์ง€์šด๋‹ค(ยง`onReadFailure`).\n * ๊ทธ ๋‘˜์„ ์„ž์œผ๋ฉด ์กฐ์šฉํ•œ ์›๋ณธ์ด ๋Š๊ธด ์›๋ณธ์œผ๋กœ ๋ณด์ธ๋‹ค.\n */\n TwinEngine.clearIngestReadFailure(domainId, instanceId)\n TwinEngine.recordIngestResult(domainId, instanceId, offered, rejected, Date.now(), undelivered)\n if (rejected.length) {\n const why = rejected[0]?.errors?.join(' ยท ') ?? 'unknown'\n twinWarn(`[twin-live] \"${instanceId}\": ${rejected.length}/${offered} record(s) rejected โ€” e.g. ${why}`)\n }\n /* ๋ฒ„๋ ค์ง„ ๊ฒƒ์€ ๋กœ๊ทธ์—๋„ ๋‚จ๊ธด๋‹ค โ€” ํ™”๋ฉด์„ ๋ณด์ง€ ์•Š๋Š” ์‚ฌ๋žŒ๋„ ์•Œ์•„์•ผ ํ•œ๋‹ค(์‚ฌ์‹ค์ด ์‚ฌ๋ผ์ง€๋Š” ์ค‘์ด๋‹ค). */\n if (undelivered > 0) {\n twinWarn(\n `[twin-live] \"${instanceId}\": ${undelivered}/${offered} record(s) NOT delivered โ€” the twin is not running live; ` +\n `records are being discarded. Start the twin, or disconnect the feed.`\n )\n }\n }, continuity)\n feeds.set(instanceId, unsubscribe)\n return true\n}\n\n/**\n * ๊ทธ ์›๋ณธ์˜ ์ฝ๊ธฐ ์ปค์„œ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ , ์›€์ง์ด๋ฉด ์ €์žฅํ•˜๋Š” ํ†ต๋กœ๋ฅผ ๋งŒ๋“ ๋‹ค.\n *\n * ์ปค์„œ๋Š” **์›๋ณธ(`TwinReference`)์˜ ๊ฒƒ**์ด์ง€ ์ธ์Šคํ„ด์Šค์˜ ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค โ€” ํ•œ ์›๋ณธ์ด ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๋ฅผ ๋‚ณ๊ณ \n * (`scopeSpec.produced`) ์ฝ๋Š” ๊ฒƒ์€ ํ•œ ๋ฒˆ์ด๋‹ค. ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ์ปค์„œ๋ฅผ ๋‘๋ฉด ๊ฐ™์€ ํ‘œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ฝ๋Š”๋‹ค.\n *\n * ์ €์žฅ ์‹คํŒจ๋Š” **์‚ผํ‚ค์ง€ ์•Š๊ณ  ๋งํ•œ๋‹ค.** ์กฐ์šฉํžˆ ์‹คํŒจํ•˜๋ฉด ๋‹ค์Œ ์žฌ๊ธฐ๋™์—์„œ ์ฐฝ์ด ๋‹ค์‹œ ๋ฏธ๋„๋Ÿฌ์ง€๊ณ , ๊ทธ ์†์‹ค์€\n * ์˜ค๋ฅ˜ ์—†์ด ์‚ฌ๋ผ์ง„๋‹ค โ€” ์ด ์ปฌ๋Ÿผ์„ ๋งŒ๋“  ์ด์œ ๊ฐ€ ๋ฐ”๋กœ ๊ทธ๊ฒƒ์ด๋‹ค.\n */\nasync function loadLiveCursor(\n domainId: string,\n /** ์ฝ๊ธฐ ์‹คํŒจ๋ฅผ ์ ์„ ํŠธ์œˆ โ€” ์ปค์„œ๋Š” ๋ ˆํผ๋Ÿฐ์Šค์˜ ๊ฒƒ์ด์ง€๋งŒ **๊ฑด๊ฐ•์€ ์ธ์Šคํ„ด์Šค์˜ ๊ฒƒ**์ด๋‹ค(ํ•œ ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ์—ฌ๋Ÿฟ์„ ๋‚ณ๋Š”๋‹ค). */\n instanceId: string,\n source?: string\n): Promise<LiveFeedContinuity | undefined> {\n if (!source) {\n /* ์›๋ณธ์„ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์—†์œผ๋ฉด ์ €์žฅํ•  ์ž๋ฆฌ๋„ ์—†๋‹ค โ€” ๊ทธ ์‚ฌ์‹ค์„ ๋งํ•œ๋‹ค(์กฐ์šฉํžˆ ์ปค์„œ ์—†์ด ๋Œ์ง€ ์•Š๊ฒŒ). */\n twinWarn('[twin-live] live cursor not carried โ€” the caller did not say which reference this feed reads from')\n return undefined\n }\n const repo = getRepository(TwinReference)\n const row = await repo.findOne({ where: { domain: { id: domainId } as any, source } }).catch(() => null)\n if (!row) {\n twinWarn(`[twin-live] live cursor not carried โ€” no reference row for source \"${source}\"`)\n return undefined\n }\n const stored = row.liveCursor\n if (stored?.firstAttachedAt) {\n twinLog(`[twin-live] \"${source}\": resuming from stored cursor โ€” knowledge starts ${stored.firstAttachedAt}`)\n }\n return {\n cursor: stored ?? undefined,\n /*\n * **์›๋ณธ์— ๋‹ฟ์ง€ ๋ชปํ–ˆ๋‹ค** โ€” ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์•Œ๋ฆฌ๋ฉด ์ƒํƒœ์— ์ ๋Š”๋‹ค(ยง`onReadFailure`).\n *\n * ๋กœ๊ทธ๋กœ๋Š” ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ด๋ฏธ ๋งํ•˜๊ณ  ์žˆ์—ˆ๋‹ค(์žฌ์‹œ๋„ ๊ฒฝ๊ณ ). ๊ทธ๋Ÿฌ๋‚˜ **๋กœ๊ทธ๋Š” ์‚ฌ๋žŒ์ด ๋ณผ ๋•Œ๋งŒ ๊ฐ’์ด ์žˆ๋‹ค** โ€”\n * ํ™”๋ฉด์ด ๋งํ•˜๋ ค๋ฉด ์กฐํšŒ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค.\n *\n * โ”€โ”€ `status` ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค (2026-08-23, ๋˜๋Œ๋ฆฐ ์ž๋ฆฌ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n * ์ฒ˜์Œ์—๋Š” ๋ ˆํผ๋Ÿฐ์Šค ํ–‰์˜ `status` ๋ฅผ `'error'` ๋กœ๋„ ์ ์—ˆ๋‹ค โ€” ใ€Œ๋ชฉ๋ก์ด ใ€Œ์—ฐ๊ฒฐ๋จใ€์ด๋ผ ๋งํ•˜๋ฉด์„œ ์‹ค์ œ๋กœ๋Š”\n * ๋ชป ์ฝ๋Š” ์ƒํƒœ๋ฅผ ์—†์• ๋ ค๊ณ ใ€. **๊ทธ๊ฒƒ์ด ํ”ผ๋“œ๋ฅผ ์˜๊ตฌํžˆ ๊ป๋‹ค.**\n *\n * ๋ถ€ํŒ… ์žฌ๋ถ€์ฐฉ์€ `status: 'connected'` ์ธ ๋ ˆํผ๋Ÿฐ์Šค๋งŒ ๋˜์‚ด๋ฆฐ๋‹ค(ยง`resumeReferenceLiveFeeds`). ๊ทธ๋ž˜์„œ\n * ์›๋ณธ์ด **์ž ๊น** ๋Š๊ธด ๊ฒƒ์ด ๊ทธ ํ–‰์„ `error` ๋กœ ๋งŒ๋“ค๊ณ , ๋‹ค์Œ ์žฌ๊ธฐ๋™์—์„œ ๊ทธ ํ”ผ๋“œ๋Š” ์•„์˜ˆ ๋ถ™์ง€ ์•Š์•˜๋‹ค โ€”\n * ์‹ค์ธก์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ๋‚ฌ๋‹ค(09:25 ํŒ์— `[twin-live]` ์ค„์ด ํ•œ ์ค„๋„ ์—†์—ˆ๋‹ค).\n *\n * ๋‘ ์ถ•์„ ํ•œ ์นธ์— ์„ž์€ ๊ฒƒ์ด ์›์ธ์ด๋‹ค:\n * `status` **์—ฐ๊ฒฐ ์„ค์ •์ด ์„ฑ๋ฆฝํ•˜๋‚˜** โ€” ์ ‘์† ์‹œํ—˜ยท๋งˆ์Šคํ„ฐ ๋™๊ธฐ๊ฐ€ ์ •ํ•˜๋Š” ์ถ•(์žฌ๋ถ€์ฐฉ์˜ ์ž๊ฒฉ์ด๋‹ค)\n * ์ฝ๊ธฐ ์‹คํŒจ **์ง€๊ธˆ ๋‹ฟ๋‚˜** โ€” ์˜ค๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์‚ฌ์‹ค์ด๊ณ , ์œ ์ž… ์žฅ๋ถ€๊ฐ€ ๊ทธ ์ถ•์„ ๊ฐ–๋Š”๋‹ค\n *\n * ๊ทธ๋Ÿฌ๋‹ˆ ์—ฌ๊ธฐ์„œ๋Š” `lastError` ์—๋งŒ ์ ๋Š”๋‹ค(์‚ฌ๋žŒ์ด ๋ชฉ๋ก์—์„œ ์ด์œ ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ). ใ€Œ์ง€๊ธˆ ๋‹ฟ๋‚˜ใ€๋Š”\n * `ingestHealth().readFailure` ๊ฐ€ ๋‹ตํ•˜๊ณ , ์„ฑ๊ณตํ•œ ์ฃผ๊ธฐ๊ฐ€ ๊ทธ๊ฒƒ์„ ์ง€์šด๋‹ค.\n */\n onReadFailure: ({ reason, stream }) => {\n TwinEngine.recordIngestReadFailure(domainId, instanceId, reason, Date.now(), stream)\n twinWarn(`[twin-live] \"${source}\": cannot read from the source${stream ? ` (${stream})` : ''} โ€” ${reason}`)\n repo\n .update({ id: row.id }, { lastError: `live: ${reason}` } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": read failure not recorded โ€” ${err?.message ?? err}`))\n },\n onCursor: next => {\n /* ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ค€ ๋ชจ์–‘์„ ๊ทธ๋Œ€๋กœ ์ ๋Š”๋‹ค โ€” ์ด ์ธต์€ ํ๋ฆ„์˜ ๋œป์„ ๋ชจ๋ฅธ๋‹ค(์—ด์‡ ๋Š” ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ •ํ•œ๋‹ค). */\n repo\n .update({ id: row.id }, { liveCursor: next } as any)\n .catch(err => twinWarn(`[twin-live] \"${source}\": live cursor not saved โ€” ${err?.message ?? err}`))\n }\n }\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.restartPolicy === 'resync')\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, ref.source)\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 twinError('[twin-live] feed resume scan failed', e?.message)\n }\n\n if (resumed.length) twinLog(`[twin-live] reattached ${resumed.length} feed(s): ${resumed.join(', ')}`)\n /* **์กฐ์šฉํžˆ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค** โ€” ๋ถ™์ง€ ์•Š์€ ๋ฏธ๋Ÿฌ๋Š” ใ€Œ๋„๋Š”๋ฐ ์•„๋ฌด๊ฒƒ๋„ ์˜ค์ง€ ์•Š๋Š”ใ€ ํŠธ์œˆ์ด๋‹ค. */\n for (const f of failed) twinWarn(`[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"]}
@@ -421,6 +421,21 @@ export interface ReferenceMaster {
421
421
  * BOM ์„ ์„ค๋ช… ๋ฌธ์„œ์—๋งŒ ์ ์–ด ๋‘” ์ฑ„ ํ‚คํŠธ๋ฅผ ๋ชป ๋งŒ๋“œ๋Š” ํŠธ์œˆ์„ ๋งŒ๋“  ์›์ธ). ๊ทผ๊ฑฐ๋Š” ์ปค๋„ ๊ณ„์•ฝ ์ฃผ์„์—.
422
422
  */
423
423
  productionSpec?: unknown;
424
+ /**
425
+ * **์ด ์›๋ณธ์ด ์‹๋ณ„์ž๋ฅผ ๋งŒ๋“œ๋Š” ์ด๋ฆ„๊ณต๊ฐ„** โ€” `TwinModelDef.identity` ๋กœ ํ†ต๊ณผํ•œ๋‹ค.
426
+ *
427
+ * **์„ ์–ธ์ด ์—†์œผ๋ฉด ์ปค๋„์ด ๊ฑฐ์ ˆํ•œ๋‹ค** (0.7.45~). ์˜ˆ์ „์—๋Š” ์ƒ์ˆ˜ ํ”„๋ฆฌํ”ฝ์Šค๋กœ ํŒ”๋ ˆํŠธยทํŠธ๋ ˆ์ผ๋Ÿฌยท๋ฌธ์„œ๋ฅผ
428
+ * ๋งŒ๋“ค์—ˆ๋Š”๋ฐ(`0614141` โ€” GS1 ์ด ๊ทœ๊ฒฉ ์˜ˆ์ œ๋กœ ์“ฐ๋‹ค ๋ฒ„๋ฆฐ ๊ฐ’์ด๊ณ  `06โ€ฆ` ์€ ์‹ค ๋ฐฐ์ • ๋Œ€์—ญ์ด๋‹ค), ๊ทธ ๊ฐ’์€
429
+ * ์ €๋„์— ์˜์›ํžˆ ๋‚จ์œผ๋ฉด์„œ ์ง€์–ด๋‚ธ ๊ฒƒ์ด๋ผ๊ณ  ๋งํ•ด ์ฃผ๋Š” ๋ฐ๊ฐ€ ์—†์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ปค๋„์ด ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค.
430
+ *
431
+ * ์›๋ณธ์ด ์ž๊ธฐ ๋„๋ฉ”์ธ์„ ๋งํ•˜๋ฉด ๊ทธ ์•„๋ž˜์—์„œ ๋งŒ๋“ ๋‹ค โ€” ๊ทธ๊ฒƒ์ด ํ‘œ์ค€ ์ •ํ•ฉ์ด๋‹ค(CBV 2.0 ยง8.2.4ยทยง8.5.5,
432
+ * ใ€Œ๊ทธ ์ธํ„ฐ๋„ท ๋„๋ฉ”์ธ ์†Œ์œ ์ž๊ฐ€ ๋ฐฐ์ •ํ•œ๋‹คใ€). GS1 ํ‚ค๋ฅผ ์“ฐ๋ ค๋ฉด `documentTypes` + `companyPrefix` ์ด๊ณ ,
433
+ * ๊ทธ๋•Œ **์ž๋ฆฌ ์ˆ˜๊ฐ€ ๋งž์•„์•ผ ํ•œ๋‹ค**(SGTIN ์€ ํ”„๋ฆฌํ”ฝ์Šค + ํ’ˆ๋ชฉ์ฐธ์กฐ = 13์ž๋ฆฌ, GS1 TDS).
434
+ *
435
+ * ์ •์ฒด์„ฑ์€ ์ƒ์‚ฐ๊ณผ ๋ฌด๊ด€ํ•˜๋ฏ€๋กœ `productionSpec` ์ด ์•„๋‹ˆ๋ผ **๋ชจ๋ธ**์ด ๊ฐ–๋Š”๋‹ค โ€” ์ฐฝ๊ณ ยท์•ผ๋“œ๋Š” ์•„๋ฌด๊ฒƒ๋„
436
+ * ์ƒ์‚ฐํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ํŒ”๋ ˆํŠธยทํŠธ๋ ˆ์ผ๋Ÿฌ๋ฅผ ์‹๋ณ„ํ•œ๋‹ค.
437
+ */
438
+ identity?: unknown;
424
439
  /**
425
440
  * ์‚ฌ๋žŒ โ€” ์„ ์–ธํ•˜์ง€ ์•Š์œผ๋ฉด **์ธ์› ์ œ์•ฝ์ด ์—†๋Š” ํŠธ์œˆ**์ด ๋œ๋‹ค(์„ค๋น„๋งŒ ์žˆ์œผ๋ฉด ์–ธ์ œ๋‚˜ ๊ฐ€๋™๋˜๋Š” ๊ณต์žฅ).
426
441
  * ๊ณต์ •์ด ์ธ์›์„ ์š”๊ตฌํ•˜๋Š”๋ฐ ์‚ฌ๋žŒ์ด ์—†์œผ๋ฉด ๊ทธ ์ž‘์—…์€ ์‹œ์ž‘๋˜์ง€ ์•Š๋Š”๋‹ค โ€” ๊ทธ๊ฒƒ์ด ์‚ฌ์‹ค์ด๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ๋‘”๋‹ค.
@@ -449,6 +464,21 @@ export interface ReferenceMaster {
449
464
  * ๋†“๊ณ ๋„ ์กฐ์šฉํžˆ ์„ฑ๊ณตํ–ˆ๋‹ค. ์˜ฎ๊ธฐ๋Š” ์‚ฌ๋žŒ๋งŒ ์•„๋Š” ์‚ฌ์‹ค์€ ์˜ฎ๊ธฐ๋Š” ์ž๋ฆฌ์—์„œ ์‹ค์–ด์•ผ ํ•œ๋‹ค.
450
465
  */
451
466
  warnings?: IngestWarning[];
467
+ /**
468
+ * **์›๋ณธ์ด ๊ฐ€์ง„ ์ˆ˜** โ€” ์˜ฎ๊ธด ์ˆ˜์˜ ๋ถ„๋ชจ๋‹ค.
469
+ *
470
+ * ์ดํ‰์€ ๋„์ฐฉํ•œ ๋ชจ๋ธ๋งŒ ๋ณผ ์ˆ˜ ์žˆ์–ด์„œ ใ€Œ๋ ˆ์‹œํ”ผ 1,261ใ€๊นŒ์ง€๋งŒ ๋งํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์‚ฌ๋žŒ์ด ๋ฌป๋Š” ๊ฒƒ์€
471
+ * ใ€Œ๊ทธ๊ฒŒ ์ „๋ถ€์ธ๊ฐ€ใ€๋‹ค โ€” ์›๋ณธ์— 1,408๊ฐœ๊ฐ€ ์žˆ์—ˆ๊ณ  147๊ฐœ๋ฅผ ์˜ฎ๊ธฐ์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด ๊ทธ ์‚ฌ์‹ค์ด ์ดํ‰์˜ ์ฒซ ์ค„์—
472
+ * ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ํ’ˆ๋ชฉ 2,789 ์™€ ์ž์žฌ 1,789 ์˜ ์ฐจ์ด๋ฅผ ํ™”๋ฉด์—์„œ ์ฝ์–ด ๋‚ด์ง€ ๋ชปํ•ด ์ง์ ‘
473
+ * ๋ฌผ์—ˆ๋‹ค(2026-08-22).
474
+ *
475
+ * ๋ถ„๋ชจ๋ฅผ ์•„๋Š” ๊ฒƒ์€ **์ปค๋„ฅํ„ฐ๋ฟ์ด๋‹ค.** ๊ทธ๋ž˜์„œ ์ปค๋„ฅํ„ฐ๊ฐ€ ์‹ค์–ด ๋ณด๋‚ธ๋‹ค. ๋ณด๋‚ด์ง€ ์•Š์œผ๋ฉด ์ดํ‰์€ ๋ถ„๋ชจ ์—†์ด
476
+ * ์˜ฎ๊ธด ์ˆ˜๋งŒ ๋งํ•œ๋‹ค โ€” **์ง€์–ด๋‚ด์ง€ ์•Š๋Š”๋‹ค.**
477
+ *
478
+ * ์—ด์‡ ๋Š” ์ดํ‰์ด ์„ธ๋Š” ์ด๋ฆ„๊ณผ ๊ฐ™๋‹ค(`recipes`ยท`materials`ยท`totalInputs`ยทโ€ฆ). ๊ฐ’์€ **์›๋ณธ์„ ๋๊นŒ์ง€ ์ฝ์–ด์„œ
479
+ * ์„ผ ๊ฒƒ**์ด์–ด์•ผ ํ•œ๋‹ค. ์ž˜๋ฆฐ ์ฝ๊ธฐ์˜ ์ˆ˜๋ฅผ ์—ฌ๊ธฐ ๋‹ด์œผ๋ฉด ใ€Œ์ „๋ถ€ ์˜ฎ๊ฒผ๋‹คใ€๋Š” ๊ฑฐ์ง“์ด ๋œ๋‹ค.
480
+ */
481
+ originCounts?: Record<string, number>;
452
482
  }
453
483
  export interface IngestedTwin {
454
484
  model: any;
@@ -495,8 +525,24 @@ export interface IngestWarning {
495
525
  * ๊ทธ ๊ฐ€์ • ์œ„์— ์„ ๋‹ค) ๋Œ€์‹  **๊ทธ ์กฐํ•ฉ์„ ๋งํ•œ๋‹ค** โ€” ๊ณต๊ฐ„์— ์‹œ๊ฐ„๋Œ€๋ฅผ ์„ ์–ธํ•˜๋ฉด ์‚ฌ๋ผ์ง€๋Š” ๊ฒฝ๊ณ ๋‹ค.
496
526
  */
497
527
  export declare const generationWithoutTimeZone: (spaceId: string, equipmentId: string) => IngestWarning;
498
- /** ์ปค๋„ ์นดํƒˆ๋กœ๊ทธ๊ฐ€ ๋ชจ๋ฅด๋Š” ์ž๋ฆฌ ํƒ€์ž… โ€” ๊ทธ ์ž๋ฆฌ๋Š” ํ๋ฆ„์— ์ฐธ์—ฌํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค. */
528
+ /**
529
+ * ์•„๋ฌด๋„ ์„ ์–ธํ•˜์ง€ ์•Š์€ ์ž๋ฆฌ ํƒ€์ž… โ€” ๊ทธ ์ž๋ฆฌ๋Š” ํ๋ฆ„์— ์ฐธ์—ฌํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค.
530
+ *
531
+ * **๋ฌป๋Š” ์ƒ๋Œ€๋ฅผ ๊ณ ์ณค๋‹ค (2026-08-20).** ์˜ˆ์ „์—๋Š” ๋‚ด์žฅ ์นดํƒˆ๋กœ๊ทธ์—๊ฒŒ๋งŒ ๋ฌผ์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์›๋ณธ์ด ์ž๊ธฐ
532
+ * `DomainDefinition.locationTypes` ๋ฅผ ํ•จ๊ป˜ ๋ณด๋‚ด๋ฉด **๊ทธ ์„ ์–ธ์ด ์ •๋ณธ**์ด๋‹ค โ€” ์นดํƒˆ๋กœ๊ทธ๋Š” ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š”
533
+ * ๋„๋ฉ”์ธ๋“ค์˜ ๊ธฐ๋ณธ ์–ดํœ˜์ผ ๋ฟ์ด๋‹ค. ๋ฌผ์„ ์ƒ๋Œ€๊ฐ€ ํ‹€๋ ธ์œผ๋‹ˆ, ์ž๊ธฐ ์–ดํœ˜๋ฅผ ์„ ์–ธํ•œ ์›๋ณธ์€ ์ •์ƒ์ ์ธ ํƒ€์ž…๋งˆ๋‹ค
534
+ * ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›์•˜๋‹ค(F&B ๋ผ์ธ์„ ๋ถ™์ด๋ฉด ์ž๋ฆฌ๋งˆ๋‹ค ํ•˜๋‚˜์”ฉ). ์†Œ์Œ์ด ๋˜๋ฉด ์‚ฌ๋žŒ์€ ๊ฒฝ๊ณ ๋ฅผ ์ฝ์ง€ ์•Š๊ฒŒ ๋˜๊ณ , ๊ทธ๋•Œ
535
+ * ์ง„์งœ ๊ฒฝ๊ณ ๋„ ํ•จ๊ป˜ ์ฃฝ๋Š”๋‹ค.
536
+ */
499
537
  export declare const unknownLocationType: (system: string, type: string, id: string) => IngestWarning;
538
+ /**
539
+ * ์›๋ณธ์ด ํ•จ๊ป˜ ๋ณด๋‚ธ ์ž๋ฆฌ ํƒ€์ž… ์„ ์–ธ โ€” ์žˆ์œผ๋ฉด **์ด๊ฒƒ์ด ์ •๋ณธ**์ด๋‹ค.
540
+ *
541
+ * ์ปค๋„ ๊ณ„์•ฝ์˜ `DomainDefinition.locationTypes` ๋Š” `{ key, label, โ€ฆ }` ๋ชฉ๋ก์ด๋‹ค. ์ฝ์„ ์ˆ˜ ์—†๋Š” ๋ชจ์–‘์ด๋ฉด
542
+ * `undefined` ๋ฅผ ๋Œ๋ ค **์นดํƒˆ๋กœ๊ทธ๋กœ ๋–จ์–ด์ง„๋‹ค** โ€” ๋นˆ ๋ฐฐ์—ด์„ ๋Œ๋ ค์ฃผ๋ฉด ใ€Œ์•„๋ฌด ํƒ€์ž…๋„ ์„ ์–ธ๋˜์ง€ ์•Š์•˜๋‹คใ€๊ฐ€
543
+ * ๋˜์–ด ๋ชจ๋“  ์ž๋ฆฌ๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›๋Š”๋‹ค(์—†๋Š” ๊ฒƒ๊ณผ ๋ชป ์ฝ์€ ๊ฒƒ์€ ๋‹ค๋ฅด๋‹ค).
544
+ */
545
+ export declare function declaredLocationTypes(productionSpec: unknown): string[] | undefined;
500
546
  /** ์‹œ๊ฐ„๋Œ€ ๋ฌธ์ž์—ด์„ ๋ชป ์ฝ์—ˆ๋‹ค โ€” ์‹œ๊ฐ ํ•ด์„์ด UTC ๋กœ ๋–จ์–ด์ง„๋‹ค(์กฐ์šฉํžˆ ํ˜„์ง€ ์‹œ๊ฐ์œผ๋กœ ๊ฐ€์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค). */
501
547
  export declare const unknownTimezone: (timezone: string) => IngestWarning;
502
548
  /** ์ž‘์—… ์ •์˜์— ์†Œ์š”์‹œ๊ฐ„ยทํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†๋‹ค โ€” ์ปค๋„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค. */
@@ -10,6 +10,7 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.siteEquipmentAdopted = exports.knobDefaultsFilled = exports.describeWarnings = exports.connectorMappingFallback = exports.structureAdopted = exports.projectionFailed = exports.unresolvedReference = exports.noViewableRepresentation = exports.operationWithoutDuration = exports.unknownTimezone = exports.unknownLocationType = exports.generationWithoutTimeZone = void 0;
12
12
  exports.utcOffsetOf = utcOffsetOf;
13
+ exports.declaredLocationTypes = declaredLocationTypes;
13
14
  exports.masterToTwin = masterToTwin;
14
15
  exports.pickOriginSite = pickOriginSite;
15
16
  /**
@@ -44,7 +45,7 @@ function utcOffsetOf(timezone, at = new Date()) {
44
45
  return sign * (Number(m[2]) * 60 + Number(m[3] ?? 0));
45
46
  }
46
47
  catch {
47
- return undefined; // ์•Œ ์ˆ˜ ์—†๋Š” ์ด๋ฆ„ โ€” Intl ์ด ๋˜์ง„๋‹ค
48
+ return undefined; // ์•Œ ์ˆ˜ ์—†๋Š” ์ด๋ฆ„ โ€” Intl ์ด ์˜ค๋ฅ˜๋ฅผ ๋‚ธ๋‹ค
48
49
  }
49
50
  }
50
51
  /**
@@ -66,13 +67,36 @@ const generationWithoutTimeZone = (spaceId, equipmentId) => ({
66
67
  'so the generation peak will not land at local noon. Declare the space time zone (we do not guess one).'
67
68
  });
68
69
  exports.generationWithoutTimeZone = generationWithoutTimeZone;
69
- /** ์ปค๋„ ์นดํƒˆ๋กœ๊ทธ๊ฐ€ ๋ชจ๋ฅด๋Š” ์ž๋ฆฌ ํƒ€์ž… โ€” ๊ทธ ์ž๋ฆฌ๋Š” ํ๋ฆ„์— ์ฐธ์—ฌํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค. */
70
+ /**
71
+ * ์•„๋ฌด๋„ ์„ ์–ธํ•˜์ง€ ์•Š์€ ์ž๋ฆฌ ํƒ€์ž… โ€” ๊ทธ ์ž๋ฆฌ๋Š” ํ๋ฆ„์— ์ฐธ์—ฌํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค.
72
+ *
73
+ * **๋ฌป๋Š” ์ƒ๋Œ€๋ฅผ ๊ณ ์ณค๋‹ค (2026-08-20).** ์˜ˆ์ „์—๋Š” ๋‚ด์žฅ ์นดํƒˆ๋กœ๊ทธ์—๊ฒŒ๋งŒ ๋ฌผ์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์›๋ณธ์ด ์ž๊ธฐ
74
+ * `DomainDefinition.locationTypes` ๋ฅผ ํ•จ๊ป˜ ๋ณด๋‚ด๋ฉด **๊ทธ ์„ ์–ธ์ด ์ •๋ณธ**์ด๋‹ค โ€” ์นดํƒˆ๋กœ๊ทธ๋Š” ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š”
75
+ * ๋„๋ฉ”์ธ๋“ค์˜ ๊ธฐ๋ณธ ์–ดํœ˜์ผ ๋ฟ์ด๋‹ค. ๋ฌผ์„ ์ƒ๋Œ€๊ฐ€ ํ‹€๋ ธ์œผ๋‹ˆ, ์ž๊ธฐ ์–ดํœ˜๋ฅผ ์„ ์–ธํ•œ ์›๋ณธ์€ ์ •์ƒ์ ์ธ ํƒ€์ž…๋งˆ๋‹ค
76
+ * ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›์•˜๋‹ค(F&B ๋ผ์ธ์„ ๋ถ™์ด๋ฉด ์ž๋ฆฌ๋งˆ๋‹ค ํ•˜๋‚˜์”ฉ). ์†Œ์Œ์ด ๋˜๋ฉด ์‚ฌ๋žŒ์€ ๊ฒฝ๊ณ ๋ฅผ ์ฝ์ง€ ์•Š๊ฒŒ ๋˜๊ณ , ๊ทธ๋•Œ
77
+ * ์ง„์งœ ๊ฒฝ๊ณ ๋„ ํ•จ๊ป˜ ์ฃฝ๋Š”๋‹ค.
78
+ */
70
79
  const unknownLocationType = (system, type, id) => ({
71
80
  code: 'unknown-location-type',
72
81
  params: { system, type, id },
73
- message: `"${type}" is not a known ${system} location type (${id}) โ€” the kernel may not include this location in the flow`
82
+ message: `"${type}" is declared nowhere for ${system} (${id}) โ€” neither in the master's own locationTypes nor in the built-in ` +
83
+ `catalog, so the kernel may not include this location in the flow`
74
84
  });
75
85
  exports.unknownLocationType = unknownLocationType;
86
+ /**
87
+ * ์›๋ณธ์ด ํ•จ๊ป˜ ๋ณด๋‚ธ ์ž๋ฆฌ ํƒ€์ž… ์„ ์–ธ โ€” ์žˆ์œผ๋ฉด **์ด๊ฒƒ์ด ์ •๋ณธ**์ด๋‹ค.
88
+ *
89
+ * ์ปค๋„ ๊ณ„์•ฝ์˜ `DomainDefinition.locationTypes` ๋Š” `{ key, label, โ€ฆ }` ๋ชฉ๋ก์ด๋‹ค. ์ฝ์„ ์ˆ˜ ์—†๋Š” ๋ชจ์–‘์ด๋ฉด
90
+ * `undefined` ๋ฅผ ๋Œ๋ ค **์นดํƒˆ๋กœ๊ทธ๋กœ ๋–จ์–ด์ง„๋‹ค** โ€” ๋นˆ ๋ฐฐ์—ด์„ ๋Œ๋ ค์ฃผ๋ฉด ใ€Œ์•„๋ฌด ํƒ€์ž…๋„ ์„ ์–ธ๋˜์ง€ ์•Š์•˜๋‹คใ€๊ฐ€
91
+ * ๋˜์–ด ๋ชจ๋“  ์ž๋ฆฌ๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›๋Š”๋‹ค(์—†๋Š” ๊ฒƒ๊ณผ ๋ชป ์ฝ์€ ๊ฒƒ์€ ๋‹ค๋ฅด๋‹ค).
92
+ */
93
+ function declaredLocationTypes(productionSpec) {
94
+ const list = productionSpec?.definition?.locationTypes;
95
+ if (!Array.isArray(list) || !list.length)
96
+ return undefined;
97
+ const keys = list.map((t) => (typeof t === 'string' ? t : t?.key)).filter((k) => typeof k === 'string' && k);
98
+ return keys.length ? keys : undefined;
99
+ }
76
100
  /** ์‹œ๊ฐ„๋Œ€ ๋ฌธ์ž์—ด์„ ๋ชป ์ฝ์—ˆ๋‹ค โ€” ์‹œ๊ฐ ํ•ด์„์ด UTC ๋กœ ๋–จ์–ด์ง„๋‹ค(์กฐ์šฉํžˆ ํ˜„์ง€ ์‹œ๊ฐ์œผ๋กœ ๊ฐ€์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค). */
77
101
  const unknownTimezone = (timezone) => ({
78
102
  code: 'unknown-timezone',
@@ -177,7 +201,11 @@ exports.siteEquipmentAdopted = siteEquipmentAdopted;
177
201
  function masterToTwin(master, catalog) {
178
202
  /* ์ปค๋„ฅํ„ฐ๊ฐ€ ์˜ฎ๊ธฐ๋ฉฐ ์•Œ๊ฒŒ ๋œ ๊ฒƒ์„ ๋จผ์ € ์‹ฃ๋Š”๋‹ค โ€” ์›์ธ์ด ์•ž์— ์˜ค๋Š” ํŽธ์ด ์ฝ๊ธฐ ์‰ฝ๋‹ค(๋Œ€์ฒด๊ฐ€ ๋จผ์ €, ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ ๋’ค). */
179
203
  const warnings = [...(master.warnings ?? [])];
180
- const known = catalog?.[master.system]?.locationTypes ?? [];
204
+ /*
205
+ * ์›๋ณธ์ด ์ž๊ธฐ ์–ดํœ˜๋ฅผ ์„ ์–ธํ–ˆ์œผ๋ฉด **๊ทธ๊ฒƒ์— ๋Œ€์กฐํ•œ๋‹ค.** ๋‚ด์žฅ ์นดํƒˆ๋กœ๊ทธ๋Š” ๊ทธ ์„ ์–ธ์ด ์—†์„ ๋•Œ์˜ ๊ธฐ๋ณธ์ด๋‹ค โ€”
206
+ * ์ƒ๋Œ€๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š์œผ๋ฉด, ์ž๊ธฐ ์–ดํœ˜๋ฅผ ์„ ์–ธํ•œ ์›๋ณธ์ด ์ •์ƒ์ ์ธ ์ž๋ฆฌ๋งˆ๋‹ค ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›๋Š”๋‹ค.
207
+ */
208
+ const known = declaredLocationTypes(master.productionSpec) ?? catalog?.[master.system]?.locationTypes ?? [];
181
209
  const locationEntries = master.locations.filter(l => l.role === 'location' || l.role === 'both');
182
210
  const areaLocs = master.locations.filter(l => l.role === 'area' || l.role === 'both');
183
211
  const locations = locationEntries.map(l => {
@@ -276,6 +304,8 @@ function masterToTwin(master, catalog) {
276
304
  locations, equipment, layout, spaceId,
277
305
  /* ์ƒ์‚ฐ ์ •์˜๋„ ์†๋Œ€์ง€ ์•Š๊ณ  ํ†ต๊ณผ โ€” ๊ฒฝ๊ณ„์—์„œ ์–ดํœ˜๋ฅผ ๊ฐˆ์ง€ ์•Š๋Š”๋‹ค(์ปค๋„์ด ๊ทธ๋Œ€๋กœ ์†Œ๋น„ํ•œ๋‹ค). */
278
306
  ...(master.productionSpec ? { productionSpec: master.productionSpec } : {}),
307
+ /* ์ •์ฒด์„ฑ ์„ ์–ธ๋„ ๊ทธ๋Œ€๋กœ ํ†ต๊ณผํ•œ๋‹ค โ€” ์—†์œผ๋ฉด ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค(์ปค๋„์ด ๋ ˆ๊ฑฐ์‹œ ์ƒ์ˆ˜๋กœ ๋–จ์–ด์ง„๋‹ค). */
308
+ ...(master.identity ? { identity: master.identity } : {}),
279
309
  ...(materialDefinitions?.length ? { materialDefinitions } : {}),
280
310
  ...(materialClasses?.length ? { materialClasses } : {}),
281
311
  ...(utcOffsetMinutes !== undefined ? { utcOffsetMinutes } : {}),